3 Commits

Author SHA1 Message Date
f05eec5255 Merge branch 'feature/170-nostr_zaps' into live
All checks were successful
continuous-integration/drone/push Build is passing
2024-05-19 16:48:28 +02:00
66ca2dc6b0 Merge branch 'feature/173-nostr_ldap' into live
All checks were successful
continuous-integration/drone/push Build is passing
2024-05-19 13:44:24 +02:00
800183e9da Increase sidekiq concurrency in prod
All checks were successful
continuous-integration/drone/push Build is passing
2024-05-19 13:44:01 +02:00
59 changed files with 126 additions and 1000 deletions

View File

@@ -29,7 +29,6 @@
#
# Service Integrations
# (sorted alphabetically by service name)
#
# BTCPAY_PUBLIC_URL='https://btcpay.example.com'
@@ -63,9 +62,5 @@
# MEDIAWIKI_PUBLIC_URL='https://wiki.kosmos.org'
# NOSTR_PRIVATE_KEY='123456abcdef...'
# NOSTR_PUBLIC_KEY='123456abcdef...'
# NOSTR_RELAY_URL='wss://nostr.kosmos.org'
# RS_STORAGE_URL='https://storage.kosmos.org'
# RS_REDIS_URL='redis://localhost:6379/2'

View File

@@ -61,7 +61,7 @@ gem "sentry-rails"
# Services
gem 'discourse_api'
gem "lnurl"
gem 'manifique', '~> 1.1.0'
gem 'manifique'
gem 'nostr', '~> 0.6.0'
group :development, :test do

View File

@@ -245,7 +245,7 @@ GEM
net-imap
net-pop
net-smtp
manifique (1.1.0)
manifique (1.0.1)
faraday (~> 2.9.0)
faraday-follow_redirects (= 0.3.0)
nokogiri (~> 1.16.0)
@@ -515,7 +515,7 @@ DEPENDENCIES
listen (~> 3.2)
lnurl
lockbox
manifique (~> 1.1.0)
manifique
net-ldap
nostr (~> 0.6.0)
pagy (~> 6.0, >= 6.0.2)

View File

@@ -1,5 +1,5 @@
@layer components {
.services > div > a {
background-image: linear-gradient(110deg, rgba(255,255,255,0.99) 20%, rgba(255,255,255,0.88) 100%);
background-image: linear-gradient(110deg, rgba(255,255,255,0.99) 0, rgba(255,255,255,0.88) 100%);
}
}

View File

@@ -6,7 +6,6 @@
) do %>
<%= method("#{@type}_field").call :setting, @key,
value: Setting.public_send(@key),
placeholder: @placeholder,
data: {
:'default-value' => Setting.get_field(@key)[:default]
},

View File

@@ -2,7 +2,7 @@
module FormElements
class FieldsetResettableSettingComponent < ViewComponent::Base
def initialize(tag: "li", key:, type: :text, title:, description: nil, placeholder: nil)
def initialize(tag: "li", key:, type: :text, title:, description: nil)
@tag = tag
@positioning = :vertical
@title = title
@@ -10,7 +10,6 @@ module FormElements
@key = key.to_sym
@type = type
@resettable = is_resettable?(@key)
@placeholder = placeholder
end
def is_resettable?(key)

View File

@@ -6,7 +6,7 @@
<div class="flex flex-col">
<label class="font-bold mb-1"><%= @title %></label>
<% if @description.present? %>
<p class="text-gray-500"><%= @description %></p>
<p class="text-gray-500"><%= @descripton %></p>
<% end %>
</div>
<div class="relative ml-4 inline-flex flex-shrink-0">

View File

@@ -12,7 +12,7 @@ module FormElements
@enabled = enabled
@input_enabled = input_enabled
@title = title
@description = description
@descripton = description
@button_text = @enabled ? "Switch off" : "Switch on"
end
end

View File

@@ -8,7 +8,8 @@ class Services::RemotestorageController < Services::BaseController
# unless current_user.service_enabled?(:remotestorage)
# redirect_to service_remotestorage_info_path
# end
# @rs_apps_connected = current_user.remote_storage_authorizations.any?
@rs_auths = current_user.remote_storage_authorizations
# TODO sort by app name
end
private

View File

