diff --git a/src/controllers/api/accounts.ts b/src/controllers/api/accounts.ts index 1ae6a4f6..d5beac03 100644 --- a/src/controllers/api/accounts.ts +++ b/src/controllers/api/accounts.ts @@ -9,7 +9,7 @@ import { booleanParamSchema, fileSchema } from '@/schema.ts'; import { Storages } from '@/storages.ts'; import { uploadFile } from '@/utils/upload.ts'; import { nostrNow } from '@/utils.ts'; -import { createEvent, paginated, parseBody, updateEvent, updateListEvent } from '@/utils/api.ts'; +import { assertAuthenticated, createEvent, paginated, parseBody, updateEvent, updateListEvent } from '@/utils/api.ts'; import { extractIdentifier, lookupAccount, lookupPubkey } from '@/utils/lookup.ts'; import { renderAccounts, renderEventAccounts, renderStatuses } from '@/views.ts'; import { accountFromPubkey, renderAccount } from '@/views/mastodon/accounts.ts'; @@ -82,6 +82,7 @@ const accountController: AppController = async (c) => { const event = await getAuthor(pubkey); if (event) { + assertAuthenticated(c, event); return c.json(await renderAccount(event)); } else { return c.json(await accountFromPubkey(pubkey)); @@ -97,6 +98,7 @@ const accountLookupController: AppController = async (c) => { const event = await lookupAccount(decodeURIComponent(acct)); if (event) { + assertAuthenticated(c, event); return c.json(await renderAccount(event)); } try { @@ -204,7 +206,15 @@ const accountStatusesController: AppController = async (c) => { const store = await Storages.db(); - const [user] = await store.query([{ kinds: [30382], authors: [Conf.pubkey], '#d': [pubkey], limit: 1 }], { signal }); + const [[author], [user]] = await Promise.all([ + store.query([{ kinds: [0], authors: [pubkey], limit: 1 }], { signal }), + store.query([{ kinds: [30382], authors: [Conf.pubkey], '#d': [pubkey], limit: 1 }], { signal }), + ]); + + if (author) { + assertAuthenticated(c, author); + } + const names = getTagSet(user?.tags ?? [], 'n'); if (names.has('disabled')) { diff --git a/src/controllers/api/statuses.ts b/src/controllers/api/statuses.ts index 8e66f7cb..98337777 100644 --- a/src/controllers/api/statuses.ts +++ b/src/controllers/api/statuses.ts @@ -16,7 +16,7 @@ import { lookupPubkey } from '@/utils/lookup.ts'; import { languageSchema } from '@/schema.ts'; import { Storages } from '@/storages.ts'; import { hydrateEvents } from '@/storages/hydrate.ts'; -import { createEvent, paginated, paginatedList, parseBody, updateListEvent } from '@/utils/api.ts'; +import { assertAuthenticated, createEvent, paginated, paginatedList, parseBody, updateListEvent } from '@/utils/api.ts'; import { getInvoice, getLnurl } from '@/utils/lnurl.ts'; import { purifyEvent } from '@/utils/purify.ts'; import { getZapSplits } from '@/utils/zap-split.ts'; @@ -48,13 +48,18 @@ const createStatusSchema = z.object({ const statusController: AppController = async (c) => { const id = c.req.param('id'); + const signal = AbortSignal.any([c.req.raw.signal, AbortSignal.timeout(1500)]); - const event = await getEvent(id, { - signal: AbortSignal.timeout(1500), - }); + const event = await getEvent(id, { signal }); + + if (event?.author) { + assertAuthenticated(c, event.author); + } if (event) { - return c.json(await renderStatus(event, { viewerPubkey: await c.get('signer')?.getPublicKey() })); + const viewerPubkey = await c.get('signer')?.getPublicKey(); + const status = await renderStatus(event, { viewerPubkey }); + return c.json(status); } return c.json({ error: 'Event not found.' }, 404); diff --git a/src/utils/api.ts b/src/utils/api.ts index 829a2cce..e47779d2 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -286,8 +286,22 @@ function localRequest(c: Context): Request { }); } +/** Actors with Bluesky's `!no-unauthenticated` self-label should require authorization to view. */ +function assertAuthenticated(c: AppContext, author: NostrEvent): void { + if ( + !c.get('signer') && author.tags.some(([name, value, ns]) => + name === 'l' && + value === '!no-unauthenticated' && + ns === 'com.atproto.label.defs#selfLabels' + ) + ) { + throw new HTTPException(401, { message: 'Sign-in required.' }); + } +} + export { activityJson, + assertAuthenticated, createAdminEvent, createEvent, type EventStub,