66 Commits

Author SHA1 Message Date
Râu Cao
d4a3f8dadb Fix spec after renaming job
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-01-12 11:50:13 +08:00
Râu Cao
9e988e92d1 Notify user about incoming sats via XMPP
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2023-01-12 11:44:55 +08:00
Râu Cao
4232df302b Add send_message to ejabberd service 2023-01-12 11:44:28 +08:00
Râu Cao
2c8b3cdacc Rename job 2023-01-12 11:43:30 +08:00
Râu Cao
51952ecdc2 Add migration for unencrypted ln login field
All checks were successful
continuous-integration/drone/push Build is passing
2023-01-11 19:50:01 +08:00
Râu Cao
68e0d00f6e WIP Add Webhooks controller, allowed IP config
All checks were successful
continuous-integration/drone/push Build is passing
2023-01-11 19:17:27 +08:00
2e1d930e0f Merge pull request 'Docker Compose config, local 389ds/dirsrv, LDAP and user seeds' (#74) from feature/docker_compose into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #74
2022-12-27 06:26:43 +00:00
d849d28f62 Merge pull request 'Add support and migration for lndhub.go' (#77) from feature/73-lndhub-go into feature/docker_compose
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Reviewed-on: #77
2022-12-27 06:25:37 +00:00
Râu Cao
f2a22adf6b Switch legacy to lndhub.go
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Temporary fix
2022-12-23 17:42:20 +07:00
Râu Cao
e1aaa2c434 Re-authorize when token is invalid 2022-12-23 17:42:17 +07:00
Râu Cao
e62bf67262 Use v2 API for creating new lndhub accounts
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2022-12-23 12:39:57 +07:00
Râu Cao
6df3d5933c Update test env
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2022-12-22 20:11:38 +07:00
Râu Cao
a5a90c4d83 Add support and migration for lndhub.go
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
Slightly WIP
2022-12-22 20:01:14 +07:00
Râu Cao
80ef75ff42 Improve README, add quick start instructions
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2022-12-07 18:15:04 +01:00
Râu Cao
67e2e45dd8 Remove pid dir from git 2022-12-07 18:14:49 +01:00
Râu Cao
3834e5230b Comment encryption option in admin ldap users controller
Refactor to use the service later
2022-12-07 18:13:58 +01:00
Râu Cao
4cb7c0998f Add db/user seeds 2022-12-07 18:12:54 +01:00
Râu Cao
20382f7df7 Rename ldap seed task to setup 2022-12-07 18:11:57 +01:00
Râu Cao
add94eee8d Don't start phpldapadmin by default 2022-12-07 18:11:23 +01:00
Râu Cao
067dc3b63d Remove obsolete method 2022-12-07 18:11:03 +01:00
Râu Cao
1a470cf1c8 Add flag for creating pre-confirmed users 2022-12-07 18:09:44 +01:00
Râu Cao
f85b7f4f62 Define patch version for Ruby base image
No need to re-download new images for every patch version
2022-12-07 18:07:53 +01:00
Râu Cao
8635413002 Delete admin role manually on reset
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2022-12-07 15:20:34 +01:00
Râu Cao
a3da956b48 Add missing ACI and role to LDAP seeds
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2022-12-07 14:27:51 +01:00
Râu Cao
3c40dc98ca Add note about resetting LDAP server
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2022-12-06 10:28:34 +01:00
28b31e63f9 Merge pull request 'Update Docker image in CI' (#75) from chore/ci_image_upgrade into feature/docker_compose
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Reviewed-on: #75
2022-12-06 09:23:05 +00:00
Râu Cao
efafd38f68 Update Docker image in CI
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
We need a newer node.js version.
2022-12-06 10:19:47 +01:00
Râu Cao
537e1a4774 Update database schema (from Rails upgrade)
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2022-12-05 13:36:49 +01:00
Râu Cao
c3b9ff8b4a Add LDAP service and seed task 2022-12-05 13:36:33 +01:00
Râu Cao
93d56f79d5 Add config and documentation for running dirsrv with Docker 2022-12-05 13:35:30 +01:00
Râu Cao
1a30345f46 Add byebug for debugging in development 2022-12-05 13:20:47 +01:00
Râu Cao
778babcc05 Add Docker Compose config and 389ds service
Some checks failed
continuous-integration/drone/push Build is failing
refs #2
2022-12-02 19:21:13 +01:00
Râu Cao
fa3b53d3b3 Add Dockerfile for development 2022-12-02 19:19:02 +01:00
Râu Cao
0ca85656b7 Update dependencies 2022-12-02 19:16:56 +01:00
Râu Cao
f7183f68d5 Decrease mininum sats for Lighting invoices
All checks were successful
continuous-integration/drone/push Build is passing
2022-09-16 11:20:29 +02:00
87027b514b Merge pull request 'Update gems' (#72) from chore/update_gems into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #72
2022-07-27 13:47:33 +00:00
Râu Cao
16ad621365 Update gems
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
In particular Rails due to security updates:

https://rubyonrails.org/2022/7/12/Rails-Versions-7-0-3-1-6-1-6-1-6-0-5-1-and-5-2-8-1-have-been-released
2022-07-27 15:22:24 +02:00
33e87d6472 Merge pull request 'Add BTCPay service, Kredits API' (#71) from feature/community_funds_balance into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #71
Reviewed-by: bumi <bumi@noreply.kosmos.org>
2022-06-12 05:15:05 +00:00
03dc6c7a9c Log unexpected kredits API errors
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2022-05-24 13:42:00 +02:00
897b5bf4ea Specify whole API base URL in config
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2022-05-23 22:49:39 +02:00
caea2d0121 Add kredits API with wallet balance endpoint
All checks were successful
continuous-integration/drone/push Build is passing
2022-05-23 22:47:08 +02:00
e1ff5c479e Initial BTCPay integration 2022-05-23 21:35:03 +02:00
9b3386de30 Update credentials 2022-05-23 18:49:37 +02:00
f2287c1186 Remove separate development credentials files 2022-05-23 18:49:22 +02:00
b29197cf4e Merge pull request 'Various UI improvements' (#70) from feature/ui_improvements into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #70
2022-04-28 13:05:10 +00:00
5c48055ac8 Use feather icon for wallet on dashboard
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
fixes #51
2022-04-28 15:01:20 +02:00
5ead3476b7 Normalize overall (font) size
The entire UI is a bit too large. This normalizes the font size and
dimensions, so it doesn't look zoomed in on most screens.
2022-04-28 14:56:03 +02:00
fbf163740a Merge pull request 'Replace the LDAP production config for the new server' (#69) from chore/new_ldap_server into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #69
2022-04-28 10:11:01 +00:00
Greg Karékinian
1fc1457e97 Replace the LDAP production config for the new server
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Refs kosmos/chef#227
2022-04-28 11:54:14 +02:00
1f57bbd9c2 Merge pull request 'Add admin task to list LndHub balances' (#68) from feature/list_lndhub_balances into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #68
2022-04-18 08:41:40 +00:00
2a2793ae44 Print sum of user balances
All checks were successful
continuous-integration/drone/pr Build is passing
2022-04-12 16:05:46 +02:00
8773bf5f9e Slow down LndHub auth requests in task 2022-04-12 15:42:44 +02:00
d9970c126a List balances of LndHub accounts 2022-04-12 15:36:45 +02:00
4e0d4bf86d 0.4.0
All checks were successful
continuous-integration/drone/push Build is passing
2022-03-17 14:59:07 -06:00
333bcbfe7e Remove Sass dependency
All checks were successful
continuous-integration/drone/push Build is passing
2022-03-17 13:30:10 -06:00
875af6d14c Merge pull request 'Add transaction history view to wallet' (#66) from feature/wallet_history into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #66
2022-03-17 19:28:58 +00:00
8f87a03060 Merge pull request 'Finish Tailwind migration' (#67) from chore/finish_tailwind_migration into feature/wallet_history
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Reviewed-on: #67
2022-03-17 19:27:52 +00:00
7838fe5f34 Remove legacy CSS build from task
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2022-03-17 13:26:36 -06:00
512798d122 Port last remaining styles from legacy to Tailwind
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is failing
2022-03-17 13:24:13 -06:00
384c28aaaa Build PRs for all branches
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2022-03-17 13:06:33 -06:00
8e5d6dabdc Port most remaining legacy styles to Tailwind
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2022-03-11 10:15:09 -06:00
ade9261c2c Remove obsolete CSS 2022-03-11 09:52:11 -06:00
bd2a161306 Add tab menu to wallet pages
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2022-03-02 19:18:28 -06:00
78c243c985 Add wallet transactions
All checks were successful
continuous-integration/drone/push Build is passing
2022-03-02 18:43:22 -06:00
cf62bfc5c2 WIP Add wallet transactions route, view
All checks were successful
continuous-integration/drone/push Build is passing
Adds a new component for the wallet summary as well, and makes the
component tests work with RSpec.
2022-03-02 15:31:39 -06:00
10f179a095 Port shared CSS for tables to Tailwind 2022-03-02 15:30:50 -06:00
73 changed files with 1504 additions and 972 deletions

View File

@@ -17,7 +17,7 @@ steps:
branch: branch:
- master - master
- name: rspec - name: rspec
image: guildeducation/rails:2.7.2-12.22.0 image: guildeducation/rails:2.7.2-14.20.0
environment: environment:
RAILS_ENV: test RAILS_ENV: test
commands: commands:
@@ -29,9 +29,6 @@ steps:
- yarn install - yarn install
- rake css:build - rake css:build
- rake spec - rake spec
when:
branch:
- master
- name: rebuild-cache - name: rebuild-cache
image: drillster/drone-volume-cache image: drillster/drone-volume-cache
volumes: volumes:

View File

@@ -1,3 +1,13 @@
LDAP_HOST=localhost
LDAP_PORT=389
LDAP_ADMIN_PASSWORD=passthebutter
LDAP_SUFFIX="dc=kosmos,dc=org"
EJABBERD_API_URL='https://xmpp.kosmos.org/api' EJABBERD_API_URL='https://xmpp.kosmos.org/api'
BTCPAY_API_URL='http://localhost:23001/api/v1'
LNDHUB_API_URL='http://localhost:3023' LNDHUB_API_URL='http://localhost:3023'
LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org' LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org'
WEBHOOKS_ALLOWED_IPS='10.1.1.163'

View File

@@ -1,3 +1,9 @@
EJABBERD_API_URL='https://xmpp.kosmos.org:5443/api' EJABBERD_API_URL='https://xmpp.kosmos.org:5443/api'
LNDHUB_API_URL='http://10.1.1.163:3023'
BTCPAY_API_URL='http://10.1.1.163:23001/api/v1'
LNDHUB_LEGACY_API_URL='http://10.1.1.163:3026'
LNDHUB_API_URL='http://10.1.1.163:3026'
LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org' LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org'
WEBHOOKS_ALLOWED_IPS='10.1.1.163'

View File

@@ -1,3 +1,9 @@
EJABBERD_API_URL='http://xmpp.example.com/api' EJABBERD_API_URL='http://xmpp.example.com/api'
LNDHUB_API_URL='http://localhost:3023'
BTCPAY_API_URL='http://btcpay.example.com/api/v1'
LNDHUB_LEGACY_API_URL='http://localhost:3023'
LNDHUB_API_URL='http://localhost:3026'
LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org' LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org'
WEBHOOKS_ALLOWED_IPS='10.1.1.23'

21
Dockerfile Normal file
View File

@@ -0,0 +1,21 @@
# syntax=docker/dockerfile:1
FROM ruby:2.7.6
RUN apt-get update -qq && apt-get install -y curl ldap-utils
RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -
RUN apt-get update && apt-get install -y nodejs
WORKDIR /akkounts
COPY Gemfile /akkounts/Gemfile
COPY Gemfile.lock /akkounts/Gemfile.lock
COPY package.json /akkounts/package.json
RUN bundle install
RUN npm install -g yarn
RUN yarn install
# Add a script to be executed every time the container starts.
COPY docker/entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000
# Configure the main process to run when running the image
CMD ["bin", "dev"]

View File

@@ -43,13 +43,14 @@ gem "rqrcode", "~> 2.0"
gem 'faraday' gem 'faraday'
# Background/scheduled jobs # Background/scheduled jobs
gem 'sidekiq' gem 'sidekiq', '< 7'
gem 'sidekiq-scheduler' gem 'sidekiq-scheduler'
group :development, :test do group :development, :test do
# Use sqlite3 as the database for Active Record # Use sqlite3 as the database for Active Record
gem 'sqlite3', '~> 1.4' gem 'sqlite3', '~> 1.4'
gem 'rspec-rails' gem 'rspec-rails'
gem "byebug", "~> 11.1"
end end
group :development do group :development do
@@ -58,6 +59,7 @@ group :development do
gem 'listen', '~> 3.2' gem 'listen', '~> 3.2'
gem 'letter_opener' gem 'letter_opener'
gem 'letter_opener_web' gem 'letter_opener_web'
gem 'faker'
end end
group :test do group :test do

View File

@@ -1,77 +1,78 @@
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
actioncable (7.0.2.2) actioncable (7.0.4)
actionpack (= 7.0.2.2) actionpack (= 7.0.4)
activesupport (= 7.0.2.2) activesupport (= 7.0.4)
nio4r (~> 2.0) nio4r (~> 2.0)
websocket-driver (>= 0.6.1) websocket-driver (>= 0.6.1)
actionmailbox (7.0.2.2) actionmailbox (7.0.4)
actionpack (= 7.0.2.2) actionpack (= 7.0.4)
activejob (= 7.0.2.2) activejob (= 7.0.4)
activerecord (= 7.0.2.2) activerecord (= 7.0.4)
activestorage (= 7.0.2.2) activestorage (= 7.0.4)
activesupport (= 7.0.2.2) activesupport (= 7.0.4)
mail (>= 2.7.1) mail (>= 2.7.1)
net-imap net-imap
net-pop net-pop
net-smtp net-smtp
actionmailer (7.0.2.2) actionmailer (7.0.4)
actionpack (= 7.0.2.2) actionpack (= 7.0.4)
actionview (= 7.0.2.2) actionview (= 7.0.4)
activejob (= 7.0.2.2) activejob (= 7.0.4)
activesupport (= 7.0.2.2) activesupport (= 7.0.4)
mail (~> 2.5, >= 2.5.4) mail (~> 2.5, >= 2.5.4)
net-imap net-imap
net-pop net-pop
net-smtp net-smtp
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
actionpack (7.0.2.2) actionpack (7.0.4)
actionview (= 7.0.2.2) actionview (= 7.0.4)
activesupport (= 7.0.2.2) activesupport (= 7.0.4)
rack (~> 2.0, >= 2.2.0) rack (~> 2.0, >= 2.2.0)
rack-test (>= 0.6.3) rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0)
actiontext (7.0.2.2) actiontext (7.0.4)
actionpack (= 7.0.2.2) actionpack (= 7.0.4)
activerecord (= 7.0.2.2) activerecord (= 7.0.4)
activestorage (= 7.0.2.2) activestorage (= 7.0.4)
activesupport (= 7.0.2.2) activesupport (= 7.0.4)
globalid (>= 0.6.0) globalid (>= 0.6.0)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
actionview (7.0.2.2) actionview (7.0.4)
activesupport (= 7.0.2.2) activesupport (= 7.0.4)
builder (~> 3.1) builder (~> 3.1)
erubi (~> 1.4) erubi (~> 1.4)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.1, >= 1.2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0)
activejob (7.0.2.2) activejob (7.0.4)
activesupport (= 7.0.2.2) activesupport (= 7.0.4)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (7.0.2.2) activemodel (7.0.4)
activesupport (= 7.0.2.2) activesupport (= 7.0.4)
activerecord (7.0.2.2) activerecord (7.0.4)
activemodel (= 7.0.2.2) activemodel (= 7.0.4)
activesupport (= 7.0.2.2) activesupport (= 7.0.4)
activestorage (7.0.2.2) activestorage (7.0.4)
actionpack (= 7.0.2.2) actionpack (= 7.0.4)
activejob (= 7.0.2.2) activejob (= 7.0.4)
activerecord (= 7.0.2.2) activerecord (= 7.0.4)
activesupport (= 7.0.2.2) activesupport (= 7.0.4)
marcel (~> 1.0) marcel (~> 1.0)
mini_mime (>= 1.1.0) mini_mime (>= 1.1.0)
activesupport (7.0.2.2) activesupport (7.0.4)
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2) i18n (>= 1.6, < 2)
minitest (>= 5.1) minitest (>= 5.1)
tzinfo (~> 2.0) tzinfo (~> 2.0)
addressable (2.8.0) addressable (2.8.1)
public_suffix (>= 2.0.2, < 5.0) public_suffix (>= 2.0.2, < 6.0)
bcrypt (3.1.16) bcrypt (3.1.18)
bindex (0.8.1) bindex (0.8.1)
builder (3.2.4) builder (3.2.4)
capybara (3.36.0) byebug (11.1.3)
capybara (3.38.0)
addressable addressable
matrix matrix
mini_mime (>= 0.1.3) mini_mime (>= 0.1.3)
@@ -81,12 +82,12 @@ GEM
regexp_parser (>= 1.5, < 3.0) regexp_parser (>= 1.5, < 3.0)
xpath (~> 3.2) xpath (~> 3.2)
chunky_png (1.4.0) chunky_png (1.4.0)
concurrent-ruby (1.1.9) concurrent-ruby (1.1.10)
connection_pool (2.2.5) connection_pool (2.3.0)
crack (0.4.5) crack (0.4.5)
rexml rexml
crass (1.0.6) crass (1.0.6)
cssbundling-rails (1.0.0) cssbundling-rails (1.1.1)
railties (>= 6.0.0) railties (>= 6.0.0)
database_cleaner (2.0.1) database_cleaner (2.0.1)
database_cleaner-active_record (~> 2.0.0) database_cleaner-active_record (~> 2.0.0)
@@ -104,44 +105,43 @@ GEM
devise (>= 3.4.1) devise (>= 3.4.1)
net-ldap (>= 0.16.0) net-ldap (>= 0.16.0)
diff-lcs (1.5.0) diff-lcs (1.5.0)
digest (3.1.0) dotenv (2.8.1)
dotenv (2.7.6) dotenv-rails (2.8.1)
dotenv-rails (2.7.6) dotenv (= 2.8.1)
dotenv (= 2.7.6)
railties (>= 3.2) railties (>= 3.2)
e2mmap (0.1.0) erubi (1.11.0)
erubi (1.10.0) et-orbi (1.2.7)
et-orbi (1.2.6)
tzinfo tzinfo
factory_bot (6.2.0) factory_bot (6.2.1)
activesupport (>= 5.0.0) activesupport (>= 5.0.0)
factory_bot_rails (6.2.0) factory_bot_rails (6.2.0)
factory_bot (~> 6.2.0) factory_bot (~> 6.2.0)
railties (>= 5.0.0) railties (>= 5.0.0)
faraday (2.2.0) faker (3.0.0)
faraday-net_http (~> 2.0) i18n (>= 1.8.11, < 2)
faraday (2.7.1)
faraday-net_http (>= 2.0, < 3.1)
ruby2_keywords (>= 0.0.4) ruby2_keywords (>= 0.0.4)
faraday-net_http (2.0.1) faraday-net_http (3.0.2)
ffi (1.15.5) ffi (1.15.5)
fugit (1.5.2) fugit (1.7.2)
et-orbi (~> 1.1, >= 1.1.8) et-orbi (~> 1, >= 1.2.7)
raabro (~> 1.4) raabro (~> 1.4)
globalid (1.0.0) globalid (1.0.0)
activesupport (>= 5.0) activesupport (>= 5.0)
hashdiff (1.0.1) hashdiff (1.0.1)
i18n (1.9.1) i18n (1.12.0)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
importmap-rails (1.0.2) importmap-rails (1.1.5)
actionpack (>= 6.0.0) actionpack (>= 6.0.0)
railties (>= 6.0.0) railties (>= 6.0.0)
io-wait (0.2.1)
jbuilder (2.11.5) jbuilder (2.11.5)
actionview (>= 5.0.0) actionview (>= 5.0.0)
activesupport (>= 5.0.0) activesupport (>= 5.0.0)
launchy (2.5.0) launchy (2.5.0)
addressable (~> 2.7) addressable (~> 2.7)
letter_opener (1.7.0) letter_opener (1.8.1)
launchy (~> 2.2) launchy (>= 2.2, < 3)
letter_opener_web (2.0.0) letter_opener_web (2.0.0)
actionmailer (>= 5.2) actionmailer (>= 5.2)
letter_opener (~> 1.7) letter_opener (~> 1.7)
@@ -150,8 +150,8 @@ GEM
listen (3.7.1) listen (3.7.1)
rb-fsevent (~> 0.10, >= 0.10.3) rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10) rb-inotify (~> 0.9, >= 0.9.10)
lockbox (0.6.8) lockbox (1.1.0)
loofah (2.14.0) loofah (2.19.0)
crass (~> 1.0.2) crass (~> 1.0.2)
nokogiri (>= 1.5.9) nokogiri (>= 1.5.9)
mail (2.7.1) mail (2.7.1)
@@ -160,130 +160,129 @@ GEM
matrix (0.4.2) matrix (0.4.2)
method_source (1.0.0) method_source (1.0.0)
mini_mime (1.1.2) mini_mime (1.1.2)
minitest (5.15.0) mini_portile2 (2.8.0)
net-imap (0.2.3) minitest (5.16.3)
digest net-imap (0.3.1)
net-protocol net-protocol
strscan net-ldap (0.17.1)
net-ldap (0.17.0) net-pop (0.1.2)
net-pop (0.1.1)
digest
net-protocol net-protocol
net-protocol (0.1.3)
timeout timeout
net-protocol (0.1.2) net-smtp (0.3.3)
io-wait
timeout
net-smtp (0.3.1)
digest
net-protocol net-protocol
timeout
nio4r (2.5.8) nio4r (2.5.8)
nokogiri (1.13.1-x86_64-linux) nokogiri (1.13.9)
mini_portile2 (~> 2.8.0)
racc (~> 1.4)
nokogiri (1.13.9-x86_64-linux)
racc (~> 1.4) racc (~> 1.4)
orm_adapter (0.5.0) orm_adapter (0.5.0)
pg (1.2.3) pg (1.2.3)
public_suffix (4.0.6) public_suffix (5.0.0)
puma (4.3.11) puma (4.3.12)
nio4r (~> 2.0) nio4r (~> 2.0)
raabro (1.4.0) raabro (1.4.0)
racc (1.6.0) racc (1.6.0)
rack (2.2.3) rack (2.2.4)
rack-test (1.1.0) rack-test (2.0.2)
rack (>= 1.0, < 3) rack (>= 1.3)
rails (7.0.2.2) rails (7.0.4)
actioncable (= 7.0.2.2) actioncable (= 7.0.4)
actionmailbox (= 7.0.2.2) actionmailbox (= 7.0.4)
actionmailer (= 7.0.2.2) actionmailer (= 7.0.4)
actionpack (= 7.0.2.2) actionpack (= 7.0.4)
actiontext (= 7.0.2.2) actiontext (= 7.0.4)
actionview (= 7.0.2.2) actionview (= 7.0.4)
activejob (= 7.0.2.2) activejob (= 7.0.4)
activemodel (= 7.0.2.2) activemodel (= 7.0.4)
activerecord (= 7.0.2.2) activerecord (= 7.0.4)
activestorage (= 7.0.2.2) activestorage (= 7.0.4)
activesupport (= 7.0.2.2) activesupport (= 7.0.4)
bundler (>= 1.15.0) bundler (>= 1.15.0)
railties (= 7.0.2.2) railties (= 7.0.4)
rails-dom-testing (2.0.3) rails-dom-testing (2.0.3)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
nokogiri (>= 1.6) nokogiri (>= 1.6)
rails-html-sanitizer (1.4.2) rails-html-sanitizer (1.4.3)
loofah (~> 2.3) loofah (~> 2.3)
railties (7.0.2.2) railties (7.0.4)
actionpack (= 7.0.2.2) actionpack (= 7.0.4)
activesupport (= 7.0.2.2) activesupport (= 7.0.4)
method_source method_source
rake (>= 12.2) rake (>= 12.2)
thor (~> 1.0) thor (~> 1.0)
zeitwerk (~> 2.5) zeitwerk (~> 2.5)
rake (13.0.6) rake (13.0.6)
rb-fsevent (0.11.1) rb-fsevent (0.11.2)
rb-inotify (0.10.1) rb-inotify (0.10.1)
ffi (~> 1.0) ffi (~> 1.0)
redis (4.6.0) redis (5.0.5)
regexp_parser (2.2.1) redis-client (>= 0.9.0)
redis-client (0.11.2)
connection_pool
regexp_parser (2.6.1)
responders (3.0.1) responders (3.0.1)
actionpack (>= 5.0) actionpack (>= 5.0)
railties (>= 5.0) railties (>= 5.0)
rexml (3.2.5) rexml (3.2.5)
rqrcode (2.1.1) rqrcode (2.1.2)
chunky_png (~> 1.0) chunky_png (~> 1.0)
rqrcode_core (~> 1.0) rqrcode_core (~> 1.0)
rqrcode_core (1.2.0) rqrcode_core (1.2.0)
rspec-core (3.11.0) rspec-core (3.12.0)
rspec-support (~> 3.11.0) rspec-support (~> 3.12.0)
rspec-expectations (3.11.0) rspec-expectations (3.12.0)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.11.0) rspec-support (~> 3.12.0)
rspec-mocks (3.11.0) rspec-mocks (3.12.0)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.11.0) rspec-support (~> 3.12.0)
rspec-rails (5.1.0) rspec-rails (6.0.1)
actionpack (>= 5.2) actionpack (>= 6.1)
activesupport (>= 5.2) activesupport (>= 6.1)
railties (>= 5.2) railties (>= 6.1)
rspec-core (~> 3.10) rspec-core (~> 3.11)
rspec-expectations (~> 3.10) rspec-expectations (~> 3.11)
rspec-mocks (~> 3.10) rspec-mocks (~> 3.11)
rspec-support (~> 3.10) rspec-support (~> 3.11)
rspec-support (3.11.0) rspec-support (3.12.0)
ruby2_keywords (0.0.5) ruby2_keywords (0.0.5)
rufus-scheduler (3.8.1) rufus-scheduler (3.8.2)
fugit (~> 1.1, >= 1.1.6) fugit (~> 1.1, >= 1.1.6)
sidekiq (6.4.1) sidekiq (6.5.5)
connection_pool (>= 2.2.2) connection_pool (>= 2.2.2)
rack (~> 2.0) rack (~> 2.0)
redis (>= 4.5.0)
sidekiq-scheduler (4.0.3)
redis (>= 4.2.0) redis (>= 4.2.0)
sidekiq-scheduler (3.1.1)
e2mmap
redis (>= 3, < 5)
rufus-scheduler (~> 3.2) rufus-scheduler (~> 3.2)
sidekiq (>= 3) sidekiq (>= 4, < 7)
thwait
tilt (>= 1.4.0) tilt (>= 1.4.0)
sprockets (4.0.2) sprockets (4.1.1)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
rack (> 1, < 3) rack (> 1, < 3)
sprockets-rails (3.4.2) sprockets-rails (3.4.2)
actionpack (>= 5.2) actionpack (>= 5.2)
activesupport (>= 5.2) activesupport (>= 5.2)
sprockets (>= 3.0.0) sprockets (>= 3.0.0)
sqlite3 (1.4.2) sqlite3 (1.5.4)
stimulus-rails (1.0.2) mini_portile2 (~> 2.8.0)
sqlite3 (1.5.4-x86_64-linux)
stimulus-rails (1.2.1)
railties (>= 6.0.0) railties (>= 6.0.0)
strscan (3.0.1)
thor (1.2.1) thor (1.2.1)
thwait (0.2.0) tilt (2.0.11)
e2mmap timeout (0.3.0)
tilt (2.0.10) turbo-rails (1.3.2)
timeout (0.2.0)
turbo-rails (1.0.1)
actionpack (>= 6.0.0) actionpack (>= 6.0.0)
activejob (>= 6.0.0)
railties (>= 6.0.0) railties (>= 6.0.0)
tzinfo (2.0.4) tzinfo (2.0.5)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
view_component (2.49.0) view_component (2.78.0)
activesupport (>= 5.0.0, < 8.0) activesupport (>= 5.0.0, < 8.0)
concurrent-ruby (~> 1.0)
method_source (~> 1.0) method_source (~> 1.0)
warden (1.2.9) warden (1.2.9)
rack (>= 2.0.9) rack (>= 2.0.9)
@@ -292,7 +291,7 @@ GEM
activemodel (>= 6.0.0) activemodel (>= 6.0.0)
bindex (>= 0.4.0) bindex (>= 0.4.0)
railties (>= 6.0.0) railties (>= 6.0.0)
webmock (3.14.0) webmock (3.18.1)
addressable (>= 2.8.0) addressable (>= 2.8.0)
crack (>= 0.3.2) crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0) hashdiff (>= 0.4.0, < 2.0.0)
@@ -301,12 +300,14 @@ GEM
websocket-extensions (0.1.5) websocket-extensions (0.1.5)
xpath (3.2.0) xpath (3.2.0)
nokogiri (~> 1.8) nokogiri (~> 1.8)
zeitwerk (2.5.4) zeitwerk (2.6.6)
PLATFORMS PLATFORMS
ruby
x86_64-linux x86_64-linux
DEPENDENCIES DEPENDENCIES
byebug (~> 11.1)
capybara capybara
cssbundling-rails cssbundling-rails
database_cleaner database_cleaner
@@ -314,6 +315,7 @@ DEPENDENCIES
devise_ldap_authenticatable devise_ldap_authenticatable
dotenv-rails dotenv-rails
factory_bot_rails factory_bot_rails
faker
faraday faraday
importmap-rails importmap-rails
jbuilder (~> 2.7) jbuilder (~> 2.7)
@@ -327,7 +329,7 @@ DEPENDENCIES
rails (~> 7.0.2) rails (~> 7.0.2)
rqrcode (~> 2.0) rqrcode (~> 2.0)
rspec-rails rspec-rails
sidekiq sidekiq (< 7)
sidekiq-scheduler sidekiq-scheduler
sprockets-rails sprockets-rails
sqlite3 (~> 1.4) sqlite3 (~> 1.4)

View File

@@ -1,2 +1,2 @@
web: bin/rails server -p 3000 web: bin/rails server -b 0.0.0.0 -p 3000
css: yarn build:css --watch css: yarn build:css --watch

View File

@@ -7,6 +7,27 @@ credentials, invites, donations, etc..
## Development ## Development
### Quick Start
The easiest way to get a working development setup is using Docker Compose like
so:
1. Make sure [Docker Compose is installed][1] and Docker is running (included in
Docker Desktop)
2. Uncomment the `web` section in `docker-compose.yml`
3. Run `docker compose up` and wait until 389ds announces its successful start
in the log output
4. `docker-compose exec ldap dsconf localhost backend create --suffix="dc=kosmos,dc=org" --be-name="dev"`
5. `docker compose run web rails ldap:setup`
5. `docker compose run web rails db:setup`
After these steps, you should have a working Rails app with a handful of test
users running on [http://localhost:3000](http://localhost:3000).
Log in with username "admin" and password "admin is admin". All users listed on
[http://localhost:3000/admin/ldap_users](http://localhost:3000/admin/ldap_users)
have the password "user is user".
### Rails app ### Rails app
Installing dependencies: Installing dependencies:
@@ -31,10 +52,32 @@ Running all specs:
bundle exec rspec bundle exec rspec
### LDAP server ### Docker (Compose)
TODO make it easy to run a local Kosmos LDAP server for development, without There is a working Docker Compose config file, which allows you to spin up both
manual LDIF imports etc. (or provide a staging instance) an app server for Rails as well as a local 389ds (LDAP) server.
By default, `docker-compose up` will only start the LDAP server, listening on
port 389 on your machine. Uncomment other services in `docker-compose.yml` if
you want to use them.
#### LDAP server
After creating the Docker container for the first time (or after deleting it),
you need to run the following command once, in order to create the dirsrv
back-end:
docker-compose exec ldap dsconf localhost backend create --suffix="dc=kosmos,dc=org" --be-name="dev"
Now you can seed the back-end with data using this Rails task:
bundle exec rails ldap:setup
The setup task will first delete any existing entries in the directory tree
("dc=kosmos,dc=org"), and then create our development entries.
Note that all 389ds data is stored in `tmp/389ds`. So if you want to start over
with a fresh installation, delete both that directory as well as the container.
## Documentation ## Documentation
@@ -63,3 +106,5 @@ manual LDIF imports etc. (or provide a staging instance)
## License ## License
[GNU Affero General Public License v3.0](https://choosealicense.com/licenses/agpl-3.0/) [GNU Affero General Public License v3.0](https://choosealicense.com/licenses/agpl-3.0/)
[1]: https://docs.docker.com/compose/install/

View File

@@ -7,3 +7,4 @@
@import "components/forms"; @import "components/forms";
@import "components/links"; @import "components/links";
@import "components/notifications"; @import "components/notifications";
@import "components/tables";

View File

@@ -1,11 +1,16 @@
@layer base { @layer base {
body { html {
@apply leading-none font-size: 14px;
} }
/* h1, h2, h3 { */ body {
/* @apply font-light; */ @apply leading-none bg-cover bg-fixed;
/* } */ background-image: linear-gradient(35deg, rgba(255,0,255,0.2) 0, rgba(13,79,153,0.8) 100%), url('/img/bg-1.jpg');
}
body#admin {
background-image: linear-gradient(35deg, rgba(255,0,255,0.2) 0, rgba(153,12,14,0.9) 100%), url('/img/bg-1.jpg');
}
h1 { h1 {
@apply text-3xl uppercase; @apply text-3xl uppercase;
@@ -26,4 +31,16 @@
main section:first-of-type { main section:first-of-type {
@apply pt-0; @apply pt-0;
} }
main p {
@apply mb-4 leading-6;
}
main ul {
@apply mb-6;
}
main ul li {
@apply leading-6;
}
} }

View File

@@ -0,0 +1,22 @@
@layer components {
table {
@apply w-full;
}
table thead tr {
@apply text-left;
}
table th {
@apply pb-3.5 text-sm font-normal uppercase text-gray-500;
}
table th:not(:last-of-type),
table td:not(:last-of-type) {
@apply pr-2;
}
table td {
@apply py-2;
}
}

View File

@@ -1,2 +0,0 @@
@import "legacy/layout";
@import "legacy/main_nav";

View File

@@ -1,118 +0,0 @@
@import "variables";
@import "mediaqueries";
body {
background: linear-gradient(35deg, rgba(255,0,255,0.2) 0, rgba(13,79,153,0.8) 100%),
url('/img/bg-1.jpg');
background-size: cover;
background-attachment: fixed;
}
body#admin {
background: linear-gradient(35deg, rgba(255,0,255,0.2) 0, rgba(153,12,14,0.9) 100%),
url('/img/bg-1.jpg');
background-size: cover;
background-attachment: fixed;
}
.ks-site-icon {
svg {
display: inline-block;
height: 1.875rem;
vertical-align: top;
width: auto;
}
}
#wrapper {
width: 100%;
text-align: center;
> header {
margin: 0 auto;
padding: 4rem 0;
text-align: center;
background: linear-gradient(35deg, rgba(255,0,255,0.2) 0, rgba(13,79,153,0.8) 100%),
url('/img/bg-1.jpg');
background-size: cover;
@include media-max(small) {
padding: 3rem 0;
}
h1 {
color: #fff;
span.project-name {
display: none;
}
}
p.current-user {
color: rgba(255,255,255,0.6);
@include media-max(small) {
font-size: 0.85rem;
}
}
a {
color: rgba(255,255,255,0.6);
transition: color 0.1s linear;
&:hover, &:active {
color: #fff;
}
}
}
}
main {
p {
line-height: 1.5rem;
margin-bottom: 1rem;
&.notice {
text-align: center;
}
}
ul {
margin-bottom: 1.5rem;
li {
line-height: 1.5rem;
}
}
table {
width: 100%;
th {
color: $text-color-discreet;
font-weight: normal;
text-transform: uppercase;
font-size: 0.85rem;
padding-bottom: 0.825rem;
}
td {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
}
}
}
.grid {
display: grid;
&.services {
grid-template-columns: 1fr 1fr 1fr;
grid-row-gap: 1rem;
grid-column-gap: 2rem;
@include media-max(small) {
grid-template-columns: 1fr;
}
}
}

View File

@@ -1,54 +0,0 @@
@import "variables";
@import "mediaqueries";
#main-nav {
width: 100%;
text-align: center;
background-color: #efefef;
.wrapper {
width: $content-width;
max-width: $content-max-width;
margin: 0 auto;
}
ul {
@include media-max(large) {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr 1fr;
}
li {
@include media-min(large) {
display: inline;
}
@include media-max(large) {
display: block;
}
a {
display: inline-block;
padding: 1.5rem 2rem;
text-decoration: none;
color: $text-color-discreet;
@include media-max(large) {
display: block;
text-align: center;
padding-left: 0;
padding-right: 0;
}
@include media-max(small) {
font-size: 0.85rem;
}
&.active {
color: $text-color-body;
border-bottom: 2px solid #4ea2df;
}
}
}
}
}

View File

@@ -1,33 +0,0 @@
$breakpoints-max: (
small: 600px,
medium: 960px,
large: 1280px
);
$breakpoints-min: (
small: 601px,
medium: 961px,
large: 1281px
);
@mixin media-max($screen-size) {
@if map-has-key($breakpoints-max, $screen-size) {
@media (max-width: map-get($breakpoints-max, $screen-size)) {
@content;
}
} @else {
// Debugging
@warn "'#{$screen-size}' has not been declared as a breakpoint."
}
}
@mixin media-min($screen-size) {
@if map-has-key($breakpoints-min, $screen-size) {
@media (min-width: map-get($breakpoints-min, $screen-size)) {
@content;
}
} @else {
// Debugging
@warn "'#{$screen-size}' has not been declared as a breakpoint."
}
}

View File

@@ -1,13 +0,0 @@
$content-width: 800px;
$content-max-width: 100%;
$text-color-body: #222;
$text-color-discreet: #888;
$background-color-notice: #efffc4;
$background-color-alert: #fff4c2;
$color-blue: #0d4f99;
$color-purple: #8955a0;
$color-red-bright: #c00;
$color-red-dark: #990c0e;

View File

@@ -0,0 +1,18 @@
<section class="w-full grid grid-cols-1 md:grid-cols-12 md:mb-0">
<div class="md:col-span-8">
<p>
Send and receive sats via the Bitcoin Lightning Network.
</p>
</div>
<div class="md:col-span-4 mt-4 md:mt-0">
<p class="font-mono md:text-right mb-0 p-4 border border-gray-300 rounded-lg overflow-hidden">
<% if @balance %>
<span class="text-xl"><%= number_with_delimiter @balance %> sats</span><br>
<span class="text-sm text-gray-500">Available balance</span>
<% else %>
<span class="text-xl">n/a sats</span><br>
<span class="text-sm text-gray-500">Balance unavailable</span>
<% end %>
</p>
</div>
</section>

View File

@@ -0,0 +1,8 @@
# frozen_string_literal: true
class WalletSummaryComponent < ViewComponent::Base
def initialize(balance:)
@balance = balance
end
end

View File

@@ -27,7 +27,7 @@ class Admin::LdapUsersController < Admin::BaseController
def ldap_client def ldap_client
ldap_client ||= Net::LDAP.new host: ldap_config['host'], ldap_client ||= Net::LDAP.new host: ldap_config['host'],
port: ldap_config['port'], port: ldap_config['port'],
encryption: ldap_config['ssl'], # encryption: ldap_config['ssl'],
auth: { auth: {
method: :simple, method: :simple,
username: ldap_config['admin_user'], username: ldap_config['admin_user'],

View File

@@ -0,0 +1,5 @@
class Api::BaseController < ApplicationController
layout false
end

View File

@@ -0,0 +1,13 @@
class Api::KreditsController < Api::BaseController
def onchain_btc_balance
btcpay = BtcPay.new
balance = btcpay.onchain_wallet_balance
render json: balance
rescue => error
Rails.logger.warn "Failed to fetch kredits BTC wallet balance: #{error.message}"
render json: { error: 'Failed to fetch wallet balance' },
status: 500
end
end

View File

@@ -1,7 +1,7 @@
class LnurlpayController < ApplicationController class LnurlpayController < ApplicationController
before_action :find_user_by_address before_action :find_user_by_address
MIN_SATS = 100 MIN_SATS = 10
MAX_SATS = 1_000_000 MAX_SATS = 1_000_000
MAX_COMMENT_CHARS = 100 MAX_COMMENT_CHARS = 100

View File

@@ -3,10 +3,10 @@ require "rqrcode"
class WalletController < ApplicationController class WalletController < ApplicationController
before_action :require_user_signed_in before_action :require_user_signed_in
before_action :authenticate_with_lndhub before_action :authenticate_with_lndhub
before_action :set_current_section
before_action :fetch_balance
def index def index
@current_section = :wallet
@wallet_url = "lndhub://#{current_user.ln_login}:#{current_user.ln_password}@#{ENV['LNDHUB_PUBLIC_URL']}" @wallet_url = "lndhub://#{current_user.ln_login}:#{current_user.ln_password}@#{ENV['LNDHUB_PUBLIC_URL']}"
qrcode = RQRCode::QRCode.new(@wallet_url) qrcode = RQRCode::QRCode.new(@wallet_url)
@@ -20,28 +20,71 @@ class WalletController < ApplicationController
class: 'inline-block' class: 'inline-block'
} }
) )
end
@balance = fetch_balance rescue nil def transactions
@transactions = fetch_transactions
end end
private private
def authenticate_with_lndhub def authenticate_with_lndhub(options={})
if session["ln_auth_token"].present? if session[:ln_auth_token].present? && !options[:force_reauth]
@ln_auth_token = session["ln_auth_token"] @ln_auth_token = session[:ln_auth_token]
else else
lndhub = Lndhub.new lndhub = Lndhub.new
auth_token = lndhub.authenticate(current_user) auth_token = lndhub.authenticate(current_user)
session["ln_auth_token"] = auth_token session[:ln_auth_token] = auth_token
@ln_auth_token = auth_token @ln_auth_token = auth_token
end end
rescue rescue
# TODO add exception tracking # TODO add exception tracking
end end
def set_current_section
@current_section = :wallet
end
def fetch_balance def fetch_balance
lndhub = Lndhub.new lndhub = Lndhub.new
data = lndhub.balance @ln_auth_token data = lndhub.balance @ln_auth_token
data["BTC"]["AvailableBalance"] @balance = data["BTC"]["AvailableBalance"] rescue nil
rescue
authenticate_with_lndhub(force_reauth: true)
return nil if @fetch_balance_retried
@fetch_balance_retried = true
fetch_balance
end
def fetch_transactions
lndhub = Lndhub.new
txs = lndhub.gettxs @ln_auth_token
invoices = lndhub.getuserinvoices(@ln_auth_token).select{|i| i["ispaid"]}
process_transactions(txs + invoices)
rescue
authenticate_with_lndhub(force_reauth: true)
return [] if @fetch_transactions_retried
@fetch_transactions_retried = true
fetch_transactions
end
def process_transactions(txs)
txs.collect do |tx|
if tx["type"] == "bitcoind_tx"
tx["amount_sats"] = (tx["amount"] * 100000000).to_i
tx["datetime"] = Time.at(tx["time"].to_i)
tx["title"] = "Received"
tx["description"] = "On-chain topup"
tx["received"] = true
else
tx["amount_sats"] = tx["value"] || tx["amt"]
tx["datetime"] = Time.at(tx["timestamp"].to_i)
tx["title"] = tx["type"] == "paid_invoice" ? "Sent" : "Received"
tx["description"] = tx["memo"] || tx["description"]
tx["received"] = tx["type"] == "user_invoice"
end
end
txs.sort{ |a,b| b["datetime"] <=> a["datetime"] }
end end
end end

View File

@@ -0,0 +1,41 @@
class WebhooksController < ApplicationController
skip_forgery_protection
before_action :authorize_request
def lndhub
begin
payload = JSON.parse(request.body.read, symbolize_names: true)
return unless payload[:type] == "incoming"
rescue
head :unprocessable_entity and return
end
user = User.find_by(ln_account: payload[:user_login])
# TODO make configurable
notify_xmpp(user.address, payload[:amount], payload[:memo])
head :ok
end
private
def notify_xmpp(address, amt_sats, memo)
payload = {
type: "normal",
from: "kosmos.org", # TODO domain config
# to: address,
to: "raucao@kosmos.org",
subject: "Sats received!",
body: "#{amt_sats} sats received in your wallet. Memo: \"#{memo}\""
}
XmppSendMessageJob.perform_later(payload)
end
def authorize_request
if !ENV['WEBHOOKS_ALLOWED_IPS'].split(',').include?(request.remote_ip)
head :forbidden and return
end
end
end

View File

@@ -18,7 +18,7 @@ class CreateLdapUserJob < ApplicationJob
def ldap_client def ldap_client
ldap_client ||= Net::LDAP.new host: ldap_config['host'], ldap_client ||= Net::LDAP.new host: ldap_config['host'],
port: ldap_config['port'], port: ldap_config['port'],
encryption: ldap_config['ssl'], # encryption: ldap_config['ssl'],
auth: { auth: {
method: :simple, method: :simple,
username: ldap_config['admin_user'], username: ldap_config['admin_user'],

View File

@@ -0,0 +1,14 @@
class CreateLndhubAccountJob < ApplicationJob
queue_as :default
def perform(user)
return if user.ln_login.present? && user.ln_password.present?
lndhub = LndhubV2.new
credentials = lndhub.create_account
user.update! ln_account: credentials["login"],
ln_login: credentials["login"], # TODO remove when production is migrated
ln_password: credentials["password"]
end
end

View File

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

@@ -1,4 +1,4 @@
class ExchangeXmppContactsJob < ApplicationJob class XmppExchangeContactsJob < ApplicationJob
queue_as :default queue_as :default
def perform(inviter, username, domain) def perform(inviter, username, domain)

View File

@@ -0,0 +1,8 @@
class XmppSendMessageJob < ApplicationJob
queue_as :default
def perform(payload)
ejabberd = EjabberdApiClient.new
ejabberd.send_message payload
end
end

32
app/services/btc_pay.rb Normal file
View File

@@ -0,0 +1,32 @@
#
# API Docs: https://docs.btcpayserver.org/API/Greenfield/v1/
#
class BtcPay
def initialize
@base_url = ENV["BTCPAY_API_URL"]
@store_id = Rails.application.credentials.btcpay[:store_id]
@auth_token = Rails.application.credentials.btcpay[:auth_token]
end
def onchain_wallet_balance
res = get "stores/#{@store_id}/payment-methods/onchain/BTC/wallet"
{
balance: res["balance"].to_f,
unconfirmed_balance: res["unconfirmedBalance"].to_f,
confirmed_balance: res["confirmedBalance"].to_f
}
end
private
def get(endpoint)
res = Faraday.get("#{@base_url}/#{endpoint}", {}, {
"Content-Type" => "application/json",
"Accept" => "application/json",
"Authorization" => "token #{@auth_token}"
})
JSON.parse(res.body)
end
end

View File

@@ -5,12 +5,13 @@ class CreateAccount < ApplicationService
@email = args[:email] @email = args[:email]
@password = args[:password] @password = args[:password]
@invitation = args[:invitation] @invitation = args[:invitation]
@confirmed = args[:confirmed]
end end
def call def call
user = create_user_in_database user = create_user_in_database
add_ldap_document add_ldap_document
create_lndhub_wallet(user) create_lndhub_account(user)
if @invitation.present? if @invitation.present?
update_invitation(user.id) update_invitation(user.id)
@@ -26,7 +27,8 @@ class CreateAccount < ApplicationService
ou: @domain, ou: @domain,
email: @email, email: @email,
password: @password, password: @password,
password_confirmation: @password password_confirmation: @password,
confirmed_at: @confirmed ? DateTime.now : nil
) )
end end
@@ -35,6 +37,7 @@ class CreateAccount < ApplicationService
end end
# TODO move to confirmation # TODO move to confirmation
# (and/or add email_confirmed to entry and use in login filter)
def add_ldap_document def add_ldap_document
hashed_pw = Devise.ldap_auth_password_builder.call(@password) hashed_pw = Devise.ldap_auth_password_builder.call(@password)
CreateLdapUserJob.perform_later(@username, @domain, @email, hashed_pw) CreateLdapUserJob.perform_later(@username, @domain, @email, hashed_pw)
@@ -43,24 +46,12 @@ class CreateAccount < ApplicationService
def exchange_xmpp_contacts def exchange_xmpp_contacts
#TODO enable in development when we have easy setup of ejabberd etc. #TODO enable in development when we have easy setup of ejabberd etc.
return if Rails.env.development? return if Rails.env.development?
ExchangeXmppContactsJob.perform_later(@invitation.user, @username, @domain) XmppExchangeContactsJob.perform_later(@invitation.user, @username, @domain)
end end
def create_lndhub_wallet(user) def create_lndhub_account(user)
CreateLndhubWalletJob.perform_later(user) #TODO enable in development when we have a local lndhub (mock?) API
end return if Rails.env.development?
CreateLndhubAccountJob.perform_later(user)
def exchange_xmpp_contacts_between_inviter_and_invitee
ejabberd = EjabberdApiClient.new
EjabberdApiClient.add_roster_item({
"localuser": @username,
"localhost": @domain,
"user": @inviter.cn,
"host": @inviter.ou,
"nick": @username,
"group": "Friends",
"subs": "both"
})
end end
end end

View File

@@ -17,4 +17,8 @@ class EjabberdApiClient
def add_rosteritem(payload) def add_rosteritem(payload)
post "add_rosteritem", payload post "add_rosteritem", payload
end end
def send_message(payload)
post "send_message", payload
end
end end

View File

@@ -0,0 +1,135 @@
class LdapService < ApplicationService
def initialize
@suffix = ENV["LDAP_SUFFIX"] || "dc=kosmos,dc=org"
end
def add_entry(dn, attrs, interactive=false)
puts "Adding entry: #{dn}" if interactive
res = ldap_client.add dn: dn, attributes: attrs
puts res.inspect if interactive && !res
res
end
def add_attribute(dn, attr, value)
ldap_client.add_attribute dn, attr, value
end
def delete_entry(dn, interactive=false)
puts "Deleting entry: #{dn}" if interactive
res = ldap_client.delete dn: dn
puts res.inspect if interactive && !res
res
end
def delete_all_entries!
if Rails.env.production?
raise "Mass deletion of entries not allowed in production"
end
filter = Net::LDAP::Filter.eq("objectClass", "*")
entries = ldap_client.search(base: @suffix, filter: filter, attributes: %w{dn})
entries.sort_by!{ |e| e.dn.length }.reverse!
entries.each do |e|
delete_entry e.dn, true
end
end
def fetch_users(args={})
if args[:ou]
treebase = "ou=#{args[:ou]},cn=users,#{@suffix}"
else
treebase = ldap_config["base"]
end
attributes = %w{dn cn uid mail admin}
filter = Net::LDAP::Filter.eq("uid", "*")
entries = ldap_client.search(base: treebase, filter: filter, attributes: attributes)
entries.sort_by! { |e| e.cn[0] }
entries = entries.collect do |e|
{
uid: e.uid.first,
mail: e.try(:mail) ? e.mail.first : nil,
admin: e.try(:admin) ? 'admin' : nil
# password: e.userpassword.first
}
end
end
def fetch_organizations
attributes = %w{dn ou description}
filter = Net::LDAP::Filter.eq("objectClass", "organizationalUnit")
# filter = Net::LDAP::Filter.eq("objectClass", "*")
treebase = "cn=users,#{@suffix}"
entries = ldap_client.search(base: treebase, filter: filter, attributes: attributes)
entries.sort_by! { |e| e.ou[0] }
entries = entries.collect do |e|
{
dn: e.dn,
ou: e.ou.first,
description: e.try(:description) ? e.description.first : nil,
}
end
end
def add_organization(ou, description, interactive=false)
dn = "ou=#{ou},cn=users,#{@suffix}"
aci = <<-EOS
(target="ldap:///cn=*,ou=#{ou},cn=users,#{@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,#{@suffix}";)
EOS
attrs = {
objectClass: ["top", "organizationalUnit"],
description: description,
ou: ou,
aci: aci
}
add_entry dn, attrs, interactive
end
def reset_directory!
if Rails.env.production?
raise "Resetting the directory not allowed in production"
end
delete_all_entries!
user_read_aci = <<-EOS
(target="ldap:///#{@suffix}")(targetattr="*") (version 3.0; acl "user-read-search-own-attributes"; allow (read,search) userdn="ldap:///self";)
EOS
add_entry @suffix, {
dc: "kosmos", objectClass: ["top", "domain"], aci: user_read_aci
}, true
add_entry "cn=users,#{@suffix}", {
cn: "users", objectClass: ["top", "organizationalRole"]
}, true
end
private
def ldap_client
ldap_client ||= Net::LDAP.new host: ldap_config['host'],
port: ldap_config['port'],
# TODO has to be :simple_tls if TLS is enabled
# encryption: ldap_config['ssl'],
auth: {
method: :simple,
username: ldap_config['admin_user'],
password: ldap_config['admin_password']
}
end
def ldap_config
ldap_config ||= YAML.load(ERB.new(File.read("#{Rails.root}/config/ldap.yml")).result)[Rails.env]
end
end

View File

@@ -2,7 +2,7 @@ class Lndhub
attr_accessor :auth_token attr_accessor :auth_token
def initialize def initialize
@base_url = ENV["LNDHUB_API_URL"] @base_url = ENV["LNDHUB_LEGACY_API_URL"]
end end
def post(endpoint, payload) def post(endpoint, payload)
@@ -28,8 +28,13 @@ class Lndhub
"Accept" => "application/json", "Accept" => "application/json",
"Authorization" => "Bearer #{auth_token}" "Authorization" => "Bearer #{auth_token}"
}) })
data = JSON.parse(res.body)
JSON.parse(res.body) if data.is_a?(Hash) && data["error"] && data["message"] == "bad auth"
raise "BAD_AUTH"
else
data
end
end end
def create(payload) def create(payload)
@@ -42,10 +47,18 @@ class Lndhub
self.auth_token self.auth_token
end end
def balance(user_token) def balance(user_token=nil)
get "balance", user_token || auth_token get "balance", user_token || auth_token
end end
def gettxs(user_token=nil)
get "gettxs", user_token || auth_token
end
def getuserinvoices(user_token=nil)
get "getuserinvoices", user_token || auth_token
end
def addinvoice(payload) def addinvoice(payload)
invoice = post "addinvoice", { invoice = post "addinvoice", {
amt: payload[:amount], amt: payload[:amount],

81
app/services/lndhub_v2.rb Normal file
View File

@@ -0,0 +1,81 @@
class LndhubV2
attr_accessor :auth_token
def initialize
@base_url = ENV["LNDHUB_API_URL"]
end
def post(endpoint, payload, options={})
headers = { "Content-Type" => "application/json" }
if auth_token
headers.merge!({ "Authorization" => "Bearer #{auth_token}" })
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
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=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
def create_invoice(payload)
# Payload: { amount: 1000, description: "", description_hash: "" }
post "v2/invoices", payload
end
end

View File

@@ -2,14 +2,14 @@
<%= render MainSimpleComponent.new do %> <%= render MainSimpleComponent.new do %>
<% if @donations.any? %> <% if @donations.any? %>
<table class="w-full"> <table>
<thead> <thead>
<tr class="text-left"> <tr>
<th>User</th> <th>User</th>
<th>Amount BTC</th> <th class="text-right">Amount BTC</th>
<th>in EUR</th> <th class="text-right">in EUR</th>
<th>in USD</th> <th class="text-right">in USD</th>
<th>Public name</th> <th class="pl-2">Public name</th>
<th>Date</th> <th>Date</th>
<th colspan="3"></th> <th colspan="3"></th>
</tr> </tr>
@@ -19,10 +19,10 @@
<% @donations.each do |donation| %> <% @donations.each do |donation| %>
<tr> <tr>
<td><%= donation.user.address %></td> <td><%= donation.user.address %></td>
<td><%= sats_to_btc donation.amount_sats %> BTC</td> <td class="text-right"><%= sats_to_btc donation.amount_sats %></td>
<td><% if donation.amount_eur.present? %><%= number_to_currency donation.amount_eur / 100, unit: "" %><% end %></td> <td class="text-right"><% if donation.amount_eur.present? %><%= number_to_currency donation.amount_eur / 100, unit: "" %><% end %></td>
<td><% if donation.amount_usd.present? %><%= number_to_currency donation.amount_usd / 100, unit: "" %><% end %></td> <td class="text-right"><% if donation.amount_usd.present? %><%= number_to_currency donation.amount_usd / 100, unit: "" %><% end %></td>
<td><%= donation.public_name %></td> <td class="pl-2"><%= donation.public_name %></td>
<td><%= donation.paid_at ? donation.paid_at.strftime("%Y-%m-%d") : "" %></td> <td><%= donation.paid_at ? donation.paid_at.strftime("%Y-%m-%d") : "" %></td>
<td><%= link_to 'Show', admin_donation_path(donation), class: 'btn btn-sm btn-gray' %></td> <td><%= link_to 'Show', admin_donation_path(donation), class: 'btn btn-sm btn-gray' %></td>
<td><%= link_to 'Edit', edit_admin_donation_path(donation), class: 'btn btn-sm btn-gray' %></td> <td><%= link_to 'Edit', edit_admin_donation_path(donation), class: 'btn btn-sm btn-gray' %></td>

View File

@@ -14,7 +14,7 @@
<h3>Accepted (<%= @invitations_used.length %>)</h3> <h3>Accepted (<%= @invitations_used.length %>)</h3>
<table> <table>
<thead> <thead>
<tr class="text-left"> <tr>
<th>Token</th> <th>Token</th>
<th>Accepted</th> <th>Accepted</th>
<th>Invited user</th> <th>Invited user</th>

View File

@@ -13,7 +13,7 @@
<table> <table>
<thead> <thead>
<tr class="text-left"> <tr>
<th>UID</th> <th>UID</th>
<th>E-Mail</th> <th>E-Mail</th>
<th>Admin</th> <th>Admin</th>

View File

@@ -6,7 +6,7 @@
Your Kosmos account and password currently give you access to these Your Kosmos account and password currently give you access to these
services: services:
</p> </p>
<div class="grid services mt-12"> <div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6 services mt-12">
<div> <div>
<h3 class="mb-3.5"> <h3 class="mb-3.5">
<%= link_to "Chat", "https://wiki.kosmos.org/Services:Chat", class: "ks-text-link" %> <%= link_to "Chat", "https://wiki.kosmos.org/Services:Chat", class: "ks-text-link" %>
@@ -25,7 +25,7 @@
</div> </div>
<div> <div>
<h3 class="mb-3.5"> <h3 class="mb-3.5">
<span class="text-yellow-500">🗲</span> <%= render partial: "icons/zap", locals: { custom_class: "text-amber-500 h-4 w-4 inline" } %>
<%= link_to "Lightning Wallet", wallet_path, class: "ks-text-link" %> <%= link_to "Lightning Wallet", wallet_path, class: "ks-text-link" %>
</h3> </h3>
<p class="text-gray-500"> <p class="text-gray-500">

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link-2"><path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path><line x1="8" y1="12" x2="16" y2="12"></line></svg> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link-2 <%= custom_class %>"><path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path><line x1="8" y1="12" x2="16" y2="12"></line></svg>

Before

Width:  |  Height:  |  Size: 355 B

After

Width:  |  Height:  |  Size: 376 B

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link <%= custom_class %>"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>

Before

Width:  |  Height:  |  Size: 371 B

After

Width:  |  Height:  |  Size: 392 B

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-zap"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"></polygon></svg> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-zap <%= custom_class %>"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"></polygon></svg>

Before

Width:  |  Height:  |  Size: 282 B

After

Width:  |  Height:  |  Size: 303 B

View File

@@ -8,7 +8,7 @@
</p> </p>
<table class="mt-12"> <table class="mt-12">
<thead> <thead>
<tr class="text-left"> <tr>
<th>URL</th> <th>URL</th>
</tr> </tr>
</thead> </thead>
@@ -33,7 +33,7 @@
<h3>Accepted Invitations</h3> <h3>Accepted Invitations</h3>
<table> <table>
<thead> <thead>
<tr class="text-left"> <tr>
<th class="hidden md:block">ID</th> <th class="hidden md:block">ID</th>
<th>Accepted</th> <th>Accepted</th>
<th>Invited user</th> <th>Invited user</th>

View File

@@ -6,7 +6,6 @@
<%= csp_meta_tag %> <%= csp_meta_tag %>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://assets.kosmos.org/fonts/open-sans/open-sans.css" rel="stylesheet"> <link href="https://assets.kosmos.org/fonts/open-sans/open-sans.css" rel="stylesheet">
<%= stylesheet_link_tag 'legacy', "data-turbo-track": "reload" %>
<%= stylesheet_link_tag 'application', "data-turbo-track": "reload" %> <%= stylesheet_link_tag 'application', "data-turbo-track": "reload" %>
<%= javascript_importmap_tags %> <%= javascript_importmap_tags %>
</head> </head>
@@ -18,7 +17,7 @@
<div class="flex items-center justify-between h-16 px-4 sm:px-0"> <div class="flex items-center justify-between h-16 px-4 sm:px-0">
<div class="flex items-center"> <div class="flex items-center">
<div class="ks-site-icon flex-shrink-0"> <div class="ks-site-icon flex-shrink-0">
<%= render partial: "shared/icons/comet" %> <%= render partial: "shared/icons/comet", locals: { custom_class: "inline-block align-top w-auto h-7" } %>
</div> </div>
<% if user_signed_in? && current_user.confirmed? %> <% if user_signed_in? && current_user.confirmed? %>
<div class="hidden md:block"> <div class="hidden md:block">

View File

@@ -1 +1 @@
<svg id="icon-comet" width="65.364" height="55.773" enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 65.364 55.773" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><g id="layer1" transform="translate(28.868 20.259)" fill="#fff"><path id="path2" d="m22.81-9.2546-0.0137-0.0072c-0.0445-0.0196-0.0895-0.04052-0.13335-0.06078l-23.822-10.937s2.0034 9.219 2.914 11.778c0 0-27.292-8.1582-30.623-8.9354 1.0916 4.2618 20.006 40.848 20.006 40.848 3.8225 7.7608 12.677 12.083 21.912 12.083 12.949 0 23.446-10.497 23.446-23.446 6.6e-4 -9.4655-5.609-17.62-13.685-21.323z" fill="#fff" stroke-width=".65365"/></g></svg> <svg id="icon-comet" class="<%= custom_class %>" width="65.364" height="55.773" enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 65.364 55.773" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><g id="layer1" transform="translate(28.868 20.259)" fill="#fff"><path id="path2" d="m22.81-9.2546-0.0137-0.0072c-0.0445-0.0196-0.0895-0.04052-0.13335-0.06078l-23.822-10.937s2.0034 9.219 2.914 11.778c0 0-27.292-8.1582-30.623-8.9354 1.0916 4.2618 20.006 40.848 20.006 40.848 3.8225 7.7608 12.677 12.083 21.912 12.083 12.949 0 23.446-10.497 23.446-23.446 6.6e-4 -9.4655-5.609-17.62-13.685-21.323z" fill="#fff" stroke-width=".65365"/></g></svg>

Before

Width:  |  Height:  |  Size: 627 B

After

Width:  |  Height:  |  Size: 655 B

View File

@@ -1,22 +1,14 @@
<%= render HeaderComponent.new(title: "Wallet") %> <%= render HeaderComponent.new(title: "Wallet") %>
<%= render MainSimpleComponent.new do %> <%= render MainSimpleComponent.new do %>
<section class="w-full grid grid-cols-1 md:grid-cols-12 md:mb-0"> <%= render WalletSummaryComponent.new(balance: @balance) %>
<div class="md:col-span-8">
<p> <section>
Send and receive sats via the Bitcoin Lightning Network. <div class="border-b border-gray-200">
</p> <nav class="-mb-px flex" aria-label="Tabs">
</div> <%= link_to "Info", wallet_path, class: "border-indigo-500 text-indigo-600 w-1/2 py-4 px-1 text-center border-b-2", "aria-current": "page" %>
<div class="md:col-span-4 mt-4 md:mt-0"> <%= link_to "Transactions", wallet_transactions_path, class: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 w-1/2 py-4 px-1 text-center border-b-2" %>
<p class="font-mono md:text-right mb-0 p-4 border border-gray-300 rounded-lg overflow-hidden"> </nav>
<% if @balance %>
<span class="text-xl"><%= number_with_delimiter @balance %> sats</span><br>
<span class="text-sm text-gray-500">Available balance</span>
<% else %>
<span class="text-xl">n/a sats</span><br>
<span class="text-sm text-gray-500">Balance unavailable</span>
<% end %>
</p>
</div> </div>
</section> </section>

View File

@@ -0,0 +1,57 @@
<%= render HeaderComponent.new(title: "Wallet") %>
<%= render MainSimpleComponent.new do %>
<%= render WalletSummaryComponent.new(balance: @balance) %>
<section>
<div class="border-b border-gray-200">
<nav class="-mb-px flex" aria-label="Tabs">
<%= link_to "Info", wallet_path, class: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 w-1/2 py-4 px-1 text-center border-b-2" %>
<%= link_to "Transactions", wallet_transactions_path, class: "border-indigo-500 text-indigo-600 w-1/2 py-4 px-1 text-center border-b-2", "aria-current": "page" %>
</nav>
</div>
</section>
<section>
<h3 class="hidden">Transactions</h3>
<% if @transactions.any? %>
<ul class="list-none">
<% @transactions.each do |tx| %>
<li class="py-4 md:py-4 grid gap-y-1 gap-x-2 grid-cols-4 border-b border-dotted border-gray-300">
<h3 class="col-span-2 md:col-span-3 mb-0">
<% if tx["type"] == "bitcoind_tx" %>
<span class="inline-block">
<%= render partial: "icons/link-2", locals: { custom_class: "text-emerald-500 h-4 w-4 mr-0.5" } %>
</span>
<% else %>
<span class="inline-block">
<%= render partial: "icons/zap", locals: { custom_class: "text-amber-500 h-4 w-4 mr-0.5" } %>
</span>
<% end %>
<%= tx["title"] %>
</h3>
<p class="col-span-2 md:col-span-1 mb-0 text-right">
<span class="text-xl font-mono <%= tx["received"] ? "text-emerald-600" : "" %>">
<%= tx["received"] ? "+" : "" %><%= number_with_delimiter tx["amount_sats"] %>
<span class="hidden md:inline">sats</span>
</span>
</p>
<p class="col-span-4 md:col-span-3 mb-0 text-gray-500">
<%= tx["description"].present? ? tx["description"] : raw("<span class='text-gray-400'>No memo</span>") %>
</p>
<p class="col-span-4 md:col-span-1 md:text-right mb-0">
<span class="col-span-2 md:col-span-1 text-sm text-gray-500">
<%= tx["datetime"].strftime("%B %e, %H:%M") %>
</span>
</p>
</li>
<% end %>
</ul>
<% else %>
<p class="text-gray-500">
No transactions yet. As soon as you start receiving sats, you will find some entries here.
</p>
<% end %>
</section>
<% end %>

View File

@@ -1 +1 @@
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== yEs5CyuAbqphlDWgtw/YQvkPn+EN4ecen2dAjs7zvYErkRRWp99FinGlQIMe6NRkMLLLSIj2BwR/wlscn1kLpIfwGpxfSZ89srK3do6Mb5QogpxdUsnQB8qv5PTGRQFBcjM47s1Q5m0t+OKxGvOnLyKnQp+cVS2KFJMbSzQarW8wIZSz2gKArn9Ttk0kqUHMlJWNY7Yh6xIrrxlEalaTOVzPdtnF7u8Tobminu15eeWHMormMRz4dYSaDc6hUtfpdy1NzOHaeXIU9A9RY/iytxuIQNgcMAlcWbPe//rVk/unH2F8xqSOfed4h/nC08F/qq4z8va3kEXBSdW/G91aIDMu1mo0kX3YNibq8s25C/CfGpzw39ozJ9erTBH7hy6nfmxU6qZuWcTGDj3NOfKe/XIfDcpOjsqkT2IOFARrYodb67q23IuOufraK1/FD4LXu8l0S8/Oi0cqMjtPPs7tS0M1C3DrbmlEzGKETrHpmoKHqjA0rgOmK4ZZM9LeI+l8Z+fDpYcCak9fLGGxnjf+nKiYMSUtm9+1dwycG2lpBV6fbmIKHJWngO2jVGcycODkc525oUaAO4hdPMqrz1AdU3AzYmLJTxW3aZ4uL5NyEJ7TbUBC0HT7h2gEi/tUry4cfD2EsM9bCrCUNuMBrnPqd4r8AvORoqqYIw1IEsP0RgWa2+hfeG1QCjBRPFHQOcqo+W25CelivMe79qI08w0iC8S4hfOQO4QrmMgtd1BhcR+wVpVE3X9EJZi3Hl7z14hXcSic+gkswJMtVZcnJL4rmZ0iEW1mpqUuegsX5vB/4qPxiQyeB80pg8Q33shvUbixzSBkl6znmLSiIffsiDsGOsnuzfl/MUT+JBs3UswNt4tSp7nEwhUjKFHrZHrAJiGCdtIS6yDPGe3HfQv1JkQ+9A8zv88hRmzeIx2JyT/shtIqGo+4ZTJd5cma--Lij/n0+cpstyZD28--FOUhwW3y+0jdaYkKvG2xrg==

View File

@@ -1 +0,0 @@
2a8a17892dd9f41ea50c61310c83240b

View File

@@ -1 +0,0 @@
zdpQvlbfqXzaExLzw2LGZkXj97HH42jWZFSTlINpf/HlFr6NubPxLkVxeGsittJj5rm8yL+V21zPxp30Z7Q7R45qyCKFtevtVeqb+u1nZ/FsVfkwb/7wDW9scurgXw==--SB6C0aaNf8qPdteG--9hP+6tpsMnyiAVVvAIq+7w==

View File

@@ -1 +1 @@
xgPOFd8315z7lFtTR5/nD6WDBM2M6Grt/pmkCPdaqlw0WAmFKzbiRGFsXoUQ02JNzvT1/FVtBSsAcyK1Pdr1QQztlWC+/ywaflloMBS4//D8IEXvEgCK6uff5gcf1A==--WbFrw9advCJ4mqsK--HTVHZqO0ddG1toFpY0KKgQ== vqH5By5qFLImVjdlWj+7FwGg8APKnr/AEd7WqekG7L0vNA32WGBpwS1uGzs02LIcATRwGj8DyJxiBOB/w9z8cwoO+t6Woi5hAnOSCQwFWKLT0dZq7jgtT8pxK0Yu/Nf91PEFN1rc/8ZFy2KKVpbtMbMPyivT38e/ctBZD/lHrWkndvLXYvFVhqWjUnDOGbhwl/U0RZgqBBjvlm3B0JkQfiN8VXPlCJL2Cd8kd0+MpRCRTgtcxA==--OdVXnDP7OhzJxCsP--+8SI6IFIeXyDxXb+WpqhIQ==

View File

@@ -63,4 +63,7 @@ Rails.application.configure do
config.action_mailer.raise_delivery_errors = false config.action_mailer.raise_delivery_errors = false
# Base URL to be used by email template link helpers # 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" }
# Allow requests from any IP
config.web_console.whiny_requests = false
end end

View File

@@ -26,13 +26,13 @@ authorizations: &AUTHORIZATIONS
## Environment ## Environment
development: development:
host: 192.168.56.5 host: <%= ENV["LDAP_HOST"] || "localhost" %>
port: 389 port: <%= ENV["LDAP_PORT"] || "389" %>
attribute: cn attribute: cn
base: ou=kosmos.org,cn=users,dc=kosmos,dc=org base: <%= ENV["LDAP_BASE"] || "ou=kosmos.org,cn=users,dc=kosmos,dc=org" %>
admin_user: "cn=Directory Manager" admin_user: "cn=Directory Manager"
admin_password: localpass admin_password: <%= ENV["LDAP_ADMIN_PASSWORD"] %>
# ssl: false ssl: <%= ENV["LDAP_USE_TLS"] || "false" %>
# <<: *AUTHORIZATIONS # <<: *AUTHORIZATIONS
test: test:
@@ -46,11 +46,11 @@ test:
# <<: *AUTHORIZATIONS # <<: *AUTHORIZATIONS
production: production:
host: ldap.kosmos.org host: ldap.kosmos.local
port: 636 port: 389
attribute: cn attribute: cn
base: ou=kosmos.org,cn=users,dc=kosmos,dc=org base: ou=kosmos.org,cn=users,dc=kosmos,dc=org
admin_user: <%= Rails.application.credentials.ldap[:username] rescue nil %> admin_user: <%= Rails.application.credentials.ldap[:username] rescue nil %>
admin_password: <%= Rails.application.credentials.ldap[:password] rescue nil %> admin_password: <%= Rails.application.credentials.ldap[:password] rescue nil %>
ssl: simple_tls # ssl: false
# <<: *AUTHORIZATIONS # <<: *AUTHORIZATIONS

View File

@@ -20,10 +20,17 @@ Rails.application.routes.draw do
resources :donations resources :donations
get 'wallet', to: 'wallet#index' get 'wallet', to: 'wallet#index'
get 'wallet/transactions', to: 'wallet#transactions'
get 'lnurlpay/:address', to: 'lnurlpay#index', constraints: { address: /[^\/]+/} get 'lnurlpay/:address', to: 'lnurlpay#index', constraints: { address: /[^\/]+/}
get 'lnurlpay/:address/invoice', to: 'lnurlpay#invoice', constraints: { address: /[^\/]+/} get 'lnurlpay/:address/invoice', to: 'lnurlpay#invoice', constraints: { address: /[^\/]+/}
post 'webhooks/lndhub', to: 'webhooks#lndhub'
namespace :api do
get 'kredits/onchain_btc_balance', to: 'kredits#onchain_btc_balance'
end
namespace :admin do namespace :admin do
root to: 'dashboard#index' root to: 'dashboard#index'
get 'invitations', to: 'invitations#index' get 'invitations', to: 'invitations#index'

View File

@@ -0,0 +1,9 @@
class AddLnAccountToUsers < ActiveRecord::Migration[7.0]
def change
add_column :users, :ln_account, :string
User.all.each do |user|
user.update! ln_account: user.ln_login
end
end
end

View File

@@ -2,25 +2,24 @@
# of editing this file, please use the migrations feature of Active Record to # of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition. # incrementally modify your database, and then regenerate this schema definition.
# #
# This file is the source Rails uses to define your schema when running `rails # This file is the source Rails uses to define your schema when running `bin/rails
# db:schema:load`. When creating a new database, `rails db:schema:load` tends to # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
# be faster and is potentially less error prone than running all of your # be faster and is potentially less error prone than running all of your
# migrations from scratch. Old migrations may fail to apply correctly if those # migrations from scratch. Old migrations may fail to apply correctly if those
# migrations use external dependencies or application code. # migrations use external dependencies or application code.
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2021_11_20_010540) do ActiveRecord::Schema[7.0].define(version: 2023_01_11_113139) do
create_table "donations", force: :cascade do |t| create_table "donations", force: :cascade do |t|
t.integer "user_id" t.integer "user_id"
t.integer "amount_sats" t.integer "amount_sats"
t.integer "amount_eur" t.integer "amount_eur"
t.integer "amount_usd" t.integer "amount_usd"
t.string "public_name" t.string "public_name"
t.datetime "created_at", precision: 6, null: false t.datetime "created_at", null: false
t.datetime "updated_at", precision: 6, null: false t.datetime "updated_at", null: false
t.datetime "paid_at" t.datetime "paid_at", precision: nil
t.index ["user_id"], name: "index_donations_on_user_id" t.index ["user_id"], name: "index_donations_on_user_id"
end end
@@ -28,25 +27,28 @@ ActiveRecord::Schema.define(version: 2021_11_20_010540) do
t.string "token" t.string "token"
t.integer "user_id" t.integer "user_id"
t.integer "invited_user_id" t.integer "invited_user_id"
t.datetime "used_at" t.datetime "used_at", precision: nil
t.datetime "created_at", precision: 6, null: false t.datetime "created_at", null: false
t.datetime "updated_at", precision: 6, null: false t.datetime "updated_at", null: false
t.index ["invited_user_id"], name: "index_invitations_on_invited_user_id"
t.index ["user_id"], name: "index_invitations_on_user_id"
end end
create_table "users", force: :cascade do |t| create_table "users", force: :cascade do |t|
t.string "cn" t.string "cn"
t.string "ou" t.string "ou"
t.datetime "created_at", precision: 6, null: false t.datetime "created_at", null: false
t.datetime "updated_at", precision: 6, null: false t.datetime "updated_at", null: false
t.string "email", default: "", null: false t.string "email", default: "", null: false
t.string "reset_password_token" t.string "reset_password_token"
t.datetime "reset_password_sent_at" t.datetime "reset_password_sent_at", precision: nil
t.string "confirmation_token" t.string "confirmation_token"
t.datetime "confirmed_at" t.datetime "confirmed_at", precision: nil
t.datetime "confirmation_sent_at" t.datetime "confirmation_sent_at", precision: nil
t.string "unconfirmed_email" t.string "unconfirmed_email"
t.text "ln_login_ciphertext" t.text "ln_login_ciphertext"
t.text "ln_password_ciphertext" t.text "ln_password_ciphertext"
t.string "ln_account"
t.index ["email"], name: "index_users_on_email", unique: true t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end end

View File

@@ -1,7 +1,22 @@
# This file should contain all the record creation needed to seed the database with its default values. require 'sidekiq/testing'
# The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup).
# ldap = LdapService.new
# Examples:
# Sidekiq::Testing.inline! do
# movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }]) CreateAccount.call(
# Character.create(name: 'Luke', movie: movies.first) username: "admin", domain: "kosmos.org", email: "admin@example.com",
password: "admin is admin", confirmed: true
)
ldap.add_attribute "cn=admin,ou=kosmos.org,cn=users,dc=kosmos,dc=org", :admin, "true"
5.times do |n|
username = Faker::Name.unique.first_name.downcase
email = Faker::Internet.unique.email
CreateAccount.call(
username: username, domain: "kosmos.org", email: email,
password: "user is user", confirmed: true
)
end
end

