@nostrify/policies 0.36.7 → 0.36.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/.turbo/turbo-build.log +9 -17
  2. package/CHANGELOG.md +9 -0
  3. package/dist/AntiDuplicationPolicy.d.ts +43 -0
  4. package/dist/AntiDuplicationPolicy.d.ts.map +1 -0
  5. package/dist/AntiDuplicationPolicy.js +44 -0
  6. package/dist/AntiDuplicationPolicy.ts +82 -0
  7. package/dist/AnyPolicy.d.ts +8 -0
  8. package/dist/AnyPolicy.d.ts.map +1 -0
  9. package/dist/AnyPolicy.js +20 -0
  10. package/dist/AnyPolicy.ts +24 -0
  11. package/dist/AuthorPolicy.d.ts +9 -0
  12. package/dist/AuthorPolicy.d.ts.map +1 -0
  13. package/dist/AuthorPolicy.js +22 -0
  14. package/dist/AuthorPolicy.ts +29 -0
  15. package/dist/DomainPolicy.d.ts +21 -0
  16. package/dist/DomainPolicy.d.ts.map +1 -0
  17. package/dist/DomainPolicy.js +62 -0
  18. package/dist/DomainPolicy.ts +96 -0
  19. package/dist/FiltersPolicy.d.ts +18 -0
  20. package/dist/FiltersPolicy.d.ts.map +1 -0
  21. package/dist/FiltersPolicy.js +17 -0
  22. package/dist/FiltersPolicy.ts +30 -0
  23. package/dist/HashtagPolicy.d.ts +16 -0
  24. package/dist/HashtagPolicy.d.ts.map +1 -0
  25. package/dist/HashtagPolicy.js +18 -0
  26. package/dist/HashtagPolicy.ts +28 -0
  27. package/dist/HellthreadPolicy.d.ts +14 -0
  28. package/dist/HellthreadPolicy.d.ts.map +1 -0
  29. package/dist/HellthreadPolicy.js +20 -0
  30. package/dist/HellthreadPolicy.ts +30 -0
  31. package/dist/InvertPolicy.d.ts +9 -0
  32. package/dist/InvertPolicy.d.ts.map +1 -0
  33. package/dist/InvertPolicy.js +20 -0
  34. package/dist/InvertPolicy.ts +23 -0
  35. package/dist/KeywordPolicy.d.ts +16 -0
  36. package/dist/KeywordPolicy.d.ts.map +1 -0
  37. package/dist/KeywordPolicy.js +18 -0
  38. package/dist/KeywordPolicy.ts +28 -0
  39. package/dist/NoOpPolicy.d.ts +6 -0
  40. package/dist/NoOpPolicy.d.ts.map +1 -0
  41. package/dist/NoOpPolicy.js +9 -0
  42. package/dist/NoOpPolicy.ts +9 -0
  43. package/dist/OpenAIPolicy.d.ts +80 -0
  44. package/dist/OpenAIPolicy.d.ts.map +1 -0
  45. package/dist/OpenAIPolicy.js +38 -0
  46. package/dist/OpenAIPolicy.ts +116 -0
  47. package/dist/PipePolicy.d.ts +26 -0
  48. package/dist/PipePolicy.d.ts.map +1 -0
  49. package/dist/PipePolicy.js +18 -0
  50. package/dist/PipePolicy.ts +39 -0
  51. package/dist/PowPolicy.d.ts +21 -0
  52. package/dist/PowPolicy.d.ts.map +1 -0
  53. package/dist/PowPolicy.js +27 -0
  54. package/dist/PowPolicy.ts +44 -0
  55. package/dist/PubkeyBanPolicy.d.ts +15 -0
  56. package/dist/PubkeyBanPolicy.d.ts.map +1 -0
  57. package/dist/PubkeyBanPolicy.js +18 -0
  58. package/dist/PubkeyBanPolicy.ts +27 -0
  59. package/dist/ReadOnlyPolicy.d.ts +6 -0
  60. package/dist/ReadOnlyPolicy.d.ts.map +1 -0
  61. package/dist/ReadOnlyPolicy.js +9 -0
  62. package/dist/ReadOnlyPolicy.ts +9 -0
  63. package/dist/RegexPolicy.d.ts +15 -0
  64. package/dist/RegexPolicy.d.ts.map +1 -0
  65. package/dist/RegexPolicy.js +16 -0
  66. package/dist/RegexPolicy.ts +25 -0
  67. package/dist/ReplyBotPolicy.d.ts +25 -0
  68. package/dist/ReplyBotPolicy.d.ts.map +1 -0
  69. package/dist/ReplyBotPolicy.js +42 -0
  70. package/dist/ReplyBotPolicy.ts +62 -0
  71. package/dist/SizePolicy.d.ts +23 -0
  72. package/dist/SizePolicy.d.ts.map +1 -0
  73. package/dist/SizePolicy.js +19 -0
  74. package/dist/SizePolicy.ts +39 -0
  75. package/dist/WhitelistPolicy.d.ts +16 -0
  76. package/dist/WhitelistPolicy.d.ts.map +1 -0
  77. package/dist/WhitelistPolicy.js +25 -0
  78. package/dist/WhitelistPolicy.ts +36 -0
  79. package/dist/WoTPolicy.d.ts +26 -0
  80. package/dist/WoTPolicy.d.ts.map +1 -0
  81. package/dist/WoTPolicy.js +39 -0
  82. package/dist/WoTPolicy.ts +64 -0
  83. package/dist/mod.d.ts +21 -0
  84. package/dist/mod.d.ts.map +1 -0
  85. package/dist/mod.js +42 -0
  86. package/dist/mod.ts +20 -0
  87. package/dist/tsconfig.tsbuildinfo +1 -0
  88. package/package.json +3 -3
