From 425edf2174ac9e4f45c7970969918cde1c380588 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 10 Feb 2025 12:41:41 -0600 Subject: [PATCH] Add controller test, refactor some middlewares --- src/controllers/api/cashu.test.ts | 26 ++++++++++++++++++++++++++ src/controllers/api/cashu.ts | 13 ++++--------- src/middleware/requireSigner.ts | 17 +++++++++++++++++ src/middleware/signerMiddleware.ts | 6 +++--- src/middleware/storeMiddleware.ts | 9 +++++++-- src/utils/api.ts | 2 +- 6 files changed, 58 insertions(+), 15 deletions(-) create mode 100644 src/controllers/api/cashu.test.ts diff --git a/src/controllers/api/cashu.test.ts b/src/controllers/api/cashu.test.ts new file mode 100644 index 00000000..9d9f37f2 --- /dev/null +++ b/src/controllers/api/cashu.test.ts @@ -0,0 +1,26 @@ +// deno-lint-ignore-file require-await +import { NSecSigner } from '@nostrify/nostrify'; +import { assertEquals } from '@std/assert'; +import { generateSecretKey } from 'nostr-tools'; + +import { createTestDB } from '@/test.ts'; + +import cashuApp from './cashu.ts'; + +Deno.test('PUT /wallet', async () => { + await using db = await createTestDB(); + const store = db.store; + + const sk = generateSecretKey(); + const signer = new NSecSigner(sk); + + const app = cashuApp.use( + '*', + async (c) => c.set('store', store), + async (c) => c.set('signer', signer), + ); + + const response = await app.request('/wallet', { method: 'PUT' }); + + assertEquals(response.status, 200); +}); diff --git a/src/controllers/api/cashu.ts b/src/controllers/api/cashu.ts index 8219d8c7..7ac0dfe6 100644 --- a/src/controllers/api/cashu.ts +++ b/src/controllers/api/cashu.ts @@ -11,10 +11,10 @@ import { isNostrId } from '@/utils.ts'; import { createEvent, parseBody } from '@/utils/api.ts'; import { errorJson } from '@/utils/log.ts'; import { signerMiddleware } from '@/middleware/signerMiddleware.ts'; -import { requireSigner } from '@/middleware/requireSigner.ts'; +import { requireNip44Signer } from '@/middleware/requireSigner.ts'; import { storeMiddleware } from '@/middleware/storeMiddleware.ts'; -const app = new Hono(); +const app = new Hono().use('*', storeMiddleware, signerMiddleware); // CASHU_MINTS = ['https://mint.cashu.io/1', 'https://mint.cashu.io/2', 'https://mint.cashu.io/3'] @@ -55,7 +55,7 @@ const createCashuWalletSchema = z.object({ * Creates a replaceable Cashu wallet. * https://github.com/nostr-protocol/nips/blob/master/60.md */ -app.post('/wallet', storeMiddleware, signerMiddleware, requireSigner, async (c) => { +app.post('/wallet', requireNip44Signer, async (c) => { const signer = c.get('signer'); const store = c.get('store'); const pubkey = await signer.getPublicKey(); @@ -67,11 +67,6 @@ app.post('/wallet', storeMiddleware, signerMiddleware, requireSigner, async (c) return c.json({ error: 'Bad schema', schema: result.error }, 400); } - const nip44 = signer.nip44; - if (!nip44) { - return c.json({ error: 'Signer does not have nip 44' }, 400); - } - const [event] = await store.query([{ authors: [pubkey], kinds: [17375] }], { signal }); if (event) { return c.json({ error: 'You already have a wallet 😏' }, 400); @@ -90,7 +85,7 @@ app.post('/wallet', storeMiddleware, signerMiddleware, requireSigner, async (c) contentTags.push(['mint', mint]); } - const encryptedContentTags = await nip44.encrypt(pubkey, JSON.stringify(contentTags)); + const encryptedContentTags = await signer.nip44.encrypt(pubkey, JSON.stringify(contentTags)); // Wallet await createEvent({ diff --git a/src/middleware/requireSigner.ts b/src/middleware/requireSigner.ts index e360ab42..7733b26f 100644 --- a/src/middleware/requireSigner.ts +++ b/src/middleware/requireSigner.ts @@ -1,6 +1,7 @@ import { MiddlewareHandler } from '@hono/hono'; import { HTTPException } from '@hono/hono/http-exception'; import { NostrSigner } from '@nostrify/nostrify'; +import { SetRequired } from 'type-fest'; /** Throw a 401 if a signer isn't set. */ export const requireSigner: MiddlewareHandler<{ Variables: { signer: NostrSigner } }> = async (c, next) => { @@ -10,3 +11,19 @@ export const requireSigner: MiddlewareHandler<{ Variables: { signer: NostrSigner await next(); }; + +/** Throw a 401 if a NIP-44 signer isn't set. */ +export const requireNip44Signer: MiddlewareHandler<{ Variables: { signer: SetRequired } }> = + async (c, next) => { + const signer = c.get('signer'); + + if (!signer) { + throw new HTTPException(401, { message: 'No pubkey provided' }); + } + + if (!signer.nip44) { + throw new HTTPException(401, { message: 'No NIP-44 signer provided' }); + } + + await next(); + }; diff --git a/src/middleware/signerMiddleware.ts b/src/middleware/signerMiddleware.ts index 8fca06a3..aa7b537f 100644 --- a/src/middleware/signerMiddleware.ts +++ b/src/middleware/signerMiddleware.ts @@ -1,8 +1,8 @@ +import { MiddlewareHandler } from '@hono/hono'; import { HTTPException } from '@hono/hono/http-exception'; -import { NSecSigner } from '@nostrify/nostrify'; +import { NostrSigner, NSecSigner } from '@nostrify/nostrify'; import { nip19 } from 'nostr-tools'; -import { AppMiddleware } from '@/app.ts'; import { Conf } from '@/config.ts'; import { ConnectSigner } from '@/signers/ConnectSigner.ts'; import { ReadOnlySigner } from '@/signers/ReadOnlySigner.ts'; @@ -14,7 +14,7 @@ import { getTokenHash } from '@/utils/auth.ts'; const BEARER_REGEX = new RegExp(`^Bearer (${nip19.BECH32_REGEX.source})$`); /** Make a `signer` object available to all controllers, or unset if the user isn't logged in. */ -export const signerMiddleware: AppMiddleware = async (c, next) => { +export const signerMiddleware: MiddlewareHandler<{ Variables: { signer: NostrSigner } }> = async (c, next) => { const header = c.req.header('authorization'); const match = header?.match(BEARER_REGEX); diff --git a/src/middleware/storeMiddleware.ts b/src/middleware/storeMiddleware.ts index 4e24ab05..37d04856 100644 --- a/src/middleware/storeMiddleware.ts +++ b/src/middleware/storeMiddleware.ts @@ -1,9 +1,14 @@ -import { AppMiddleware } from '@/app.ts'; +import { MiddlewareHandler } from '@hono/hono'; +import { NostrSigner, NStore } from '@nostrify/nostrify'; + import { UserStore } from '@/storages/UserStore.ts'; import { Storages } from '@/storages.ts'; /** Store middleware. */ -export const storeMiddleware: AppMiddleware = async (c, next) => { +export const storeMiddleware: MiddlewareHandler<{ Variables: { signer?: NostrSigner; store: NStore } }> = async ( + c, + next, +) => { const pubkey = await c.get('signer')?.getPublicKey(); if (pubkey) { diff --git a/src/utils/api.ts b/src/utils/api.ts index 29304cbd..a01cf277 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -19,7 +19,7 @@ import { purifyEvent } from '@/utils/purify.ts'; type EventStub = TypeFest.SetOptional; /** Publish an event through the pipeline. */ -async function createEvent(t: EventStub, c: AppContext): Promise { +async function createEvent(t: EventStub, c: Context): Promise { const signer = c.get('signer'); if (!signer) {