34
docker-compose.yml Normal file
View File

@@ -0,0 +1,34 @@
services:
ldap:
image: 4teamwork/389ds:latest
volumes:
- ./tmp/389ds:/data
ports:
- "389:3389"
environment:
DS_DM_PASSWORD: passthebutter
SUFFIX_NAME: "dc=kosmos,dc=org"
# phpldapadmin:
# image: osixia/phpldapadmin:0.9.0
# ports:
# - "8389:80"
# environment:
# PHPLDAPADMIN_HTTPS: false
# PHPLDAPADMIN_LDAP_HOSTS: "#PYTHON2BASH:[{'ldap': [{'server': [{'tls': False}, {'port': 3389}]}, {'login': [{'bind_id': 'cn=Directory Manager'}, {'bind_pass': 'passthebutter'}]}]}]"
# PHPLDAPADMIN_LDAP_CLIENT_TLS: false
# web:
# build: .
# tty: true
# command: bash -c "sleep 5 && rm -f tmp/pids/server.pid && bin/dev"
# volumes:
# - .:/akkounts
# ports:
# - "3000:3000"
# environment:
# RAILS_ENV: development
# LDAP_HOST: ldap
# LDAP_PORT: 3389
# LDAP_ADMIN_PASSWORD: passthebutter
# LDAP_USE_TLS: "false"
# depends_on:
# - ldap