@@ -1,27 +1,19 @@
1
1
 
2
2
 
3
- > @nostrify/policies@0.36.6 build /home/sid/repos/nostrify/packages/policies
4
- > tsc -p tsconfig.json && node ../../esbuild.config.js --package ./
3
+ > @nostrify/policies@0.36.7 build /home/sid/repos/nostrify/packages/policies
4
+ > npx tsc -p tsconfig.json && node ../../esbuild.config.js --package ./
5
5
 
6
+ npm warn Unknown env config "verify-deps-before-run". This will stop working in the next major version of npm.
7
+ npm warn Unknown env config "_jsr-registry". This will stop working in the next major version of npm.
8
+ ⠙⠙Building with esbuild...
6
9
 
7
10
  dist/DomainPolicy.js 2.1kb
8
11
  dist/ReplyBotPolicy.js 1.4kb
9
12
  dist/mod.js 1.3kb
10
13
  dist/AntiDuplicationPolicy.js 1.2kb
11
14
  dist/WoTPolicy.js 1.0kb
12
- dist/OpenAIPolicy.js 985b 
13
- dist/PowPolicy.js 667b 
14
- dist/AuthorPolicy.js 640b 
15
- dist/HellthreadPolicy.js 494b 
16
- dist/WhitelistPolicy.js 490b 
17
- dist/SizePolicy.js 453b 
18
- dist/HashtagPolicy.js 425b 
19
- dist/AnyPolicy.js 412b 
20
- dist/KeywordPolicy.js 411b 
21
- dist/FiltersPolicy.js 410b 
22
- dist/InvertPolicy.js 385b 
23
- dist/PipePolicy.js 383b 
24
- dist/PubkeyBanPolicy.js 367b 
25
- ...and 3 more output files...
15
+ ...and 16 more output files...
26
16
 
27
- ⚡ Done in 23ms
17
+ ⚡ Done in 22ms
18
+ Copying source files...
19
+ Done!
package/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.36.8
4
+
5
+ ### Patch Changes
6
+
7
+ - distribute ts files
8
+ - Updated dependencies
9
+ - @nostrify/nostrify@0.46.10
10
+ - @nostrify/types@0.36.6
11
+
3
12
  ## 0.36.7
4
13
 
5
14
  ### Patch Changes
