86 Commits

Author SHA1 Message Date
aa0ba18763 Merge pull request 'Fix password validation during password reset' (#83) from bugfix/28-password_reset into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #83
Reviewed-by: bumi <bumi@noreply.kosmos.org>
2023-02-19 14:01:25 +00:00
Râu Cao
7dae66959e Formatting
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-02-19 15:54:59 +08:00
Râu Cao
b67d6139ac Fix password validation during password reset
fixes #28
2023-02-19 15:54:55 +08:00
Râu Cao
b9259958f4 Add spec to prove issue #28
All checks were successful
continuous-integration/drone/push Build is passing
2023-02-19 14:41:45 +08:00
Râu Cao
832d1e3bd7 Improve layout of password reset form 2023-02-19 14:41:16 +08:00
Râu Cao
8f9e1c3e84 Improve lnurlp message and notification
All checks were successful
continuous-integration/drone/push Build is passing
2023-01-25 13:18:44 +08:00
4a045bf61c Merge pull request 'Various front-end improvements and bugfixes' (#78) from feature/frontend_improvements into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #78
Reviewed-by: galfert <garret.alfert@gmail.com>
Reviewed-by: bumi <bumi@noreply.kosmos.org>
2023-01-25 04:16:55 +00:00
f62e49f524 Merge pull request 'Add Webhooks and XMPP notifications for incoming sats' (#79) from feature/webhooks into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #79
Reviewed-by: bumi <bumi@noreply.kosmos.org>
2023-01-13 04:33:02 +00:00
Râu Cao
b0c787bbc7 Throw exception when user cannot be found
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-01-13 12:24:22 +08:00
Râu Cao
86dc44d096 Add empty state for wallet transactions view
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-01-12 16:21:40 +08:00
Râu Cao
a1663b9f9d Add specs for lndhub webhook
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-01-12 14:33:31 +08:00
Râu Cao
aa3c2b4fa2 Remove hardcoded user address from hook 2023-01-12 14:32:53 +08:00
Râu Cao
4c0d8283e3 Make status code explicit 2023-01-12 14:32:35 +08:00
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
Râu Cao
99dc36f13a Make empty donations page prettier
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-01-10 14:13:28 +08:00
Râu Cao
ee74c4847f Make invitation page prettier when it's empty 2023-01-10 14:13:27 +08:00
Râu Cao
15b63eee73 Add coming-soon note to disabled settings nav items 2023-01-10 14:13:27 +08:00
Râu Cao
c756528d32 Allow to copy invitation URLs via button 2023-01-10 14:13:27 +08:00
Râu Cao
fef29b4fc0 Add more info about project contributions 2023-01-10 14:13:27 +08:00
Râu Cao
38608e053d Add Zeus to recommended wallet apps 2023-01-10 14:13:26 +08:00
Râu Cao
5f215b8ed8 Replace vanilla JS with new clipboard code 2023-01-10 14:13:26 +08:00
Râu Cao
87aae35974 Add a clipboard controller and wire up the copy button 2023-01-10 14:13:26 +08:00
Râu Cao
6ad02e69a2 WIP Profile settings page
Show the user's user address, and provide a button for copying it to the
clipboard
2023-01-10 14:13:26 +08:00
Râu Cao
94ca0f3764 Rename settings page 2023-01-10 14:13:25 +08:00
Râu Cao
0fec37e0a9 Add inviter and time to admin invitations list 2023-01-10 14:13:25 +08:00
Râu Cao
620befd7c0 Fix devise not rendering errors as flash messages
https://github.com/heartcombo/devise/issues/5446

closes #63
2023-01-10 14:13:25 +08:00
Râu Cao
aba4930696 Set a minimum height for content with sidenav 2023-01-10 14:13:25 +08:00
Râu Cao
0492b42327 Improve button style 2023-01-10 14:13:25 +08:00
Râu Cao
445a1c80a6 Refactor settings routes and menu
Use sub controllers/routes for the sections
2023-01-10 14:13:24 +08:00
Râu Cao
cf48f76553 Fix web container start when offline 2023-01-10 14:13:24 +08:00
Râu Cao
70fa43f5d2 Use tabnav component for wallet view 2023-01-10 14:13:24 +08:00
Râu Cao
b37a0c25a4 Wording 2023-01-10 14:13:23 +08:00
Râu Cao
3197743a55 Change donations to contrbutions, add tabbed nav
Introduces components for tabbed navigation and adds a tab menu and item
for non-financial contributions to the donations/contributions page.
2023-01-10 14:13:23 +08:00
Râu Cao
3f49e4a3b8 Use more appropriate icon in sidenav 2023-01-10 14:13:23 +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
97 changed files with 1904 additions and 810 deletions

View File

@@ -17,7 +17,7 @@ steps:
branch:
- master
- name: rspec
image: guildeducation/rails:2.7.2-12.22.0
image: guildeducation/rails:2.7.2-14.20.0
environment:
RAILS_ENV: test
commands:

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'
BTCPAY_API_URL='http://localhost:23001/api/v1'
LNDHUB_API_URL='http://localhost:3023'
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'
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'
WEBHOOKS_ALLOWED_IPS='10.1.1.163'

View File

@@ -1,3 +1,9 @@
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'
WEBHOOKS_ALLOWED_IPS='10.1.1.23'

22
Dockerfile Normal file
View File

@@ -0,0 +1,22 @@
# 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 gem install foreman
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'
# Background/scheduled jobs
gem 'sidekiq'
gem 'sidekiq', '< 7'
gem 'sidekiq-scheduler'
group :development, :test do
# Use sqlite3 as the database for Active Record
gem 'sqlite3', '~> 1.4'
gem 'rspec-rails'
gem "byebug", "~> 11.1"
end
group :development do
@@ -58,6 +59,7 @@ group :development do
gem 'listen', '~> 3.2'
gem 'letter_opener'
gem 'letter_opener_web'
gem 'faker'
end
group :test do

View File

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

View File

@@ -7,6 +7,27 @@ credentials, invites, donations, etc..
## 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
Installing dependencies:
@@ -31,10 +52,32 @@ Running all specs:
bundle exec rspec
### LDAP server
### Docker (Compose)
TODO make it easy to run a local Kosmos LDAP server for development, without
manual LDIF imports etc. (or provide a staging instance)
There is a working Docker Compose config file, which allows you to spin up both
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
@@ -63,3 +106,5 @@ manual LDIF imports etc. (or provide a staging instance)
## License
[GNU Affero General Public License v3.0](https://choosealicense.com/licenses/agpl-3.0/)
[1]: https://docs.docker.com/compose/install/

View File

@@ -1,4 +1,8 @@
@layer base {
html {
font-size: 14px;
}
body {
@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');

View File

@@ -6,12 +6,16 @@
.btn-md {
@apply btn;
@apply py-2.5 px-5 shadow-md;
@apply py-3 px-6;
}
.btn-sm {
@apply btn;
@apply py-1 px-2 text-sm shadow-sm;
@apply py-1 px-2 text-sm;
}
.btn-icon {
@apply px-3;
}
.btn-gray {
@@ -28,4 +32,8 @@
@apply bg-red-600 hover:bg-red-700 text-white
focus:ring-red-500 focus:ring-opacity-75;
}
input[type=text]:disabled {
@apply text-gray-700;
}
}

View File

@@ -1,5 +1,5 @@
<main class="w-full max-w-6xl mx-auto pb-12 px-4 md:px-6 lg:px-8">
<div class="bg-white rounded-lg shadow px-6 sm:px-12 py-8 sm:py-12">
<div class="md:min-h-[50vh] bg-white rounded-lg shadow px-6 sm:px-12 py-8 sm:py-12">
<%= content %>
</div>
</main>

View File

@@ -1,6 +1,6 @@
<main class="w-full max-w-6xl mx-auto pb-12 px-4 md:px-6 lg:px-8">
<div class="bg-white rounded-lg shadow">
<div class="divide-y divide-gray-200 lg:grid lg:grid-cols-12 lg:divide-y-0 lg:divide-x">
<div class="md:min-h-[50vh] divide-y divide-gray-200 lg:grid lg:grid-cols-12 lg:divide-y-0 lg:divide-x">
<aside class="py-6 sm:py-8 lg:col-span-3">
<nav class="space-y-1">
<%= render partial: @sidenav_partial %>

View File

@@ -0,0 +1,10 @@
<main class="w-full max-w-6xl mx-auto pb-12 px-4 md:px-6 lg:px-8">
<div class="bg-white rounded-lg shadow">
<div class="px-6 sm:px-12 pt-2 sm:pt-4">
<%= render partial: @tabnav_partial %>
</div>
<div class="px-6 sm:px-12 py-8 sm:py-12">
<%= content %>
</div>
</div>
</main>

View File

@@ -0,0 +1,7 @@
# frozen_string_literal: true
class MainWithTabnavComponent < ViewComponent::Base
def initialize(tabnav_partial:)
@tabnav_partial = tabnav_partial
end
end

View File

@@ -1,4 +1,4 @@
<%= link_to @path, class: @link_class do %>
<%= link_to @path, class: @link_class, title: (@disabled ? "Coming soon" : nil) do %>
<%= render partial: "icons/#{@icon}", locals: { custom_class: @icon_class } %>
<span class="truncate"><%= @name %></span>
<% end %>

View File

@@ -0,0 +1,3 @@
<%= link_to @path, class: @link_class do %>
<%= @name %>
<% end %>

View File

@@ -0,0 +1,21 @@
# frozen_string_literal: true
class TabnavLinkComponent < ViewComponent::Base
def initialize(name:, path:, active: false, disabled: false)
@name = name
@path = path
@active = active
@disabled = disabled
@link_class = class_names_link(path)
end
def class_names_link(path)
if @active
"border-indigo-500 text-indigo-600 w-1/2 py-4 px-1 text-center border-b-2"
elsif @disabled
"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"
else
"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"
end
end
end

View File

@@ -0,0 +1,7 @@
class AccountController < ApplicationController
before_action :require_user_signed_in
def index
@current_section = :account
end
end

View File

@@ -27,7 +27,7 @@ class Admin::LdapUsersController < Admin::BaseController
def ldap_client
ldap_client ||= Net::LDAP.new host: ldap_config['host'],
port: ldap_config['port'],
encryption: ldap_config['ssl'],
# encryption: ldap_config['ssl'],
auth: {
method: :simple,
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,4 +1,4 @@
class DonationsController < ApplicationController
class Contributions::DonationsController < ApplicationController
before_action :require_user_signed_in
# GET /donations

View File

@@ -0,0 +1,8 @@
class Contributions::ProjectsController < ApplicationController
before_action :require_user_signed_in
# GET /contributions
def index
@current_section = :contributions
end
end

View File

@@ -1,7 +1,7 @@
class LnurlpayController < ApplicationController
before_action :find_user_by_address
MIN_SATS = 100
MIN_SATS = 10
MAX_SATS = 1_000_000
MAX_COMMENT_CHARS = 100
@@ -32,7 +32,7 @@ class LnurlpayController < ApplicationController
return
end
memo = "Sats for #{address}"
memo = "To #{address}"
memo = "#{memo}: \"#{comment}\"" if comment.present?
payment_request = @user.ln_create_invoice({

View File

@@ -1,7 +0,0 @@
class SecurityController < ApplicationController
before_action :require_user_signed_in
def index
@current_section = :security
end
end

View File

@@ -0,0 +1,13 @@
class Settings::AccountController < SettingsController
def index
end
def reset_password
current_user.send_reset_password_instructions
sign_out current_user
msg = "We have sent you an email with a link to reset your password."
redirect_to check_your_email_path, notice: msg
end
end

View File

@@ -0,0 +1,11 @@
class Settings::ProfileController < SettingsController
def index
@user = current_user
end
def update
end
end

View File

@@ -1,13 +1,13 @@
class SettingsController < ApplicationController
before_action :require_user_signed_in
before_action :set_current_section
def index
end
def reset_password
current_user.send_reset_password_instructions
sign_out current_user
msg = "We have sent you an email with a link to reset your password."
redirect_to check_your_email_path, notice: msg
private
def set_current_section
@current_section = :settings
end
end

View File

@@ -0,0 +1,18 @@
class TurboController < ApplicationController
class Responder < ActionController::Responder
def to_turbo_stream
controller.render(options.merge(formats: :html))
rescue ActionView::MissingTemplate => error
if get?
raise error
elsif has_errors? && default_action
render rendering_options.merge(formats: :html, status: :unprocessable_entity)
else
redirect_to navigation_location
end
end
end
self.responder = Responder
respond_to :html, :turbo_stream
end

View File

@@ -0,0 +1,18 @@
class Users::DeviseController < ApplicationController
class Responder < ActionController::Responder
def to_turbo_stream
controller.render(options.merge(formats: :html))
rescue ActionView::MissingTemplate => error
if get?
raise error
elsif has_errors? && default_action
render rendering_options.merge(formats: :html, status: :unprocessable_entity)
else
redirect_to navigation_location
end
end
end
self.responder = Responder
respond_to :html, :turbo_stream
end

View File

@@ -28,13 +28,13 @@ class WalletController < ApplicationController
private
def authenticate_with_lndhub
if session["ln_auth_token"].present?
@ln_auth_token = session["ln_auth_token"]
def authenticate_with_lndhub(options={})
if session[:ln_auth_token].present? && !options[:force_reauth]
@ln_auth_token = session[:ln_auth_token]
else
lndhub = Lndhub.new
auth_token = lndhub.authenticate(current_user)
session["ln_auth_token"] = auth_token
session[:ln_auth_token] = auth_token
@ln_auth_token = auth_token
end
rescue
@@ -49,14 +49,23 @@ class WalletController < ApplicationController
lndhub = Lndhub.new
data = lndhub.balance @ln_auth_token
@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)

View File

@@ -0,0 +1,40 @@
class WebhooksController < ApplicationController
skip_forgery_protection
before_action :authorize_request
def lndhub
begin
payload = JSON.parse(request.body.read, symbolize_names: true)
head :no_content and 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,
subject: "Sats received!",
body: "#{amt_sats} sats received in your Lightning wallet:\n> #{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

@@ -0,0 +1,16 @@
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["source", "trigger"]
copy (event) {
event.preventDefault();
navigator.clipboard.writeText(this.sourceTarget.value);
this.triggerTarget.querySelector('.content-initial').classList.add('hidden');
this.triggerTarget.querySelector('.content-active').classList.remove('hidden');
setTimeout(() => {
this.triggerTarget.querySelector('.content-initial').classList.remove('hidden');
this.triggerTarget.querySelector('.content-active').classList.add('hidden');
}, 2000)
}
}

View File

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

View File

@@ -33,10 +33,12 @@ class User < ApplicationRecord
end
def reset_password(new_password, new_password_confirmation)
if new_password == new_password_confirmation && ::Devise.ldap_update_password
Devise::LDAP::Adapter.update_password(login_with, new_password)
end
clear_reset_password_token if valid?
self.password = new_password
self.password_confirmation = new_password_confirmation
return false unless valid?
Devise::LDAP::Adapter.update_password(login_with, new_password)
clear_reset_password_token
save
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]
@password = args[:password]
@invitation = args[:invitation]
@confirmed = args[:confirmed]
end
def call
user = create_user_in_database
add_ldap_document
create_lndhub_wallet(user)
create_lndhub_account(user)
if @invitation.present?
update_invitation(user.id)
@@ -26,7 +27,8 @@ class CreateAccount < ApplicationService
ou: @domain,
email: @email,
password: @password,
password_confirmation: @password
password_confirmation: @password,
confirmed_at: @confirmed ? DateTime.now : nil
)
end
@@ -35,6 +37,7 @@ class CreateAccount < ApplicationService
end
# TODO move to confirmation
# (and/or add email_confirmed to entry and use in login filter)
def add_ldap_document
hashed_pw = Devise.ldap_auth_password_builder.call(@password)
CreateLdapUserJob.perform_later(@username, @domain, @email, hashed_pw)
@@ -43,24 +46,12 @@ class CreateAccount < ApplicationService
def exchange_xmpp_contacts
#TODO enable in development when we have easy setup of ejabberd etc.
return if Rails.env.development?
ExchangeXmppContactsJob.perform_later(@invitation.user, @username, @domain)
XmppExchangeContactsJob.perform_later(@invitation.user, @username, @domain)
end
def create_lndhub_wallet(user)
CreateLndhubWalletJob.perform_later(user)
end
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"
})
def create_lndhub_account(user)
#TODO enable in development when we have a local lndhub (mock?) API
return if Rails.env.development?
CreateLndhubAccountJob.perform_later(user)
end
end

View File

@@ -17,4 +17,8 @@ class EjabberdApiClient
def add_rosteritem(payload)
post "add_rosteritem", payload
end
def send_message(payload)
post "send_message", payload
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
def initialize
@base_url = ENV["LNDHUB_API_URL"]
@base_url = ENV["LNDHUB_LEGACY_API_URL"]
end
def post(endpoint, payload)
@@ -28,8 +28,13 @@ class Lndhub
"Accept" => "application/json",
"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
def create(payload)
@@ -42,15 +47,15 @@ class Lndhub
self.auth_token
end
def balance(user_token)
def balance(user_token=nil)
get "balance", user_token || auth_token
end
def gettxs(user_token)
def gettxs(user_token=nil)
get "gettxs", user_token || auth_token
end
def getuserinvoices(user_token)
def getuserinvoices(user_token=nil)
get "getuserinvoices", user_token || auth_token
end

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

@@ -17,6 +17,7 @@
<tr>
<th>Token</th>
<th>Accepted</th>
<th>Inviter</th>
<th>Invited user</th>
</tr>
</thead>
@@ -24,7 +25,8 @@
<% @invitations_used.each do |invitation| %>
<tr>
<td class="overflow-ellipsis font-mono"><%= invitation.token %></td>
<td><%= invitation.used_at.strftime("%Y-%m-%d") %></td>
<td><%= invitation.used_at.strftime("%Y-%m-%d (%H:%M UTC)") %></td>
<td><%= invitation.user.address %></td>
<td><%= User.find(invitation.invited_user_id).address %></td>
</tr>
<% end %>

View File

@@ -1,12 +1,12 @@
<%= render HeaderComponent.new(title: "Donations") %>
<%= render HeaderComponent.new(title: "Contributions") %>
<%= render MainSimpleComponent.new do %>
<%= render MainWithTabnavComponent.new(tabnav_partial: "shared/tabnav_contributions") do %>
<section>
<p class="mb-12">
Your financial contributions to the development and upkeep of Kosmos
software and services.
</p>
<% if @donations.any? %>
<p class="mb-12">
Your financial contributions to the development and upkeep of Kosmos
software and services.
</p>
<ul class="list-none">
<% @donations.each do |donation| %>
<li class="mb-8 grid gap-y-2 gap-x-8 grid-cols-2 items-center">
@@ -33,9 +33,19 @@
<% end %>
</ul>
<% else %>
<p class="text-gray-500">
No donations to show.
</p>
<div class="text-center">
<p class="mt-8 mb-12 inline-flex align-center items-center">
<%= image_tag("/img/illustrations/undraw_savings_re_eq4w.svg", class: 'h-48') %>
</p>
<h3>
No donations yet
</h3>
<p class="text-gray-500">
The donation process is not automated yet.<br>Please
<a href="https://wiki.kosmos.org/Main_Page#Community_.2F_Getting_in_touch_.2F_Getting_involved" class="ks-text-link" target="_blank">contact us</a>
if you'd like to contribute this way right now.
</p>
</div>
<% end %>
</section>
<% end %>

View File

@@ -0,0 +1,49 @@
<%= render HeaderComponent.new(title: "Contributions") %>
<%= render MainWithTabnavComponent.new(tabnav_partial: "shared/tabnav_contributions") do %>
<section>
<p class="mb-8">
Project contributions are how we develop and run all Kosmos software and
services. Everything we create and provide is free and open-source
software, even the page you're looking at right now!
</p>
<h3>Start contributing</h3>
<p>
Check out our
<a href="https://kosmos.org/projects/" target="_blank" class="ks-text-link">projects page</a>
for some (but not all) potential places that can use your help.
</p>
<p>
There's something to do for everyone, especially non-programmers! For
example, we need more help with graphics, UI/UX design, and
content/copywriting. We also need moderators for social media. And beta
testers for our software. The list doesn't end there.
</p>
<p>
A good way to get started is to join one of our
<a href="https://community.kosmos.org/t/kosmos-weekly-call/36" target="_blank" class="ks-text-link">weekly calls</a>
and introduce yourself. Alternatively, you can also ping us on any other
medium, or even just grab an open issue on
<a href="https://github.com/67P/" target="_blank" class="ks-text-link">GitHub</a>
or our
<a href="https://gitea.kosmos.org/kosmos/" target="_blank" class="ks-text-link">Gitea</a>
and dive right in (be sure to comment first, to prevent double efforts).
</p>
<p class="mb-8">
Last but not least, if you want to help by proposing new features or
services, head over to the
<a href="https://community.kosmos.org/" target="_blank" class="ks-text-link">community forums</a>,
where you can do just that.
</p>
<h3>Open Source Grants</h3>
<p>
Money coming in from financial contributions is first used to pay for our
bills. Additional funds are being paid out directly to our contributors,
including you, according to their rough share of contributions.
</p>
<p>
We have run two 6-month trials so far, with the next trial period
starting sometime in Q1 2023. Watch your email for notifications about it!
</p>
</section>
<% end %>

View File

@@ -25,7 +25,7 @@
</div>
<div>
<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" %>
</h3>
<p class="text-gray-500">

View File

@@ -11,7 +11,7 @@
<%= f.label :password, "New password" %>
</p>
<p>
<%= f.password_field :password, autofocus: true, autocomplete: "new-password" %>
<%= f.password_field :password, autofocus: true, autocomplete: "new-password", class: "w-full" %>
<% if @minimum_password_length %>
<br><em class="text-sm text-gray-500">(<%= @minimum_password_length %> characters minimum)</em>
<% end %>
@@ -20,10 +20,10 @@
<%= f.label :password_confirmation, "Confirm new password" %>
</p>
<p>
<%= f.password_field :password_confirmation, autocomplete: "new-password" %>
<%= f.password_field :password_confirmation, autocomplete: "new-password", class: "w-full" %>
</p>
<p class="mt-8">
<%= f.submit "Change my password", class: 'btn-md btn-blue' %>
<%= f.submit "Change my password", class: 'btn-md btn-blue w-full' %>
</p>
<% end %>

View File

@@ -2,7 +2,7 @@
<div id="error_explanation">
<ul>
<% resource.errors.full_messages.each do |message| %>
<li><%= message %></li>
<li class="text-red-600"><%= message %></li>
<% end %>
</ul>
</div>

View File

@@ -1 +0,0 @@
json.array! @donations, partial: "donations/donation", as: :donation

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-check"><polyline points="20 6 9 17 4 12"></polyline></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-check <%= custom_class %>"><polyline points="20 6 9 17 4 12"></polyline></svg>

Before

Width:  |  Height:  |  Size: 262 B

After

Width:  |  Height:  |  Size: 283 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-copy"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></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-copy <%= custom_class %>"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>

Before

Width:  |  Height:  |  Size: 351 B

After

Width:  |  Height:  |  Size: 372 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-user"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path><circle cx="12" cy="7" r="4"></circle></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-user <%= custom_class %>"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path><circle cx="12" cy="7" r="4"></circle></svg>

Before

Width:  |  Height:  |  Size: 313 B

After

Width:  |  Height:  |  Size: 334 B

View File

@@ -3,28 +3,41 @@
<%= render MainSimpleComponent.new do %>
<section>
<% if @invitations_unused.any? %>
<p>
<p class="mb-8">
Invite your friends to a Kosmos account by sharing an invitation URL with them:
</p>
<table class="mt-12">
<thead>
<tr>
<th>URL</th>
</tr>
</thead>
<tbody>
<% @invitations_unused.each do |invitation| %>
<tr>
<td class="font-mono"><%= invitation_url(invitation.token) %></td>
</tr>
<% end %>
</tbody>
</table>
<ul>
<% @invitations_unused.each do |invitation| %>
<li class="font-mono mb-1 flex gap-1 md:block"
data-controller="clipboard">
<input type="text" disabled class="md:w-3/4 flex-1"
value="<%= invitation_url(invitation.token) %>"
data-clipboard-target="source" />
<button id="copy-user-address" class="btn-md btn-icon btn-blue flex-none w-auto"
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>
</li>
<% end %>
</ul>
<% else %>
<p>
You do not have any invitations to give away yet. All good
things come in time.
</p>
<div class="text-center">
<p class="my-12 inline-flex align-center items-center">
<%= image_tag("/img/illustrations/undraw_loading_re_5axr.svg", class: 'h-48') %>
</p>
<h3>
No invitations available yet
</h3>
<p class="text-gray-500">
We will notify you, as soon as you can invite others.
</p>
</div>
<% end %>
</section>

View File

@@ -3,7 +3,7 @@
<%= render MainWithSidenavComponent.new(sidenav_partial: 'shared/sidenav_settings') do %>
<section>
<h3>Password</h3>
<p class="mb-12">Use the following button to request an email with a password reset link:</p>
<p class="mb-8">Use the following button to request an email with a password reset link:</p>
<p>
<%= form_with(url: settings_reset_password_path, method: :post) do %>
<%= submit_tag("Send me a password reset link", class: 'btn-md btn-gray w-full sm:w-auto') %>

View File

@@ -1 +0,0 @@
<h2>Settings</h2>

View File

@@ -0,0 +1,34 @@
<%= render HeaderComponent.new(title: "Settings") %>
<%= render MainWithSidenavComponent.new(sidenav_partial: 'shared/sidenav_settings') do %>
<section>
<h3>Profile</h3>
<p class="mb-1">
<%= label :user_address, 'User address', class: 'font-bold' %>
</p>
<p data-controller="clipboard" class="flex gap-1 mb-2 sm:block">
<input type="text" id="user_address" class="flex-1 sm:w-3/5"
value=<%= @user.address %> disabled="disabled"
data-clipboard-target="source" />
<button id="copy-user-address" class="btn-md btn-icon btn-blue flex-none w-auto"
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>
<p class="text-sm text-gray-500">
Your user address for Chat and Lightning Network.
</p>
<%# <%= form_for(@user, as: "profile", url: settings_profile_path) do |f| %>
<%# <p class="mt-8">
<%# <%= f.submit "Save changes", class: 'btn-md btn-blue w-full sm:w-auto' %>
<%# </p>
<%# <% end %>
</section>
<% end %>

View File

@@ -1,10 +1,10 @@
<%= link_to "Services", root_path,
class: main_nav_class(@current_section, :dashboard) %>
<%= link_to "Contributions", contributions_donations_path,
class: main_nav_class(@current_section, :contributions) %>
<%= link_to "Invitations", invitations_path,
class: main_nav_class(@current_section, :invitations) %>
<%= link_to "Donations", donations_path,
class: main_nav_class(@current_section, :contributions) %>
<%= link_to "Wallet", wallet_path,
class: main_nav_class(@current_section, :wallet) %>
<%= link_to "Settings", security_path,
class: main_nav_class(@current_section, :security) %>
<%= link_to "Settings", settings_profile_path,
class: main_nav_class(@current_section, :settings) %>

View File

@@ -1,9 +1,10 @@
<%= render SidenavLinkComponent.new(
name: "Account", path: "#", icon: "settings", disabled: true
name: "Profile", path: settings_profile_path, icon: "user",
active: current_page?(settings_profile_path)
) %>
<%= render SidenavLinkComponent.new(
name: "Password", path: security_path, icon: "key",
active: current_page?(security_path)
name: "Account", path: settings_account_path, icon: "key",
active: current_page?(settings_account_path)
) %>
<%= render SidenavLinkComponent.new(
name: "Security", path: "#", icon: "shield", disabled: true

View File

@@ -0,0 +1,12 @@
<div class="border-b border-gray-200">
<nav class="-mb-px flex" aria-label="Tabs">
<%= render TabnavLinkComponent.new(
name: "Donations", path: contributions_donations_path,
active: current_page?(contributions_donations_path)
) %>
<%= render TabnavLinkComponent.new(
name: "Projects", path: contributions_projects_path,
active: current_page?(contributions_projects_path)
) %>
</nav>
</div>

View File

@@ -0,0 +1,14 @@
<section>
<div class="border-b border-gray-200">
<nav class="-mb-px flex" aria-label="Tabs">
<%= render TabnavLinkComponent.new(
name: "Info", path: wallet_path,
active: current_page?(wallet_path)
) %>
<%= render TabnavLinkComponent.new(
name: "Transactions", path: wallet_transactions_path,
active: current_page?(wallet_transactions_path)
) %>
</nav>
</div>
</section>

View File

@@ -3,14 +3,7 @@
<%= 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-indigo-500 text-indigo-600 w-1/2 py-4 px-1 text-center border-b-2", "aria-current": "page" %>
<%= 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" %>
</nav>
</div>
</section>
<%= render partial: "shared/tabnav_wallet" %>
<section>
<h3>Lightning Address</h3>
@@ -33,8 +26,14 @@
accounts should be able to add/import your account using our setup
code/URL:
</p>
<p class="my-6 text-center md:text-left">
<button id="copy-setup-code" class="btn-md btn-blue w-full sm:w-auto">Copy setup code/URL</button>
<p data-controller="clipboard" class="my-6 text-center md:text-left">
<input type="text" disabled class="hidden" aria-hidden=true
value="<%= @wallet_url%>" data-clipboard-target="source" />
<button id="copy-setup-code" class="btn-md btn-blue w-full sm:w-auto"
data-action="clipboard#copy" data-clipboard-target="trigger">
<span class="content-initial">Copy setup code/URL</span>
<span class="content-active hidden">Copied ✔</span>
</button>
<span class="mx-2 my-2 md:my-0 block md:inline">or</span>
<button id="show-setup-code" class="btn-md btn-blue w-full sm:w-auto">Show setup QR code</button>
<button id="hide-setup-code" class="hidden btn-md btn-blue w-full sm:w-auto">Hide setup QR code</button>
@@ -48,19 +47,6 @@
<h3>Recommended Apps</h3>
<div class="w-full grid grid-cols-1 gap-y-4 md:grid-cols-12
md:gap-y-6 md:gap-x-4 md:items-center">
<h4 class="md:col-span-3">
<a href="https://bluewallet.io" class="ks-text-link text-xl"
title="Blue Wallet" target="_blank">
<%= image_tag("/img/logos/bluewallet.svg", class: 'h-16') %>
</a>
</h4>
<p class="md:col-span-4 mb-0 text-gray-500">
Android / iOS / macOS
</p>
<p class="md:col-span-5 mb-0">
When adding a wallet, choose "Import wallet" on the bottom of the screen,
then scan the setup QR code.
</p>
<h4 class="md:col-span-3">
<a href="https://getalby.com/" class="ks-text-link text-xl"
title="Alby" target="_blank">
@@ -74,6 +60,32 @@
Choose "LNDHub (Bluewallet)" in the connect dialog and paste the setup
URL in the "LNDHub Export URI" field.
</p>
<h4 class="md:col-span-3 mt-4 mb:mt-0">
<a href="https://bluewallet.io" class="ks-text-link text-xl"
title="Blue Wallet" target="_blank">
<%= image_tag("/img/logos/bluewallet.svg", class: 'h-16') %>
</a>
</h4>
<p class="md:col-span-4 mb-0 text-gray-500">
Android / iOS / macOS
</p>
<p class="md:col-span-5 mb-0">
When adding a wallet, choose "Import wallet" on the bottom of the screen,
then scan the setup QR code.
</p>
<h4 class="md:col-span-3 mt-4 mb:mt-0">
<a href="https://zeusln.app" class="ks-text-link text-xl"
title="Zeus" target="_blank">
<%= image_tag("/img/logos/zeus.svg", class: 'h-16') %>
</a>
</h4>
<p class="md:col-span-4 mb-0 text-gray-500">
Android / iOS
</p>
<p class="md:col-span-5 mb-0">
On first launch, tap "Scan node config" and scan the setup QR code.
Add your Lightning address as a nickname, then "Save node config".
</p>
</div>
</section>
<% end %>
@@ -82,7 +94,6 @@
(function () {
const buttonShow = document.querySelector('#show-setup-code');
const buttonHide = document.querySelector('#hide-setup-code');
const buttonCopy = document.querySelector('#copy-setup-code');
const setupCode = document.querySelector('#setup-code');
buttonShow.addEventListener('click', function(ev) {
@@ -100,16 +111,5 @@
buttonHide.classList.add('hidden');
buttonShow.classList.remove('hidden');
});
buttonCopy.addEventListener('click', function(ev) {
ev.preventDefault();
navigator.clipboard.writeText('<%= @wallet_url %>').then(() => {
const buttonText = buttonCopy.innerText;
buttonCopy.innerText = 'Copied ✔';
setTimeout(() => {
buttonCopy.innerText = buttonText;
}, 2000);
});
});
})();
</script>

View File

@@ -3,14 +3,7 @@
<%= 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>
<%= render partial: "shared/tabnav_wallet" %>
<section>
<h3 class="hidden">Transactions</h3>
@@ -49,9 +42,17 @@
<% 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>
<div class="text-center">
<p class="mt-4 mb-12 inline-flex align-center items-center">
<%= image_tag("/img/illustrations/undraw_digital_currency_qpak.svg", class: 'h-48') %>
</p>
<h3>
No transactions yet
</h3>
<p class="text-gray-500">
As soon as you start receiving sats, you will find some entries here.
</p>
</div>
<% 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
# Base URL to be used by email template link helpers
config.action_mailer.default_url_options = { host: "localhost:3000", protocol: "http" }
# Allow requests from any IP
config.web_console.whiny_requests = false
end

View File

@@ -3,6 +3,21 @@ require 'digest'
require 'securerandom'
# frozen_string_literal: true
# Create custom failure for turbo
class TurboFailureApp < Devise::FailureApp
def respond
if request_format == :turbo_stream
redirect
else
super
end
end
def skip_format?
%w(html turbo_stream */*).include? request_format.to_s
end
end
# Assuming you have not yet modified this file, each configuration option below
# is set to its default value. Note that some are commented out while others
# are not: uncommented lines are intended to protect your configuration from
@@ -44,6 +59,7 @@ Devise.setup do |config|
# ==> Controller configuration
# Configure the parent class to the devise controllers.
# config.parent_controller = 'DeviseController'
config.parent_controller = 'TurboController'
# ==> Mailer Configuration
# Configure the e-mail address which will be shown in Devise::Mailer,
@@ -289,6 +305,7 @@ Devise.setup do |config|
#
# The "*/*" below is required to match Internet Explorer requests.
# config.navigational_formats = ['*/*', :html]
config.navigational_formats = ['*/*', :html, :turbo_stream]
# The default HTTP method used to sign out a resource. Default is :delete.
config.sign_out_via = :get
@@ -302,10 +319,11 @@ Devise.setup do |config|
# If you want to use other strategies, that are not supported by Devise, or
# change the failure app, you can configure them inside the config.warden block.
#
# config.warden do |manager|
# manager.intercept_401 = false
# manager.default_strategies(scope: :user).unshift :some_external_strategy
# end
config.warden do |manager|
manager.failure_app = TurboFailureApp
# manager.intercept_401 = false
# manager.default_strategies(scope: :user).unshift :some_external_strategy
end
# ==> Mountable engine configurations
# When using Devise inside an engine, let's call it `MyEngine`, and this engine

View File

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

View File

@@ -10,21 +10,33 @@ Rails.application.routes.draw do
match 'signup/:step', to: 'signup#steps', as: :signup_steps, via: [:get, :post]
post 'signup_validate', to: 'signup#validate'
get 'settings', to: 'settings#index'
post 'settings_reset_password', to: 'settings#reset_password'
namespace :settings do
get 'profile', to: 'profile#index'
post 'profile', to: 'profile#update'
get 'account', to: 'account#index'
post 'reset_password', to: 'account#reset_password'
end
get 'security', to: 'security#index'
namespace :contributions do
root to: 'donations#index'
get 'projects', to: 'projects#index'
resources :donations, only: ['index']
end
resources :invitations, only: ['index', 'show', 'create', 'destroy']
resources :donations
get 'wallet', to: 'wallet#index'
get 'wallet/transactions', to: 'wallet#transactions'
get 'lnurlpay/:address', to: 'lnurlpay#index', 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
root to: 'dashboard#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
# incrementally modify your database, and then regenerate this schema definition.
#
# This file is the source Rails uses to define your schema when running `rails
# db:schema:load`. When creating a new database, `rails db:schema:load` tends to
# This file is the source Rails uses to define your schema when running `bin/rails
# 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
# migrations from scratch. Old migrations may fail to apply correctly if those
# migrations use external dependencies or application code.
#
# 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|
t.integer "user_id"
t.integer "amount_sats"
t.integer "amount_eur"
t.integer "amount_usd"
t.string "public_name"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.datetime "paid_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "paid_at", precision: nil
t.index ["user_id"], name: "index_donations_on_user_id"
end
@@ -28,25 +27,28 @@ ActiveRecord::Schema.define(version: 2021_11_20_010540) do
t.string "token"
t.integer "user_id"
t.integer "invited_user_id"
t.datetime "used_at"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.datetime "used_at", precision: nil
t.datetime "created_at", 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
create_table "users", force: :cascade do |t|
t.string "cn"
t.string "ou"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "email", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "reset_password_sent_at", precision: nil
t.string "confirmation_token"
t.datetime "confirmed_at"
t.datetime "confirmation_sent_at"
t.datetime "confirmed_at", precision: nil
t.datetime "confirmation_sent_at", precision: nil
t.string "unconfirmed_email"
t.text "ln_login_ciphertext"
t.text "ln_password_ciphertext"
t.string "ln_account"
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end

View File

@@ -1,7 +1,22 @@
# This file should contain all the record creation needed to seed the database with its default values.
# The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup).
#
# Examples:
#
# movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }])
# Character.create(name: 'Luke', movie: movies.first)
require 'sidekiq/testing'
ldap = LdapService.new
Sidekiq::Testing.inline! do
CreateAccount.call(
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)
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

View File

@@ -2,14 +2,14 @@
"name": "akkounts",
"private": true,
"dependencies": {
"@tailwindcss/forms": "^0.4.0",
"autoprefixer": "^10.4.2",
"postcss": "^8.4.6",
"@tailwindcss/forms": "^0.5.3",
"autoprefixer": "^10.4.13",
"postcss": "^8.4.19",
"postcss-flexbugs-fixes": "^5.0.2",
"postcss-import": "^14.0.2",
"postcss-nested": "^5.0.6",
"postcss-preset-env": "^7.3.1",
"tailwindcss": "^3.0.22"
"postcss-import": "^15.0.1",
"postcss-nested": "^6.0.0",
"postcss-preset-env": "^7.8.3",
"tailwindcss": "^3.2.4"
},
"version": "0.4.0",
"scripts": {

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -0,0 +1 @@
<svg data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="430.91406" height="559.70956" viewBox="0 0 430.91406 559.70956" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M689.29823,324.68445q0,4.785-.31006,9.49a143.75442,143.75442,0,0,1-13.46973,52.19c-.06006.14-.13037.27-.18994.4-.36035.76-.73047,1.52-1.11035,2.27a142.03868,142.03868,0,0,1-7.6499,13.5,144.462,144.462,0,0,1-118.56006,66.72l1.43018,82.24,18.6499-9.82,3.33008,6.33-21.83985,11.5,2.66992,152.74.02979,2.04-14.41992,1.21.02978-.05,4.54-246.18a144.17482,144.17482,0,0,1-102-44.38c-.90967-.94-1.81006-1.91-2.68994-2.87-.04-.04-.06982-.08-.1001-.11a144.76758,144.76758,0,0,1-26.33984-40.76c.14014.16.29.31.43017.47a144.642,144.642,0,0,1,68.57959-186.38c.5-.25,1.01026-.49,1.51026-.74a144.75207,144.75207,0,0,1,187.52978,56.93c.88037,1.48005,1.73047,2.99006,2.5503,4.51A143.85218,143.85218,0,0,1,689.29823,324.68445Z" transform="translate(-384.54297 -170.14522)" fill="#e5e5e5"/><circle cx="198.2848" cy="502.61836" r="43.06733" fill="#2f2e41"/><rect x="210.6027" y="532.22265" width="38.58356" height="13.08374" fill="#2f2e41"/><ellipse cx="249.45884" cy="534.4033" rx="4.08868" ry="10.90314" fill="#2f2e41"/><rect x="201.6027" y="531.22265" width="38.58356" height="13.08374" fill="#2f2e41"/><ellipse cx="240.45884" cy="533.4033" rx="4.08868" ry="10.90314" fill="#2f2e41"/><path d="M541.051,632.71229c-3.47748-15.5738,7.63866-31.31043,24.82866-35.14881s33.94421,5.67511,37.42169,21.2489-7.91492,21.31769-25.10486,25.156S544.5285,648.28608,541.051,632.71229Z" transform="translate(-384.54297 -170.14522)" fill="#14b8a6"/><path d="M599.38041,670.31119a10.75135,10.75135,0,0,1-10.33984-7.12305,1,1,0,0,1,1.896-.63672c1.51416,4.50782,6.69825,6.86524,11.55457,5.25342a9.60826,9.60826,0,0,0,5.57251-4.74756,8.23152,8.23152,0,0,0,.48547-6.33789,1,1,0,0,1,1.896-.63672,10.217,10.217,0,0,1-.59229,7.86817,11.62362,11.62362,0,0,1-6.73218,5.75244A11.87976,11.87976,0,0,1,599.38041,670.31119Z" transform="translate(-384.54297 -170.14522)" fill="#fff"/><path d="M618.56452,676.16463a9.57244,9.57244,0,1,1-17.04506,8.71737h0l-.00855-.01674c-2.40264-4.70921.91734-7.63227,5.62657-10.03485S616.162,671.45547,618.56452,676.16463Z" transform="translate(-384.54297 -170.14522)" fill="#fff"/><path d="M772.27559,716.2189h-381a1,1,0,0,1,0-2h381a1,1,0,0,1,0,2Z" transform="translate(-384.54297 -170.14522)" fill="#3f3d56"/><ellipse cx="567.22606" cy="706.64241" rx="7.50055" ry="23.89244" transform="translate(-543.03826 -6.10526) rotate(-14.4613)" fill="#2f2e41"/><path d="M645.50888,621.42349H629.12323a.77274.77274,0,0,1-.51881-1.3455l14.90017-13.49467h-13.7669a.77274.77274,0,0,1,0-1.54548h15.77119a.77275.77275,0,0,1,.51881,1.34551L631.12753,619.878h14.38135a.77274.77274,0,1,1,0,1.54548Z" transform="translate(-384.54297 -170.14522)" fill="#cbcbcb"/><path d="M666.37288,597.46853H649.98723a.77275.77275,0,0,1-.51881-1.34551l14.90017-13.49466h-13.7669a.77274.77274,0,0,1,0-1.54548h15.77119a.77274.77274,0,0,1,.51881,1.3455l-14.90016,13.49467h14.38135a.77274.77274,0,1,1,0,1.54548Z" transform="translate(-384.54297 -170.14522)" fill="#cbcbcb"/><path d="M657.1,571.19534H640.71434a.77274.77274,0,0,1-.51881-1.3455l14.90017-13.49467H641.3288a.77274.77274,0,0,1,0-1.54548H657.1a.77275.77275,0,0,1,.51881,1.34551l-14.90016,13.49466H657.1a.77274.77274,0,0,1,0,1.54548Z" transform="translate(-384.54297 -170.14522)" fill="#cbcbcb"/><path d="M770.66217,347.522,783.457,337.28854c-9.93976-1.09662-14.0238,4.32429-15.69525,8.615-7.76532-3.22446-16.21881,1.00136-16.21881,1.00136l25.6001,9.29375A19.37209,19.37209,0,0,0,770.66217,347.522Z" transform="translate(-384.54297 -170.14522)" fill="#3f3d56"/><path d="M403.66217,180.522,416.457,170.28854c-9.93976-1.09662-14.0238,4.32429-15.69525,8.615-7.76532-3.22446-16.21881,1.00136-16.21881,1.00136l25.6001,9.29375A19.37209,19.37209,0,0,0,403.66217,180.522Z" transform="translate(-384.54297 -170.14522)" fill="#3f3d56"/><path d="M802.66217,215.522,815.457,205.28854c-9.93976-1.09662-14.0238,4.32429-15.69525,8.615-7.76532-3.22446-16.21881,1.00136-16.21881,1.00136l25.6001,9.29375A19.37209,19.37209,0,0,0,802.66217,215.522Z" transform="translate(-384.54297 -170.14522)" fill="#3f3d56"/></svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 14 KiB

64
public/img/logos/zeus.svg Normal file
View File

@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="330"
height="160"
viewBox="0 0 330 160"
fill="none"
version="1.1"
id="svg12"
sodipodi:docname="zeus.svg"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview9"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="2.3383653"
inkscape:cx="179.61266"
inkscape:cy="89.164854"
inkscape:window-width="1920"
inkscape:window-height="1011"
inkscape:window-x="0"
inkscape:window-y="32"
inkscape:window-maximized="1"
inkscape:current-layer="svg12" />
<defs
id="defs16" />
<g
id="g120"
transform="matrix(0.8832226,0,0,0.89599484,21.917939,12.35239)">
<path
d="m 215.456,79.5133 c 0,0 10.514,-46.5605 32.708,-76.34661 L 324,0 c 0,0 -36.958,34.6773 -47.661,53.4896 L 320.6,50.1974 c 0,0 -69.855,49.9466 -93.339,100.8026 0,0 -5.1,-41.857 18.857,-74.6534 z"
fill="#ffd93f"
id="path2" />
<path
d="m 121.235,19.9094 c 0,0 -13.033,5.6124 -27.9858,17.0879 -14.9532,11.4755 -23.2325,24.4873 -23.2325,24.4873 0,0 4.5332,0.3762 3.0851,2.6023 -1.4481,2.2262 -5.572,6.1767 -5.572,6.1767 0,0 7.5868,4.954 30.0007,14.705 22.4145,9.7511 25.6565,11.4442 25.9085,10.5662 0.283,-0.8779 10.577,1.7559 10.577,1.7559 0,0 -26.821,-15.2694 -40.2946,-27.3092 0,0 3.2425,-2.8219 11.3956,-5.6437 8.154,-2.8219 12.466,-3.6057 12.624,-4.609 0.157,-1.0033 -2.487,-1.4423 -2.487,-1.4423 l 9.444,-2.9159 c 0,0 -18.07,-0.6898 -37.2727,3.637 0,0 9.0664,-14.3913 18.6047,-21.6027 9.539,-7.2114 13.631,-9.124 13.789,-10.5036 0.157,-1.3795 -2.078,-2.2261 -2.078,-2.2261 0,0 0.535,-2.6964 3.494,-4.7658 z"
fill="white"
id="path4"
style="fill:#333333" />
<path
d="m 57.9283,43.0484 c 0,0 -11.2699,12.1653 -22.7602,29.9429 C 23.6777,90.769 8.69313,113.5 12.2819,115.789 c 3.5887,2.289 9.2552,1.443 9.2552,1.443 0,0 -0.7555,2.539 0.9129,2.759 1.6685,0.219 13.6939,-9.908 27.986,-15.364 14.2921,-5.4866 25.688,-8.5593 26.0028,-10.0329 0.3148,-1.505 -4.3758,-2.3829 -4.0925,-2.3829 0.2519,0 5.3517,-1.2855 5.7294,-2.9472 0.4093,-1.6618 -3.3054,-2.3829 -8.9089,-1.7245 -5.6035,0.6584 -30.7248,8.152 -30.7248,8.152 0,0 18.416,-33.047 28.6471,-46.5605 10.2311,-13.5135 13.568,-16.1472 13.5365,-18.1852 -0.0314,-2.038 -8.4367,-1.7871 -8.4367,-1.7871 0,0 -1.1018,-2.3202 -2.3295,-2.3516 -1.2277,-0.0313 -32.4247,1.411 -54.1147,5.6437 C -5.94522,36.6836 0.319361,36.9344 2.68038,38.9097 5.00993,40.9163 1.3897,44.961 1.3897,44.961 c 0,0 10.6403,-0.4389 10.4829,1.7558 -0.1574,2.1948 -3.46281,4.6717 -3.46281,4.6717 0,0 25.24721,-6.8978 49.51851,-8.3401 z"
fill="white"
id="path6"
style="fill:#333333" />
<path
d="m 130.585,21.8848 c 0,0 3.589,19.0004 4.753,35.5866 1.165,16.5861 0.063,31.3537 0.913,31.8554 0.819,0.5017 8.091,-2.5397 17.566,-1.505 9.476,1.0347 16.496,1.3482 17.283,0.4076 0.787,-0.9406 0.032,-3.1354 0.032,-3.1354 0,0 1.825,0.2822 2.172,-1.0033 0.346,-1.2855 -1.417,-2.665 -1.417,-2.665 0,0 0,-13.0433 -0.755,-28.877 -0.724,-15.8023 -5.604,-29.6606 -5.604,-29.6606 0,0 -3.211,53.2388 -4.124,56.4369 0,0 -8.751,-3.01 -15.488,-2.4143 0,0 -3.809,-21.6028 -6.957,-36.4645 -3.148,-14.8617 -4.03,-17.809 -4.785,-17.6522 -0.661,0.1254 -1.26,2.8845 -1.26,2.8845 z"
fill="white"
id="path8"
style="fill:#333333" />
<path
d="m 243.001,47.9712 c 0,0 -25.058,-14.1406 -41.208,-17.9658 -16.149,-3.8252 -19.203,-2.9786 -17.975,-2.0694 1.228,0.8779 4.187,2.5711 4.187,2.5711 0,0 -10.924,-0.2508 -9.948,1.0974 0.976,1.3482 4.093,11.5695 4.03,22.2925 -0.063,10.723 -0.063,18.0598 0.566,18.342 0.63,0.2822 6.202,-0.1881 6.202,-0.1881 0,0 -1.385,2.1634 -0.063,2.3829 1.322,0.2194 7.713,-1.1288 14.481,-0.6898 6.768,0.4389 9.885,1.2855 9.885,1.2855 0,0 0.661,14.7676 -3.526,25.6785 0,0 -31.795,-12.698 -32.866,-11.3185 -1.07,1.3482 0.819,4.3896 0.819,4.3896 l -12.277,4.9853 c 0,0 16.747,3.2606 30.567,8.7476 13.82,5.487 21.753,10.19 23.044,9.72 1.29,-0.471 3.462,-6.428 6.925,-24.4249 3.463,-17.9971 1.511,-26.8702 0.158,-27.9363 -3.022,-2.3515 -34.251,-1.0033 -34.251,-1.0033 0,0 1.417,-12.7297 0.283,-20.4113 0.032,-0.0314 1.354,-4.7658 50.967,4.515 z"
fill="white"
id="path10"
style="fill:#333333" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

@@ -2,8 +2,10 @@ FactoryBot.define do
factory :user do
id { 1 }
cn { "jimmy" }
ou { "kosmos.org" }
email { "jimmy@example.com" }
password { "dis-muh-password" }
confirmed_at { DateTime.now }
ln_account { "123456" }
end
end

View File

@@ -0,0 +1,54 @@
require 'rails_helper'
RSpec.describe 'Password reset', type: :feature do
let(:user) { create :user }
before do
login_as user, :scope => :user
end
scenario 'Send password reset email' do
expect(user.reset_password_token).to be_nil
visit settings_account_path
click_button "Send me a password reset link"
expect(page).to have_content 'Please check your inbox'
expect(user.reload.reset_password_token).to be_a(String)
end
describe "Password reset form" do
# Generate a raw reset token, since the stored one is only a digest
let(:token) { user.send(:set_reset_password_token) }
before do
logout
end
scenario "Submit with invalid passwords" do
expect(Devise::LDAP::Adapter).not_to receive(:update_password)
visit edit_user_password_path(reset_password_token: token)
fill_in :user_password, with: 'nice try'
fill_in :user_password_confirmation, with: 'nice try o'
click_button 'Change my password'
expect(page).to have_content 'Password is too short'
fill_in :user_password, with: 'a new password'
fill_in :user_password_confirmation, with: 'a new password with a typo'
click_button 'Change my password'
expect(page).to have_content 'Password confirmation doesn\'t match'
end
scenario "Submit with valid passwords" do
expect(Devise::LDAP::Adapter).to receive(:update_password)
.with(user.cn, 'catch me if you can').and_return(true)
visit edit_user_password_path(reset_password_token: token)
fill_in :user_password, with: 'catch me if you can'
fill_in :user_password_confirmation, with: 'catch me if you can'
click_button 'Change my password'
expect(page).to have_content 'Your password has been changed successfully'
expect(user.reload.reset_password_token).to be_nil
end
end
end

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

@@ -0,0 +1,19 @@
{
"id": 58,
"type": "incoming",
"user_login": "123456abcdef",
"amount": 12300,
"fee": 0,
"memo": "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"
}

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

@@ -0,0 +1,19 @@
{
"id": 59,
"type": "outgoing",
"user_login": "123456abcdef",
"amount": 12400,
"fee": 10,
"memo": "Top up mobile phone",
"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 'webmock/rspec'
RSpec.describe CreateLndhubWalletJob, type: :job do
RSpec.describe CreateLndhubAccountJob, type: :job do
let(:user) { create :user, cn: "willherschel", ou: "kosmos.org" }
subject(:job) { described_class.perform_later(user) }
before do
stub_request(:post, "http://localhost:3023/create")
stub_request(:post, "http://localhost:3026/v2/users")
.to_return(status: 200, headers: {},
body: { login: "abc123", password: "def456" }.to_json)
end
@@ -15,8 +15,8 @@ RSpec.describe CreateLndhubWalletJob, type: :job do
it "creates a new LndHub account" do
perform_enqueued_jobs { job }
expect(WebMock).to have_requested(:post, "http://localhost:3023/create")
.with { |req| req.body == '{"partnerid":"kosmos.org","accounttype":"user"}' }
expect(WebMock).to have_requested(:post, "http://localhost:3026/v2/users")
.with { |req| req.body == '{}' }
user.reload
expect(user.ln_login).to eq("abc123")

View File

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

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,77 @@
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
before do
ENV['WEBHOOKS_ALLOWED_IPS'] = '127.0.0.1'
end
it "does not return a 403 status" do
post "/webhooks/lndhub"
expect(response).not_to have_http_status(:forbidden)
end
end
end
# Webhooks from lndhub.go
describe "/webhooks/lndhub" do
before do
ENV['WEBHOOKS_ALLOWED_IPS'] = '127.0.0.1'
end
describe "Payload cannot be processed as JSON" do
before do
post "/webhooks/lndhub", params: "Foo"
end
it "returns a 422 status" do
expect(response).to have_http_status(:unprocessable_entity)
end
end
describe "Valid payload for outgoing payment" do
let(:payload) { JSON.parse(File.read(File.expand_path("../fixtures/lndhub/outgoing.json", File.dirname(__FILE__)))) }
before do
post "/webhooks/lndhub", params: payload.to_json
end
it "returns a 204 status" do
expect(response).to have_http_status(:no_content)
end
end
describe "Valid payload for incoming payment" do
let(:user) { create :user, ln_account: "123456abcdef" }
let(:payload) { JSON.parse(File.read(File.expand_path("../fixtures/lndhub/incoming.json", File.dirname(__FILE__)))) }
before do
user.save! #FIXME this should not be necessary
post "/webhooks/lndhub", params: payload.to_json
end
it "returns a 200 status" do
expect(response).to have_http_status(:ok)
end
it "sends an XMPP message to the account owner's JID" do
expect(enqueued_jobs.size).to eq(1)
msg = enqueued_jobs.first['arguments'].first
expect(msg["type"]).to eq('normal')
expect(msg["from"]).to eq('kosmos.org')
expect(msg["to"]).to eq(user.address)
expect(msg["subject"]).to eq('Sats received!')
expect(msg["body"]).to match(/^12300 sats received/)
end
end
end
end

View File

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

View File

837
yarn.lock

File diff suppressed because it is too large Load Diff