8
docker/entrypoint.sh Normal file
View File

@@ -0,0 +1,8 @@
#!/bin/bash
set -e
# Remove a potentially pre-existing server.pid for Rails.
rm -f /myapp/tmp/pids/server.pid
# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"

28
lib/tasks/ldap.rake Normal file
View File

@@ -0,0 +1,28 @@
namespace :ldap do
desc "Reset the LDAP directory and set up base entries and default org"
task setup: :environment do |t, args|
ldap = LdapService.new
ldap.delete_entry "cn=admin_role,ou=kosmos.org,cn=users,dc=kosmos,dc=org", true
# Delete all existing entries and re-add base entries
ldap.reset_directory!
ldap.add_organization "kosmos.org", "Kosmos", true
# add admin role
ldap.add_entry "cn=admin_role,ou=kosmos.org,cn=users,dc=kosmos,dc=org", {
objectClass: %w{top LDAPsubentry nsRoleDefinition nsComplexRoleDefinition nsFilteredRoleDefinition},
cn: "admin_role",
nsRoleFilter: "(&(objectclass=person)(admin=true))",
description: "filtered role for admins"
}, true
end
desc "List user domains/organizations"
task list_organizations: :environment do |t, args|
ldap = LdapService.new
orgs = ldap.fetch_organizations
puts orgs.inspect
end
end

