Compare commits
75 Commits
560f193c4b
...
feature/no
| Author | SHA1 | Date | |
|---|---|---|---|
|
9866cd0404
|
|||
|
10d29b6fab
|
|||
|
6f8f60a9e2
|
|||
|
c1b4665706
|
|||
|
5447150d4d
|
|||
|
bf26703b2d
|
|||
|
21c6264ea9
|
|||
|
79ef9fa6d5
|
|||
|
04a9061663
|
|||
|
5283f6fce7
|
|||
|
a08a4746f7
|
|||
|
9e3652479b
|
|||
|
011386fb8d
|
|||
|
4d77f5d38c
|
|||
|
64de4deddd
|
|||
|
8f7994d82e
|
|||
|
a7d0e71ab6
|
|||
|
27d9f73c61
|
|||
|
ed3de8b16f
|
|||
|
d7b4c67953
|
|||
| 7489d4a32f | |||
|
ac77e5b7c1
|
|||
|
e544c28105
|
|||
|
4909dac5c2
|
|||
| 3cf4348695 | |||
|
af3da0a26c
|
|||
|
2d32320c7d
|
|||
|
fc2bec6246
|
|||
|
5addd25186
|
|||
|
215d178e69
|
|||
|
5474bf66e7
|
|||
|
ef2a37e2bf
|
|||
|
0e3180602c
|
|||
|
15e2f9b962
|
|||
|
4ae10c9b53
|
|||
| 45137e0cfe | |||
|
717fe93104
|
|||
|
fdac789ccb
|
|||
|
9355dab6b6
|
|||
|
f3676949d2
|
|||
|
79952b73c5
|
|||
| 17c419403e | |||
|
6d06312a5c
|
|||
|
acb399b0b7
|
|||
|
bf20b6467e
|
|||
|
b91d90d75c
|
|||
|
3284bbf6ca
|
|||
|
171b84ee81
|
|||
|
54b01dd282
|
|||
|
e08ea64f47
|
|||
|
8cc2c9554f
|
|||
| 32dff9c67f | |||
|
126b8b20e0
|
|||
|
5abf69f356
|
|||
|
210a69bd9b
|
|||
|
bbed3cd367
|
|||
|
7943da0f17
|
|||
| 620167eedf | |||
|
e077debfc2
|
|||
|
531b2c3002
|
|||
|
6d2bc729b8
|
|||
|
2630ec2af4
|
|||
| daed5c1eea | |||
| 2e9429bb32 | |||
|
37c15c7a62
|
|||
|
01ecea74ff
|
|||
|
f401a03590
|
|||
|
48ab96dda9
|
|||
|
7ac3130c18
|
|||
|
cbfa148051
|
|||
|
87d900b627
|
|||
|
926dc06294
|
|||
|
00b73b06d7
|
|||
|
0daac33915
|
|||
|
0e472bc311
|
@@ -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'
|
||||||
|
|||||||
13
Dockerfile
@@ -1,18 +1,11 @@
|
|||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
FROM debian:bullseye-slim as base
|
FROM ruby:3.3.4
|
||||||
|
|
||||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||||
|
|
||||||
# TODO Remove when upstream Ruby works properly on Apple silicon
|
RUN apt-get update -qq && apt-get install -y --no-install-recommends curl \
|
||||||
RUN apt update && apt install -y build-essential wget autoconf libpq-dev pkg-config
|
ldap-utils tini libvips
|
||||||
RUN wget https://github.com/postmodern/ruby-install/releases/download/v0.9.3/ruby-install-0.9.3.tar.gz \
|
|
||||||
&& tar -xzvf ruby-install-0.9.3.tar.gz \
|
|
||||||
&& cd ruby-install-0.9.3/ \
|
|
||||||
&& make install
|
|
||||||
RUN ruby-install -p https://github.com/ruby/ruby/pull/9371.diff ruby 3.3.0
|
|
||||||
ENV PATH="/opt/rubies/ruby-3.3.0/bin:${PATH}"
|
|
||||||
|
|
||||||
RUN apt-get install -y --no-install-recommends curl ldap-utils tini libvips
|
|
||||||
RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -
|
RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -
|
||||||
RUN apt-get update && apt-get install -y nodejs
|
RUN apt-get update && apt-get install -y nodejs
|
||||||
|
|
||||||
|
|||||||
2
Gemfile
@@ -61,7 +61,7 @@ gem "sentry-rails"
|
|||||||
# Services
|
# Services
|
||||||
gem 'discourse_api'
|
gem 'discourse_api'
|
||||||
gem "lnurl"
|
gem "lnurl"
|
||||||
gem 'manifique'
|
gem 'manifique', '~> 1.1.0'
|
||||||
gem 'nostr', '~> 0.6.0'
|
gem 'nostr', '~> 0.6.0'
|
||||||
|
|
||||||
group :development, :test do
|
group :development, :test do
|
||||||
|
|||||||
@@ -245,7 +245,7 @@ GEM
|
|||||||
net-imap
|
net-imap
|
||||||
net-pop
|
net-pop
|
||||||
net-smtp
|
net-smtp
|
||||||
manifique (1.0.1)
|
manifique (1.1.0)
|
||||||
faraday (~> 2.9.0)
|
faraday (~> 2.9.0)
|
||||||
faraday-follow_redirects (= 0.3.0)
|
faraday-follow_redirects (= 0.3.0)
|
||||||
nokogiri (~> 1.16.0)
|
nokogiri (~> 1.16.0)
|
||||||
@@ -515,7 +515,7 @@ DEPENDENCIES
|
|||||||
listen (~> 3.2)
|
listen (~> 3.2)
|
||||||
lnurl
|
lnurl
|
||||||
lockbox
|
lockbox
|
||||||
manifique
|
manifique (~> 1.1.0)
|
||||||
net-ldap
|
net-ldap
|
||||||
nostr (~> 0.6.0)
|
nostr (~> 0.6.0)
|
||||||
pagy (~> 6.0, >= 6.0.2)
|
pagy (~> 6.0, >= 6.0.2)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
@layer components {
|
@layer components {
|
||||||
.services > div > a {
|
.services > div > a {
|
||||||
background-image: linear-gradient(110deg, rgba(255,255,255,0.99) 0, rgba(255,255,255,0.88) 100%);
|
background-image: linear-gradient(110deg, rgba(255,255,255,0.99) 20%, rgba(255,255,255,0.88) 100%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<div class="w-[72vw] md:w-[500px]">
|
||||||
|
<header class="absolute z-10 h-36 sm:h-44 inset-x-1 top-1 rounded-t
|
||||||
|
bg-cover bg-center bg-gray-50"
|
||||||
|
style="background-image: url('<%= @profile["banner"]%>');">
|
||||||
|
<div class="inline-block z-20 size-28 sm:size-32 ml-4 mt-16 sm:mt-20">
|
||||||
|
<% if @profile["picture"].present? %>
|
||||||
|
<img src="<%= @profile["picture"] %>"
|
||||||
|
class="inline-block size:28 sm:size-32 rounded-full border-2 border-white" />
|
||||||
|
<% else %>
|
||||||
|
<span class="inline-block size:28 sm:size-32 overflow-hidden rounded-full border-2 border-white bg-gray-100">
|
||||||
|
<svg class="size-full text-gray-300" fill="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path d="M24 20.993V24H0v-2.996A14.977 14.977 0 0112.004 15c4.904 0 9.26 2.354 11.996 5.993zM16.002 8.999a4 4 0 11-8 0 4 4 0 018 0z" />
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<main class="mt-44 sm:mt-52">
|
||||||
|
<%= form_for(@user, url: setting_path(:nostr), html: { :method => :put }) do |f| %>
|
||||||
|
<%= render FormElements::FieldsetComponent.new(tag: "div", title: "Display name") do %>
|
||||||
|
<%= f.text_field :display_name, value: @display_name, class: "w-full sm:w-3/5" %>
|
||||||
|
<% if @validation_errors.present? && @validation_errors[:display_name].present? %>
|
||||||
|
<p class="error-msg mt-2"><%= @validation_errors[:display_name].first %></p>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
<%= render FormElements::FieldsetComponent.new(tag: "div", title: "Nostr address (NIP-05)") do %>
|
||||||
|
<%= f.text_field :nip05_address, value: @profile["nip05"], class: "w-full sm:w-3/5" %>
|
||||||
|
<% if @validation_errors.present? && @validation_errors[:nip05_address].present? %>
|
||||||
|
<p class="error-msg mt-2"><%= @validation_errors[:nip05_address].first %></p>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
<%= render FormElements::FieldsetComponent.new(tag: "div", title: "Ligtning address for Zaps") do %>
|
||||||
|
<%= f.text_field :lud16_address, value: @profile["lud16"], class: "w-full sm:w-3/5" %>
|
||||||
|
<% if @validation_errors.present? && @validation_errors[:lud16_address].present? %>
|
||||||
|
<p class="error-msg mt-2"><%= @validation_errors[:lud16_address].first %></p>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
</main>
|
||||||
|
<footer>
|
||||||
|
<%# <%= @profile.inspect %>
|
||||||
|
<%# <%= @profile_event.inspect %>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
28
app/components/settings/nostr_edit_profile_component.rb
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Settings
|
||||||
|
class NostrEditProfileComponent < ViewComponent::Base
|
||||||
|
def initialize(user:, profile_event:)
|
||||||
|
if profile_event.present?
|
||||||
|
@user = user
|
||||||
|
@profile_event = profile_event
|
||||||
|
@profile = JSON.parse(profile_event["content"])
|
||||||
|
@display_name = @profile["display_name"] || @profile["displayName"]
|
||||||
|
|
||||||
|
if @profile["nip05"].present? && @profile["nip05"] == @user.address
|
||||||
|
# "Your profile's Nostr address is set to <strong>#{ user_address }</strong>"
|
||||||
|
else
|
||||||
|
# "Your profile's Nostr address is not set to <strong>#{ user_address }</strong> yet"
|
||||||
|
end
|
||||||
|
|
||||||
|
if @profile["lud16"].present? && @profile["lud16"] == @user.address
|
||||||
|
# "Your profile's Lightning address is set to <strong>#{ user_address }</strong>"
|
||||||
|
else
|
||||||
|
# "Your profile's Lightning address is not set to <strong>#{ user_address }</strong> yet"
|
||||||
|
end
|
||||||
|
else
|
||||||
|
# "We could not find a profile for your public key"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<% @statuses.each do |status| %>
|
||||||
|
<%= render StatusTextComponent.new(
|
||||||
|
text: status[:text],
|
||||||
|
icon_name: status[:icon_name],
|
||||||
|
icon_color: status[:icon_color]
|
||||||
|
) %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<% if @status == 1 %>
|
||||||
|
<p class="mt-8">
|
||||||
|
<button class="btn-md btn-blue">
|
||||||
|
Edit my profile
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
<% elsif @status == 2 %>
|
||||||
|
<p class="mt-8">
|
||||||
|
<button class="btn-md btn-blue">
|
||||||
|
Create my profile
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
<% end %>
|
||||||
53
app/components/settings/nostr_profile_status_component.rb
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Settings
|
||||||
|
class NostrProfileStatusComponent < ViewComponent::Base
|
||||||
|
def initialize(profile_event:, user_address:)
|
||||||
|
@statuses = []
|
||||||
|
|
||||||
|
if profile_event.present?
|
||||||
|
profile = JSON.parse(profile_event["content"])
|
||||||
|
|
||||||
|
@statuses.push({
|
||||||
|
text: "You have a public Nostr profile",
|
||||||
|
icon_name: "check-circle",
|
||||||
|
icon_color: "emerald-500"
|
||||||
|
})
|
||||||
|
|
||||||
|
if profile["nip05"].present? && profile["nip05"] == user_address
|
||||||
|
@statuses.push({
|
||||||
|
text: "Your profile's Nostr address is set to <strong>#{ user_address }</strong>",
|
||||||
|
icon_name: "check-circle",
|
||||||
|
icon_color: "emerald-500"
|
||||||
|
})
|
||||||
|
else
|
||||||
|
@statuses.push({
|
||||||
|
text: "Your profile's Nostr address is not set to <strong>#{ user_address }</strong> yet",
|
||||||
|
icon_name: "alert-octagon",
|
||||||
|
icon_color: "amber-500"
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
if profile["lud16"].present? && profile["lud16"] == user_address
|
||||||
|
@statuses.push({
|
||||||
|
text: "Your profile's Lightning address is set to <strong>#{ user_address }</strong>",
|
||||||
|
icon_name: "check-circle",
|
||||||
|
icon_color: "emerald-500"
|
||||||
|
})
|
||||||
|
else
|
||||||
|
@statuses.push({
|
||||||
|
text: "Your profile's Lightning address is not set to <strong>#{ user_address }</strong> yet",
|
||||||
|
icon_name: "alert-octagon",
|
||||||
|
icon_color: "amber-500"
|
||||||
|
})
|
||||||
|
end
|
||||||
|
else
|
||||||
|
@statuses.push({
|
||||||
|
text: "We could not find a profile for your public key",
|
||||||
|
icon_name: "alert-octagon",
|
||||||
|
icon_color: "amber-500"
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<%= render StatusTextComponent.new(
|
||||||
|
text: @text,
|
||||||
|
icon_name: @icon_name,
|
||||||
|
icon_color: @icon_color) %>
|
||||||
|
|
||||||
|
<% if @status == 1 %>
|
||||||
|
<p class="mt-8">
|
||||||
|
<button class="btn-md btn-blue">
|
||||||
|
Add the relay to my list
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
<% elsif @status == 2 %>
|
||||||
|
<p class="mt-8">
|
||||||
|
<button class="btn-md btn-blue">
|
||||||
|
Set up default relays
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
<% end %>
|
||||||
34
app/components/settings/nostr_relay_status_component.rb
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Settings
|
||||||
|
class NostrRelayStatusComponent < ViewComponent::Base
|
||||||
|
def initialize(nip65_event:)
|
||||||
|
if nip65_event.present?
|
||||||
|
if relay_urls(nip65_event).any? { |r| r.include?("wss://nostr.kosmos.org") }
|
||||||
|
@text = "You have a relay list, and the Kosmos relay is part of it"
|
||||||
|
@icon_name = "check-circle"
|
||||||
|
@icon_color = "emerald-500"
|
||||||
|
@status = 0
|
||||||
|
else
|
||||||
|
@text = "The Kosmos relay is missing from your relay list"
|
||||||
|
@icon_name = "alert-octagon"
|
||||||
|
@icon_color = "amber-500"
|
||||||
|
@status = 1
|
||||||
|
end
|
||||||
|
else
|
||||||
|
@text = "We could not find a relay list for your public key"
|
||||||
|
@icon_name = "alert-octagon"
|
||||||
|
@icon_color = "amber-500"
|
||||||
|
@status = 2
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def relay_urls(nip65_event)
|
||||||
|
nip65_event["tags"].select{ |t| t[0] == "r" }.map{ |t| t[1] }
|
||||||
|
# @inbox_relay_urls = relay_tags&.select{ |t| t[2] == "read" }&.map{ |t| t[1] }
|
||||||
|
# @outbox_relay_urls = relay_tags&.select{ |t| t[2] != "read" }&.map{ |t| t[1] }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
8
app/components/status_text_component.html.erb
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<p class="flex gap-x-4 items-center">
|
||||||
|
<span class="inline-block h-6 w-6 grow-0 text-<%= @icon_color %>">
|
||||||
|
<%= render "icons/#{@icon_name}" %>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<%= raw @text %>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
7
app/components/status_text_component.rb
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
class StatusTextComponent < ViewComponent::Base
|
||||||
|
def initialize(text:, icon_name:, icon_color:)
|
||||||
|
@text = text
|
||||||
|
@icon_name = icon_name
|
||||||
|
@icon_color = icon_color
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -9,4 +9,12 @@ class Admin::Settings::RegistrationsController < Admin::SettingsController
|
|||||||
success: "Settings saved"
|
success: "Settings saved"
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def setting_params
|
||||||
|
params.require(:setting).permit([
|
||||||
|
:reserved_usernames, default_services: []
|
||||||
|
])
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -9,11 +9,12 @@ class Admin::SettingsController < Admin::BaseController
|
|||||||
changed_keys = []
|
changed_keys = []
|
||||||
|
|
||||||
setting_params.keys.each do |key|
|
setting_params.keys.each do |key|
|
||||||
next if setting_params[key].nil? ||
|
next if clean_param(key).nil? ||
|
||||||
(Setting.send(key).to_s == setting_params[key].strip)
|
(Setting.send(key).to_s == clean_param(key))
|
||||||
|
|
||||||
changed_keys.push(key)
|
changed_keys.push(key)
|
||||||
setting = Setting.new(var: key)
|
setting = Setting.new(var: key)
|
||||||
setting.value = setting_params[key].strip
|
setting.value = clean_param(key)
|
||||||
unless setting.valid?
|
unless setting.valid?
|
||||||
@errors.merge!(setting.errors)
|
@errors.merge!(setting.errors)
|
||||||
end
|
end
|
||||||
@@ -24,7 +25,7 @@ class Admin::SettingsController < Admin::BaseController
|
|||||||
end
|
end
|
||||||
|
|
||||||
changed_keys.each do |key|
|
changed_keys.each do |key|
|
||||||
Setting.send("#{key}=", setting_params[key].strip)
|
Setting.send("#{key}=", clean_param(key))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -37,4 +38,12 @@ class Admin::SettingsController < Admin::BaseController
|
|||||||
def setting_params
|
def setting_params
|
||||||
params.require(:setting).permit(Setting.editable_keys.map(&:to_sym))
|
params.require(:setting).permit(Setting.editable_keys.map(&:to_sym))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def clean_param(key)
|
||||||
|
if Setting.get_field(key)[:type] == :string
|
||||||
|
setting_params[key].strip
|
||||||
|
else
|
||||||
|
setting_params[key]
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
class LnurlpayController < ApplicationController
|
class LnurlpayController < ApplicationController
|
||||||
before_action :check_service_available
|
before_action :check_service_available
|
||||||
before_action :find_user
|
before_action :find_user
|
||||||
before_action :set_cors_access_control_headers, only: [:invoice]
|
before_action :set_cors_access_control_headers
|
||||||
|
|
||||||
MIN_SATS = 10
|
MIN_SATS = 10
|
||||||
MAX_SATS = 1_000_000
|
MAX_SATS = 1_000_000
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ class Services::ChatController < Services::BaseController
|
|||||||
before_action :require_service_available
|
before_action :require_service_available
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@service_enabled = current_user.service_enabled?(:xmpp)
|
@service_enabled = current_user.service_enabled?(:ejabberd)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -8,8 +8,7 @@ class Services::RemotestorageController < Services::BaseController
|
|||||||
# unless current_user.service_enabled?(:remotestorage)
|
# unless current_user.service_enabled?(:remotestorage)
|
||||||
# redirect_to service_remotestorage_info_path
|
# redirect_to service_remotestorage_info_path
|
||||||
# end
|
# end
|
||||||
@rs_auths = current_user.remote_storage_authorizations
|
# @rs_apps_connected = current_user.remote_storage_authorizations.any?
|
||||||
# TODO sort by app name
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -3,13 +3,18 @@ class Services::RsAuthsController < Services::BaseController
|
|||||||
before_action :require_feature_enabled
|
before_action :require_feature_enabled
|
||||||
before_action :require_service_available
|
before_action :require_service_available
|
||||||
# before_action :require_service_enabled
|
# before_action :require_service_enabled
|
||||||
before_action :find_rs_auth
|
before_action :find_rs_auth, only: [:destroy, :launch_app]
|
||||||
|
|
||||||
|
def index
|
||||||
|
@rs_auths = current_user.remote_storage_authorizations
|
||||||
|
# TODO sort by app name?
|
||||||
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
@auth.destroy!
|
@auth.destroy!
|
||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html do redirect_to services_storage_url, flash: {
|
format.html do redirect_to apps_services_storage_url, flash: {
|
||||||
success: 'App authorization revoked'
|
success: 'App authorization revoked'
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,8 +4,13 @@ require "bcrypt"
|
|||||||
class SettingsController < ApplicationController
|
class SettingsController < ApplicationController
|
||||||
before_action :authenticate_user!
|
before_action :authenticate_user!
|
||||||
before_action :set_main_nav_section
|
before_action :set_main_nav_section
|
||||||
before_action :set_settings_section, only: [:show, :update, :update_email, :reset_email_password]
|
before_action :set_settings_section, only: [
|
||||||
before_action :set_user, only: [:show, :update, :update_email, :reset_email_password]
|
:show, :update, :update_email, :reset_email_password
|
||||||
|
]
|
||||||
|
before_action :set_user, only: [
|
||||||
|
:show, :update, :update_email, :reset_email_password,
|
||||||
|
:fetch_nostr_user_metadata
|
||||||
|
]
|
||||||
|
|
||||||
def index
|
def index
|
||||||
redirect_to setting_path(:profile)
|
redirect_to setting_path(:profile)
|
||||||
@@ -128,6 +133,28 @@ class SettingsController < ApplicationController
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def fetch_nostr_user_metadata
|
||||||
|
if @user.nostr_pubkey.present?
|
||||||
|
outbox_relay_urls = nil
|
||||||
|
|
||||||
|
# if @nip65_event = NostrManager::DiscoverUserRelays.call(pubkey: @user.nostr_pubkey)
|
||||||
|
# relay_tags = @nip65_event["tags"].select{ |t| t[0] == "r" }
|
||||||
|
# outbox_relay_urls = relay_tags&.select{ |t| t[2] != "read" }&.map{ |t| t[1] }
|
||||||
|
# end
|
||||||
|
|
||||||
|
# @profile = NostrManager::DiscoverUserProfile.call(
|
||||||
|
# pubkey: @user.nostr_pubkey,
|
||||||
|
# relays: outbox_relay_urls
|
||||||
|
# )
|
||||||
|
@profile = {"content"=>"{\"name\":\"jimmy\",\"picture\":\"https://storage.kosmos.org/jimmy/public/shares/241028-1117-tony.jpg\",\"banner\":\"https://storage.kosmos.org/raucao/public/shares/240604-1517-1500x500.jpg\",\"nip05\":\"jimmy@kosmos.org\",\"lud16\":\"jimmy@kosmos.org\",\"pubkey\":\"07e188a1ff87ce171d517b8ed2bb7a31b1d3453a0db3b15379ec07b724d232f3\",\"display_name\":\"Jimmy\",\"displayName\":\"Jimmy\",\"about\":\"I don't exist. Follow at your own peril.\"}", "created_at"=>1730114246, "id"=>"6b15b1308a61ee837bd3b50319978314650e435891c259f4ea499f819f35a4f6", "kind"=>0, "pubkey"=>"07e188a1ff87ce171d517b8ed2bb7a31b1d3453a0db3b15379ec07b724d232f3", "sig"=>"4f681f4b95646bbf88a6eae9ca92c0f2ce5effecfa017556a23490f91a99243aedf81d956ee2466ed64fecb9a03b6b89cd80ff116df0178830977e203867d7ae", "tags"=>[]}
|
||||||
|
# @profile = {"content"=>"{\"name\":\"jimmy\",\"nip05\":\"jimmy@kosmos.org\",\"lud16\":\"jimmy@kosmos.org\",\"pubkey\":\"07e188a1ff87ce171d517b8ed2bb7a31b1d3453a0db3b15379ec07b724d232f3\",\"display_name\":\"Jimmy\",\"displayName\":\"Jimmy\",\"about\":\"I don't exist. Follow at your own peril.\"}", "created_at"=>1730114246, "id"=>"6b15b1308a61ee837bd3b50319978314650e435891c259f4ea499f819f35a4f6", "kind"=>0, "pubkey"=>"07e188a1ff87ce171d517b8ed2bb7a31b1d3453a0db3b15379ec07b724d232f3", "sig"=>"4f681f4b95646bbf88a6eae9ca92c0f2ce5effecfa017556a23490f91a99243aedf81d956ee2466ed64fecb9a03b6b89cd80ff116df0178830977e203867d7ae", "tags"=>[]}
|
||||||
|
else
|
||||||
|
@relays, @profile = [nil, nil]
|
||||||
|
end
|
||||||
|
|
||||||
|
render partial: 'nostr_user_metadata'
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def set_main_nav_section
|
def set_main_nav_section
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
class WebfingerController < ApplicationController
|
class WebfingerController < WellKnownController
|
||||||
before_action :allow_cross_origin_requests, only: [:show]
|
before_action :allow_cross_origin_requests, only: [:show]
|
||||||
|
|
||||||
layout false
|
|
||||||
|
|
||||||
def show
|
def show
|
||||||
resource = params[:resource]
|
resource = params[:resource]
|
||||||
|
|
||||||
@@ -76,7 +74,7 @@ class WebfingerController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def remotestorage_link
|
def remotestorage_link
|
||||||
auth_url = new_rs_oauth_url(@username)
|
auth_url = new_rs_oauth_url(@username, host: Setting.accounts_domain)
|
||||||
storage_url = "#{Setting.rs_storage_url}/#{@username}"
|
storage_url = "#{Setting.rs_storage_url}/#{@username}"
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -91,10 +89,4 @@ class WebfingerController < ApplicationController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def allow_cross_origin_requests
|
|
||||||
return unless Rails.env.development?
|
|
||||||
headers['Access-Control-Allow-Origin'] = "*"
|
|
||||||
headers['Access-Control-Allow-Methods'] = "GET"
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,16 +1,47 @@
|
|||||||
class WellKnownController < ApplicationController
|
class WellKnownController < ApplicationController
|
||||||
|
before_action :require_nostr_enabled, only: [ :nostr ]
|
||||||
|
before_action :allow_cross_origin_requests, only: [ :nostr ]
|
||||||
|
|
||||||
|
layout false
|
||||||
|
|
||||||
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.presence
|
||||||
http_status :not_found and return if @user.nil? || @user.nostr_pubkey.blank?
|
|
||||||
|
if params[:name] == "_"
|
||||||
|
if domain == Setting.primary_domain
|
||||||
|
# pubkey for the primary domain without a username (e.g. kosmos.org)
|
||||||
|
res = { names: { "_": Setting.nostr_public_key_primary_domain.presence || Setting.nostr_public_key } }
|
||||||
|
else
|
||||||
|
# pubkey for the akkounts domain without a username (e.g. accounts.kosmos.org)
|
||||||
|
res = { names: { "_": Setting.nostr_public_key } }
|
||||||
|
end
|
||||||
|
|
||||||
|
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 } }
|
||||||
|
res[:relays] = { @user.nostr_pubkey => [ relay_url ] } if 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
|
||||||
|
|
||||||
|
def allow_cross_origin_requests
|
||||||
|
headers['Access-Control-Allow-Origin'] = "*"
|
||||||
|
headers['Access-Control-Allow-Methods'] = "GET"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
module DashboardHelper
|
|
||||||
end
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
module DonationsHelper
|
|
||||||
end
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
module InvitationsHelper
|
|
||||||
end
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
module LnurlpayHelper
|
|
||||||
end
|
|
||||||
12
app/helpers/services_helper.rb
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
module ServicesHelper
|
||||||
|
|
||||||
|
def service_human_name(key, category = :external)
|
||||||
|
SERVICES[category][key][:name] || key.to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
def service_display_name(key, category = :external)
|
||||||
|
SERVICES[category][key][:display_name] ||
|
||||||
|
service_human_name(key, category)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
module SettingsHelper
|
|
||||||
end
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
module SignupHelper
|
|
||||||
end
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
module UsersHelper
|
|
||||||
end
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
module WalletHelper
|
|
||||||
end
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
module WelcomeHelper
|
|
||||||
end
|
|
||||||
@@ -2,8 +2,8 @@ class XmppExchangeContactsJob < ApplicationJob
|
|||||||
queue_as :default
|
queue_as :default
|
||||||
|
|
||||||
def perform(inviter, invitee)
|
def perform(inviter, invitee)
|
||||||
return unless inviter.service_enabled?(:xmpp) &&
|
return unless inviter.service_enabled?(:ejabberd) &&
|
||||||
invitee.service_enabled?(:xmpp) &&
|
invitee.service_enabled?(:ejabberd) &&
|
||||||
inviter.preferences[:xmpp_exchange_contacts_with_invitees]
|
inviter.preferences[:xmpp_exchange_contacts_with_invitees]
|
||||||
|
|
||||||
ejabberd = EjabberdApiClient.new
|
ejabberd = EjabberdApiClient.new
|
||||||
|
|||||||
24
app/models/concerns/settings/btcpay_settings.rb
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
module Settings
|
||||||
|
module BtcpaySettings
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
field :btcpay_api_url, type: :string,
|
||||||
|
default: ENV["BTCPAY_API_URL"].presence
|
||||||
|
|
||||||
|
field :btcpay_enabled, type: :boolean,
|
||||||
|
default: ENV["BTCPAY_API_URL"].present?
|
||||||
|
|
||||||
|
field :btcpay_public_url, type: :string,
|
||||||
|
default: ENV["BTCPAY_PUBLIC_URL"].presence
|
||||||
|
|
||||||
|
field :btcpay_store_id, type: :string,
|
||||||
|
default: ENV["BTCPAY_STORE_ID"].presence
|
||||||
|
|
||||||
|
field :btcpay_auth_token, type: :string,
|
||||||
|
default: ENV["BTCPAY_AUTH_TOKEN"].presence
|
||||||
|
|
||||||
|
field :btcpay_publish_wallet_balances, type: :boolean, default: true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
16
app/models/concerns/settings/discourse_settings.rb
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
module Settings
|
||||||
|
module DiscourseSettings
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
field :discourse_public_url, type: :string,
|
||||||
|
default: ENV["DISCOURSE_PUBLIC_URL"].presence
|
||||||
|
|
||||||
|
field :discourse_enabled, type: :boolean,
|
||||||
|
default: ENV["DISCOURSE_PUBLIC_URL"].present?
|
||||||
|
|
||||||
|
field :discourse_connect_secret, type: :string,
|
||||||
|
default: ENV["DISCOURSE_CONNECT_SECRET"].presence
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
13
app/models/concerns/settings/drone_ci_settings.rb
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
module Settings
|
||||||
|
module DroneCiSettings
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
field :droneci_public_url, type: :string,
|
||||||
|
default: ENV["DRONECI_PUBLIC_URL"].presence
|
||||||
|
|
||||||
|
field :droneci_enabled, type: :boolean,
|
||||||
|
default: ENV["DRONECI_PUBLIC_URL"].present?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
19
app/models/concerns/settings/ejabberd_settings.rb
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
module Settings
|
||||||
|
module EjabberdSettings
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
field :ejabberd_enabled, type: :boolean,
|
||||||
|
default: ENV["EJABBERD_API_URL"].present?
|
||||||
|
|
||||||
|
field :ejabberd_api_url, type: :string,
|
||||||
|
default: ENV["EJABBERD_API_URL"].presence
|
||||||
|
|
||||||
|
field :ejabberd_admin_url, type: :string,
|
||||||
|
default: ENV["EJABBERD_ADMIN_URL"].presence
|
||||||
|
|
||||||
|
field :ejabberd_buddy_roster, type: :string,
|
||||||
|
default: "Buddies"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
28
app/models/concerns/settings/email_settings.rb
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
module Settings
|
||||||
|
module EmailSettings
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
field :email_enabled, type: :boolean,
|
||||||
|
default: ENV["EMAIL_SMTP_HOST"].present?
|
||||||
|
|
||||||
|
# field :email_smtp_host, type: :string,
|
||||||
|
# default: ENV["EMAIL_SMTP_HOST"].presence
|
||||||
|
#
|
||||||
|
# field :email_smtp_port, type: :string,
|
||||||
|
# default: ENV["EMAIL_SMTP_PORT"].presence || 587
|
||||||
|
#
|
||||||
|
# field :email_smtp_enable_starttls, type: :string,
|
||||||
|
# default: ENV["EMAIL_SMTP_PORT"].presence || true
|
||||||
|
#
|
||||||
|
# field :email_auth_method, type: :string,
|
||||||
|
# default: ENV["EMAIL_AUTH_METHOD"].presence || "plain"
|
||||||
|
#
|
||||||
|
# field :email_imap_host, type: :string,
|
||||||
|
# default: ENV["EMAIL_IMAP_HOST"].presence
|
||||||
|
#
|
||||||
|
# field :email_imap_port, type: :string,
|
||||||
|
# default: ENV["EMAIL_IMAP_PORT"].presence || 993
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
34
app/models/concerns/settings/general_settings.rb
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
module Settings
|
||||||
|
module GeneralSettings
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
field :primary_domain, type: :string,
|
||||||
|
default: ENV["PRIMARY_DOMAIN"].presence
|
||||||
|
|
||||||
|
field :accounts_domain, type: :string,
|
||||||
|
default: ENV["AKKOUNTS_DOMAIN"].presence
|
||||||
|
|
||||||
|
#
|
||||||
|
# Internal services
|
||||||
|
#
|
||||||
|
|
||||||
|
field :redis_url, type: :string,
|
||||||
|
default: ENV["REDIS_URL"] || "redis://localhost:6379/0"
|
||||||
|
|
||||||
|
field :s3_enabled, type: :boolean,
|
||||||
|
default: ENV["S3_ENABLED"] && ENV["S3_ENABLED"].to_s != "false"
|
||||||
|
|
||||||
|
field :sentry_enabled, type: :boolean, readonly: true,
|
||||||
|
default: ENV["SENTRY_DSN"].present?
|
||||||
|
|
||||||
|
#
|
||||||
|
# Registrations
|
||||||
|
#
|
||||||
|
|
||||||
|
field :reserved_usernames, type: :array, default: %w[
|
||||||
|
account accounts donations mail webmaster support
|
||||||
|
]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
13
app/models/concerns/settings/gitea_settings.rb
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
module Settings
|
||||||
|
module GiteaSettings
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
field :gitea_public_url, type: :string,
|
||||||
|
default: ENV["GITEA_PUBLIC_URL"].presence
|
||||||
|
|
||||||
|
field :gitea_enabled, type: :boolean,
|
||||||
|
default: ENV["GITEA_PUBLIC_URL"].present?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
25
app/models/concerns/settings/lightning_network_settings.rb
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
module Settings
|
||||||
|
module LightningNetworkSettings
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
field :lndhub_api_url, type: :string,
|
||||||
|
default: ENV["LNDHUB_API_URL"].presence
|
||||||
|
|
||||||
|
field :lndhub_enabled, type: :boolean,
|
||||||
|
default: ENV["LNDHUB_API_URL"].present?
|
||||||
|
|
||||||
|
field :lndhub_admin_token, type: :string,
|
||||||
|
default: ENV["LNDHUB_ADMIN_TOKEN"].presence
|
||||||
|
|
||||||
|
field :lndhub_admin_enabled, type: :boolean,
|
||||||
|
default: ENV["LNDHUB_ADMIN_UI"] || false
|
||||||
|
|
||||||
|
field :lndhub_public_key, type: :string,
|
||||||
|
default: (ENV["LNDHUB_PUBLIC_KEY"] || "")
|
||||||
|
|
||||||
|
field :lndhub_keysend_enabled, type: :boolean,
|
||||||
|
default: -> { self.lndhub_public_key.present? }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
16
app/models/concerns/settings/mastodon_settings.rb
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
module Settings
|
||||||
|
module MastodonSettings
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
field :mastodon_public_url, type: :string,
|
||||||
|
default: ENV["MASTODON_PUBLIC_URL"].presence
|
||||||
|
|
||||||
|
field :mastodon_enabled, type: :boolean,
|
||||||
|
default: ENV["MASTODON_PUBLIC_URL"].present?
|
||||||
|
|
||||||
|
field :mastodon_address_domain, type: :string,
|
||||||
|
default: ENV["MASTODON_ADDRESS_DOMAIN"].presence || self.primary_domain
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
13
app/models/concerns/settings/media_wiki_settings.rb
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
module Settings
|
||||||
|
module MediaWikiSettings
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
field :mediawiki_public_url, type: :string,
|
||||||
|
default: ENV["MEDIAWIKI_PUBLIC_URL"].presence
|
||||||
|
|
||||||
|
field :mediawiki_enabled, type: :boolean,
|
||||||
|
default: ENV["MEDIAWIKI_PUBLIC_URL"].present?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
38
app/models/concerns/settings/nostr_settings.rb
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
module Settings
|
||||||
|
module NostrSettings
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
field :nostr_enabled, type: :boolean,
|
||||||
|
default: ENV["NOSTR_PRIVATE_KEY"].present?
|
||||||
|
|
||||||
|
field :nostr_private_key, type: :string,
|
||||||
|
default: ENV["NOSTR_PRIVATE_KEY"].presence
|
||||||
|
|
||||||
|
field :nostr_public_key, type: :string,
|
||||||
|
default: ENV["NOSTR_PUBLIC_KEY"].presence
|
||||||
|
|
||||||
|
field :nostr_public_key_primary_domain, type: :string,
|
||||||
|
default: ENV["NOSTR_PUBLIC_KEY_PRIMARY_DOMAIN"].presence
|
||||||
|
|
||||||
|
field :nostr_relay_url, type: :string,
|
||||||
|
default: ENV["NOSTR_RELAY_URL"].presence
|
||||||
|
|
||||||
|
field :nostr_zaps_relay_limit, type: :integer,
|
||||||
|
default: 12
|
||||||
|
|
||||||
|
field :nostr_discovery_relays, type: :array, default: %w[
|
||||||
|
wss://nostr.kosmos.org
|
||||||
|
wss://purplepag.es
|
||||||
|
wss://relay.nostr.band
|
||||||
|
wss://njump.me
|
||||||
|
wss://relay.damus.io
|
||||||
|
]
|
||||||
|
|
||||||
|
def self.nostr_relay_url_http
|
||||||
|
self.nostr_relay_url.gsub(/^ws:/, "http:")
|
||||||
|
.gsub(/^wss:/, "https:")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
9
app/models/concerns/settings/open_collective_settings.rb
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
module Settings
|
||||||
|
module OpenCollectiveSettings
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
field :opencollective_enabled, type: :boolean, default: true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
16
app/models/concerns/settings/remote_storage_settings.rb
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
module Settings
|
||||||
|
module RemoteStorageSettings
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
field :remotestorage_enabled, type: :boolean,
|
||||||
|
default: ENV["RS_STORAGE_URL"].present?
|
||||||
|
|
||||||
|
field :rs_storage_url, type: :string,
|
||||||
|
default: ENV["RS_STORAGE_URL"].presence
|
||||||
|
|
||||||
|
field :rs_redis_url, type: :string,
|
||||||
|
default: ENV["RS_REDIS_URL"] || "redis://localhost:6379/1"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
11
app/models/concerns/settings/xmpp_settings.rb
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
module Settings
|
||||||
|
module XmppSettings
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
field :xmpp_default_rooms, type: :array, default: []
|
||||||
|
field :xmpp_autojoin_default_rooms, type: :boolean, default: false
|
||||||
|
field :xmpp_notifications_from_address, type: :string, default: primary_domain
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -2,223 +2,30 @@
|
|||||||
class Setting < RailsSettings::Base
|
class Setting < RailsSettings::Base
|
||||||
cache_prefix { "v1" }
|
cache_prefix { "v1" }
|
||||||
|
|
||||||
field :primary_domain, type: :string,
|
Dir[Rails.root.join('app', 'models', 'concerns', 'settings', '*.rb')].each do |file|
|
||||||
default: ENV["PRIMARY_DOMAIN"].presence
|
require file
|
||||||
|
|
||||||
field :accounts_domain, type: :string,
|
|
||||||
default: ENV["AKKOUNTS_DOMAIN"].presence
|
|
||||||
|
|
||||||
#
|
|
||||||
# Internal services
|
|
||||||
#
|
|
||||||
|
|
||||||
field :redis_url, type: :string,
|
|
||||||
default: ENV["REDIS_URL"] || "redis://localhost:6379/0"
|
|
||||||
|
|
||||||
field :s3_enabled, type: :boolean,
|
|
||||||
default: ENV["S3_ENABLED"] && ENV["S3_ENABLED"].to_s != "false"
|
|
||||||
|
|
||||||
#
|
|
||||||
# Registrations
|
|
||||||
#
|
|
||||||
|
|
||||||
field :reserved_usernames, type: :array, default: %w[
|
|
||||||
account accounts donations mail webmaster support
|
|
||||||
]
|
|
||||||
|
|
||||||
#
|
|
||||||
# XMPP
|
|
||||||
#
|
|
||||||
|
|
||||||
field :xmpp_default_rooms, type: :array, default: []
|
|
||||||
field :xmpp_autojoin_default_rooms, type: :boolean, default: false
|
|
||||||
field :xmpp_notifications_from_address, type: :string, default: primary_domain
|
|
||||||
|
|
||||||
#
|
|
||||||
# Sentry
|
|
||||||
#
|
|
||||||
|
|
||||||
field :sentry_enabled, type: :boolean, readonly: true,
|
|
||||||
default: ENV["SENTRY_DSN"].present?
|
|
||||||
|
|
||||||
#
|
|
||||||
# BTCPay Server
|
|
||||||
#
|
|
||||||
|
|
||||||
field :btcpay_api_url, type: :string,
|
|
||||||
default: ENV["BTCPAY_API_URL"].presence
|
|
||||||
|
|
||||||
field :btcpay_enabled, type: :boolean,
|
|
||||||
default: ENV["BTCPAY_API_URL"].present?
|
|
||||||
|
|
||||||
field :btcpay_public_url, type: :string,
|
|
||||||
default: ENV["BTCPAY_PUBLIC_URL"].presence
|
|
||||||
|
|
||||||
field :btcpay_store_id, type: :string,
|
|
||||||
default: ENV["BTCPAY_STORE_ID"].presence
|
|
||||||
|
|
||||||
field :btcpay_auth_token, type: :string,
|
|
||||||
default: ENV["BTCPAY_AUTH_TOKEN"].presence
|
|
||||||
|
|
||||||
field :btcpay_publish_wallet_balances, type: :boolean, default: true
|
|
||||||
|
|
||||||
#
|
|
||||||
# Discourse
|
|
||||||
#
|
|
||||||
|
|
||||||
field :discourse_public_url, type: :string,
|
|
||||||
default: ENV["DISCOURSE_PUBLIC_URL"].presence
|
|
||||||
|
|
||||||
field :discourse_enabled, type: :boolean,
|
|
||||||
default: ENV["DISCOURSE_PUBLIC_URL"].present?
|
|
||||||
|
|
||||||
field :discourse_connect_secret, type: :string,
|
|
||||||
default: ENV["DISCOURSE_CONNECT_SECRET"].presence
|
|
||||||
|
|
||||||
#
|
|
||||||
# Drone CI
|
|
||||||
#
|
|
||||||
|
|
||||||
field :droneci_public_url, type: :string,
|
|
||||||
default: ENV["DRONECI_PUBLIC_URL"].presence
|
|
||||||
|
|
||||||
field :droneci_enabled, type: :boolean,
|
|
||||||
default: ENV["DRONECI_PUBLIC_URL"].present?
|
|
||||||
|
|
||||||
#
|
|
||||||
# ejabberd
|
|
||||||
#
|
|
||||||
|
|
||||||
field :ejabberd_enabled, type: :boolean,
|
|
||||||
default: ENV["EJABBERD_API_URL"].present?
|
|
||||||
|
|
||||||
field :ejabberd_api_url, type: :string,
|
|
||||||
default: ENV["EJABBERD_API_URL"].presence
|
|
||||||
|
|
||||||
field :ejabberd_admin_url, type: :string,
|
|
||||||
default: ENV["EJABBERD_ADMIN_URL"].presence
|
|
||||||
|
|
||||||
field :ejabberd_buddy_roster, type: :string,
|
|
||||||
default: "Buddies"
|
|
||||||
|
|
||||||
#
|
|
||||||
# Gitea
|
|
||||||
#
|
|
||||||
|
|
||||||
field :gitea_public_url, type: :string,
|
|
||||||
default: ENV["GITEA_PUBLIC_URL"].presence
|
|
||||||
|
|
||||||
field :gitea_enabled, type: :boolean,
|
|
||||||
default: ENV["GITEA_PUBLIC_URL"].present?
|
|
||||||
|
|
||||||
#
|
|
||||||
# Lightning Network
|
|
||||||
#
|
|
||||||
|
|
||||||
field :lndhub_api_url, type: :string,
|
|
||||||
default: ENV["LNDHUB_API_URL"].presence
|
|
||||||
|
|
||||||
field :lndhub_enabled, type: :boolean,
|
|
||||||
default: ENV["LNDHUB_API_URL"].present?
|
|
||||||
|
|
||||||
field :lndhub_admin_token, type: :string,
|
|
||||||
default: ENV["LNDHUB_ADMIN_TOKEN"].presence
|
|
||||||
|
|
||||||
field :lndhub_admin_enabled, type: :boolean,
|
|
||||||
default: ENV["LNDHUB_ADMIN_UI"] || false
|
|
||||||
|
|
||||||
field :lndhub_public_key, type: :string,
|
|
||||||
default: (ENV["LNDHUB_PUBLIC_KEY"] || "")
|
|
||||||
|
|
||||||
field :lndhub_keysend_enabled, type: :boolean,
|
|
||||||
default: -> { self.lndhub_public_key.present? }
|
|
||||||
|
|
||||||
#
|
|
||||||
# Mastodon
|
|
||||||
#
|
|
||||||
|
|
||||||
field :mastodon_public_url, type: :string,
|
|
||||||
default: ENV["MASTODON_PUBLIC_URL"].presence
|
|
||||||
|
|
||||||
field :mastodon_enabled, type: :boolean,
|
|
||||||
default: ENV["MASTODON_PUBLIC_URL"].present?
|
|
||||||
|
|
||||||
field :mastodon_address_domain, type: :string,
|
|
||||||
default: ENV["MASTODON_ADDRESS_DOMAIN"].presence || self.primary_domain
|
|
||||||
|
|
||||||
#
|
|
||||||
# MediaWiki
|
|
||||||
#
|
|
||||||
|
|
||||||
field :mediawiki_public_url, type: :string,
|
|
||||||
default: ENV["MEDIAWIKI_PUBLIC_URL"].presence
|
|
||||||
|
|
||||||
field :mediawiki_enabled, type: :boolean,
|
|
||||||
default: ENV["MEDIAWIKI_PUBLIC_URL"].present?
|
|
||||||
|
|
||||||
#
|
|
||||||
# Nostr
|
|
||||||
#
|
|
||||||
|
|
||||||
field :nostr_enabled, type: :boolean,
|
|
||||||
default: ENV["NOSTR_PRIVATE_KEY"].present?
|
|
||||||
|
|
||||||
field :nostr_private_key, type: :string,
|
|
||||||
default: ENV["NOSTR_PRIVATE_KEY"].presence
|
|
||||||
|
|
||||||
field :nostr_public_key, type: :string,
|
|
||||||
default: ENV["NOSTR_PUBLIC_KEY"].presence
|
|
||||||
|
|
||||||
field :nostr_zaps_relay_limit, type: :integer,
|
|
||||||
default: 12
|
|
||||||
|
|
||||||
#
|
|
||||||
# OpenCollective
|
|
||||||
#
|
|
||||||
|
|
||||||
field :opencollective_enabled, type: :boolean, default: true
|
|
||||||
|
|
||||||
#
|
|
||||||
# RemoteStorage
|
|
||||||
#
|
|
||||||
|
|
||||||
field :remotestorage_enabled, type: :boolean,
|
|
||||||
default: ENV["RS_STORAGE_URL"].present?
|
|
||||||
|
|
||||||
field :rs_storage_url, type: :string,
|
|
||||||
default: ENV["RS_STORAGE_URL"].presence
|
|
||||||
|
|
||||||
field :rs_redis_url, type: :string,
|
|
||||||
default: ENV["RS_REDIS_URL"] || "redis://localhost:6379/1"
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# E-Mail Service
|
|
||||||
#
|
|
||||||
|
|
||||||
field :email_enabled, type: :boolean,
|
|
||||||
default: ENV["EMAIL_SMTP_HOST"].present?
|
|
||||||
|
|
||||||
# field :email_smtp_host, type: :string,
|
|
||||||
# default: ENV["EMAIL_SMTP_HOST"].presence
|
|
||||||
#
|
|
||||||
# field :email_smtp_port, type: :string,
|
|
||||||
# default: ENV["EMAIL_SMTP_PORT"].presence || 587
|
|
||||||
#
|
|
||||||
# field :email_smtp_enable_starttls, type: :string,
|
|
||||||
# default: ENV["EMAIL_SMTP_PORT"].presence || true
|
|
||||||
#
|
|
||||||
# field :email_auth_method, type: :string,
|
|
||||||
# default: ENV["EMAIL_AUTH_METHOD"].presence || "plain"
|
|
||||||
#
|
|
||||||
# field :email_imap_host, type: :string,
|
|
||||||
# default: ENV["EMAIL_IMAP_HOST"].presence
|
|
||||||
#
|
|
||||||
# field :email_imap_port, type: :string,
|
|
||||||
# default: ENV["EMAIL_IMAP_PORT"].presence || 993
|
|
||||||
|
|
||||||
def self.default_services
|
|
||||||
# TODO Make configurable from respective service settings page
|
|
||||||
%w[ discourse gitea mastodon mediawiki xmpp ]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
include Settings::GeneralSettings
|
||||||
|
include Settings::BtcpaySettings
|
||||||
|
include Settings::DiscourseSettings
|
||||||
|
include Settings::DroneCiSettings
|
||||||
|
include Settings::EjabberdSettings
|
||||||
|
include Settings::EmailSettings
|
||||||
|
include Settings::GiteaSettings
|
||||||
|
include Settings::LightningNetworkSettings
|
||||||
|
include Settings::MastodonSettings
|
||||||
|
include Settings::MediaWikiSettings
|
||||||
|
include Settings::NostrSettings
|
||||||
|
include Settings::OpenCollectiveSettings
|
||||||
|
include Settings::RemoteStorageSettings
|
||||||
|
include Settings::XmppSettings
|
||||||
|
|
||||||
|
def self.available_services
|
||||||
|
known_services = SERVICES[:external].keys
|
||||||
|
known_services.select {|s| Setting.send "#{s}_enabled?" }
|
||||||
|
end
|
||||||
|
|
||||||
|
field :default_services, type: :array,
|
||||||
|
default: self.available_services
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -180,14 +180,14 @@ class User < ApplicationRecord
|
|||||||
def enable_service(service)
|
def enable_service(service)
|
||||||
current_services = services_enabled
|
current_services = services_enabled
|
||||||
new_services = Array(service).map(&:to_s)
|
new_services = Array(service).map(&:to_s)
|
||||||
services = (current_services + new_services).uniq
|
services = (current_services + new_services).uniq.sort
|
||||||
ldap.replace_attribute(dn, :serviceEnabled, services)
|
ldap.replace_attribute(dn, :serviceEnabled, services)
|
||||||
end
|
end
|
||||||
|
|
||||||
def disable_service(service)
|
def disable_service(service)
|
||||||
current_services = services_enabled
|
current_services = services_enabled
|
||||||
disabled_services = Array(service).map(&:to_s)
|
disabled_services = Array(service).map(&:to_s)
|
||||||
services = (current_services - disabled_services).uniq
|
services = (current_services - disabled_services).uniq.sort
|
||||||
ldap.replace_attribute(dn, :serviceEnabled, services)
|
ldap.replace_attribute(dn, :serviceEnabled, services)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
21
app/services/nostr_manager/discover_user_profile.rb
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
module NostrManager
|
||||||
|
class DiscoverUserProfile < NostrManagerService
|
||||||
|
def initialize(pubkey:, relays: nil)
|
||||||
|
@pubkey = pubkey
|
||||||
|
@relays = relays.present? ? relays : Setting.nostr_discovery_relays
|
||||||
|
end
|
||||||
|
|
||||||
|
def call
|
||||||
|
filter = Nostr::Filter.new(
|
||||||
|
authors: [@pubkey],
|
||||||
|
kinds: [0],
|
||||||
|
limit: 1,
|
||||||
|
)
|
||||||
|
|
||||||
|
NostrManager::FetchLatestEvent.call(
|
||||||
|
relays: @relays,
|
||||||
|
filter: filter
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
21
app/services/nostr_manager/discover_user_relays.rb
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
module NostrManager
|
||||||
|
class DiscoverUserRelays < NostrManagerService
|
||||||
|
def initialize(pubkey:)
|
||||||
|
@pubkey = pubkey
|
||||||
|
@relays = Setting.nostr_discovery_relays
|
||||||
|
end
|
||||||
|
|
||||||
|
def call
|
||||||
|
filter = Nostr::Filter.new(
|
||||||
|
authors: [@pubkey],
|
||||||
|
kinds: [10002],
|
||||||
|
limit: 1,
|
||||||
|
)
|
||||||
|
|
||||||
|
NostrManager::FetchLatestEvent.call(
|
||||||
|
relays: @relays,
|
||||||
|
filter: filter
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
59
app/services/nostr_manager/fetch_event.rb
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
module NostrManager
|
||||||
|
class FetchEvent < NostrManagerService
|
||||||
|
TIMEOUT = 10
|
||||||
|
|
||||||
|
def initialize(filter:, relay_url:)
|
||||||
|
@filter = filter
|
||||||
|
@relay = new_relay(relay_url)
|
||||||
|
@client = Nostr::Client.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def call
|
||||||
|
filter, client, relay = @filter, @client, @relay
|
||||||
|
event = nil
|
||||||
|
mutex = Mutex.new
|
||||||
|
received_event = ConditionVariable.new
|
||||||
|
log_prefix = "[nostr][#{@relay.name}]"
|
||||||
|
|
||||||
|
thread = Thread.new do
|
||||||
|
client.on :connect do
|
||||||
|
client.subscribe(filter: filter)
|
||||||
|
end
|
||||||
|
|
||||||
|
client.on :error do |e|
|
||||||
|
Rails.logger.info "#{log_prefix} Error: #{e}"
|
||||||
|
Thread.current.exit
|
||||||
|
end
|
||||||
|
|
||||||
|
client.on :message do |m|
|
||||||
|
msg = JSON.parse(m) rescue nil
|
||||||
|
if msg && msg[0] == "EVENT" && msg[2]
|
||||||
|
Rails.logger.debug "#{log_prefix} Event received: #{msg[2]["id"]}"
|
||||||
|
mutex.synchronize do
|
||||||
|
event = msg[2]
|
||||||
|
received_event.signal
|
||||||
|
end
|
||||||
|
elsif msg && msg[0] == "EOSE"
|
||||||
|
Thread.current.exit
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
client.connect relay
|
||||||
|
end
|
||||||
|
|
||||||
|
begin
|
||||||
|
Timeout.timeout(TIMEOUT) do
|
||||||
|
mutex.synchronize do
|
||||||
|
received_event.wait(mutex) if event.nil?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
rescue Timeout::Error
|
||||||
|
Rails.logger.debug "#{log_prefix} Timeout: No event received within #{TIMEOUT} seconds"
|
||||||
|
ensure
|
||||||
|
thread.exit
|
||||||
|
end
|
||||||
|
|
||||||
|
event
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
44
app/services/nostr_manager/fetch_latest_event.rb
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
module NostrManager
|
||||||
|
class FetchLatestEvent < NostrManagerService
|
||||||
|
TIMEOUT = 20
|
||||||
|
|
||||||
|
def initialize(relays:, filter:, max_events: 2)
|
||||||
|
@relays = relays
|
||||||
|
@filter = filter
|
||||||
|
@max_events = max_events
|
||||||
|
end
|
||||||
|
|
||||||
|
def call
|
||||||
|
received_events = 0
|
||||||
|
events = []
|
||||||
|
|
||||||
|
begin
|
||||||
|
Timeout.timeout(TIMEOUT) do
|
||||||
|
@relays.each do |url|
|
||||||
|
event = NostrManager::FetchEvent.call(filter: @filter, relay_url: url)
|
||||||
|
|
||||||
|
if event.present?
|
||||||
|
events << event if events.none? { |e| e["id"] == event["id"] }
|
||||||
|
received_events += 1
|
||||||
|
end
|
||||||
|
|
||||||
|
if received_events >= @max_events
|
||||||
|
Rails.logger.debug "Found #{@max_events} events, ending the search"
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
events.min_by { |e| e["created_at"] }
|
||||||
|
end
|
||||||
|
rescue Timeout::Error
|
||||||
|
if events.size == 1
|
||||||
|
Rails.logger.debug "[nostr] Timeout: only found 1 event within #{TIMEOUT} seconds for filter: #{@filter.inspect}"
|
||||||
|
events.first
|
||||||
|
else
|
||||||
|
Rails.logger.debug "[nostr] Timeout: no events found within #{TIMEOUT} seconds for filter: #{@filter.inspect}"
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -19,28 +19,28 @@ module NostrManager
|
|||||||
|
|
||||||
thread = Thread.new do
|
thread = Thread.new do
|
||||||
client.on :connect do
|
client.on :connect do
|
||||||
puts "#{log_prefix} Publishing #{event.id}..."
|
Rails.logger.debug "#{log_prefix} Publishing #{event.id}..."
|
||||||
client.publish event
|
client.publish event
|
||||||
end
|
end
|
||||||
|
|
||||||
client.on :error do |e|
|
client.on :error do |e|
|
||||||
puts "#{log_prefix} Error: #{e}"
|
Rails.logger.debug "#{log_prefix} Error: #{e}"
|
||||||
puts "#{log_prefix} Closing thread..."
|
Rails.logger.debug "#{log_prefix} Closing thread..."
|
||||||
thread.exit
|
thread.exit
|
||||||
end
|
end
|
||||||
|
|
||||||
client.on :message do |m|
|
client.on :message do |m|
|
||||||
puts "#{log_prefix} Message: #{m}"
|
Rails.logger.debug "#{log_prefix} Message: #{m}"
|
||||||
msg = JSON.parse(m) rescue []
|
msg = JSON.parse(m) rescue []
|
||||||
if msg[0] == "OK" && msg[1] == event.id && msg[2]
|
if msg[0] == "OK" && msg[1] == event.id && msg[2]
|
||||||
puts "#{log_prefix} Event published. Closing thread..."
|
Rails.logger.debug "#{log_prefix} Event published. Closing thread..."
|
||||||
else
|
else
|
||||||
puts "#{log_prefix} Unexpected message from relay. Closing thread..."
|
Rails.logger.debug "#{log_prefix} Unexpected message from relay. Closing thread..."
|
||||||
end
|
end
|
||||||
thread.exit
|
thread.exit
|
||||||
end
|
end
|
||||||
|
|
||||||
puts "#{log_prefix} Connecting to #{relay.url}..."
|
Rails.logger.debug "#{log_prefix} Connecting to #{relay.url}..."
|
||||||
client.connect relay
|
client.connect relay
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ require "nostr"
|
|||||||
class NostrManagerService < ApplicationService
|
class NostrManagerService < ApplicationService
|
||||||
def parse_tags(tags)
|
def parse_tags(tags)
|
||||||
out = {}
|
out = {}
|
||||||
|
# TODO support more than 1 item for each tag type
|
||||||
tags.each do |tag|
|
tags.each do |tag|
|
||||||
out[tag[0].to_sym] = tag[1, tag.length]
|
out[tag[0].to_sym] = tag[1, tag.length]
|
||||||
end
|
end
|
||||||
@@ -19,4 +20,8 @@ class NostrManagerService < ApplicationService
|
|||||||
def site_user
|
def site_user
|
||||||
Nostr::User.new(keypair: site_keypair)
|
Nostr::User.new(keypair: site_keypair)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def new_relay(url)
|
||||||
|
Nostr::Relay.new(url: url, name: URI.parse(url).host)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -38,8 +38,8 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td class="overflow-ellipsis font-mono"><%= invitation.token %></td>
|
<td class="overflow-ellipsis font-mono"><%= invitation.token %></td>
|
||||||
<td><%= invitation.used_at.strftime("%Y-%m-%d (%H:%M UTC)") %></td>
|
<td><%= invitation.used_at.strftime("%Y-%m-%d (%H:%M UTC)") %></td>
|
||||||
<td><%= link_to invitation.user.address, admin_user_path(invitation.user.address), class: "ks-text-link" %></td>
|
<td><%= link_to invitation.user.cn, admin_user_path(invitation.user.cn), class: "ks-text-link" %></td>
|
||||||
<td><%= link_to invitation.invitee.address, admin_user_path(invitation.invitee.address), class: "ks-text-link" %></td>
|
<td><%= link_to invitation.invitee.cn, admin_user_path(invitation.invitee.cn), class: "ks-text-link" %></td>
|
||||||
</tr>
|
</tr>
|
||||||
<% end %>
|
<% end %>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<% if user = @users.find{ |u| u[2] == account.login } %>
|
<% if user = @users.find{ |u| u[2] == account.login } %>
|
||||||
<%= link_to "#{user[0]}@#{user[1]}", admin_user_path("#{user[0]}@#{user[1]}"), class: "ks-text-link" %>
|
<%= link_to user[0], admin_user_path(user[0]), class: "ks-text-link" %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</td>
|
</td>
|
||||||
<td><%= number_with_delimiter account.balance.to_i.to_s %></td>
|
<td><%= number_with_delimiter account.balance.to_i.to_s %></td>
|
||||||
|
|||||||
@@ -9,18 +9,36 @@
|
|||||||
<%= render partial: "admin/settings/errors", locals: { errors: @errors } %>
|
<%= render partial: "admin/settings/errors", locals: { errors: @errors } %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<label class="block">
|
<ul role="list">
|
||||||
<p class="font-bold mb-1">Reserved usernames</p>
|
<%= render FormElements::FieldsetComponent.new(
|
||||||
<p class="text-gray-500">
|
title: "Reserved usernames",
|
||||||
These usernames cannot be registered as accounts:
|
description: "These usernames cannot be registered as accounts."
|
||||||
</p>
|
) do %>
|
||||||
<%= f.text_area :reserved_usernames,
|
<%= f.text_area :reserved_usernames,
|
||||||
value: Setting.reserved_usernames.join("\n"),
|
value: Setting.reserved_usernames.join("\n"),
|
||||||
class: "h-44 mb-2" %>
|
class: "h-44 w-60" %>
|
||||||
<p class="text-sm text-gray-500">
|
<p class="text-sm text-gray-500">
|
||||||
One username per line
|
One username per line
|
||||||
</p>
|
</p>
|
||||||
</label>
|
<% end %>
|
||||||
|
<li>
|
||||||
|
<p class="font-bold mb-1">Default services</p>
|
||||||
|
<p class="text-gray-500">
|
||||||
|
These services are enabled for new users by default after signup.
|
||||||
|
</p>
|
||||||
|
<div class="flex flex-wrap gap-x-6 gap-y-2">
|
||||||
|
<% Setting.available_services.each do |option| %>
|
||||||
|
<div class="md:inline-block">
|
||||||
|
<%= f.check_box :default_services,
|
||||||
|
{ multiple: true, checked: Setting.default_services.include?(option),
|
||||||
|
class: "h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-600 mr-0.5" },
|
||||||
|
option, nil %>
|
||||||
|
<%= f.label "default_services_#{option.parameterize}", service_human_name(option) %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
|
|||||||
@@ -19,15 +19,40 @@
|
|||||||
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_public_key_primary_domain,
|
||||||
|
title: "Public key for primary domain (NIP-05)",
|
||||||
|
description: "(optional) A different pubkey to announce for the _@#{Setting.primary_domain} Nostr address"
|
||||||
|
) %>
|
||||||
|
<%= 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>
|
||||||
<h3>Zaps</h3>
|
<h3>Zaps</h3>
|
||||||
<ul role="list">
|
<ul role="list">
|
||||||
<%= render FormElements::FieldsetResettableSettingComponent.new(
|
<%= render FormElements::FieldsetResettableSettingComponent.new(
|
||||||
key: :nostr_zaps_relay_limit,
|
key: :nostr_zaps_relay_limit,
|
||||||
title: "Relay limit",
|
title: "Relay limit",
|
||||||
description: "The maximum number of relays to publish zap receipts to"
|
description: "The maximum number of sender-defined relays to try to publish zap receipts to"
|
||||||
) %>
|
) %>
|
||||||
</ul>
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h3>Onboarding</h3>
|
||||||
|
<ul role="list">
|
||||||
|
<%= render FormElements::FieldsetComponent.new(
|
||||||
|
title: "Discovery relays",
|
||||||
|
description: "Used to discover a user's published relay list and/or profile"
|
||||||
|
) do %>
|
||||||
|
<%= f.text_area :nostr_discovery_relays,
|
||||||
|
value: Setting.nostr_discovery_relays.join("\n"),
|
||||||
|
class: "h-44 w-80" %>
|
||||||
|
<% end %>
|
||||||
|
</ul>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
<h3>RemoteStorage</h3>
|
<h3>RemoteStorage</h3>
|
||||||
<p class="text-red-600 mb-8">Feature currently in development.</p>
|
|
||||||
<ul role="list">
|
<ul role="list">
|
||||||
<%= render FormElements::FieldsetToggleComponent.new(
|
<%= render FormElements::FieldsetToggleComponent.new(
|
||||||
form: f,
|
form: f,
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
<th>Invited by</th>
|
<th>Invited by</th>
|
||||||
<td>
|
<td>
|
||||||
<% if @user.inviter %>
|
<% if @user.inviter %>
|
||||||
<%= link_to @user.inviter.address, admin_user_path(@user.inviter.address), class: 'ks-text-link' %>
|
<%= link_to @user.inviter.cn, admin_user_path(@user.inviter.cn), class: 'ks-text-link' %>
|
||||||
<% else %>—<% end %>
|
<% else %>—<% end %>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -78,7 +78,7 @@
|
|||||||
<% if @user.invitees.length > 0 %>
|
<% if @user.invitees.length > 0 %>
|
||||||
<ul class="mb-0">
|
<ul class="mb-0">
|
||||||
<% @user.invitees.order(cn: :asc).each do |invitee| %>
|
<% @user.invitees.order(cn: :asc).each do |invitee| %>
|
||||||
<li class="leading-none mb-2 last:mb-0"><%= link_to invitee.address, admin_user_path(invitee.address), class: 'ks-text-link' %></li>
|
<li class="leading-none mb-2 last:mb-0"><%= link_to invitee.cn, admin_user_path(invitee.cn), class: 'ks-text-link' %></li>
|
||||||
<% end %>
|
<% end %>
|
||||||
</ul>
|
</ul>
|
||||||
<% else %>—<% end %>
|
<% else %>—<% end %>
|
||||||
@@ -124,6 +124,19 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<% if Setting.email_enabled %>
|
||||||
|
<tr>
|
||||||
|
<td>E-Mail</td>
|
||||||
|
<td>
|
||||||
|
<%= render FormElements::ToggleComponent.new(
|
||||||
|
enabled: Flipper.enabled?(:email, current_user),
|
||||||
|
input_enabled: false
|
||||||
|
) %>
|
||||||
|
</td>
|
||||||
|
<td class="text-right">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<% end %>
|
||||||
<% if Setting.gitea_enabled %>
|
<% if Setting.gitea_enabled %>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Gitea</td>
|
<td>Gitea</td>
|
||||||
@@ -171,7 +184,7 @@
|
|||||||
<td>XMPP (ejabberd)</td>
|
<td>XMPP (ejabberd)</td>
|
||||||
<td>
|
<td>
|
||||||
<%= render FormElements::ToggleComponent.new(
|
<%= render FormElements::ToggleComponent.new(
|
||||||
enabled: @services_enabled.include?("xmpp"),
|
enabled: @services_enabled.include?("ejabberd"),
|
||||||
input_enabled: false
|
input_enabled: false
|
||||||
) %>
|
) %>
|
||||||
</td>
|
</td>
|
||||||
@@ -182,6 +195,33 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<% if Setting.nostr_enabled %>
|
||||||
|
<tr>
|
||||||
|
<td>Nostr</td>
|
||||||
|
<td>
|
||||||
|
<%= render FormElements::ToggleComponent.new(
|
||||||
|
enabled: @user.nostr_pubkey.present?,
|
||||||
|
input_enabled: false
|
||||||
|
) %>
|
||||||
|
</td>
|
||||||
|
<td class="text-right">
|
||||||
|
<%= link_to "Open profile", "https://njump.me/#{@user.nostr_pubkey_bech32}", class: "btn-sm btn-gray" %>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<% end %>
|
||||||
|
<% if Setting.remotestorage_enabled %>
|
||||||
|
<tr>
|
||||||
|
<td>remoteStorage</td>
|
||||||
|
<td>
|
||||||
|
<%= render FormElements::ToggleComponent.new(
|
||||||
|
enabled: Flipper.enabled?(:remotestorage, current_user) && @services_enabled.include?("remotestorage"),
|
||||||
|
input_enabled: false
|
||||||
|
) %>
|
||||||
|
</td>
|
||||||
|
<td class="text-right">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<% end %>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -43,7 +43,7 @@
|
|||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
We have run two 6-month trials so far, with the next trial period
|
We have run two 6-month trials so far, with the next trial period
|
||||||
starting sometime in Q2 2024. Watch your email for notifications about it!
|
starting sometime soon. Watch your email for notifications about it!
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<div class="services grid grid-cols-1 sm:grid-cols-2 gap-4 sm:gap-6">
|
<div class="services grid grid-cols-1 sm:grid-cols-2 gap-4 sm:gap-6">
|
||||||
<% if Setting.ejabberd_enabled? %>
|
<% if Setting.ejabberd_enabled? %>
|
||||||
<div class="border border-gray-300 rounded-md hover:border-gray-400
|
<div class="border border-gray-300 rounded-md hover:border-gray-400
|
||||||
bg-cover bg-[center_top_-50px] bg-no-repeat
|
bg-[length:86%] bg-[center_top_-40px] bg-no-repeat
|
||||||
bg-[url(/img/logos/icon_xmpp.svg)]">
|
bg-[url(/img/logos/icon_xmpp.svg)]">
|
||||||
<%= link_to services_chat_path,
|
<%= link_to services_chat_path,
|
||||||
class: "block h-full px-6 py-6 rounded-md" do %>
|
class: "block h-full px-6 py-6 rounded-md" do %>
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<% if Setting.mastodon_enabled? %>
|
<% if Setting.mastodon_enabled? %>
|
||||||
<div class="border border-gray-300 rounded-md hover:border-gray-400
|
<div class="border border-gray-300 rounded-md hover:border-gray-400
|
||||||
bg-[length:80%] bg-[right_top_-30px] bg-no-repeat
|
bg-[length:88%] bg-[center_top_-40px] bg-no-repeat
|
||||||
bg-[url(/img/logos/icon_mastodon.svg)]">
|
bg-[url(/img/logos/icon_mastodon.svg)]">
|
||||||
<%= link_to services_mastodon_path, class: "block h-full px-6 py-6 rounded-md" do %>
|
<%= link_to services_mastodon_path, class: "block h-full px-6 py-6 rounded-md" do %>
|
||||||
<h3 class="mb-3.5">Mastodon</h3>
|
<h3 class="mb-3.5">Mastodon</h3>
|
||||||
@@ -30,7 +30,9 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<% if Setting.email_enabled? &&
|
<% if Setting.email_enabled? &&
|
||||||
Flipper.enabled?(:email, current_user) %>
|
Flipper.enabled?(:email, current_user) %>
|
||||||
<div class="border border-gray-300 rounded-md hover:border-gray-400">
|
<div class="border border-gray-300 rounded-md hover:border-gray-400
|
||||||
|
bg-[length:90%] bg-[center_top_-160px] bg-no-repeat
|
||||||
|
bg-[url(/img/logos/icon_mail.svg)]">
|
||||||
<%= link_to services_email_path, class: "block h-full px-6 py-6 rounded-md" do %>
|
<%= link_to services_email_path, class: "block h-full px-6 py-6 rounded-md" do %>
|
||||||
<h3 class="mb-3.5">E-Mail</h3>
|
<h3 class="mb-3.5">E-Mail</h3>
|
||||||
<p class="text-gray-600">
|
<p class="text-gray-600">
|
||||||
@@ -39,15 +41,16 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if Setting.discourse_enabled? %>
|
<% if Setting.remotestorage_enabled? &&
|
||||||
|
Flipper.enabled?(:remotestorage, current_user) %>
|
||||||
<div class="border border-gray-300 rounded-md hover:border-gray-400
|
<div class="border border-gray-300 rounded-md hover:border-gray-400
|
||||||
bg-[length:95%] bg-center bg-no-repeat
|
bg-[length:80%] bg-[center_top_-156px] bg-no-repeat
|
||||||
bg-[url(/img/logos/icon_discourse.svg)]">
|
bg-[url(/img/logos/icon_remotestorage.svg)]">
|
||||||
<%= link_to "#{Setting.discourse_public_url}/session/sso?return_path=/",
|
<%= link_to services_storage_path,
|
||||||
class: "block h-full px-6 py-6 rounded-md" do %>
|
class: "block h-full px-6 py-6 rounded-md" do %>
|
||||||
<h3 class="mb-3.5">Discourse</h3>
|
<h3 class="mb-3.5">Storage</h3>
|
||||||
<p class="text-gray-600">
|
<p class="text-gray-600">
|
||||||
Kosmos community forums and user support/help site
|
Sync your data between apps and devices
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
@@ -65,21 +68,22 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if Setting.remotestorage_enabled? &&
|
<% if Setting.discourse_enabled? %>
|
||||||
Flipper.enabled?(:remotestorage, current_user) %>
|
<div class="border border-gray-300 rounded-md hover:border-gray-400
|
||||||
<div class="border border-gray-300 rounded-md hover:border-gray-400">
|
bg-[length:80%] bg-center bg-no-repeat
|
||||||
<%= link_to services_storage_path,
|
bg-[url(/img/logos/icon_discourse.svg)]">
|
||||||
|
<%= link_to "#{Setting.discourse_public_url}/session/sso?return_path=/",
|
||||||
class: "block h-full px-6 py-6 rounded-md" do %>
|
class: "block h-full px-6 py-6 rounded-md" do %>
|
||||||
<h3 class="mb-3.5">Storage</h3>
|
<h3 class="mb-3.5">Discourse</h3>
|
||||||
<p class="text-gray-600">
|
<p class="text-gray-600">
|
||||||
Sync your data between apps and devices
|
Community forums and support/help site
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if Setting.gitea_enabled? %>
|
<% if Setting.gitea_enabled? %>
|
||||||
<div class="border border-gray-300 rounded-md hover:border-gray-400
|
<div class="border border-gray-300 rounded-md hover:border-gray-400
|
||||||
bg-cover bg-center bg-no-repeat
|
bg-[length:92%] bg-center bg-no-repeat
|
||||||
bg-[url(/img/logos/icon_gitea.png)]">
|
bg-[url(/img/logos/icon_gitea.png)]">
|
||||||
<%= link_to Setting.gitea_public_url,
|
<%= link_to Setting.gitea_public_url,
|
||||||
class: "block h-full px-6 py-6 rounded-md" do %>
|
class: "block h-full px-6 py-6 rounded-md" do %>
|
||||||
@@ -92,7 +96,7 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<% if Setting.droneci_enabled? %>
|
<% if Setting.droneci_enabled? %>
|
||||||
<div class="border border-gray-300 rounded-md hover:border-gray-400
|
<div class="border border-gray-300 rounded-md hover:border-gray-400
|
||||||
bg-cover bg-[center_top_-70px] bg-no-repeat
|
bg-[length:86%] bg-[center_top_-60px] bg-no-repeat
|
||||||
bg-[url(/img/logos/icon_droneci.svg)]">
|
bg-[url(/img/logos/icon_droneci.svg)]">
|
||||||
<%= link_to Setting.droneci_public_url,
|
<%= link_to Setting.droneci_public_url,
|
||||||
class: "block h-full px-6 py-6 rounded-md" do %>
|
class: "block h-full px-6 py-6 rounded-md" do %>
|
||||||
|
|||||||
@@ -100,6 +100,14 @@
|
|||||||
["Website", "https://www.thunderbird.net"]
|
["Website", "https://www.thunderbird.net"]
|
||||||
]
|
]
|
||||||
) %>
|
) %>
|
||||||
|
<%= render AppInfoComponent.new(
|
||||||
|
name: "Geary",
|
||||||
|
description: "Built around conversations, for the GNOME desktop",
|
||||||
|
icon_path: "/img/logos/icon_geary.png",
|
||||||
|
links: [
|
||||||
|
["Website", "https://wiki.gnome.org/Apps/Geary"]
|
||||||
|
]
|
||||||
|
) %>
|
||||||
</div>
|
</div>
|
||||||
<div id="apps-windows" class="hidden grid grid-cols-1 gap-6"
|
<div id="apps-windows" class="hidden grid grid-cols-1 gap-6"
|
||||||
data-tabs-target="panel">
|
data-tabs-target="panel">
|
||||||
|
|||||||
@@ -2,15 +2,162 @@
|
|||||||
|
|
||||||
<%= render MainSimpleComponent.new do %>
|
<%= render MainSimpleComponent.new do %>
|
||||||
<section>
|
<section>
|
||||||
<h3 class="mb-10">Connected Apps</h3>
|
<p class="mb-6">
|
||||||
<% if @rs_auths.any? %>
|
Store and synchronize your app data across different devices.
|
||||||
<div class="w-full grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-y-10 gap-x-12">
|
</p>
|
||||||
<% @rs_auths.each do |auth| %>
|
</section>
|
||||||
<%= render RsAuthComponent.new(auth: auth) %>
|
|
||||||
<% end %>
|
<%= render partial: "shared/tabnav_remotestorage" %>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h3>Your Storage Address</h3>
|
||||||
|
<p class="mb-6">
|
||||||
|
In order to connect an app to your storage account, give it your address:
|
||||||
|
</p>
|
||||||
|
<p data-controller="clipboard" class="flex items-center gap-1 sm:w-2/5">
|
||||||
|
<img src="/img/logos/icon_remotestorage.svg" class="inline-block h-6 w-6 mr-1">
|
||||||
|
<input type="text" id="user_address" class="grow"
|
||||||
|
value=<%= current_user.address %> disabled="disabled"
|
||||||
|
data-clipboard-target="source" />
|
||||||
|
<button id="copy-user-address" class="btn-md btn-icon btn-outline shrink-0"
|
||||||
|
data-clipboard-target="trigger" data-action="clipboard#copy"
|
||||||
|
title="Copy to clipboard">
|
||||||
|
<span class="content-initial">
|
||||||
|
<%= render partial: "icons/copy", locals: { custom_class: "text-blue-600 h-4 w-4 inline" } %>
|
||||||
|
</span>
|
||||||
|
<span class="content-active hidden">
|
||||||
|
<%= render partial: "icons/check", locals: { custom_class: "text-blue-600 h-4 w-4 inline" } %>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h3>Compatible Apps</h3>
|
||||||
|
<p>
|
||||||
|
Your Storage account is based on a new open standard called
|
||||||
|
<a href="https://remotestorage.io" target="_blank">
|
||||||
|
<img src="/img/logos/icon_remotestorage.svg" class="h-4 w-4 inline">
|
||||||
|
<strong>remoteStorage</strong>
|
||||||
|
</a>, which is not yet widely supported. Look
|
||||||
|
for the remoteStorage icon, or check the Sync settings in apps.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
If you want your favorite apps to support syncing data with your own
|
||||||
|
Storage account, let the developers know! All relevant information is
|
||||||
|
available on the <a href="https://remotestorage.io"
|
||||||
|
target="_blank" class="ks-text-link">remoteStorage website</a>.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h3>Recommended Apps</h3>
|
||||||
|
<div data-controller="tabs"
|
||||||
|
data-tabs-active-tab-class="-mb-px border-gray-200 border-l border-t border-r rounded-t text-indigo-600 hover:text-indigo-600"
|
||||||
|
data-tabs-inactive-tab-class="text-gray-500 hover:text-gray-700"
|
||||||
|
class="mb-12">
|
||||||
|
<select data-action="tabs#change" data-tabs-target="select"
|
||||||
|
class="block w-full mb-8 sm:hidden">
|
||||||
|
<option>Productivity</option>
|
||||||
|
<option>Bookmarks</option>
|
||||||
|
<option>Reading</option>
|
||||||
|
<option>File sharing</option>
|
||||||
|
<option>Learning</option>
|
||||||
|
</select>
|
||||||
|
<ul class="hidden sm:flex list-reset mb-8 border-gray-200 border-b">
|
||||||
|
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
|
||||||
|
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
|
||||||
|
Productivity
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
|
||||||
|
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
|
||||||
|
Bookmarks
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
|
||||||
|
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
|
||||||
|
Reading
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
|
||||||
|
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
|
||||||
|
File sharing
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
|
||||||
|
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
|
||||||
|
Learning
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="hidden grid grid-cols-1 gap-6" data-tabs-target="panel">
|
||||||
|
<%= render AppInfoComponent.new(
|
||||||
|
name: "Hyperdraft",
|
||||||
|
description: "Create text notes and (optionally) turn them into a website",
|
||||||
|
icon_path: "/img/app_icons/hyperdraft.png",
|
||||||
|
links: [
|
||||||
|
["Website", "https://hyperdraft.rosano.ca"],
|
||||||
|
]
|
||||||
|
) %>
|
||||||
|
<%= render AppInfoComponent.new(
|
||||||
|
name: "Notes Together",
|
||||||
|
description: "A powerful note-taking app, with support for attaching images and other files",
|
||||||
|
icon_path: "/img/app_icons/notes-together.png",
|
||||||
|
links: [
|
||||||
|
["Web App", "https://notestogether.hominidsoftware.com"],
|
||||||
|
]
|
||||||
|
) %>
|
||||||
|
<%= render AppInfoComponent.new(
|
||||||
|
name: "Papiers",
|
||||||
|
description: "A simple note-taking app",
|
||||||
|
icon_path: "/img/app_icons/papiers.png",
|
||||||
|
links: [
|
||||||
|
["Web App", "https://papiers.gitlab.io"],
|
||||||
|
]
|
||||||
|
) %>
|
||||||
|
</div>
|
||||||
|
<div class="hidden grid grid-cols-1 gap-6" data-tabs-target="panel">
|
||||||
|
<%= render AppInfoComponent.new(
|
||||||
|
name: "Webmarks",
|
||||||
|
description: "Archive your bookmarks in your remote storage",
|
||||||
|
icon_path: "/img/app_icons/webmarks.png",
|
||||||
|
links: [
|
||||||
|
["Web App", "https://webmarks.5apps.com"],
|
||||||
|
]
|
||||||
|
) %>
|
||||||
|
</div>
|
||||||
|
<div class="hidden grid grid-cols-1 gap-6" data-tabs-target="panel">
|
||||||
|
<%= render AppInfoComponent.new(
|
||||||
|
name: "Pétrolette",
|
||||||
|
description: "A news aggregator that syncs with your remote storage",
|
||||||
|
icon_path: "/img/app_icons/petrolette.png",
|
||||||
|
links: [
|
||||||
|
["Web App", "https://petrolette.space"],
|
||||||
|
]
|
||||||
|
) %>
|
||||||
|
</div>
|
||||||
|
<div class="hidden grid grid-cols-1 gap-6" data-tabs-target="panel">
|
||||||
|
<%= render AppInfoComponent.new(
|
||||||
|
name: "Sharesome",
|
||||||
|
description: "Quickly and easily share files from your remote storage",
|
||||||
|
icon_path: "/img/app_icons/sharesome.png",
|
||||||
|
links: [
|
||||||
|
["Web App", "https://sharesome.5apps.com"],
|
||||||
|
]
|
||||||
|
) %>
|
||||||
|
</div>
|
||||||
|
<div class="hidden grid grid-cols-1 gap-6" data-tabs-target="panel">
|
||||||
|
<%= render AppInfoComponent.new(
|
||||||
|
name: "Kommit",
|
||||||
|
description: "Create flashcards and learn them with spaced-repetition",
|
||||||
|
icon_path: "/img/app_icons/kommit.png",
|
||||||
|
links: [
|
||||||
|
["Website", "https://kommit.rosano.ca"],
|
||||||
|
]
|
||||||
|
) %>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
|
||||||
<p>No apps connected yet.</p>
|
|
||||||
<% end %>
|
|
||||||
</section>
|
</section>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
33
app/views/services/rs_auths/index.html.erb
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<%= render HeaderComponent.new(title: "Storage") %>
|
||||||
|
|
||||||
|
<%= render MainSimpleComponent.new do %>
|
||||||
|
<section>
|
||||||
|
<p class="mb-6">
|
||||||
|
Store and synchronize your app data across different devices.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<%= render partial: "shared/tabnav_remotestorage" %>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<% if @rs_auths.any? %>
|
||||||
|
<div class="w-full grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-y-10 gap-x-12 mt-4">
|
||||||
|
<% @rs_auths.each do |auth| %>
|
||||||
|
<%= render RsAuthComponent.new(auth: auth) %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<% else %>
|
||||||
|
<div class="text-center">
|
||||||
|
<p class="mt-4 mb-12 inline-flex align-center items-center">
|
||||||
|
<%= image_tag("/img/illustrations/undraw_friends_r511.svg", class: 'h-48') %>
|
||||||
|
</p>
|
||||||
|
<h3>
|
||||||
|
No apps connected
|
||||||
|
</h3>
|
||||||
|
<p class="text-gray-500">
|
||||||
|
When connected, your apps will show up here.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</section>
|
||||||
|
<% end %>
|
||||||
@@ -1,47 +1,43 @@
|
|||||||
<section>
|
<div data-controller="settings--nostr-pubkey"
|
||||||
<h3>Nostr</h3>
|
data-settings--nostr-pubkey-user-address-value="<%= current_user.address %>"
|
||||||
<h4 class="mb-0">Public Key</h4>
|
data-settings--nostr-pubkey-site-value="<%= Setting.accounts_domain %>"
|
||||||
<div data-controller="settings--nostr-pubkey"
|
data-settings--nostr-pubkey-shared-secret-value="<%= session[:shared_secret] %>"
|
||||||
data-settings--nostr-pubkey-user-address-value="<%= current_user.address %>"
|
data-settings--nostr-pubkey-pubkey-hex-value="<%= current_user.nostr_pubkey %>">
|
||||||
data-settings--nostr-pubkey-site-value="<%= Setting.accounts_domain %>"
|
<section class="mb-8 sm:mb-12">
|
||||||
data-settings--nostr-pubkey-shared-secret-value="<%= session[:shared_secret] %>"
|
<h3>Nostr</h3>
|
||||||
data-settings--nostr-pubkey-pubkey-hex-value="<%= current_user.nostr_pubkey %>">
|
<h4 class="mb-0">
|
||||||
|
Public Key
|
||||||
<p class="<%= current_user.nostr_pubkey.present? ? '' : 'hidden' %> mt-2 flex gap-1">
|
</h4>
|
||||||
|
<p class="<%= current_user.nostr_pubkey.present? ? '' : 'hidden' %> mt-2 flex gap-x-1">
|
||||||
<input type="text" value="<%= current_user.nostr_pubkey_bech32 %>" disabled
|
<input type="text" value="<%= current_user.nostr_pubkey_bech32 %>" disabled
|
||||||
data-settings--nostr-pubkey-target="pubkeyBech32Input"
|
data-settings--nostr-pubkey-target="pubkeyBech32Input"
|
||||||
name="nostr_public_key" class="relative grow" />
|
name="nostr_public_key" class="w-full" />
|
||||||
<%= link_to nostr_pubkey_settings_path,
|
<%= link_to nostr_pubkey_settings_path,
|
||||||
class: 'btn-md btn-outline text-red-700 relative shrink-0',
|
class: 'btn-md btn-outline relative grow-0 shrink-0 text-red-700',
|
||||||
data: { turbo_method: :delete, turbo_confirm: 'Are you sure?' } do %>
|
data: { turbo_method: :delete, turbo_confirm: 'Are you sure?' } do %>
|
||||||
Remove
|
Remove
|
||||||
<% end %>
|
<% end %>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<% if current_user.nostr_pubkey.present? %>
|
<% if current_user.nostr_pubkey.present? %>
|
||||||
<div class="rounded-md bg-blue-50 p-4">
|
<!-- <div> -->
|
||||||
<div class="flex">
|
<!-- Pubkey present -->
|
||||||
<div class="flex-shrink-0">
|
<!-- </div> -->
|
||||||
<svg class="h-5 w-5 text-blue-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
|
||||||
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z" clip-rule="evenodd" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="ml-3 flex-1">
|
|
||||||
<p class="text-sm text-blue-800">
|
|
||||||
Your user address <strong><%= current_user.address %></strong> is
|
|
||||||
also a Nostr address now. Use your favorite Nostr app, or for
|
|
||||||
example <a href="http://metadata.nostr.com" target="_blank"
|
|
||||||
class="underline">metadata.nostr.com</a>, to add this
|
|
||||||
<strong>NIP-05</strong> address to your public profile.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<% else %>
|
<% else %>
|
||||||
<p class="my-4">
|
<p class="my-4">
|
||||||
If you use any apps on the Nostr network, you can verify your public key
|
Verify your Nostr public key with us in order to enable Nostr-specific
|
||||||
with us in order to enable Nostr-specific features for your account.
|
features for your account:
|
||||||
</p>
|
</p>
|
||||||
|
<ul class="list-disc list-inside">
|
||||||
|
<li>Log in with Nostr (no password needed)</li>
|
||||||
|
<li>Verified Nostr address</li>
|
||||||
|
<% if Setting.lndhub_enabled? %>
|
||||||
|
<li>Receive zaps in your Lightning account</li>
|
||||||
|
<% end %>
|
||||||
|
<% if Setting.nostr_relay_url.present? %>
|
||||||
|
<li>Publish notes on <%= link_to "our relay", Setting.nostr_relay_url_http, class: "ks-text-link", target: "_blank" %></li>
|
||||||
|
<% end %>
|
||||||
|
</ul>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div data-settings--nostr-pubkey-target="noExtension"
|
<div data-settings--nostr-pubkey-target="noExtension"
|
||||||
@@ -58,8 +54,8 @@
|
|||||||
</h3>
|
</h3>
|
||||||
<div class="mt-2 mb-0 text-sm text-blue-800">
|
<div class="mt-2 mb-0 text-sm text-blue-800">
|
||||||
<p>
|
<p>
|
||||||
We recommend Alby, which you can also use for your Lightning
|
We recommend Alby, which you can also use a wallet for your
|
||||||
Wallet.
|
Lightning account.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
@@ -86,5 +82,11 @@
|
|||||||
</button>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</section>
|
||||||
</section>
|
|
||||||
|
<% if current_user.nostr_pubkey.present? %>
|
||||||
|
<%= turbo_frame_tag "nostr_user_metadata", src: nostr_user_metadata_settings_path do %>
|
||||||
|
<p>Loading...</p>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
|||||||
27
app/views/settings/_nostr_user_metadata.html.erb
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<%= turbo_frame_tag "nostr_user_metadata" do %>
|
||||||
|
<section>
|
||||||
|
<h3>Relays</h3>
|
||||||
|
<%= render Settings::NostrRelayStatusComponent.new(
|
||||||
|
nip65_event: @nip65_event
|
||||||
|
) %>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h3>Profile</h3>
|
||||||
|
<%= render Settings::NostrProfileStatusComponent.new(
|
||||||
|
profile_event: @profile,
|
||||||
|
user_address: current_user.address
|
||||||
|
) %>
|
||||||
|
<div class="mt-8" data-controller="modal" data-action="keydown.esc->modal#close">
|
||||||
|
<button data-action="click->modal#open" class="btn-md btn-blue w-full sm:w-auto">
|
||||||
|
Edit profile
|
||||||
|
</button>
|
||||||
|
<%= render ModalComponent.new(show_close_button: false) do %>
|
||||||
|
<%= render Settings::NostrEditProfileComponent.new(
|
||||||
|
user: current_user,
|
||||||
|
profile_event: @profile
|
||||||
|
) %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<% end %>
|
||||||
@@ -19,12 +19,6 @@
|
|||||||
active: @settings_section.to_s == "email"
|
active: @settings_section.to_s == "email"
|
||||||
) %>
|
) %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if Setting.lndhub_enabled %>
|
|
||||||
<%= render SidenavLinkComponent.new(
|
|
||||||
name: "Lightning", path: setting_path(:lightning), icon: "zap",
|
|
||||||
active: @settings_section.to_s == "lightning"
|
|
||||||
) %>
|
|
||||||
<% end %>
|
|
||||||
<% if Setting.remotestorage_enabled? &&
|
<% if Setting.remotestorage_enabled? &&
|
||||||
Flipper.enabled?(:remotestorage, current_user) %>
|
Flipper.enabled?(:remotestorage, current_user) %>
|
||||||
<%= render SidenavLinkComponent.new(
|
<%= render SidenavLinkComponent.new(
|
||||||
@@ -32,6 +26,12 @@
|
|||||||
active: @settings_section.to_s == "remotestorage"
|
active: @settings_section.to_s == "remotestorage"
|
||||||
) %>
|
) %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<% if Setting.lndhub_enabled %>
|
||||||
|
<%= render SidenavLinkComponent.new(
|
||||||
|
name: "Lightning", path: setting_path(:lightning), icon: "zap",
|
||||||
|
active: @settings_section.to_s == "lightning"
|
||||||
|
) %>
|
||||||
|
<% end %>
|
||||||
<% if Setting.nostr_enabled %>
|
<% if Setting.nostr_enabled %>
|
||||||
<%= render SidenavLinkComponent.new(
|
<%= render SidenavLinkComponent.new(
|
||||||
name: "Nostr", path: setting_path(:nostr), icon: "nostrich-head",
|
name: "Nostr", path: setting_path(:nostr), icon: "nostrich-head",
|
||||||
|
|||||||
14
app/views/shared/_tabnav_remotestorage.html.erb
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<section>
|
||||||
|
<div class="border-b border-gray-200">
|
||||||
|
<nav class="-mb-px flex" aria-label="Tabs">
|
||||||
|
<%= render TabnavLinkComponent.new(
|
||||||
|
name: "Info", path: services_storage_path,
|
||||||
|
active: current_page?(services_storage_path)
|
||||||
|
) %>
|
||||||
|
<%= render TabnavLinkComponent.new(
|
||||||
|
name: "Connected Apps", path: apps_services_storage_path,
|
||||||
|
active: current_page?(apps_services_storage_path)
|
||||||
|
) %>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
3
app/views/shared/nostr/_edit_user_profile.html.erb
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<div>
|
||||||
|
<%= profile.inspect %>
|
||||||
|
</div>
|
||||||
2
config/initializers/service_details.rb
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
config_path = Rails.root.join('config', 'services.yml')
|
||||||
|
SERVICES = YAML.load_file(config_path).deep_symbolize_keys.with_indifferent_access
|
||||||
@@ -48,7 +48,8 @@ Rails.application.routes.draw do
|
|||||||
end
|
end
|
||||||
|
|
||||||
resource :storage, controller: 'remotestorage', only: [:show] do
|
resource :storage, controller: 'remotestorage', only: [:show] do
|
||||||
resources :rs_auths, only: [:destroy] do
|
get :apps, to: "rs_auths#index"
|
||||||
|
resources :rs_auths, only: [:index, :destroy] do
|
||||||
member do
|
member do
|
||||||
get :revoke, to: 'rs_auths#destroy'
|
get :revoke, to: 'rs_auths#destroy'
|
||||||
get :launch_app
|
get :launch_app
|
||||||
@@ -64,6 +65,7 @@ Rails.application.routes.draw do
|
|||||||
post 'reset_email_password'
|
post 'reset_email_password'
|
||||||
post 'set_nostr_pubkey'
|
post 'set_nostr_pubkey'
|
||||||
delete 'nostr_pubkey', to: 'settings#remove_nostr_pubkey'
|
delete 'nostr_pubkey', to: 'settings#remove_nostr_pubkey'
|
||||||
|
get 'fetch_nostr_user_metadata', as: 'nostr_user_metadata'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
30
config/services.yml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
internal:
|
||||||
|
btcpay:
|
||||||
|
name: BTCPay Server
|
||||||
|
postgres:
|
||||||
|
name: PostgreSQL
|
||||||
|
sentry:
|
||||||
|
name: Sentry
|
||||||
|
external:
|
||||||
|
discourse:
|
||||||
|
name: Discourse
|
||||||
|
droneci:
|
||||||
|
name: Drone CI
|
||||||
|
ejabberd:
|
||||||
|
display_name: Chat
|
||||||
|
email:
|
||||||
|
name: E-Mail
|
||||||
|
gitea:
|
||||||
|
name: Gitea
|
||||||
|
lndhub:
|
||||||
|
name: LNDHub
|
||||||
|
display_name: Lightning Network
|
||||||
|
mastodon:
|
||||||
|
name: Mastodon
|
||||||
|
mediawiki:
|
||||||
|
name: MediaWiki
|
||||||
|
nostr:
|
||||||
|
name: Nostr
|
||||||
|
remotestorage:
|
||||||
|
name: remoteStorage
|
||||||
|
display_name: Storage
|
||||||
@@ -47,6 +47,9 @@ services:
|
|||||||
RS_REDIS_URL: redis://redis:6379/1
|
RS_REDIS_URL: redis://redis:6379/1
|
||||||
RS_STORAGE_URL: "http://localhost:4567"
|
RS_STORAGE_URL: "http://localhost:4567"
|
||||||
S3_ENABLED: false
|
S3_ENABLED: false
|
||||||
|
NOSTR_PUBLIC_KEY: bdd76ce2934b2f591f9fad2ebe9da18f20d2921de527494ba00eeaa0a0efadcf
|
||||||
|
NOSTR_PRIVATE_KEY: 7c3ef7e448505f0615137af38569d01807d3b05b5005d5ecf8aaafcd40323cea
|
||||||
|
NOSTR_RELAY_URL: "ws://strfry:7777"
|
||||||
depends_on:
|
depends_on:
|
||||||
- ldap
|
- ldap
|
||||||
- redis
|
- redis
|
||||||
@@ -111,9 +114,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
|
||||||
@@ -125,6 +126,7 @@ services:
|
|||||||
LDAP_BIND_DN: 'cn=Directory Manager'
|
LDAP_BIND_DN: 'cn=Directory Manager'
|
||||||
LDAP_PASSWORD: passthebutter
|
LDAP_PASSWORD: passthebutter
|
||||||
LDAP_SEARCH_DN: 'ou=kosmos.org,cn=users,dc=kosmos,dc=org'
|
LDAP_SEARCH_DN: 'ou=kosmos.org,cn=users,dc=kosmos,dc=org'
|
||||||
|
WHITELIST_PUBKEYS: 'bdd76ce2934b2f591f9fad2ebe9da18f20d2921de527494ba00eeaa0a0efadcf'
|
||||||
|
|
||||||
# phpldapadmin:
|
# phpldapadmin:
|
||||||
# image: osixia/phpldapadmin:0.9.0
|
# image: osixia/phpldapadmin:0.9.0
|
||||||
|
|||||||
@@ -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
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"imports": {
|
||||||
|
"@nostr/tools": "jsr:@nostr/tools@^2.3.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
196
extras/strfry/deno.lock
generated
Normal file
@@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,18 +1,54 @@
|
|||||||
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 { Client } from 'npm:ldapts';
|
||||||
|
import { nip57 } from '@nostr/tools';
|
||||||
|
|
||||||
interface LdapConfig {
|
interface LdapConfig {
|
||||||
url: string;
|
url: string;
|
||||||
bindDN: string;
|
bindDN: string;
|
||||||
password: string;
|
password: string;
|
||||||
searchDN: string;
|
searchDN: string;
|
||||||
|
whitelistPubkeys?: IterablePubkeys;
|
||||||
}
|
}
|
||||||
|
|
||||||
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 }
|
||||||
|
|
||||||
|
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);
|
||||||
|
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);
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
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 +56,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 {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ const ldapConfig = {
|
|||||||
bindDN: Deno.env.get("LDAP_BIND_DN"),
|
bindDN: Deno.env.get("LDAP_BIND_DN"),
|
||||||
password: Deno.env.get("LDAP_PASSWORD"),
|
password: Deno.env.get("LDAP_PASSWORD"),
|
||||||
searchDN: Deno.env.get("LDAP_SEARCH_DN"),
|
searchDN: Deno.env.get("LDAP_SEARCH_DN"),
|
||||||
|
whitelistPubkeys: Deno.env.get("WHITELIST_PUBKEYS")?.split(',')
|
||||||
}
|
}
|
||||||
|
|
||||||
for await (const msg of readStdin()) {
|
for await (const msg of readStdin()) {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
"postcss-preset-env": "^7.8.3",
|
"postcss-preset-env": "^7.8.3",
|
||||||
"tailwindcss": "^3.2.4"
|
"tailwindcss": "^3.2.4"
|
||||||
},
|
},
|
||||||
"version": "0.9.0",
|
"version": "0.10.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build:css:tailwind": "tailwindcss --postcss -i ./app/assets/stylesheets/application.tailwind.css -o ./app/assets/builds/application.css",
|
"build:css:tailwind": "tailwindcss --postcss -i ./app/assets/stylesheets/application.tailwind.css -o ./app/assets/builds/application.css",
|
||||||
"build:css": "yarn run build:css:tailwind"
|
"build:css": "yarn run build:css:tailwind"
|
||||||
|
|||||||
BIN
public/img/app_icons/hyperdraft.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
public/img/app_icons/kommit.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
public/img/app_icons/notes-together.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
public/img/app_icons/papiers.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
public/img/app_icons/petrolette.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
public/img/app_icons/sharesome.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
public/img/app_icons/webmarks.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
1
public/img/illustrations/undraw_friends_r511.svg
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
public/img/logos/icon_geary.png
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
11
public/img/logos/icon_mail.svg
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg version="1.1" viewBox="-5 -10 110 135" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="m63.57 53.199-5.5742 5.5742c-2.1211 2.1211-4.9961 3.3125-7.9961 3.3125s-5.875-1.1914-8-3.3125l-5.5742-5.5742-30.066 30.066c0.90625 0.43359 1.9023 0.66406 2.9258 0.66406h81.43c1.0234 0 2.0195-0.23047 2.9258-0.66406z"
|
||||||
|
style="fill:#4f97ef;fill-opacity:1" />
|
||||||
|
<path d="m96.836 19.934c0.43359 0.90234 0.66406 1.9023 0.66406 2.9219v54.285c0 1.0234-0.23047 2.0195-0.66406 2.9258l-30.066-30.066z"
|
||||||
|
style="fill:#4f97ef;fill-opacity:1" />
|
||||||
|
<path d="m3.1641 19.934 30.066 30.066-30.066 30.066c-0.43359-0.90625-0.66406-1.9023-0.66406-2.9258v-54.285c0-1.0195 0.23047-2.0195 0.66406-2.9219z"
|
||||||
|
style="fill:#4f97ef;fill-opacity:1" />
|
||||||
|
<path d="m93.641 16.734c-0.90625-0.43359-1.9023-0.66406-2.9258-0.66406h-81.43c-1.0234 0-2.0195 0.23047-2.9258 0.66406l38.84 38.84c1.2734 1.2734 3 1.9883 4.8008 1.9883s3.5273-0.71484 4.7969-1.9883z"
|
||||||
|
style="fill:#4f97ef;fill-opacity:1" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1018 B |
18
public/img/logos/icon_remotestorage.svg
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<!-- Creator: CorelDRAW X7 -->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="0.739008in" height="0.853339in" version="1.1" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
|
||||||
|
viewBox="0 0 739 853"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<defs>
|
||||||
|
<style type="text/css">
|
||||||
|
<![CDATA[
|
||||||
|
.fil0 {fill:#FF4B03}
|
||||||
|
]]>
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<g id="Layer_x0020_1">
|
||||||
|
<metadata id="CorelCorpID_0Corel-Layer"/>
|
||||||
|
<polygon class="fil0" points="370,754 0,542 0,640 185,747 370,853 554,747 739,640 739,525 739,525 739,476 739,427 739,378 653,427 370,589 86,427 86,427 86,361 185,418 370,524 554,418 653,361 739,311 739,213 739,213 554,107 370,0 185,107 58,180 144,230 228,181 370,100 511,181 652,263 370,425 87,263 87,263 0,213 0,213 0,311 0,378 0,427 0,476 86,525 185,582 370,689 554,582 653,525 653,590 653,592 "/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
25
spec/helpers/services_helper_spec.rb
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
describe ServicesHelper do
|
||||||
|
|
||||||
|
describe "#service_human_name" do
|
||||||
|
it "returns the human name when it's configured" do
|
||||||
|
expect(service_human_name("mastodon")).to eq("Mastodon")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns the key when there is no human name" do
|
||||||
|
expect(service_human_name("ejabberd")).to eq("ejabberd")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#service_display_name" do
|
||||||
|
it "returns the display name when it's configured" do
|
||||||
|
expect(service_display_name("lndhub")).to eq("Lightning Network")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns the human name when there is no display name" do
|
||||||
|
expect(service_display_name("mastodon")).to eq("Mastodon")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
@@ -44,7 +44,7 @@ RSpec.describe CreateLdapUserJob, type: :job do
|
|||||||
|
|
||||||
it "adds default services for pre-confirmed accounts" do
|
it "adds default services for pre-confirmed accounts" do
|
||||||
allow(ldap_client_mock).to receive(:add) # spy on mock
|
allow(ldap_client_mock).to receive(:add) # spy on mock
|
||||||
allow(Setting).to receive(:default_services).and_return(["xmpp", "discourse"])
|
Setting.default_services = ["ejabberd", "discourse"]
|
||||||
|
|
||||||
perform_enqueued_jobs { job_for_preconfirmed_account }
|
perform_enqueued_jobs { job_for_preconfirmed_account }
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ RSpec.describe CreateLdapUserJob, type: :job do
|
|||||||
sn: "halfinney",
|
sn: "halfinney",
|
||||||
uid: "halfinney",
|
uid: "halfinney",
|
||||||
mail: "halfinney@example.com",
|
mail: "halfinney@example.com",
|
||||||
serviceEnabled: ["xmpp", "discourse"],
|
serviceEnabled: ["ejabberd", "discourse"],
|
||||||
userPassword: "remember-remember-the-5th-of-november"
|
userPassword: "remember-remember-the-5th-of-november"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ RSpec.describe XmppExchangeContactsJob, type: :job do
|
|||||||
before do
|
before do
|
||||||
stub_request(:post, "http://xmpp.example.com/api/add_rosteritem")
|
stub_request(:post, "http://xmpp.example.com/api/add_rosteritem")
|
||||||
.to_return(status: 200, body: "", headers: {})
|
.to_return(status: 200, body: "", headers: {})
|
||||||
allow_any_instance_of(User).to receive(:services_enabled).and_return(["xmpp"])
|
allow_any_instance_of(User).to receive(:services_enabled).and_return(["ejabberd"])
|
||||||
end
|
end
|
||||||
|
|
||||||
it "posts add_rosteritem commands to the ejabberd API" do
|
it "posts add_rosteritem commands to the ejabberd API" do
|
||||||
|
|||||||
25
spec/models/setting_spec.rb
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe Setting, type: :model do
|
||||||
|
|
||||||
|
describe ".available_services" do
|
||||||
|
before do
|
||||||
|
Setting.discourse_enabled = true
|
||||||
|
Setting.ejabberd_enabled = true
|
||||||
|
Setting.email_enabled = false
|
||||||
|
Setting.gitea_enabled = false
|
||||||
|
Setting.lndhub_enabled = true
|
||||||
|
Setting.mastodon_enabled = true
|
||||||
|
Setting.mediawiki_enabled = false
|
||||||
|
Setting.nostr_enabled = false
|
||||||
|
Setting.remotestorage_enabled = true
|
||||||
|
end
|
||||||
|
|
||||||
|
it "contains all enabled services" do
|
||||||
|
expect(Setting.available_services).to eq(%w[
|
||||||
|
discourse ejabberd lndhub mastodon remotestorage
|
||||||
|
])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
@@ -78,9 +78,9 @@ RSpec.describe User, type: :model do
|
|||||||
it "returns the entries from the LDAP service attribute" do
|
it "returns the entries from the LDAP service attribute" do
|
||||||
expect(user).to receive(:ldap_entry).and_return({
|
expect(user).to receive(:ldap_entry).and_return({
|
||||||
uid: user.cn, ou: user.ou, mail: user.email, admin: nil,
|
uid: user.cn, ou: user.ou, mail: user.email, admin: nil,
|
||||||
services_enabled: ["discourse", "email", "gitea", "wiki", "xmpp"]
|
services_enabled: ["discourse", "ejabberd", "email", "gitea", "wiki"]
|
||||||
})
|
})
|
||||||
expect(user.services_enabled).to eq(["discourse", "email", "gitea", "wiki", "xmpp"])
|
expect(user.services_enabled).to eq(["discourse", "ejabberd", "email", "gitea", "wiki"])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -88,7 +88,7 @@ RSpec.describe User, type: :model do
|
|||||||
before do
|
before do
|
||||||
allow(user).to receive(:ldap_entry).and_return({
|
allow(user).to receive(:ldap_entry).and_return({
|
||||||
uid: user.cn, ou: user.ou, mail: user.email, admin: nil,
|
uid: user.cn, ou: user.ou, mail: user.email, admin: nil,
|
||||||
services_enabled: ["gitea", "xmpp"]
|
services_enabled: ["ejabberd", "gitea"]
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -121,9 +121,9 @@ RSpec.describe User, type: :model do
|
|||||||
|
|
||||||
it "adds multiple service to the LDAP entry" do
|
it "adds multiple service to the LDAP entry" do
|
||||||
expect_any_instance_of(LdapService).to receive(:replace_attribute)
|
expect_any_instance_of(LdapService).to receive(:replace_attribute)
|
||||||
.with(dn, :serviceEnabled, ["discourse", "gitea", "wiki", "xmpp"]).and_return(true)
|
.with(dn, :serviceEnabled, ["discourse", "ejabberd", "gitea", "wiki"]).and_return(true)
|
||||||
|
|
||||||
user.enable_service([:wiki, :xmpp])
|
user.enable_service([:ejabberd, :wiki])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -131,7 +131,7 @@ RSpec.describe User, type: :model do
|
|||||||
before do
|
before do
|
||||||
allow(user).to receive(:ldap_entry).and_return({
|
allow(user).to receive(:ldap_entry).and_return({
|
||||||
uid: user.cn, ou: user.ou, mail: user.email, admin: nil,
|
uid: user.cn, ou: user.ou, mail: user.email, admin: nil,
|
||||||
services_enabled: ["discourse", "gitea", "xmpp"]
|
services_enabled: ["discourse", "ejabberd", "gitea"]
|
||||||
})
|
})
|
||||||
allow(user).to receive(:dn).and_return(dn)
|
allow(user).to receive(:dn).and_return(dn)
|
||||||
end
|
end
|
||||||
@@ -140,14 +140,14 @@ RSpec.describe User, type: :model do
|
|||||||
expect_any_instance_of(LdapService).to receive(:replace_attribute)
|
expect_any_instance_of(LdapService).to receive(:replace_attribute)
|
||||||
.with(dn, :serviceEnabled, ["discourse", "gitea"]).and_return(true)
|
.with(dn, :serviceEnabled, ["discourse", "gitea"]).and_return(true)
|
||||||
|
|
||||||
user.disable_service(:xmpp)
|
user.disable_service(:ejabberd)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "removes multiple services from the LDAP entry" do
|
it "removes multiple services from the LDAP entry" do
|
||||||
expect_any_instance_of(LdapService).to receive(:replace_attribute)
|
expect_any_instance_of(LdapService).to receive(:replace_attribute)
|
||||||
.with(dn, :serviceEnabled, ["discourse"]).and_return(true)
|
.with(dn, :serviceEnabled, ["discourse"]).and_return(true)
|
||||||
|
|
||||||
user.disable_service([:xmpp, "gitea"])
|
user.disable_service([:ejabberd, "gitea"])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -178,7 +178,7 @@ RSpec.describe User, type: :model do
|
|||||||
after { clear_enqueued_jobs }
|
after { clear_enqueued_jobs }
|
||||||
|
|
||||||
it "enables default services" do
|
it "enables default services" do
|
||||||
expect(user).to receive(:enable_service).with(%w[ discourse gitea mastodon mediawiki xmpp ])
|
expect(user).to receive(:enable_service).with(Setting.default_services)
|
||||||
user.send :devise_after_confirmation
|
user.send :devise_after_confirmation
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ RSpec.describe "WebFinger", type: :request do
|
|||||||
before do
|
before do
|
||||||
allow_any_instance_of(User).to receive(:ldap_entry).and_return({
|
allow_any_instance_of(User).to receive(:ldap_entry).and_return({
|
||||||
uid: user.cn, ou: user.ou, mail: user.email, admin: nil,
|
uid: user.cn, ou: user.ou, mail: user.email, admin: nil,
|
||||||
services_enabled: ["xmpp"]
|
services_enabled: ["ejabberd"]
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -92,7 +92,13 @@ RSpec.describe "WebFinger", type: :request do
|
|||||||
expect(rs_link["href"]).to eql("#{Setting.rs_storage_url}/tony")
|
expect(rs_link["href"]).to eql("#{Setting.rs_storage_url}/tony")
|
||||||
|
|
||||||
oauth_url = rs_link["properties"]["http://tools.ietf.org/html/rfc6749#section-4.2"]
|
oauth_url = rs_link["properties"]["http://tools.ietf.org/html/rfc6749#section-4.2"]
|
||||||
expect(oauth_url).to eql("http://www.example.com/rs/oauth/tony")
|
expect(oauth_url).to eql("http://accounts.kosmos.org/rs/oauth/tony")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns CORS headers" do
|
||||||
|
get "/.well-known/nostr.json?name=bobdylan"
|
||||||
|
expect(response.headers['Access-Control-Allow-Origin']).to eq("*")
|
||||||
|
expect(response.headers['Access-Control-Allow-Methods']).to eq('GET')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -100,7 +106,7 @@ RSpec.describe "WebFinger", type: :request do
|
|||||||
before do
|
before do
|
||||||
allow_any_instance_of(User).to receive(:ldap_entry).and_return({
|
allow_any_instance_of(User).to receive(:ldap_entry).and_return({
|
||||||
uid: user.cn, ou: user.ou, mail: user.email, admin: nil,
|
uid: user.cn, ou: user.ou, mail: user.email, admin: nil,
|
||||||
services_enabled: ["xmpp"]
|
services_enabled: ["ejabberd"]
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -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,95 @@ 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
|
||||||
|
|
||||||
|
it "returns CORS headers" do
|
||||||
|
get "/.well-known/nostr.json?name=bobdylan"
|
||||||
|
expect(response.headers['Access-Control-Allow-Origin']).to eq("*")
|
||||||
|
expect(response.headers['Access-Control-Allow-Methods']).to eq('GET')
|
||||||
|
end
|
||||||
|
|
||||||
|
context "without relay configured" do
|
||||||
|
before do
|
||||||
|
Setting.nostr_relay_url = ""
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
||||||
|
describe "for primary domain" do
|
||||||
|
context "no different pubkey configured for primary domain" do
|
||||||
|
it "returns the akkounts nostr pubkey" do
|
||||||
|
get "/.well-known/nostr.json?name=_"
|
||||||
|
res = JSON.parse(response.body)
|
||||||
|
expect(res["names"]["_"]).to eq("bdd76ce2934b2f591f9fad2ebe9da18f20d2921de527494ba00eeaa0a0efadcf")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "different pubkey configured for primary domain" do
|
||||||
|
before do
|
||||||
|
Setting.nostr_public_key_primary_domain = "b3e8f62fbe41217ffc0aa1e178d297339932d8ba4f46d9c7df3b61575e78fecc"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns the primary domain's nostr pubkey" do
|
||||||
|
get "/.well-known/nostr.json?name=_"
|
||||||
|
res = JSON.parse(response.body)
|
||||||
|
expect(res["names"]["_"]).to eq("b3e8f62fbe41217ffc0aa1e178d297339932d8ba4f46d9c7df3b61575e78fecc")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "for akkounts domain" do
|
||||||
|
it "returns the configured nostr pubkey" do
|
||||||
|
headers = { "X-Forwarded-Host" => "accounts.kosmos.org" }
|
||||||
|
get "/.well-known/nostr.json?name=_"
|
||||||
|
|
||||||
|
res = JSON.parse(response.body)
|
||||||
|
expect(res["names"]["_"]).to eq("bdd76ce2934b2f591f9fad2ebe9da18f20d2921de527494ba00eeaa0a0efadcf")
|
||||||
|
end
|
||||||
|
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
|
||||||
|
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
|
||||||
|
|||||||