signerMiddleware: accept token1 bech32's

This commit is contained in:
Alex Gleason 2024-05-26 20:18:49 -05:00
parent 0141a732c3
commit b1a497b127
No known key found for this signature in database
GPG Key ID: 7211D1F99744FBB7
4 changed files with 63 additions and 26 deletions

View File

@ -3,7 +3,7 @@ import { HTTPException } from 'hono';
import { type AppContext, type AppMiddleware } from '@/app.ts'; import { type AppContext, type AppMiddleware } from '@/app.ts';
import { findUser, User } from '@/db/users.ts'; import { findUser, User } from '@/db/users.ts';
import { ConnectSigner } from '@/signers/ConnectSigner.ts'; import { ReadOnlySigner } from '@/signers/ReadOnlySigner.ts';
import { localRequest } from '@/utils/api.ts'; import { localRequest } from '@/utils/api.ts';
import { import {
buildAuthEventTemplate, buildAuthEventTemplate,
@ -22,7 +22,7 @@ function auth98Middleware(opts: ParseAuthRequestOpts = {}): AppMiddleware {
const result = await parseAuthRequest(req, opts); const result = await parseAuthRequest(req, opts);
if (result.success) { if (result.success) {
c.set('signer', new ConnectSigner(result.data.pubkey)); c.set('signer', new ReadOnlySigner(result.data.pubkey));
c.set('proof', result.data); c.set('proof', result.data);
} }
@ -70,7 +70,8 @@ function withProof(
opts?: ParseAuthRequestOpts, opts?: ParseAuthRequestOpts,
): AppMiddleware { ): AppMiddleware {
return async (c, next) => { return async (c, next) => {
const pubkey = await c.get('signer')?.getPublicKey(); const signer = c.get('signer');
const pubkey = await signer?.getPublicKey();
const proof = c.get('proof') || await obtainProof(c, opts); const proof = c.get('proof') || await obtainProof(c, opts);
// Prevent people from accidentally using the wrong account. This has no other security implications. // Prevent people from accidentally using the wrong account. This has no other security implications.
@ -79,8 +80,12 @@ function withProof(
} }
if (proof) { if (proof) {
c.set('signer', new ConnectSigner(proof.pubkey));
c.set('proof', proof); c.set('proof', proof);
if (!signer) {
c.set('signer', new ReadOnlySigner(proof.pubkey));
}
await handler(c, proof, next); await handler(c, proof, next);
} else { } else {
throw new HTTPException(401, { message: 'No proof' }); throw new HTTPException(401, { message: 'No proof' });

View File

@ -1,11 +1,11 @@
import { NSecSigner } from '@nostrify/nostrify'; import { NSecSigner } from '@nostrify/nostrify';
import { Stickynotes } from '@soapbox/stickynotes';
import { nip19 } from 'nostr-tools'; import { nip19 } from 'nostr-tools';
import { AppMiddleware } from '@/app.ts'; import { AppMiddleware } from '@/app.ts';
import { ConnectSigner } from '@/signers/ConnectSigner.ts'; import { ConnectSigner } from '@/signers/ConnectSigner.ts';
import { ReadOnlySigner } from '@/signers/ReadOnlySigner.ts';
const console = new Stickynotes('ditto:signerMiddleware'); import { HTTPException } from 'hono';
import { DittoDB } from '@/db/DittoDB.ts';
/** We only accept "Bearer" type. */ /** We only accept "Bearer" type. */
const BEARER_REGEX = new RegExp(`^Bearer (${nip19.BECH32_REGEX.source})$`); const BEARER_REGEX = new RegExp(`^Bearer (${nip19.BECH32_REGEX.source})$`);
@ -18,22 +18,38 @@ export const signerMiddleware: AppMiddleware = async (c, next) => {
if (match) { if (match) {
const [_, bech32] = match; const [_, bech32] = match;
if (bech32.startsWith('token1')) {
try {
const kysely = await DittoDB.getInstance();
const { user_pubkey, server_seckey, relays } = await kysely
.selectFrom('connections')
.select(['user_pubkey', 'server_seckey', 'relays'])
.where('api_token', '=', bech32)
.executeTakeFirstOrThrow();
c.set('signer', new ConnectSigner(user_pubkey, new NSecSigner(server_seckey), JSON.parse(relays)));
} catch {
throw new HTTPException(401);
}
} else {
try { try {
const decoded = nip19.decode(bech32!); const decoded = nip19.decode(bech32!);
switch (decoded.type) { switch (decoded.type) {
case 'npub': case 'npub':
c.set('signer', new ConnectSigner(decoded.data)); c.set('signer', new ReadOnlySigner(decoded.data));
break; break;
case 'nprofile': case 'nprofile':
c.set('signer', new ConnectSigner(decoded.data.pubkey, decoded.data.relays)); c.set('signer', new ReadOnlySigner(decoded.data.pubkey));
break; break;
case 'nsec': case 'nsec':
c.set('signer', new NSecSigner(decoded.data)); c.set('signer', new NSecSigner(decoded.data));
break; break;
} }
} catch { } catch {
console.debug('The user is not logged in'); throw new HTTPException(401);
}
} }
} }

View File

@ -1,7 +1,6 @@
// deno-lint-ignore-file require-await // deno-lint-ignore-file require-await
import { NConnectSigner, NostrEvent, NostrSigner } from '@nostrify/nostrify'; import { NConnectSigner, NostrEvent, NostrSigner } from '@nostrify/nostrify';
import { AdminSigner } from '@/signers/AdminSigner.ts';
import { Storages } from '@/storages.ts'; import { Storages } from '@/storages.ts';
/** /**
@ -12,16 +11,16 @@ import { Storages } from '@/storages.ts';
export class ConnectSigner implements NostrSigner { export class ConnectSigner implements NostrSigner {
private signer: Promise<NConnectSigner>; private signer: Promise<NConnectSigner>;
constructor(private pubkey: string, private relays?: string[]) { constructor(private pubkey: string, signer: NostrSigner, private relays?: string[]) {
this.signer = this.init(); this.signer = this.init(signer);
} }
async init(): Promise<NConnectSigner> { async init(signer: NostrSigner): Promise<NConnectSigner> {
return new NConnectSigner({ return new NConnectSigner({
pubkey: this.pubkey, pubkey: this.pubkey,
// TODO: use a remote relay for `nprofile` signing (if present and `Conf.relay` isn't already in the list) // TODO: use a remote relay for `nprofile` signing (if present and `Conf.relay` isn't already in the list)
relay: await Storages.pubsub(), relay: await Storages.pubsub(),
signer: new AdminSigner(), signer,
timeout: 60000, timeout: 60000,
}); });
} }

View File

@ -0,0 +1,17 @@
// deno-lint-ignore-file require-await
import { NostrEvent, NostrSigner } from '@nostrify/nostrify';
import { HTTPException } from 'hono';
export class ReadOnlySigner implements NostrSigner {
constructor(private pubkey: string) {}
async signEvent(): Promise<NostrEvent> {
throw new HTTPException(401, {
message: "Can't sign events with just an npub",
});
}
async getPublicKey(): Promise<string> {
return this.pubkey;
}
}