View File

@@ -5,4 +5,47 @@ namespace :lndhub do
CreateLndhubWalletJob.perform_later(user) CreateLndhubWalletJob.perform_later(user)
end end
end end
desc "List wallet balances"
task :balances => :environment do |t, args|
sum = 0
User.all.each do |user|
lndhub = Lndhub.new
auth_token = lndhub.authenticate(user)
data = lndhub.balance(auth_token)
balance = data["BTC"]["AvailableBalance"] rescue nil
if balance && balance > 0
sum += balance
puts "#{user.address}: #{balance} sats"
end
end
puts "--\nSum of user balances: #{sum} sats"
end
desc "Migrate existing accounts to lndhub.go"
task :migrate => :environment do |t, args|
# user = User.find_by cn: "jimmy"
User.all.each do |user|
puts "Migrating #{user.cn}"
puts "Creating account..."
lndhub_v2 = LndhubV2.new
res = lndhub_v2.create_account login: user.ln_login, password: user.ln_password
puts res.inspect
lndhub = Lndhub.new
lndhub.authenticate(user)
data = lndhub.balance
balance = data["BTC"]["AvailableBalance"] rescue 0
if balance > 0
lndhub_v2.authenticate(user)
desc = "Balance migration from old Kosmos Lightning back-end"
res = lndhub_v2.create_invoice amount: balance, description: desc
puts "Payment request for #{user.cn} (#{balance} sats):"
puts res["payment_request"]
end
puts "---"
end
end
end end