@@ -3,18 +3,13 @@ class Services::RsAuthsController < Services::BaseController
before_action :require_feature_enabled
before_action :require_service_available
# before_action :require_service_enabled
before_action :find_rs_auth, only: [:destroy, :launch_app]
def index
@rs_auths = current_user.remote_storage_authorizations
# TODO sort by app name?
end
before_action :find_rs_auth
def destroy
@auth.destroy!
respond_to do |format|
format.html do redirect_to apps_services_storage_url, flash: {
format.html do redirect_to services_storage_url, flash: {
success: 'App authorization revoked'
}
end

View File

@@ -12,11 +12,7 @@ class SettingsController < ApplicationController
end
def show
case @settings_section
when "lightning"
@notifications_enabled = @user.preferences[:lightning_notify_sats_received] != "disabled" ||
@user.preferences[:lightning_notify_zap_received] != "disabled"
when "nostr"
if @settings_section == "nostr"
session[:shared_secret] ||= SecureRandom.base64(12)
end
end
@@ -151,9 +147,11 @@ class SettingsController < ApplicationController
end
def user_params
params.require(:user).permit(
:display_name, :avatar, preferences: UserPreferences.pref_keys
)
params.require(:user).permit(:display_name, :avatar, preferences: [
:lightning_notify_sats_received,
:remotestorage_notify_auth_created,
:xmpp_exchange_contacts_with_invitees
])
end
def email_params

View File

@@ -7,15 +7,14 @@ class WebhooksController < ApplicationController
def lndhub
@user = User.find_by!(ln_account: @payload[:user_login])
if @zap = @user.zaps.find_by(payment_request: @payload[:payment_request])
settled_at = Time.parse(@payload[:settled_at])
if zap = @user.zaps.find_by(payment_request: @payload[:payment_request])
zap_receipt = NostrManager::CreateZapReceipt.call(
zap: @zap,
paid_at: settled_at.to_i,
zap: zap,
paid_at: Time.parse(@payload[:settled_at]).to_i,
preimage: @payload[:preimage]
)
@zap.update! settled_at: settled_at, receipt: zap_receipt.to_h
NostrManager::PublishZapReceipt.call(zap: @zap)
zap.update! receipt: zap_receipt.to_h
NostrManager::PublishZapReceipt.call(zap: zap)
end
send_notifications
@@ -42,16 +41,7 @@ class WebhooksController < ApplicationController
end
def send_notifications
return if @payload[:amount] < @user.preferences[:lightning_notify_min_sats]
if @user.preferences[:lightning_notify_only_with_message]
return if @payload[:memo].blank?
end
target = @zap.present? ? @user.preferences[:lightning_notify_zap_received] :
@user.preferences[:lightning_notify_sats_received]
case target
case @user.preferences[:lightning_notify_sats_received]
when "xmpp"
notify_xmpp
when "email"

View File

@@ -1,33 +1,16 @@
class WellKnownController < ApplicationController
before_action :require_nostr_enabled, only: [ :nostr ]
def nostr
http_status :unprocessable_entity and return if params[:name].blank?
domain = request.headers["X-Forwarded-Host"].presence || Setting.primary_domain
relay_url = Setting.nostr_relay_url.presence
if params[:name] == "_"
# pubkey for the primary domain without a username (e.g. kosmos.org)
res = { names: { "_": Setting.nostr_public_key } }
res[:relays] = { "_" => [ relay_url ] } if relay_url
else
@user = User.where(cn: params[:name], ou: domain).first
http_status :not_found and return if @user.nil? || @user.nostr_pubkey.blank?
res = { names: { @user.cn => @user.nostr_pubkey } }
res[:relays] = { @user.nostr_pubkey => [ relay_url ] } if relay_url
end
@user = User.where(cn: params[:name], ou: domain).first
http_status :not_found and return if @user.nil? || @user.nostr_pubkey.blank?
respond_to do |format|
format.json do
render json: res.to_json
render json: {
names: { "#{@user.cn}": @user.nostr_pubkey }
}.to_json
end
end
end
private
def require_nostr_enabled
http_status :not_found unless Setting.nostr_enabled?
end
end

View File

@@ -1,7 +1,7 @@
class NostrPublishEventJob < ApplicationJob
queue_as :nostr
def perform(event:, relay_url:)
NostrManager::PublishEvent.call(event: event, relay_url: relay_url)
def perform(event:, relay:)
NostrManager::PublishEvent.call(event: event, relay: relay)
end
end

View File

@@ -169,9 +169,6 @@ class Setting < RailsSettings::Base
field :nostr_public_key, type: :string,
default: ENV["NOSTR_PUBLIC_KEY"].presence
field :nostr_relay_url, type: :string,
default: ENV["NOSTR_RELAY_URL"].presence
field :nostr_zaps_relay_limit, type: :integer,
default: 12

View File

@@ -26,8 +26,4 @@ class UserPreferences
end
hash.stringify_keys!.to_h
end
def self.pref_keys
DEFAULT_PREFS.keys.map(&:to_sym)
end
end

View File

@@ -1,9 +1,6 @@
class Zap < ApplicationRecord
belongs_to :user
scope :settled, -> { where.not(settled_at: nil) }
scope :unpaid, -> { where(settled_at: nil) }
def request_event
nostr_event_from_hash(request)
end

View File

@@ -101,7 +101,7 @@ class LdapService < ApplicationService
dn = "ou=#{ou},cn=users,#{ldap_suffix}"
aci = <<-EOS
(target="ldap:///cn=*,ou=#{ou},cn=users,#{ldap_suffix}")(targetattr="cn || sn || uid || userPassword || mail || mailRoutingAddress || serviceEnabled || nostrKey || nsRole || objectClass") (version 3.0; acl "service-#{ou.gsub(".", "-")}-read-search"; allow (read,search) userdn="ldap:///uid=service,ou=#{ou},cn=applications,#{ldap_suffix}";)
(target="ldap:///cn=*,ou=#{ou},cn=users,#{ldap_suffix}")(targetattr="cn || sn || uid || mail || userPassword || nsRole || objectClass") (version 3.0; acl "service-#{ou.gsub(".", "-")}-read-search"; allow (read,search) userdn="ldap:///uid=service,ou=#{ou},cn=applications,#{ldap_suffix}";)
EOS
attrs = {

View File

@@ -1,50 +1,50 @@
module NostrManager
class PublishEvent < NostrManagerService
def initialize(event:, relay_url:)
relay_name = URI.parse(relay_url).host
def initialize(event: nil, relay_url: nil)
relay_name = relay_url.gsub(/^ws(s):\/\//, "")
@relay = Nostr::Relay.new(url: relay_url, name: relay_name)
if event.is_a?(Nostr::Event)
@event = event
else
@event = Nostr::Event.new(**event.symbolize_keys)
end
@event = case event.class
when Nostr::Event
event
when Hash
Nostr::Event.new(**event.symbolize_keys)
# else
# TODO
# raise NotImplementedError
end
@client = Nostr::Client.new
end
def call
client, relay, event = @client, @relay, @event
log_prefix = "[nostr][#{relay.name}]"
thread = Thread.new do
client.on :connect do
puts "#{log_prefix} Publishing #{event.id}..."
client.publish event
end
client.on :error do |e|
puts "#{log_prefix} Error: #{e}"
puts "#{log_prefix} Closing thread..."
thread.exit
end
client.on :message do |m|
puts "#{log_prefix} Message: #{m}"
msg = JSON.parse(m) rescue []
if msg[0] == "OK" && msg[1] == event.id && msg[2]
puts "#{log_prefix} Event published. Closing thread..."
else
puts "#{log_prefix} Unexpected message from relay. Closing thread..."
end
thread.exit
end
puts "#{log_prefix} Connecting to #{relay.url}..."
client.connect relay
client.on :connect do
puts "Connected to #{relay.url}"
puts "Publishing #{event.id}..."
client.publish event
end
thread.join
client.on :error do |e|
# TODO log relay URL/name
puts "Error: #{e}"
puts "Closing thread..."
Thread.exit
end
client.on :message do |m|
puts "Message: #{m}"
msg = JSON.parse(m) rescue []
if msg[0] == "OK" && msg[1] == event.id
puts "Event published. Closing thread..."
else
# TODO log relay URL/name
puts "Unexpected message from relay. Closing thread..."
end
Thread.exit
end
puts "Connecting to #{relay.url}..."
client.connect relay
end
end
end

View File

@@ -6,17 +6,12 @@ module NostrManager
def call
tags = parse_tags(@zap.request_event.tags)
relays = tags[:relays].take(Setting.nostr_zaps_relay_limit)
if Setting.nostr_relay_url.present?
relays << Setting.nostr_relay_url
end
relays.uniq.each do |relay_url|
tags[:relays].take(Setting.nostr_zaps_relay_limit).each do |relay_url|
if @delayed
NostrPublishEventJob.perform_later(event: @zap.receipt, relay_url: relay_url)
NostrPublishEventJob.perform_later(event: @zap.receipt, relay: relay_url)
else
NostrManager::PublishEvent.call(event: @zap.receipt_event, relay_url: relay_url)
NostrManager::PublishEvent.call(event: @zap.receipt_event, relay: relay_url)
end
end
end

View File

@@ -38,8 +38,8 @@
<tr>
<td class="overflow-ellipsis font-mono"><%= invitation.token %></td>
<td><%= invitation.used_at.strftime("%Y-%m-%d (%H:%M UTC)") %></td>
<td><%= link_to invitation.user.cn, admin_user_path(invitation.user.cn), class: "ks-text-link" %></td>
<td><%= link_to invitation.invitee.cn, admin_user_path(invitation.invitee.cn), class: "ks-text-link" %></td>
<td><%= link_to invitation.user.address, admin_user_path(invitation.user.address), class: "ks-text-link" %></td>
<td><%= link_to invitation.invitee.address, admin_user_path(invitation.invitee.address), class: "ks-text-link" %></td>
</tr>
<% end %>
</tbody>

View File

@@ -36,7 +36,7 @@
</td>
<td>
<% if user = @users.find{ |u| u[2] == account.login } %>
<%= link_to user[0], admin_user_path(user[0]), class: "ks-text-link" %>
<%= link_to "#{user[0]}@#{user[1]}", admin_user_path("#{user[0]}@#{user[1]}"), class: "ks-text-link" %>
<% end %>
</td>
<td><%= number_with_delimiter account.balance.to_i.to_s %></td>

View File

@@ -19,11 +19,6 @@
title: "Public key",
description: "The corresponding public key of the accounts service"
) %>
<%= render FormElements::FieldsetResettableSettingComponent.new(
key: :nostr_relay_url,
title: "Relay URL",
description: "Websockets URL of a relay associated with #{Setting.primary_domain}"
) %>
</ul>
</section>
<section>

View File

@@ -36,7 +36,7 @@
<th>Invited by</th>
<td>
<% if @user.inviter %>
<%= link_to @user.inviter.cn, admin_user_path(@user.inviter.cn), class: 'ks-text-link' %>
<%= link_to @user.inviter.address, admin_user_path(@user.inviter.address), class: 'ks-text-link' %>
<% else %>&mdash;<% end %>
</td>
</tr>
@@ -78,7 +78,7 @@
<% if @user.invitees.length > 0 %>
<ul class="mb-0">
<% @user.invitees.order(cn: :asc).each do |invitee| %>
<li class="leading-none mb-2 last:mb-0"><%= link_to invitee.cn, admin_user_path(invitee.cn), class: 'ks-text-link' %></li>
<li class="leading-none mb-2 last:mb-0"><%= link_to invitee.address, admin_user_path(invitee.address), class: 'ks-text-link' %></li>
<% end %>
</ul>
<% else %>&mdash;<% end %>
@@ -124,19 +124,6 @@
</td>
</tr>
<% 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 %>
<tr>
<td>Gitea</td>
@@ -195,33 +182,6 @@
</td>
</tr>
<% 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>
</table>
</section>

View File

@@ -43,7 +43,7 @@
</p>
<p>
We have run two 6-month trials so far, with the next trial period
starting sometime soon. Watch your email for notifications about it!
starting sometime in Q2 2024. Watch your email for notifications about it!
</p>
</section>
<% end %>

View File

@@ -5,7 +5,7 @@
<div class="services grid grid-cols-1 sm:grid-cols-2 gap-4 sm:gap-6">
<% if Setting.ejabberd_enabled? %>
<div class="border border-gray-300 rounded-md hover:border-gray-400
bg-[length:86%] bg-[center_top_-40px] bg-no-repeat
bg-cover bg-[center_top_-50px] bg-no-repeat
bg-[url(/img/logos/icon_xmpp.svg)]">
<%= link_to services_chat_path,
class: "block h-full px-6 py-6 rounded-md" do %>
@@ -18,7 +18,7 @@
<% end %>
<% if Setting.mastodon_enabled? %>
<div class="border border-gray-300 rounded-md hover:border-gray-400
bg-[length:88%] bg-[center_top_-40px] bg-no-repeat
bg-[length:80%] bg-[right_top_-30px] bg-no-repeat
bg-[url(/img/logos/icon_mastodon.svg)]">
<%= link_to services_mastodon_path, class: "block h-full px-6 py-6 rounded-md" do %>
<h3 class="mb-3.5">Mastodon</h3>
@@ -30,9 +30,7 @@
<% end %>
<% if Setting.email_enabled? &&
Flipper.enabled?(:email, current_user) %>
<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)]">
<div class="border border-gray-300 rounded-md hover:border-gray-400">
<%= link_to services_email_path, class: "block h-full px-6 py-6 rounded-md" do %>
<h3 class="mb-3.5">E-Mail</h3>
<p class="text-gray-600">
@@ -41,16 +39,15 @@
<% end %>
</div>
<% end %>
<% if Setting.remotestorage_enabled? &&
Flipper.enabled?(:remotestorage, current_user) %>
<% if Setting.discourse_enabled? %>
<div class="border border-gray-300 rounded-md hover:border-gray-400
bg-[length:80%] bg-[center_top_-156px] bg-no-repeat
bg-[url(/img/logos/icon_remotestorage.svg)]">
<%= link_to services_storage_path,
bg-[length:95%] bg-center bg-no-repeat
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 %>
<h3 class="mb-3.5">Storage</h3>
<h3 class="mb-3.5">Discourse</h3>
<p class="text-gray-600">
Sync your data between apps and devices
Kosmos community forums and user support/help site
</p>
<% end %>
</div>
@@ -68,22 +65,21 @@
<% end %>
</div>
<% end %>
<% if Setting.discourse_enabled? %>
<div class="border border-gray-300 rounded-md hover:border-gray-400
bg-[length:80%] bg-center bg-no-repeat
bg-[url(/img/logos/icon_discourse.svg)]">
<%= link_to "#{Setting.discourse_public_url}/session/sso?return_path=/",
<% if Setting.remotestorage_enabled? &&
Flipper.enabled?(:remotestorage, current_user) %>
<div class="border border-gray-300 rounded-md hover:border-gray-400">
<%= link_to services_storage_path,
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">
Community forums and support/help site
Sync your data between apps and devices
</p>
<% end %>
</div>
<% end %>
<% if Setting.gitea_enabled? %>
<div class="border border-gray-300 rounded-md hover:border-gray-400
bg-[length:92%] bg-center bg-no-repeat
bg-cover bg-center bg-no-repeat
bg-[url(/img/logos/icon_gitea.png)]">
<%= link_to Setting.gitea_public_url,
class: "block h-full px-6 py-6 rounded-md" do %>
@@ -96,7 +92,7 @@
<% end %>
<% if Setting.droneci_enabled? %>
<div class="border border-gray-300 rounded-md hover:border-gray-400
bg-[length:86%] bg-[center_top_-60px] bg-no-repeat
bg-cover bg-[center_top_-70px] bg-no-repeat
bg-[url(/img/logos/icon_droneci.svg)]">
<%= link_to Setting.droneci_public_url,
class: "block h-full px-6 py-6 rounded-md" do %>

