From 6868f3971961ddea2be45a9b1cc336022de810e4 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 20 Nov 2023 12:34:19 -0600 Subject: [PATCH 1/4] NIP-46: request target proof-of-work difficulty when signing events --- src/middleware/auth98.ts | 2 +- src/sign.ts | 23 +++++++++++++++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/middleware/auth98.ts b/src/middleware/auth98.ts index 8eb73b5..0520010 100644 --- a/src/middleware/auth98.ts +++ b/src/middleware/auth98.ts @@ -91,7 +91,7 @@ function withProof( async function obtainProof(c: AppContext, opts?: ParseAuthRequestOpts) { const req = localRequest(c); const reqEvent = await buildAuthEventTemplate(req, opts); - const resEvent = await signEvent(reqEvent, c); + const resEvent = await signEvent(reqEvent, c, opts); const result = await validateAuthEvent(req, resEvent, opts); if (result.success) { diff --git a/src/sign.ts b/src/sign.ts index 14c192f..b5bbd33 100644 --- a/src/sign.ts +++ b/src/sign.ts @@ -8,13 +8,22 @@ import { Sub } from '@/subs.ts'; import { eventMatchesTemplate, Time } from '@/utils.ts'; import { createAdminEvent } from '@/utils/web.ts'; +interface SignEventOpts { + /** Target proof-of-work difficulty for the signed event. */ + pow?: number; +} + /** * 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 NIP-46 to sign the event. */ -async function signEvent(event: EventTemplate, c: AppContext): Promise> { +async function signEvent( + event: EventTemplate, + c: AppContext, + opts: SignEventOpts = {}, +): Promise> { const seckey = c.get('seckey'); const header = c.req.headers.get('x-nostr-sign'); @@ -23,7 +32,7 @@ async function signEvent(event: EventTemplate, c: } if (header) { - return await signNostrConnect(event, c); + return await signNostrConnect(event, c, opts); } throw new HTTPException(400, { @@ -32,7 +41,11 @@ async function signEvent(event: EventTemplate, c: } /** Sign event with NIP-46, waiting in the background for the signed event. */ -async function signNostrConnect(event: EventTemplate, c: AppContext): Promise> { +async function signNostrConnect( + event: EventTemplate, + c: AppContext, + opts: SignEventOpts = {}, +): Promise> { const pubkey = c.get('pubkey'); if (!pubkey) { @@ -48,7 +61,9 @@ async function signNostrConnect(event: EventTemplate< JSON.stringify({ id: messageId, method: 'sign_event', - params: [event], + params: [event, { + pow: opts.pow, + }], }), ), tags: [['p', pubkey]], From bedc8fdf91e68af5a11344793f36b68f93a00f13 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 20 Nov 2023 12:35:58 -0600 Subject: [PATCH 2/4] Upgrade nostr-tools to v1.17.0 --- src/deps.ts | 3 ++- src/utils/nip98.ts | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/deps.ts b/src/deps.ts index 9c460f6..ee8277b 100644 --- a/src/deps.ts +++ b/src/deps.ts @@ -25,8 +25,9 @@ export { nip19, nip21, type UnsignedEvent, + type VerifiedEvent, verifySignature, -} from 'npm:nostr-tools@^1.14.0'; +} from 'npm:nostr-tools@^1.17.0'; export { findReplyTag } from 'https://gitlab.com/soapbox-pub/mostr/-/raw/c67064aee5ade5e01597c6d23e22e53c628ef0e2/src/nostr/tags.ts'; export { parseFormData } from 'npm:formdata-helper@^0.3.0'; // @deno-types="npm:@types/lodash@4.14.194" diff --git a/src/utils/nip98.ts b/src/utils/nip98.ts index facc9af..bd7e673 100644 --- a/src/utils/nip98.ts +++ b/src/utils/nip98.ts @@ -1,4 +1,4 @@ -import { type Event, type EventTemplate, nip13 } from '@/deps.ts'; +import { type Event, type EventTemplate, nip13, type VerifiedEvent } from '@/deps.ts'; import { decode64Schema, jsonSchema } from '@/schema.ts'; import { signedEventSchema } from '@/schemas/nostr.ts'; import { eventAge, findTag, nostrNow, sha256 } from '@/utils.ts'; @@ -32,7 +32,7 @@ function validateAuthEvent(req: Request, event: Event, opts: ParseAuthRequestOpt const { maxAge = Time.minutes(1), validatePayload = true, pow = 0 } = opts; const schema = signedEventSchema - .refine((event): event is Event<27235> => event.kind === 27235, 'Event must be kind 27235') + .refine((event): event is VerifiedEvent<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') From c1bf326981f0d3ff39305e3793a1f7238e798ab0 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 20 Nov 2023 12:39:20 -0600 Subject: [PATCH 3/4] c.req.headers.get --> c.req.header, hono deprecation --- src/controllers/api/streaming.ts | 4 ++-- src/controllers/nostr/relay.ts | 2 +- src/middleware/auth19.ts | 2 +- src/sign.ts | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/controllers/api/streaming.ts b/src/controllers/api/streaming.ts index 6a9e535..d7aa677 100644 --- a/src/controllers/api/streaming.ts +++ b/src/controllers/api/streaming.ts @@ -28,8 +28,8 @@ const streamSchema = z.enum([ type Stream = z.infer; const streamingController: AppController = (c) => { - const upgrade = c.req.headers.get('upgrade'); - const token = c.req.headers.get('sec-websocket-protocol'); + const upgrade = c.req.header('upgrade'); + const token = c.req.header('sec-websocket-protocol'); const stream = streamSchema.optional().catch(undefined).parse(c.req.query('stream')); if (upgrade?.toLowerCase() !== 'websocket') { diff --git a/src/controllers/nostr/relay.ts b/src/controllers/nostr/relay.ts index 3a5a9aa..9f5cd59 100644 --- a/src/controllers/nostr/relay.ts +++ b/src/controllers/nostr/relay.ts @@ -117,7 +117,7 @@ function prepareFilters(filters: ClientREQ[2][]): Filter[] { } const relayController: AppController = (c) => { - const upgrade = c.req.headers.get('upgrade'); + const upgrade = c.req.header('upgrade'); if (upgrade?.toLowerCase() !== 'websocket') { return c.text('Please use a Nostr client to connect.', 400); diff --git a/src/middleware/auth19.ts b/src/middleware/auth19.ts index fec79ad..19344fb 100644 --- a/src/middleware/auth19.ts +++ b/src/middleware/auth19.ts @@ -6,7 +6,7 @@ const BEARER_REGEX = new RegExp(`^Bearer (${nip19.BECH32_REGEX.source})$`); /** NIP-19 auth middleware. */ const auth19: AppMiddleware = async (c, next) => { - const authHeader = c.req.headers.get('authorization'); + const authHeader = c.req.header('authorization'); const match = authHeader?.match(BEARER_REGEX); if (match) { diff --git a/src/sign.ts b/src/sign.ts index b5bbd33..0662668 100644 --- a/src/sign.ts +++ b/src/sign.ts @@ -25,7 +25,7 @@ async function signEvent( opts: SignEventOpts = {}, ): Promise> { const seckey = c.get('seckey'); - const header = c.req.headers.get('x-nostr-sign'); + const header = c.req.header('x-nostr-sign'); if (seckey) { return finishEvent(event, seckey); From 5b030c99c508481d62b89cba44e084d786d72cc3 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 20 Nov 2023 12:42:18 -0600 Subject: [PATCH 4/4] Upgrade Hono to v3.10.1 --- src/deps.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/deps.ts b/src/deps.ts index ee8277b..cdf3e3f 100644 --- a/src/deps.ts +++ b/src/deps.ts @@ -6,8 +6,8 @@ export { Hono, HTTPException, type MiddlewareHandler, -} from 'https://deno.land/x/hono@v3.7.5/mod.ts'; -export { cors, logger, serveStatic } from 'https://deno.land/x/hono@v3.7.5/middleware.ts'; +} from 'https://deno.land/x/hono@v3.10.1/mod.ts'; +export { cors, logger, serveStatic } from 'https://deno.land/x/hono@v3.10.1/middleware.ts'; export { z } from 'https://deno.land/x/zod@v3.21.4/mod.ts'; export { Author, RelayPool } from 'https://dev.jspm.io/nostr-relaypool@0.6.28'; export {