View File

@@ -2,20 +2,18 @@
"name": "akkounts", "name": "akkounts",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@tailwindcss/forms": "^0.4.0", "@tailwindcss/forms": "^0.5.3",
"autoprefixer": "^10.4.2", "autoprefixer": "^10.4.13",
"postcss": "^8.4.6", "postcss": "^8.4.19",
"postcss-flexbugs-fixes": "^5.0.2", "postcss-flexbugs-fixes": "^5.0.2",
"postcss-import": "^14.0.2", "postcss-import": "^15.0.1",
"postcss-nested": "^5.0.6", "postcss-nested": "^6.0.0",
"postcss-preset-env": "^7.3.1", "postcss-preset-env": "^7.8.3",
"sass": "^1.49.7", "tailwindcss": "^3.2.4"
"tailwindcss": "^3.0.22"
}, },
"version": "0.3.0", "version": "0.4.0",
"scripts": { "scripts": {
"build:css:sass": "sass ./app/assets/stylesheets/legacy.sass.scss ./app/assets/builds/legacy.css --no-source-map --load-path=node_modules",
"build:css:tailwind": "tailwindcss --postcss -i ./app/assets/stylesheets/application.tailwind.css -o ./app/assets/builds/application.css", "build:css:tailwind": "tailwindcss --postcss -i ./app/assets/stylesheets/application.tailwind.css -o ./app/assets/builds/application.css",
"build:css": "yarn run build:css:sass && yarn run build:css:tailwind" "build:css": "yarn run build:css:tailwind"
} }
} }

