diff --git a/src/controllers/api/accounts.ts b/src/controllers/api/accounts.ts index 68edfbc..88d19b1 100644 --- a/src/controllers/api/accounts.ts +++ b/src/controllers/api/accounts.ts @@ -29,7 +29,7 @@ const createAccountSchema = z.object({ }); const createAccountController: AppController = async (c) => { - const pubkey = c.get('pubkey')!; + const pubkey = await c.get('signer')?.getPublicKey()!; const result = createAccountSchema.safeParse(await c.req.json()); if (!result.success) { @@ -45,7 +45,7 @@ const createAccountController: AppController = async (c) => { }; const verifyCredentialsController: AppController = async (c) => { - const pubkey = c.get('pubkey')!; + const pubkey = await c.get('signer')?.getPublicKey()!; const event = await getAuthor(pubkey, { relations: ['author_stats'] }); if (event) { @@ -122,7 +122,7 @@ const accountSearchController: AppController = async (c) => { }; const relationshipsController: AppController = async (c) => { - const pubkey = c.get('pubkey')!; + const pubkey = await c.get('signer')?.getPublicKey()!; const ids = z.array(z.string()).safeParse(c.req.queries('id[]')); if (!ids.success) { @@ -178,7 +178,11 @@ const accountStatusesController: AppController = async (c) => { return events; }); - const statuses = await Promise.all(events.map((event) => renderStatus(event, { viewerPubkey: c.get('pubkey') }))); + const viewerPubkey = await c.get('signer')?.getPublicKey(); + + const statuses = await Promise.all( + events.map((event) => renderStatus(event, { viewerPubkey })), + ); return paginated(c, events, statuses); }; @@ -194,7 +198,7 @@ const updateCredentialsSchema = z.object({ }); const updateCredentialsController: AppController = async (c) => { - const pubkey = c.get('pubkey')!; + const pubkey = await c.get('signer')?.getPublicKey()!; const body = await parseBody(c.req.raw); const result = updateCredentialsSchema.safeParse(body); @@ -236,7 +240,7 @@ const updateCredentialsController: AppController = async (c) => { /** https://docs.joinmastodon.org/methods/accounts/#follow */ const followController: AppController = async (c) => { - const sourcePubkey = c.get('pubkey')!; + const sourcePubkey = await c.get('signer')?.getPublicKey()!; const targetPubkey = c.req.param('pubkey'); await updateListEvent( @@ -253,7 +257,7 @@ const followController: AppController = async (c) => { /** https://docs.joinmastodon.org/methods/accounts/#unfollow */ const unfollowController: AppController = async (c) => { - const sourcePubkey = c.get('pubkey')!; + const sourcePubkey = await c.get('signer')?.getPublicKey()!; const targetPubkey = c.req.param('pubkey'); await updateListEvent( @@ -290,7 +294,7 @@ const unblockController: AppController = (c) => { /** https://docs.joinmastodon.org/methods/accounts/#mute */ const muteController: AppController = async (c) => { - const sourcePubkey = c.get('pubkey')!; + const sourcePubkey = await c.get('signer')?.getPublicKey()!; const targetPubkey = c.req.param('pubkey'); await updateListEvent( @@ -305,7 +309,7 @@ const muteController: AppController = async (c) => { /** https://docs.joinmastodon.org/methods/accounts/#unmute */ const unmuteController: AppController = async (c) => { - const sourcePubkey = c.get('pubkey')!; + const sourcePubkey = await c.get('signer')?.getPublicKey()!; const targetPubkey = c.req.param('pubkey'); await updateListEvent( @@ -319,7 +323,7 @@ const unmuteController: AppController = async (c) => { }; const favouritesController: AppController = async (c) => { - const pubkey = c.get('pubkey')!; + const pubkey = await c.get('signer')?.getPublicKey()!; const params = paginationSchema.parse(c.req.query()); const { signal } = c.req.raw; @@ -335,7 +339,11 @@ const favouritesController: AppController = async (c) => { const events1 = await Storages.db.query([{ kinds: [1], ids }], { signal }) .then((events) => hydrateEvents({ events, storage: Storages.db, signal })); - const statuses = await Promise.all(events1.map((event) => renderStatus(event, { viewerPubkey: c.get('pubkey') }))); + const viewerPubkey = await c.get('signer')?.getPublicKey(); + + const statuses = await Promise.all( + events1.map((event) => renderStatus(event, { viewerPubkey })), + ); return paginated(c, events1, statuses); }; diff --git a/src/controllers/api/bookmarks.ts b/src/controllers/api/bookmarks.ts index 8d44f95..1616fa2 100644 --- a/src/controllers/api/bookmarks.ts +++ b/src/controllers/api/bookmarks.ts @@ -5,7 +5,7 @@ import { renderStatuses } from '@/views.ts'; /** https://docs.joinmastodon.org/methods/bookmarks/#get */ const bookmarksController: AppController = async (c) => { - const pubkey = c.get('pubkey')!; + const pubkey = await c.get('signer')?.getPublicKey()!; const { signal } = c.req.raw; const [event10003] = await Storages.db.query( diff --git a/src/controllers/api/markers.ts b/src/controllers/api/markers.ts index ce1c4ec..005ebbe 100644 --- a/src/controllers/api/markers.ts +++ b/src/controllers/api/markers.ts @@ -14,7 +14,7 @@ interface Marker { } export const markersController: AppController = async (c) => { - const pubkey = c.get('pubkey')!; + const pubkey = await c.get('signer')?.getPublicKey()!; const timelines = c.req.queries('timeline[]') ?? []; const results = await kv.getMany( @@ -37,7 +37,7 @@ const markerDataSchema = z.object({ }); export const updateMarkersController: AppController = async (c) => { - const pubkey = c.get('pubkey')!; + const pubkey = await c.get('signer')?.getPublicKey()!; const record = z.record(z.enum(['home', 'notifications']), markerDataSchema).parse(await parseBody(c.req.raw)); const timelines = Object.keys(record) as Timeline[]; diff --git a/src/controllers/api/media.ts b/src/controllers/api/media.ts index dd36a53..33b7981 100644 --- a/src/controllers/api/media.ts +++ b/src/controllers/api/media.ts @@ -14,7 +14,7 @@ const mediaBodySchema = z.object({ }); const mediaController: AppController = async (c) => { - const pubkey = c.get('pubkey')!; + const pubkey = await c.get('signer')?.getPublicKey()!; const result = mediaBodySchema.safeParse(await parseBody(c.req.raw)); const { signal } = c.req.raw; diff --git a/src/controllers/api/mutes.ts b/src/controllers/api/mutes.ts index 77b60e3..fe048e9 100644 --- a/src/controllers/api/mutes.ts +++ b/src/controllers/api/mutes.ts @@ -5,7 +5,7 @@ import { renderAccounts } from '@/views.ts'; /** https://docs.joinmastodon.org/methods/mutes/#get */ const mutesController: AppController = async (c) => { - const pubkey = c.get('pubkey')!; + const pubkey = await c.get('signer')?.getPublicKey()!; const { signal } = c.req.raw; const [event10000] = await Storages.db.query( diff --git a/src/controllers/api/notifications.ts b/src/controllers/api/notifications.ts index b2fa15e..857f2a3 100644 --- a/src/controllers/api/notifications.ts +++ b/src/controllers/api/notifications.ts @@ -5,8 +5,8 @@ import { hydrateEvents } from '@/storages/hydrate.ts'; import { paginated, paginationSchema } from '@/utils/api.ts'; import { renderNotification } from '@/views/mastodon/notifications.ts'; -const notificationsController: AppController = (c) => { - const pubkey = c.get('pubkey')!; +const notificationsController: AppController = async (c) => { + const pubkey = await c.get('signer')?.getPublicKey()!; const { since, until } = paginationSchema.parse(c.req.query()); return renderNotifications(c, [{ kinds: [1, 6, 7], '#p': [pubkey], since, until }]); @@ -14,7 +14,7 @@ const notificationsController: AppController = (c) => { async function renderNotifications(c: AppContext, filters: NostrFilter[]) { const store = c.get('store'); - const pubkey = c.get('pubkey')!; + const pubkey = await c.get('signer')?.getPublicKey()!; const { signal } = c.req.raw; const events = await store diff --git a/src/controllers/api/reports.ts b/src/controllers/api/reports.ts index eb85bd2..55fb601 100644 --- a/src/controllers/api/reports.ts +++ b/src/controllers/api/reports.ts @@ -55,9 +55,15 @@ const reportController: AppController = async (c) => { /** https://docs.joinmastodon.org/methods/admin/reports/#get */ const adminReportsController: AppController = async (c) => { const store = c.get('store'); + const viewerPubkey = await c.get('signer')?.getPublicKey(); + const reports = await store.query([{ kinds: [1984], '#P': [Conf.pubkey] }]) .then((events) => hydrateEvents({ storage: store, events: events, signal: c.req.raw.signal })) - .then((events) => Promise.all(events.map((event) => renderAdminReport(event, { viewerPubkey: c.get('pubkey') })))); + .then((events) => + Promise.all( + events.map((event) => renderAdminReport(event, { viewerPubkey })), + ) + ); return c.json(reports); }; @@ -67,7 +73,7 @@ const adminReportController: AppController = async (c) => { const eventId = c.req.param('id'); const { signal } = c.req.raw; const store = c.get('store'); - const pubkey = c.get('pubkey'); + const pubkey = await c.get('signer')?.getPublicKey(); const [event] = await store.query([{ kinds: [1984], @@ -89,7 +95,7 @@ const adminReportResolveController: AppController = async (c) => { const eventId = c.req.param('id'); const { signal } = c.req.raw; const store = c.get('store'); - const pubkey = c.get('pubkey'); + const pubkey = await c.get('signer')?.getPublicKey(); const [event] = await store.query([{ kinds: [1984], diff --git a/src/controllers/api/search.ts b/src/controllers/api/search.ts index e674d0e..fe08ace 100644 --- a/src/controllers/api/search.ts +++ b/src/controllers/api/search.ts @@ -43,6 +43,7 @@ const searchController: AppController = async (c) => { } const results = dedupeEvents(events); + const viewerPubkey = await c.get('signer')?.getPublicKey(); const [accounts, statuses] = await Promise.all([ Promise.all( @@ -54,7 +55,7 @@ const searchController: AppController = async (c) => { Promise.all( results .filter((event) => event.kind === 1) - .map((event) => renderStatus(event, { viewerPubkey: c.get('pubkey') })) + .map((event) => renderStatus(event, { viewerPubkey })) .filter(Boolean), ), ]); diff --git a/src/controllers/api/statuses.ts b/src/controllers/api/statuses.ts index 3420383..1138c0a 100644 --- a/src/controllers/api/statuses.ts +++ b/src/controllers/api/statuses.ts @@ -47,7 +47,7 @@ const statusController: AppController = async (c) => { }); if (event) { - return c.json(await renderStatus(event, { viewerPubkey: c.get('pubkey') })); + return c.json(await renderStatus(event, { viewerPubkey: await c.get('signer')?.getPublicKey() })); } return c.json({ error: 'Event not found.' }, 404); @@ -89,9 +89,11 @@ const createStatusController: AppController = async (c) => { tags.push(['subject', data.spoiler_text]); } + const viewerPubkey = await c.get('signer')?.getPublicKey(); + if (data.media_ids?.length) { const media = await getUnattachedMediaByIds(data.media_ids) - .then((media) => media.filter(({ pubkey }) => pubkey === c.get('pubkey'))) + .then((media) => media.filter(({ pubkey }) => pubkey === viewerPubkey)) .then((media) => media.map(({ url, data }) => ['media', url, data])); tags.push(...media); @@ -143,12 +145,12 @@ const createStatusController: AppController = async (c) => { }); } - return c.json(await renderStatus({ ...event, author }, { viewerPubkey: c.get('pubkey') })); + return c.json(await renderStatus({ ...event, author }, { viewerPubkey: await c.get('signer')?.getPublicKey() })); }; const deleteStatusController: AppController = async (c) => { const id = c.req.param('id'); - const pubkey = c.get('pubkey'); + const pubkey = await c.get('signer')?.getPublicKey(); const event = await getEvent(id, { signal: c.req.raw.signal }); @@ -172,9 +174,12 @@ const deleteStatusController: AppController = async (c) => { const contextController: AppController = async (c) => { const id = c.req.param('id'); const event = await getEvent(id, { kind: 1, relations: ['author', 'event_stats', 'author_stats'] }); + const viewerPubkey = await c.get('signer')?.getPublicKey(); async function renderStatuses(events: NostrEvent[]) { - const statuses = await Promise.all(events.map((event) => renderStatus(event, { viewerPubkey: c.get('pubkey') }))); + const statuses = await Promise.all( + events.map((event) => renderStatus(event, { viewerPubkey })), + ); return statuses.filter(Boolean); } @@ -204,7 +209,7 @@ const favouriteController: AppController = async (c) => { ], }, c); - const status = await renderStatus(target, { viewerPubkey: c.get('pubkey') }); + const status = await renderStatus(target, { viewerPubkey: await c.get('signer')?.getPublicKey() }); if (status) { status.favourited = true; @@ -247,7 +252,7 @@ const reblogStatusController: AppController = async (c) => { signal: signal, }); - const status = await renderReblog(reblogEvent, { viewerPubkey: c.get('pubkey') }); + const status = await renderReblog(reblogEvent, { viewerPubkey: await c.get('signer')?.getPublicKey() }); return c.json(status); }; @@ -255,7 +260,7 @@ const reblogStatusController: AppController = async (c) => { /** https://docs.joinmastodon.org/methods/statuses/#unreblog */ const unreblogStatusController: AppController = async (c) => { const eventId = c.req.param('id'); - const pubkey = c.get('pubkey') as string; + const pubkey = await c.get('signer')?.getPublicKey() as string; const event = await getEvent(eventId, { kind: 1, @@ -282,7 +287,7 @@ const rebloggedByController: AppController = (c) => { /** https://docs.joinmastodon.org/methods/statuses/#bookmark */ const bookmarkController: AppController = async (c) => { - const pubkey = c.get('pubkey')!; + const pubkey = await c.get('signer')?.getPublicKey()!; const eventId = c.req.param('id'); const event = await getEvent(eventId, { @@ -309,7 +314,7 @@ const bookmarkController: AppController = async (c) => { /** https://docs.joinmastodon.org/methods/statuses/#unbookmark */ const unbookmarkController: AppController = async (c) => { - const pubkey = c.get('pubkey')!; + const pubkey = await c.get('signer')?.getPublicKey()!; const eventId = c.req.param('id'); const event = await getEvent(eventId, { @@ -336,7 +341,7 @@ const unbookmarkController: AppController = async (c) => { /** https://docs.joinmastodon.org/methods/statuses/#pin */ const pinController: AppController = async (c) => { - const pubkey = c.get('pubkey')!; + const pubkey = await c.get('signer')?.getPublicKey()!; const eventId = c.req.param('id'); const event = await getEvent(eventId, { @@ -363,7 +368,7 @@ const pinController: AppController = async (c) => { /** https://docs.joinmastodon.org/methods/statuses/#unpin */ const unpinController: AppController = async (c) => { - const pubkey = c.get('pubkey')!; + const pubkey = await c.get('signer')?.getPublicKey()!; const eventId = c.req.param('id'); const { signal } = c.req.raw; @@ -423,7 +428,7 @@ const zapController: AppController = async (c) => { ], }, c); - const status = await renderStatus(target, { viewerPubkey: c.get('pubkey') }); + const status = await renderStatus(target, { viewerPubkey: await c.get('signer')?.getPublicKey() }); status.zapped = true; return c.json(status); diff --git a/src/controllers/api/timelines.ts b/src/controllers/api/timelines.ts index 27459a8..0880d84 100644 --- a/src/controllers/api/timelines.ts +++ b/src/controllers/api/timelines.ts @@ -11,7 +11,7 @@ import { renderReblog, renderStatus } from '@/views/mastodon/statuses.ts'; const homeTimelineController: AppController = async (c) => { const params = paginationSchema.parse(c.req.query()); - const pubkey = c.get('pubkey')!; + const pubkey = await c.get('signer')?.getPublicKey()!; const authors = await getFeedPubkeys(pubkey); return renderStatuses(c, [{ authors, kinds: [1, 6], ...params }]); }; @@ -61,11 +61,13 @@ async function renderStatuses(c: AppContext, filters: NostrFilter[]) { return c.json([]); } + const viewerPubkey = await c.get('signer')?.getPublicKey(); + const statuses = (await Promise.all(events.map((event) => { if (event.kind === 6) { - return renderReblog(event, { viewerPubkey: c.get('pubkey') }); + return renderReblog(event, { viewerPubkey }); } - return renderStatus(event, { viewerPubkey: c.get('pubkey') }); + return renderStatus(event, { viewerPubkey }); }))).filter((boolean) => boolean); if (!statuses.length) { diff --git a/src/middleware/auth98.ts b/src/middleware/auth98.ts index db025ae..d761b95 100644 --- a/src/middleware/auth98.ts +++ b/src/middleware/auth98.ts @@ -8,7 +8,6 @@ import { validateAuthEvent, } from '@/utils/nip98.ts'; import { localRequest } from '@/utils/api.ts'; -import { APISigner } from '@/signers/APISigner.ts'; import { findUser, User } from '@/db/users.ts'; /** @@ -70,7 +69,7 @@ function withProof( opts?: ParseAuthRequestOpts, ): AppMiddleware { return async (c, next) => { - const pubkey = c.get('pubkey'); + const pubkey = await c.get('signer')?.getPublicKey(); const proof = c.get('proof') || await obtainProof(c, opts); // Prevent people from accidentally using the wrong account. This has no other security implications. @@ -90,9 +89,16 @@ function withProof( /** Get the proof over Nostr Connect. */ async function obtainProof(c: AppContext, opts?: ParseAuthRequestOpts) { + const signer = c.get('signer'); + if (!signer) { + throw new HTTPException(401, { + res: c.json({ error: 'No way to sign Nostr event' }, 401), + }); + } + const req = localRequest(c); const reqEvent = await buildAuthEventTemplate(req, opts); - const resEvent = await new APISigner(c).signEvent(reqEvent); + const resEvent = await signer.signEvent(reqEvent); const result = await validateAuthEvent(req, resEvent, opts); if (result.success) { diff --git a/src/middleware/store.ts b/src/middleware/store.ts index 60055b3..40b7c59 100644 --- a/src/middleware/store.ts +++ b/src/middleware/store.ts @@ -4,7 +4,7 @@ import { Storages } from '@/storages.ts'; /** Store middleware. */ const storeMiddleware: AppMiddleware = async (c, next) => { - const pubkey = c.get('pubkey'); + const pubkey = await c.get('signer')?.getPublicKey(); if (pubkey) { const store = new UserStore(pubkey, Storages.admin); diff --git a/src/utils/api.ts b/src/utils/api.ts index cba7c66..3dfdfa3 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -10,7 +10,6 @@ import { type AppContext } from '@/app.ts'; import { Conf } from '@/config.ts'; import * as pipeline from '@/pipeline.ts'; import { AdminSigner } from '@/signers/AdminSigner.ts'; -import { APISigner } from '@/signers/APISigner.ts'; import { Storages } from '@/storages.ts'; import { nostrNow } from '@/utils.ts'; @@ -21,7 +20,13 @@ type EventStub = TypeFest.SetOptional { - const signer = new APISigner(c); + const signer = c.get('signer'); + + if (!signer) { + throw new HTTPException(401, { + res: c.json({ error: 'No way to sign Nostr event' }, 401), + }); + } const event = await signer.signEvent({ content: '', diff --git a/src/views.ts b/src/views.ts index 9c31dfc..451ce14 100644 --- a/src/views.ts +++ b/src/views.ts @@ -59,8 +59,10 @@ async function renderStatuses(c: AppContext, ids: string[], signal = AbortSignal const sortedEvents = [...events].sort((a, b) => ids.indexOf(a.id) - ids.indexOf(b.id)); + const viewerPubkey = await c.get('signer')?.getPublicKey(); + const statuses = await Promise.all( - sortedEvents.map((event) => renderStatus(event, { viewerPubkey: c.get('pubkey') })), + sortedEvents.map((event) => renderStatus(event, { viewerPubkey })), ); // TODO: pagination with min_id and max_id based on the order of `ids`.