diff --git a/entrypoint.example.ts b/entrypoint.example.ts index 75a23f8..d933176 100644 --- a/entrypoint.example.ts +++ b/entrypoint.example.ts @@ -5,6 +5,7 @@ import { hellthreadPolicy, noopPolicy, pipeline, + pubkeyBanPolicy, rateLimitPolicy, readStdin, writeStdout, @@ -16,6 +17,7 @@ for await (const msg of readStdin()) { [hellthreadPolicy, { limit: 100 }], [antiDuplicationPolicy, { ttl: 60000, minLength: 50 }], [rateLimitPolicy, { whitelist: ['127.0.0.1'] }], + [pubkeyBanPolicy, ['e810fafa1e89cdf80cced8e013938e87e21b699b24c8570537be92aec4b12c18']], ]); writeStdout(result); diff --git a/mod.ts b/mod.ts index d05e972..bef8ed9 100644 --- a/mod.ts +++ b/mod.ts @@ -1,6 +1,7 @@ export { default as antiDuplicationPolicy } from './src/policies/anti-duplication-policy.ts'; export { default as hellthreadPolicy } from './src/policies/hellthread-policy.ts'; export { default as noopPolicy } from './src/policies/noop-policy.ts'; +export { default as pubkeyBanPolicy } from './src/policies/pubkey-ban-policy.ts'; export { default as rateLimitPolicy } from './src/policies/rate-limit-policy.ts'; export { default as readOnlyPolicy } from './src/policies/read-only-policy.ts'; diff --git a/src/policies/pubkey-ban-policy.test.ts b/src/policies/pubkey-ban-policy.test.ts new file mode 100644 index 0000000..6cd04c8 --- /dev/null +++ b/src/policies/pubkey-ban-policy.test.ts @@ -0,0 +1,15 @@ +import { assert } from '../deps.ts'; +import { buildEvent, buildInputMessage } from '../test.ts'; + +import pubkeyBanPolicy from './pubkey-ban-policy.ts'; + +Deno.test('blocks banned pubkeys', async () => { + const msgA = buildInputMessage({ event: buildEvent({ pubkey: 'A' }) }); + const msgB = buildInputMessage({ event: buildEvent({ pubkey: 'B' }) }); + const msgC = buildInputMessage({ event: buildEvent({ pubkey: 'C' }) }); + + assert((await pubkeyBanPolicy(msgA, [])).action === 'accept'); + assert((await pubkeyBanPolicy(msgA, ['A'])).action === 'reject'); + assert((await pubkeyBanPolicy(msgC, ['B', 'A'])).action === 'accept'); + assert((await pubkeyBanPolicy(msgB, ['B', 'A'])).action === 'reject'); +}); diff --git a/src/policies/pubkey-ban-policy.ts b/src/policies/pubkey-ban-policy.ts new file mode 100644 index 0000000..691a7ba --- /dev/null +++ b/src/policies/pubkey-ban-policy.ts @@ -0,0 +1,22 @@ +import { Policy } from '../types.ts'; + +/** Ban individual pubkeys from publishing events to the relay. */ +const pubkeyPolicy: Policy = ({ event: { id, pubkey } }, pubkeys = []) => { + const isMatch = pubkeys.includes(pubkey); + + if (isMatch) { + return { + id, + action: 'reject', + msg: 'Pubkey is banned.', + }; + } + + return { + id, + action: 'accept', + msg: '', + }; +}; + +export default pubkeyPolicy; diff --git a/src/policies/read-only-policy.test.ts b/src/policies/read-only-policy.test.ts index ee61c75..3da9cc0 100644 --- a/src/policies/read-only-policy.test.ts +++ b/src/policies/read-only-policy.test.ts @@ -1,23 +1,10 @@ import { assert } from '../deps.ts'; +import { buildInputMessage } from '../test.ts'; import readOnlyPolicy from './read-only-policy.ts'; Deno.test('always rejects', async () => { - const result = await readOnlyPolicy({ - event: { - kind: 1, - id: '', - content: '', - created_at: 0, - pubkey: '', - sig: '', - tags: [], - }, - receivedAt: 0, - sourceInfo: '127.0.0.1', - sourceType: 'IP4', - type: 'new', - }); - + const msg = buildInputMessage(); + const result = await readOnlyPolicy(msg); assert(result.action === 'reject'); }); diff --git a/src/test.ts b/src/test.ts new file mode 100644 index 0000000..0685a54 --- /dev/null +++ b/src/test.ts @@ -0,0 +1,31 @@ +import type { Event, InputMessage } from './types.ts'; + +/** Constructs a fake event for tests. */ +function buildEvent(attrs: Partial = {}): Event { + const event: Event = { + kind: 1, + id: '', + content: '', + created_at: 0, + pubkey: '', + sig: '', + tags: [], + }; + + return Object.assign(event, attrs); +} + +/** Constructs a fake strfry input message for tests. */ +function buildInputMessage(attrs: Partial = {}): InputMessage { + const msg = { + event: buildEvent(), + receivedAt: 0, + sourceInfo: '127.0.0.1', + sourceType: 'IP4', + type: 'new', + }; + + return Object.assign(msg, attrs); +} + +export { buildEvent, buildInputMessage };