View File

@@ -0,0 +1,11 @@
require "rails_helper"
RSpec.describe WalletSummaryComponent, type: :component do
it "renders the balance as a human-readable number" do
expect(
render_inline(described_class.new(balance: 2301000)) {}.css("section").to_html
).to include(
"2,301,000 sats"
)
end
end

19
spec/fixtures/lndhub/incoming.json vendored Normal file
View File

@@ -0,0 +1,19 @@
{
"id": 58,
"type": "incoming",
"user_login": "689e27b237798b41d123",
"amount": 100,
"fee": 0,
"memo": "Sats for jimmy@kosmos.org: \"Buy you some beers\"",
"description_hash": "106af234beebd478206535486051b4f212bd31d2ed0f93e3efce7b5e7603d743",
"payment_request": "lnbc1u1p3mull3pp5qw4x46ew6kjknudypyjsg8maw935tr5kkuz7t6h7pugp3pt4msyqhp5zp40yd97a028sgr9x4yxq5d57gft6vwja58e8cl0eea4uasr6apscqzpgxqyz5vqsp53m2n8h6yeflgukv5fhwm802kur6un9w8nvycl7auk67w5g2u008q9qyyssqml8rfmxyvp32qd5939qx7uu0w6ppjuujlpwsrz28m9u0dzp799hz5j72w0xm8pg97hd4hdvwh9zxaw2hewnnmzewvc550f9y3qsfaegphmk0mu",
"destination_pubkey_hex": "024cd3be18617f39cf645851e3ba63f51fc13f0bb09e3bb25e6fd4de556486d946",
"r_hash": "03aa6aeb2ed5a569f1a40925041f7d7163458e96b705e5eafe0f10188575dc08",
"preimage": "3539663535656537343331663432653165396430623966633664656664646563",
"keysend": false,
"state": "settled",
"created_at": "2023-01-11T09:22:57.546364Z",
"expires_at": "2023-01-12T09:22:57.547209Z",
"updated_at": "2023-01-11T09:22:58.046236131Z",
"settled_at": "2023-01-11T09:22:58.046232174Z"
}