View File

@@ -100,14 +100,6 @@
["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 id="apps-windows" class="hidden grid grid-cols-1 gap-6"
data-tabs-target="panel">

View File

@@ -2,143 +2,15 @@
<%= 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>
<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 gap-1 sm:w-2/5">
<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>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>
<h3 class="mb-10">Connected Apps</h3>
<% 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">
<% @rs_auths.each do |auth| %>
<%= render RsAuthComponent.new(auth: auth) %>
<% end %>
</div>
<% else %>
<p>No apps connected yet.</p>
<% end %>
</section>
<% end %>

View File

@@ -1,33 +0,0 @@
<%= 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 %>

View File

@@ -5,7 +5,7 @@
<%= render FormElements::FieldsetComponent.new(
positioning: :horizontal,
title: "Sats received",
description: "Notify me when sats are sent to my Lightning account"
description: "Notify me when sats are sent to my Lightning Address"
) do %>
<% f.fields_for :preferences do |p| %>
<%= p.select :lightning_notify_sats_received, options_for_select([
@@ -15,38 +15,6 @@
], selected: @user.preferences[:lightning_notify_sats_received]) %>
<% end %>
<% end %>
<% if @user.nostr_pubkey.present? %>
<%= render FormElements::FieldsetComponent.new(
positioning: :horizontal,
title: "Zap received",
description: "Notify me when someone zaps me on Nostr"
) do %>
<% f.fields_for :preferences do |p| %>
<%= p.select :lightning_notify_zap_received, options_for_select([
["off", "disabled"],
["Chat (Jabber)", "xmpp"],
["E-Mail", "email"]
], selected: @user.preferences[:lightning_notify_zap_received]) %>
<% end %>
<% end %>
<% end %>
<% if @notifications_enabled %>
<%= render FormElements::FieldsetToggleComponent.new(
field_name: "user[preferences][lightning_notify_only_with_message]",
enabled: @user.preferences[:lightning_notify_only_with_message],
title: "Ignore transactions without message",
description: "Only send notifications when there is a message attached to the payment"
) %>
<%= render FormElements::FieldsetComponent.new(
title: "Minimum amount",
description: "Only send notifications when amount is higher than this"
) do %>
<%= f.number_field :lightning_notify_min_sats,
name: "user[preferences][lightning_notify_min_sats]",
class: "w-full",
value: @user.preferences[:lightning_notify_min_sats].to_i %>
<% end %>
<% end %>
</ul>
</section>
<section>

View File

@@ -1,14 +0,0 @@
<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>

View File

@@ -1,6 +1,3 @@
lightning_notify_sats_received: email
lightning_notify_zap_received: disabled
lightning_notify_min_sats: 0
lightning_notify_only_with_message: false
remotestorage_notify_auth_created: email
lightning_notify_sats_received: disabled # or xmpp, email
remotestorage_notify_auth_created: email # or xmpp, email
xmpp_exchange_contacts_with_invitees: true

View File

@@ -48,8 +48,7 @@ Rails.application.routes.draw do
end
resource :storage, controller: 'remotestorage', only: [:show] do
get :apps, to: "rs_auths#index"
resources :rs_auths, only: [:index, :destroy] do
resources :rs_auths, only: [:destroy] do
member do
get :revoke, to: 'rs_auths#destroy'
get :launch_app

View File

@@ -1,4 +1,6 @@
:concurrency: 2
production:
:concurrency: 10
:queues:
- default
- mailers

View File

@@ -1,9 +0,0 @@
class AddSettledAtToZaps < ActiveRecord::Migration[7.1]
def change
add_column :zaps, :settled_at, :datetime, default: nil
Zap.where.not(receipt: nil).each do |zap|
zap.update! settled_at: Time.at(zap.receipt_event.created_at).to_datetime
end
end
end

View File

@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.1].define(version: 2024_06_07_123654) do
ActiveRecord::Schema[7.1].define(version: 2024_04_22_171653) do
create_table "active_storage_attachments", force: :cascade do |t|
t.string "name", null: false
t.string "record_type", null: false
@@ -144,7 +144,6 @@ ActiveRecord::Schema[7.1].define(version: 2024_06_07_123654) do
t.bigint "amount"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "settled_at"
t.index ["user_id"], name: "index_zaps_on_user_id"
end

