Add LndHub wallets #33

Merged
raucao merged 13 commits from feature/lndhub into master 2021-11-22 23:04:19 +00:00
22 changed files with 491 additions and 130 deletions

View File

@ -1 +1,3 @@
EJABBERD_API_URL='https://xmpp.kosmos.org/api'
LNDHUB_API_URL='http://localhost:3023'
LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org'

View File

@ -1 +1,3 @@
EJABBERD_API_URL='https://xmpp.kosmos.org:5443/api'
LNDHUB_API_URL='http://10.1.1.163:3023'
LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org'

2
.gitignore vendored
View File

@ -42,3 +42,5 @@ yarn-debug.log*
# Ignore redis dumps from sidekiq
dump.rdb
/config/credentials/development.key

View File

@ -24,12 +24,18 @@ gem 'bootsnap', '>= 1.4.2', require: false
# Configuration
gem 'dotenv-rails'
# Security
gem 'lockbox'
# Authentication
gem 'warden'
gem 'devise'
gem 'devise_ldap_authenticatable'
gem 'net-ldap'
# Utilities
gem "rqrcode", "~> 2.0"
# HTTP requests
gem 'faraday'

View File

@ -1,84 +1,91 @@
GEM
remote: https://rubygems.org/
specs:
actioncable (6.0.3.4)
actionpack (= 6.0.3.4)
actioncable (6.0.4.1)
actionpack (= 6.0.4.1)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
actionmailbox (6.0.3.4)
actionpack (= 6.0.3.4)
activejob (= 6.0.3.4)
activerecord (= 6.0.3.4)
activestorage (= 6.0.3.4)
activesupport (= 6.0.3.4)
actionmailbox (6.0.4.1)
actionpack (= 6.0.4.1)
activejob (= 6.0.4.1)
activerecord (= 6.0.4.1)
activestorage (= 6.0.4.1)
activesupport (= 6.0.4.1)
mail (>= 2.7.1)
actionmailer (6.0.3.4)
actionpack (= 6.0.3.4)
actionview (= 6.0.3.4)
activejob (= 6.0.3.4)
actionmailer (6.0.4.1)
actionpack (= 6.0.4.1)
actionview (= 6.0.4.1)
activejob (= 6.0.4.1)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
actionpack (6.0.3.4)
actionview (= 6.0.3.4)
activesupport (= 6.0.3.4)
actionpack (6.0.4.1)
actionview (= 6.0.4.1)
activesupport (= 6.0.4.1)
rack (~> 2.0, >= 2.0.8)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0)
actiontext (6.0.3.4)
actionpack (= 6.0.3.4)
activerecord (= 6.0.3.4)
activestorage (= 6.0.3.4)
activesupport (= 6.0.3.4)
actiontext (6.0.4.1)
actionpack (= 6.0.4.1)
activerecord (= 6.0.4.1)
activestorage (= 6.0.4.1)
activesupport (= 6.0.4.1)
nokogiri (>= 1.8.5)
actionview (6.0.3.4)
activesupport (= 6.0.3.4)
actionview (6.0.4.1)
activesupport (= 6.0.4.1)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.1, >= 1.2.0)
activejob (6.0.3.4)
activesupport (= 6.0.3.4)
activejob (6.0.4.1)
activesupport (= 6.0.4.1)
globalid (>= 0.3.6)
activemodel (6.0.3.4)
activesupport (= 6.0.3.4)
activerecord (6.0.3.4)
activemodel (= 6.0.3.4)
activesupport (= 6.0.3.4)
activestorage (6.0.3.4)
actionpack (= 6.0.3.4)
activejob (= 6.0.3.4)
activerecord (= 6.0.3.4)
marcel (~> 0.3.1)
activesupport (6.0.3.4)
activemodel (6.0.4.1)
activesupport (= 6.0.4.1)
activerecord (6.0.4.1)
activemodel (= 6.0.4.1)
activesupport (= 6.0.4.1)
activestorage (6.0.4.1)
actionpack (= 6.0.4.1)
activejob (= 6.0.4.1)
activerecord (= 6.0.4.1)
marcel (~> 1.0.0)
activesupport (6.0.4.1)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2)
minitest (~> 5.1)
tzinfo (~> 1.1)
zeitwerk (~> 2.2, >= 2.2.2)
addressable (2.7.0)
addressable (2.8.0)
public_suffix (>= 2.0.2, < 5.0)
bcrypt (3.1.16)
bindex (0.8.1)
bootsnap (1.5.0)
bootsnap (1.9.1)
msgpack (~> 1.0)
builder (3.2.4)
byebug (11.1.3)
capybara (3.33.0)
capybara (3.36.0)
addressable
matrix
mini_mime (>= 0.1.3)
nokogiri (~> 1.8)
rack (>= 1.6.0)
rack-test (>= 0.6.3)
regexp_parser (~> 1.5)
regexp_parser (>= 1.5, < 3.0)
xpath (~> 3.2)
concurrent-ruby (1.1.7)
connection_pool (2.2.3)
crack (0.4.3)
safe_yaml (~> 1.0.0)
chunky_png (1.4.0)
concurrent-ruby (1.1.9)
connection_pool (2.2.5)
crack (0.4.5)
rexml
crass (1.0.6)
database_cleaner (1.8.5)
devise (4.7.3)
database_cleaner (2.0.1)
database_cleaner-active_record (~> 2.0.0)
database_cleaner-active_record (2.0.1)
activerecord (>= 5.a)
database_cleaner-core (~> 2.0.0)
database_cleaner-core (2.0.1)
devise (4.8.0)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 4.1.0)
@ -88,127 +95,149 @@ GEM
devise (>= 3.4.1)
net-ldap (>= 0.16.0)
diff-lcs (1.4.4)
dotenv (2.7.2)
dotenv-rails (2.7.2)
dotenv (= 2.7.2)
railties (>= 3.2, < 6.1)
dotenv (2.7.6)
dotenv-rails (2.7.6)
dotenv (= 2.7.6)
railties (>= 3.2)
e2mmap (0.1.0)
erubi (1.9.0)
et-orbi (1.2.4)
erubi (1.10.0)
et-orbi (1.2.6)
tzinfo
factory_bot (6.1.0)
factory_bot (6.2.0)
activesupport (>= 5.0.0)
factory_bot_rails (6.1.0)
factory_bot (~> 6.1.0)
factory_bot_rails (6.2.0)
factory_bot (~> 6.2.0)
railties (>= 5.0.0)
faraday (0.17.0)
faraday (1.8.0)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
faraday-httpclient (~> 1.0.1)
faraday-net_http (~> 1.0)
faraday-net_http_persistent (~> 1.1)
faraday-patron (~> 1.0)
faraday-rack (~> 1.0)
multipart-post (>= 1.2, < 3)
ffi (1.13.1)
fugit (1.4.2)
ruby2_keywords (>= 0.0.4)
faraday-em_http (1.0.0)
faraday-em_synchrony (1.0.0)
faraday-excon (1.1.0)
faraday-httpclient (1.0.1)
faraday-net_http (1.0.1)
faraday-net_http_persistent (1.2.0)
faraday-patron (1.0.0)
faraday-rack (1.0.0)
ffi (1.15.4)
fugit (1.5.2)
et-orbi (~> 1.1, >= 1.1.8)
raabro (~> 1.4)
globalid (0.4.2)
activesupport (>= 4.2.0)
hashdiff (0.4.0)
i18n (1.8.5)
globalid (0.5.2)
activesupport (>= 5.0)
hashdiff (1.0.1)
i18n (1.8.11)
concurrent-ruby (~> 1.0)
jbuilder (2.10.1)
jbuilder (2.11.3)
activesupport (>= 5.0.0)
launchy (2.4.3)
addressable (~> 2.3)
launchy (2.5.0)
addressable (~> 2.7)
letter_opener (1.7.0)
launchy (~> 2.2)
letter_opener_web (1.3.4)
letter_opener_web (1.4.1)
actionmailer (>= 3.2)
letter_opener (~> 1.0)
railties (>= 3.2)
listen (3.2.1)
listen (3.7.0)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
loofah (2.7.0)
lockbox (0.6.6)
loofah (2.12.0)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
mail (2.7.1)
mini_mime (>= 0.1.1)
marcel (0.3.3)
mimemagic (~> 0.3.2)
marcel (1.0.2)
matrix (0.4.2)
method_source (1.0.0)
mimemagic (0.3.5)
mini_mime (1.0.2)
mini_portile2 (2.4.0)
minitest (5.14.2)
msgpack (1.3.3)
mini_mime (1.1.2)
minitest (5.14.4)
msgpack (1.4.2)
multipart-post (2.1.1)
net-ldap (0.16.3)
nio4r (2.5.4)
nokogiri (1.10.10)
mini_portile2 (~> 2.4.0)
net-ldap (0.17.0)
nio4r (2.5.8)
nokogiri (1.12.5-x86_64-linux)
racc (~> 1.4)
orm_adapter (0.5.0)
pg (1.2.3)
public_suffix (4.0.6)
puma (4.3.6)
puma (4.3.10)
nio4r (~> 2.0)
raabro (1.4.0)
racc (1.6.0)
rack (2.2.3)
rack-proxy (0.6.5)
rack-proxy (0.7.0)
rack
rack-test (1.1.0)
rack (>= 1.0, < 3)
rails (6.0.3.4)
actioncable (= 6.0.3.4)
actionmailbox (= 6.0.3.4)
actionmailer (= 6.0.3.4)
actionpack (= 6.0.3.4)
actiontext (= 6.0.3.4)
actionview (= 6.0.3.4)
activejob (= 6.0.3.4)
activemodel (= 6.0.3.4)
activerecord (= 6.0.3.4)
activestorage (= 6.0.3.4)
activesupport (= 6.0.3.4)
rails (6.0.4.1)
actioncable (= 6.0.4.1)
actionmailbox (= 6.0.4.1)
actionmailer (= 6.0.4.1)
actionpack (= 6.0.4.1)
actiontext (= 6.0.4.1)
actionview (= 6.0.4.1)
activejob (= 6.0.4.1)
activemodel (= 6.0.4.1)
activerecord (= 6.0.4.1)
activestorage (= 6.0.4.1)
activesupport (= 6.0.4.1)
bundler (>= 1.3.0)
railties (= 6.0.3.4)
railties (= 6.0.4.1)
sprockets-rails (>= 2.0.0)
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
nokogiri (>= 1.6)
rails-html-sanitizer (1.3.0)
rails-html-sanitizer (1.4.2)
loofah (~> 2.3)
railties (6.0.3.4)
actionpack (= 6.0.3.4)
activesupport (= 6.0.3.4)
railties (6.0.4.1)
actionpack (= 6.0.4.1)
activesupport (= 6.0.4.1)
method_source
rake (>= 0.8.7)
thor (>= 0.20.3, < 2.0)
rake (13.0.1)
rb-fsevent (0.10.4)
rake (13.0.6)
rb-fsevent (0.11.0)
rb-inotify (0.10.1)
ffi (~> 1.0)
redis (4.2.5)
regexp_parser (1.8.2)
redis (4.5.1)
regexp_parser (2.1.1)
responders (3.0.1)
actionpack (>= 5.0)
railties (>= 5.0)
rspec-core (3.10.0)
rexml (3.2.5)
rqrcode (2.1.0)
chunky_png (~> 1.0)
rqrcode_core (~> 1.0)
rqrcode_core (1.2.0)
rspec-core (3.10.1)
rspec-support (~> 3.10.0)
rspec-expectations (3.10.0)
rspec-expectations (3.10.1)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.10.0)
rspec-mocks (3.10.0)
rspec-mocks (3.10.2)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.10.0)
rspec-rails (4.0.1)
actionpack (>= 4.2)
activesupport (>= 4.2)
railties (>= 4.2)
rspec-core (~> 3.9)
rspec-expectations (~> 3.9)
rspec-mocks (~> 3.9)
rspec-support (~> 3.9)
rspec-support (3.10.0)
rufus-scheduler (3.7.0)
rspec-rails (5.0.2)
actionpack (>= 5.2)
activesupport (>= 5.2)
railties (>= 5.2)
rspec-core (~> 3.10)
rspec-expectations (~> 3.10)
rspec-mocks (~> 3.10)
rspec-support (~> 3.10)
rspec-support (3.10.3)
ruby2_keywords (0.0.5)
rufus-scheduler (3.8.0)
fugit (~> 1.1, >= 1.1.6)
safe_yaml (1.0.5)
sass-rails (6.0.0)
sassc-rails (~> 2.1, >= 2.1.1)
sassc (2.4.0)
@ -219,11 +248,11 @@ GEM
sprockets (> 3.0)
sprockets-rails
tilt
sidekiq (6.1.3)
sidekiq (6.3.1)
connection_pool (>= 2.2.2)
rack (~> 2.0)
redis (>= 4.2.0)
sidekiq-scheduler (3.0.1)
sidekiq-scheduler (3.1.0)
e2mmap
redis (>= 3, < 5)
rufus-scheduler (~> 3.2)
@ -237,12 +266,12 @@ GEM
sprockets (4.0.2)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
sprockets-rails (3.2.2)
actionpack (>= 4.0)
activesupport (>= 4.0)
sprockets-rails (3.4.0)
actionpack (>= 5.2)
activesupport (>= 5.2)
sprockets (>= 3.0.0)
sqlite3 (1.4.2)
thor (1.0.1)
thor (1.1.0)
thread_safe (0.3.6)
thwait (0.2.0)
e2mmap
@ -250,29 +279,29 @@ GEM
turbolinks (5.2.1)
turbolinks-source (~> 5.2)
turbolinks-source (5.2.0)
tzinfo (1.2.7)
tzinfo (1.2.9)
thread_safe (~> 0.1)
warden (1.2.9)
rack (>= 2.0.9)
web-console (4.1.0)
web-console (4.2.0)
actionview (>= 6.0.0)
activemodel (>= 6.0.0)
bindex (>= 0.4.0)
railties (>= 6.0.0)
webmock (3.6.0)
addressable (>= 2.3.6)
webmock (3.14.0)
addressable (>= 2.8.0)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
webpacker (4.3.0)
activesupport (>= 4.2)
rack-proxy (>= 0.6.1)
railties (>= 4.2)
websocket-driver (0.7.3)
websocket-driver (0.7.5)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
xpath (3.2.0)
nokogiri (~> 1.8)
zeitwerk (2.4.1)
zeitwerk (2.5.1)
PLATFORMS
ruby
@ -292,10 +321,12 @@ DEPENDENCIES
letter_opener
letter_opener_web
listen (~> 3.2)
lockbox
net-ldap
pg (~> 1.2.3)
puma (~> 4.1)
rails (~> 6.0.3, >= 6.0.3.4)
rqrcode (~> 2.0)
rspec-rails
sass-rails (>= 6)
sidekiq