View File

@@ -1,13 +1,13 @@
require 'rails_helper' require 'rails_helper'
require 'webmock/rspec' require 'webmock/rspec'
RSpec.describe CreateLndhubWalletJob, type: :job do RSpec.describe CreateLndhubAccountJob, type: :job do
let(:user) { create :user, cn: "willherschel", ou: "kosmos.org" } let(:user) { create :user, cn: "willherschel", ou: "kosmos.org" }
subject(:job) { described_class.perform_later(user) } subject(:job) { described_class.perform_later(user) }
before do before do
stub_request(:post, "http://localhost:3023/create") stub_request(:post, "http://localhost:3026/v2/users")
.to_return(status: 200, headers: {}, .to_return(status: 200, headers: {},
body: { login: "abc123", password: "def456" }.to_json) body: { login: "abc123", password: "def456" }.to_json)
end end
@@ -15,8 +15,8 @@ RSpec.describe CreateLndhubWalletJob, type: :job do
it "creates a new LndHub account" do it "creates a new LndHub account" do
perform_enqueued_jobs { job } perform_enqueued_jobs { job }
expect(WebMock).to have_requested(:post, "http://localhost:3023/create") expect(WebMock).to have_requested(:post, "http://localhost:3026/v2/users")
.with { |req| req.body == '{"partnerid":"kosmos.org","accounttype":"user"}' } .with { |req| req.body == '{}' }
user.reload user.reload
expect(user.ln_login).to eq("abc123") expect(user.ln_login).to eq("abc123")

