diff --git a/src/controllers/api/oauth.ts b/src/controllers/api/oauth.ts index eb5aee2d..334b4f0a 100644 --- a/src/controllers/api/oauth.ts +++ b/src/controllers/api/oauth.ts @@ -111,7 +111,7 @@ const revokeTokenController: AppController = async (c) => { }; async function getToken( - { pubkey, secret, relays = [] }: { pubkey: string; secret?: string; relays?: string[] }, + { pubkey: bunkerPubkey, secret, relays = [] }: { pubkey: string; secret?: string; relays?: string[] }, ): Promise<`token1${string}`> { const kysely = await Storages.kysely(); const { token, hash } = await generateToken(); @@ -119,17 +119,19 @@ async function getToken( const nip46Seckey = generateSecretKey(); const signer = new NConnectSigner({ - pubkey, + pubkey: bunkerPubkey, signer: new NSecSigner(nip46Seckey), relay: await Storages.pubsub(), // TODO: Use the relays from the request. timeout: 60_000, }); await signer.connect(secret); + const userPubkey = await signer.getPublicKey(); await kysely.insertInto('auth_tokens').values({ token_hash: hash, - pubkey, + pubkey: userPubkey, + bunker_pubkey: bunkerPubkey, nip46_sk_enc: await aesEncrypt(Conf.seckey, nip46Seckey), nip46_relays: relays, created_at: new Date(), diff --git a/src/db/DittoTables.ts b/src/db/DittoTables.ts index 6046cf71..ec21170e 100644 --- a/src/db/DittoTables.ts +++ b/src/db/DittoTables.ts @@ -37,6 +37,7 @@ interface EventStatsRow { interface AuthTokenRow { token_hash: Uint8Array; pubkey: string; + bunker_pubkey: string; nip46_sk_enc: Uint8Array; nip46_relays: string[]; created_at: Date; diff --git a/src/db/migrations/040_add_bunker_pubkey.ts b/src/db/migrations/040_add_bunker_pubkey.ts new file mode 100644 index 00000000..58ab0a5e --- /dev/null +++ b/src/db/migrations/040_add_bunker_pubkey.ts @@ -0,0 +1,22 @@ +import { Kysely } from 'kysely'; + +export async function up(db: Kysely): Promise { + await db.schema + .alterTable('auth_tokens') + .addColumn('bunker_pubkey', 'char(64)') + .execute(); + + await db.updateTable('auth_tokens').set((eb) => ({ bunker_pubkey: eb.ref('pubkey') })).execute(); + + await db.schema + .alterTable('auth_tokens') + .alterColumn('bunker_pubkey', (col) => col.setNotNull()) + .execute(); +} + +export async function down(db: Kysely): Promise { + await db.schema + .alterTable('auth_tokens') + .dropColumn('bunker_pubkey') + .execute(); +} diff --git a/src/middleware/signerMiddleware.ts b/src/middleware/signerMiddleware.ts index b4cab1ec..8fca06a3 100644 --- a/src/middleware/signerMiddleware.ts +++ b/src/middleware/signerMiddleware.ts @@ -26,15 +26,23 @@ export const signerMiddleware: AppMiddleware = async (c, next) => { const kysely = await Storages.kysely(); const tokenHash = await getTokenHash(bech32 as `token1${string}`); - const { pubkey, nip46_sk_enc, nip46_relays } = await kysely + const { pubkey: userPubkey, bunker_pubkey: bunkerPubkey, nip46_sk_enc, nip46_relays } = await kysely .selectFrom('auth_tokens') - .select(['pubkey', 'nip46_sk_enc', 'nip46_relays']) + .select(['pubkey', 'bunker_pubkey', 'nip46_sk_enc', 'nip46_relays']) .where('token_hash', '=', tokenHash) .executeTakeFirstOrThrow(); const nep46Seckey = await aesDecrypt(Conf.seckey, nip46_sk_enc); - c.set('signer', new ConnectSigner(pubkey, new NSecSigner(nep46Seckey), nip46_relays)); + c.set( + 'signer', + new ConnectSigner({ + bunkerPubkey, + userPubkey, + signer: new NSecSigner(nep46Seckey), + relays: nip46_relays, + }), + ); } catch { throw new HTTPException(401); } diff --git a/src/signers/ConnectSigner.ts b/src/signers/ConnectSigner.ts index 26a9cbc9..de1b5fc9 100644 --- a/src/signers/ConnectSigner.ts +++ b/src/signers/ConnectSigner.ts @@ -4,6 +4,13 @@ import { NConnectSigner, NostrEvent, NostrSigner } from '@nostrify/nostrify'; import { Storages } from '@/storages.ts'; +interface ConnectSignerOpts { + bunkerPubkey: string; + userPubkey: string; + signer: NostrSigner; + relays?: string[]; +} + /** * NIP-46 signer. * @@ -12,13 +19,13 @@ import { Storages } from '@/storages.ts'; export class ConnectSigner implements NostrSigner { private signer: Promise; - constructor(private pubkey: string, signer: NostrSigner, private relays?: string[]) { - this.signer = this.init(signer); + constructor(private opts: ConnectSignerOpts) { + this.signer = this.init(opts.signer); } async init(signer: NostrSigner): Promise { return new NConnectSigner({ - pubkey: this.pubkey, + pubkey: this.opts.bunkerPubkey, // TODO: use a remote relay for `nprofile` signing (if present and `Conf.relay` isn't already in the list) relay: await Storages.pubsub(), signer, @@ -30,8 +37,8 @@ export class ConnectSigner implements NostrSigner { const signer = await this.signer; try { return await signer.signEvent(event); - } catch (e: any) { - if (e.name === 'AbortError') { + } catch (e) { + if (e instanceof Error && e.name === 'AbortError') { throw new HTTPException(408, { message: 'The event was not signed quickly enough' }); } else { throw e; @@ -44,8 +51,8 @@ export class ConnectSigner implements NostrSigner { const signer = await this.signer; try { return await signer.nip04.encrypt(pubkey, plaintext); - } catch (e: any) { - if (e.name === 'AbortError') { + } catch (e) { + if (e instanceof Error && e.name === 'AbortError') { throw new HTTPException(408, { message: 'Text was not encrypted quickly enough', }); @@ -59,8 +66,8 @@ export class ConnectSigner implements NostrSigner { const signer = await this.signer; try { return await signer.nip04.decrypt(pubkey, ciphertext); - } catch (e: any) { - if (e.name === 'AbortError') { + } catch (e) { + if (e instanceof Error && e.name === 'AbortError') { throw new HTTPException(408, { message: 'Text was not decrypted quickly enough', }); @@ -76,8 +83,8 @@ export class ConnectSigner implements NostrSigner { const signer = await this.signer; try { return await signer.nip44.encrypt(pubkey, plaintext); - } catch (e: any) { - if (e.name === 'AbortError') { + } catch (e) { + if (e instanceof Error && e.name === 'AbortError') { throw new HTTPException(408, { message: 'Text was not encrypted quickly enough', }); @@ -91,8 +98,8 @@ export class ConnectSigner implements NostrSigner { const signer = await this.signer; try { return await signer.nip44.decrypt(pubkey, ciphertext); - } catch (e: any) { - if (e.name === 'AbortError') { + } catch (e) { + if (e instanceof Error && e.name === 'AbortError') { throw new HTTPException(408, { message: 'Text was not decrypted quickly enough', }); @@ -105,12 +112,12 @@ export class ConnectSigner implements NostrSigner { // Prevent unnecessary NIP-46 round-trips. async getPublicKey(): Promise { - return this.pubkey; + return this.opts.userPubkey; } /** Get the user's relays if they passed in an `nprofile` auth token. */ async getRelays(): Promise> { - return this.relays?.reduce>((acc, relay) => { + return this.opts.relays?.reduce>((acc, relay) => { acc[relay] = { read: true, write: true }; return acc; }, {}) ?? {};