Merge pull request 'Upgrade strfry/deno, port strfry policies to @nostrify/policies' (#214) from chore/upgrade_strfry_deno into master
All checks were successful
continuous-integration/drone/push Build is passing

Reviewed-on: #214
This commit is contained in:
Râu Cao 2025-04-18 10:51:35 +00:00
commit 536052e9bf
5 changed files with 360 additions and 171 deletions

View File

@ -111,7 +111,7 @@ services:
- redis - redis
strfry: strfry:
image: gitea.kosmos.org/kosmos/strfry-deno:1.1.1 image: gitea.kosmos.org/kosmos/strfry-deno:2.0.0
volumes: volumes:
- ./docker/strfry/strfry.conf:/etc/strfry.conf - ./docker/strfry/strfry.conf:/etc/strfry.conf
- ./extras/strfry:/opt/strfry - ./extras/strfry:/opt/strfry

57
docs/dev/nostr.md Normal file
View File

@ -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

320
extras/strfry/deno.lock generated
View File

@ -1,101 +1,231 @@
{ {
"version": "3", "version": "4",
"packages": { "specifiers": {
"specifiers": { "jsr:@nostr/tools@*": "2.3.1",
"jsr:@nostr/tools@^2.3.1": "jsr:@nostr/tools@2.3.1", "jsr:@nostr/tools@^2.3.1": "2.3.1",
"npm:@noble/ciphers@^0.5.1": "npm:@noble/ciphers@0.5.3", "jsr:@nostrify/nostrify@0.36": "0.36.2",
"npm:@noble/curves@1.2.0": "npm:@noble/curves@1.2.0", "jsr:@nostrify/policies@*": "0.36.1",
"npm:@noble/hashes@1.3.1": "npm:@noble/hashes@1.3.1", "jsr:@nostrify/strfry@*": "0.2.1",
"npm:@scure/base@1.1.1": "npm:@scure/base@1.1.1", "jsr:@nostrify/types@0.35": "0.35.0",
"npm:ldapts": "npm:ldapts@7.0.12" "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": { "@nostrify/nostrify@0.36.2": {
"@nostr/tools@2.3.1": { "integrity": "cc4787ca170b623a2e5dfed1baa4426077daa6143af728ea7dd325d58f4d04d6",
"integrity": "af01dc45cb28784c584d7a0699707196f397bcc53946efa582a01b11ddde4d61", "dependencies": [
"dependencies": [ "jsr:@nostrify/types@0.35",
"npm:@noble/ciphers@^0.5.1", "jsr:@std/encoding",
"npm:@noble/curves@1.2.0", "npm:@scure/bip32",
"npm:@noble/hashes@1.3.1", "npm:@scure/bip39",
"npm:@scure/base@1.1.1" "npm:lru-cache",
] "npm:nostr-tools",
} "npm:websocket-ts",
"npm:zod"
]
}, },
"npm": { "@nostrify/policies@0.36.1": {
"@noble/ciphers@0.5.3": { "integrity": "6d59af115a687fcd18b6caebab0e4f50ee6cdb0aafa2aacd0aec2065021275b4",
"integrity": "sha512-B0+6IIHiqEs3BPMT0hcRmHvEj2QHOLu+uwt+tqDDeVd0oyVzh7BPrDcPjRnV1PV/5LaknXJJQvOuRGR0zQJz+w==", "dependencies": [
"dependencies": {} "jsr:@nostrify/nostrify",
}, "jsr:@nostrify/types@0.35",
"@noble/curves@1.2.0": { "npm:nostr-tools"
"integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", ]
"dependencies": { },
"@noble/hashes": "@noble/hashes@1.3.2" "@nostrify/strfry@0.2.1": {
} "integrity": "be437b13f49e6564e557da23072bf642723a603568f672543a64d9fda6663432",
}, "dependencies": [
"@noble/hashes@1.3.1": { "jsr:@nostrify/types@0.36",
"integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==", "jsr:@std/json",
"dependencies": {} "jsr:@std/streams@^1.0.8"
}, ]
"@noble/hashes@1.3.2": { },
"integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", "@nostrify/types@0.35.0": {
"dependencies": {} "integrity": "b8d515563d467072694557d5626fa1600f74e83197eef45dd86a9a99c64f7fe6"
}, },
"@scure/base@1.1.1": { "@nostrify/types@0.36.0": {
"integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==", "integrity": "b3413467debcbd298d217483df4e2aae6c335a34765c90ac7811cf7c637600e7"
"dependencies": {} },
}, "@std/bytes@1.0.5": {
"@types/asn1@0.2.4": { "integrity": "4465dd739d7963d964c809202ebea6d5c6b8e3829ef25c6a224290fbb8a1021e"
"integrity": "sha512-V91DSJ2l0h0gRhVP4oBfBzRBN9lAbPUkGDMCnwedqPKX2d84aAMc9CulOvxdw1f7DfEYx99afab+Rsm3e52jhA==", },
"dependencies": { "@std/encoding@0.224.3": {
"@types/node": "@types/node@18.16.19" "integrity": "5e861b6d81be5359fad4155e591acf17c0207b595112d1840998bb9f476dbdaf"
} },
}, "@std/json@1.0.1": {
"@types/node@18.16.19": { "integrity": "1f0f70737e8827f9acca086282e903677bc1bb0c8ffcd1f21bca60039563049f",
"integrity": "sha512-IXl7o+R9iti9eBW4Wg2hx1xQDig183jj7YLn8F7udNceyfkbn1ZxmzZXuak20gR40D7pIkIY1kYGx5VIGbaHKA==", "dependencies": [
"dependencies": {} "jsr:@std/streams@^1.0.7"
}, ]
"@types/uuid@9.0.8": { },
"integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", "@std/streams@1.0.9": {
"dependencies": {} "integrity": "a9d26b1988cdd7aa7b1f4b51e1c36c1557f3f252880fa6cc5b9f37078b1a5035",
}, "dependencies": [
"asn1@0.2.6": { "jsr:@std/bytes"
"integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", ]
"dependencies": { }
"safer-buffer": "safer-buffer@2.1.2" },
} "npm": {
}, "@noble/ciphers@0.5.3": {
"debug@4.3.5": { "integrity": "sha512-B0+6IIHiqEs3BPMT0hcRmHvEj2QHOLu+uwt+tqDDeVd0oyVzh7BPrDcPjRnV1PV/5LaknXJJQvOuRGR0zQJz+w=="
"integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", },
"dependencies": { "@noble/curves@1.1.0": {
"ms": "ms@2.1.2" "integrity": "sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==",
} "dependencies": [
}, "@noble/hashes@1.3.1"
"ldapts@7.0.12": { ]
"integrity": "sha512-orwgIejUi/ZyGah9y8jWZmFUg8Ci5M8WAv0oZjSf3MVuk1sRBdor9Qy1ttGHbYpWj96HXKFunQ8AYZ8WWGp17g==", },
"dependencies": { "@noble/curves@1.2.0": {
"@types/asn1": "@types/asn1@0.2.4", "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==",
"@types/uuid": "@types/uuid@9.0.8", "dependencies": [
"asn1": "asn1@0.2.6", "@noble/hashes@1.3.2"
"debug": "debug@4.3.5", ]
"strict-event-emitter-types": "strict-event-emitter-types@2.0.0", },
"uuid": "uuid@9.0.1" "@noble/curves@1.8.2": {
} "integrity": "sha512-vnI7V6lFNe0tLAuJMu+2sX+FcL14TaCWy1qiczg1VwRmPrpQCdq5ESXQMqUc2tluRNf6irBXrWbl1mGN8uaU/g==",
}, "dependencies": [
"ms@2.1.2": { "@noble/hashes@1.7.2"
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", ]
"dependencies": {} },
}, "@noble/hashes@1.3.1": {
"safer-buffer@2.1.2": { "integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA=="
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", },
"dependencies": {} "@noble/hashes@1.3.2": {
}, "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ=="
"strict-event-emitter-types@2.0.0": { },
"integrity": "sha512-Nk/brWYpD85WlOgzw5h173aci0Teyv8YdIAEtV+N88nDB0dLlazZyJMIsN6eo1/AR61l+p6CJTG1JIyFaoNEEA==", "@noble/hashes@1.7.2": {
"dependencies": {} "integrity": "sha512-biZ0NUSxyjLLqo6KxEJ1b+C2NAx0wtDoFvCaXHGgUkeHzf3Xc1xKumFKREuT7f7DARNZ/slvYUwFG6B0f2b6hQ=="
}, },
"uuid@9.0.1": { "@scure/base@1.1.1": {
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", "integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA=="
"dependencies": {} },
} "@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": { "remote": {

View File

@ -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 { Client } from 'npm:ldapts';
import { nip57 } from '@nostr/tools';
interface LdapConfig { export interface LdapConfig {
url: string; url: string;
bindDN: string; bindDN: string;
password: string; password: string;
@ -10,68 +10,73 @@ interface LdapConfig {
whitelistPubkeys?: IterablePubkeys; whitelistPubkeys?: IterablePubkeys;
} }
const ldapPolicy: Policy<LdapConfig> = async (msg, opts) => { export class LdapPolicy implements NPolicy {
const client = new Client({ url: opts.url }); constructor(private opts: LdapConfig) {}
const { kind, tags } = msg.event;
let { pubkey } = msg.event;
let out = { id: msg.event.id }
if (opts.whitelistPubkeys.includes(pubkey)) { // deno-lint-ignore require-await
out['action'] = 'accept'; async call(event: NostrEvent): Promise<NostrRelayOK> {
out['msg'] = ''; const client = new Client({ url: this.opts.url });
return out; const { id, kind, tags } = event;
} let { pubkey } = event;
// Zap receipt if (this.opts.whitelistPubkeys.includes(pubkey)) {
if (kind === 9735) { return ['OK', id, true, ''];
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;
} }
const zapRequestJSON = descriptionTag[1]; // Zap receipt
const validationResult = nip57.validateZapRequest(zapRequestJSON); 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 if (typeof descriptionTag === 'undefined') {
// 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). return ['OK', id, false, invalidZapRequestMsg];
// 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) { const zapRequestJSON = descriptionTag[1];
pubkey = JSON.parse(zapRequestJSON).pubkey; const validationResult = nip57.validateZapRequest(zapRequestJSON);
} else {
out['action'] = 'reject'; // TODO
out['msg'] = invalidZapRequestMsg; // 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).
return out; // 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 { get info(): NostrRelayInfo {
await client.bind(opts.bindDN, opts.password); return {
limitation: {
const { searchEntries } = await client.search(opts.searchDN, { restricted_writes: true,
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;
} }
}; }
export default ldapPolicy;

View File

@ -1,20 +1,20 @@
#!/bin/sh #!/bin/sh
//bin/true; exec deno run -A "$0" "$@" //bin/true; exec deno run --unstable-kv -A "$0" "$@"
import { import {
antiDuplicationPolicy, AntiDuplicationPolicy,
hellthreadPolicy, HellthreadPolicy,
pipeline, PipePolicy,
rateLimitPolicy,
readStdin, readStdin,
writeStdout, writeStdout,
} from 'https://gitlab.com/soapbox-pub/strfry-policies/-/raw/develop/mod.ts'; } from 'jsr:@nostrify/policies';
import ldapPolicy from './ldap-policy.ts'; 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"; import { load } from "https://deno.land/std@0.224.0/dotenv/mod.ts";
const dirname = new URL('.', import.meta.url).pathname; const dirname = new URL('.', import.meta.url).pathname;
await load({ envPath: `${dirname}/.env`, export: true }); await load({ envPath: `${dirname}/.env`, export: true });
const ldapConfig = { const ldapConfig: LdapConfig = {
url: Deno.env.get("LDAP_URL"), url: Deno.env.get("LDAP_URL"),
bindDN: Deno.env.get("LDAP_BIND_DN"), bindDN: Deno.env.get("LDAP_BIND_DN"),
password: Deno.env.get("LDAP_PASSWORD"), password: Deno.env.get("LDAP_PASSWORD"),
@ -22,13 +22,10 @@ const ldapConfig = {
whitelistPubkeys: Deno.env.get("WHITELIST_PUBKEYS")?.split(',') whitelistPubkeys: Deno.env.get("WHITELIST_PUBKEYS")?.split(',')
} }
for await (const msg of readStdin()) { const policy = new PipePolicy([
const result = await pipeline(msg, [ new HellthreadPolicy({ limit: 10 }),
[hellthreadPolicy, { limit: 10 }], new AntiDuplicationPolicy({ kv: await Deno.openKv(), expireIn: 60000, minLength: 50 }),
[antiDuplicationPolicy, { ttl: 60000, minLength: 50 }], new LdapPolicy(ldapConfig)
[rateLimitPolicy, { whitelist: ['127.0.0.1'] }], ]);
[ldapPolicy, ldapConfig],
]);
writeStdout(result); await strfry(policy);
}