View File

@@ -1,7 +1,7 @@
require 'rails_helper' require 'rails_helper'
require 'webmock/rspec' require 'webmock/rspec'
RSpec.describe ExchangeXmppContactsJob, type: :job do RSpec.describe XmppExchangeContactsJob, type: :job do
let(:user) { create :user, cn: "willherschel", ou: "kosmos.org" } let(:user) { create :user, cn: "willherschel", ou: "kosmos.org" }
subject(:job) { subject(:job) {

View File

@@ -10,6 +10,8 @@ require 'capybara'
require 'devise' require 'devise'
require 'support/controller_macros' require 'support/controller_macros'
require 'support/database_cleaner' require 'support/database_cleaner'
require "view_component/test_helpers"
require "capybara/rspec"
# Requires supporting ruby files with custom matchers and macros, etc, in # Requires supporting ruby files with custom matchers and macros, etc, in
# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
@@ -70,5 +72,8 @@ RSpec.configure do |config|
config.include Warden::Test::Helpers config.include Warden::Test::Helpers
config.include FactoryBot::Syntax::Methods config.include FactoryBot::Syntax::Methods
config.include ActiveJob::TestHelper, type: :job config.include ActiveJob::TestHelper, type: :job
config.include ViewComponent::TestHelpers, type: :component
config.include Capybara::RSpecMatchers, type: :component
config.extend ControllerMacros, :type => :controller config.extend ControllerMacros, :type => :controller
end end

View File

@@ -0,0 +1,43 @@
require 'rails_helper'
require 'webmock/rspec'
RSpec.describe "/api/kredits", type: :request do
describe "GET /onchain_btc_balance" do
before do
stub_request(:get, "http://btcpay.example.com/api/v1/stores/123456/payment-methods/onchain/BTC/wallet")
.to_return(status: 200, headers: {}, body: {
balance: 0.91108606,
unconfirmedBalance: 0,
confirmedBalance: 0.91108606
}.to_json)
end
it "returns a formatted result for the onchain wallet balance" do
get api_kredits_onchain_btc_balance_path
expect(response).to have_http_status(:ok)
res = JSON.parse(response.body)
expect(res["balance"]).to eq(0.91108606)
expect(res["unconfirmed_balance"]).to eq(0)
expect(res["confirmed_balance"]).to eq(0.91108606)
end
context "upstream request error" do
before do
stub_request(:get, "http://btcpay.example.com/api/v1/stores/123456/payment-methods/onchain/BTC/wallet")
.to_return(status: 500, headers: {}, body: "")
end
it "returns a formatted error" do
get api_kredits_onchain_btc_balance_path
expect(response).to have_http_status(:server_error)
res = JSON.parse(response.body)
expect(res["error"]).not_to be_nil
end
end
end
end

View File

@@ -0,0 +1,20 @@
require 'rails_helper'
RSpec.describe "Webhooks", type: :request do
describe "Allowed IP addresses" do
context "IP not allowed" do
it "returns a 403 status" do
post "/webhooks/lndhub"
expect(response).to have_http_status(:forbidden)
end
end
context "IP allowed" do
it "returns a 403 status" do
ENV['WEBHOOKS_ALLOWED_IPS'] = '127.0.0.1'
post "/webhooks/lndhub"
expect(response).to have_http_status(:unprocessable_entity)
end
end
end
end

View File

@@ -93,7 +93,7 @@ RSpec.describe CreateAccount, type: :model do
end end
end end
describe "#create_lndhub_wallet" do describe "#create_lndhub_account" do
include ActiveJob::TestHelper include ActiveJob::TestHelper
let(:service) { CreateAccount.new( let(:service) { CreateAccount.new(
@@ -102,8 +102,8 @@ RSpec.describe CreateAccount, type: :model do
)} )}
let(:new_user) { create :user, cn: "halfinney", ou: "kosmos.org" } let(:new_user) { create :user, cn: "halfinney", ou: "kosmos.org" }
it "enqueues a job to create an LndHub wallet" do it "enqueues a job to create an LndHub account" do
service.send(:create_lndhub_wallet, new_user) service.send(:create_lndhub_account, new_user)
expect(enqueued_jobs.size).to eq(1) expect(enqueued_jobs.size).to eq(1)

View File

855
yarn.lock

File diff suppressed because it is too large Load Diff