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/controllers/well_known_controller.rb b/app/controllers/well_known_controller.rb index 02eb02c..bc342db 100644 --- a/app/controllers/well_known_controller.rb +++ b/app/controllers/well_known_controller.rb @@ -1,16 +1,35 @@ 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 + + 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? + + res = { names: { @user.cn => @user.nostr_pubkey } } + end + + if relay_url + res[:relays] = { @user.nostr_pubkey => [ 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 + + private + + def require_nostr_enabled + http_status :not_found unless Setting.nostr_enabled? + end end 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/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/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}" + ) %>
diff --git a/spec/requests/well_known_spec.rb b/spec/requests/well_known_spec.rb index 9862b79..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! @@ -45,6 +45,46 @@ 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 + + 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 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