Compare commits
22 Commits
feature/im
...
bafddd436b
| Author | SHA1 | Date | |
|---|---|---|---|
|
bafddd436b
|
|||
|
560f193c4b
|
|||
|
8aabbad5bb
|
|||
|
48ab96dda9
|
|||
|
7ac3130c18
|
|||
|
cbfa148051
|
|||
|
87d900b627
|
|||
|
926dc06294
|
|||
|
00b73b06d7
|
|||
|
0daac33915
|
|||
|
0e472bc311
|
|||
|
ba8d21eb7a
|
|||
|
53df455d53
|
|||
|
9f1af3a9aa
|
|||
|
1d09008ce2
|
|||
|
57c5317c38
|
|||
|
41bd920060
|
|||
|
0815fa6040
|
|||
|
af0e99aa50
|
|||
|
f05eec5255
|
|||
|
66ca2dc6b0
|
|||
|
800183e9da
|
@@ -29,6 +29,7 @@
|
|||||||
|
|
||||||
#
|
#
|
||||||
# Service Integrations
|
# Service Integrations
|
||||||
|
# (sorted alphabetically by service name)
|
||||||
#
|
#
|
||||||
|
|
||||||
# BTCPAY_PUBLIC_URL='https://btcpay.example.com'
|
# BTCPAY_PUBLIC_URL='https://btcpay.example.com'
|
||||||
@@ -62,5 +63,9 @@
|
|||||||
|
|
||||||
# MEDIAWIKI_PUBLIC_URL='https://wiki.kosmos.org'
|
# 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_STORAGE_URL='https://storage.kosmos.org'
|
||||||
# RS_REDIS_URL='redis://localhost:6379/2'
|
# RS_REDIS_URL='redis://localhost:6379/2'
|
||||||
|
|||||||
@@ -1,16 +1,35 @@
|
|||||||
class WellKnownController < ApplicationController
|
class WellKnownController < ApplicationController
|
||||||
|
before_action :require_nostr_enabled, only: [ :nostr ]
|
||||||
|
|
||||||
def nostr
|
def nostr
|
||||||
http_status :unprocessable_entity and return if params[:name].blank?
|
http_status :unprocessable_entity and return if params[:name].blank?
|
||||||
domain = request.headers["X-Forwarded-Host"].presence || Setting.primary_domain
|
domain = request.headers["X-Forwarded-Host"].presence || Setting.primary_domain
|
||||||
@user = User.where(cn: params[:name], ou: domain).first
|
relay_url = Setting.nostr_relay_url
|
||||||
http_status :not_found and return if @user.nil? || @user.nostr_pubkey.blank?
|
|
||||||
|
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|
|
respond_to do |format|
|
||||||
format.json do
|
format.json do
|
||||||
render json: {
|
render json: res.to_json
|
||||||
names: { "#{@user.cn}": @user.nostr_pubkey }
|
|
||||||
}.to_json
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def require_nostr_enabled
|
||||||
|
http_status :not_found unless Setting.nostr_enabled?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -169,6 +169,9 @@ class Setting < RailsSettings::Base
|
|||||||
field :nostr_public_key, type: :string,
|
field :nostr_public_key, type: :string,
|
||||||
default: ENV["NOSTR_PUBLIC_KEY"].presence
|
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,
|
field :nostr_zaps_relay_limit, type: :integer,
|
||||||
default: 12
|
default: 12
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,13 @@ module NostrManager
|
|||||||
|
|
||||||
def call
|
def call
|
||||||
tags = parse_tags(@zap.request_event.tags)
|
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
|
if @delayed
|
||||||
NostrPublishEventJob.perform_later(event: @zap.receipt, relay_url: relay_url)
|
NostrPublishEventJob.perform_later(event: @zap.receipt, relay_url: relay_url)
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -19,6 +19,11 @@
|
|||||||
title: "Public key",
|
title: "Public key",
|
||||||
description: "The corresponding public key of the accounts service"
|
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}"
|
||||||
|
) %>
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
:concurrency: 2
|
:concurrency: 2
|
||||||
|
production:
|
||||||
|
:concurrency: 10
|
||||||
:queues:
|
:queues:
|
||||||
- default
|
- default
|
||||||
- mailers
|
- mailers
|
||||||
|
|||||||
@@ -111,9 +111,7 @@ services:
|
|||||||
image: gitea.kosmos.org/kosmos/strfry-deno:1.1.1
|
image: gitea.kosmos.org/kosmos/strfry-deno:1.1.1
|
||||||
volumes:
|
volumes:
|
||||||
- ./docker/strfry/strfry.conf:/etc/strfry.conf
|
- ./docker/strfry/strfry.conf:/etc/strfry.conf
|
||||||
- ./extras/strfry/ldap-policy.ts:/opt/ldap-policy.ts
|
- ./extras/strfry:/opt/strfry
|
||||||
- ./extras/strfry/strfry-policy.ts:/opt/strfry-policy.ts
|
|
||||||
- ./extras/strfry/strfry-sync.ts:/opt/strfry-sync.ts
|
|
||||||
- strfry-data:/var/lib/strfry
|
- strfry-data:/var/lib/strfry
|
||||||
networks:
|
networks:
|
||||||
- external_network
|
- external_network
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ relay {
|
|||||||
|
|
||||||
writePolicy {
|
writePolicy {
|
||||||
# If non-empty, path to an executable script that implements the writePolicy plugin logic
|
# If non-empty, path to an executable script that implements the writePolicy plugin logic
|
||||||
plugin = "/opt/strfry-policy.ts"
|
plugin = "/opt/strfry/strfry-policy.ts"
|
||||||
}
|
}
|
||||||
|
|
||||||
compression {
|
compression {
|
||||||
|
|||||||
5
extras/strfry/deno.json
Normal file
5
extras/strfry/deno.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"imports": {
|
||||||
|
"@nostr/tools": "jsr:@nostr/tools@^2.3.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { Policy } from 'https://gitlab.com/soapbox-pub/strfry-policies/-/raw/develop/mod.ts';
|
import type { Policy } from 'https://gitlab.com/soapbox-pub/strfry-policies/-/raw/develop/mod.ts';
|
||||||
import { Client } from 'npm:ldapts';
|
import { Client } from 'npm:ldapts';
|
||||||
|
import { nip57 } from '@nostr/tools';
|
||||||
|
|
||||||
interface LdapConfig {
|
interface LdapConfig {
|
||||||
url: string;
|
url: string;
|
||||||
@@ -10,9 +11,33 @@ interface LdapConfig {
|
|||||||
|
|
||||||
const ldapPolicy: Policy<LdapConfig> = async (msg, opts) => {
|
const ldapPolicy: Policy<LdapConfig> = async (msg, opts) => {
|
||||||
const client = new Client({ url: opts.url });
|
const client = new Client({ url: opts.url });
|
||||||
const { pubkey, kind, tags } = msg.event;
|
const { kind, tags } = msg.event;
|
||||||
|
let { pubkey } = msg.event;
|
||||||
let out = { id: msg.event.id }
|
let out = { id: msg.event.id }
|
||||||
|
|
||||||
|
// 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') {
|
||||||
|
out['action'] = 'reject';
|
||||||
|
out['msg'] = invalidZapRequestMsg;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
const zapRequestJSON = descriptionTag[1];
|
||||||
|
const validationResult = nip57.validateZapRequest(zapRequestJSON);
|
||||||
|
|
||||||
|
if (validationResult === null) {
|
||||||
|
pubkey = JSON.parse(zapRequestJSON).pubkey;
|
||||||
|
} else {
|
||||||
|
out['action'] = 'reject';
|
||||||
|
out['msg'] = invalidZapRequestMsg;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await client.bind(opts.bindDN, opts.password);
|
await client.bind(opts.bindDN, opts.password);
|
||||||
|
|
||||||
@@ -20,14 +45,9 @@ const ldapPolicy: Policy<LdapConfig> = async (msg, opts) => {
|
|||||||
filter: `(nostrKey=${pubkey})`,
|
filter: `(nostrKey=${pubkey})`,
|
||||||
attributes: ['nostrKey']
|
attributes: ['nostrKey']
|
||||||
});
|
});
|
||||||
|
|
||||||
const memberKey = searchEntries[0]?.nostrKey;
|
const memberKey = searchEntries[0]?.nostrKey;
|
||||||
|
|
||||||
const accepted = (memberKey === pubkey);
|
if (memberKey === pubkey) {
|
||||||
// TODO if kind is 9735, check that "description" tag contains valid 9734 event,
|
|
||||||
// signed by memberKey and with "p" tag being the same as pubkey (receipt sender)
|
|
||||||
|
|
||||||
if (accepted) {
|
|
||||||
out['action'] = 'accept';
|
out['action'] = 'accept';
|
||||||
out['msg'] = '';
|
out['msg'] = '';
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -2,21 +2,21 @@ require 'rails_helper'
|
|||||||
|
|
||||||
RSpec.describe "Well-known URLs", type: :request do
|
RSpec.describe "Well-known URLs", type: :request do
|
||||||
describe "GET /nostr" do
|
describe "GET /nostr" do
|
||||||
context "without username param" do
|
describe "without username param" do
|
||||||
it "returns a 422 status" do
|
it "returns a 422 status" do
|
||||||
get "/.well-known/nostr.json"
|
get "/.well-known/nostr.json"
|
||||||
expect(response).to have_http_status(:unprocessable_entity)
|
expect(response).to have_http_status(:unprocessable_entity)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "non-existent user" do
|
describe "non-existent user" do
|
||||||
it "returns a 404 status" do
|
it "returns a 404 status" do
|
||||||
get "/.well-known/nostr.json?name=bob"
|
get "/.well-known/nostr.json?name=bob"
|
||||||
expect(response).to have_http_status(:not_found)
|
expect(response).to have_http_status(:not_found)
|
||||||
end
|
end
|
||||||
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' }
|
let(:user) { create :user, cn: 'spongebob', ou: 'kosmos.org' }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
@@ -30,7 +30,7 @@ RSpec.describe "Well-known URLs", type: :request do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "user with nostr pubkey" do
|
describe "user with nostr pubkey" do
|
||||||
let(:user) { create :user, cn: 'bobdylan', ou: 'kosmos.org' }
|
let(:user) { create :user, cn: 'bobdylan', ou: 'kosmos.org' }
|
||||||
before do
|
before do
|
||||||
user.save!
|
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"].keys.size).to eq(1)
|
||||||
expect(res["names"]["bobdylan"]).to eq(user.nostr_pubkey)
|
expect(res["names"]["bobdylan"]).to eq(user.nostr_pubkey)
|
||||||
end
|
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
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -35,6 +35,19 @@ RSpec.describe NostrManager::PublishZapReceipt, type: :model do
|
|||||||
|
|
||||||
described_class.call(zap: zap)
|
described_class.call(zap: zap)
|
||||||
end
|
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
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user