import { NostrEvent, NostrRelayInfo, NostrRelayOK, NPolicy } from 'jsr:@nostrify/types@^0.35.0'; import { nip57 } from 'jsr:@nostr/tools'; import { Client } from 'npm:ldapts'; export interface LdapConfig { url: string; bindDN: string; password: string; searchDN: string; whitelistPubkeys?: IterablePubkeys; } export class LdapPolicy implements NPolicy { constructor(private opts: LdapConfig) {} // 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; if (this.opts.whitelistPubkeys.includes(pubkey)) { return ['OK', id, true, '']; } // 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') { return ['OK', id, false, invalidZapRequestMsg]; } 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 (e) { out['accept'] = false; out['msg'] = 'Auth service temporarily unavailable'; console.warn(`[ldap-policy] Auth service temporarily unavailable: ${e.message}`) } finally { await client.unbind(); return ['OK', id, out['accept'], out['msg']]; } } get info(): NostrRelayInfo { return { limitation: { restricted_writes: true, }, }; } }