diff --git a/packages/api/DittoEnv.ts b/packages/api/DittoEnv.ts index d82ed911..fc35bb58 100644 --- a/packages/api/DittoEnv.ts +++ b/packages/api/DittoEnv.ts @@ -1,7 +1,7 @@ import type { DittoConf } from '@ditto/conf'; import type { DittoDatabase } from '@ditto/db'; import type { Env } from '@hono/hono'; -import type { NRelay } from '@nostrify/nostrify'; +import type { NostrSigner, NRelay, NStore } from '@nostrify/nostrify'; export interface DittoEnv extends Env { Variables: { @@ -13,5 +13,12 @@ export interface DittoEnv extends Env { db: DittoDatabase; /** Abort signal for the request. */ signal: AbortSignal; + /** The current user */ + user?: { + /** The user's signer. */ + signer: NostrSigner; + /** The user's store. */ + store: NStore; + }; }; } diff --git a/packages/api/DittoRoute.ts b/packages/api/DittoRoute.ts index 73a63090..9fcc011d 100644 --- a/packages/api/DittoRoute.ts +++ b/packages/api/DittoRoute.ts @@ -27,6 +27,7 @@ export class DittoRoute extends Hono { if (!vars.signal) this.throwMissingVar('signal'); return { + ...vars, db: vars.db, conf: vars.conf, store: vars.store, diff --git a/packages/api/middleware/mod.ts b/packages/api/middleware/mod.ts index fc06aa58..da56cab9 100644 --- a/packages/api/middleware/mod.ts +++ b/packages/api/middleware/mod.ts @@ -1 +1,2 @@ +export { requireVar } from './requireVar.ts'; export { userMiddleware } from './userMiddleware.ts'; diff --git a/packages/api/middleware/requireVar.ts b/packages/api/middleware/requireVar.ts new file mode 100644 index 00000000..d1527e9a --- /dev/null +++ b/packages/api/middleware/requireVar.ts @@ -0,0 +1,17 @@ +import { HTTPException } from '@hono/hono/http-exception'; + +import type { SetRequired } from 'type-fest'; +import type { DittoEnv } from '../DittoEnv.ts'; +import type { DittoMiddleware } from '../DittoMiddleware.ts'; + +type DittoVars = DittoEnv['Variables']; + +export function requireVar(key: K): DittoMiddleware> { + return (c, next) => { + if (!c.var[key]) { + throw new HTTPException(500, { message: `Missing required variable: ${key}` }); + } + + return next(); + }; +} diff --git a/packages/api/middleware/userMiddleware.ts b/packages/api/middleware/userMiddleware.ts index 2eb5dd96..f8ff997e 100644 --- a/packages/api/middleware/userMiddleware.ts +++ b/packages/api/middleware/userMiddleware.ts @@ -18,14 +18,15 @@ interface UserMiddlewareOpts { privileged: boolean; } -// @ts-ignore The types are right. -export function userMiddleware(opts: { privileged: boolean; required: true }): DittoMiddleware<{ user: User }>; +export function userMiddleware(opts: { privileged: false; required: true }): DittoMiddleware<{ user: User }>; export function userMiddleware(opts: { privileged: true; required?: boolean }): DittoMiddleware<{ user: User }>; export function userMiddleware(opts: { privileged: false; required?: boolean }): DittoMiddleware<{ user?: User }>; -export function userMiddleware(opts: { privileged?: boolean; required?: boolean }): DittoMiddleware<{ user?: User }> { +export function userMiddleware(opts: { privileged: boolean; required?: boolean }): DittoMiddleware<{ user?: User }> { /** We only accept "Bearer" type. */ const BEARER_REGEX = new RegExp(`^Bearer (${nip19.BECH32_REGEX.source})$`); + const { privileged, required = false } = opts; + return async (c, next) => { const { conf, db, store } = c.var; @@ -83,7 +84,7 @@ export function userMiddleware(opts: { privileged?: boolean; required?: boolean if (signer) { const user: User = { signer, store }; c.set('user', user); - } else { + } else if (required) { throw new HTTPException(401); } diff --git a/packages/api/routes/timelinesRoute.ts b/packages/api/routes/timelinesRoute.ts index be76563a..3147109a 100644 --- a/packages/api/routes/timelinesRoute.ts +++ b/packages/api/routes/timelinesRoute.ts @@ -1,5 +1,5 @@ import { DittoRoute } from '@ditto/api'; -import { userMiddleware } from '@ditto/api/middleware'; +import { requireVar, userMiddleware } from '@ditto/api/middleware'; import { booleanParamSchema, languageSchema } from '@ditto/api/schema'; import { z } from 'zod'; @@ -12,7 +12,7 @@ const homeQuerySchema = z.object({ only_media: booleanParamSchema.optional(), }); -route.get('/home', async (c) => { +route.get('/home', requireVar('user'), async (c) => { const { user, pagination } = c.var; const pubkey = await user.signer.getPublicKey()!;