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/5] 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/5] 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/5] 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/5] 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/5] 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