From 801e68c6c46d06bb8b59f9d5d87ba51194da21c4 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Sat, 11 May 2024 12:03:41 -0300 Subject: [PATCH 1/8] fix: add error prefix according to NIP-01 --- src/policies/MuteListPolicy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/policies/MuteListPolicy.ts b/src/policies/MuteListPolicy.ts index 1db8556..cae08eb 100644 --- a/src/policies/MuteListPolicy.ts +++ b/src/policies/MuteListPolicy.ts @@ -10,7 +10,7 @@ export class MuteListPolicy implements NPolicy { const pubkeys = getTagSet(muteList?.tags ?? [], 'p'); if (pubkeys.has(event.pubkey)) { - return ['OK', event.id, false, 'You are banned in this server.']; + return ['OK', event.id, false, 'blocked: Your account has been deactivated.']; } return ['OK', event.id, true, '']; From fe66937bba49d6aa00679ed5da3bbf4b16c29069 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Sat, 11 May 2024 12:04:44 -0300 Subject: [PATCH 2/8] feat: do not allow deactivated accounts to post --- src/pipeline.ts | 42 ++++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/src/pipeline.ts b/src/pipeline.ts index 3eb8913..d05b09a 100644 --- a/src/pipeline.ts +++ b/src/pipeline.ts @@ -21,18 +21,10 @@ import { AdminSigner } from '@/signers/AdminSigner.ts'; import { lnurlCache } from '@/utils/lnurl.ts'; import { nip05Cache } from '@/utils/nip05.ts'; +import { MuteListPolicy } from '@/policies/MuteListPolicy.ts'; + const debug = Debug('ditto:pipeline'); -let UserPolicy: any; - -try { - UserPolicy = (await import('../data/policy.ts')).default; - debug('policy loaded from data/policy.ts'); -} catch (_e) { - // do nothing - debug('policy not found'); -} - /** * Common pipeline function to process (and maybe store) events. * It is idempotent, so it can be called multiple times for the same event. @@ -43,17 +35,8 @@ async function handleEvent(event: DittoEvent, signal: AbortSignal): Promise ${event.id}`); await hydrateEvent(event, signal); - if (UserPolicy) { - const result = await new UserPolicy().call(event, signal); - debug(JSON.stringify(result)); - const [_, _eventId, ok, reason] = result; - if (!ok) { - const [prefix, ...rest] = reason.split(': '); - throw new RelayError(prefix, rest.join(': ')); - } - } - await Promise.all([ + policyFilter(event), storeEvent(event, signal), parseMetadata(event, signal), processDeletions(event, signal), @@ -66,6 +49,25 @@ async function handleEvent(event: DittoEvent, signal: AbortSignal): Promise { + const UserPolicy = new MuteListPolicy(Conf.pubkey, Storages.admin); + const result = await UserPolicy.call(event); + + debug(JSON.stringify(result)); + + const [_, _eventId, ok, reason] = result; + if (!ok) { + const [prefix, ...rest] = reason.split(': '); + if (['duplicate', 'pow', 'blocked', 'rate-limited', 'invalid'].includes(prefix)) { + const error = new RelayError(prefix as any, rest.join(': ')); + return Promise.reject(error); + } else { + const error = new RelayError('error', rest.join(': ')); + return Promise.reject(error); + } + } +} + /** Encounter the event, and return whether it has already been encountered. */ async function encounterEvent(event: NostrEvent, signal: AbortSignal): Promise { const [existing] = await Storages.cache.query([{ ids: [event.id], limit: 1 }]); From 04968fefaa9abd57e24c0609d45392644e8e50a3 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Sat, 11 May 2024 14:02:24 -0300 Subject: [PATCH 3/8] test(MuteListPolicy): update error msg --- src/policies/MuteListPolicy.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/policies/MuteListPolicy.test.ts b/src/policies/MuteListPolicy.test.ts index 69561f8..2c3baa3 100644 --- a/src/policies/MuteListPolicy.test.ts +++ b/src/policies/MuteListPolicy.test.ts @@ -27,7 +27,7 @@ Deno.test('block event: muted user cannot post', async () => { const ok = await policy.call(event1authorUserMeCopy); - assertEquals(ok, ['OK', event1authorUserMeCopy.id, false, 'You are banned in this server.']); + assertEquals(ok, ['OK', event1authorUserMeCopy.id, false, 'blocked: Your account has been deactivated.']); }); Deno.test('allow event: user is NOT muted because there is no muted event', async () => { From 9bff7a5086b62a0f61d6ff2a84f891e591c18eb9 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 13 May 2024 12:30:56 -0500 Subject: [PATCH 4/8] Fix some issues in pipeline and utils/api.ts --- deno.json | 2 +- src/pipeline.ts | 17 +++++++++-------- src/utils/api.ts | 6 ++---- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/deno.json b/deno.json index 5d45588..0225510 100644 --- a/deno.json +++ b/deno.json @@ -20,7 +20,7 @@ "@db/sqlite": "jsr:@db/sqlite@^0.11.1", "@isaacs/ttlcache": "npm:@isaacs/ttlcache@^1.4.1", "@noble/secp256k1": "npm:@noble/secp256k1@^2.0.0", - "@nostrify/nostrify": "jsr:@nostrify/nostrify@^0.17.1", + "@nostrify/nostrify": "jsr:@nostrify/nostrify@^0.19.0", "@sentry/deno": "https://deno.land/x/sentry@7.112.2/index.mjs", "@soapbox/kysely-deno-sqlite": "jsr:@soapbox/kysely-deno-sqlite@^2.1.0", "@soapbox/stickynotes": "jsr:@soapbox/stickynotes@^0.4.0", diff --git a/src/pipeline.ts b/src/pipeline.ts index d05b09a..c086c92 100644 --- a/src/pipeline.ts +++ b/src/pipeline.ts @@ -35,8 +35,9 @@ async function handleEvent(event: DittoEvent, signal: AbortSignal): Promise ${event.id}`); await hydrateEvent(event, signal); + await policyFilter(event); + await Promise.all([ - policyFilter(event), storeEvent(event, signal), parseMetadata(event, signal), processDeletions(event, signal), @@ -50,8 +51,8 @@ async function handleEvent(event: DittoEvent, signal: AbortSignal): Promise { - const UserPolicy = new MuteListPolicy(Conf.pubkey, Storages.admin); - const result = await UserPolicy.call(event); + const policy = new MuteListPolicy(Conf.pubkey, Storages.admin); + const result = await policy.call(event); debug(JSON.stringify(result)); @@ -59,11 +60,9 @@ async function policyFilter(event: NostrEvent): Promise { if (!ok) { const [prefix, ...rest] = reason.split(': '); if (['duplicate', 'pow', 'blocked', 'rate-limited', 'invalid'].includes(prefix)) { - const error = new RelayError(prefix as any, rest.join(': ')); - return Promise.reject(error); + throw new RelayError(prefix as RelayErrorPrefix, rest.join(': ')); } else { - const error = new RelayError('error', rest.join(': ')); - return Promise.reject(error); + throw new RelayError('error', rest.join(': ')); } } } @@ -272,9 +271,11 @@ async function streamOut(event: NostrEvent): Promise { } } +type RelayErrorPrefix = 'duplicate' | 'pow' | 'blocked' | 'rate-limited' | 'invalid' | 'error'; + /** NIP-20 command line result. */ class RelayError extends Error { - constructor(prefix: 'duplicate' | 'pow' | 'blocked' | 'rate-limited' | 'invalid' | 'error', message: string) { + constructor(prefix: RelayErrorPrefix, message: string) { super(`${prefix}: ${message}`); } } diff --git a/src/utils/api.ts b/src/utils/api.ts index cba7c66..8da87fb 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -103,10 +103,8 @@ async function updateAdminEvent( async function publishEvent(event: NostrEvent, c: AppContext): Promise { debug('EVENT', event); try { - await Promise.all([ - pipeline.handleEvent(event, c.req.raw.signal), - Storages.client.event(event), - ]); + await pipeline.handleEvent(event, c.req.raw.signal); + await Storages.client.event(event); } catch (e) { if (e instanceof pipeline.RelayError) { throw new HTTPException(422, { From 6105e00c808ebc5a5b2ff2a764aa121ed95d2be8 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 13 May 2024 12:43:01 -0500 Subject: [PATCH 5/8] pipeline: add a placeholder for custom policy --- src/pipeline.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/pipeline.ts b/src/pipeline.ts index c086c92..7da026f 100644 --- a/src/pipeline.ts +++ b/src/pipeline.ts @@ -1,5 +1,6 @@ import { NostrEvent, NSchema as n } from '@nostrify/nostrify'; import { LNURL } from '@nostrify/nostrify/ln'; +import { PipePolicy } from '@nostrify/nostrify/policies'; import Debug from '@soapbox/stickynotes/debug'; import { sql } from 'kysely'; @@ -33,9 +34,12 @@ async function handleEvent(event: DittoEvent, signal: AbortSignal): Promise ${event.id}`); - await hydrateEvent(event, signal); - await policyFilter(event); + if (event.kind !== 24133) { + await policyFilter(event); + } + + await hydrateEvent(event, signal); await Promise.all([ storeEvent(event, signal), @@ -51,9 +55,12 @@ async function handleEvent(event: DittoEvent, signal: AbortSignal): Promise { - const policy = new MuteListPolicy(Conf.pubkey, Storages.admin); - const result = await policy.call(event); + const policy = new PipePolicy([ + new MuteListPolicy(Conf.pubkey, Storages.admin), + // put custom policy here + ]); + const result = await policy.call(event); debug(JSON.stringify(result)); const [_, _eventId, ok, reason] = result; From 4029971407165173cacf65105fbc4a04dc682816 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Mon, 13 May 2024 17:44:33 -0300 Subject: [PATCH 6/8] fix(pipeline): load custom policy if available --- src/pipeline.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/pipeline.ts b/src/pipeline.ts index 7da026f..a47a2da 100644 --- a/src/pipeline.ts +++ b/src/pipeline.ts @@ -1,4 +1,4 @@ -import { NostrEvent, NSchema as n } from '@nostrify/nostrify'; +import { NostrEvent, NPolicy, NSchema as n } from '@nostrify/nostrify'; import { LNURL } from '@nostrify/nostrify/ln'; import { PipePolicy } from '@nostrify/nostrify/policies'; import Debug from '@soapbox/stickynotes/debug'; @@ -55,10 +55,18 @@ async function handleEvent(event: DittoEvent, signal: AbortSignal): Promise { - const policy = new PipePolicy([ + const policies: NPolicy[] = [ new MuteListPolicy(Conf.pubkey, Storages.admin), - // put custom policy here - ]); + ]; + + try { + const customPolicy = (await import('../data/policy.ts')).default; + policies.push(new customPolicy()); + } catch (_e) { + debug('policy not found - https://docs.soapbox.pub/ditto/policies/'); + } + + const policy = new PipePolicy(policies.reverse()); const result = await policy.call(event); debug(JSON.stringify(result)); From ecfea827e1a3b5a06f6fad43f6e2a1806e683ffb Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 14 May 2024 14:38:05 -0500 Subject: [PATCH 7/8] Move RelayError into its own file, add helper methods --- src/RelayError.ts | 24 ++++++++++++++++++++++++ src/controllers/nostr/relay.ts | 3 ++- src/pipeline.ts | 22 +++------------------- src/utils/api.ts | 3 ++- 4 files changed, 31 insertions(+), 21 deletions(-) create mode 100644 src/RelayError.ts diff --git a/src/RelayError.ts b/src/RelayError.ts new file mode 100644 index 0000000..1d275f6 --- /dev/null +++ b/src/RelayError.ts @@ -0,0 +1,24 @@ +import { NostrRelayOK } from '@nostrify/nostrify'; + +export type RelayErrorPrefix = 'duplicate' | 'pow' | 'blocked' | 'rate-limited' | 'invalid' | 'error'; + +/** NIP-01 command line result. */ +export class RelayError extends Error { + constructor(prefix: RelayErrorPrefix, message: string) { + super(`${prefix}: ${message}`); + } + + /** Construct a RelayError from the reason message. */ + static fromReason(reason: string): RelayError { + const [prefix, ...rest] = reason.split(': '); + return new RelayError(prefix as RelayErrorPrefix, rest.join(': ')); + } + + /** Throw a new RelayError if the OK message is false. */ + static assert(msg: NostrRelayOK): void { + const [_, _eventId, ok, reason] = msg; + if (!ok) { + throw RelayError.fromReason(reason); + } + } +} diff --git a/src/controllers/nostr/relay.ts b/src/controllers/nostr/relay.ts index 7d70ad9..c0fa026 100644 --- a/src/controllers/nostr/relay.ts +++ b/src/controllers/nostr/relay.ts @@ -10,6 +10,7 @@ import { } from '@nostrify/nostrify'; import { relayInfoController } from '@/controllers/nostr/relay-info.ts'; import * as pipeline from '@/pipeline.ts'; +import { RelayError } from '@/RelayError.ts'; import { Storages } from '@/storages.ts'; import type { AppController } from '@/app.ts'; @@ -95,7 +96,7 @@ function connectStream(socket: WebSocket) { await pipeline.handleEvent(event, AbortSignal.timeout(1000)); send(['OK', event.id, true, '']); } catch (e) { - if (e instanceof pipeline.RelayError) { + if (e instanceof RelayError) { send(['OK', event.id, false, e.message]); } else { send(['OK', event.id, false, 'error: something went wrong']); diff --git a/src/pipeline.ts b/src/pipeline.ts index a47a2da..995abf2 100644 --- a/src/pipeline.ts +++ b/src/pipeline.ts @@ -10,6 +10,7 @@ import { deleteAttachedMedia } from '@/db/unattached-media.ts'; import { DittoEvent } from '@/interfaces/DittoEvent.ts'; import { isEphemeralKind } from '@/kinds.ts'; import { DVM } from '@/pipeline/DVM.ts'; +import { RelayError } from '@/RelayError.ts'; import { updateStats } from '@/stats.ts'; import { hydrateEvents, purifyEvent } from '@/storages/hydrate.ts'; import { Storages } from '@/storages.ts'; @@ -71,15 +72,7 @@ async function policyFilter(event: NostrEvent): Promise { const result = await policy.call(event); debug(JSON.stringify(result)); - const [_, _eventId, ok, reason] = result; - if (!ok) { - const [prefix, ...rest] = reason.split(': '); - if (['duplicate', 'pow', 'blocked', 'rate-limited', 'invalid'].includes(prefix)) { - throw new RelayError(prefix as RelayErrorPrefix, rest.join(': ')); - } else { - throw new RelayError('error', rest.join(': ')); - } - } + RelayError.assert(result); } /** Encounter the event, and return whether it has already been encountered. */ @@ -286,13 +279,4 @@ async function streamOut(event: NostrEvent): Promise { } } -type RelayErrorPrefix = 'duplicate' | 'pow' | 'blocked' | 'rate-limited' | 'invalid' | 'error'; - -/** NIP-20 command line result. */ -class RelayError extends Error { - constructor(prefix: RelayErrorPrefix, message: string) { - super(`${prefix}: ${message}`); - } -} - -export { handleEvent, RelayError }; +export { handleEvent }; diff --git a/src/utils/api.ts b/src/utils/api.ts index 8da87fb..70fd995 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -9,6 +9,7 @@ import { z } from 'zod'; 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 { APISigner } from '@/signers/APISigner.ts'; import { Storages } from '@/storages.ts'; @@ -106,7 +107,7 @@ async function publishEvent(event: NostrEvent, c: AppContext): Promise Date: Tue, 14 May 2024 14:39:48 -0500 Subject: [PATCH 8/8] Uppercase CustomPolicy --- src/pipeline.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/pipeline.ts b/src/pipeline.ts index 995abf2..b3eea25 100644 --- a/src/pipeline.ts +++ b/src/pipeline.ts @@ -61,8 +61,8 @@ async function policyFilter(event: NostrEvent): Promise { ]; try { - const customPolicy = (await import('../data/policy.ts')).default; - policies.push(new customPolicy()); + const CustomPolicy = (await import('../data/policy.ts')).default; + policies.push(new CustomPolicy()); } catch (_e) { debug('policy not found - https://docs.soapbox.pub/ditto/policies/'); } @@ -71,7 +71,6 @@ async function policyFilter(event: NostrEvent): Promise { const result = await policy.call(event); debug(JSON.stringify(result)); - RelayError.assert(result); }