12 Commits

Author SHA1 Message Date
Râu Cao
332ad757a5 Use respond_to for request formats
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-06-13 12:30:38 +02:00
Râu Cao
07fe8dba71 Add a copy button for the Lightning address
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Same as on profile settings page.
2023-06-12 18:18:47 +02:00
Râu Cao
aedaabc7ba Offer lnurl-pay QR codes for download on the Lightning page 2023-06-12 18:18:06 +02:00
Râu Cao
8eb5f093a4 Don't show flash message when opening the root URL while signed out 2023-06-08 08:04:23 +03:00
de45d070aa Merge pull request 'Report Lndhub API errors to Sentry' (#133) from refactor/lndhub_integration into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #133
2023-06-06 15:44:36 +00:00
c0b1112e49 Merge pull request 'Hide unsuccessful outgoing lndhub txs in list' (#132) from bugfix/lndhub_tx_list into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #132
2023-06-06 15:43:38 +00:00
Râu Cao
2f90393eb6 Lndhub v2 service inherits from v1, only adds v2-specific code
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 3s
2023-06-05 13:53:24 +03:00
Râu Cao
8b87072485 Raise custom auth error, re-raise on failed re-auth 2023-06-05 13:52:41 +03:00
Râu Cao
82019f47be Report lndhub errors to Sentry 2023-06-05 13:51:59 +03:00
Râu Cao
259e72167b Hide unsuccessful outgoing lndhub txs in list
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 4s
2023-06-05 13:06:49 +03:00
Râu Cao
7000908891 Auto-login Discourse link
All checks were successful
continuous-integration/drone/push Build is passing
2023-06-04 15:15:09 +03:00
Râu Cao
df0c13b400 Fix potential nil access
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-31 14:43:00 +02:00
11 changed files with 119 additions and 87 deletions

View File

@@ -58,6 +58,9 @@ gem 'discourse_api'
gem "sentry-ruby"
gem "sentry-rails"
# Lightning
gem "lnurl"
group :development, :test do
# Use sqlite3 as the database for Active Record
gem 'sqlite3', '~> 1.4'

View File

@@ -180,6 +180,8 @@ GEM
listen (3.7.1)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
lnurl (1.0.1)
bech32 (~> 1.1)
lockbox (1.1.0)
loofah (2.19.0)
crass (~> 1.0.2)
@@ -409,6 +411,7 @@ DEPENDENCIES
letter_opener
letter_opener_web
listen (~> 3.2)
lnurl
lockbox
net-ldap
pagy (~> 6.0, >= 6.0.2)

View File

@@ -1,5 +1,5 @@
class DashboardController < ApplicationController
before_action :authenticate_user!
before_action :require_user_signed_in
def index
@current_section = :services

View File

@@ -1,4 +1,5 @@
require "rqrcode"
require "lnurl"
class Services::LightningController < ApplicationController
before_action :authenticate_user!
@@ -8,9 +9,56 @@ class Services::LightningController < ApplicationController
def index
@wallet_url = "lndhub://#{current_user.ln_account}:#{current_user.ln_password}@#{ENV['LNDHUB_PUBLIC_URL']}"
initialize_lndhub_qr_code
end
qrcode = RQRCode::QRCode.new(@wallet_url)
@svg = qrcode.as_svg(
def transactions
@transactions = fetch_transactions
end
def qr_lnurlp
respond_to do |format|
lnurlp_url = "https://kosmos.org/.well-known/lnurlp/#{current_user.cn}"
lnurlp_bech32 = Lnurl.new(lnurlp_url).to_bech32
qr_code = RQRCode::QRCode.new("lightning:" + lnurlp_bech32)
format.svg do
qr_svg = qr_code.as_svg(
color: "000",
shape_rendering: "crispEdges",
module_size: 6,
standalone: true,
use_path: true,
svg_attributes: {
class: 'inline-block'
}
)
send_data(
qr_svg,
filename: "bitcoin-lightning-#{current_user.address}.svg",
type: "image/svg+xml"
)
end
format.png do
qr_png = qr_code.as_png(
fill: "white",
color: "black",
size: 1024,
)
send_data(
qr_png,
filename: "bitcoin-lightning-#{current_user.address}.png",
type: "image/png"
)
end
end
end
private
def initialize_lndhub_qr_code
qr_code = RQRCode::QRCode.new(@wallet_url)
@lndhub_qr_svg = qr_code.as_svg(
color: "000",
shape_rendering: "crispEdges",
module_size: 6,
@@ -22,12 +70,6 @@ class Services::LightningController < ApplicationController
)
end
def transactions
@transactions = fetch_transactions
end
private
def authenticate_with_lndhub(options={})
if session[:ln_auth_token].present? && !options[:force_reauth]
@ln_auth_token = session[:ln_auth_token]
@@ -37,8 +79,8 @@ class Services::LightningController < ApplicationController
session[:ln_auth_token] = auth_token
@ln_auth_token = auth_token
end
rescue
# TODO add exception tracking
rescue => e
Sentry.capture_exception(e) if Setting.sentry_enabled?
end
def set_current_section
@@ -49,9 +91,9 @@ class Services::LightningController < ApplicationController
lndhub = Lndhub.new
data = lndhub.balance @ln_auth_token
@balance = data["BTC"]["AvailableBalance"] rescue nil
rescue
rescue AuthError
authenticate_with_lndhub(force_reauth: true)
return nil if @fetch_balance_retried
raise if @fetch_balance_retried
@fetch_balance_retried = true
fetch_balance
end
@@ -61,9 +103,9 @@ class Services::LightningController < ApplicationController
txs = lndhub.gettxs @ln_auth_token
invoices = lndhub.getuserinvoices(@ln_auth_token).select{|i| i["ispaid"]}
process_transactions(txs + invoices)
rescue
rescue AuthError
authenticate_with_lndhub(force_reauth: true)
return [] if @fetch_transactions_retried
raise if @fetch_transactions_retried
@fetch_transactions_retried = true
fetch_transactions
end
@@ -86,6 +128,10 @@ class Services::LightningController < ApplicationController
end
end
# Handle an edge case where lndhub.go includes a failed payment in the
# list, which wasn't actually booked
txs.reject!{ |tx| tx["type"] == "paid_invoice" && tx["payment_preimage"].blank? }
txs.sort{ |a,b| b["datetime"] <=> a["datetime"] }
end
end

1
app/errors/auth_error.rb Normal file
View File

@@ -0,0 +1 @@
class AuthError < StandardError; end

View File

@@ -12,12 +12,7 @@ class Lndhub
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
log_error(res) if res.status != 200
JSON.parse(res.body)
end
@@ -31,7 +26,7 @@ class Lndhub
data = JSON.parse(res.body)
if data.is_a?(Hash) && data["error"] && data["message"] == "bad auth"
raise "BAD_AUTH"
raise AuthError
else
data
end
@@ -68,4 +63,13 @@ class Lndhub
invoice["payment_request"]
end
def log_error(res)
Rails.logger.error "[lndhub] API request failed:"
Rails.logger.error res.body
if Setting.sentry_enabled?
Sentry.capture_message("Lndhub API request failed: #{res.body}")
end
end
end

View File

@@ -1,9 +1,4 @@
class LndhubV2
attr_accessor :auth_token
def initialize
@base_url = ENV["LNDHUB_API_URL"]
end
class LndhubV2 < Lndhub
def post(endpoint, payload, options={})
headers = { "Content-Type" => "application/json" }
@@ -12,64 +7,12 @@ class LndhubV2
elsif options[:admin_token]
headers.merge!({ "Authorization" => "Bearer #{options[:admin_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
log_error(res) if res.status != 200
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_account, password: user.ln_password }
self.auth_token = credentials["access_token"]
self.auth_token
end
def balance(user_token=nil)
get "balance", user_token || auth_token
end
def gettxs(user_token)
get "gettxs", user_token || auth_token
end
def getuserinvoices(user_token)
get "getuserinvoices", user_token || auth_token
end
def addinvoice(payload)
invoice = post "addinvoice", {
amt: payload[:amount],
memo: payload[:memo],
description_hash: payload[:description_hash]
}
invoice["payment_request"]
end
#
# V2
#
def create_account(payload={})
post "v2/users", payload, admin_token: Rails.application.credentials.lndhub[:admin_token]
end
@@ -78,4 +21,5 @@ class LndhubV2
# Payload: { amount: 1000, description: "", description_hash: "" }
post "v2/invoices", payload
end
end

View File

@@ -21,7 +21,7 @@
<div class="border border-gray-300 rounded-md hover:border-gray-400
bg-[length:95%] bg-center bg-no-repeat
bg-[url(/img/logos/icon_discourse.svg)]">
<%= link_to "https://community.kosmos.org",
<%= 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">Discourse</h3>
<p class="text-gray-600">

View File

@@ -1,6 +1,6 @@
<%
# TODO remove when https://github.com/hotwired/turbo/issues/203 is fixed
enable_turbo = !session[:user_return_to].match?('/discourse/connect')
enable_turbo = !session[:user_return_to] || !session[:user_return_to].match?('/discourse/connect')
%>
<%= render HeaderCompactComponent.new(title: "Log in") %>

View File

@@ -7,13 +7,25 @@
<section>
<h3>Lightning Address</h3>
<p>
<p class="mb-6">
Your Kosmos user address is also a
<a class="ks-text-link" href="https://lightningaddress.com/" target="_blank">Lightning Address</a>!
The easiest way to receive sats is by just giving out your address:
</p>
<p>
<strong><%= current_user.address %></strong>
<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-blue 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-white h-4 w-4 inline" } %>
</span>
<span class="content-active hidden">
<%= render partial: "icons/check", locals: { custom_class: "text-white h-4 w-4 inline" } %>
</span>
</button>
</p>
</section>
@@ -39,7 +51,7 @@
<button id="hide-setup-code" class="hidden btn-md btn-blue w-full sm:w-auto">Hide setup QR code</button>
</p>
<p id="setup-code" class="hidden my-10 w-full text-center">
<%= raw @svg %>
<%= raw @lndhub_qr_svg %>
</p>
</section>
@@ -88,6 +100,24 @@
</p>
</div>
</section>
<section class="mb-12">
<h3>QR Code for Donations/Tips</h3>
<p>
You can print out or publish a QR code for people to scan with their
wallet apps, so they can send you sats without a direct personal
interaction (for example at a concert, or on your website).
</p>
<p class="my-6 text-center md:text-left">
<%= link_to "Download SVG file",
qr_lnurlp_services_lightning_index_path(format: "svg"),
class: "btn-md btn-blue w-full sm:w-auto"%>
<span class="mx-2 my-2 md:my-0 block md:inline">or</span>
<%= link_to "Download PNG file",
qr_lnurlp_services_lightning_index_path(format: "png"),
class: "btn-md btn-blue w-full sm:w-auto"%>
</p>
</section>
<% end %>
<script type="text/javascript">

View File

@@ -24,6 +24,7 @@ Rails.application.routes.draw do
resources :lightning, only: [:index] do
collection do
get 'transactions'
get 'qr_lnurlp'
end
end
end