Compare commits
14 Commits
9f6fa6deba
...
chore/btcp
| Author | SHA1 | Date | |
|---|---|---|---|
|
aca13a25c3
|
|||
|
7df56479a4
|
|||
| 8aa3ca9e23 | |||
| 3ad1d03785 | |||
| e258a8bd27 | |||
|
339462f320
|
|||
|
c4c2d16342
|
|||
|
3ee76e26ab
|
|||
|
729e4fd566
|
|||
|
8ad6adbaeb
|
|||
|
534e5a9d3c
|
|||
|
1b72c97f42
|
|||
|
bfd8ca16a9
|
|||
|
64de4deddd
|
@@ -84,21 +84,26 @@ class Contributions::DonationsController < ApplicationController
|
||||
@donation.paid_at = DateTime.now
|
||||
@donation.payment_status = "settled"
|
||||
@donation.save!
|
||||
flash_message = { success: "Thank you!" }
|
||||
msg = { success: "Thank you!" }
|
||||
when "Processing"
|
||||
unless @donation.processing?
|
||||
@donation.payment_status = "processing"
|
||||
@donation.save!
|
||||
flash_message = { success: "Thank you! We will send you an email when the payment is confirmed." }
|
||||
msg = { success: "Thank you! We will send you an email when the payment is confirmed." }
|
||||
BtcpayCheckDonationJob.set(wait: 20.seconds).perform_later(@donation)
|
||||
end
|
||||
when "Expired"
|
||||
flash_message = { warning: "The payment request for this donation has expired" }
|
||||
if invoice["additionalStatus"] &&
|
||||
invoice["additionalStatus"] == "PaidLate"
|
||||
# TODO introduce state machine
|
||||
mark_as_paid(donation)
|
||||
end
|
||||
msg = { warning: "The payment request for this donation has expired" }
|
||||
else
|
||||
flash_message = { warning: "Could not determine status of payment" }
|
||||
msg = { warning: "Could not determine status of payment" }
|
||||
end
|
||||
|
||||
redirect_to contributions_donations_url, flash: flash_message
|
||||
redirect_to contributions_donations_url, flash: msg
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
class WebKeyDirectoryController < WellKnownController
|
||||
before_action :allow_cross_origin_requests, only: [ :show ]
|
||||
before_action :allow_cross_origin_requests
|
||||
|
||||
# /.well-known/openpgpkey/hu/:hashed_username(.txt)
|
||||
def show
|
||||
@user = User.find_by(cn: params[:l])
|
||||
@user = User.find_by(cn: params[:l].downcase)
|
||||
|
||||
if @user.nil? ||
|
||||
@user.pgp_pubkey.empty? ||
|
||||
@user.pgp_pubkey.blank? ||
|
||||
!@user.pgp_pubkey_contains_user_address?
|
||||
http_status :not_found and return
|
||||
end
|
||||
@@ -29,6 +29,7 @@ class WebKeyDirectoryController < WellKnownController
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def policy
|
||||
head :ok
|
||||
end
|
||||
end
|
||||
|
||||
@@ -9,20 +9,29 @@ class BtcpayCheckDonationJob < ApplicationJob
|
||||
)
|
||||
|
||||
case invoice["status"]
|
||||
when "Settled"
|
||||
donation.paid_at = DateTime.now
|
||||
donation.payment_status = "settled"
|
||||
donation.save!
|
||||
|
||||
NotificationMailer.with(user: donation.user)
|
||||
.bitcoin_donation_confirmed
|
||||
.deliver_later
|
||||
when "Processing"
|
||||
re_enqueue_job(donation)
|
||||
when "Settled"
|
||||
mark_as_paid(donation)
|
||||
when "Expired"
|
||||
if invoice["additionalStatus"] &&
|
||||
invoice["additionalStatus"] == "PaidLate"
|
||||
mark_as_paid(donation)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def re_enqueue_job(donation)
|
||||
self.class.set(wait: 20.seconds).perform_later(donation)
|
||||
end
|
||||
|
||||
def mark_as_paid(donation)
|
||||
donation.paid_at = DateTime.now
|
||||
donation.payment_status = "settled"
|
||||
donation.save!
|
||||
|
||||
NotificationMailer.with(user: donation.user)
|
||||
.bitcoin_donation_confirmed
|
||||
.deliver_later
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,3 +1,90 @@
|
||||
class ApplicationMailer < ActionMailer::Base
|
||||
default Rails.application.config.action_mailer.default_options
|
||||
layout 'mailer'
|
||||
|
||||
private
|
||||
|
||||
def send_mail
|
||||
@template ||= "#{self.class.name.underscore}/#{caller[0][/`([^']*)'/, 1]}"
|
||||
headers['Message-ID'] = message_id
|
||||
|
||||
if @user.pgp_pubkey.present?
|
||||
mail(to: @user.email, subject: "...", content_type: pgp_content_type) do |format|
|
||||
format.text { render plain: pgp_content }
|
||||
end
|
||||
else
|
||||
mail(to: @user.email, subject: @subject) do |format|
|
||||
format.text { render @template }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def from_address
|
||||
self.class.default[:from]
|
||||
end
|
||||
|
||||
def from_domain
|
||||
Mail::Address.new(from_address).domain
|
||||
end
|
||||
|
||||
def message_id
|
||||
@message_id ||= "#{SecureRandom.uuid}@#{from_domain}"
|
||||
end
|
||||
|
||||
def boundary
|
||||
@boundary ||= SecureRandom.hex(8)
|
||||
end
|
||||
|
||||
def pgp_content_type
|
||||
"multipart/encrypted; protocol=\"application/pgp-encrypted\"; boundary=\"------------#{boundary}\""
|
||||
end
|
||||
|
||||
def pgp_nested_content
|
||||
message_content = render_to_string(template: @template)
|
||||
message_content_base64 = Base64.encode64(message_content)
|
||||
nested_boundary = SecureRandom.hex(8)
|
||||
|
||||
<<~NESTED_CONTENT
|
||||
Content-Type: multipart/mixed; boundary="------------#{nested_boundary}"; protected-headers="v1"
|
||||
Subject: #{@subject}
|
||||
From: <#{from_address}>
|
||||
To: #{@user.display_name || @user.cn} <#{@user.email}>
|
||||
Message-ID: <#{message_id}>
|
||||
|
||||
--------------#{nested_boundary}
|
||||
Content-Type: text/plain; charset=UTF-8; format=flowed
|
||||
Content-Transfer-Encoding: base64
|
||||
|
||||
#{message_content_base64}
|
||||
|
||||
--------------#{nested_boundary}--
|
||||
NESTED_CONTENT
|
||||
end
|
||||
|
||||
def pgp_content
|
||||
encrypted_content = UserManager::PgpEncrypt.call(user: @user, text: pgp_nested_content)
|
||||
encrypted_base64 = Base64.encode64(encrypted_content.to_s)
|
||||
|
||||
<<~EMAIL_CONTENT
|
||||
This is an OpenPGP/MIME encrypted message (RFC 4880 and 3156)
|
||||
--------------#{boundary}
|
||||
Content-Type: application/pgp-encrypted
|
||||
Content-Description: PGP/MIME version identification
|
||||
|
||||
Version: 1
|
||||
|
||||
--------------#{boundary}
|
||||
Content-Type: application/octet-stream; name="encrypted.asc"
|
||||
Content-Description: OpenPGP encrypted message
|
||||
Content-Disposition: inline; filename="encrypted.asc"
|
||||
|
||||
-----BEGIN PGP MESSAGE-----
|
||||
|
||||
#{encrypted_base64}
|
||||
|
||||
-----END PGP MESSAGE-----
|
||||
|
||||
--------------#{boundary}--
|
||||
EMAIL_CONTENT
|
||||
end
|
||||
end
|
||||
|
||||
@@ -18,6 +18,6 @@ class CustomMailer < ApplicationMailer
|
||||
@user = params[:user]
|
||||
@subject = params[:subject]
|
||||
@body = params[:body]
|
||||
mail(to: @user.email, subject: @subject)
|
||||
send_mail
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,7 +3,7 @@ class NotificationMailer < ApplicationMailer
|
||||
@user = params[:user]
|
||||
@amount_sats = params[:amount_sats]
|
||||
@subject = "Sats received"
|
||||
mail to: @user.email, subject: @subject
|
||||
send_mail
|
||||
end
|
||||
|
||||
def remotestorage_auth_created
|
||||
@@ -15,19 +15,19 @@ class NotificationMailer < ApplicationMailer
|
||||
"#{access} #{directory}"
|
||||
end
|
||||
@subject = "New app connected to your storage"
|
||||
mail to: @user.email, subject: @subject
|
||||
send_mail
|
||||
end
|
||||
|
||||
def new_invitations_available
|
||||
@user = params[:user]
|
||||
@subject = "New invitations added to your account"
|
||||
mail to: @user.email, subject: @subject
|
||||
send_mail
|
||||
end
|
||||
|
||||
def bitcoin_donation_confirmed
|
||||
@user = params[:user]
|
||||
@donation = params[:donation]
|
||||
@subject = "Donation confirmed"
|
||||
mail to: @user.email, subject: @subject
|
||||
send_mail
|
||||
end
|
||||
end
|
||||
|
||||
@@ -174,7 +174,8 @@ class User < ApplicationRecord
|
||||
|
||||
def gnupg_key
|
||||
return nil unless pgp_pubkey.present?
|
||||
@gnupg_key ||= GPGME::Key.get(pgp_fpr)
|
||||
GPGME::Key.import(pgp_pubkey)
|
||||
GPGME::Key.get(pgp_fpr)
|
||||
end
|
||||
|
||||
def pgp_pubkey_contains_user_address?
|
||||
|
||||
19
app/services/user_manager/pgp_encrypt.rb
Normal file
19
app/services/user_manager/pgp_encrypt.rb
Normal file
@@ -0,0 +1,19 @@
|
||||
require 'gpgme'
|
||||
|
||||
module UserManager
|
||||
class PgpEncrypt < UserManagerService
|
||||
def initialize(user:, text:)
|
||||
@user = user
|
||||
@text = text
|
||||
end
|
||||
|
||||
def call
|
||||
crypto = GPGME::Crypto.new
|
||||
crypto.encrypt(
|
||||
@text,
|
||||
recipients: @user.gnupg_key,
|
||||
always_trust: true
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -218,7 +218,7 @@
|
||||
<td>XMPP (ejabberd)</td>
|
||||
<td>
|
||||
<%= render FormElements::ToggleComponent.new(
|
||||
enabled: @services_enabled.include?("xmpp"),
|
||||
enabled: @services_enabled.include?("ejabberd"),
|
||||
input_enabled: false
|
||||
) %>
|
||||
</td>
|
||||
|
||||
@@ -57,16 +57,22 @@ Rails.application.configure do
|
||||
# routes, locales, etc. This feature depends on the listen gem.
|
||||
config.file_watcher = ActiveSupport::EventedFileUpdateChecker
|
||||
|
||||
config.action_mailer.default_options = {
|
||||
from: "accounts@localhost"
|
||||
}
|
||||
|
||||
# Don't actually send emails, cache them for viewing via letter opener
|
||||
config.action_mailer.delivery_method = :letter_opener
|
||||
|
||||
# Don't care if the mailer can't send
|
||||
config.action_mailer.raise_delivery_errors = true
|
||||
|
||||
# Base URL to be used by email template link helpers
|
||||
config.action_mailer.default_url_options = { host: "localhost:3000", protocol: "http" }
|
||||
config.action_mailer.default_url_options = {
|
||||
host: "localhost:3000",
|
||||
protocol: "http"
|
||||
}
|
||||
|
||||
config.action_mailer.default_options = {
|
||||
from: "accounts@localhost",
|
||||
message_id: -> { "<#{Mail.random_tag}@localhost>" },
|
||||
}
|
||||
|
||||
# Allow requests from any IP
|
||||
config.web_console.permissions = '0.0.0.0/0'
|
||||
|
||||
@@ -63,7 +63,7 @@ Rails.application.configure do
|
||||
outgoing_email_domain = Mail::Address.new(outgoing_email_address).domain
|
||||
|
||||
config.action_mailer.default_url_options = {
|
||||
host: ENV['AKKOUNTS_DOMAIN'],
|
||||
host: ENV.fetch('AKKOUNTS_DOMAIN'),
|
||||
protocol: "https",
|
||||
}
|
||||
|
||||
|
||||
@@ -46,8 +46,12 @@ Rails.application.configure do
|
||||
|
||||
config.action_mailer.default_url_options = {
|
||||
host: "accounts.kosmos.org",
|
||||
protocol: "https",
|
||||
from: "accounts@kosmos.org"
|
||||
protocol: "https"
|
||||
}
|
||||
|
||||
config.action_mailer.default_options = {
|
||||
from: "accounts@kosmos.org",
|
||||
message_id: -> { "<#{Mail.random_tag}@kosmos.org>" },
|
||||
}
|
||||
|
||||
config.active_job.queue_adapter = :test
|
||||
|
||||
@@ -73,6 +73,7 @@ Rails.application.routes.draw do
|
||||
get '.well-known/lnurlp/:username', to: 'lnurlpay#index', as: :lightning_address
|
||||
get '.well-known/keysend/:username', to: 'lnurlpay#keysend', as: :lightning_address_keysend
|
||||
get '.well-known/openpgpkey/hu/:hashed_username(.:format)', to: 'web_key_directory#show', as: :wkd_key
|
||||
get '.well-known/openpgpkey/policy', to: 'web_key_directory#policy'
|
||||
|
||||
get 'lnurlpay/:username/invoice', to: 'lnurlpay#invoice', as: :lnurlpay_invoice
|
||||
|
||||
|
||||
@@ -5,5 +5,5 @@ attributeTypes: ( 1.3.6.1.4.1.61554.1.1.2.1.21
|
||||
NAME 'nostrKey'
|
||||
DESC 'Nostr public key'
|
||||
EQUALITY caseIgnoreMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
|
||||
SINGLE-VALUE )
|
||||
|
||||
87
spec/mailers/notification_mailer_spec.rb
Normal file
87
spec/mailers/notification_mailer_spec.rb
Normal file
@@ -0,0 +1,87 @@
|
||||
# spec/mailers/welcome_mailer_spec.rb
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe NotificationMailer, type: :mailer do
|
||||
describe '#lightning_sats_received' do
|
||||
|
||||
context "without PGP key" do
|
||||
let(:user) { create(:user, cn: "phil", email: 'phil@example.com') }
|
||||
|
||||
before do
|
||||
allow_any_instance_of(User).to receive(:ldap_entry).and_return({
|
||||
uid: user.cn, ou: user.ou, display_name: nil, pgp_key: nil
|
||||
})
|
||||
end
|
||||
|
||||
describe "unencrypted email" do
|
||||
let(:mail) { described_class.with(user: user, amount_sats: 21000).lightning_sats_received }
|
||||
|
||||
it 'renders the correct to/from headers' do
|
||||
expect(mail.to).to eq([user.email])
|
||||
expect(mail.from).to eq(['accounts@kosmos.org'])
|
||||
end
|
||||
|
||||
it 'renders the correct subject' do
|
||||
expect(mail.subject).to eq('Sats received')
|
||||
end
|
||||
|
||||
it 'uses the correct content type' do
|
||||
expect(mail.header['content-type'].to_s).to include('text/plain')
|
||||
end
|
||||
|
||||
it 'renders the body with correct content' do
|
||||
expect(mail.body.encoded).to match(/You just received 21,000 sats/)
|
||||
expect(mail.body.encoded).to include(user.address)
|
||||
end
|
||||
|
||||
it 'includes a link to the lightning service page' do
|
||||
expect(mail.body.encoded).to include("https://accounts.kosmos.org/services/lightning")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "with PGP key" do
|
||||
let(:pgp_pubkey) { File.read("#{Rails.root}/spec/fixtures/files/pgp_key_valid_alice.asc") }
|
||||
let(:pgp_fingerprint) { "EB85BB5FA33A75E15E944E63F231550C4F47E38E" }
|
||||
let(:user) { create(:user, id: 2, cn: "alice", email: 'alice@example.com', pgp_fpr: pgp_fingerprint) }
|
||||
|
||||
before do
|
||||
allow_any_instance_of(User).to receive(:ldap_entry).and_return({
|
||||
uid: user.cn, ou: user.ou, display_name: nil, pgp_key: pgp_pubkey
|
||||
})
|
||||
end
|
||||
|
||||
describe "encrypted email" do
|
||||
let(:mail) { described_class.with(user: user, amount_sats: 21000).lightning_sats_received }
|
||||
|
||||
it 'renders the correct to/from headers' do
|
||||
expect(mail.to).to eq([user.email])
|
||||
expect(mail.from).to eq(['accounts@kosmos.org'])
|
||||
end
|
||||
|
||||
it 'encrypts the subject line' do
|
||||
expect(mail.subject).to eq('...')
|
||||
end
|
||||
|
||||
it 'uses the correct content type' do
|
||||
expect(mail.header['content-type'].to_s).to include('multipart/encrypted')
|
||||
expect(mail.header['content-type'].to_s).to include('protocol="application/pgp-encrypted"')
|
||||
end
|
||||
|
||||
it 'renders the PGP version part' do
|
||||
expect(mail.body.encoded).to include("Content-Type: application/pgp-encrypted")
|
||||
expect(mail.body.encoded).to include("Content-Description: PGP/MIME version identification")
|
||||
expect(mail.body.encoded).to include("Version: 1")
|
||||
end
|
||||
|
||||
it 'renders the encrypted PGP part' do
|
||||
expect(mail.body.encoded).to include('Content-Type: application/octet-stream; name="encrypted.asc"')
|
||||
expect(mail.body.encoded).to include('Content-Description: OpenPGP encrypted message')
|
||||
expect(mail.body.encoded).to include('Content-Disposition: inline; filename="encrypted.asc"')
|
||||
expect(mail.body.encoded).to include('-----BEGIN PGP MESSAGE-----')
|
||||
expect(mail.body.encoded).to include('hF4DR')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,6 +1,14 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe "OpenPGP Web Key Directory", type: :request do
|
||||
describe "policy" do
|
||||
it "returns an empty 200 response" do
|
||||
get "/.well-known/openpgpkey/policy"
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(response.body).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
describe "non-existent user" do
|
||||
it "returns a 404 status" do
|
||||
get "/.well-known/openpgpkey/hu/fmb8gw3n4zdj4xpwaziki4mwcxr1368i?l=aristotle"
|
||||
@@ -64,6 +72,15 @@ RSpec.describe "OpenPGP Web Key Directory", type: :request do
|
||||
expect(response.body).to eq(expected_binary_data)
|
||||
end
|
||||
|
||||
context "with wrong capitalization of username" do
|
||||
it "returns the pubkey as ASCII Armor plain text" do
|
||||
get "/.well-known/openpgpkey/hu/yuca4ky39mhwkjo78qb8zjgbfj1hg3yf?l=JimmY"
|
||||
expect(response).to have_http_status(:ok)
|
||||
expected_binary_data = File.binread("#{Rails.root}/spec/fixtures/files/pgp_key_valid_jimmy.pem")
|
||||
expect(response.body).to eq(expected_binary_data)
|
||||
end
|
||||
end
|
||||
|
||||
context "with .txt extension" do
|
||||
it "returns the pubkey as ASCII Armor plain text" do
|
||||
get "/.well-known/openpgpkey/hu/yuca4ky39mhwkjo78qb8zjgbfj1hg3yf.txt?l=jimmy"
|
||||
|
||||
Reference in New Issue
Block a user