View File

@ -0,0 +1,57 @@
class LnurlpayController < ApplicationController
before_action :find_user_by_address
def index
render json: {
status: "OK",
callback: "https://accounts.kosmos.org/lnurlpay/#{@user.address}/invoice",
tag: "payRequest",
maxSendable: 1000000,
minSendable: 1000,
metadata: metadata(@user.address),
commentAllowed: 0
Outdated
Review

we should set this to 0 because we do not allow the payer to send a comment with the payment.

we should set this to `0` because we do not allow the payer to send a comment with the payment.
}
end
def invoice
amount = params[:amount].to_i # msats
address = params[:address]
validate_amount(amount)
payment_request = @user.ln_create_invoice({
amount: amount,
Review

this must be amount/1000 - amount is in msats but we create invoices for sats.

this must be `amount/1000` - amount is in msats but we create invoices for sats.
description_hash: Digest::SHA2.hexdigest(metadata(address))
})
render json: {
status: "OK",
successAction: {
tag: "message",
message: "Payment received. Thanks!"
},
routes: [],
pr: payment_request
}
end
private
def find_user_by_address
address = params[:address].split("@")
@user = User.where(cn: address.first, ou: address.last).first
http_status :not_found if @user.nil?
end
def metadata(address)
"[[\"text/identifier\", \"#{address}\"], [\"text/plain\", \"Sats for #{address}\"]]"
end
def validate_amount(amount)
if amount > 1000000 || amount < 1000
render json: { status: "ERROR", reason: "Invalid amount" }
Review

