diff --git a/deno.lock b/deno.lock index 0356894a..1b740e4e 100644 --- a/deno.lock +++ b/deno.lock @@ -7,6 +7,7 @@ "jsr:@denosaurs/plug@1.0.3": "1.0.3", "jsr:@esroyo/scoped-performance@^3.1.0": "3.1.0", "jsr:@gfx/canvas-wasm@~0.4.2": "0.4.2", + "jsr:@gleasonator/policy@*": "0.9.0", "jsr:@hono/hono@^4.4.6": "4.7.5", "jsr:@negrel/http-ece@0.6.0": "0.6.0", "jsr:@negrel/webpush@0.3": "0.3.0", @@ -133,6 +134,13 @@ "jsr:@std/encoding@1.0.5" ] }, + "@gleasonator/policy@0.9.0": { + "integrity": "483f87c3a18fb39f795495d3388453193f1115ab7e130981121f7828ce6b51bb", + "dependencies": [ + "jsr:@nostrify/nostrify@0.36", + "jsr:@nostrify/policies" + ] + }, "@hono/hono@4.7.5": { "integrity": "36a7e1b3db8a58e5dc2bd36a76be53346f0966e04c24c635c4d6f58875575b0a" }, diff --git a/fixtures/policy.ts b/fixtures/policy.ts index e44c0924..b893fe48 100644 --- a/fixtures/policy.ts +++ b/fixtures/policy.ts @@ -3,7 +3,7 @@ import { HashtagPolicy } from '@nostrify/policies'; export default class TestPolicy implements NPolicy { call(event: NostrEvent): Promise { - return new HashtagPolicy(['porn']).call(event); + return new HashtagPolicy(['other-blocked-tag']).call(event); } info?: NostrRelayInfo | undefined; } diff --git a/packages/conf/DittoConf.ts b/packages/conf/DittoConf.ts index 278217b0..59a3fde4 100644 --- a/packages/conf/DittoConf.ts +++ b/packages/conf/DittoConf.ts @@ -368,10 +368,6 @@ export class DittoConf { return this.env.get('DITTO_POLICY') || path.join(this.dataDir, 'policy.ts'); } - get policyMode(): 'script' | 'event' { - return this.env.get('DITTO_CUSTOM_POLICY') ? 'script' : 'event'; - } - /** Absolute path to the data directory used by Ditto. */ get dataDir(): string { return this.env.get('DITTO_DATA_DIR') || path.join(Deno.cwd(), 'data'); diff --git a/packages/ditto/controllers/api/policies.ts b/packages/ditto/controllers/api/policies.ts index 97242038..48cdfc09 100644 --- a/packages/ditto/controllers/api/policies.ts +++ b/packages/ditto/controllers/api/policies.ts @@ -26,10 +26,10 @@ export const adminCurrentPolicyController: AppController = async (c) => { kinds: [11984], }]).then((events) => events[0]); - if (current) return c.json({ mode: conf.policyMode, spec: JSON.parse(current.content) }); + if (current) return c.json({ spec: JSON.parse(current.content) }); await relay.event(await createPolicyEvent(conf, DEFAULT_POLICY_SPEC)); - return c.json({ mode: conf.policyMode, spec: DEFAULT_POLICY_SPEC }); + return c.json({ spec: DEFAULT_POLICY_SPEC }); }; const PolicySpecSchema = z.object({ @@ -41,13 +41,6 @@ const PolicySpecSchema = z.object({ export const adminUpdatePolicyController: AppController = async (c) => { const { relay, conf } = c.var; - if (conf.policyMode === 'script') { - return c.json({ - error: - "The Ditto policy mode is set to 'script'. You will not be able to use the Policy UI until you change it to 'event'.", - }); - } - try { const req = await c.req.json(); const parsed = PolicySpecSchema.parse(req); diff --git a/packages/ditto/workers/policy.test.ts b/packages/ditto/workers/policy.test.ts index 02815597..c6c42a7c 100644 --- a/packages/ditto/workers/policy.test.ts +++ b/packages/ditto/workers/policy.test.ts @@ -9,8 +9,8 @@ const blocked = { id: '19afd70437944671e7f5a02b29221ad444ef7cf60113a5731667e272e59a3979', pubkey: '79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798', kind: 1, - tags: [['t', 'porn']], - content: 'this is a test of the policy system #porn', + tags: [['t', 'porn'], ['t', 'other-blocked-tag']], + content: 'this is a test of the policy system', sig: '1d73a7480cfd737b89dc1e0e7175dff67119915f31d24a279a45d56622f4b991b01e431d07b693ee6cd652f3f27274d9e203ee43ae44af7e70ce8647e5326196', created_at: 1743685015, @@ -20,7 +20,6 @@ Deno.test('PolicyWorker with script policy', async () => { const conf = new DittoConf( new Map([ ['DITTO_NSEC', nip19.nsecEncode(generateSecretKey())], - ['DITTO_CUSTOM_POLICY', '1'], ['DATABASE_URL', Deno.env.get('DATABASE_URL')], ['DITTO_POLICY', join(Deno.cwd(), 'fixtures', 'policy.ts')], ]), diff --git a/packages/ditto/workers/policy.ts b/packages/ditto/workers/policy.ts index 5eebc77a..32eb9691 100644 --- a/packages/ditto/workers/policy.ts +++ b/packages/ditto/workers/policy.ts @@ -53,28 +53,15 @@ export class PolicyWorker implements NPolicy { path: conf.policy, databaseUrl: conf.databaseUrl, pubkey: await conf.signer.getPublicKey(), - mode: conf.policyMode, }); logi({ level: 'info', ns: 'ditto.system.policy', - msg: `Using ${conf.policyMode === 'script' ? 'custom' : 'event'} policy`, - path: conf.policyMode === 'script' ? conf.policy : undefined, + msg: `Initialising custom policy`, + path: conf.policy, enabled: true, }); } catch (e) { - if (e instanceof Error && e.message.includes('Module not found')) { - logi({ - level: 'info', - ns: 'ditto.system.policy', - msg: 'Custom policy not found ', - path: null, - enabled: false, - }); - this.enabled = false; - return; - } - if (e instanceof Error && e.message.includes('PGlite is not supported in worker threads')) { logi({ level: 'warn', diff --git a/packages/ditto/workers/policy.worker.ts b/packages/ditto/workers/policy.worker.ts index 0803b45d..8b15c695 100644 --- a/packages/ditto/workers/policy.worker.ts +++ b/packages/ditto/workers/policy.worker.ts @@ -13,26 +13,14 @@ import { logi } from '@soapbox/logi'; // @ts-ignore Don't try to access the env from this worker. Deno.env = new Map(); -interface PolicyInitCommon { +interface PolicyInit { /** Database URL to connect to. */ databaseUrl: string; /** Admin pubkey to use for DittoPgStore checks. */ pubkey: string; - mode: 'script' | 'event'; -} - -interface ScriptPolicyInit { - /** Path to the policy module (https, jsr, file, etc) */ path: string; - mode: 'script'; } -interface EventPolicyInit { - mode: 'event'; -} - -type PolicyInit = PolicyInitCommon & (ScriptPolicyInit | EventPolicyInit); - export class CustomPolicy implements NPolicy { private policy: NPolicy = new ReadOnlyPolicy(); @@ -55,40 +43,47 @@ export class CustomPolicy implements NPolicy { }, }); + const policies: NPolicy[] = []; const store = new DittoPgStore({ db, conf, timeout: 5_000, }); - - if (opts.mode === 'script') { + try { const Policy = (await import(opts.path)).default; - this.policy = new Policy({ db, store, pubkey }); - } else { - const registry = new PolicyRegistry({ store, antiDuplicationPolicyStore: await Deno.openKv() }); - const policies: NPolicy[] = []; - const event = await store - .query([{ kinds: [11984], authors: [await conf.signer.getPublicKey()] }]) - .then((results) => results[0]); - - const spec: PolicySpec = event ? JSON.parse(event.content) : DEFAULT_POLICY_SPEC; - - for (const item of spec.policies) { - const policy = registry.available[item.name]; - if (!policy) continue; - try { - policies.push(policy.instantiate(item.params || {})); - } catch (e) { - logi({ - level: 'error', - ns: 'ditto.system.policy.worker', - msg: `Error instantiating policy ${item.name} with params \`${JSON.stringify(item.params)}\`: ${e}`, - }); - } + policies.push(new Policy({ db, store, pubkey })); + } catch (e) { + if (e instanceof Error && e.message.includes('Module not found')) { + logi({ + level: 'info', + ns: 'ditto.system.policy', + msg: 'Custom policy not found ', + path: null, + }); } - - this.policy = new PipePolicy(policies); } + const registry = new PolicyRegistry({ store, antiDuplicationPolicyStore: await Deno.openKv() }); + const event = await store + .query([{ kinds: [11984], authors: [await conf.signer.getPublicKey()] }]) + .then((results) => results[0]); + + const spec: PolicySpec = event ? JSON.parse(event.content) : DEFAULT_POLICY_SPEC; + + for (const item of spec.policies) { + const policy = registry.available[item.name]; + if (!policy) continue; + try { + policies.push(policy.instantiate(item.params || {})); + } catch (e) { + logi({ + level: 'error', + ns: 'ditto.system.policy.worker', + msg: `Error instantiating policy ${item.name} with params \`${JSON.stringify(item.params)}\`: ${e}`, + }); + } + } + + this.policy = new PipePolicy(policies); } }