From 926dc062942c9627ad52aafabe3668062c550f11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Wed, 19 Jun 2024 19:57:09 +0200 Subject: [PATCH 1/8] Add global setting for own nostr relay --- .env.example | 5 +++++ app/models/setting.rb | 3 +++ app/views/admin/settings/services/_nostr.html.erb | 5 +++++ 3 files changed, 13 insertions(+) diff --git a/.env.example b/.env.example index 1006d96..438b759 100644 --- a/.env.example +++ b/.env.example @@ -29,6 +29,7 @@ # # Service Integrations +# (sorted alphabetically by service name) # # BTCPAY_PUBLIC_URL='https://btcpay.example.com' @@ -62,5 +63,9 @@ # MEDIAWIKI_PUBLIC_URL='https://wiki.kosmos.org' +# NOSTR_PRIVATE_KEY='123456abcdef...' +# NOSTR_PUBLIC_KEY='123456abcdef...' +# NOSTR_RELAY_URL='wss://nostr.kosmos.org' + # RS_STORAGE_URL='https://storage.kosmos.org' # RS_REDIS_URL='redis://localhost:6379/2' diff --git a/app/models/setting.rb b/app/models/setting.rb index 15c8755..0b41a30 100644 --- a/app/models/setting.rb +++ b/app/models/setting.rb @@ -169,6 +169,9 @@ class Setting < RailsSettings::Base field :nostr_public_key, type: :string, default: ENV["NOSTR_PUBLIC_KEY"].presence + field :nostr_relay_url, type: :string, + default: ENV["NOSTR_RELAY_URL"].presence + field :nostr_zaps_relay_limit, type: :integer, default: 12 diff --git a/app/views/admin/settings/services/_nostr.html.erb b/app/views/admin/settings/services/_nostr.html.erb index 302c52a..c5db5e3 100644 --- a/app/views/admin/settings/services/_nostr.html.erb +++ b/app/views/admin/settings/services/_nostr.html.erb @@ -19,6 +19,11 @@ title: "Public key", description: "The corresponding public key of the accounts service" ) %> + <%= render FormElements::FieldsetResettableSettingComponent.new( + key: :nostr_relay_url, + title: "Relay URL", + description: "Websockets URL of a relay associated with #{Setting.primary_domain}" + ) %>
From 87d900b627126a70a6bae5acac371936e848a8e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Wed, 19 Jun 2024 20:06:07 +0200 Subject: [PATCH 2/8] Add own relay to NIP-05 relay list if configured --- app/controllers/well_known_controller.rb | 14 +++++++++++--- spec/requests/well_known_spec.rb | 21 +++++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/app/controllers/well_known_controller.rb b/app/controllers/well_known_controller.rb index 02eb02c..5cf4398 100644 --- a/app/controllers/well_known_controller.rb +++ b/app/controllers/well_known_controller.rb @@ -5,11 +5,19 @@ class WellKnownController < ApplicationController @user = User.where(cn: params[:name], ou: domain).first http_status :not_found and return if @user.nil? || @user.nostr_pubkey.blank? + res = { + names: { "#{@user.cn}": @user.nostr_pubkey } + } + + if Setting.nostr_relay_url + res[:relays] = { + @user.nostr_pubkey => [ Setting.nostr_relay_url ] + } + end + respond_to do |format| format.json do - render json: { - names: { "#{@user.cn}": @user.nostr_pubkey } - }.to_json + render json: res.to_json end end end diff --git a/spec/requests/well_known_spec.rb b/spec/requests/well_known_spec.rb index 9862b79..eba66e8 100644 --- a/spec/requests/well_known_spec.rb +++ b/spec/requests/well_known_spec.rb @@ -45,6 +45,27 @@ RSpec.describe "Well-known URLs", type: :request do expect(res["names"].keys.size).to eq(1) expect(res["names"]["bobdylan"]).to eq(user.nostr_pubkey) end + + context "without relay configured" do + it "does not include a recommended relay" do + get "/.well-known/nostr.json?name=bobdylan" + res = JSON.parse(response.body) + expect(res["relays"]).to be_nil + end + end + + context "with relay configured" do + before do + Setting.nostr_relay_url = "wss://nostr.kosmos.org" + end + + it "includes a recommended relay" do + get "/.well-known/nostr.json?name=bobdylan" + res = JSON.parse(response.body) + expect(res["relays"][user.nostr_pubkey].length).to eq(1) + expect(res["relays"][user.nostr_pubkey].first).to eq("wss://nostr.kosmos.org") + end + end end end end From cbfa1480510bde8ea88e987f886582d135e52e1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Wed, 19 Jun 2024 20:26:24 +0200 Subject: [PATCH 3/8] Publish zap receipts to own relay in addition to requested ones --- app/services/nostr_manager/publish_zap_receipt.rb | 7 ++++++- .../nostr_manager/publish_zap_receipt_spec.rb | 13 +++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/app/services/nostr_manager/publish_zap_receipt.rb b/app/services/nostr_manager/publish_zap_receipt.rb index 22a7714..b529c41 100644 --- a/app/services/nostr_manager/publish_zap_receipt.rb +++ b/app/services/nostr_manager/publish_zap_receipt.rb @@ -6,8 +6,13 @@ module NostrManager def call tags = parse_tags(@zap.request_event.tags) + relays = tags[:relays].take(Setting.nostr_zaps_relay_limit) - tags[:relays].take(Setting.nostr_zaps_relay_limit).each do |relay_url| + if Setting.nostr_relay_url.present? + relays << Setting.nostr_relay_url + end + + relays.uniq.each do |relay_url| if @delayed NostrPublishEventJob.perform_later(event: @zap.receipt, relay_url: relay_url) else diff --git a/spec/services/nostr_manager/publish_zap_receipt_spec.rb b/spec/services/nostr_manager/publish_zap_receipt_spec.rb index 452e34e..5136af9 100644 --- a/spec/services/nostr_manager/publish_zap_receipt_spec.rb +++ b/spec/services/nostr_manager/publish_zap_receipt_spec.rb @@ -35,6 +35,19 @@ RSpec.describe NostrManager::PublishZapReceipt, type: :model do described_class.call(zap: zap) end + + context "with own relay configured" do + before do + Setting.nostr_relay_url = "wss://foobar.kosmos.org" + end + + it "also publishes the receipt to our own relay" do + expect(NostrPublishEventJob).to receive(:perform_later) + .exactly(13).times.and_return(true) + + described_class.call(zap: zap) + end + end end end end From 7ac3130c18d34d38bb7904ce89749db1afdd1276 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Wed, 19 Jun 2024 20:31:31 +0200 Subject: [PATCH 4/8] Consistent formatting --- app/controllers/well_known_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/well_known_controller.rb b/app/controllers/well_known_controller.rb index 5cf4398..8e4ac07 100644 --- a/app/controllers/well_known_controller.rb +++ b/app/controllers/well_known_controller.rb @@ -6,7 +6,7 @@ class WellKnownController < ApplicationController http_status :not_found and return if @user.nil? || @user.nostr_pubkey.blank? res = { - names: { "#{@user.cn}": @user.nostr_pubkey } + names: { @user.cn => @user.nostr_pubkey } } if Setting.nostr_relay_url From 48ab96dda936b3f1b7055dfd51f3ca25c0b5c01f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Wed, 19 Jun 2024 20:57:22 +0200 Subject: [PATCH 5/8] Support "_" placeholder username for domain's own NIP-05 --- app/controllers/well_known_controller.rb | 29 ++++++++++++++++-------- spec/requests/well_known_spec.rb | 27 ++++++++++++++++++---- 2 files changed, 43 insertions(+), 13 deletions(-) diff --git a/app/controllers/well_known_controller.rb b/app/controllers/well_known_controller.rb index 8e4ac07..bc342db 100644 --- a/app/controllers/well_known_controller.rb +++ b/app/controllers/well_known_controller.rb @@ -1,18 +1,23 @@ class WellKnownController < ApplicationController + before_action :require_nostr_enabled, only: [ :nostr ] + def nostr http_status :unprocessable_entity and return if params[:name].blank? domain = request.headers["X-Forwarded-Host"].presence || Setting.primary_domain - @user = User.where(cn: params[:name], ou: domain).first - http_status :not_found and return if @user.nil? || @user.nostr_pubkey.blank? + relay_url = Setting.nostr_relay_url - res = { - names: { @user.cn => @user.nostr_pubkey } - } + if params[:name] == "_" + # pubkey for the primary domain without a username (e.g. kosmos.org) + res = { names: { "_": Setting.nostr_public_key } } + else + @user = User.where(cn: params[:name], ou: domain).first + http_status :not_found and return if @user.nil? || @user.nostr_pubkey.blank? - if Setting.nostr_relay_url - res[:relays] = { - @user.nostr_pubkey => [ Setting.nostr_relay_url ] - } + res = { names: { @user.cn => @user.nostr_pubkey } } + end + + if relay_url + res[:relays] = { @user.nostr_pubkey => [ relay_url ] } end respond_to do |format| @@ -21,4 +26,10 @@ class WellKnownController < ApplicationController end end end + + private + + def require_nostr_enabled + http_status :not_found unless Setting.nostr_enabled? + end end diff --git a/spec/requests/well_known_spec.rb b/spec/requests/well_known_spec.rb index eba66e8..0300edf 100644 --- a/spec/requests/well_known_spec.rb +++ b/spec/requests/well_known_spec.rb @@ -2,21 +2,21 @@ require 'rails_helper' RSpec.describe "Well-known URLs", type: :request do describe "GET /nostr" do - context "without username param" do + describe "without username param" do it "returns a 422 status" do get "/.well-known/nostr.json" expect(response).to have_http_status(:unprocessable_entity) end end - context "non-existent user" do + describe "non-existent user" do it "returns a 404 status" do get "/.well-known/nostr.json?name=bob" expect(response).to have_http_status(:not_found) end end - context "user does not have a nostr pubkey configured" do + describe "user does not have a nostr pubkey configured" do let(:user) { create :user, cn: 'spongebob', ou: 'kosmos.org' } before do @@ -30,7 +30,7 @@ RSpec.describe "Well-known URLs", type: :request do end end - context "user with nostr pubkey" do + describe "user with nostr pubkey" do let(:user) { create :user, cn: 'bobdylan', ou: 'kosmos.org' } before do user.save! @@ -67,5 +67,24 @@ RSpec.describe "Well-known URLs", type: :request do end end end + + describe "placeholder username for domain's own pubkey" do + it "returns the configured nostr pubkey" do + get "/.well-known/nostr.json?name=_" + res = JSON.parse(response.body) + expect(res["names"]["_"]).to eq(Setting.nostr_public_key) + end + + context "nostr service integration not enabled" do + before do + Setting.nostr_enabled = false + end + + it "returns a 404 status" do + get "/.well-known/nostr.json?name=_" + expect(response).to have_http_status(:not_found) + end + end + end end end From f401a03590df94d7faddc2464333f8f229e1ad90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Thu, 20 Jun 2024 14:50:02 +0200 Subject: [PATCH 6/8] Fix exception for NIP-05 JSON of "_" with relay configured --- app/controllers/well_known_controller.rb | 6 ++---- spec/requests/well_known_spec.rb | 13 +++++++++++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/app/controllers/well_known_controller.rb b/app/controllers/well_known_controller.rb index bc342db..b028562 100644 --- a/app/controllers/well_known_controller.rb +++ b/app/controllers/well_known_controller.rb @@ -9,15 +9,13 @@ class WellKnownController < ApplicationController if params[:name] == "_" # pubkey for the primary domain without a username (e.g. kosmos.org) res = { names: { "_": Setting.nostr_public_key } } + res[:relays] = { "_" => [ relay_url ] } if relay_url else @user = User.where(cn: params[:name], ou: domain).first http_status :not_found and return if @user.nil? || @user.nostr_pubkey.blank? res = { names: { @user.cn => @user.nostr_pubkey } } - end - - if relay_url - res[:relays] = { @user.nostr_pubkey => [ relay_url ] } + res[:relays] = { @user.nostr_pubkey => [ relay_url ] } if relay_url end respond_to do |format| diff --git a/spec/requests/well_known_spec.rb b/spec/requests/well_known_spec.rb index 0300edf..989ed00 100644 --- a/spec/requests/well_known_spec.rb +++ b/spec/requests/well_known_spec.rb @@ -75,6 +75,19 @@ RSpec.describe "Well-known URLs", type: :request do expect(res["names"]["_"]).to eq(Setting.nostr_public_key) end + context "with relay configured" do + before do + Setting.nostr_relay_url = "wss://nostr.kosmos.org" + end + + it "returns the pubkey and relay" do + get "/.well-known/nostr.json?name=_" + res = JSON.parse(response.body) + expect(res["relays"]["_"].length).to eq(1) + expect(res["relays"]["_"].first).to eq("wss://nostr.kosmos.org") + end + end + context "nostr service integration not enabled" do before do Setting.nostr_enabled = false From 01ecea74ff6d4dbbd220f34a24aed7be294e7970 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Thu, 20 Jun 2024 15:26:31 +0200 Subject: [PATCH 7/8] Add pubkey whitelist to strfry policy And allow the local akkounts instance to publish on the local relay --- docker-compose.yml | 4 ++++ extras/strfry/ldap-policy.ts | 9 ++++++++- extras/strfry/strfry-policy.ts | 1 + 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 32410ff..9237089 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -47,6 +47,9 @@ services: RS_REDIS_URL: redis://redis:6379/1 RS_STORAGE_URL: "http://localhost:4567" S3_ENABLED: false + NOSTR_PUBLIC_KEY: bdd76ce2934b2f591f9fad2ebe9da18f20d2921de527494ba00eeaa0a0efadcf + NOSTR_PRIVATE_KEY: 7c3ef7e448505f0615137af38569d01807d3b05b5005d5ecf8aaafcd40323cea + NOSTR_RELAY_URL: "ws://strfry:7777" depends_on: - ldap - redis @@ -123,6 +126,7 @@ services: LDAP_BIND_DN: 'cn=Directory Manager' LDAP_PASSWORD: passthebutter LDAP_SEARCH_DN: 'ou=kosmos.org,cn=users,dc=kosmos,dc=org' + WHITELIST_PUBKEYS: 'bdd76ce2934b2f591f9fad2ebe9da18f20d2921de527494ba00eeaa0a0efadcf' # phpldapadmin: # image: osixia/phpldapadmin:0.9.0 diff --git a/extras/strfry/ldap-policy.ts b/extras/strfry/ldap-policy.ts index 423c74e..03224cf 100644 --- a/extras/strfry/ldap-policy.ts +++ b/extras/strfry/ldap-policy.ts @@ -1,4 +1,4 @@ -import type { Policy } from 'https://gitlab.com/soapbox-pub/strfry-policies/-/raw/develop/mod.ts'; +import type { IterablePubkeys, Policy } from 'https://gitlab.com/soapbox-pub/strfry-policies/-/raw/develop/mod.ts'; import { Client } from 'npm:ldapts'; import { nip57 } from '@nostr/tools'; @@ -7,6 +7,7 @@ interface LdapConfig { bindDN: string; password: string; searchDN: string; + whitelistPubkeys?: IterablePubkeys; } const ldapPolicy: Policy = async (msg, opts) => { @@ -15,6 +16,12 @@ const ldapPolicy: Policy = async (msg, opts) => { let { pubkey } = msg.event; let out = { id: msg.event.id } + if (opts.whitelistPubkeys.includes(pubkey)) { + out['action'] = 'accept'; + out['msg'] = ''; + return out; + } + // Zap receipt if (kind === 9735) { const descriptionTag = tags.find(([t, v]) => t === 'description' && v); diff --git a/extras/strfry/strfry-policy.ts b/extras/strfry/strfry-policy.ts index 15fafe9..8086756 100755 --- a/extras/strfry/strfry-policy.ts +++ b/extras/strfry/strfry-policy.ts @@ -19,6 +19,7 @@ const ldapConfig = { bindDN: Deno.env.get("LDAP_BIND_DN"), password: Deno.env.get("LDAP_PASSWORD"), searchDN: Deno.env.get("LDAP_SEARCH_DN"), + whitelistPubkeys: Deno.env.get("WHITELIST_PUBKEYS")?.split(',') } for await (const msg of readStdin()) { From 37c15c7a62f4824659e1c717fa284dcfd2ce2e0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Thu, 20 Jun 2024 15:51:40 +0200 Subject: [PATCH 8/8] Check in deno lockfile --- extras/strfry/deno.lock | 196 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100644 extras/strfry/deno.lock diff --git a/extras/strfry/deno.lock b/extras/strfry/deno.lock new file mode 100644 index 0000000..c5c416f --- /dev/null +++ b/extras/strfry/deno.lock @@ -0,0 +1,196 @@ +{ + "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" + }, + "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" + ] + } + }, + "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": {} + } + } + }, + "remote": { + "https://deno.land/std@0.181.0/_util/asserts.ts": "178dfc49a464aee693a7e285567b3d0b555dc805ff490505a8aae34f9cfb1462", + "https://deno.land/std@0.181.0/bytes/bytes_list.ts": "b4cbdfd2c263a13e8a904b12d082f6177ea97d9297274a4be134e989450dfa6a", + "https://deno.land/std@0.181.0/bytes/concat.ts": "d26d6f3d7922e6d663dacfcd357563b7bf4a380ce5b9c2bbe0c8586662f25ce2", + "https://deno.land/std@0.181.0/bytes/copy.ts": "939d89e302a9761dcf1d9c937c7711174ed74c59eef40a1e4569a05c9de88219", + "https://deno.land/std@0.181.0/fmt/colors.ts": "d67e3cd9f472535241a8e410d33423980bec45047e343577554d3356e1f0ef4e", + "https://deno.land/std@0.181.0/io/buf_reader.ts": "abeb92b18426f11d72b112518293a96aef2e6e55f80b84235e8971ac910affb5", + "https://deno.land/std@0.181.0/io/buf_writer.ts": "48c33c8f00b61dcbc7958706741cec8e59810bd307bc6a326cbd474fe8346dfd", + "https://deno.land/std@0.181.0/io/buffer.ts": "17f4410eaaa60a8a85733e8891349a619eadfbbe42e2f319283ce2b8f29723ab", + "https://deno.land/std@0.181.0/io/copy_n.ts": "0cc7ce07c75130f6fc18621ec1911c36e147eb9570664fee0ea12b1988167590", + "https://deno.land/std@0.181.0/io/limited_reader.ts": "6c9a216f8eef39c1ee2a6b37a29372c8fc63455b2eeb91f06d9646f8f759fc8b", + "https://deno.land/std@0.181.0/io/mod.ts": "2665bcccc1fd6e8627cca167c3e92aaecbd9897556b6f69e6d258070ef63fd9b", + "https://deno.land/std@0.181.0/io/multi_reader.ts": "9c2a0a31686c44b277e16da1d97b4686a986edcee48409b84be25eedbc39b271", + "https://deno.land/std@0.181.0/io/read_delim.ts": "c02b93cc546ae8caad8682ae270863e7ace6daec24c1eddd6faabc95a9d876a3", + "https://deno.land/std@0.181.0/io/read_int.ts": "7cb8bcdfaf1107586c3bacc583d11c64c060196cb070bb13ae8c2061404f911f", + "https://deno.land/std@0.181.0/io/read_lines.ts": "c526c12a20a9386dc910d500f9cdea43cba974e853397790bd146817a7eef8cc", + "https://deno.land/std@0.181.0/io/read_long.ts": "f0aaa420e3da1261c5d33c5e729f09922f3d9fa49f046258d4ff7a00d800c71e", + "https://deno.land/std@0.181.0/io/read_range.ts": "28152daf32e43dd9f7d41d8466852b0d18ad766cd5c4334c91fef6e1b3a74eb5", + "https://deno.land/std@0.181.0/io/read_short.ts": "805cb329574b850b84bf14a92c052c59b5977a492cd780c41df8ad40826c1a20", + "https://deno.land/std@0.181.0/io/read_string_delim.ts": "5dc9f53bdf78e7d4ee1e56b9b60352238ab236a71c3e3b2a713c3d78472a53ce", + "https://deno.land/std@0.181.0/io/slice_long_to_bytes.ts": "48d9bace92684e880e46aa4a2520fc3867f9d7ce212055f76ecc11b22f9644b7", + "https://deno.land/std@0.181.0/io/string_reader.ts": "da0f68251b3d5b5112485dfd4d1b1936135c9b4d921182a7edaf47f74c25cc8f", + "https://deno.land/std@0.181.0/io/string_writer.ts": "8a03c5858c24965a54c6538bed15f32a7c72f5704a12bda56f83a40e28e5433e", + "https://deno.land/std@0.181.0/testing/_diff.ts": "1a3c044aedf77647d6cac86b798c6417603361b66b54c53331b312caeb447aea", + "https://deno.land/std@0.181.0/testing/_format.ts": "a69126e8a469009adf4cf2a50af889aca364c349797e63174884a52ff75cf4c7", + "https://deno.land/std@0.181.0/testing/asserts.ts": "e16d98b4d73ffc4ed498d717307a12500ae4f2cbe668f1a215632d19fcffc22f", + "https://deno.land/std@0.224.0/dotenv/mod.ts": "0180eaeedaaf88647318811cdaa418cc64dc51fb08354f91f5f480d0a1309f7d", + "https://deno.land/std@0.224.0/dotenv/parse.ts": "09977ff88dfd1f24f9973a338f0f91bbdb9307eb5ff6085446e7c423e4c7ba0c", + "https://deno.land/std@0.224.0/dotenv/stringify.ts": "275da322c409170160440836342eaa7cf012a1d11a7e700d8ca4e7f2f8aa4615", + "https://deno.land/std@0.88.0/async/deferred.ts": "f89ed49ba5e1dd0227c6bd5b23f017be46c3f92e4f0338dda08ff5aa54b9f6c9", + "https://deno.land/std@0.88.0/async/delay.ts": "9de1d8d07d1927767ab7f82434b883f3d8294fb19cad819691a2ad81a728cf3d", + "https://deno.land/std@0.88.0/async/mod.ts": "253b41c658d768613eacfb11caa0a9ca7148442f932018a45576f7f27554c853", + "https://deno.land/std@0.88.0/async/mux_async_iterator.ts": "b9091909db04cdb0af6f7807677372f64c1488de6c4bd86004511b064bf230d6", + "https://deno.land/std@0.88.0/async/pool.ts": "876f9e6815366cd017a3b4fbb9e9ae40310b1b6972f1bd541c94358bc11fb7e5", + "https://deno.land/std@0.88.0/encoding/base64.ts": "eecae390f1f1d1cae6f6c6d732ede5276bf4b9cd29b1d281678c054dc5cc009e", + "https://deno.land/std@0.88.0/encoding/hex.ts": "f952e0727bddb3b2fd2e6889d104eacbd62e92091f540ebd6459317a61932d9b", + "https://deno.land/std@0.88.0/fmt/colors.ts": "db22b314a2ae9430ae7460ce005e0a7130e23ae1c999157e3bb77cf55800f7e4", + "https://deno.land/std@0.88.0/node/_utils.ts": "067c386d676432e9418808851e8de72df7774f009a652904f62358b4c94504cf", + "https://deno.land/std@0.88.0/node/buffer.ts": "e98af24a3210d8fc3f022b6eb26d6e5bdf98fb0e02931e5983d20db9fed1b590", + "https://deno.land/std@0.88.0/testing/_diff.ts": "961eaf6d9f5b0a8556c9d835bbc6fa74f5addd7d3b02728ba7936ff93364f7a3", + "https://deno.land/std@0.88.0/testing/asserts.ts": "7fae8128125106ddf8e4b3ac84cc3b5fb2378e3fbf8ba38947ebe24faa002ce2", + "https://deno.land/x/module_cache@0.0.3/mod.ts": "c5e724477146e68b7a4d7ba440cd18f2ef4b28e4244ce48358c79efe98e3cd24", + "https://deno.land/x/sqlite@v3.7.1/build/sqlite.js": "c59f109f100c2bae0b9342f04e0d400583e2e3211d08bb71095177a4109ee5bf", + "https://deno.land/x/sqlite@v3.7.1/build/vfs.js": "08533cc78fb29b9d9bd62f6bb93e5ef333407013fed185776808f11223ba0e70", + "https://deno.land/x/sqlite@v3.7.1/mod.ts": "e09fc79d8065fe222578114b109b1fd60077bff1bb75448532077f784f4d6a83", + "https://deno.land/x/sqlite@v3.7.1/src/constants.ts": "90f3be047ec0a89bcb5d6fc30db121685fc82cb00b1c476124ff47a4b0472aa9", + "https://deno.land/x/sqlite@v3.7.1/src/db.ts": "59c6c2b5c4127132558bb8c610eadd811822f1a5d7f9c509704179ca192f94e0", + "https://deno.land/x/sqlite@v3.7.1/src/error.ts": "f7a15cb00d7c3797da1aefee3cf86d23e0ae92e73f0ba3165496c3816ab9503a", + "https://deno.land/x/sqlite@v3.7.1/src/function.ts": "e4c83b8ec64bf88bafad2407376b0c6a3b54e777593c70336fb40d43a79865f2", + "https://deno.land/x/sqlite@v3.7.1/src/query.ts": "d58abda928f6582d77bad685ecf551b1be8a15e8e38403e293ec38522e030cad", + "https://deno.land/x/sqlite@v3.7.1/src/wasm.ts": "e79d0baa6e42423257fb3c7cc98091c54399254867e0f34a09b5bdef37bd9487", + "https://esm.sh/nostr-tools@1.8.4?pin=v115": "62e5b620dbbaea0ee399efcc700260da12836a353fa521d35969d3454e591a77", + "https://esm.sh/v115/@noble/hashes@1.2.0/denonext/_assert.js": "2d47b1ae1c443fbcda3aa75e6d66c26da566d1775dcd757165314e8e9d1162da", + "https://esm.sh/v115/@noble/hashes@1.2.0/denonext/crypto.js": "0880be2fb91177484b9a5916a286aadce6a1c8b1b5cf6be47393361e6b121a17", + "https://esm.sh/v115/@noble/hashes@1.2.0/denonext/hmac.js": "cdb442a8326674449570b98daa44b07317908eae81205c178cab542ea754b91d", + "https://esm.sh/v115/@noble/hashes@1.2.0/denonext/pbkdf2.js": "e8b8e2ff70ecb35442fabfece10e76850ac8dc6aaf44a769871c9e6dbe60d264", + "https://esm.sh/v115/@noble/hashes@1.2.0/denonext/ripemd160.js": "8cd5e59afc12f6f6a2c980495f699a76d812ca30772d4c085ff8477fe4b1a2fe", + "https://esm.sh/v115/@noble/hashes@1.2.0/denonext/sha256.js": "8dec7d1bb4d0799f9cdf8f9ea7d8c3e91790255d547defcf62a626a0a190185e", + "https://esm.sh/v115/@noble/hashes@1.2.0/denonext/sha512.js": "85ccf57544faca95a6aeab11951f98f49e56b3cbad0618f624838c7e8fb4361d", + "https://esm.sh/v115/@noble/hashes@1.2.0/denonext/utils.js": "11431fc23031cb324977bc992e699fda8ec7c63fcc17c2b4f71a3902d48e99e5", + "https://esm.sh/v115/@noble/secp256k1@1.7.0/denonext/secp256k1.mjs": "36fb68b95b2f62de23d275be52b2eec68813083b93b78f7032492188ef59c77b", + "https://esm.sh/v115/@noble/secp256k1@1.7.1/denonext/secp256k1.mjs": "43c5a7ba14ae81b36e5ce64abf45962119527e926cddb764b7e510869b05f0bd", + "https://esm.sh/v115/@scure/base@1.1.1/denonext/base.mjs": "8f9cb853c4f6a4367c2f5bfb921d54b4ed61e41829944435e5878781b54d94a9", + "https://esm.sh/v115/@scure/bip32@1.1.4/denonext/bip32.mjs": "05471356192b1286874be6c28bea4ebac6dd6bc680bce795640604bb317c2165", + "https://esm.sh/v115/@scure/bip39@1.1.1/denonext/bip39.mjs": "00ccac2e221996db35b6780b3ae2cf37a153111bd1d348c9defe3a4341ec683d", + "https://esm.sh/v115/@scure/bip39@1.1.1/denonext/wordlists/english.js": "72ca7f3b2e856a62caa00441579008da89ea21a9c8a428ae547cdcffd17ae40c", + "https://esm.sh/v115/nostr-tools@1.8.4/denonext/nostr-tools.mjs": "f8023312404e4a83f0c052653643bcdbf5169a1585bd5399f11c65f37f7bcf16", + "https://gitlab.com/soapbox-pub/strfry-policies/-/raw/develop/mod.ts": "26add79f9bf2b12d088bacd3417dbb590684171f80be2dbf2e6b83b324df54f7", + "https://gitlab.com/soapbox-pub/strfry-policies/-/raw/develop/src/deps.ts": "3c06f4dafe1b04c2413977e9dfdc4956136505f401e0ced14a1c7aff484ad699", + "https://gitlab.com/soapbox-pub/strfry-policies/-/raw/develop/src/io.ts": "1f87789a4ea53ed73438c475bb4b6a82eba2bb389d4c8c9179450a4b490f1953", + "https://gitlab.com/soapbox-pub/strfry-policies/-/raw/develop/src/pipeline.ts": "4b881ebc1893b4f9f8dcbab260097a0402e0a398b937ef6723915db7c2a86a90", + "https://gitlab.com/soapbox-pub/strfry-policies/-/raw/develop/src/policies/anti-duplication-policy.ts": "82a3868b671e68e1379104c0ee1fb8085a5c2d9b802b6eedf31eaae87e778a53", + "https://gitlab.com/soapbox-pub/strfry-policies/-/raw/develop/src/policies/filter-policy.ts": "320e736a01bf82d95ab5bc0b8de97c635d71f7779925ff209e3064b01e145e72", + "https://gitlab.com/soapbox-pub/strfry-policies/-/raw/develop/src/policies/hellthread-policy.ts": "965469606bdbb04b4bb0c61f90b7f6f0d073e394fa271e17784d2afde085476e", + "https://gitlab.com/soapbox-pub/strfry-policies/-/raw/develop/src/policies/keyword-policy.ts": "c88db7137d336631b4fcc3532c5059c4a1e27caa50d6332a5fb593bf295d28df", + "https://gitlab.com/soapbox-pub/strfry-policies/-/raw/develop/src/policies/noop-policy.ts": "e4164ab252c328d3ec72310d458cdcfc85bfbfdb7504f41e1d9ab4fd6fdcf4ef", + "https://gitlab.com/soapbox-pub/strfry-policies/-/raw/develop/src/policies/openai-policy.ts": "cde09abe6dbdebdbb77ea13731a27ce8bcacbbd1fb21760d7784878dca587d81", + "https://gitlab.com/soapbox-pub/strfry-policies/-/raw/develop/src/policies/pow-policy.ts": "d667623a4570e888d0cfdb41bf99bbbac0eb44eab5d97f5be1eeb190e06d34cb", + "https://gitlab.com/soapbox-pub/strfry-policies/-/raw/develop/src/policies/pubkey-ban-policy.ts": "af2e3d6f5266bcb1785325a004a0a92088d18fa2433760f807158314184a82c9", + "https://gitlab.com/soapbox-pub/strfry-policies/-/raw/develop/src/policies/rate-limit-policy.ts": "02e8539f30e67f7f7541628120358d70c4b05f362b4f21bbcceda475a6d3e357", + "https://gitlab.com/soapbox-pub/strfry-policies/-/raw/develop/src/policies/read-only-policy.ts": "ec849ed7b06133bc11e3ce40412dd58469838376764a4326ffc043ea985c9739", + "https://gitlab.com/soapbox-pub/strfry-policies/-/raw/develop/src/policies/regex-policy.ts": "626f7d4eb61eace9aa685a4f51b0b142b30abc96554ac5e375bbf3dc2a5ab685", + "https://gitlab.com/soapbox-pub/strfry-policies/-/raw/develop/src/policies/whitelist-policy.ts": "f5cb4f616dc41c88505eb45adb2b2102a284ae7351ce9f76a76d53dd7b8bf575", + "https://gitlab.com/soapbox-pub/strfry-policies/-/raw/develop/src/types.ts": "792aa1196dd290d815081ef874f8e66dacde344c9e30a8bf9031a1ebeb1da21d", + "https://raw.githubusercontent.com/alexgleason/Keydb/1bda308df9e589339532daf31f1717ef7a59d2af/adapter.ts": "32e5182648011b188952ada0528f564b374260449ec3b06237f36225d4d19510", + "https://raw.githubusercontent.com/alexgleason/Keydb/1bda308df9e589339532daf31f1717ef7a59d2af/jsonb.ts": "1b540f8bd0b43fe847cd3e2a852d2f53e610cd77b81c11d175ebe91a3f110be8", + "https://raw.githubusercontent.com/alexgleason/Keydb/1bda308df9e589339532daf31f1717ef7a59d2af/keydb.ts": "616c4c866c9e11c29d5654d367468ed51b689565043f53fdeb5eb66f25138156", + "https://raw.githubusercontent.com/alexgleason/Keydb/1bda308df9e589339532daf31f1717ef7a59d2af/memory.ts": "f0ab6faf293c4ad3539fd3cf89c764d7f34d39d24e471ea59eebb5d1f5a510dc", + "https://raw.githubusercontent.com/alexgleason/Keydb/1bda308df9e589339532daf31f1717ef7a59d2af/sqlite.ts": "c8f172cfea9425cb16e844622375c9578db508de7d710ad3987cf6cd6bff197a" + }, + "workspace": { + "dependencies": [ + "jsr:@nostr/tools@^2.3.1" + ] + } +}