Shouldn't this also give an error response code? Maybe 422?

Shouldn't this also give an error response code? Maybe 422?
Review

You would think so, but no. That's how ldnhub clients are expected to implement errors. ;)

You would think so, but no. That's how ldnhub clients are expected to implement errors. ;)
Review

WHUT?

WHUT? ![](https://storage.5apps.com/galfert/public/shares/170629-1354-lebowski.gif)
Review

the lnurl spec specifically says to ignore HTTP status codes :( - no idea why... but because of that it's better to return a 200

the lnurl spec specifically says to ignore HTTP status codes :( - no idea why... but because of that it's better to return a 200
return
end
end
end

View File

@ -0,0 +1,44 @@
require "rqrcode"
class WalletController < ApplicationController
before_action :require_user_signed_in
before_action :authenticate_with_lndhub
def index
@current_section = :wallet
@wallet_url = "lndhub://#{current_user.ln_login}:#{current_user.ln_password}@#{ENV['LNDHUB_PUBLIC_URL']}"
qrcode = RQRCode::QRCode.new(@wallet_url)
@svg = qrcode.as_svg(
color: "000",
shape_rendering: "crispEdges",
module_size: 6,
standalone: true,
use_path: true
)
@balance = fetch_balance rescue nil
end
private
def authenticate_with_lndhub
if session["ln_auth_token"].present?
@ln_auth_token = session["ln_auth_token"]
else
lndhub = Lndhub.new
auth_token = lndhub.authenticate(current_user)
session["ln_auth_token"] = auth_token
@ln_auth_token = auth_token
end
rescue
# TODO add exception tracking
end
def fetch_balance
lndhub = Lndhub.new
data = lndhub.balance @ln_auth_token
data["BTC"]["AvailableBalance"]
end
end

View File

@ -0,0 +1,2 @@
module LnurlpayHelper
end

View File

@ -0,0 +1,2 @@
module WalletHelper
end

View File

@ -0,0 +1,13 @@
class CreateLndhubWalletJob < ApplicationJob
queue_as :default
def perform(user)
return if user.ln_login.present? && user.ln_password.present?
lndhub = Lndhub.new
credentials = lndhub.create({ partnerid: user.ou, accounttype: "user" })
user.update! ln_login: credentials["login"],
ln_password: credentials["password"]
end
end

View File

@ -10,6 +10,9 @@ class User < ApplicationRecord
validates_uniqueness_of :email
validates :email, email: true
encrypts :ln_login
encrypts :ln_password
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :ldap_authenticatable,
@ -53,4 +56,10 @@ class User < ApplicationRecord
self.valid?
self.errors[attribute_name].blank?
end
def ln_create_invoice(payload)
lndhub = Lndhub.new
lndhub.authenticate self
lndhub.addinvoice payload
end
end

View File

@ -10,6 +10,7 @@ class CreateAccount < ApplicationService
def call
user = create_user_in_database
add_ldap_document
create_lndhub_wallet
if @invitation.present?
update_invitation(user.id)
@ -44,4 +45,8 @@ class CreateAccount < ApplicationService
return if Rails.env.development?
ExchangeXmppContactsJob.perform_later(@invitation.user, @username, @domain)
end
def create_lndhub_wallet
CreateLndhubWalletJob.perform_later(user)
end
end

57
app/services/lndhub.rb Normal file
View File

@ -0,0 +1,57 @@
class Lndhub
attr_accessor :auth_token
def initialize
@base_url = ENV["LNDHUB_API_URL"]
end
def post(endpoint, payload)
headers = { "Content-Type" => "application/json" }
if auth_token
headers.merge!({ "Authorization" => "Bearer #{auth_token}" })
end
res = Faraday.post "#{@base_url}/#{endpoint}", payload.to_json, headers
if res.status != 200
Rails.logger.error "[lndhub] API request failed:"
Rails.logger.error res.body
#TODO add some kind of exception tracking/notifications
end
JSON.parse(res.body)
end
def get(endpoint, auth_token)
res = Faraday.get("#{@base_url}/#{endpoint}", {}, {
"Content-Type" => "application/json",
"Accept" => "application/json",
"Authorization" => "Bearer #{auth_token}"
})
JSON.parse(res.body)
end
def create(payload)
post "create", payload
end
def authenticate(user)
credentials = post "auth?type=auth", { login: user.ln_login, password: user.ln_password }
self.auth_token = credentials["access_token"]
self.auth_token
end
def balance(user_token)
get "balance", user_token || auth_token
end
def addinvoice(payload)
invoice = post "addinvoice", {
amt: payload[:amount],
description_hash: payload[:description_hash]
}
invoice["payment_request"]
end
end

View File

@ -13,6 +13,10 @@
<%= link_to "Donations", donations_path,
class: @current_section == :contributions ? "active" : nil %>
</li>
<li>
<%= link_to "Wallet", wallet_path,
class: @current_section == :wallet ? "active" : nil %>
</li>
<li>
<%= link_to "Security", security_path,
class: @current_section == :security ? "active" : nil %>

View File

@ -0,0 +1,54 @@
<section class="w-full grid grid-cols-1 md:grid-cols-2">
<div>
<h2>Wallet</h2>
<p>
Send and receive BTC via the Lightning Network.
</p>
</div>
<p class="font-mono mt-4 md:text-right md:mt-0">
<% if @balance %>
<span class="text-xl"><%= @balance %> sats</span><br>
<span class="text-sm text-gray-500">Available balance</span>
<% end %>
</p>
</section>
<section>
<h3>Blue Wallet</h3>
<p>
You can connect
<%= link_to "Blue Wallet", "https://bluewallet.io",
class: "ks-text-link", target: "_blank" %>
(Android or iOS) to your Kosmos lightning wallet. In order to do so,
scan the setup QR code from the Import Wallet screen in the app.
</p>
<p>
<a id="show-setup-code" class="ks-text-link cursor-pointer">Show setup code</a>
<a id="hide-setup-code" class="ks-text-link cursor-pointer" style="display: none;">Hide setup code</a>
</p>
<p id="setup-code" style="display: none;">
<%= raw @svg %>
</p>
</section>
<script type="text/javascript">
let linkShow = document.querySelector('#show-setup-code');
let linkHide = document.querySelector('#hide-setup-code');
let setupCode = document.querySelector('#setup-code');
linkShow.addEventListener('click', function(ev) {
ev.preventDefault();
setupCode.style.display = 'block';
linkShow.style.display = 'none';
linkHide.style.display = 'block';
window.scrollTo(0, document.body.scrollHeight);
});
linkHide.addEventListener('click', function(ev) {
ev.preventDefault();
const el = document.querySelector('#setup-code');
setupCode.style.display = 'none';
linkShow.style.display = 'block';
linkHide.style.display = 'none';
});
</script>

View File

@ -1 +1 @@
LXwVUXfG3P3NMBp56xEQHKY4YABoVvECgctVLuj4XRkCUKQURUC3pt60yBbMfw8Qs7yH1oeNiUv5ouoNrxR35PiquFr05oIPoHbHewB/aRShe7y313q1iXfddfYnom9r7wEETkeuFLBULNrLjJjzlgJgB8A9lmcSKMy9lEdGKJ5LghA6pEGq4KRxlixr8vC8ExcDtdENuxSWPU97cc5YHGjTTaiB9XIM61xtGcBUcREaizSyzloeRjPee71GfWKfVx3XNTQDoKfs6cecWhrSLlOVjsWGoiGlwpff3cPo1pSuYIoNo/YRnfHEr1WTw/+R3nc0wgPMBZSmFLK/8Uunzr8T6Q3vqqWB1QnKBquOcASa8jRaPFBs5k8PUeCT6aYdVBV0GSOGlyll2dBtfwrZgt2ssk24BuqK5pru5vIdhWNR7zl3+R0o2ABLTEcBSuxe754NiPasEKRQ8/K4lojOjnOcwdhdqC7jN2RVS5G03ZgHEqk1m+Z9svtu9PaRAD8I9vrpCQfq9Bifm2qZUSBJrgexHtDnSMfg/36hCUeyd2z0naoq3IWJ/xyCf1S5uzyd5MPh7N76L9FudD5nK39Jrj+yuVayAXQ1M9K89kSCiC2/ZbBE--TCbSksW4umkXZY62--jE6cZdM44o8/AWe7alnotA==
PRI1laGy6XiLpsII0ixFOTCpzzDbrRpIQ7GJ2flvA+UEEiPVQQ4e/pt6ZjumaZ/geTBHCRZklLtS1u6HjucVKHGSR4K/lE1q7u6nfi/wAvwGQdCTJeKf8PetdlsiVZg2OW4Wi2YF7qtb1ipGS/uRlOeLP8JOwp5BNVS1J6tvNX4T5gA7rrB4Cvtfwp1dwLWODHiXbeZK7SN2j7V3NPGZl0LbNg2jquC4DzQOBhMOlwu1IAgOAcqKbHh9tDabpVOcOD5pNSaVaoBO01P8ObbX8x0S0Ug7BPkM5nnyJTQdNH7LHKvtNmrUEk7+fd+VZR6WqEp6SFBoAA7XxvOLy9dIOqvXbAYnrNZX7iI4IpgZWRNIQ7G1qSBjpyPLKC7+nSPmvd4IcgCHVUBdYp0+yYDxLzwuuhPVtyAY4JAIdZY1mhYxx7BzGNiMc3p8AlMS6hPeO9lVQZykViBGnTjR+iah9OQYAihX9FYywtBkNvMXLiYqsVZmv6em9Uk/ivKSIsaFwNwrelCI5H4Q/+/hKFJ1JaMjbYheXCJgcGgfhiC+fIHig/8y8rXd9lWGa1T+PrpA8akcq/K8I6gbxYxpLWcy5Vnz/SyaMurLPVfCq4cD/JfpLSfy0HophaiycZ7D6dr43rnHsPfrt6kveDOgWUK9d7CqHxsWdXbCpJeBoh8/1usIsGsmyhfwHq0dEpRx78bR2EJeM4LOAuQgf69/5rFJydIbXq76l1BftTls1Pgh--OI8c/RQGQ30wT1Ff--M/ltnmdl+FRdlNliTb79lg==

View File

@ -0,0 +1 @@
Ga2G0A==--RCOGf20OFDHTm7qw--PLI8AZQQylfrplQVYg603Q==

View File

@ -1,7 +1,6 @@
require 'sidekiq/web'
Rails.application.routes.draw do
resources :donations
devise_for :users
get 'welcome', to: 'welcome#index'
@ -18,6 +17,13 @@ Rails.application.routes.draw do
resources :invitations, only: ['index', 'show', 'create', 'destroy']
resources :donations
get 'wallet', to: 'wallet#index'
get 'lnurlpay/:address', to: 'lnurlpay#index', constraints: { address: /[^\/]+/}
get 'lnurlpay/:address/invoice', to: 'lnurlpay#invoice', constraints: { address: /[^\/]+/}
namespace :admin do
root to: 'dashboard#index'
get 'invitations', to: 'invitations#index'

View File

@ -0,0 +1,6 @@
class AddLndhubCredentialsToUser < ActiveRecord::Migration[6.0]
def change
add_column :users, :ln_login_ciphertext, :text
add_column :users, :ln_password_ciphertext, :text
end
end

View File

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2020_12_19_121808) do
ActiveRecord::Schema.define(version: 2021_11_20_010540) do
create_table "donations", force: :cascade do |t|
t.integer "user_id"
@ -45,6 +45,8 @@ ActiveRecord::Schema.define(version: 2020_12_19_121808) do
t.datetime "confirmed_at"
t.datetime "confirmation_sent_at"
t.string "unconfirmed_email"
t.text "ln_login_ciphertext"
t.text "ln_password_ciphertext"
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end

8
lib/tasks/lndhub.rake Normal file
View File

@ -0,0 +1,8 @@
namespace :lndhub do
desc "Generate wallets for all users"
task :generate_wallets => :environment do |t, args|
User.all.each do |user|
CreateLndhubWalletJob.perform_later(user)
end
end
end

View File

@ -0,0 +1,48 @@
require 'rails_helper'
require 'webmock/rspec'
RSpec.describe CreateLndhubWalletJob, type: :job do
let(:user) { create :user, cn: "willherschel", ou: "kosmos.org" }
subject(:job) { described_class.perform_later(user) }
before do
stub_request(:post, "http://localhost:3023/create")
.to_return(status: 200, headers: {},
body: { login: "abc123", password: "def456" }.to_json)
end
it "creates a new LndHub account" do
perform_enqueued_jobs { job }
expect(WebMock).to have_requested(:post, "http://localhost:3023/create")
.with { |req| req.body == '{"partnerid":"kosmos.org","accounttype":"user"}' }
user.reload
expect(user.ln_login).to eq("abc123")
expect(user.ln_password).to eq("def456")
end
context "with existing credentials stored" do
before do
user.ln_login = "foo"
user.ln_password = "bar"
user.save!
end
it "does not create a new LndHub account" do
perform_enqueued_jobs { job }
expect(WebMock).to_not have_requested(:post, "http://localhost:3023/create")
user.reload
expect(user.ln_login).to eq("foo")
expect(user.ln_password).to eq("bar")
end
end
after do
clear_enqueued_jobs
clear_performed_jobs
end
end