From 0841563d6981953822ca8589cd9116b0d44c66df Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 20 Feb 2025 12:04:52 -0600 Subject: [PATCH] Remove AdminSigner, Conf.pubkey, Conf.nsec, add Conf.signer --- packages/api/middleware/confMw.test.ts | 2 +- packages/conf/DittoConf.test.ts | 15 ++++--- packages/conf/DittoConf.ts | 42 ++++++++++--------- packages/ditto/controllers/api/accounts.ts | 4 +- packages/ditto/controllers/api/admin.ts | 22 +++++++--- packages/ditto/controllers/api/ditto.ts | 26 +++++++----- packages/ditto/controllers/api/instance.ts | 4 +- .../ditto/controllers/api/notifications.ts | 2 +- packages/ditto/controllers/api/pleroma.ts | 8 ++-- packages/ditto/controllers/api/reports.ts | 4 +- packages/ditto/controllers/api/statuses.ts | 2 +- packages/ditto/controllers/api/suggestions.ts | 17 +++++--- packages/ditto/controllers/api/timelines.ts | 2 +- packages/ditto/controllers/api/trends.ts | 6 +-- .../ditto/controllers/nostr/relay-info.ts | 2 +- packages/ditto/middleware/auth98Middleware.ts | 2 +- packages/ditto/pipeline.ts | 16 ++++--- packages/ditto/signers/AdminSigner.ts | 9 ---- packages/ditto/storages.ts | 4 +- packages/ditto/storages/AdminStore.ts | 6 ++- packages/ditto/storages/hydrate.bench.ts | 2 +- packages/ditto/storages/hydrate.ts | 17 ++++---- packages/ditto/test.ts | 2 +- packages/ditto/trends.ts | 3 +- packages/ditto/utils/api.ts | 5 +-- packages/ditto/utils/connect.ts | 2 +- packages/ditto/utils/instance.ts | 2 +- packages/ditto/utils/nip05.ts | 2 +- packages/ditto/utils/outbox.ts | 2 +- packages/ditto/utils/pleroma.ts | 6 +-- packages/ditto/utils/zap-split.ts | 5 +-- .../ditto/views/mastodon/notifications.ts | 4 +- packages/ditto/workers/policy.ts | 2 +- scripts/admin-event.ts | 4 +- scripts/admin-role.ts | 4 +- scripts/setup-kind0.ts | 3 +- 36 files changed, 135 insertions(+), 125 deletions(-) delete mode 100644 packages/ditto/signers/AdminSigner.ts diff --git a/packages/api/middleware/confMw.test.ts b/packages/api/middleware/confMw.test.ts index 5eac707c..350a585f 100644 --- a/packages/api/middleware/confMw.test.ts +++ b/packages/api/middleware/confMw.test.ts @@ -10,7 +10,7 @@ Deno.test('confMw', async () => { const app = new Hono(); - app.get('/', confMw(env), (c) => c.text(c.var.conf.pubkey)); + app.get('/', confMw(env), async (c) => c.text(await c.var.conf.signer.getPublicKey())); const response = await app.request('/'); const body = await response.text(); diff --git a/packages/conf/DittoConf.test.ts b/packages/conf/DittoConf.test.ts index c2e87c46..b6c2b707 100644 --- a/packages/conf/DittoConf.test.ts +++ b/packages/conf/DittoConf.test.ts @@ -9,12 +9,11 @@ Deno.test('DittoConfig', async (t) => { const config = new DittoConf(env); - await t.step('nsec', () => { - assertEquals(config.nsec, 'nsec19shyxpuzd0cq2p5078fwnws7tyykypud6z205fzhlmlrs2vpz6hs83zwkw'); - }); - - await t.step('pubkey', () => { - assertEquals(config.pubkey, '1ba0c5ed1bbbf3b7eb0d7843ba16836a0201ea68a76bafcba507358c45911ff6'); + await t.step('signer', async () => { + assertEquals( + await config.signer.getPublicKey(), + '1ba0c5ed1bbbf3b7eb0d7843ba16836a0201ea68a76bafcba507358c45911ff6', + ); }); }); @@ -22,8 +21,8 @@ Deno.test('DittoConfig defaults', async (t) => { const env = new Map(); const config = new DittoConf(env); - await t.step('nsec throws', () => { - assertThrows(() => config.nsec); + await t.step('signer throws', () => { + assertThrows(() => config.signer); }); await t.step('port', () => { diff --git a/packages/conf/DittoConf.ts b/packages/conf/DittoConf.ts index 456e9cd2..b7f5be79 100644 --- a/packages/conf/DittoConf.ts +++ b/packages/conf/DittoConf.ts @@ -1,10 +1,11 @@ import os from 'node:os'; import path from 'node:path'; -import ISO6391, { type LanguageCode } from 'iso-639-1'; -import { getPublicKey, nip19 } from 'nostr-tools'; +import { NSecSigner } from '@nostrify/nostrify'; import { decodeBase64 } from '@std/encoding/base64'; import { encodeBase64Url } from '@std/encoding/base64url'; +import ISO6391, { type LanguageCode } from 'iso-639-1'; +import { nip19 } from 'nostr-tools'; import { getEcdsaPublicKey } from './utils/crypto.ts'; import { optionalBooleanSchema, optionalNumberSchema } from './utils/schema.ts'; @@ -14,35 +15,36 @@ import { mergeURLPath } from './utils/url.ts'; export class DittoConf { constructor(private env: { get(key: string): string | undefined }) {} - /** Cached parsed admin pubkey value. */ - private _pubkey: string | undefined; + /** Cached parsed admin signer. */ + private _signer: NSecSigner | undefined; /** Cached parsed VAPID public key value. */ private _vapidPublicKey: Promise | undefined; - /** Ditto admin secret key in nip19 format. This is the way it's configured by an admin. */ - get nsec(): `nsec1${string}` { - const value = this.env.get('DITTO_NSEC'); - if (!value) { + /** + * Ditto admin secret key in hex format. + * @deprecated Use `signer` instead. TODO: handle auth tokens. + */ + get seckey(): Uint8Array { + const nsec = this.env.get('DITTO_NSEC'); + + if (!nsec) { throw new Error('Missing DITTO_NSEC'); } - if (!value.startsWith('nsec1')) { + + if (!nsec.startsWith('nsec1')) { throw new Error('Invalid DITTO_NSEC'); } - return value as `nsec1${string}`; + + return nip19.decode(nsec as `nsec1${string}`).data; } - /** Ditto admin secret key in hex format. */ - get seckey(): Uint8Array { - return nip19.decode(this.nsec).data; - } - - /** Ditto admin public key in hex format. */ - get pubkey(): string { - if (!this._pubkey) { - this._pubkey = getPublicKey(this.seckey); + /** Ditto admin signer. */ + get signer(): NSecSigner { + if (!this._signer) { + this._signer = new NSecSigner(this.seckey); } - return this._pubkey; + return this._signer; } /** Port to use when serving the HTTP server. */ diff --git a/packages/ditto/controllers/api/accounts.ts b/packages/ditto/controllers/api/accounts.ts index 8a1b9e3d..27710063 100644 --- a/packages/ditto/controllers/api/accounts.ts +++ b/packages/ditto/controllers/api/accounts.ts @@ -211,7 +211,9 @@ const accountStatusesController: AppController = async (c) => { 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 }), + store.query([{ kinds: [30382], authors: [await conf.signer.getPublicKey()], '#d': [pubkey], limit: 1 }], { + signal, + }), ]); if (author) { diff --git a/packages/ditto/controllers/api/admin.ts b/packages/ditto/controllers/api/admin.ts index 1e3b4615..9e9ba5d0 100644 --- a/packages/ditto/controllers/api/admin.ts +++ b/packages/ditto/controllers/api/admin.ts @@ -43,13 +43,15 @@ const adminAccountsController: AppController = async (c) => { staff, } = adminAccountQuerySchema.parse(c.req.query()); + const adminPubkey = await conf.signer.getPublicKey(); + if (pending) { if (disabled || silenced || suspended || sensitized) { return c.json([]); } const orig = await store.query( - [{ kinds: [30383], authors: [conf.pubkey], '#k': ['3036'], '#n': ['pending'], ...params }], + [{ kinds: [30383], authors: [adminPubkey], '#k': ['3036'], '#n': ['pending'], ...params }], { signal }, ); @@ -86,7 +88,10 @@ const adminAccountsController: AppController = async (c) => { n.push('moderator'); } - const events = await store.query([{ kinds: [30382], authors: [conf.pubkey], '#n': n, ...params }], { signal }); + const events = await store.query( + [{ kinds: [30382], authors: [adminPubkey], '#n': n, ...params }], + { signal }, + ); const pubkeys = new Set( events @@ -157,9 +162,11 @@ const adminActionController: AppController = async (c) => { } if (data.type === 'revoke_name') { n.revoke_name = true; - store.remove([{ kinds: [30360], authors: [conf.pubkey], '#p': [authorId] }]).catch((e: unknown) => { - logi({ level: 'error', ns: 'ditto.api.admin.account.action', type: data.type, error: errorJson(e) }); - }); + store.remove([{ kinds: [30360], authors: [await conf.signer.getPublicKey()], '#p': [authorId] }]).catch( + (e: unknown) => { + logi({ level: 'error', ns: 'ditto.api.admin.account.action', type: data.type, error: errorJson(e) }); + }, + ); } await updateUser(authorId, n, c); @@ -185,7 +192,10 @@ const adminApproveController: AppController = async (c) => { return c.json({ error: 'Invalid NIP-05' }, 400); } - const [existing] = await store.query([{ kinds: [30360], authors: [conf.pubkey], '#d': [r], limit: 1 }]); + const [existing] = await store.query([ + { kinds: [30360], authors: [await conf.signer.getPublicKey()], '#d': [r], limit: 1 }, + ]); + if (existing) { return c.json({ error: 'NIP-05 already granted to another user' }, 400); } diff --git a/packages/ditto/controllers/api/ditto.ts b/packages/ditto/controllers/api/ditto.ts index b9fef08a..752124dc 100644 --- a/packages/ditto/controllers/api/ditto.ts +++ b/packages/ditto/controllers/api/ditto.ts @@ -9,7 +9,6 @@ import { createEvent, paginated, parseBody, updateAdminEvent } from '@/utils/api import { getInstanceMetadata } from '@/utils/instance.ts'; import { deleteTag } from '@/utils/tags.ts'; import { DittoZapSplits, getZapSplits } from '@/utils/zap-split.ts'; -import { AdminSigner } from '@/signers/AdminSigner.ts'; import { screenshotsSchema } from '@/schemas/nostr.ts'; import { booleanParamSchema, percentageSchema, wsUrlSchema } from '@/schema.ts'; import { hydrateEvents } from '@/storages/hydrate.ts'; @@ -33,7 +32,7 @@ export const adminRelaysController: AppController = async (c) => { const store = await Storages.db(); const [event] = await store.query([ - { kinds: [10002], authors: [conf.pubkey], limit: 1 }, + { kinds: [10002], authors: [await conf.signer.getPublicKey()], limit: 1 }, ]); if (!event) { @@ -44,10 +43,11 @@ export const adminRelaysController: AppController = async (c) => { }; export const adminSetRelaysController: AppController = async (c) => { + const { conf } = c.var; const store = await Storages.db(); const relays = relaySchema.array().parse(await c.req.json()); - const event = await new AdminSigner().signEvent({ + const event = await conf.signer.signEvent({ kind: 10002, tags: relays.map(({ url, marker }) => marker ? ['r', url, marker] : ['r', url]), content: '', @@ -98,7 +98,7 @@ export const nameRequestController: AppController = async (c) => { ['r', name], ['L', 'nip05.domain'], ['l', name.split('@')[1], 'nip05.domain'], - ['p', conf.pubkey], + ['p', await conf.signer.getPublicKey()], ], }, c); @@ -124,7 +124,7 @@ export const nameRequestsController: AppController = async (c) => { const filter: NostrFilter = { kinds: [30383], - authors: [conf.pubkey], + authors: [await conf.signer.getPublicKey()], '#k': ['3036'], '#p': [pubkey], ...params, @@ -179,7 +179,9 @@ export const updateZapSplitsController: AppController = async (c) => { return c.json({ error: result.error }, 400); } - const dittoZapSplit = await getZapSplits(store, conf.pubkey); + const adminPubkey = await conf.signer.getPublicKey(); + + const dittoZapSplit = await getZapSplits(store, adminPubkey); if (!dittoZapSplit) { return c.json({ error: 'Zap split not activated, restart the server.' }, 404); } @@ -192,7 +194,7 @@ export const updateZapSplitsController: AppController = async (c) => { } await updateListAdminEvent( - { kinds: [30078], authors: [conf.pubkey], '#d': ['pub.ditto.zapSplits'], limit: 1 }, + { kinds: [30078], authors: [adminPubkey], '#d': ['pub.ditto.zapSplits'], limit: 1 }, (tags) => pubkeys.reduce((accumulator, pubkey) => { return addTag(accumulator, ['p', pubkey, data[pubkey].weight.toString(), data[pubkey].message]); @@ -215,7 +217,9 @@ export const deleteZapSplitsController: AppController = async (c) => { return c.json({ error: result.error }, 400); } - const dittoZapSplit = await getZapSplits(store, conf.pubkey); + const adminPubkey = await conf.signer.getPublicKey(); + + const dittoZapSplit = await getZapSplits(store, adminPubkey); if (!dittoZapSplit) { return c.json({ error: 'Zap split not activated, restart the server.' }, 404); } @@ -223,7 +227,7 @@ export const deleteZapSplitsController: AppController = async (c) => { const { data } = result; await updateListAdminEvent( - { kinds: [30078], authors: [conf.pubkey], '#d': ['pub.ditto.zapSplits'], limit: 1 }, + { kinds: [30078], authors: [adminPubkey], '#d': ['pub.ditto.zapSplits'], limit: 1 }, (tags) => data.reduce((accumulator, currentValue) => { return deleteTag(accumulator, ['p', currentValue]); @@ -238,7 +242,7 @@ export const getZapSplitsController: AppController = async (c) => { const { conf } = c.var; const store = c.get('store'); - const dittoZapSplit: DittoZapSplits | undefined = await getZapSplits(store, conf.pubkey) ?? {}; + const dittoZapSplit: DittoZapSplits | undefined = await getZapSplits(store, await conf.signer.getPublicKey()) ?? {}; if (!dittoZapSplit) { return c.json({ error: 'Zap split not activated, restart the server.' }, 404); } @@ -311,7 +315,7 @@ export const updateInstanceController: AppController = async (c) => { const { conf } = c.var; const body = await parseBody(c.req.raw); const result = updateInstanceSchema.safeParse(body); - const pubkey = conf.pubkey; + const pubkey = await conf.signer.getPublicKey(); if (!result.success) { return c.json(result.error, 422); diff --git a/packages/ditto/controllers/api/instance.ts b/packages/ditto/controllers/api/instance.ts index d17a91c1..8c3c6e4c 100644 --- a/packages/ditto/controllers/api/instance.ts +++ b/packages/ditto/controllers/api/instance.ts @@ -68,7 +68,7 @@ const instanceV1Controller: AppController = async (c) => { version, email: meta.email, nostr: { - pubkey: conf.pubkey, + pubkey: await conf.signer.getPublicKey(), relay: `${wsProtocol}//${host}/relay`, }, rules: [], @@ -141,7 +141,7 @@ const instanceV2Controller: AppController = async (c) => { }, }, nostr: { - pubkey: conf.pubkey, + pubkey: await conf.signer.getPublicKey(), relay: `${wsProtocol}//${host}/relay`, }, pleroma: { diff --git a/packages/ditto/controllers/api/notifications.ts b/packages/ditto/controllers/api/notifications.ts index fd8b5720..dfd4a03c 100644 --- a/packages/ditto/controllers/api/notifications.ts +++ b/packages/ditto/controllers/api/notifications.ts @@ -68,7 +68,7 @@ const notificationsController: AppController = async (c) => { } if (types.has('ditto:name_grant') && !account_id) { - filters.push({ kinds: [30360], authors: [conf.pubkey], '#p': [pubkey], ...params }); + filters.push({ kinds: [30360], authors: [await conf.signer.getPublicKey()], '#p': [pubkey], ...params }); } return renderNotifications(filters, types, params, c); diff --git a/packages/ditto/controllers/api/pleroma.ts b/packages/ditto/controllers/api/pleroma.ts index 302eaca6..721347f3 100644 --- a/packages/ditto/controllers/api/pleroma.ts +++ b/packages/ditto/controllers/api/pleroma.ts @@ -2,7 +2,6 @@ import { z } from 'zod'; import { type AppController } from '@/app.ts'; import { configSchema, elixirTupleSchema } from '@/schemas/pleroma-api.ts'; -import { AdminSigner } from '@/signers/AdminSigner.ts'; import { Storages } from '@/storages.ts'; import { createAdminEvent, updateAdminEvent, updateUser } from '@/utils/api.ts'; import { lookupPubkey } from '@/utils/lookup.ts'; @@ -34,7 +33,6 @@ const configController: AppController = async (c) => { /** Pleroma admin config controller. */ const updateConfigController: AppController = async (c) => { const { conf } = c.var; - const { pubkey } = conf; const store = await Storages.db(); const configs = await getPleromaConfigs(store, c.req.raw.signal); @@ -44,7 +42,7 @@ const updateConfigController: AppController = async (c) => { await createAdminEvent({ kind: 30078, - content: await new AdminSigner().nip44.encrypt(pubkey, JSON.stringify(configs)), + content: await conf.signer.nip44.encrypt(await conf.signer.getPublicKey(), JSON.stringify(configs)), tags: [ ['d', 'pub.ditto.pleroma.config'], ['encrypted', 'nip44'], @@ -77,7 +75,7 @@ const pleromaAdminTagController: AppController = async (c) => { if (!pubkey) continue; await updateAdminEvent( - { kinds: [30382], authors: [conf.pubkey], '#d': [pubkey], limit: 1 }, + { kinds: [30382], authors: [await conf.signer.getPublicKey()], '#d': [pubkey], limit: 1 }, (prev) => { const tags = prev?.tags ?? [['d', pubkey]]; @@ -110,7 +108,7 @@ const pleromaAdminUntagController: AppController = async (c) => { if (!pubkey) continue; await updateAdminEvent( - { kinds: [30382], authors: [conf.pubkey], '#d': [pubkey], limit: 1 }, + { kinds: [30382], authors: [await conf.signer.getPublicKey()], '#d': [pubkey], limit: 1 }, (prev) => ({ kind: 30382, content: prev?.content ?? '', diff --git a/packages/ditto/controllers/api/reports.ts b/packages/ditto/controllers/api/reports.ts index b25e7233..11285825 100644 --- a/packages/ditto/controllers/api/reports.ts +++ b/packages/ditto/controllers/api/reports.ts @@ -36,7 +36,7 @@ const reportController: AppController = async (c) => { const tags = [ ['p', account_id, category], - ['P', conf.pubkey], + ['P', await conf.signer.getPublicKey()], ]; for (const status of status_ids) { @@ -70,7 +70,7 @@ const adminReportsController: AppController = async (c) => { const filter: NostrFilter = { kinds: [30383], - authors: [conf.pubkey], + authors: [await conf.signer.getPublicKey()], '#k': ['1984'], ...params, }; diff --git a/packages/ditto/controllers/api/statuses.ts b/packages/ditto/controllers/api/statuses.ts index 7c2276c7..6aa53308 100644 --- a/packages/ditto/controllers/api/statuses.ts +++ b/packages/ditto/controllers/api/statuses.ts @@ -196,7 +196,7 @@ const createStatusController: AppController = async (c) => { if (conf.zapSplitsEnabled) { const meta = n.json().pipe(n.metadata()).catch({}).parse(author?.content); const lnurl = getLnurl(meta); - const dittoZapSplit = await getZapSplits(store, conf.pubkey); + const dittoZapSplit = await getZapSplits(store, await conf.signer.getPublicKey()); if (lnurl && dittoZapSplit) { const totalSplit = Object.values(dittoZapSplit).reduce((total, { weight }) => total + weight, 0); for (const zapPubkey in dittoZapSplit) { diff --git a/packages/ditto/controllers/api/suggestions.ts b/packages/ditto/controllers/api/suggestions.ts index 0a85b95b..5dbf0d14 100644 --- a/packages/ditto/controllers/api/suggestions.ts +++ b/packages/ditto/controllers/api/suggestions.ts @@ -31,8 +31,8 @@ async function renderV2Suggestions(c: AppContext, params: { offset: number; limi const pubkey = await signer?.getPublicKey(); const filters: NostrFilter[] = [ - { kinds: [30382], authors: [conf.pubkey], '#n': ['suggested'], limit }, - { kinds: [1985], '#L': ['pub.ditto.trends'], '#l': [`#p`], authors: [conf.pubkey], limit: 1 }, + { kinds: [30382], authors: [await conf.signer.getPublicKey()], '#n': ['suggested'], limit }, + { kinds: [1985], '#L': ['pub.ditto.trends'], '#l': [`#p`], authors: [await conf.signer.getPublicKey()], limit: 1 }, ]; if (pubkey) { @@ -41,13 +41,20 @@ async function renderV2Suggestions(c: AppContext, params: { offset: number; limi } const events = await store.query(filters, { signal }); + const adminPubkey = await conf.signer.getPublicKey(); const [userEvents, followsEvent, mutesEvent, trendingEvent] = [ - events.filter((event) => matchFilter({ kinds: [30382], authors: [conf.pubkey], '#n': ['suggested'] }, event)), + events.filter((event) => matchFilter({ kinds: [30382], authors: [adminPubkey], '#n': ['suggested'] }, event)), pubkey ? events.find((event) => matchFilter({ kinds: [3], authors: [pubkey] }, event)) : undefined, pubkey ? events.find((event) => matchFilter({ kinds: [10000], authors: [pubkey] }, event)) : undefined, events.find((event) => - matchFilter({ kinds: [1985], '#L': ['pub.ditto.trends'], '#l': [`#p`], authors: [conf.pubkey], limit: 1 }, event) + matchFilter({ + kinds: [1985], + '#L': ['pub.ditto.trends'], + '#l': [`#p`], + authors: [adminPubkey], + limit: 1, + }, event) ), ]; @@ -95,7 +102,7 @@ export const localSuggestionsController: AppController = async (c) => { const store = c.get('store'); const grants = await store.query( - [{ kinds: [30360], authors: [conf.pubkey], ...params }], + [{ kinds: [30360], authors: [await conf.signer.getPublicKey()], ...params }], { signal }, ); diff --git a/packages/ditto/controllers/api/timelines.ts b/packages/ditto/controllers/api/timelines.ts index e8b8987a..a6f872b9 100644 --- a/packages/ditto/controllers/api/timelines.ts +++ b/packages/ditto/controllers/api/timelines.ts @@ -95,7 +95,7 @@ const suggestedTimelineController: AppController = async (c) => { const params = c.get('pagination'); const [follows] = await store.query( - [{ kinds: [3], authors: [conf.pubkey], limit: 1 }], + [{ kinds: [3], authors: [await conf.signer.getPublicKey()], limit: 1 }], ); const authors = [...getTagSet(follows?.tags ?? [], 'p')]; diff --git a/packages/ditto/controllers/api/trends.ts b/packages/ditto/controllers/api/trends.ts index 88ea335e..f14cf0b7 100644 --- a/packages/ditto/controllers/api/trends.ts +++ b/packages/ditto/controllers/api/trends.ts @@ -53,7 +53,7 @@ const trendingTagsController: AppController = async (c) => { async function getTrendingHashtags(conf: DittoConf) { const store = await Storages.db(); - const trends = await getTrendingTags(store, 't', conf.pubkey); + const trends = await getTrendingTags(store, 't', await conf.signer.getPublicKey()); return trends.map((trend) => { const hashtag = trend.value; @@ -106,7 +106,7 @@ const trendingLinksController: AppController = async (c) => { async function getTrendingLinks(conf: DittoConf) { const store = await Storages.db(); - const trends = await getTrendingTags(store, 'r', conf.pubkey); + const trends = await getTrendingTags(store, 'r', await conf.signer.getPublicKey()); return Promise.all(trends.map(async (trend) => { const link = trend.value; @@ -148,7 +148,7 @@ const trendingStatusesController: AppController = async (c) => { kinds: [1985], '#L': ['pub.ditto.trends'], '#l': ['#e'], - authors: [conf.pubkey], + authors: [await conf.signer.getPublicKey()], until, limit: 1, }]); diff --git a/packages/ditto/controllers/nostr/relay-info.ts b/packages/ditto/controllers/nostr/relay-info.ts index 54576b38..d4721cdc 100644 --- a/packages/ditto/controllers/nostr/relay-info.ts +++ b/packages/ditto/controllers/nostr/relay-info.ts @@ -14,7 +14,7 @@ const relayInfoController: AppController = async (c) => { return c.json({ name: meta.name, description: meta.about, - pubkey: conf.pubkey, + pubkey: await conf.signer.getPublicKey(), contact: meta.email, supported_nips: [1, 5, 9, 11, 16, 45, 50, 46, 98], software: 'Ditto', diff --git a/packages/ditto/middleware/auth98Middleware.ts b/packages/ditto/middleware/auth98Middleware.ts index 889e5ea9..573853f0 100644 --- a/packages/ditto/middleware/auth98Middleware.ts +++ b/packages/ditto/middleware/auth98Middleware.ts @@ -40,7 +40,7 @@ function requireRole(role: UserRole, opts?: ParseAuthRequestOpts): AppMiddleware const [user] = await store.query([{ kinds: [30382], - authors: [conf.pubkey], + authors: [await conf.signer.getPublicKey()], '#d': [proof.pubkey], limit: 1, }]); diff --git a/packages/ditto/pipeline.ts b/packages/ditto/pipeline.ts index 07be1bd9..602d0e2b 100644 --- a/packages/ditto/pipeline.ts +++ b/packages/ditto/pipeline.ts @@ -11,7 +11,6 @@ import { Conf } from '@/config.ts'; import { DittoPush } from '@/DittoPush.ts'; import { DittoEvent } from '@/interfaces/DittoEvent.ts'; import { RelayError } from '@/RelayError.ts'; -import { AdminSigner } from '@/signers/AdminSigner.ts'; import { hydrateEvents } from '@/storages/hydrate.ts'; import { Storages } from '@/storages.ts'; import { eventAge, Time } from '@/utils.ts'; @@ -83,7 +82,7 @@ async function handleEvent(event: DittoEvent, opts: PipelineOpts): Promise } // Ensure the event doesn't violate the policy. - if (event.pubkey !== Conf.pubkey) { + if (event.pubkey !== await Conf.signer.getPublicKey()) { await policyFilter(event, opts.signal); } @@ -297,11 +296,12 @@ async function webPush(event: NostrEvent): Promise { } async function generateSetEvents(event: NostrEvent): Promise { - const tagsAdmin = event.tags.some(([name, value]) => ['p', 'P'].includes(name) && value === Conf.pubkey); + const signer = Conf.signer; + const pubkey = await signer.getPublicKey(); + + const tagsAdmin = event.tags.some(([name, value]) => ['p', 'P'].includes(name) && value === pubkey); if (event.kind === 1984 && tagsAdmin) { - const signer = new AdminSigner(); - const rel = await signer.signEvent({ kind: 30383, content: '', @@ -310,8 +310,8 @@ async function generateSetEvents(event: NostrEvent): Promise { ['p', event.pubkey], ['k', '1984'], ['n', 'open'], - ...[...getTagSet(event.tags, 'p')].map((pubkey) => ['P', pubkey]), - ...[...getTagSet(event.tags, 'e')].map((pubkey) => ['e', pubkey]), + ...[...getTagSet(event.tags, 'p')].map((value) => ['P', value]), + ...[...getTagSet(event.tags, 'e')].map((value) => ['e', value]), ], created_at: Math.floor(Date.now() / 1000), }); @@ -320,8 +320,6 @@ async function generateSetEvents(event: NostrEvent): Promise { } if (event.kind === 3036 && tagsAdmin) { - const signer = new AdminSigner(); - const rel = await signer.signEvent({ kind: 30383, content: '', diff --git a/packages/ditto/signers/AdminSigner.ts b/packages/ditto/signers/AdminSigner.ts deleted file mode 100644 index 5aea2e21..00000000 --- a/packages/ditto/signers/AdminSigner.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { NSecSigner } from '@nostrify/nostrify'; -import { Conf } from '@/config.ts'; - -/** Sign events as the Ditto server. */ -export class AdminSigner extends NSecSigner { - constructor() { - super(Conf.seckey); - } -} diff --git a/packages/ditto/storages.ts b/packages/ditto/storages.ts index 320714f7..7b77a037 100644 --- a/packages/ditto/storages.ts +++ b/packages/ditto/storages.ts @@ -42,7 +42,7 @@ export class Storages { const db = await this.database(); const store = new DittoPgStore({ db, - pubkey: Conf.pubkey, + pubkey: await Conf.signer.getPublicKey(), timeout: Conf.db.timeouts.default, notify: Conf.notifyEnabled, }); @@ -68,7 +68,7 @@ export class Storages { const db = await this.db(); const [relayList] = await db.query([ - { kinds: [10002], authors: [Conf.pubkey], limit: 1 }, + { kinds: [10002], authors: [await Conf.signer.getPublicKey()], limit: 1 }, ]); const tags = relayList?.tags ?? []; diff --git a/packages/ditto/storages/AdminStore.ts b/packages/ditto/storages/AdminStore.ts index 4ebe2743..ae03c59d 100644 --- a/packages/ditto/storages/AdminStore.ts +++ b/packages/ditto/storages/AdminStore.ts @@ -18,15 +18,17 @@ export class AdminStore implements NStore { const users = await this.store.query([{ kinds: [30382], - authors: [Conf.pubkey], + authors: [await Conf.signer.getPublicKey()], '#d': [...pubkeys], limit: pubkeys.size, }]); + const adminPubkey = await Conf.signer.getPublicKey(); + return events.filter((event) => { const user = users.find( ({ kind, pubkey, tags }) => - kind === 30382 && pubkey === Conf.pubkey && tags.find(([name]) => name === 'd')?.[1] === event.pubkey, + kind === 30382 && pubkey === adminPubkey && tags.find(([name]) => name === 'd')?.[1] === event.pubkey, ); const n = getTagSet(user?.tags ?? [], 'n'); diff --git a/packages/ditto/storages/hydrate.bench.ts b/packages/ditto/storages/hydrate.bench.ts index 026b1f81..4da8afbf 100644 --- a/packages/ditto/storages/hydrate.bench.ts +++ b/packages/ditto/storages/hydrate.bench.ts @@ -10,5 +10,5 @@ const testStats = JSON.parse(await Deno.readTextFile('fixtures/stats.json')); const events = testEvents.slice(0, 20); Deno.bench('assembleEvents with home feed', () => { - assembleEvents(events, testEvents, testStats); + assembleEvents('', events, testEvents, testStats); }); diff --git a/packages/ditto/storages/hydrate.ts b/packages/ditto/storages/hydrate.ts index 0836bd76..96341a1f 100644 --- a/packages/ditto/storages/hydrate.ts +++ b/packages/ditto/storages/hydrate.ts @@ -79,15 +79,18 @@ async function hydrateEvents(opts: HydrateOpts): Promise { // Dedupe events. const results = [...new Map(cache.map((event) => [event.id, event])).values()]; + const admin = await Conf.signer.getPublicKey(); + // First connect all the events to each-other, then connect the connected events to the original list. - assembleEvents(results, results, stats); - assembleEvents(events, results, stats); + assembleEvents(admin, results, results, stats); + assembleEvents(admin, events, results, stats); return events; } /** Connect the events in list `b` to the DittoEvent fields in list `a`. */ export function assembleEvents( + admin: string, a: DittoEvent[], b: DittoEvent[], stats: { @@ -96,8 +99,6 @@ export function assembleEvents( favicons: Record; }, ): DittoEvent[] { - const admin = Conf.pubkey; - const authorStats = stats.authors.reduce((result, { pubkey, ...stat }) => { result[pubkey] = { ...stat, @@ -316,7 +317,7 @@ async function gatherProfiles({ events, store, signal }: HydrateOpts): Promise { +async function gatherUsers({ events, store, signal }: HydrateOpts): Promise { const pubkeys = new Set(events.map((event) => event.pubkey)); if (!pubkeys.size) { @@ -324,13 +325,13 @@ function gatherUsers({ events, store, signal }: HydrateOpts): Promise { +async function gatherInfo({ events, store, signal }: HydrateOpts): Promise { const ids = new Set(); for (const event of events) { @@ -344,7 +345,7 @@ function gatherInfo({ events, store, signal }: HydrateOpts): Promise { - const signer = new AdminSigner(); + const signer = Conf.signer; const event = await signer.signEvent({ content: '', @@ -126,7 +125,7 @@ function updateEventInfo(id: string, n: Record, c: AppContext): } async function updateNames(k: number, d: string, n: Record, c: AppContext): Promise { - const signer = new AdminSigner(); + const signer = Conf.signer; const admin = await signer.getPublicKey(); return updateAdminEvent( diff --git a/packages/ditto/utils/connect.ts b/packages/ditto/utils/connect.ts index 7726fa89..095b93c4 100644 --- a/packages/ditto/utils/connect.ts +++ b/packages/ditto/utils/connect.ts @@ -20,7 +20,7 @@ export async function getClientConnectUri(signal?: AbortSignal): Promise url: Conf.localDomain, }; - uri.host = Conf.pubkey; + uri.host = await Conf.signer.getPublicKey(); uri.searchParams.set('relay', Conf.relay); uri.searchParams.set('metadata', JSON.stringify(metadata)); diff --git a/packages/ditto/utils/instance.ts b/packages/ditto/utils/instance.ts index c0b9c0d4..3f746e07 100644 --- a/packages/ditto/utils/instance.ts +++ b/packages/ditto/utils/instance.ts @@ -18,7 +18,7 @@ export interface InstanceMetadata extends NostrMetadata { /** Get and parse instance metadata from the kind 0 of the admin user. */ export async function getInstanceMetadata(store: NStore, signal?: AbortSignal): Promise { const [event] = await store.query( - [{ kinds: [0], authors: [Conf.pubkey], limit: 1 }], + [{ kinds: [0], authors: [await Conf.signer.getPublicKey()], limit: 1 }], { signal }, ); diff --git a/packages/ditto/utils/nip05.ts b/packages/ditto/utils/nip05.ts index 798fabdf..6c53c18c 100644 --- a/packages/ditto/utils/nip05.ts +++ b/packages/ditto/utils/nip05.ts @@ -57,7 +57,7 @@ export async function localNip05Lookup(store: NStore, localpart: string): Promis const [grant] = await store.query([{ kinds: [30360], '#d': [`${localpart}@${Conf.url.host}`], - authors: [Conf.pubkey], + authors: [await Conf.signer.getPublicKey()], limit: 1, }]); diff --git a/packages/ditto/utils/outbox.ts b/packages/ditto/utils/outbox.ts index 891cccb8..074518bc 100644 --- a/packages/ditto/utils/outbox.ts +++ b/packages/ditto/utils/outbox.ts @@ -6,7 +6,7 @@ export async function getRelays(store: NStore, pubkey: string): Promise(); const events = await store.query([ - { kinds: [10002], authors: [pubkey, Conf.pubkey], limit: 2 }, + { kinds: [10002], authors: [pubkey, await Conf.signer.getPublicKey()], limit: 2 }, ]); for (const event of events) { diff --git a/packages/ditto/utils/pleroma.ts b/packages/ditto/utils/pleroma.ts index 05c35b7c..db3ca6a1 100644 --- a/packages/ditto/utils/pleroma.ts +++ b/packages/ditto/utils/pleroma.ts @@ -2,11 +2,11 @@ import { NSchema as n, NStore } from '@nostrify/nostrify'; import { Conf } from '@/config.ts'; import { configSchema } from '@/schemas/pleroma-api.ts'; -import { AdminSigner } from '@/signers/AdminSigner.ts'; import { PleromaConfigDB } from '@/utils/PleromaConfigDB.ts'; export async function getPleromaConfigs(store: NStore, signal?: AbortSignal): Promise { - const { pubkey } = Conf; + const signer = Conf.signer; + const pubkey = await signer.getPublicKey(); const [event] = await store.query([{ kinds: [30078], @@ -20,7 +20,7 @@ export async function getPleromaConfigs(store: NStore, signal?: AbortSignal): Pr } try { - const decrypted = await new AdminSigner().nip44.decrypt(Conf.pubkey, event.content); + const decrypted = await signer.nip44.decrypt(pubkey, event.content); const configs = n.json().pipe(configSchema.array()).catch([]).parse(decrypted); return new PleromaConfigDB(configs); } catch (_e) { diff --git a/packages/ditto/utils/zap-split.ts b/packages/ditto/utils/zap-split.ts index e5df1538..85b6f056 100644 --- a/packages/ditto/utils/zap-split.ts +++ b/packages/ditto/utils/zap-split.ts @@ -1,4 +1,3 @@ -import { AdminSigner } from '@/signers/AdminSigner.ts'; import { Conf } from '@/config.ts'; import { NSchema as n, NStore } from '@nostrify/nostrify'; import { nostrNow } from '@/utils.ts'; @@ -38,13 +37,13 @@ export async function getZapSplits(store: NStore, pubkey: string): Promise name === 'p' && value === opts.viewerPubkey); if (event.kind === 1 && mentioned) { @@ -29,7 +29,7 @@ function renderNotification(event: DittoEvent, opts: RenderNotificationOpts) { return renderReaction(event, opts); } - if (event.kind === 30360 && event.pubkey === Conf.pubkey) { + if (event.kind === 30360 && event.pubkey === await Conf.signer.getPublicKey()) { return renderNameGrant(event); } diff --git a/packages/ditto/workers/policy.ts b/packages/ditto/workers/policy.ts index 7b3d23b0..02de539c 100644 --- a/packages/ditto/workers/policy.ts +++ b/packages/ditto/workers/policy.ts @@ -48,7 +48,7 @@ class PolicyWorker implements NPolicy { await this.worker.init({ path: Conf.policy, databaseUrl: Conf.databaseUrl, - pubkey: Conf.pubkey, + pubkey: await Conf.signer.getPublicKey(), }); logi({ diff --git a/scripts/admin-event.ts b/scripts/admin-event.ts index 70f8ed48..aec9e145 100644 --- a/scripts/admin-event.ts +++ b/scripts/admin-event.ts @@ -1,12 +1,12 @@ import { JsonParseStream } from '@std/json/json-parse-stream'; import { TextLineStream } from '@std/streams/text-line-stream'; -import { AdminSigner } from '../packages/ditto/signers/AdminSigner.ts'; +import { Conf } from '../packages/ditto/config.ts'; import { Storages } from '../packages/ditto/storages.ts'; import { type EventStub } from '../packages/ditto/utils/api.ts'; import { nostrNow } from '../packages/ditto/utils.ts'; -const signer = new AdminSigner(); +const signer = Conf.signer; const store = await Storages.db(); const readable = Deno.stdin.readable diff --git a/scripts/admin-role.ts b/scripts/admin-role.ts index 369440c9..4da9610e 100644 --- a/scripts/admin-role.ts +++ b/scripts/admin-role.ts @@ -1,7 +1,7 @@ import { NSchema } from '@nostrify/nostrify'; import { nip19 } from 'nostr-tools'; -import { AdminSigner } from '../packages/ditto/signers/AdminSigner.ts'; +import { Conf } from '../packages/ditto/config.ts'; import { Storages } from '../packages/ditto/storages.ts'; import { nostrNow } from '../packages/ditto/utils.ts'; @@ -20,7 +20,7 @@ if (!['admin', 'user'].includes(role)) { Deno.exit(1); } -const signer = new AdminSigner(); +const signer = Conf.signer; const admin = await signer.getPublicKey(); const [existing] = await store.query([{ diff --git a/scripts/setup-kind0.ts b/scripts/setup-kind0.ts index ff7cbd1a..85f7a6ca 100644 --- a/scripts/setup-kind0.ts +++ b/scripts/setup-kind0.ts @@ -1,7 +1,6 @@ import { Command } from 'commander'; import { NostrEvent } from 'nostr-tools'; -import { AdminSigner } from '../packages/ditto/signers/AdminSigner.ts'; import { nostrNow } from '../packages/ditto/utils.ts'; import { Conf } from '../packages/ditto/config.ts'; import { Storages } from '../packages/ditto/storages.ts'; @@ -36,7 +35,7 @@ if (import.meta.main) { content.picture = image; content.website = Conf.localDomain; - const signer = new AdminSigner(); + const signer = Conf.signer; const bare: Omit = { created_at: nostrNow(), kind: 0,