From 4c41ce8c0a56c26b5e9e0b9cff0f01c085e1397c Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 2 Sep 2023 13:43:01 -0500 Subject: [PATCH 01/10] @/nip05.ts --> @/utils/nip05.ts --- src/controllers/api/search.ts | 2 +- src/transformers/nostr-to-mastoapi.ts | 2 +- src/utils.ts | 2 +- src/{ => utils}/nip05.ts | 0 4 files changed, 3 insertions(+), 3 deletions(-) rename src/{ => utils}/nip05.ts (100%) diff --git a/src/controllers/api/search.ts b/src/controllers/api/search.ts index 6ba6183f..ee8497ea 100644 --- a/src/controllers/api/search.ts +++ b/src/controllers/api/search.ts @@ -2,11 +2,11 @@ import { AppController } from '@/app.ts'; import * as eventsDB from '@/db/events.ts'; import { type Event, type Filter, nip19, z } from '@/deps.ts'; import * as mixer from '@/mixer.ts'; -import { lookupNip05Cached } from '@/nip05.ts'; import { booleanParamSchema } from '@/schema.ts'; import { nostrIdSchema } from '@/schemas/nostr.ts'; import { toAccount, toStatus } from '@/transformers/nostr-to-mastoapi.ts'; import { dedupeEvents, Time } from '@/utils.ts'; +import { lookupNip05Cached } from '@/utils/nip05.ts'; /** Matches NIP-05 names with or without an @ in front. */ const ACCT_REGEX = /^@?(?:([\w.+-]+)@)?([\w.-]+)$/; diff --git a/src/transformers/nostr-to-mastoapi.ts b/src/transformers/nostr-to-mastoapi.ts index 8a6384ad..ca8c4113 100644 --- a/src/transformers/nostr-to-mastoapi.ts +++ b/src/transformers/nostr-to-mastoapi.ts @@ -3,12 +3,12 @@ import { isCWTag } from 'https://gitlab.com/soapbox-pub/mostr/-/raw/c67064aee5ad import { Conf } from '@/config.ts'; import * as eventsDB from '@/db/events.ts'; import { type Event, findReplyTag, lodash, nip19, sanitizeHtml, TTLCache, unfurl, z } from '@/deps.ts'; -import { verifyNip05Cached } from '@/nip05.ts'; import { getMediaLinks, type MediaLink, parseNoteContent } from '@/note.ts'; import { getAuthor, getFollowedPubkeys, getFollows } from '@/queries.ts'; import { emojiTagSchema, filteredArray } from '@/schema.ts'; import { jsonMetaContentSchema } from '@/schemas/nostr.ts'; import { isFollowing, type Nip05, nostrDate, parseNip05, Time } from '@/utils.ts'; +import { verifyNip05Cached } from '@/utils/nip05.ts'; const DEFAULT_AVATAR = 'https://gleasonator.com/images/avi.png'; const DEFAULT_BANNER = 'https://gleasonator.com/images/banner.png'; diff --git a/src/utils.ts b/src/utils.ts index d6d24d8c..525c47f8 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,6 +1,6 @@ import { type Event, nip19, z } from '@/deps.ts'; -import { lookupNip05Cached } from '@/nip05.ts'; import { getAuthor } from '@/queries.ts'; +import { lookupNip05Cached } from '@/utils/nip05.ts'; /** Get the current time in Nostr format. */ const nostrNow = (): number => Math.floor(Date.now() / 1000); diff --git a/src/nip05.ts b/src/utils/nip05.ts similarity index 100% rename from src/nip05.ts rename to src/utils/nip05.ts From 61f5acc937ef8eeaa291327b27f9fb4de19d9a8f Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 2 Sep 2023 18:32:01 -0500 Subject: [PATCH 02/10] nip98: add a dedicated nip98 module, refactor auth98.ts --- src/middleware/auth98.ts | 44 ++++++-------------------------------- src/utils.ts | 2 +- src/utils/nip98.ts | 46 ++++++++++++++++++++++++++++++++++++++++ src/utils/web.ts | 18 +++++++++++++++- 4 files changed, 71 insertions(+), 39 deletions(-) create mode 100644 src/utils/nip98.ts diff --git a/src/middleware/auth98.ts b/src/middleware/auth98.ts index 003ad488..4ef54617 100644 --- a/src/middleware/auth98.ts +++ b/src/middleware/auth98.ts @@ -1,50 +1,20 @@ import { type AppMiddleware } from '@/app.ts'; -import { Conf } from '@/config.ts'; -import { type Event, HTTPException } from '@/deps.ts'; -import { decode64Schema, jsonSchema } from '@/schema.ts'; -import { signedEventSchema } from '@/schemas/nostr.ts'; -import { eventAge, findTag, sha256, Time } from '@/utils.ts'; - -const decodeEventSchema = decode64Schema.pipe(jsonSchema).pipe(signedEventSchema); - -interface Auth98Opts { - timeout?: number; -} +import { HTTPException } from '@/deps.ts'; +import { parseAuthRequest, type ParseAuthRequestOpts } from '@/utils/nip98.ts'; +import { localRequest } from '@/utils/web.ts'; /** * NIP-98 auth. * https://github.com/nostr-protocol/nips/blob/master/98.md */ -function auth98(opts: Auth98Opts = {}): AppMiddleware { +function auth98(opts: ParseAuthRequestOpts = {}): AppMiddleware { return async (c, next) => { - const authHeader = c.req.headers.get('authorization'); - const base64 = authHeader?.match(/^Nostr (.+)$/)?.[1]; - const { timeout = Time.minutes(1) } = opts; - - const schema = decodeEventSchema - .refine((event) => event.kind === 27235) - .refine((event) => eventAge(event) < timeout) - .refine((event) => findTag(event.tags, 'method')?.[1] === c.req.method) - .refine((event) => { - const url = findTag(event.tags, 'u')?.[1]; - try { - return url === Conf.local(c.req.url); - } catch (_e) { - return false; - } - }) - .refine(async (event) => { - const body = await c.req.raw.clone().text(); - if (!body) return true; - const hash = findTag(event.tags, 'payload')?.[1]; - return hash === await sha256(body); - }); - - const result = await schema.safeParseAsync(base64); + const req = localRequest(c); + const result = await parseAuthRequest(req, opts); if (result.success) { c.set('pubkey', result.data.pubkey); - c.set('proof', result.data as Event<27235>); + c.set('proof', result.data); } await next(); diff --git a/src/utils.ts b/src/utils.ts index 525c47f8..ede1e9c5 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -67,7 +67,7 @@ async function lookupAccount(value: string): Promise | undefined> { /** Return the event's age in milliseconds. */ function eventAge(event: Event): number { - return new Date().getTime() - nostrDate(event.created_at).getTime(); + return Date.now() - nostrDate(event.created_at).getTime(); } function findTag(tags: string[][], name: string): string[] | undefined { diff --git a/src/utils/nip98.ts b/src/utils/nip98.ts new file mode 100644 index 00000000..123a6ef2 --- /dev/null +++ b/src/utils/nip98.ts @@ -0,0 +1,46 @@ +import { type Event } from '@/deps.ts'; +import { decode64Schema, jsonSchema } from '@/schema.ts'; +import { signedEventSchema } from '@/schemas/nostr.ts'; +import { eventAge, findTag, sha256 } from '@/utils.ts'; +import { Time } from '@/utils/time.ts'; + +/** Decode a Nostr event from a base64 encoded string. */ +const decode64EventSchema = decode64Schema.pipe(jsonSchema).pipe(signedEventSchema); + +interface ParseAuthRequestOpts { + /** Max event age (in ms). */ + maxAge?: number; + /** Whether to validate the request body of the request with the payload of the auth event. (default: `true`) */ + validatePayload?: boolean; +} + +/** Parse the auth event from a Request, returning a zod SafeParse type. */ +function parseAuthRequest(req: Request, opts: ParseAuthRequestOpts = {}) { + const { maxAge = Time.minutes(1), validatePayload = true } = opts; + + const header = req.headers.get('authorization'); + const base64 = header?.match(/^Nostr (.+)$/)?.[1]; + + const schema = decode64EventSchema + .refine((event): event is Event<27235> => event.kind === 27235, 'Event must be kind 27235') + .refine((event) => eventAge(event) < maxAge, 'Event expired') + .refine((event) => tagValue(event, 'method') === req.method, 'Event method does not match HTTP request method') + .refine((event) => tagValue(event, 'u') === req.url, 'Event URL does not match request URL') + .refine(validateBody, 'Event payload does not match request body'); + + function validateBody(event: Event<27235>) { + if (!validatePayload) return true; + return req.clone().text() + .then(sha256) + .then((hash) => hash === tagValue(event, 'payload')); + } + + return schema.safeParseAsync(base64); +} + +/** Get the value for the first matching tag name in the event. */ +function tagValue(event: Event, tagName: string): string | undefined { + return findTag(event.tags, tagName)?.[1]; +} + +export { parseAuthRequest, type ParseAuthRequestOpts }; diff --git a/src/utils/web.ts b/src/utils/web.ts index 5bae5c9c..b02209f1 100644 --- a/src/utils/web.ts +++ b/src/utils/web.ts @@ -123,4 +123,20 @@ function activityJson(c: Context, object: T) { return response; } -export { activityJson, createAdminEvent, createEvent, paginated, type PaginationParams, paginationSchema, parseBody }; +/** Rewrite the URL of the request object to use the local domain. */ +function localRequest(c: Context): Request { + return Object.create(c.req.raw, { + url: { value: Conf.local(c.req.url) }, + }); +} + +export { + activityJson, + createAdminEvent, + createEvent, + localRequest, + paginated, + type PaginationParams, + paginationSchema, + parseBody, +}; From c8d6389132244b590428629961bbecc7d59faeca Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 2 Sep 2023 18:56:20 -0500 Subject: [PATCH 03/10] sign: refactor the unnecessarily complex awaitSignedEvent function --- src/sign.ts | 43 +++++++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/src/sign.ts b/src/sign.ts index 2a91a061..cdce1504 100644 --- a/src/sign.ts +++ b/src/sign.ts @@ -58,7 +58,7 @@ async function signNostrConnect(event: EventTemplate< } /** Wait for signed event to be sent through Nostr relay. */ -function awaitSignedEvent( +async function awaitSignedEvent( pubkey: string, messageId: string, c: AppContext, @@ -69,30 +69,29 @@ function awaitSignedEvent( Sub.close(messageId); } - return new Promise((resolve, reject) => { - const timeout = setTimeout(() => { - close(); - reject( - new HTTPException(408, { - res: c.json({ id: 'ditto.timeout', error: 'Signing timeout' }), - }), - ); - }, Time.minutes(1)); + const timeout = setTimeout(() => { + close(); + throw new HTTPException(408, { + res: c.json({ id: 'ditto.timeout', error: 'Signing timeout' }), + }); + }, Time.minutes(1)); - (async () => { - for await (const event of sub) { - if (event.kind === 24133) { - const decrypted = await decryptAdmin(event.pubkey, event.content); - const msg = jsonSchema.pipe(connectResponseSchema).parse(decrypted); + for await (const event of sub) { + if (event.kind === 24133) { + const decrypted = await decryptAdmin(event.pubkey, event.content); + const msg = jsonSchema.pipe(connectResponseSchema).parse(decrypted); - if (msg.id === messageId) { - close(); - clearTimeout(timeout); - resolve(msg.result as Event); - } - } + if (msg.id === messageId) { + close(); + clearTimeout(timeout); + return msg.result as Event; } - })(); + } + } + + // This should never happen. + throw new HTTPException(500, { + res: c.json({ id: 'ditto.sign', error: 'Unable to sign event' }, 500), }); } From 7595dfa2e550dcaf1899265afe8a4c69043f0fc3 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 2 Sep 2023 19:33:13 -0500 Subject: [PATCH 04/10] auth98: make requireProof obtain the event over NIP-46 --- src/middleware/auth98.ts | 15 ++++++++++----- src/sign.ts | 2 +- src/utils/nip98.ts | 23 ++++++++++++++++++++--- 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/src/middleware/auth98.ts b/src/middleware/auth98.ts index 4ef54617..7e7f93a8 100644 --- a/src/middleware/auth98.ts +++ b/src/middleware/auth98.ts @@ -1,7 +1,8 @@ import { type AppMiddleware } from '@/app.ts'; import { HTTPException } from '@/deps.ts'; -import { parseAuthRequest, type ParseAuthRequestOpts } from '@/utils/nip98.ts'; +import { buildAuthEventTemplate, parseAuthRequest, type ParseAuthRequestOpts } from '@/utils/nip98.ts'; import { localRequest } from '@/utils/web.ts'; +import { signNostrConnect } from '@/sign.ts'; /** * NIP-98 auth. @@ -22,12 +23,16 @@ function auth98(opts: ParseAuthRequestOpts = {}): AppMiddleware { } const requireProof: AppMiddleware = async (c, next) => { + const header = c.req.headers.get('x-nostr-sign'); const pubkey = c.get('pubkey'); - const proof = c.get('proof'); + const proof = c.get('proof') || header ? await obtainProof() : undefined; - // if (!proof && hasWebsocket(c.req)) { - // // TODO: attempt to sign nip98 event through websocket - // } + /** Get the proof over Nostr Connect. */ + async function obtainProof() { + const req = localRequest(c); + const event = await buildAuthEventTemplate(req); + return signNostrConnect(event, c); + } if (!pubkey || !proof || proof.pubkey !== pubkey) { throw new HTTPException(401); diff --git a/src/sign.ts b/src/sign.ts index cdce1504..f4f8d2e7 100644 --- a/src/sign.ts +++ b/src/sign.ts @@ -101,4 +101,4 @@ async function signAdminEvent(event: EventTemplate return finishEvent(event, Conf.seckey); } -export { signAdminEvent, signEvent }; +export { signAdminEvent, signEvent, signNostrConnect }; diff --git a/src/utils/nip98.ts b/src/utils/nip98.ts index 123a6ef2..e1cae5d6 100644 --- a/src/utils/nip98.ts +++ b/src/utils/nip98.ts @@ -1,7 +1,7 @@ -import { type Event } from '@/deps.ts'; +import { type Event, type EventTemplate } from '@/deps.ts'; import { decode64Schema, jsonSchema } from '@/schema.ts'; import { signedEventSchema } from '@/schemas/nostr.ts'; -import { eventAge, findTag, sha256 } from '@/utils.ts'; +import { eventAge, findTag, nostrNow, sha256 } from '@/utils.ts'; import { Time } from '@/utils/time.ts'; /** Decode a Nostr event from a base64 encoded string. */ @@ -38,9 +38,26 @@ function parseAuthRequest(req: Request, opts: ParseAuthRequestOpts = {}) { return schema.safeParseAsync(base64); } +/** Create an auth EventTemplate from a Request. */ +async function buildAuthEventTemplate(req: Request): Promise> { + const { method, url } = req; + const payload = await req.clone().text().then(sha256); + + return { + kind: 27235, + content: '', + tags: [ + ['method', method], + ['u', url], + ['payload', payload], + ], + created_at: nostrNow(), + }; +} + /** Get the value for the first matching tag name in the event. */ function tagValue(event: Event, tagName: string): string | undefined { return findTag(event.tags, tagName)?.[1]; } -export { parseAuthRequest, type ParseAuthRequestOpts }; +export { buildAuthEventTemplate, parseAuthRequest, type ParseAuthRequestOpts }; From 007565b513395d591002e5797943aa93c2e02c59 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 2 Sep 2023 19:40:28 -0500 Subject: [PATCH 05/10] eventSchema: ensure the event ID matches its hash --- src/schemas/nostr.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/schemas/nostr.ts b/src/schemas/nostr.ts index 6b5c30c1..12948046 100644 --- a/src/schemas/nostr.ts +++ b/src/schemas/nostr.ts @@ -1,4 +1,4 @@ -import { verifySignature, z } from '@/deps.ts'; +import { getEventHash, verifySignature, z } from '@/deps.ts'; import { jsonSchema, safeUrlSchema } from '../schema.ts'; @@ -19,7 +19,9 @@ const eventSchema = z.object({ }); /** Nostr event schema that also verifies the event's signature. */ -const signedEventSchema = eventSchema.refine(verifySignature); +const signedEventSchema = eventSchema + .refine((event) => event.id === getEventHash(event), 'Event ID does not match hash') + .refine(verifySignature, 'Event signature is invalid'); /** Nostr relay filter schema. */ const filterSchema = z.object({ From c8a5da086ee97feb40d820261da0f15fddc7bdd6 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 2 Sep 2023 20:09:28 -0500 Subject: [PATCH 06/10] sign: ensure the NIP-46 result matches the template --- src/sign.ts | 18 ++++++++++++------ src/utils.ts | 8 +++++++- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/sign.ts b/src/sign.ts index f4f8d2e7..94b8c626 100644 --- a/src/sign.ts +++ b/src/sign.ts @@ -5,14 +5,14 @@ import { type Event, type EventTemplate, finishEvent, HTTPException } from '@/de import { connectResponseSchema } from '@/schemas/nostr.ts'; import { jsonSchema } from '@/schema.ts'; import { Sub } from '@/subs.ts'; -import { Time } from '@/utils.ts'; +import { eventMatchesTemplate, Time } from '@/utils.ts'; import { createAdminEvent } from '@/utils/web.ts'; /** * Sign Nostr event using the app context. * * - If a secret key is provided, it will be used to sign the event. - * - If `X-Nostr-Sign` is passed, it will use a NIP-46 to sign the event. + * - If `X-Nostr-Sign` is passed, it will use NIP-46 to sign the event. */ async function signEvent(event: EventTemplate, c: AppContext): Promise> { const seckey = c.get('seckey'); @@ -54,13 +54,14 @@ async function signNostrConnect(event: EventTemplate< tags: [['p', pubkey]], }, c); - return awaitSignedEvent(pubkey, messageId, c); + return awaitSignedEvent(pubkey, messageId, event, c); } /** Wait for signed event to be sent through Nostr relay. */ async function awaitSignedEvent( pubkey: string, messageId: string, + template: EventTemplate, c: AppContext, ): Promise> { const sub = Sub.sub(messageId, '1', [{ kinds: [24133], authors: [pubkey], '#p': [Conf.pubkey] }]); @@ -79,12 +80,17 @@ async function awaitSignedEvent( for await (const event of sub) { if (event.kind === 24133) { const decrypted = await decryptAdmin(event.pubkey, event.content); - const msg = jsonSchema.pipe(connectResponseSchema).parse(decrypted); - if (msg.id === messageId) { + const result = jsonSchema + .pipe(connectResponseSchema) + .refine((msg) => msg.id === messageId) + .refine((msg) => eventMatchesTemplate(msg.result, template)) + .safeParse(decrypted); + + if (result.success) { close(); clearTimeout(timeout); - return msg.result as Event; + return result.data.result as Event; } } } diff --git a/src/utils.ts b/src/utils.ts index ede1e9c5..18362989 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,4 +1,4 @@ -import { type Event, nip19, z } from '@/deps.ts'; +import { type Event, type EventTemplate, getEventHash, nip19, z } from '@/deps.ts'; import { getAuthor } from '@/queries.ts'; import { lookupNip05Cached } from '@/utils/nip05.ts'; @@ -106,11 +106,17 @@ function dedupeEvents(events: Event[]): Event[] { return [...new Map(events.map((event) => [event.id, event])).values()]; } +/** Ensure the template and event match on their shared keys. */ +function eventMatchesTemplate(event: Event, template: EventTemplate): boolean { + return getEventHash(event) === getEventHash({ pubkey: event.pubkey, ...template }); +} + export { bech32ToPubkey, dedupeEvents, eventAge, eventDateComparator, + eventMatchesTemplate, findTag, isFollowing, isRelay, From f7d74c97ca2745636fb80e4c6c4ffbc545d13145 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 2 Sep 2023 20:52:02 -0500 Subject: [PATCH 07/10] db/users: add `admin` column --- src/db.ts | 1 + src/db/migrations/003_events_admin.ts | 12 ++++++++++++ 2 files changed, 13 insertions(+) create mode 100644 src/db/migrations/003_events_admin.ts diff --git a/src/db.ts b/src/db.ts index d14a9bcf..78b4c1c5 100644 --- a/src/db.ts +++ b/src/db.ts @@ -39,6 +39,7 @@ interface UserRow { pubkey: string; username: string; inserted_at: Date; + admin: boolean; } interface RelayRow { diff --git a/src/db/migrations/003_events_admin.ts b/src/db/migrations/003_events_admin.ts new file mode 100644 index 00000000..9183322c --- /dev/null +++ b/src/db/migrations/003_events_admin.ts @@ -0,0 +1,12 @@ +import { Kysely } from '@/deps.ts'; + +export async function up(db: Kysely): Promise { + await db.schema + .alterTable('users') + .addColumn('admin', 'boolean', (col) => col.defaultTo(false)) + .execute(); +} + +export async function down(db: Kysely): Promise { + await db.schema.dropTable('relays').execute(); +} From b81091f5dad17b252c8cabcbf44595ec7ece8e36 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 2 Sep 2023 20:52:17 -0500 Subject: [PATCH 08/10] auth98: requireProof --> requireAdmin --- src/middleware/auth98.ts | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/middleware/auth98.ts b/src/middleware/auth98.ts index 7e7f93a8..dad0d0ae 100644 --- a/src/middleware/auth98.ts +++ b/src/middleware/auth98.ts @@ -1,8 +1,9 @@ -import { type AppMiddleware } from '@/app.ts'; +import { type AppContext, type AppMiddleware } from '@/app.ts'; import { HTTPException } from '@/deps.ts'; import { buildAuthEventTemplate, parseAuthRequest, type ParseAuthRequestOpts } from '@/utils/nip98.ts'; import { localRequest } from '@/utils/web.ts'; import { signNostrConnect } from '@/sign.ts'; +import { findUser } from '@/db/users.ts'; /** * NIP-98 auth. @@ -22,23 +23,26 @@ function auth98(opts: ParseAuthRequestOpts = {}): AppMiddleware { }; } -const requireProof: AppMiddleware = async (c, next) => { +/** Require the user to prove they're an admin before invoking the controller. */ +const requireAdmin: AppMiddleware = async (c, next) => { const header = c.req.headers.get('x-nostr-sign'); - const pubkey = c.get('pubkey'); - const proof = c.get('proof') || header ? await obtainProof() : undefined; + const proof = c.get('proof') || header ? await obtainProof(c) : undefined; + const user = proof ? await findUser({ pubkey: proof.pubkey }) : undefined; - /** Get the proof over Nostr Connect. */ - async function obtainProof() { - const req = localRequest(c); - const event = await buildAuthEventTemplate(req); - return signNostrConnect(event, c); - } - - if (!pubkey || !proof || proof.pubkey !== pubkey) { + if (proof && user?.admin) { + c.set('pubkey', proof.pubkey); + c.set('proof', proof); + await next(); + } else { throw new HTTPException(401); } - - await next(); }; -export { auth98, requireProof }; +/** Get the proof over Nostr Connect. */ +async function obtainProof(c: AppContext) { + const req = localRequest(c); + const event = await buildAuthEventTemplate(req); + return signNostrConnect(event, c); +} + +export { auth98, requireAdmin }; From 93f06fd3427d371664074e45d3360d2ed642443e Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 2 Sep 2023 22:38:21 -0500 Subject: [PATCH 09/10] config: preserve query params in Conf.local --- src/config.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/config.ts b/src/config.ts index 7add7d8f..e03ef8bc 100644 --- a/src/config.ts +++ b/src/config.ts @@ -72,14 +72,15 @@ const Conf = { }, /** Merges the path with the localDomain. */ local(path: string): string { - if (path.startsWith('/')) { - // Path is a path. - return new URL(path, Conf.localDomain).toString(); - } else { - // Path is possibly a full URL. Replace the domain. - const { pathname } = new URL(path); - return new URL(pathname, Conf.localDomain).toString(); + const url = new URL(path.startsWith('/') ? path : new URL(path).pathname, Conf.localDomain); + + if (!path.startsWith('/')) { + // Copy query parameters from the original URL to the new URL + const originalUrl = new URL(path); + url.search = originalUrl.search; } + + return url.toString(); }, }; From 3c45a4a3aa7ff32a205cf4ed003317393e52e8cb Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 2 Sep 2023 22:38:55 -0500 Subject: [PATCH 10/10] sign: simplify awaitSignedEvent (remove unnecessary conditional, rearrange error, fix crash) --- src/sign.ts | 34 +++++++++++++--------------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/src/sign.ts b/src/sign.ts index 94b8c626..e260af75 100644 --- a/src/sign.ts +++ b/src/sign.ts @@ -70,34 +70,26 @@ async function awaitSignedEvent( Sub.close(messageId); } - const timeout = setTimeout(() => { - close(); - throw new HTTPException(408, { - res: c.json({ id: 'ditto.timeout', error: 'Signing timeout' }), - }); - }, Time.minutes(1)); + const timeout = setTimeout(close, Time.minutes(1)); for await (const event of sub) { - if (event.kind === 24133) { - const decrypted = await decryptAdmin(event.pubkey, event.content); + const decrypted = await decryptAdmin(event.pubkey, event.content); - const result = jsonSchema - .pipe(connectResponseSchema) - .refine((msg) => msg.id === messageId) - .refine((msg) => eventMatchesTemplate(msg.result, template)) - .safeParse(decrypted); + const result = jsonSchema + .pipe(connectResponseSchema) + .refine((msg) => msg.id === messageId, 'Message ID mismatch') + .refine((msg) => eventMatchesTemplate(msg.result, template), 'Event template mismatch') + .safeParse(decrypted); - if (result.success) { - close(); - clearTimeout(timeout); - return result.data.result as Event; - } + if (result.success) { + close(); + clearTimeout(timeout); + return result.data.result as Event; } } - // This should never happen. - throw new HTTPException(500, { - res: c.json({ id: 'ditto.sign', error: 'Unable to sign event' }, 500), + throw new HTTPException(408, { + res: c.json({ id: 'ditto.timeout', error: 'Signing timeout' }), }); }