diff --git a/docker-compose.yml b/docker-compose.yml index 9237089..0328ce0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -111,7 +111,7 @@ services: - redis strfry: - image: gitea.kosmos.org/kosmos/strfry-deno:1.1.1 + image: gitea.kosmos.org/kosmos/strfry-deno:2.0.0 volumes: - ./docker/strfry/strfry.conf:/etc/strfry.conf - ./extras/strfry:/opt/strfry diff --git a/docs/dev/nostr.md b/docs/dev/nostr.md new file mode 100644 index 0000000..2a571c4 --- /dev/null +++ b/docs/dev/nostr.md @@ -0,0 +1,57 @@ +# Nostr + +## strfry + +The `extras/strfry` directory contains code to integrate [strfry][1] with +akkounts, so that notes published to the relay have to be authored by (or in +some cases just related to) local users who have verified their Nostr public +key. + +### Requirements + +[Deno](https://deno.com/) needs to be installed on the machine that you run +strfry on. + +We provide a Docker image with recent strfry and Deno builds: + +https://gitea.kosmos.org/kosmos/-/packages/container/strfry-deno/ + +### Configuration + +You can use either environment variables (see e.g. the `strfry` service in +`docker-compose-yml`) or a local `.env` file in the same working directory +that you place the extra files in (e.g. `/opt/strfry`). + +In your `strfry.conf`, configure `strfry-policy.ts` as the write policy, like so: + +``` +writePolicy { + plugin = "/opt/strfry/strfry-policy.ts" +} +``` + +All dependencies will be downloaded and cached automatically when the plugin is +called for the first time. + +### Manual tasks + +You can sync all notes authored by local users (any account that has verified +their Nostr pubkey with akkounts) from a remote [strfry][1] relay via negentropy +sync: + + deno run -A /opt/strfry/strfry-sync.ts wss://nostr.kosmos.org + +Or, in the running container when using Docker Compose: + + docker compose exec strfry deno run -A /opt/strfry/strfry-sync.ts wss://nostr.kosmos.org + +The `strfry` service container also exposes the local relay on your local host +on port 4777. + +[nak](https://github.com/fiatjaf/nak) is a helpful tool for manual Nostr tasks. +Here's how you can grab a note by its event ID from a remote relay and publish +it to your local strfry for example: + + nak req -i 0fb010192685b86b0810b3de3706fbbf3b8c1db30b14533094a2b9700c820cdc nostr.kosmos.org | nak event ws://localhost:4777 + +[1]: https://github.com/hoytech/strfry diff --git a/extras/strfry/deno.lock b/extras/strfry/deno.lock index c5c416f..b079bfb 100644 --- a/extras/strfry/deno.lock +++ b/extras/strfry/deno.lock @@ -1,101 +1,231 @@ { - "version": "3", - "packages": { - "specifiers": { - "jsr:@nostr/tools@^2.3.1": "jsr:@nostr/tools@2.3.1", - "npm:@noble/ciphers@^0.5.1": "npm:@noble/ciphers@0.5.3", - "npm:@noble/curves@1.2.0": "npm:@noble/curves@1.2.0", - "npm:@noble/hashes@1.3.1": "npm:@noble/hashes@1.3.1", - "npm:@scure/base@1.1.1": "npm:@scure/base@1.1.1", - "npm:ldapts": "npm:ldapts@7.0.12" + "version": "4", + "specifiers": { + "jsr:@nostr/tools@*": "2.3.1", + "jsr:@nostr/tools@^2.3.1": "2.3.1", + "jsr:@nostrify/nostrify@0.36": "0.36.2", + "jsr:@nostrify/policies@*": "0.36.1", + "jsr:@nostrify/strfry@*": "0.2.1", + "jsr:@nostrify/types@0.35": "0.35.0", + "jsr:@nostrify/types@0.36": "0.36.0", + "jsr:@std/bytes@^1.0.5": "1.0.5", + "jsr:@std/encoding@~0.224.1": "0.224.3", + "jsr:@std/json@^1.0.1": "1.0.1", + "jsr:@std/streams@^1.0.7": "1.0.9", + "jsr:@std/streams@^1.0.8": "1.0.9", + "npm:@noble/ciphers@~0.5.1": "0.5.3", + "npm:@noble/curves@1.2.0": "1.2.0", + "npm:@noble/hashes@1.3.1": "1.3.1", + "npm:@scure/base@1.1.1": "1.1.1", + "npm:@scure/bip32@^1.4.0": "1.6.2", + "npm:@scure/bip39@^1.3.0": "1.5.4", + "npm:ldapts@*": "7.0.12", + "npm:lru-cache@^10.2.0": "10.4.3", + "npm:nostr-tools@^2.7.0": "2.12.0", + "npm:websocket-ts@^2.1.5": "2.2.1", + "npm:zod@^3.23.8": "3.24.2" + }, + "jsr": { + "@nostr/tools@2.3.1": { + "integrity": "af01dc45cb28784c584d7a0699707196f397bcc53946efa582a01b11ddde4d61", + "dependencies": [ + "npm:@noble/ciphers", + "npm:@noble/curves", + "npm:@noble/hashes", + "npm:@scure/base" + ] }, - "jsr": { - "@nostr/tools@2.3.1": { - "integrity": "af01dc45cb28784c584d7a0699707196f397bcc53946efa582a01b11ddde4d61", - "dependencies": [ - "npm:@noble/ciphers@^0.5.1", - "npm:@noble/curves@1.2.0", - "npm:@noble/hashes@1.3.1", - "npm:@scure/base@1.1.1" - ] - } + "@nostrify/nostrify@0.36.2": { + "integrity": "cc4787ca170b623a2e5dfed1baa4426077daa6143af728ea7dd325d58f4d04d6", + "dependencies": [ + "jsr:@nostrify/types@0.35", + "jsr:@std/encoding", + "npm:@scure/bip32", + "npm:@scure/bip39", + "npm:lru-cache", + "npm:nostr-tools", + "npm:websocket-ts", + "npm:zod" + ] }, - "npm": { - "@noble/ciphers@0.5.3": { - "integrity": "sha512-B0+6IIHiqEs3BPMT0hcRmHvEj2QHOLu+uwt+tqDDeVd0oyVzh7BPrDcPjRnV1PV/5LaknXJJQvOuRGR0zQJz+w==", - "dependencies": {} - }, - "@noble/curves@1.2.0": { - "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", - "dependencies": { - "@noble/hashes": "@noble/hashes@1.3.2" - } - }, - "@noble/hashes@1.3.1": { - "integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==", - "dependencies": {} - }, - "@noble/hashes@1.3.2": { - "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", - "dependencies": {} - }, - "@scure/base@1.1.1": { - "integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==", - "dependencies": {} - }, - "@types/asn1@0.2.4": { - "integrity": "sha512-V91DSJ2l0h0gRhVP4oBfBzRBN9lAbPUkGDMCnwedqPKX2d84aAMc9CulOvxdw1f7DfEYx99afab+Rsm3e52jhA==", - "dependencies": { - "@types/node": "@types/node@18.16.19" - } - }, - "@types/node@18.16.19": { - "integrity": "sha512-IXl7o+R9iti9eBW4Wg2hx1xQDig183jj7YLn8F7udNceyfkbn1ZxmzZXuak20gR40D7pIkIY1kYGx5VIGbaHKA==", - "dependencies": {} - }, - "@types/uuid@9.0.8": { - "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", - "dependencies": {} - }, - "asn1@0.2.6": { - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "dependencies": { - "safer-buffer": "safer-buffer@2.1.2" - } - }, - "debug@4.3.5": { - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", - "dependencies": { - "ms": "ms@2.1.2" - } - }, - "ldapts@7.0.12": { - "integrity": "sha512-orwgIejUi/ZyGah9y8jWZmFUg8Ci5M8WAv0oZjSf3MVuk1sRBdor9Qy1ttGHbYpWj96HXKFunQ8AYZ8WWGp17g==", - "dependencies": { - "@types/asn1": "@types/asn1@0.2.4", - "@types/uuid": "@types/uuid@9.0.8", - "asn1": "asn1@0.2.6", - "debug": "debug@4.3.5", - "strict-event-emitter-types": "strict-event-emitter-types@2.0.0", - "uuid": "uuid@9.0.1" - } - }, - "ms@2.1.2": { - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dependencies": {} - }, - "safer-buffer@2.1.2": { - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dependencies": {} - }, - "strict-event-emitter-types@2.0.0": { - "integrity": "sha512-Nk/brWYpD85WlOgzw5h173aci0Teyv8YdIAEtV+N88nDB0dLlazZyJMIsN6eo1/AR61l+p6CJTG1JIyFaoNEEA==", - "dependencies": {} - }, - "uuid@9.0.1": { - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "dependencies": {} - } + "@nostrify/policies@0.36.1": { + "integrity": "6d59af115a687fcd18b6caebab0e4f50ee6cdb0aafa2aacd0aec2065021275b4", + "dependencies": [ + "jsr:@nostrify/nostrify", + "jsr:@nostrify/types@0.35", + "npm:nostr-tools" + ] + }, + "@nostrify/strfry@0.2.1": { + "integrity": "be437b13f49e6564e557da23072bf642723a603568f672543a64d9fda6663432", + "dependencies": [ + "jsr:@nostrify/types@0.36", + "jsr:@std/json", + "jsr:@std/streams@^1.0.8" + ] + }, + "@nostrify/types@0.35.0": { + "integrity": "b8d515563d467072694557d5626fa1600f74e83197eef45dd86a9a99c64f7fe6" + }, + "@nostrify/types@0.36.0": { + "integrity": "b3413467debcbd298d217483df4e2aae6c335a34765c90ac7811cf7c637600e7" + }, + "@std/bytes@1.0.5": { + "integrity": "4465dd739d7963d964c809202ebea6d5c6b8e3829ef25c6a224290fbb8a1021e" + }, + "@std/encoding@0.224.3": { + "integrity": "5e861b6d81be5359fad4155e591acf17c0207b595112d1840998bb9f476dbdaf" + }, + "@std/json@1.0.1": { + "integrity": "1f0f70737e8827f9acca086282e903677bc1bb0c8ffcd1f21bca60039563049f", + "dependencies": [ + "jsr:@std/streams@^1.0.7" + ] + }, + "@std/streams@1.0.9": { + "integrity": "a9d26b1988cdd7aa7b1f4b51e1c36c1557f3f252880fa6cc5b9f37078b1a5035", + "dependencies": [ + "jsr:@std/bytes" + ] + } + }, + "npm": { + "@noble/ciphers@0.5.3": { + "integrity": "sha512-B0+6IIHiqEs3BPMT0hcRmHvEj2QHOLu+uwt+tqDDeVd0oyVzh7BPrDcPjRnV1PV/5LaknXJJQvOuRGR0zQJz+w==" + }, + "@noble/curves@1.1.0": { + "integrity": "sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==", + "dependencies": [ + "@noble/hashes@1.3.1" + ] + }, + "@noble/curves@1.2.0": { + "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "dependencies": [ + "@noble/hashes@1.3.2" + ] + }, + "@noble/curves@1.8.2": { + "integrity": "sha512-vnI7V6lFNe0tLAuJMu+2sX+FcL14TaCWy1qiczg1VwRmPrpQCdq5ESXQMqUc2tluRNf6irBXrWbl1mGN8uaU/g==", + "dependencies": [ + "@noble/hashes@1.7.2" + ] + }, + "@noble/hashes@1.3.1": { + "integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==" + }, + "@noble/hashes@1.3.2": { + "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==" + }, + "@noble/hashes@1.7.2": { + "integrity": "sha512-biZ0NUSxyjLLqo6KxEJ1b+C2NAx0wtDoFvCaXHGgUkeHzf3Xc1xKumFKREuT7f7DARNZ/slvYUwFG6B0f2b6hQ==" + }, + "@scure/base@1.1.1": { + "integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==" + }, + "@scure/base@1.2.4": { + "integrity": "sha512-5Yy9czTO47mqz+/J8GM6GIId4umdCk1wc1q8rKERQulIoc8VP9pzDcghv10Tl2E7R96ZUx/PhND3ESYUQX8NuQ==" + }, + "@scure/bip32@1.3.1": { + "integrity": "sha512-osvveYtyzdEVbt3OfwwXFr4P2iVBL5u1Q3q4ONBfDY/UpOuXmOlbgwc1xECEboY8wIays8Yt6onaWMUdUbfl0A==", + "dependencies": [ + "@noble/curves@1.1.0", + "@noble/hashes@1.3.2", + "@scure/base@1.1.1" + ] + }, + "@scure/bip32@1.6.2": { + "integrity": "sha512-t96EPDMbtGgtb7onKKqxRLfE5g05k7uHnHRM2xdE6BP/ZmxaLtPek4J4KfVn/90IQNrU1IOAqMgiDtUdtbe3nw==", + "dependencies": [ + "@noble/curves@1.8.2", + "@noble/hashes@1.7.2", + "@scure/base@1.2.4" + ] + }, + "@scure/bip39@1.2.1": { + "integrity": "sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==", + "dependencies": [ + "@noble/hashes@1.3.2", + "@scure/base@1.1.1" + ] + }, + "@scure/bip39@1.5.4": { + "integrity": "sha512-TFM4ni0vKvCfBpohoh+/lY05i9gRbSwXWngAsF4CABQxoaOHijxuaZ2R6cStDQ5CHtHO9aGJTr4ksVJASRRyMA==", + "dependencies": [ + "@noble/hashes@1.7.2", + "@scure/base@1.2.4" + ] + }, + "@types/asn1@0.2.4": { + "integrity": "sha512-V91DSJ2l0h0gRhVP4oBfBzRBN9lAbPUkGDMCnwedqPKX2d84aAMc9CulOvxdw1f7DfEYx99afab+Rsm3e52jhA==", + "dependencies": [ + "@types/node" + ] + }, + "@types/node@18.16.19": { + "integrity": "sha512-IXl7o+R9iti9eBW4Wg2hx1xQDig183jj7YLn8F7udNceyfkbn1ZxmzZXuak20gR40D7pIkIY1kYGx5VIGbaHKA==" + }, + "@types/uuid@9.0.8": { + "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==" + }, + "asn1@0.2.6": { + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dependencies": [ + "safer-buffer" + ] + }, + "debug@4.3.5": { + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "dependencies": [ + "ms" + ] + }, + "ldapts@7.0.12": { + "integrity": "sha512-orwgIejUi/ZyGah9y8jWZmFUg8Ci5M8WAv0oZjSf3MVuk1sRBdor9Qy1ttGHbYpWj96HXKFunQ8AYZ8WWGp17g==", + "dependencies": [ + "@types/asn1", + "@types/uuid", + "asn1", + "debug", + "strict-event-emitter-types", + "uuid" + ] + }, + "lru-cache@10.4.3": { + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" + }, + "ms@2.1.2": { + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "nostr-tools@2.12.0": { + "integrity": "sha512-pUWEb020gTvt1XZvTa8AKNIHWFapjsv2NKyk43Ez2nnvz6WSXsrTFE0XtkNLSRBjPn6EpxumKeNiVzLz74jNSA==", + "dependencies": [ + "@noble/ciphers", + "@noble/curves@1.2.0", + "@noble/hashes@1.3.1", + "@scure/base@1.1.1", + "@scure/bip32@1.3.1", + "@scure/bip39@1.2.1", + "nostr-wasm" + ] + }, + "nostr-wasm@0.1.0": { + "integrity": "sha512-78BTryCLcLYv96ONU8Ws3Q1JzjlAt+43pWQhIl86xZmWeegYCNLPml7yQ+gG3vR6V5h4XGj+TxO+SS5dsThQIA==" + }, + "safer-buffer@2.1.2": { + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "strict-event-emitter-types@2.0.0": { + "integrity": "sha512-Nk/brWYpD85WlOgzw5h173aci0Teyv8YdIAEtV+N88nDB0dLlazZyJMIsN6eo1/AR61l+p6CJTG1JIyFaoNEEA==" + }, + "uuid@9.0.1": { + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==" + }, + "websocket-ts@2.2.1": { + "integrity": "sha512-YKPDfxlK5qOheLZ2bTIiktZO1bpfGdNCPJmTEaPW7G9UXI1GKjDdeacOrsULUS000OPNxDVOyAuKLuIWPqWM0Q==" + }, + "zod@3.24.2": { + "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==" } }, "remote": { diff --git a/extras/strfry/ldap-policy.ts b/extras/strfry/ldap-policy.ts index 5f45539..84f3ca8 100644 --- a/extras/strfry/ldap-policy.ts +++ b/extras/strfry/ldap-policy.ts @@ -1,8 +1,8 @@ -import type { IterablePubkeys, Policy } from 'https://gitlab.com/soapbox-pub/strfry-policies/-/raw/develop/mod.ts'; +import { NostrEvent, NostrRelayInfo, NostrRelayOK, NPolicy } from 'jsr:@nostrify/types@^0.35.0'; +import { nip57 } from 'jsr:@nostr/tools'; import { Client } from 'npm:ldapts'; -import { nip57 } from '@nostr/tools'; -interface LdapConfig { +export interface LdapConfig { url: string; bindDN: string; password: string; @@ -10,68 +10,73 @@ interface LdapConfig { whitelistPubkeys?: IterablePubkeys; } -const ldapPolicy: Policy = async (msg, opts) => { - const client = new Client({ url: opts.url }); - const { kind, tags } = msg.event; - let { pubkey } = msg.event; - let out = { id: msg.event.id } +export class LdapPolicy implements NPolicy { + constructor(private opts: LdapConfig) {} - if (opts.whitelistPubkeys.includes(pubkey)) { - out['action'] = 'accept'; - out['msg'] = ''; - return out; - } + // deno-lint-ignore require-await + async call(event: NostrEvent): Promise { + const client = new Client({ url: this.opts.url }); + const { id, kind, tags } = event; + let { pubkey } = event; - // Zap receipt - if (kind === 9735) { - const descriptionTag = tags.find(([t, v]) => t === 'description' && v); - const invalidZapRequestMsg = 'Zap receipts must contain a valid zap request from a relay member'; - - if (typeof descriptionTag === 'undefined') { - out['action'] = 'reject'; - out['msg'] = invalidZapRequestMsg; - return out; + if (this.opts.whitelistPubkeys.includes(pubkey)) { + return ['OK', id, true, '']; } - const zapRequestJSON = descriptionTag[1]; - const validationResult = nip57.validateZapRequest(zapRequestJSON); + // Zap receipt + if (kind === 9735) { + const descriptionTag = tags.find(([t, v]) => t === 'description' && v); + const invalidZapRequestMsg = 'Zap receipts must contain a valid zap request from a relay member'; - // TODO - // The zap receipt event's pubkey MUST be the same as the recipient's lnurl provider's nostrPubkey (retrieved in step 1 of the protocol flow). - // The invoiceAmount contained in the bolt11 tag of the zap receipt MUST equal the amount tag of the zap request (if present). + if (typeof descriptionTag === 'undefined') { + return ['OK', id, false, invalidZapRequestMsg]; + } - if (validationResult === null) { - pubkey = JSON.parse(zapRequestJSON).pubkey; - } else { - out['action'] = 'reject'; - out['msg'] = invalidZapRequestMsg; - return out; + const zapRequestJSON = descriptionTag[1]; + const validationResult = nip57.validateZapRequest(zapRequestJSON); + + // TODO + // The zap receipt event's pubkey MUST be the same as the recipient's lnurl provider's nostrPubkey (retrieved in step 1 of the protocol flow). + // The invoiceAmount contained in the bolt11 tag of the zap receipt MUST equal the amount tag of the zap request (if present). + + if (validationResult === null) { + pubkey = JSON.parse(zapRequestJSON).pubkey; + } else { + return ['OK', id, false, invalidZapRequestMsg]; + } + } + + const out = { accept: true, msg: ''}; + + try { + await client.bind(this.opts.bindDN, this.opts.password); + + const { searchEntries } = await client.search(this.opts.searchDN, { + filter: `(nostrKey=${pubkey})`, + attributes: ['nostrKey'] + }); + const memberKey = searchEntries[0]?.nostrKey; + + if (memberKey === pubkey) { + out['accept'] = true; + } else { + out['accept'] = false; + out['msg'] = 'Only members can publish notes on this relay'; + } + } catch (ex) { + out['accept'] = false; + out['msg'] = 'Auth service temporarily unavailable'; + } finally { + await client.unbind(); + return ['OK', id, out['accept'], out['msg']]; } } - try { - await client.bind(opts.bindDN, opts.password); - - const { searchEntries } = await client.search(opts.searchDN, { - filter: `(nostrKey=${pubkey})`, - attributes: ['nostrKey'] - }); - const memberKey = searchEntries[0]?.nostrKey; - - if (memberKey === pubkey) { - out['action'] = 'accept'; - out['msg'] = ''; - } else { - out['action'] = 'reject'; - out['msg'] = 'Only members can publish notes on this relay'; - } - } catch (ex) { - out['action'] = 'reject'; - out['msg'] = 'Auth service temporarily unavailable'; - } finally { - await client.unbind(); - return out; + get info(): NostrRelayInfo { + return { + limitation: { + restricted_writes: true, + }, + }; } -}; - -export default ldapPolicy; +} diff --git a/extras/strfry/strfry-policy.ts b/extras/strfry/strfry-policy.ts index 8086756..14913da 100755 --- a/extras/strfry/strfry-policy.ts +++ b/extras/strfry/strfry-policy.ts @@ -1,20 +1,20 @@ #!/bin/sh -//bin/true; exec deno run -A "$0" "$@" +//bin/true; exec deno run --unstable-kv -A "$0" "$@" import { - antiDuplicationPolicy, - hellthreadPolicy, - pipeline, - rateLimitPolicy, + AntiDuplicationPolicy, + HellthreadPolicy, + PipePolicy, readStdin, writeStdout, -} from 'https://gitlab.com/soapbox-pub/strfry-policies/-/raw/develop/mod.ts'; -import ldapPolicy from './ldap-policy.ts'; +} from 'jsr:@nostrify/policies'; +import { strfry } from 'jsr:@nostrify/strfry'; +import { LdapConfig, LdapPolicy } from './ldap-policy.ts'; import { load } from "https://deno.land/std@0.224.0/dotenv/mod.ts"; const dirname = new URL('.', import.meta.url).pathname; await load({ envPath: `${dirname}/.env`, export: true }); -const ldapConfig = { +const ldapConfig: LdapConfig = { url: Deno.env.get("LDAP_URL"), bindDN: Deno.env.get("LDAP_BIND_DN"), password: Deno.env.get("LDAP_PASSWORD"), @@ -22,13 +22,10 @@ const ldapConfig = { whitelistPubkeys: Deno.env.get("WHITELIST_PUBKEYS")?.split(',') } -for await (const msg of readStdin()) { - const result = await pipeline(msg, [ - [hellthreadPolicy, { limit: 10 }], - [antiDuplicationPolicy, { ttl: 60000, minLength: 50 }], - [rateLimitPolicy, { whitelist: ['127.0.0.1'] }], - [ldapPolicy, ldapConfig], - ]); +const policy = new PipePolicy([ + new HellthreadPolicy({ limit: 10 }), + new AntiDuplicationPolicy({ kv: await Deno.openKv(), expireIn: 60000, minLength: 50 }), + new LdapPolicy(ldapConfig) +]); - writeStdout(result); -} +await strfry(policy);