Compare commits

..

3 commits

Author SHA1 Message Date
Siddharth Singh
b613334312
send default spec in policy worker 2025-05-07 09:56:20 +05:30
Siddharth Singh
e947e6d3f4
add more tests for policy edge cases 2025-05-07 08:49:21 +05:30
Siddharth Singh
e1d4a00a93
add close method to policyworker for testing purposes 2025-05-07 07:06:12 +05:30
5 changed files with 97 additions and 31 deletions

View file

@ -3,7 +3,7 @@ import { HashtagPolicy } from '@nostrify/policies';
export default class TestPolicy implements NPolicy { export default class TestPolicy implements NPolicy {
call(event: NostrEvent): Promise<NostrRelayOK> { call(event: NostrEvent): Promise<NostrRelayOK> {
return new HashtagPolicy(['other-blocked-tag']).call(event); return new HashtagPolicy(['potato']).call(event);
} }
info?: NostrRelayInfo | undefined; info?: NostrRelayInfo | undefined;
} }

View file

@ -1,5 +1,5 @@
import { type AppController } from '@/app.ts'; import { type AppController } from '@/app.ts';
import { createPolicyEvent } from '@/utils/policies/mod.ts'; import { createPolicyEvent, DEFAULT_POLICY_SPEC } from '@/utils/policies/mod.ts';
import { policyRegistry } from '@/utils/policies/mod.ts'; import { policyRegistry } from '@/utils/policies/mod.ts';
import { z } from 'zod'; import { z } from 'zod';
@ -27,7 +27,7 @@ export const adminCurrentPolicyController: AppController = async (c) => {
}]).then((events) => events[0]); }]).then((events) => events[0]);
if (current) return c.json({ spec: JSON.parse(current.content) }); if (current) return c.json({ spec: JSON.parse(current.content) });
return c.json({ spec: { policies: [] } }); return c.json({ spec: { policies: DEFAULT_POLICY_SPEC } });
}; };
const PolicySpecSchema = z.object({ const PolicySpecSchema = z.object({

View file

@ -1,45 +1,103 @@
import { DittoConf } from '@ditto/conf'; import { DittoConf } from '@ditto/conf';
import { generateSecretKey, nip19 } from 'nostr-tools'; import { generateSecretKey, nip19 } from 'nostr-tools';
import { PolicyWorker } from '@/workers/policy.ts'; import { PolicyWorker } from '@/workers/policy.ts';
import { assertEquals } from '@std/assert/assert-equals'; import { assert, assertEquals } from '@std/assert';
import { join } from '@std/path'; import { join } from '@std/path';
import { createPolicyEvent } from '../utils/policies/mod.ts';
import { createTestDB } from '../test.ts';
const blocked = { const blocked = {
id: '19afd70437944671e7f5a02b29221ad444ef7cf60113a5731667e272e59a3979', id: '19afd70437944671e7f5a02b29221ad444ef7cf60113a5731667e272e59a3979',
pubkey: '79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798', pubkey: '79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798',
kind: 1, kind: 1,
tags: [['t', 'porn'], ['t', 'other-blocked-tag']], tags: [['t', 'potato'], ['t', 'tomato']],
content: 'this is a test of the policy system', content: 'this is a test of the policy system',
sig: sig:
'1d73a7480cfd737b89dc1e0e7175dff67119915f31d24a279a45d56622f4b991b01e431d07b693ee6cd652f3f27274d9e203ee43ae44af7e70ce8647e5326196', '1d73a7480cfd737b89dc1e0e7175dff67119915f31d24a279a45d56622f4b991b01e431d07b693ee6cd652f3f27274d9e203ee43ae44af7e70ce8647e5326196',
created_at: 1743685015, created_at: 1743685015,
}; };
Deno.test('PolicyWorker with script policy', async () => { Deno.test('Tests for policy workers', async (t) => {
const conf = new DittoConf( const nsec = nip19.nsecEncode(generateSecretKey());
new Map([ const env = new Map([
['DITTO_NSEC', nip19.nsecEncode(generateSecretKey())], ['DITTO_NSEC', nsec],
['DATABASE_URL', Deno.env.get('DATABASE_URL')], ['DATABASE_URL', Deno.env.get('DATABASE_URL')],
['DITTO_POLICY', join(Deno.cwd(), 'fixtures', 'policy.ts')], ]);
]),
);
const worker = new PolicyWorker(conf); await using db = await createTestDB();
const [, , ok] = await worker.call(blocked);
assertEquals(ok, false);
});
Deno.test('PolicyWorker with event policy', async () => { async function clearPolicyEvents() {
const conf = new DittoConf( await db.store.remove([{ kinds: [11984] }]);
new Map([ }
['DITTO_NSEC', nip19.nsecEncode(generateSecretKey())], await clearPolicyEvents();
['DATABASE_URL', Deno.env.get('DATABASE_URL')],
]), await t.step('No DITTO_POLICY, no event-based policy (default allow)', async () => {
); await clearPolicyEvents();
const conf = new DittoConf(env);
const worker = new PolicyWorker(conf); const worker = new PolicyWorker(conf);
const [, , ok] = await worker.call(blocked); const [, , ok] = await worker.call(blocked);
assertEquals(ok, false); assertEquals(ok, true, 'Should not block anything in default config');
});
// Edge 2: No DITTO_POLICY, valid event-based policy (blocks "tomato")
await t.step('No DITTO_POLICY, valid event-based policy (blocks "tomato")', async () => {
await clearPolicyEvents();
const conf = new DittoConf(env);
await db.store.event(
await createPolicyEvent(conf, {
policies: [
{ 'name': 'HashtagPolicy', 'params': { 'hashtags': ['tomato'] } },
],
}),
);
using worker = new PolicyWorker(conf);
const [, , ok] = await worker.call(blocked);
assert(!ok, 'Event policy should block events with #tomato');
});
// Edge 3: DITTO_POLICY set but policy script is missing (should fallback)
await t.step('DITTO_POLICY set but file is missing (should fallback)', async () => {
await clearPolicyEvents();
const thisEnv = new Map([...env, ['DITTO_POLICY', 'not-a-policy.ts']]);
const conf = new DittoConf(thisEnv);
using worker = new PolicyWorker(conf);
const [, , ok] = await worker.call(blocked);
assertEquals(
ok,
true,
'Missing script policy should fall back to allowing (default policy)',
);
});
await t.step('Both DITTO_POLICY and event policy', async () => {
await clearPolicyEvents();
const policyScriptPath = join(Deno.cwd(), 'fixtures', 'policy.ts');
const thisEnv = new Map([...env, ['DITTO_POLICY', policyScriptPath]]);
const conf = new DittoConf(thisEnv);
await db.store.event(
await createPolicyEvent(conf, {
policies: [
{ 'name': 'HashtagPolicy', 'params': { 'hashtags': ['tomato'] } },
],
}),
);
using worker = new PolicyWorker(conf);
const [, , ok] = await worker.call(blocked);
assert(
!ok,
'Both script and event-based policy are present: event should be blocked if either blocks',
);
});
}); });

View file

@ -86,4 +86,8 @@ export class PolicyWorker implements NPolicy {
throw new Error(`DITTO_POLICY (error importing policy): ${conf.policy}`); throw new Error(`DITTO_POLICY (error importing policy): ${conf.policy}`);
} }
} }
[Symbol.dispose]() {
this.worker.close();
}
} }

View file

@ -85,6 +85,10 @@ export class CustomPolicy implements NPolicy {
this.policy = new PipePolicy(policies); this.policy = new PipePolicy(policies);
} }
close() {
self.close();
}
} }
Comlink.expose(new CustomPolicy()); Comlink.expose(new CustomPolicy());