diff --git a/.env.example b/.env.example index 5e587f2..ed6e513 100644 --- a/.env.example +++ b/.env.example @@ -1 +1,3 @@ EJABBERD_API_URL='https://xmpp.kosmos.org/api' +LNDHUB_API_URL='http://localhost:3023' +LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org' diff --git a/.env.production b/.env.production index 1ce4287..e9bd695 100644 --- a/.env.production +++ b/.env.production @@ -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' diff --git a/.gitignore b/.gitignore index aaab8ab..73425d2 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,5 @@ yarn-debug.log* # Ignore redis dumps from sidekiq dump.rdb + +/config/credentials/development.key diff --git a/Gemfile b/Gemfile index 6d0b53d..a0f169c 100644 --- a/Gemfile +++ b/Gemfile @@ -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' diff --git a/Gemfile.lock b/Gemfile.lock index 6a4c8c2..7608227 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -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 diff --git a/app/controllers/lnurlpay_controller.rb b/app/controllers/lnurlpay_controller.rb new file mode 100644 index 0000000..66e364b --- /dev/null +++ b/app/controllers/lnurlpay_controller.rb @@ -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 + } + end + + def invoice + amount = params[:amount].to_i # msats + address = params[:address] + + validate_amount(amount) + + payment_request = @user.ln_create_invoice({ + amount: amount, + 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" } + return + end + end + +end diff --git a/app/controllers/wallet_controller.rb b/app/controllers/wallet_controller.rb new file mode 100644 index 0000000..67d6abe --- /dev/null +++ b/app/controllers/wallet_controller.rb @@ -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 diff --git a/app/helpers/lnurlpay_helper.rb b/app/helpers/lnurlpay_helper.rb new file mode 100644 index 0000000..45170fd --- /dev/null +++ b/app/helpers/lnurlpay_helper.rb @@ -0,0 +1,2 @@ +module LnurlpayHelper +end diff --git a/app/helpers/wallet_helper.rb b/app/helpers/wallet_helper.rb new file mode 100644 index 0000000..9bd2230 --- /dev/null +++ b/app/helpers/wallet_helper.rb @@ -0,0 +1,2 @@ +module WalletHelper +end diff --git a/app/jobs/create_lndhub_wallet_job.rb b/app/jobs/create_lndhub_wallet_job.rb new file mode 100644 index 0000000..30b4778 --- /dev/null +++ b/app/jobs/create_lndhub_wallet_job.rb @@ -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 diff --git a/app/models/user.rb b/app/models/user.rb index 046b948..0a0261d 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -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 diff --git a/app/services/create_account.rb b/app/services/create_account.rb index 64901d4..51661db 100644 --- a/app/services/create_account.rb +++ b/app/services/create_account.rb @@ -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 diff --git a/app/services/lndhub.rb b/app/services/lndhub.rb new file mode 100644 index 0000000..5706148 --- /dev/null +++ b/app/services/lndhub.rb @@ -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 diff --git a/app/views/shared/_main_nav.html.erb b/app/views/shared/_main_nav.html.erb index bd2a872..0da9406 100644 --- a/app/views/shared/_main_nav.html.erb +++ b/app/views/shared/_main_nav.html.erb @@ -13,6 +13,10 @@ <%= link_to "Donations", donations_path, class: @current_section == :contributions ? "active" : nil %> +
  • + <%= link_to "Wallet", wallet_path, + class: @current_section == :wallet ? "active" : nil %> +
  • <%= link_to "Security", security_path, class: @current_section == :security ? "active" : nil %> diff --git a/app/views/wallet/index.html.erb b/app/views/wallet/index.html.erb new file mode 100644 index 0000000..fda7bd6 --- /dev/null +++ b/app/views/wallet/index.html.erb @@ -0,0 +1,54 @@ +
    +
    +

    Wallet

    +

    + Send and receive BTC via the Lightning Network. +

    +
    +

    + <% if @balance %> + <%= @balance %> sats
    + Available balance + <% end %> +

    +
    + +
    +

    Blue Wallet

    +

    + 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. +

    +

    + Show setup code + +

    + +
    + + diff --git a/config/credentials.yml.enc b/config/credentials.yml.enc index 998d8e2..a49b81a 100644 --- a/config/credentials.yml.enc +++ b/config/credentials.yml.enc @@ -1 +1 @@ -LXwVUXfG3P3NMBp56xEQHKY4YABoVvECgctVLuj4XRkCUKQURUC3pt60yBbMfw8Qs7yH1oeNiUv5ouoNrxR35PiquFr05oIPoHbHewB/aRShe7y313q1iXfddfYnom9r7wEETkeuFLBULNrLjJjzlgJgB8A9lmcSKMy9lEdGKJ5LghA6pEGq4KRxlixr8vC8ExcDtdENuxSWPU97cc5YHGjTTaiB9XIM61xtGcBUcREaizSyzloeRjPee71GfWKfVx3XNTQDoKfs6cecWhrSLlOVjsWGoiGlwpff3cPo1pSuYIoNo/YRnfHEr1WTw/+R3nc0wgPMBZSmFLK/8Uunzr8T6Q3vqqWB1QnKBquOcASa8jRaPFBs5k8PUeCT6aYdVBV0GSOGlyll2dBtfwrZgt2ssk24BuqK5pru5vIdhWNR7zl3+R0o2ABLTEcBSuxe754NiPasEKRQ8/K4lojOjnOcwdhdqC7jN2RVS5G03ZgHEqk1m+Z9svtu9PaRAD8I9vrpCQfq9Bifm2qZUSBJrgexHtDnSMfg/36hCUeyd2z0naoq3IWJ/xyCf1S5uzyd5MPh7N76L9FudD5nK39Jrj+yuVayAXQ1M9K89kSCiC2/ZbBE--TCbSksW4umkXZY62--jE6cZdM44o8/AWe7alnotA== \ No newline at end of file +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== \ No newline at end of file diff --git a/config/credentials/development.yml.enc b/config/credentials/development.yml.enc new file mode 100644 index 0000000..e484099 --- /dev/null +++ b/config/credentials/development.yml.enc @@ -0,0 +1 @@ +Ga2G0A==--RCOGf20OFDHTm7qw--PLI8AZQQylfrplQVYg603Q== \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index 2a3592d..8843058 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -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' diff --git a/db/migrate/20211120010540_add_lndhub_credentials_to_user.rb b/db/migrate/20211120010540_add_lndhub_credentials_to_user.rb new file mode 100644 index 0000000..85888e7 --- /dev/null +++ b/db/migrate/20211120010540_add_lndhub_credentials_to_user.rb @@ -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 diff --git a/db/schema.rb b/db/schema.rb index db024da..0a26607 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -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 diff --git a/lib/tasks/lndhub.rake b/lib/tasks/lndhub.rake new file mode 100644 index 0000000..5b30082 --- /dev/null +++ b/lib/tasks/lndhub.rake @@ -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 diff --git a/spec/jobs/create_lndhub_wallet_job_spec.rb b/spec/jobs/create_lndhub_wallet_job_spec.rb new file mode 100644 index 0000000..4b64ba1 --- /dev/null +++ b/spec/jobs/create_lndhub_wallet_job_spec.rb @@ -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