mirror of
https://gitlab.com/soapbox-pub/ditto.git
synced 2025-12-06 11:29:46 +00:00
Merge branch 'rm-admin-signer' into 'main'
Remove AdminSigner, Conf.pubkey, Conf.nsec, add Conf.signer See merge request soapbox-pub/ditto!682
This commit is contained in:
commit
64e71b0ba8
36 changed files with 135 additions and 125 deletions
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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<string, string>();
|
||||
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', () => {
|
||||
|
|
|
|||
|
|
@ -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<string | undefined> | 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. */
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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<string>(
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 ?? '',
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 },
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -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')];
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}]);
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}]);
|
||||
|
|
|
|||
|
|
@ -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<void>
|
|||
}
|
||||
|
||||
// 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<void> {
|
|||
}
|
||||
|
||||
async function generateSetEvents(event: NostrEvent): Promise<void> {
|
||||
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<void> {
|
|||
['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<void> {
|
|||
}
|
||||
|
||||
if (event.kind === 3036 && tagsAdmin) {
|
||||
const signer = new AdminSigner();
|
||||
|
||||
const rel = await signer.signEvent({
|
||||
kind: 30383,
|
||||
content: '',
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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 ?? [];
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -79,15 +79,18 @@ async function hydrateEvents(opts: HydrateOpts): Promise<DittoEvent[]> {
|
|||
// 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<string, string>;
|
||||
},
|
||||
): 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<D
|
|||
}
|
||||
|
||||
/** Collect users from the events. */
|
||||
function gatherUsers({ events, store, signal }: HydrateOpts): Promise<DittoEvent[]> {
|
||||
async function gatherUsers({ events, store, signal }: HydrateOpts): Promise<DittoEvent[]> {
|
||||
const pubkeys = new Set(events.map((event) => event.pubkey));
|
||||
|
||||
if (!pubkeys.size) {
|
||||
|
|
@ -324,13 +325,13 @@ function gatherUsers({ events, store, signal }: HydrateOpts): Promise<DittoEvent
|
|||
}
|
||||
|
||||
return store.query(
|
||||
[{ kinds: [30382], authors: [Conf.pubkey], '#d': [...pubkeys], limit: pubkeys.size }],
|
||||
[{ kinds: [30382], authors: [await Conf.signer.getPublicKey()], '#d': [...pubkeys], limit: pubkeys.size }],
|
||||
{ signal },
|
||||
);
|
||||
}
|
||||
|
||||
/** Collect info events from the events. */
|
||||
function gatherInfo({ events, store, signal }: HydrateOpts): Promise<DittoEvent[]> {
|
||||
async function gatherInfo({ events, store, signal }: HydrateOpts): Promise<DittoEvent[]> {
|
||||
const ids = new Set<string>();
|
||||
|
||||
for (const event of events) {
|
||||
|
|
@ -344,7 +345,7 @@ function gatherInfo({ events, store, signal }: HydrateOpts): Promise<DittoEvent[
|
|||
}
|
||||
|
||||
return store.query(
|
||||
[{ kinds: [30383], authors: [Conf.pubkey], '#d': [...ids], limit: ids.size }],
|
||||
[{ kinds: [30383], authors: [await Conf.signer.getPublicKey()], '#d': [...ids], limit: ids.size }],
|
||||
{ signal },
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ export async function createTestDB(opts?: { pure?: boolean }) {
|
|||
const store = new DittoPgStore({
|
||||
db,
|
||||
timeout: Conf.db.timeouts.default,
|
||||
pubkey: Conf.pubkey,
|
||||
pubkey: await Conf.signer.getPublicKey(),
|
||||
pure: opts?.pure ?? false,
|
||||
notify: true,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import { Kysely, sql } from 'kysely';
|
|||
|
||||
import { Conf } from '@/config.ts';
|
||||
import { handleEvent } from '@/pipeline.ts';
|
||||
import { AdminSigner } from '@/signers/AdminSigner.ts';
|
||||
import { Storages } from '@/storages.ts';
|
||||
import { errorJson } from '@/utils/log.ts';
|
||||
import { Time } from '@/utils/time.ts';
|
||||
|
|
@ -100,7 +99,7 @@ export async function updateTrendingTags(
|
|||
return;
|
||||
}
|
||||
|
||||
const signer = new AdminSigner();
|
||||
const signer = Conf.signer;
|
||||
|
||||
const label = await signer.signEvent({
|
||||
kind: 1985,
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import { type AppContext } from '@/app.ts';
|
|||
import { Conf } from '@/config.ts';
|
||||
import * as pipeline from '@/pipeline.ts';
|
||||
import { RelayError } from '@/RelayError.ts';
|
||||
import { AdminSigner } from '@/signers/AdminSigner.ts';
|
||||
import { Storages } from '@/storages.ts';
|
||||
import { nostrNow } from '@/utils.ts';
|
||||
import { parseFormData } from '@/utils/formdata.ts';
|
||||
|
|
@ -81,7 +80,7 @@ function updateListEvent(
|
|||
|
||||
/** Publish an admin event through the pipeline. */
|
||||
async function createAdminEvent(t: EventStub, c: AppContext): Promise<NostrEvent> {
|
||||
const signer = new AdminSigner();
|
||||
const signer = Conf.signer;
|
||||
|
||||
const event = await signer.signEvent({
|
||||
content: '',
|
||||
|
|
@ -126,7 +125,7 @@ function updateEventInfo(id: string, n: Record<string, boolean>, c: AppContext):
|
|||
}
|
||||
|
||||
async function updateNames(k: number, d: string, n: Record<string, boolean>, c: AppContext): Promise<NostrEvent> {
|
||||
const signer = new AdminSigner();
|
||||
const signer = Conf.signer;
|
||||
const admin = await signer.getPublicKey();
|
||||
|
||||
return updateAdminEvent(
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ export async function getClientConnectUri(signal?: AbortSignal): Promise<string>
|
|||
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));
|
||||
|
||||
|
|
|
|||
|
|
@ -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<InstanceMetadata> {
|
||||
const [event] = await store.query(
|
||||
[{ kinds: [0], authors: [Conf.pubkey], limit: 1 }],
|
||||
[{ kinds: [0], authors: [await Conf.signer.getPublicKey()], limit: 1 }],
|
||||
{ signal },
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}]);
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ export async function getRelays(store: NStore, pubkey: string): Promise<Set<stri
|
|||
const relays = new Set<`wss://${string}`>();
|
||||
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -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<PleromaConfigDB> {
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -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<Ditto
|
|||
}
|
||||
|
||||
export async function seedZapSplits(store: NStore) {
|
||||
const zapSplit: DittoZapSplits | undefined = await getZapSplits(store, Conf.pubkey);
|
||||
const zapSplit: DittoZapSplits | undefined = await getZapSplits(store, await Conf.signer.getPublicKey());
|
||||
|
||||
if (!zapSplit) {
|
||||
const dittoPubkey = '781a1527055f74c1f70230f10384609b34548f8ab6a0a6caa74025827f9fdae5';
|
||||
const dittoMsg = 'Official Ditto Account';
|
||||
|
||||
const signer = new AdminSigner();
|
||||
const signer = Conf.signer;
|
||||
const event = await signer.signEvent({
|
||||
content: '',
|
||||
created_at: nostrNow(),
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ interface RenderNotificationOpts {
|
|||
viewerPubkey: string;
|
||||
}
|
||||
|
||||
function renderNotification(event: DittoEvent, opts: RenderNotificationOpts) {
|
||||
async function renderNotification(event: DittoEvent, opts: RenderNotificationOpts) {
|
||||
const mentioned = !!event.tags.find(([name, value]) => 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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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([{
|
||||
|
|
|
|||
|
|
@ -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<NostrEvent, 'id' | 'sig' | 'pubkey'> = {
|
||||
created_at: nostrNow(),
|
||||
kind: 0,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue