diff --git a/deno.lock b/deno.lock index 459bfe5..1ca549c 100644 --- a/deno.lock +++ b/deno.lock @@ -1,5 +1,16 @@ { "version": "3", + "packages": { + "specifiers": { + "npm:@types/node": "npm:@types/node@18.16.19" + }, + "npm": { + "@types/node@18.16.19": { + "integrity": "sha512-IXl7o+R9iti9eBW4Wg2hx1xQDig183jj7YLn8F7udNceyfkbn1ZxmzZXuak20gR40D7pIkIY1kYGx5VIGbaHKA==", + "dependencies": {} + } + } + }, "remote": { "https://deno.land/std@0.181.0/_util/asserts.ts": "178dfc49a464aee693a7e285567b3d0b555dc805ff490505a8aae34f9cfb1462", "https://deno.land/std@0.181.0/bytes/bytes_list.ts": "b4cbdfd2c263a13e8a904b12d082f6177ea97d9297274a4be134e989450dfa6a", diff --git a/src/policies/size-limit-policy.test.ts b/src/policies/size-limit-policy.test.ts new file mode 100644 index 0000000..78bb9bb --- /dev/null +++ b/src/policies/size-limit-policy.test.ts @@ -0,0 +1,20 @@ +import { assertEquals } from '../deps.ts'; +import { buildEvent, buildInputMessage } from '../test.ts'; + +import sizeLimitPolicy from './size-limit-policy.ts'; + +Deno.test('Size limit policy test', async () => { + // Create a message under 12KB + const smallContent = 'Hello'.repeat(100); // Well under 12KB + const smallMessage = buildInputMessage({ event: buildEvent({ content: smallContent }) }); + + // Create a message over 12KB + const largeContent = 'Hello'.repeat(2500); // Over 12KB + const largeMessage = buildInputMessage({ event: buildEvent({ content: largeContent }) }); + + // Test that a small message is accepted + assertEquals((await sizeLimitPolicy(smallMessage)).action, 'accept', 'Small message should be accepted'); + + // Test that a large message is rejected + assertEquals((await sizeLimitPolicy(largeMessage)).action, 'reject', 'Large message should be rejected'); +}); diff --git a/src/policies/size-limit-policy.ts b/src/policies/size-limit-policy.ts new file mode 100644 index 0000000..fc036bf --- /dev/null +++ b/src/policies/size-limit-policy.ts @@ -0,0 +1,50 @@ +import { Policy } from '../types.ts'; + +/** Policy options for `sizeLimitPolicy`. */ +interface SizeLimitOptions { + /** Maximum size of the message content in bytes. Default: 12KB (12288 bytes) */ + maxContentSize?: number; + /** List of excluded event kinds */ + excludeKinds?: number[]; +} + +/** + * Reject events larger than a specified size. + * + * @example + * ```ts + * // Reject events larger than a custom size (15KB) and exclude event kinds [1, 2]. + * sizeLimitPolicy(msg, { maxContentSize: 15000, excludeKinds: [1, 2] }); + * // Reject events larger than the default size (12KB) and exclude event kinds [3]. + * sizeLimitPolicy(msg, { excludeKinds: [3] }); + * ``` + */ +const sizeLimitPolicy: Policy = ({ event: { id, content, kind } }, opts = {}) => { + const { + excludeKinds = [3], // Exclude large events such as contact lists + maxContentSize = 12 * 1024 // Default size limit is 12KB + } = opts; + + // Convert the content into bytes and check its size + const contentSize = new TextEncoder().encode(content).length; + + if (contentSize > maxContentSize && !excludeKinds.includes(kind)) { + const timestamp = new Date().toISOString(); + const logMessage = `${timestamp}: ID: ${id}, Kind: ${kind} rejected, ${contentSize} > max content size (${maxContentSize})\n`; + Deno.writeTextFileSync('./size-limit-policy.log', logMessage, { append: true }); + return { + id, + action: 'reject', + msg: `blocked: message is too large.`, + //msg: `blocked: ${timestamp}: ID: ${id}, Kind: ${kind} rejected, ${contentSize} > max content size (${maxContentSize})\n`, + }; + } + + return { + id, + action: 'accept', + msg: '', + }; +}; + +export default sizeLimitPolicy;