View File

@@ -1,29 +0,0 @@
# strfry (nostr relay)
## LDAP policy
...
## Useful scripts
### Syncing events for all local nostr users from a remote relay
You can sync all events of all local users with a pubkey stored in LDAP from a
specified remote relay to the local relay with the `strfry-sync.ts` script:
deno run -A /opt/strfry-sync.ts wss://relay.example.com
Doing the same with Docker Compose (great for seeding data to your local relay
in development):
docker compose run strfry deno run -A /opt/strfry-sync.ts wss://relay.example.com
## Docker image
In order to use the LDAP policy with Docker, you will need
[Deno](https://deno.com/) installed in your strfry container. We provide a
custom Docker image for strfry with Deno included (which we use in
development):
* Registry: https://gitea.kosmos.org/kosmos/-/packages/container/strfry-deno/1.1.1
* Source: https://github.com/raucao/strfry/blob/docker_deno/ubuntu.Dockerfile

View File

@@ -47,9 +47,6 @@ services:
RS_REDIS_URL: redis://redis:6379/1
RS_STORAGE_URL: "http://localhost:4567"
S3_ENABLED: false
NOSTR_PUBLIC_KEY: bdd76ce2934b2f591f9fad2ebe9da18f20d2921de527494ba00eeaa0a0efadcf
NOSTR_PRIVATE_KEY: 7c3ef7e448505f0615137af38569d01807d3b05b5005d5ecf8aaafcd40323cea
NOSTR_RELAY_URL: "ws://strfry:7777"
depends_on:
- ldap
- redis
@@ -110,23 +107,16 @@ services:
- minio
- redis
strfry:
image: gitea.kosmos.org/kosmos/strfry-deno:1.1.1
nostr-relay:
image: pluja/strfry:latest
volumes:
- ./docker/strfry/strfry.conf:/etc/strfry.conf
- ./extras/strfry:/opt/strfry
- strfry-data:/var/lib/strfry
- strfry-data:/app/strfry-db
networks:
- external_network
- internal_network
ports:
- "4777:7777"
environment:
LDAP_URL: 'ldap://ldap:3389'
LDAP_BIND_DN: 'cn=Directory Manager'
LDAP_PASSWORD: passthebutter
LDAP_SEARCH_DN: 'ou=kosmos.org,cn=users,dc=kosmos,dc=org'
WHITELIST_PUBKEYS: 'bdd76ce2934b2f591f9fad2ebe9da18f20d2921de527494ba00eeaa0a0efadcf'
# phpldapadmin:
# image: osixia/phpldapadmin:0.9.0

View File

@@ -3,7 +3,7 @@
##
# Directory that contains the strfry LMDB database (restart required)
db = "/var/lib/strfry/"
db = "./strfry-db/"
dbParams {
# Maximum number of threads/processes that can simultaneously have LMDB transactions open (restart required)
@@ -54,7 +54,7 @@ relay {
info {
# NIP-11: Name of this server. Short/descriptive (< 30 characters)
name = "Akkounts Nostr Relay"
name = "akkounts-nostr-relay"
# NIP-11: Detailed information about relay, free-form
description = "Local strfry instance for akkounts development"
@@ -86,7 +86,7 @@ relay {
writePolicy {
# If non-empty, path to an executable script that implements the writePolicy plugin logic
plugin = "/opt/strfry/strfry-policy.ts"
plugin = ""
}
compression {

View File

@@ -1,5 +0,0 @@
{
"imports": {
"@nostr/tools": "jsr:@nostr/tools@^2.3.1"
}
}

196
extras/strfry/deno.lock generated
View File

@@ -1,196 +0,0 @@
{
"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"
]
}
}

View File

@@ -1,77 +0,0 @@
import type { IterablePubkeys, Policy } from 'https://gitlab.com/soapbox-pub/strfry-policies/-/raw/develop/mod.ts';
import { Client } from 'npm:ldapts';
import { nip57 } from '@nostr/tools';
interface LdapConfig {
url: string;
bindDN: string;
password: string;
searchDN: string;
whitelistPubkeys?: IterablePubkeys;
}
const ldapPolicy: Policy<LdapConfig> = async (msg, opts) => {
const client = new Client({ url: opts.url });
const { kind, tags } = msg.event;
let { pubkey } = msg.event;
let out = { id: msg.event.id }
if (opts.whitelistPubkeys.includes(pubkey)) {
out['action'] = 'accept';
out['msg'] = '';
return out;
}
// Zap receipt
if (kind === 9735) {
const descriptionTag = tags.find(([t, v]) => t === 'description' && v);
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 {
await client.bind(opts.bindDN, opts.password);
const { searchEntries } = await client.search(opts.searchDN, {
filter: `(nostrKey=${pubkey})`,
attributes: ['nostrKey']
});
const memberKey = searchEntries[0]?.nostrKey;
if (memberKey === pubkey) {
out['action'] = 'accept';
out['msg'] = '';
} else {
out['action'] = 'reject';
out['msg'] = 'Only members can publish notes on this relay';
}
} catch (ex) {
out['action'] = 'reject';
out['msg'] = 'Auth service temporarily unavailable';
} finally {
await client.unbind();
return out;
}
};
export default ldapPolicy;

View File

@@ -1,34 +0,0 @@
#!/bin/sh
//bin/true; exec deno run -A "$0" "$@"
import {
antiDuplicationPolicy,
hellthreadPolicy,
pipeline,
rateLimitPolicy,
readStdin,
writeStdout,
} from 'https://gitlab.com/soapbox-pub/strfry-policies/-/raw/develop/mod.ts';
import ldapPolicy from './ldap-policy.ts';
import { load } from "https://deno.land/std@0.224.0/dotenv/mod.ts";
const dirname = new URL('.', import.meta.url).pathname;
await load({ envPath: `${dirname}/.env`, export: true });
const ldapConfig = {
url: Deno.env.get("LDAP_URL"),
bindDN: Deno.env.get("LDAP_BIND_DN"),
password: Deno.env.get("LDAP_PASSWORD"),
searchDN: Deno.env.get("LDAP_SEARCH_DN"),
whitelistPubkeys: Deno.env.get("WHITELIST_PUBKEYS")?.split(',')
}
for await (const msg of readStdin()) {
const result = await pipeline(msg, [
[hellthreadPolicy, { limit: 10 }],
[antiDuplicationPolicy, { ttl: 60000, minLength: 50 }],
[rateLimitPolicy, { whitelist: ['127.0.0.1'] }],
[ldapPolicy, ldapConfig],
]);
writeStdout(result);
}

View File

@@ -1,39 +0,0 @@
import { load } from "https://deno.land/std@0.224.0/dotenv/mod.ts";
import { Client } from 'npm:ldapts';
const dirname = new URL('.', import.meta.url).pathname;
await load({ envPath: `${dirname}/.env`, export: true });
const opts = {
url: Deno.env.get("LDAP_URL"),
bindDN: Deno.env.get("LDAP_BIND_DN"),
password: Deno.env.get("LDAP_PASSWORD"),
searchDN: Deno.env.get("LDAP_SEARCH_DN"),
relayUrl: Deno.args[0]
}
const client = new Client({ url: opts.url });
try {
await client.bind(opts.bindDN, opts.password);
const { searchEntries } = await client.search(opts.searchDN, {
filter: `(nostrKey=*)`,
attributes: ['nostrKey']
});
const pubkeys = searchEntries.map(e => e.nostrKey);
const filter = JSON.stringify({ authors: pubkeys });
const p = Deno.run({ cmd: [
"strfry", "sync", opts.relayUrl,
"--dir", "down", "--filter", filter
]});
const result = await p.status();
Deno.exit(result.code);
} catch (ex) {
console.error(ex);
Deno.exit(1);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

View File

@@ -1,11 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 1018 B

View File

@@ -1,18 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -38,15 +38,4 @@ RSpec.describe UserPreferences, type: :model do
expect(res['lightning_notify_sats_received_threshold']).to eq(1000)
end
end
describe ".pref_keys" do
let(:default_prefs) { YAML.load_file("#{Rails.root}/config/default_preferences.yml") }
it "returns the keys of all default preferences as an array of symbols" do
expect(UserPreferences.pref_keys).to be_a(Array)
expect(UserPreferences.pref_keys).to include(:lightning_notify_sats_received)
expect(UserPreferences.pref_keys).to include(:xmpp_exchange_contacts_with_invitees)
expect(UserPreferences.pref_keys.length).to eq(default_prefs.keys.length)
end
end
end

View File

@@ -60,6 +60,11 @@ RSpec.describe "Webhooks", type: :request do
expect(response).to have_http_status(:ok)
end
it "does not send notifications by default" do
post "/webhooks/lndhub", params: payload.to_json
expect(enqueued_jobs.size).to eq(0)
end
it "does not send a zap receipt" do
expect(NostrManager::PublishZapReceipt).not_to receive(:call)
post "/webhooks/lndhub", params: payload.to_json
@@ -101,34 +106,6 @@ RSpec.describe "Webhooks", type: :request do
expect(args[3]["params"]["amount_sats"]).to eq(12300)
end
end
describe "minimum threshold amount not reached" do
before do
user.update! preferences: {
lightning_notify_sats_received: "xmpp",
lightning_notify_min_sats: 21000
}
end
it "does not send a notification" do
post "/webhooks/lndhub", params: payload.to_json
expect(enqueued_jobs.size).to eq(0)
end
end
describe "no memo/description/message" do
before do
user.update! preferences: {
lightning_notify_sats_received: "xmpp",
lightning_notify_only_with_message: true
}
end
it "does not send a notification" do
post "/webhooks/lndhub", params: payload.merge({ memo: "" }).to_json
expect(enqueued_jobs.size).to eq(0)
end
end
end
describe "Valid payload for zap transaction" do
@@ -167,31 +144,16 @@ RSpec.describe "Webhooks", type: :request do
expect(response).to have_http_status(:ok)
end
it "adds the settlement date/time to the zap record" do
post "/webhooks/lndhub", params: payload.to_json
expect(user.zaps.first.settled_at.to_i).to eq(1673428978)
end
it "creates and adds a zap receipt to the zap record" do
post "/webhooks/lndhub", params: payload.to_json
expect(user.zaps.first.receipt).not_to be_nil
zap = user.zaps.first
expect(zap.receipt).not_to be_nil
end
it "publishes the zap receipt" do
expect(NostrManager::PublishZapReceipt).to receive(:call).with(zap: zap)
post "/webhooks/lndhub", params: payload.to_json
end
context "with notifications disabled for zaps" do
before do
user.update! preferences: { lightning_notify_zap_received: "disabled" }
end
it "does not send a notification" do
post "/webhooks/lndhub", params: payload.to_json
expect(enqueued_jobs.size).to eq(0)
end
end
end
end
end

View File

@@ -2,21 +2,21 @@ require 'rails_helper'
RSpec.describe "Well-known URLs", type: :request do
describe "GET /nostr" do
describe "without username param" do
context "without username param" do
it "returns a 422 status" do
get "/.well-known/nostr.json"
expect(response).to have_http_status(:unprocessable_entity)
end
end
describe "non-existent user" do
context "non-existent user" do
it "returns a 404 status" do
get "/.well-known/nostr.json?name=bob"
expect(response).to have_http_status(:not_found)
end
end
describe "user does not have a nostr pubkey configured" do
context "user does not have a nostr pubkey configured" do
let(:user) { create :user, cn: 'spongebob', ou: 'kosmos.org' }
before do
@@ -30,7 +30,7 @@ RSpec.describe "Well-known URLs", type: :request do
end
end
describe "user with nostr pubkey" do
context "user with nostr pubkey" do
let(:user) { create :user, cn: 'bobdylan', ou: 'kosmos.org' }
before do
user.save!
@@ -45,63 +45,6 @@ RSpec.describe "Well-known URLs", type: :request do
expect(res["names"].keys.size).to eq(1)
expect(res["names"]["bobdylan"]).to eq(user.nostr_pubkey)
end
context "without relay configured" do
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
it "returns the configured nostr pubkey" do
get "/.well-known/nostr.json?name=_"
res = JSON.parse(response.body)
expect(res["names"]["_"]).to eq(Setting.nostr_public_key)
end
context "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

View File

@@ -4,10 +4,6 @@ RSpec.describe NostrManager::PublishZapReceipt, type: :model do
let(:user) { create :user, ln_account: "123456abcdef" }
let(:zap) { create :zap, user: user }
before do
Setting.nostr_relay_url = ""
end
describe "Default/delayed execution" do
it "publishes zap receipts to all requested relays" do
expect(NostrPublishEventJob).to receive(:perform_later)
@@ -39,19 +35,6 @@ RSpec.describe NostrManager::PublishZapReceipt, type: :model do
described_class.call(zap: zap)
end
context "with own relay configured" do
before do
Setting.nostr_relay_url = "wss://foobar.kosmos.org"
end
it "also publishes the receipt to our own relay" do
expect(NostrPublishEventJob).to receive(:perform_later)
.exactly(13).times.and_return(true)
described_class.call(zap: zap)
end
end
end
end
end