Let bunker_pubkey be different from user pubkey

This commit is contained in:
Alex Gleason 2024-10-26 18:37:26 -05:00
parent d28db38ba9
commit 42f5e316a8
No known key found for this signature in database
GPG key ID: 7211D1F99744FBB7
5 changed files with 61 additions and 21 deletions

View file

@ -111,7 +111,7 @@ const revokeTokenController: AppController = async (c) => {
}; };
async function getToken( 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}`> { ): Promise<`token1${string}`> {
const kysely = await Storages.kysely(); const kysely = await Storages.kysely();
const { token, hash } = await generateToken(); const { token, hash } = await generateToken();
@ -119,17 +119,19 @@ async function getToken(
const nip46Seckey = generateSecretKey(); const nip46Seckey = generateSecretKey();
const signer = new NConnectSigner({ const signer = new NConnectSigner({
pubkey, pubkey: bunkerPubkey,
signer: new NSecSigner(nip46Seckey), signer: new NSecSigner(nip46Seckey),
relay: await Storages.pubsub(), // TODO: Use the relays from the request. relay: await Storages.pubsub(), // TODO: Use the relays from the request.
timeout: 60_000, timeout: 60_000,
}); });
await signer.connect(secret); await signer.connect(secret);
const userPubkey = await signer.getPublicKey();
await kysely.insertInto('auth_tokens').values({ await kysely.insertInto('auth_tokens').values({
token_hash: hash, token_hash: hash,
pubkey, pubkey: userPubkey,
bunker_pubkey: bunkerPubkey,
nip46_sk_enc: await aesEncrypt(Conf.seckey, nip46Seckey), nip46_sk_enc: await aesEncrypt(Conf.seckey, nip46Seckey),
nip46_relays: relays, nip46_relays: relays,
created_at: new Date(), created_at: new Date(),

View file

@ -37,6 +37,7 @@ interface EventStatsRow {
interface AuthTokenRow { interface AuthTokenRow {
token_hash: Uint8Array; token_hash: Uint8Array;
pubkey: string; pubkey: string;
bunker_pubkey: string;
nip46_sk_enc: Uint8Array; nip46_sk_enc: Uint8Array;
nip46_relays: string[]; nip46_relays: string[];
created_at: Date; created_at: Date;

View file

@ -0,0 +1,22 @@
import { Kysely } from 'kysely';
export async function up(db: Kysely<any>): Promise<void> {
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<any>): Promise<void> {
await db.schema
.alterTable('auth_tokens')
.dropColumn('bunker_pubkey')
.execute();
}

View file

@ -26,15 +26,23 @@ export const signerMiddleware: AppMiddleware = async (c, next) => {
const kysely = await Storages.kysely(); const kysely = await Storages.kysely();
const tokenHash = await getTokenHash(bech32 as `token1${string}`); 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') .selectFrom('auth_tokens')
.select(['pubkey', 'nip46_sk_enc', 'nip46_relays']) .select(['pubkey', 'bunker_pubkey', 'nip46_sk_enc', 'nip46_relays'])
.where('token_hash', '=', tokenHash) .where('token_hash', '=', tokenHash)
.executeTakeFirstOrThrow(); .executeTakeFirstOrThrow();
const nep46Seckey = await aesDecrypt(Conf.seckey, nip46_sk_enc); 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 { } catch {
throw new HTTPException(401); throw new HTTPException(401);
} }

View file

@ -4,6 +4,13 @@ import { NConnectSigner, NostrEvent, NostrSigner } from '@nostrify/nostrify';
import { Storages } from '@/storages.ts'; import { Storages } from '@/storages.ts';
interface ConnectSignerOpts {
bunkerPubkey: string;
userPubkey: string;
signer: NostrSigner;
relays?: string[];
}
/** /**
* NIP-46 signer. * NIP-46 signer.
* *
@ -12,13 +19,13 @@ 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, signer: NostrSigner, private relays?: string[]) { constructor(private opts: ConnectSignerOpts) {
this.signer = this.init(signer); this.signer = this.init(opts.signer);
} }
async init(signer: NostrSigner): Promise<NConnectSigner> { async init(signer: NostrSigner): Promise<NConnectSigner> {
return new NConnectSigner({ 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) // 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, signer,
@ -30,8 +37,8 @@ export class ConnectSigner implements NostrSigner {
const signer = await this.signer; const signer = await this.signer;
try { try {
return await signer.signEvent(event); return await signer.signEvent(event);
} catch (e: any) { } catch (e) {
if (e.name === 'AbortError') { if (e instanceof Error && e.name === 'AbortError') {
throw new HTTPException(408, { message: 'The event was not signed quickly enough' }); throw new HTTPException(408, { message: 'The event was not signed quickly enough' });
} else { } else {
throw e; throw e;
@ -44,8 +51,8 @@ export class ConnectSigner implements NostrSigner {
const signer = await this.signer; const signer = await this.signer;
try { try {
return await signer.nip04.encrypt(pubkey, plaintext); return await signer.nip04.encrypt(pubkey, plaintext);
} catch (e: any) { } catch (e) {
if (e.name === 'AbortError') { if (e instanceof Error && e.name === 'AbortError') {
throw new HTTPException(408, { throw new HTTPException(408, {
message: 'Text was not encrypted quickly enough', message: 'Text was not encrypted quickly enough',
}); });
@ -59,8 +66,8 @@ export class ConnectSigner implements NostrSigner {
const signer = await this.signer; const signer = await this.signer;
try { try {
return await signer.nip04.decrypt(pubkey, ciphertext); return await signer.nip04.decrypt(pubkey, ciphertext);
} catch (e: any) { } catch (e) {
if (e.name === 'AbortError') { if (e instanceof Error && e.name === 'AbortError') {
throw new HTTPException(408, { throw new HTTPException(408, {
message: 'Text was not decrypted quickly enough', message: 'Text was not decrypted quickly enough',
}); });
@ -76,8 +83,8 @@ export class ConnectSigner implements NostrSigner {
const signer = await this.signer; const signer = await this.signer;
try { try {
return await signer.nip44.encrypt(pubkey, plaintext); return await signer.nip44.encrypt(pubkey, plaintext);
} catch (e: any) { } catch (e) {
if (e.name === 'AbortError') { if (e instanceof Error && e.name === 'AbortError') {
throw new HTTPException(408, { throw new HTTPException(408, {
message: 'Text was not encrypted quickly enough', message: 'Text was not encrypted quickly enough',
}); });
@ -91,8 +98,8 @@ export class ConnectSigner implements NostrSigner {
const signer = await this.signer; const signer = await this.signer;
try { try {
return await signer.nip44.decrypt(pubkey, ciphertext); return await signer.nip44.decrypt(pubkey, ciphertext);
} catch (e: any) { } catch (e) {
if (e.name === 'AbortError') { if (e instanceof Error && e.name === 'AbortError') {
throw new HTTPException(408, { throw new HTTPException(408, {
message: 'Text was not decrypted quickly enough', message: 'Text was not decrypted quickly enough',
}); });
@ -105,12 +112,12 @@ export class ConnectSigner implements NostrSigner {
// Prevent unnecessary NIP-46 round-trips. // Prevent unnecessary NIP-46 round-trips.
async getPublicKey(): Promise<string> { async getPublicKey(): Promise<string> {
return this.pubkey; return this.opts.userPubkey;
} }
/** Get the user's relays if they passed in an `nprofile` auth token. */ /** Get the user's relays if they passed in an `nprofile` auth token. */
async getRelays(): Promise<Record<string, { read: boolean; write: boolean }>> { async getRelays(): Promise<Record<string, { read: boolean; write: boolean }>> {
return this.relays?.reduce<Record<string, { read: boolean; write: boolean }>>((acc, relay) => { return this.opts.relays?.reduce<Record<string, { read: boolean; write: boolean }>>((acc, relay) => {
acc[relay] = { read: true, write: true }; acc[relay] = { read: true, write: true };
return acc; return acc;
}, {}) ?? {}; }, {}) ?? {};