@@ -0,0 +1,43 @@
1
+ import type { NostrEvent, NostrRelayOK, NPolicy } from '@nostrify/types';
2
+ import type { Kv } from '@deno/kv';
3
+ /** Policy options for `AntiDuplicationPolicy`. */
4
+ interface AntiDuplicationPolicyOpts {
5
+ /** Deno.Kv implementation to use. */
6
+ kv: Pick<Kv, 'get' | 'set'>;
7
+ /** Time in ms until a message with this content may be posted again. Default: `60000` (1 minute). */
8
+ expireIn?: number;
9
+ /** Note text under this limit will be skipped by the policy. Default: `50`. */
10
+ minLength?: number;
11
+ /** Normalize the event's content before a hash is taken, to prevent the attacker from making small changes. Should return the normalized content. */
12
+ deobfuscate?(event: NostrEvent): string;
13
+ }
14
+ /**
15
+ * Prevent messages with the exact same content from being submitted repeatedly.
16
+ *
17
+ * It stores a hashcode for each content in a Deno.Kv database and rate-limits them. Only messages that meet the minimum length criteria are selected.
18
+ * Each time a matching message is submitted, the timer will reset, so spammers sending the same message will only ever get the first one through.
19
+ *
20
+ * ```ts
21
+ * // Open a Deno.KV instance.
22
+ * const kv = await Deno.openKv();
23
+ *
24
+ * // Prevent the same message from being posted within 60 seconds.
25
+ * new AntiDuplicationPolicy({ kv, expireIn: 60000 });
26
+ *
27
+ * // Only enforce the policy on messages with at least 50 characters.
28
+ * new AntiDuplicationPolicy({ kv, expireIn: 60000, minLength: 50 });
29
+ * ```
30
+ */
31
+ export declare class AntiDuplicationPolicy implements NPolicy {
32
+ private opts;
33
+ constructor(opts: AntiDuplicationPolicyOpts);
34
+ call(event: NostrEvent): Promise<NostrRelayOK>;
35
+ /**
36
+ * Get a "good enough" unique identifier for this content.
37
+ * This algorithm was chosen because it's very fast with a low chance of collisions.
38
+ * https://stackoverflow.com/a/8831937
39
+ */
40
+ private static hashCode;
41
+ }
42
+ export {};
43
+ //# sourceMappingURL=AntiDuplicationPolicy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AntiDuplicationPolicy.d.ts","sourceRoot":"","sources":["../AntiDuplicationPolicy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AACzE,OAAO,KAAK,EAAE,EAAE,EAAS,MAAM,UAAU,CAAC;AAE1C,kDAAkD;AAClD,UAAU,yBAAyB;IACjC,qCAAqC;IACrC,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,GAAG,KAAK,CAAC,CAAC;IAC5B,qGAAqG;IACrG,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qJAAqJ;IACrJ,WAAW,CAAC,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAAC;CACzC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,qBAAsB,YAAW,OAAO;IACnD,OAAO,CAAC,IAAI,CAA4B;gBAE5B,IAAI,EAAE,yBAAyB;IAIrC,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,YAAY,CAAC;IA4BpD;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ;CASxB"}
@@ -0,0 +1,44 @@
1
+ class AntiDuplicationPolicy {
2
+ opts;
3
+ constructor(opts) {
4
+ this.opts = opts;
5
+ }
6
+ async call(event) {
7
+ const { id, kind } = event;
8
+ const { kv, expireIn = 6e4, minLength = 50, deobfuscate } = this.opts;
9
+ const content = deobfuscate?.(event) ?? event.content;
10
+ if (kind === 1 && content.length >= minLength) {
11
+ const hash = AntiDuplicationPolicy.hashCode(content);
12
+ const key = ["nostrify", "policies", "antiduplication", hash];
13
+ const { value } = await kv.get(key);
14
+ if (value) {
15
+ await kv.set(key, true, { expireIn });
16
+ return [
17
+ "OK",
18
+ id,
19
+ false,
20
+ "blocked: the same message has been repeated too many times"
21
+ ];
22
+ }
23
+ await kv.set(key, true, { expireIn });
24
+ }
25
+ return ["OK", id, true, ""];
26
+ }
27
+ /**
28
+ * Get a "good enough" unique identifier for this content.
29
+ * This algorithm was chosen because it's very fast with a low chance of collisions.
30
+ * https://stackoverflow.com/a/8831937
31
+ */
32
+ static hashCode(str) {
33
+ let hash = 0;
34
+ for (let i = 0, len = str.length; i < len; i++) {
35
+ const chr = str.charCodeAt(i);
36
+ hash = (hash << 5) - hash + chr;
37
+ hash |= 0;
38
+ }
39
+ return hash;
40
+ }
41
+ }
42
+ export {
43
+ AntiDuplicationPolicy
44
+ };
@@ -0,0 +1,82 @@
1
+ import type { NostrEvent, NostrRelayOK, NPolicy } from '@nostrify/types';
2
+ import type { Kv, KvKey } from '@deno/kv';
3
+
4
+ /** Policy options for `AntiDuplicationPolicy`. */
5
+ interface AntiDuplicationPolicyOpts {
6
+ /** Deno.Kv implementation to use. */
7
+ kv: Pick<Kv, 'get' | 'set'>;
8
+ /** Time in ms until a message with this content may be posted again. Default: `60000` (1 minute). */
9
+ expireIn?: number;
10
+ /** Note text under this limit will be skipped by the policy. Default: `50`. */
11
+ minLength?: number;
12
+ /** Normalize the event's content before a hash is taken, to prevent the attacker from making small changes. Should return the normalized content. */
13
+ deobfuscate?(event: NostrEvent): string;
14
+ }
15
+
16
+ /**
17
+ * Prevent messages with the exact same content from being submitted repeatedly.
18
+ *
19
+ * It stores a hashcode for each content in a Deno.Kv database and rate-limits them. Only messages that meet the minimum length criteria are selected.
20
+ * Each time a matching message is submitted, the timer will reset, so spammers sending the same message will only ever get the first one through.
21
+ *
22
+ * ```ts
23
+ * // Open a Deno.KV instance.
24
+ * const kv = await Deno.openKv();
25
+ *
26
+ * // Prevent the same message from being posted within 60 seconds.
27
+ * new AntiDuplicationPolicy({ kv, expireIn: 60000 });
28
+ *
29
+ * // Only enforce the policy on messages with at least 50 characters.
30
+ * new AntiDuplicationPolicy({ kv, expireIn: 60000, minLength: 50 });
31
+ * ```
32
+ */
33
+ export class AntiDuplicationPolicy implements NPolicy {
34
+ private opts: AntiDuplicationPolicyOpts;
35
+
36
+ constructor(opts: AntiDuplicationPolicyOpts) {
37
+ this.opts = opts;
38
+ }
39
+
40
+ async call(event: NostrEvent): Promise<NostrRelayOK> {
41
+ const { id, kind } = event;
42
+ const { kv, expireIn = 60000, minLength = 50, deobfuscate } = this.opts;
43
+
44
+ const content = deobfuscate?.(event) ?? event.content;
45
+
46
+ if (kind === 1 && content.length >= minLength) {
47
+ const hash = AntiDuplicationPolicy.hashCode(content);
48
+ const key: KvKey = ['nostrify', 'policies', 'antiduplication', hash];
49
+
50
+ const { value } = await kv.get(key);
51
+
52
+ if (value) {
53
+ await kv.set(key, true, { expireIn });
54
+ return [
55
+ 'OK',
56
+ id,
57
+ false,
58
+ 'blocked: the same message has been repeated too many times',
59
+ ];
60
+ }
61
+
62
+ await kv.set(key, true, { expireIn });
63
+ }
64
+
65
+ return ['OK', id, true, ''];
66
+ }
67
+
68
+ /**
69
+ * Get a "good enough" unique identifier for this content.
70
+ * This algorithm was chosen because it's very fast with a low chance of collisions.
71
+ * https://stackoverflow.com/a/8831937
72
+ */
73
+ private static hashCode(str: string): number {
74
+ let hash = 0;
75
+ for (let i = 0, len = str.length; i < len; i++) {
76
+ const chr = str.charCodeAt(i);
77
+ hash = (hash << 5) - hash + chr;
78
+ hash |= 0; // Convert to 32bit integer
79
+ }
80
+ return hash;
81
+ }
82
+ }
@@ -0,0 +1,8 @@
1
+ import type { NostrEvent, NostrRelayOK, NPolicy } from '@nostrify/types';
2
+ /** Similar to `PipePolicy`, but passes if at least one policy passes. */
3
+ export declare class AnyPolicy implements NPolicy {
4
+ private policies;
5
+ constructor(policies: NPolicy[]);
6
+ call(event: NostrEvent, signal?: AbortSignal): Promise<NostrRelayOK>;
7
+ }
8
+ //# sourceMappingURL=AnyPolicy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AnyPolicy.d.ts","sourceRoot":"","sources":["../AnyPolicy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAEzE,yEAAyE;AACzE,qBAAa,SAAU,YAAW,OAAO;IACvC,OAAO,CAAC,QAAQ,CAAY;gBAChB,QAAQ,EAAE,OAAO,EAAE;IAIzB,IAAI,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC;CAc3E"}
@@ -0,0 +1,20 @@
1
+ class AnyPolicy {
2
+ policies;
3
+ constructor(policies) {
4
+ this.policies = policies;
5
+ }
6
+ async call(event, signal) {
7
+ let result = ["OK", event.id, false, "blocked: no policy passed"];
8
+ for (const policy of this.policies) {
9
+ result = await policy.call(event, signal);
10
+ const ok = result[2];
11
+ if (ok) {
12
+ return result;
13
+ }
14
+ }
15
+ return result;
16
+ }
17
+ }
18
+ export {
19
+ AnyPolicy
20
+ };
@@ -0,0 +1,24 @@
1
+ import type { NostrEvent, NostrRelayOK, NPolicy } from '@nostrify/types';
2
+
3
+ /** Similar to `PipePolicy`, but passes if at least one policy passes. */
4
+ export class AnyPolicy implements NPolicy {
5
+ private policies: NPolicy[];
6
+ constructor(policies: NPolicy[]) {
7
+ this.policies = policies;
8
+ }
9
+
10
+ async call(event: NostrEvent, signal?: AbortSignal): Promise<NostrRelayOK> {
11
+ let result: NostrRelayOK = ['OK', event.id, false, 'blocked: no policy passed'];
12
+
13
+ for (const policy of this.policies) {
14
+ result = await policy.call(event, signal);
15
+
16
+ const ok = result[2];
17
+ if (ok) {
18
+ return result;
19
+ }
20
+ }
21
+
22
+ return result;
23
+ }
24
+ }
@@ -0,0 +1,9 @@
1
+ import type { NostrEvent, NostrRelayOK, NPolicy, NStore } from '@nostrify/types';
2
+ /** Rejects events by authors without a kind 0, then optionally applies another policy to the kind 0. */
3
+ export declare class AuthorPolicy implements NPolicy {
4
+ private store;
5
+ private policy?;
6
+ constructor(store: NStore, policy?: NPolicy);
7
+ call(event: NostrEvent, signal?: AbortSignal): Promise<NostrRelayOK>;
8
+ }
9
+ //# sourceMappingURL=AuthorPolicy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AuthorPolicy.d.ts","sourceRoot":"","sources":["../AuthorPolicy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAEjF,wGAAwG;AACxG,qBAAa,YAAa,YAAW,OAAO;IAC1C,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,MAAM,CAAC,CAAU;gBAEb,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO;IAKrC,IAAI,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC;CAgB3E"}
@@ -0,0 +1,22 @@
1
+ class AuthorPolicy {
2
+ store;
3
+ policy;
4
+ constructor(store, policy) {
5
+ this.store = store;
6
+ this.policy = policy;
7
+ }
8
+ async call(event, signal) {
9
+ const author = event.kind === 0 ? event : await this.store.query([{ kinds: [0], authors: [event.pubkey], limit: 1 }], { signal }).then(([event2]) => event2);
10
+ if (!author) {
11
+ return ["OK", event.id, false, "blocked: author is missing a kind 0 event"];
12
+ }
13
+ if (this.policy) {
14
+ const [, , ok, reason] = await this.policy.call(author, signal);
15
+ return ["OK", event.id, ok, reason];
16
+ }
17
+ return ["OK", event.id, true, ""];
18
+ }
19
+ }
20
+ export {
21
+ AuthorPolicy
22
+ };
@@ -0,0 +1,29 @@
1
+ import type { NostrEvent, NostrRelayOK, NPolicy, NStore } from '@nostrify/types';
2
+
3
+ /** Rejects events by authors without a kind 0, then optionally applies another policy to the kind 0. */
4
+ export class AuthorPolicy implements NPolicy {
5
+ private store: NStore;
6
+ private policy?: NPolicy;
7
+
8
+ constructor(store: NStore, policy?: NPolicy) {
9
+ this.store = store;
10
+ this.policy = policy;
11
+ }
12
+
13
+ async call(event: NostrEvent, signal?: AbortSignal): Promise<NostrRelayOK> {
14
+ const author: NostrEvent | undefined = event.kind === 0 ? event : await this.store
15
+ .query([{ kinds: [0], authors: [event.pubkey], limit: 1 }], { signal })
16
+ .then(([event]: NostrEvent[]) => event);
17
+
18
+ if (!author) {
19
+ return ['OK', event.id, false, 'blocked: author is missing a kind 0 event'];
20
+ }
21
+
22
+ if (this.policy) {
23
+ const [, , ok, reason] = await this.policy.call(author, signal);
24
+ return ['OK', event.id, ok, reason];
25
+ }
26
+
27
+ return ['OK', event.id, true, ''];
28
+ }
29
+ }
@@ -0,0 +1,21 @@
1
+ import type { NPolicy, NProfilePointer, NStore } from '@nostrify/types';
2
+ import { AuthorPolicy } from './AuthorPolicy.ts';
3
+ /** Options for `DomainPolicy`. */
4
+ interface DomainPolicyOpts {
5
+ /** Custom NIP-05 lookup function. */
6
+ lookup?(nip05: string, signal?: AbortSignal): Promise<NProfilePointer>;
7
+ /** List of domains to blacklist. Reject events from users with a NIP-05 matching any of these domains. */
8
+ blacklist?: string[];
9
+ /** List of domains to whitelist. If provided, only events from users with a valid NIP-05 on the given domains will be accepted. */
10
+ whitelist?: string[];
11
+ }
12
+ /** Ban events unless their author has a valid NIP-05 name. Domains can also be whitelisted or blacklisted. */
13
+ export declare class DomainPolicy extends AuthorPolicy implements NPolicy {
14
+ constructor(store: NStore, opts?: DomainPolicyOpts);
15
+ /** Check if a domain is blacklisted, including subdomains of blacklisted domains. */
16
+ private static isDomainBlacklisted;
17
+ /** Default NIP-05 lookup method if one isn't provided by the caller. */
18
+ private static lookup;
19
+ }
20
+ export {};
21
+ //# sourceMappingURL=DomainPolicy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DomainPolicy.d.ts","sourceRoot":"","sources":["../DomainPolicy.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAc,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAEpF,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,kCAAkC;AAClC,UAAU,gBAAgB;IACxB,qCAAqC;IACrC,MAAM,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IACvE,0GAA0G;IAC1G,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,mIAAmI;IACnI,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,8GAA8G;AAC9G,qBAAa,YAAa,SAAQ,YAAa,YAAW,OAAO;gBACnD,KAAK,EAAE,MAAM,EAAE,IAAI,GAAE,gBAAqB;IAmDtD,qFAAqF;IACrF,OAAO,CAAC,MAAM,CAAC,mBAAmB;IAmBlC,wEAAwE;IACxE,OAAO,CAAC,MAAM,CAAC,MAAM;CAMtB"}
@@ -0,0 +1,62 @@
1
+ import { NIP05, NSchema as n } from "@nostrify/nostrify";
2
+ import { AuthorPolicy } from "./AuthorPolicy.js";
3
+ class DomainPolicy extends AuthorPolicy {
4
+ constructor(store, opts = {}) {
5
+ super(store, {
6
+ async call(event, signal) {
7
+ const { blacklist = [], whitelist, lookup = DomainPolicy.lookup } = opts;
8
+ const metadata = n.json().pipe(n.metadata()).safeParse(event.content);
9
+ if (!metadata.success) {
10
+ return ["OK", event.id, false, "blocked: invalid kind 0 metadata"];
11
+ }
12
+ const { nip05 } = metadata.data;
13
+ if (!nip05) {
14
+ return ["OK", event.id, false, "blocked: missing nip05"];
15
+ }
16
+ const domain = nip05.split("@").pop();
17
+ if (!domain) {
18
+ return ["OK", event.id, false, "blocked: invalid nip05"];
19
+ }
20
+ if (DomainPolicy.isDomainBlacklisted(domain, blacklist)) {
21
+ return ["OK", event.id, false, "blocked: blacklisted nip05 domain"];
22
+ }
23
+ try {
24
+ const { pubkey } = await lookup(nip05, signal);
25
+ if (pubkey !== event.pubkey) {
26
+ return ["OK", event.id, false, "blocked: mismatched nip05 pubkey"];
27
+ }
28
+ if (whitelist && !whitelist.includes(domain)) {
29
+ return [
30
+ "OK",
31
+ event.id,
32
+ false,
33
+ "blocked: nip05 domain not in whitelist"
34
+ ];
35
+ }
36
+ return ["OK", event.id, true, ""];
37
+ } catch {
38
+ return ["OK", event.id, false, "blocked: failed to lookup nip05"];
39
+ }
40
+ }
41
+ });
42
+ }
43
+ /** Check if a domain is blacklisted, including subdomains of blacklisted domains. */
44
+ static isDomainBlacklisted(domain, blacklist) {
45
+ if (blacklist.includes(domain)) {
46
+ return true;
47
+ }
48
+ for (const blacklistedDomain of blacklist) {
49
+ if (domain.endsWith("." + blacklistedDomain)) {
50
+ return true;
51
+ }
52
+ }
53
+ return false;
54
+ }
55
+ /** Default NIP-05 lookup method if one isn't provided by the caller. */
56
+ static lookup(nip05, signal) {
57
+ return NIP05.lookup(nip05, { signal });
58
+ }
59
+ }
60
+ export {
61
+ DomainPolicy
62
+ };
@@ -0,0 +1,96 @@
1
+ import { NIP05, NSchema as n } from '@nostrify/nostrify';
2
+ import type { NostrEvent, NPolicy, NProfilePointer, NStore } from '@nostrify/types';
3
+
4
+ import { AuthorPolicy } from './AuthorPolicy.ts';
5
+
6
+ /** Options for `DomainPolicy`. */
7
+ interface DomainPolicyOpts {
8
+ /** Custom NIP-05 lookup function. */
9
+ lookup?(nip05: string, signal?: AbortSignal): Promise<NProfilePointer>;
10
+ /** List of domains to blacklist. Reject events from users with a NIP-05 matching any of these domains. */
11
+ blacklist?: string[];
12
+ /** List of domains to whitelist. If provided, only events from users with a valid NIP-05 on the given domains will be accepted. */
13
+ whitelist?: string[];
14
+ }
15
+
16
+ /** Ban events unless their author has a valid NIP-05 name. Domains can also be whitelisted or blacklisted. */
17
+ export class DomainPolicy extends AuthorPolicy implements NPolicy {
18
+ constructor(store: NStore, opts: DomainPolicyOpts = {}) {
19
+ super(store, {
20
+ async call(event: NostrEvent, signal: AbortSignal) {
21
+ const { blacklist = [], whitelist, lookup = DomainPolicy.lookup } = opts;
22
+
23
+ const metadata = n.json().pipe(n.metadata()).safeParse(event.content);
24
+
25
+ if (!metadata.success) {
26
+ return ['OK', event.id, false, 'blocked: invalid kind 0 metadata'];
27
+ }
28
+
29
+ const { nip05 } = metadata.data;
30
+
31
+ if (!nip05) {
32
+ return ['OK', event.id, false, 'blocked: missing nip05'];
33
+ }
34
+
35
+ const domain = nip05.split('@').pop();
36
+
37
+ if (!domain) {
38
+ return ['OK', event.id, false, 'blocked: invalid nip05'];
39
+ }
40
+
41
+ if (DomainPolicy.isDomainBlacklisted(domain, blacklist)) {
42
+ return ['OK', event.id, false, 'blocked: blacklisted nip05 domain'];
43
+ }
44
+
45
+ try {
46
+ const { pubkey } = await lookup(nip05, signal);
47
+
48
+ if (pubkey !== event.pubkey) {
49
+ return ['OK', event.id, false, 'blocked: mismatched nip05 pubkey'];
50
+ }
51
+
52
+ if (whitelist && !whitelist.includes(domain)) {
53
+ return [
54
+ 'OK',
55
+ event.id,
56
+ false,
57
+ 'blocked: nip05 domain not in whitelist',
58
+ ];
59
+ }
60
+
61
+ return ['OK', event.id, true, ''];
62
+ } catch {
63
+ return ['OK', event.id, false, 'blocked: failed to lookup nip05'];
64
+ }
65
+ },
66
+ });
67
+ }
68
+
69
+ /** Check if a domain is blacklisted, including subdomains of blacklisted domains. */
70
+ private static isDomainBlacklisted(
71
+ domain: string,
72
+ blacklist: string[],
73
+ ): boolean {
74
+ // Check for exact match
75
+ if (blacklist.includes(domain)) {
76
+ return true;
77
+ }
78
+
79
+ // Check if domain is a subdomain of any blacklisted domain
80
+ for (const blacklistedDomain of blacklist) {
81
+ if (domain.endsWith('.' + blacklistedDomain)) {
82
+ return true;
83
+ }
84
+ }
85
+
86
+ return false;
87
+ }
88
+
89
+ /** Default NIP-05 lookup method if one isn't provided by the caller. */
90
+ private static lookup(
91
+ nip05: string,
92
+ signal?: AbortSignal,
93
+ ): Promise<NProfilePointer> {
94
+ return NIP05.lookup(nip05, { signal });
95
+ }
96
+ }
@@ -0,0 +1,18 @@
1
+ import type { NostrEvent, NostrFilter, NostrRelayOK, NPolicy } from '@nostrify/types';
2
+ /**
3
+ * Reject events which don't match the filters.
4
+ *
5
+ * Only messages which **match** the filters are allowed, and all others are dropped.
6
+ * The filter is a [NIP-01](https://github.com/nostr-protocol/nips/blob/master/01.md) relay filter.
7
+ *
8
+ * ```ts
9
+ * // Only allow kind 1, 3, 5, and 7 events.
10
+ * new FiltersPolicy([{ kinds: [0, 1, 3, 5, 6, 7] }]);
11
+ * ```
12
+ */
13
+ export declare class FiltersPolicy implements NPolicy {
14
+ private filters;
15
+ constructor(filters: NostrFilter[]);
16
+ call(event: NostrEvent): Promise<NostrRelayOK>;
17
+ }
18
+ //# sourceMappingURL=FiltersPolicy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FiltersPolicy.d.ts","sourceRoot":"","sources":["../FiltersPolicy.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAEtF;;;;;;;;;;GAUG;AACH,qBAAa,aAAc,YAAW,OAAO;IAC3C,OAAO,CAAC,OAAO,CAAgB;gBACnB,OAAO,EAAE,WAAW,EAAE;IAK5B,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,YAAY,CAAC;CAOrD"}
@@ -0,0 +1,17 @@
1
+ import { matchFilters } from "nostr-tools";
2
+ class FiltersPolicy {
3
+ filters;
4
+ constructor(filters) {
5
+ this.filters = filters;
6
+ }
7
+ // deno-lint-ignore require-await
8
+ async call(event) {
9
+ if (matchFilters(this.filters, event)) {
10
+ return ["OK", event.id, true, ""];
11
+ }
12
+ return ["OK", event.id, false, "blocked: the event doesn't match the allowed filters"];
13
+ }
14
+ }
15
+ export {
16
+ FiltersPolicy
17
+ };
@@ -0,0 +1,30 @@
1
+ import { matchFilters } from 'nostr-tools';
2
+
3
+ import type { NostrEvent, NostrFilter, NostrRelayOK, NPolicy } from '@nostrify/types';
4
+
5
+ /**
6
+ * Reject events which don't match the filters.
7
+ *
8
+ * Only messages which **match** the filters are allowed, and all others are dropped.
9
+ * The filter is a [NIP-01](https://github.com/nostr-protocol/nips/blob/master/01.md) relay filter.
10
+ *
11
+ * ```ts
12
+ * // Only allow kind 1, 3, 5, and 7 events.
13
+ * new FiltersPolicy([{ kinds: [0, 1, 3, 5, 6, 7] }]);
14
+ * ```
15
+ */
16
+ export class FiltersPolicy implements NPolicy {
17
+ private filters: NostrFilter[];
18
+ constructor(filters: NostrFilter[]) {
19
+ this.filters = filters;
20
+ }
21
+
22
+ // deno-lint-ignore require-await
23
+ async call(event: NostrEvent): Promise<NostrRelayOK> {
24
+ if (matchFilters(this.filters, event)) {
25
+ return ['OK', event.id, true, ''];
26
+ }
27
+
28
+ return ['OK', event.id, false, "blocked: the event doesn't match the allowed filters"];
29
+ }
30
+ }
@@ -0,0 +1,16 @@
1
+ import type { NostrEvent, NostrRelayOK, NPolicy } from '@nostrify/types';
2
+ /**
3
+ * Reject events containing any of the banned hashtags.
4
+ *
5
+ * @example
6
+ * ```ts
7
+ * // Reject events with banned hashtags.
8
+ * HashtagPolicy(['nsfw']);
9
+ * ```
10
+ */
11
+ export declare class HashtagPolicy implements NPolicy {
12
+ private hashtags;
13
+ constructor(hashtags: string[]);
14
+ call({ id, tags }: NostrEvent): Promise<NostrRelayOK>;
15
+ }
16
+ //# sourceMappingURL=HashtagPolicy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HashtagPolicy.d.ts","sourceRoot":"","sources":["../HashtagPolicy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAEzE;;;;;;;;GAQG;AACH,qBAAa,aAAc,YAAW,OAAO;IAC3C,OAAO,CAAC,QAAQ,CAAW;gBACf,QAAQ,EAAE,MAAM,EAAE;IAKxB,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,UAAU,GAAG,OAAO,CAAC,YAAY,CAAC;CAS5D"}
@@ -0,0 +1,18 @@
1
+ class HashtagPolicy {
2
+ hashtags;
3
+ constructor(hashtags) {
4
+ this.hashtags = hashtags;
5
+ }
6
+ // deno-lint-ignore require-await
7
+ async call({ id, tags }) {
8
+ for (const [name, value] of tags) {
9
+ if (name === "t" && this.hashtags.includes(value.toLowerCase())) {
10
+ return ["OK", id, false, "blocked: contains a banned hashtag"];
11
+ }
12
+ }
13
+ return ["OK", id, true, ""];
14
+ }
15
+ }
16
+ export {
17
+ HashtagPolicy
18
+ };