Compare commits
69 Commits
v0.8.1
...
aab6793b86
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aab6793b86
|
||
|
|
cfd0935bdc
|
||
|
|
c2dae105ff
|
||
|
|
2a70bf2fb9
|
||
|
|
9a9947f9ad
|
||
|
|
bdf5a18ad4
|
||
|
|
aa399b862a
|
||
|
|
713e91a720
|
||
|
|
8ec2a6d7e4
|
||
|
|
4ecf2c4246
|
||
|
|
4fdf8accd6
|
||
|
|
f451adcb53
|
||
|
|
721dccb499
|
||
|
|
27bb7d1bfe
|
||
|
|
1d44181fb5
|
||
|
|
de67f59d5c
|
||
|
|
1995e6dda2
|
||
|
|
600cfe0f78
|
||
|
|
00049f3743
|
||
|
|
60c0a43f33
|
||
|
|
0c1b1b4afe
|
||
|
|
92310d434a
|
||
|
|
56c127ca0c
|
||
|
|
5075fef616
|
||
|
|
8e090daa9c
|
||
|
|
def87a1621
|
||
|
|
00ec7fa21c
|
||
|
|
2b8bfaaca8
|
||
|
|
3e9a08a266
|
||
|
|
fcea11f0e5
|
||
|
|
261a782963
|
||
|
|
e964e7e52c
|
||
|
|
e508407df4
|
||
|
|
bec827acb1
|
||
|
|
0a69603643
|
||
|
|
d4f71e98ed
|
||
|
|
e56c9bd0d5
|
||
|
|
e1b7e1b2ef
|
||
|
|
1056ffd08e
|
||
| be5fe00f20 | |||
|
|
e9c4929726
|
||
| 14ff0c0e16 | |||
|
|
d939f5d649
|
||
|
|
69fffb29d8
|
||
|
|
91d3b977e9
|
||
| 7a5fd46835 | |||
|
|
9c4c5c2553
|
||
|
|
8f819d12c0
|
||
|
|
b810e27480
|
||
|
|
1949f1876f
|
||
|
|
2ba0116ca6
|
||
|
|
2c2ddabdff
|
||
|
|
dfcdbec0dd
|
||
|
|
3b67a8791c
|
||
|
|
d5ab532947
|
||
|
|
50c63d5c38
|
||
|
|
64d09cfb7f
|
||
|
|
def44618ef
|
||
|
|
9e5aeaf572
|
||
|
|
86f85a90f4
|
||
| d8a35ac3fd | |||
|
|
5a5f62e98a
|
||
|
|
074f9afcbb
|
||
|
|
725fd2e5ea
|
||
|
|
8349ca5e12
|
||
|
|
46d59e3371
|
||
|
|
e8e6ee0bc4
|
||
|
|
a91ee2bd0a
|
||
|
|
fcb6923c92
|
@@ -17,7 +17,7 @@ steps:
|
||||
branch:
|
||||
- master
|
||||
- name: rspec
|
||||
image: guildeducation/rails:2.7.2-14.20.0
|
||||
image: gitea.kosmos.org/kosmos/akkounts-ci:0.1.0
|
||||
environment:
|
||||
RAILS_ENV: test
|
||||
REDIS_URL: redis://redis:6379/0
|
||||
|
||||
10
.env.example
@@ -10,6 +10,14 @@ SMTP_DOMAIN=example.com
|
||||
SMTP_AUTH_METHOD=plain
|
||||
SMTP_ENABLE_STARTTLS=auto
|
||||
|
||||
# S3_ENABLED=true
|
||||
# S3_ENDPOINT=https://s3.kosmos.org
|
||||
# S3_REGION=garage
|
||||
# S3_BUCKET=akkounts-production
|
||||
# S3_ALIAS_HOST=accounts.s3.kosmos.org
|
||||
# S3_ACCESS_KEY=123456abcdefg
|
||||
# S3_SECRET_KEY=123456789123456789123456789
|
||||
|
||||
LDAP_HOST=localhost
|
||||
LDAP_PORT=389
|
||||
LDAP_ADMIN_PASSWORD=passthebutter
|
||||
@@ -34,6 +42,8 @@ EJABBERD_ADMIN_URL='https://xmpp.kosmos.org/admin'
|
||||
EJABBERD_API_URL='https://xmpp.kosmos.org/api'
|
||||
|
||||
BTCPAY_API_URL='http://localhost:23001/api/v1'
|
||||
BTCPAY_STORE_ID=''
|
||||
BTCPAY_AUTH_TOKEN=''
|
||||
|
||||
LNDHUB_API_URL='http://localhost:3023'
|
||||
LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org'
|
||||
|
||||
@@ -2,13 +2,14 @@ PRIMARY_DOMAIN=kosmos.org
|
||||
|
||||
REDIS_URL='redis://localhost:6379/0'
|
||||
|
||||
BTCPAY_API_URL='http://btcpay.example.com/api/v1'
|
||||
BTCPAY_STORE_ID='123456'
|
||||
|
||||
DISCOURSE_PUBLIC_URL='http://discourse.example.com'
|
||||
DISCOURSE_CONNECT_SECRET='discourse_connect_ftw'
|
||||
|
||||
EJABBERD_API_URL='http://xmpp.example.com/api'
|
||||
|
||||
BTCPAY_API_URL='http://btcpay.example.com/api/v1'
|
||||
|
||||
LNDHUB_API_URL='http://localhost:3026'
|
||||
LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org'
|
||||
LNDHUB_PUBLIC_KEY='024cd3be18617f39cf645851e3ba63f51fc13f0bb09e3bb25e6fd4de556486d946'
|
||||
|
||||
1
.gitignore
vendored
@@ -23,6 +23,7 @@
|
||||
!/tmp/pids/
|
||||
!/tmp/pids/.keep
|
||||
|
||||
/storage
|
||||
|
||||
/public/assets
|
||||
.byebug_history
|
||||
|
||||
@@ -4,7 +4,7 @@ FROM ruby:2.7.6
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
|
||||
RUN apt-get update -qq && apt-get install -y --no-install-recommends curl \
|
||||
ldap-utils tini
|
||||
ldap-utils tini libvips
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -
|
||||
RUN apt-get update && apt-get install -y nodejs
|
||||
|
||||
|
||||
4
Gemfile
@@ -37,6 +37,7 @@ gem 'devise_ldap_authenticatable'
|
||||
gem 'net-ldap'
|
||||
|
||||
# Utilities
|
||||
gem "image_processing", "~> 1.12.2"
|
||||
gem "rqrcode", "~> 2.0"
|
||||
gem 'rails-settings-cached', '~> 2.8.3'
|
||||
gem 'pagy', '~> 6.0', '>= 6.0.2'
|
||||
@@ -46,6 +47,8 @@ gem 'flipper-ui'
|
||||
|
||||
# HTTP requests
|
||||
gem 'faraday'
|
||||
gem 'down'
|
||||
gem 'aws-sdk-s3', require: false
|
||||
|
||||
# Background/scheduled jobs
|
||||
gem 'sidekiq', '< 7'
|
||||
@@ -58,6 +61,7 @@ gem "sentry-rails"
|
||||
# Services
|
||||
gem 'discourse_api'
|
||||
gem "lnurl"
|
||||
gem 'manifique', git: 'https://gitea.kosmos.org/5apps/manifique.git', branch: 'master'
|
||||
gem 'nostr', git: 'https://gitea.kosmos.org/kosmos/nostr-gem.git', branch: 'feature/ruby_2.7_compat'
|
||||
|
||||
group :development, :test do
|
||||
|
||||
278
Gemfile.lock
@@ -1,3 +1,13 @@
|
||||
GIT
|
||||
remote: https://gitea.kosmos.org/5apps/manifique.git
|
||||
revision: 8d79113438ee7c3e4288f840a135622519cffd5c
|
||||
branch: master
|
||||
specs:
|
||||
manifique (0.1.0)
|
||||
faraday (~> 2.7.11)
|
||||
faraday-follow_redirects (= 0.3.0)
|
||||
nokogiri (~> 1.15.4)
|
||||
|
||||
GIT
|
||||
remote: https://gitea.kosmos.org/kosmos/nostr-gem.git
|
||||
revision: 596529d9eb50d13b3f385245636698fccf37b442
|
||||
@@ -14,82 +24,100 @@ GIT
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actioncable (7.0.5)
|
||||
actionpack (= 7.0.5)
|
||||
activesupport (= 7.0.5)
|
||||
actioncable (7.0.8)
|
||||
actionpack (= 7.0.8)
|
||||
activesupport (= 7.0.8)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
actionmailbox (7.0.5)
|
||||
actionpack (= 7.0.5)
|
||||
activejob (= 7.0.5)
|
||||
activerecord (= 7.0.5)
|
||||
activestorage (= 7.0.5)
|
||||
activesupport (= 7.0.5)
|
||||
actionmailbox (7.0.8)
|
||||
actionpack (= 7.0.8)
|
||||
activejob (= 7.0.8)
|
||||
activerecord (= 7.0.8)
|
||||
activestorage (= 7.0.8)
|
||||
activesupport (= 7.0.8)
|
||||
mail (>= 2.7.1)
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
actionmailer (7.0.5)
|
||||
actionpack (= 7.0.5)
|
||||
actionview (= 7.0.5)
|
||||
activejob (= 7.0.5)
|
||||
activesupport (= 7.0.5)
|
||||
actionmailer (7.0.8)
|
||||
actionpack (= 7.0.8)
|
||||
actionview (= 7.0.8)
|
||||
activejob (= 7.0.8)
|
||||
activesupport (= 7.0.8)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (7.0.5)
|
||||
actionview (= 7.0.5)
|
||||
activesupport (= 7.0.5)
|
||||
actionpack (7.0.8)
|
||||
actionview (= 7.0.8)
|
||||
activesupport (= 7.0.8)
|
||||
rack (~> 2.0, >= 2.2.4)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
||||
actiontext (7.0.5)
|
||||
actionpack (= 7.0.5)
|
||||
activerecord (= 7.0.5)
|
||||
activestorage (= 7.0.5)
|
||||
activesupport (= 7.0.5)
|
||||
actiontext (7.0.8)
|
||||
actionpack (= 7.0.8)
|
||||
activerecord (= 7.0.8)
|
||||
activestorage (= 7.0.8)
|
||||
activesupport (= 7.0.8)
|
||||
globalid (>= 0.6.0)
|
||||
nokogiri (>= 1.8.5)
|
||||
actionview (7.0.5)
|
||||
activesupport (= 7.0.5)
|
||||
actionview (7.0.8)
|
||||
activesupport (= 7.0.8)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
||||
activejob (7.0.5)
|
||||
activesupport (= 7.0.5)
|
||||
activejob (7.0.8)
|
||||
activesupport (= 7.0.8)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (7.0.5)
|
||||
activesupport (= 7.0.5)
|
||||
activerecord (7.0.5)
|
||||
activemodel (= 7.0.5)
|
||||
activesupport (= 7.0.5)
|
||||
activestorage (7.0.5)
|
||||
actionpack (= 7.0.5)
|
||||
activejob (= 7.0.5)
|
||||
activerecord (= 7.0.5)
|
||||
activesupport (= 7.0.5)
|
||||
activemodel (7.0.8)
|
||||
activesupport (= 7.0.8)
|
||||
activerecord (7.0.8)
|
||||
activemodel (= 7.0.8)
|
||||
activesupport (= 7.0.8)
|
||||
activestorage (7.0.8)
|
||||
actionpack (= 7.0.8)
|
||||
activejob (= 7.0.8)
|
||||
activerecord (= 7.0.8)
|
||||
activesupport (= 7.0.8)
|
||||
marcel (~> 1.0)
|
||||
mini_mime (>= 1.1.0)
|
||||
activesupport (7.0.5)
|
||||
activesupport (7.0.8)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 1.6, < 2)
|
||||
minitest (>= 5.1)
|
||||
tzinfo (~> 2.0)
|
||||
addressable (2.8.4)
|
||||
addressable (2.8.5)
|
||||
public_suffix (>= 2.0.2, < 6.0)
|
||||
ast (2.4.2)
|
||||
aws-eventstream (1.2.0)
|
||||
aws-partitions (1.839.0)
|
||||
aws-sdk-core (3.185.1)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
aws-partitions (~> 1, >= 1.651.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
aws-sdk-kms (1.72.0)
|
||||
aws-sdk-core (~> 3, >= 3.184.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.136.0)
|
||||
aws-sdk-core (~> 3, >= 3.181.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.6)
|
||||
aws-sigv4 (1.6.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
backport (1.2.0)
|
||||
bcrypt (3.1.18)
|
||||
bech32 (1.3.0)
|
||||
base64 (0.1.1)
|
||||
bcrypt (3.1.19)
|
||||
bech32 (1.4.2)
|
||||
thor (>= 1.1.0)
|
||||
benchmark (0.2.1)
|
||||
bindex (0.8.1)
|
||||
bip-schnorr (0.6.0)
|
||||
ecdsa_ext (~> 0.5.0)
|
||||
brow (0.4.1)
|
||||
builder (3.2.4)
|
||||
byebug (11.1.3)
|
||||
capybara (3.39.2)
|
||||
@@ -107,7 +135,7 @@ GEM
|
||||
crack (0.4.5)
|
||||
rexml
|
||||
crass (1.0.6)
|
||||
cssbundling-rails (1.1.2)
|
||||
cssbundling-rails (1.3.3)
|
||||
railties (>= 6.0.0)
|
||||
database_cleaner (2.0.2)
|
||||
database_cleaner-active_record (>= 2, < 3)
|
||||
@@ -116,7 +144,7 @@ GEM
|
||||
database_cleaner-core (~> 2.0.0)
|
||||
database_cleaner-core (2.0.1)
|
||||
date (3.3.3)
|
||||
devise (4.9.2)
|
||||
devise (4.9.3)
|
||||
bcrypt (~> 3.0)
|
||||
orm_adapter (~> 0.1)
|
||||
railties (>= 4.1.0)
|
||||
@@ -135,6 +163,8 @@ GEM
|
||||
dotenv-rails (2.8.1)
|
||||
dotenv (= 2.8.1)
|
||||
railties (>= 3.2)
|
||||
down (5.4.1)
|
||||
addressable (~> 2.8)
|
||||
e2mmap (0.1.0)
|
||||
ecdsa (1.2.0)
|
||||
ecdsa_ext (0.5.0)
|
||||
@@ -149,9 +179,10 @@ GEM
|
||||
factory_bot_rails (6.2.0)
|
||||
factory_bot (~> 6.2.0)
|
||||
railties (>= 5.0.0)
|
||||
faker (3.2.0)
|
||||
faker (3.2.1)
|
||||
i18n (>= 1.8.11, < 2)
|
||||
faraday (2.7.6)
|
||||
faraday (2.7.11)
|
||||
base64
|
||||
faraday-net_http (>= 2.0, < 3.1)
|
||||
ruby2_keywords (>= 0.0.4)
|
||||
faraday-follow_redirects (0.3.0)
|
||||
@@ -159,41 +190,47 @@ GEM
|
||||
faraday-multipart (1.0.4)
|
||||
multipart-post (~> 2)
|
||||
faraday-net_http (3.0.2)
|
||||
faye-websocket (0.11.2)
|
||||
faye-websocket (0.11.3)
|
||||
eventmachine (>= 0.12.0)
|
||||
websocket-driver (>= 0.5.1)
|
||||
ffi (1.15.5)
|
||||
flipper (0.28.0)
|
||||
ffi (1.16.3)
|
||||
flipper (1.0.0)
|
||||
brow (~> 0.4.1)
|
||||
concurrent-ruby (< 2)
|
||||
flipper-active_record (0.28.0)
|
||||
flipper-active_record (1.0.0)
|
||||
activerecord (>= 4.2, < 8)
|
||||
flipper (~> 0.28.0)
|
||||
flipper-ui (0.28.0)
|
||||
flipper (~> 1.0.0)
|
||||
flipper-ui (1.0.0)
|
||||
erubi (>= 1.0.0, < 2.0.0)
|
||||
flipper (~> 0.28.0)
|
||||
rack (>= 1.4, < 3)
|
||||
flipper (~> 1.0.0)
|
||||
rack (>= 1.4, < 4)
|
||||
rack-protection (>= 1.5.3, <= 4.0.0)
|
||||
sanitize (< 7)
|
||||
fugit (1.8.1)
|
||||
et-orbi (~> 1, >= 1.2.7)
|
||||
raabro (~> 1.4)
|
||||
globalid (1.1.0)
|
||||
activesupport (>= 5.0)
|
||||
globalid (1.2.1)
|
||||
activesupport (>= 6.1)
|
||||
hashdiff (1.0.1)
|
||||
i18n (1.14.1)
|
||||
concurrent-ruby (~> 1.0)
|
||||
importmap-rails (1.1.6)
|
||||
image_processing (1.12.2)
|
||||
mini_magick (>= 4.9.5, < 5)
|
||||
ruby-vips (>= 2.0.17, < 3)
|
||||
importmap-rails (1.2.1)
|
||||
actionpack (>= 6.0.0)
|
||||
railties (>= 6.0.0)
|
||||
jaro_winkler (1.5.6)
|
||||
jbuilder (2.11.5)
|
||||
actionview (>= 5.0.0)
|
||||
activesupport (>= 5.0.0)
|
||||
jmespath (1.6.2)
|
||||
json (2.6.3)
|
||||
kramdown (2.4.0)
|
||||
rexml
|
||||
kramdown-parser-gfm (1.1.0)
|
||||
kramdown (~> 2.0)
|
||||
language_server-protocol (3.17.0.3)
|
||||
launchy (2.5.2)
|
||||
addressable (~> 2.8)
|
||||
letter_opener (1.8.1)
|
||||
@@ -206,10 +243,10 @@ GEM
|
||||
listen (3.8.0)
|
||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||
rb-inotify (~> 0.9, >= 0.9.10)
|
||||
lnurl (1.0.1)
|
||||
lnurl (1.1.0)
|
||||
bech32 (~> 1.1)
|
||||
lockbox (1.2.0)
|
||||
loofah (2.21.3)
|
||||
lockbox (1.3.0)
|
||||
loofah (2.21.4)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.12.0)
|
||||
mail (2.8.1)
|
||||
@@ -220,10 +257,11 @@ GEM
|
||||
marcel (1.0.2)
|
||||
matrix (0.4.2)
|
||||
method_source (1.0.0)
|
||||
mini_mime (1.1.2)
|
||||
minitest (5.18.0)
|
||||
mini_magick (4.12.0)
|
||||
mini_mime (1.1.5)
|
||||
minitest (5.20.0)
|
||||
multipart-post (2.3.0)
|
||||
net-imap (0.3.6)
|
||||
net-imap (0.3.7)
|
||||
date
|
||||
net-protocol
|
||||
net-ldap (0.18.0)
|
||||
@@ -231,50 +269,51 @@ GEM
|
||||
net-protocol
|
||||
net-protocol (0.2.1)
|
||||
timeout
|
||||
net-smtp (0.3.3)
|
||||
net-smtp (0.4.0)
|
||||
net-protocol
|
||||
nio4r (2.5.9)
|
||||
nokogiri (1.15.2-arm64-darwin)
|
||||
nokogiri (1.15.4-arm64-darwin)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.15.2-x86_64-linux)
|
||||
nokogiri (1.15.4-x86_64-linux)
|
||||
racc (~> 1.4)
|
||||
orm_adapter (0.5.0)
|
||||
pagy (6.0.4)
|
||||
pagy (6.1.0)
|
||||
parallel (1.23.0)
|
||||
parser (3.2.2.3)
|
||||
parser (3.2.2.4)
|
||||
ast (~> 2.4.1)
|
||||
racc
|
||||
pg (1.2.3)
|
||||
public_suffix (5.0.1)
|
||||
public_suffix (5.0.3)
|
||||
puma (4.3.12)
|
||||
nio4r (~> 2.0)
|
||||
raabro (1.4.0)
|
||||
racc (1.7.1)
|
||||
rack (2.2.7)
|
||||
rack-protection (3.0.6)
|
||||
rack
|
||||
rack (2.2.8)
|
||||
rack-protection (3.1.0)
|
||||
rack (~> 2.2, >= 2.2.4)
|
||||
rack-test (2.1.0)
|
||||
rack (>= 1.3)
|
||||
rails (7.0.5)
|
||||
actioncable (= 7.0.5)
|
||||
actionmailbox (= 7.0.5)
|
||||
actionmailer (= 7.0.5)
|
||||
actionpack (= 7.0.5)
|
||||
actiontext (= 7.0.5)
|
||||
actionview (= 7.0.5)
|
||||
activejob (= 7.0.5)
|
||||
activemodel (= 7.0.5)
|
||||
activerecord (= 7.0.5)
|
||||
activestorage (= 7.0.5)
|
||||
activesupport (= 7.0.5)
|
||||
rails (7.0.8)
|
||||
actioncable (= 7.0.8)
|
||||
actionmailbox (= 7.0.8)
|
||||
actionmailer (= 7.0.8)
|
||||
actionpack (= 7.0.8)
|
||||
actiontext (= 7.0.8)
|
||||
actionview (= 7.0.8)
|
||||
activejob (= 7.0.8)
|
||||
activemodel (= 7.0.8)
|
||||
activerecord (= 7.0.8)
|
||||
activestorage (= 7.0.8)
|
||||
activesupport (= 7.0.8)
|
||||
bundler (>= 1.15.0)
|
||||
railties (= 7.0.5)
|
||||
railties (= 7.0.8)
|
||||
rails-controller-testing (1.0.5)
|
||||
actionpack (>= 5.0.1.rc1)
|
||||
actionview (>= 5.0.1.rc1)
|
||||
activesupport (>= 5.0.1.rc1)
|
||||
rails-dom-testing (2.0.3)
|
||||
activesupport (>= 4.2.0)
|
||||
rails-dom-testing (2.2.0)
|
||||
activesupport (>= 5.0.0)
|
||||
minitest
|
||||
nokogiri (>= 1.6)
|
||||
rails-html-sanitizer (1.6.0)
|
||||
loofah (~> 2.21)
|
||||
@@ -282,9 +321,9 @@ GEM
|
||||
rails-settings-cached (2.8.3)
|
||||
activerecord (>= 5.0.0)
|
||||
railties (>= 5.0.0)
|
||||
railties (7.0.5)
|
||||
actionpack (= 7.0.5)
|
||||
activesupport (= 7.0.5)
|
||||
railties (7.0.8)
|
||||
actionpack (= 7.0.8)
|
||||
activesupport (= 7.0.8)
|
||||
method_source
|
||||
rake (>= 12.2)
|
||||
thor (~> 1.0)
|
||||
@@ -296,13 +335,13 @@ GEM
|
||||
ffi (~> 1.0)
|
||||
rbs (2.8.4)
|
||||
redis (4.8.1)
|
||||
regexp_parser (2.8.1)
|
||||
responders (3.1.0)
|
||||
regexp_parser (2.8.2)
|
||||
responders (3.1.1)
|
||||
actionpack (>= 5.2)
|
||||
railties (>= 5.2)
|
||||
reverse_markdown (2.1.1)
|
||||
nokogiri
|
||||
rexml (3.2.5)
|
||||
rexml (3.2.6)
|
||||
rqrcode (2.2.0)
|
||||
chunky_png (~> 1.0)
|
||||
rqrcode_core (~> 1.0)
|
||||
@@ -312,7 +351,7 @@ GEM
|
||||
rspec-expectations (3.12.3)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.12.0)
|
||||
rspec-mocks (3.12.5)
|
||||
rspec-mocks (3.12.6)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.12.0)
|
||||
rspec-rails (6.0.3)
|
||||
@@ -323,32 +362,36 @@ GEM
|
||||
rspec-expectations (~> 3.12)
|
||||
rspec-mocks (~> 3.12)
|
||||
rspec-support (~> 3.12)
|
||||
rspec-support (3.12.0)
|
||||
rubocop (1.52.1)
|
||||
rspec-support (3.12.1)
|
||||
rubocop (1.57.1)
|
||||
base64 (~> 0.1.1)
|
||||
json (~> 2.3)
|
||||
language_server-protocol (>= 3.17.0)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 3.2.2.3)
|
||||
parser (>= 3.2.2.4)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
regexp_parser (>= 1.8, < 3.0)
|
||||
rexml (>= 3.2.5, < 4.0)
|
||||
rubocop-ast (>= 1.28.0, < 2.0)
|
||||
rubocop-ast (>= 1.28.1, < 2.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 2.4.0, < 3.0)
|
||||
rubocop-ast (1.29.0)
|
||||
parser (>= 3.2.1.0)
|
||||
ruby-progressbar (1.13.0)
|
||||
ruby-vips (2.2.0)
|
||||
ffi (~> 1.12)
|
||||
ruby2_keywords (0.0.5)
|
||||
rufus-scheduler (3.9.1)
|
||||
fugit (~> 1.1, >= 1.1.6)
|
||||
sanitize (6.0.1)
|
||||
sanitize (6.1.0)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.12.0)
|
||||
sentry-rails (5.9.0)
|
||||
sentry-rails (5.12.0)
|
||||
railties (>= 5.0)
|
||||
sentry-ruby (~> 5.9.0)
|
||||
sentry-ruby (5.9.0)
|
||||
sentry-ruby (~> 5.12.0)
|
||||
sentry-ruby (5.12.0)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
sidekiq (6.5.9)
|
||||
sidekiq (6.5.12)
|
||||
connection_pool (>= 2.2.5, < 3)
|
||||
rack (~> 2.0)
|
||||
redis (>= 4.5.0, < 5)
|
||||
@@ -372,55 +415,57 @@ GEM
|
||||
thor (~> 1.0)
|
||||
tilt (~> 2.0)
|
||||
yard (~> 0.9, >= 0.9.24)
|
||||
sprockets (4.2.0)
|
||||
sprockets (4.2.1)
|
||||
concurrent-ruby (~> 1.0)
|
||||
rack (>= 2.2.4, < 4)
|
||||
sprockets-rails (3.4.2)
|
||||
actionpack (>= 5.2)
|
||||
activesupport (>= 5.2)
|
||||
sprockets (>= 3.0.0)
|
||||
sqlite3 (1.6.3-arm64-darwin)
|
||||
sqlite3 (1.6.3-x86_64-linux)
|
||||
stimulus-rails (1.2.1)
|
||||
sqlite3 (1.6.7-arm64-darwin)
|
||||
sqlite3 (1.6.7-x86_64-linux)
|
||||
stimulus-rails (1.3.0)
|
||||
railties (>= 6.0.0)
|
||||
thor (1.2.2)
|
||||
tilt (2.2.0)
|
||||
timeout (0.3.2)
|
||||
turbo-rails (1.4.0)
|
||||
thor (1.3.0)
|
||||
tilt (2.3.0)
|
||||
timeout (0.4.0)
|
||||
turbo-rails (1.5.0)
|
||||
actionpack (>= 6.0.0)
|
||||
activejob (>= 6.0.0)
|
||||
railties (>= 6.0.0)
|
||||
tzinfo (2.0.6)
|
||||
concurrent-ruby (~> 1.0)
|
||||
unicode-display_width (2.4.2)
|
||||
view_component (3.2.0)
|
||||
unicode-display_width (2.5.0)
|
||||
view_component (3.6.0)
|
||||
activesupport (>= 5.2.0, < 8.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
method_source (~> 1.0)
|
||||
warden (1.2.9)
|
||||
rack (>= 2.0.9)
|
||||
web-console (4.2.0)
|
||||
web-console (4.2.1)
|
||||
actionview (>= 6.0.0)
|
||||
activemodel (>= 6.0.0)
|
||||
bindex (>= 0.4.0)
|
||||
railties (>= 6.0.0)
|
||||
webmock (3.18.1)
|
||||
webmock (3.19.1)
|
||||
addressable (>= 2.8.0)
|
||||
crack (>= 0.3.2)
|
||||
hashdiff (>= 0.4.0, < 2.0.0)
|
||||
websocket-driver (0.7.5)
|
||||
websocket-driver (0.7.6)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.5)
|
||||
xpath (3.2.0)
|
||||
nokogiri (~> 1.8)
|
||||
yard (0.9.34)
|
||||
zeitwerk (2.6.8)
|
||||
zeitwerk (2.6.12)
|
||||
|
||||
PLATFORMS
|
||||
arm64-darwin-22
|
||||
ruby
|
||||
x86_64-linux
|
||||
|
||||
DEPENDENCIES
|
||||
aws-sdk-s3
|
||||
byebug (~> 11.1)
|
||||
capybara
|
||||
cssbundling-rails
|
||||
@@ -429,12 +474,14 @@ DEPENDENCIES
|
||||
devise_ldap_authenticatable
|
||||
discourse_api
|
||||
dotenv-rails
|
||||
down
|
||||
factory_bot_rails
|
||||
faker
|
||||
faraday
|
||||
flipper
|
||||
flipper-active_record
|
||||
flipper-ui
|
||||
image_processing (~> 1.12.2)
|
||||
importmap-rails
|
||||
jbuilder (~> 2.7)
|
||||
letter_opener
|
||||
@@ -442,6 +489,7 @@ DEPENDENCIES
|
||||
listen (~> 3.2)
|
||||
lnurl
|
||||
lockbox
|
||||
manifique!
|
||||
net-ldap
|
||||
nostr!
|
||||
pagy (~> 6.0, >= 6.0.2)
|
||||
|
||||
14
README.md
@@ -79,6 +79,20 @@ The setup task will first delete any existing entries in the directory tree
|
||||
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.
|
||||
|
||||
#### Minio / RS
|
||||
|
||||
If you want to run remoteStorage accounts locally, you will have to create the
|
||||
respective bucket first:
|
||||
|
||||
* `docker compose up web redis minio liquor-cabinet`
|
||||
* Head to http://localhost:9001 and log in with user `minioadmin`, password
|
||||
`minioadmin`
|
||||
* Create a new bucket called `remotestorage` (or whatever you
|
||||
change the `S3_BUCKET` config to)
|
||||
* Create a new key with ID "dev-key" and secret "123456789" (or whatever you
|
||||
change `S3_ACCESS_KEY` and `S3_SECRET_KEY` to). Leave the policy field empty,
|
||||
as it will automatically allow access to the bucket you created.
|
||||
|
||||
### Adding npm modules to use with Stimulus controllers
|
||||
|
||||
The following command downloads the specified npm module to `vendor/javascript`
|
||||
|
||||
26
app/components/dropdown_component.html.erb
Normal file
@@ -0,0 +1,26 @@
|
||||
<div data-controller="dropdown" data-action="click->dropdown#toggle click@window->dropdown#hide">
|
||||
<div class="relative inline-block">
|
||||
<div role="button" tabindex="0" data-dropdown-target="button"
|
||||
class="inline-block select-none">
|
||||
<span class="appearance-none flex items-center inline-block">
|
||||
<span class="p-2 bg-gray-50 hover:bg-gray-100 rounded-full">
|
||||
<%= render partial: "icons/kebab-menu", locals: {
|
||||
custom_class: "inline text-gray-500 h-6 w-6"
|
||||
} %>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div data-dropdown-target="menu"
|
||||
data-transition-enter="transition ease-out duration-200"
|
||||
data-transition-enter-from="opacity-0 translate-y-1"
|
||||
data-transition-enter-to="opacity-100 translate-y-0"
|
||||
data-transition-leave="transition ease-in duration-150"
|
||||
data-transition-leave-from="opacity-100 translate-y-0"
|
||||
data-transition-leave-to="opacity-0 translate-y-1"
|
||||
class="hidden absolute top-4 right-0 z-10 mt-5 flex w-screen max-w-max">
|
||||
<div class="bg-white shadow-lg rounded border overflow-hidden w-auto">
|
||||
<%= content %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
5
app/components/dropdown_component.rb
Normal file
@@ -0,0 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class DropdownComponent < ViewComponent::Base
|
||||
|
||||
end
|
||||
6
app/components/dropdown_link_component.html.erb
Normal file
@@ -0,0 +1,6 @@
|
||||
<%= link_to @href, class: @class, data: {
|
||||
'dropdown-target': "menuItem",
|
||||
'action': "keydown.up->dropdown#previousItem:prevent keydown.down->dropdown#nextItem:prevent"
|
||||
} do %>
|
||||
<%= content %>
|
||||
<% end %>
|
||||
18
app/components/dropdown_link_component.rb
Normal file
@@ -0,0 +1,18 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class DropdownLinkComponent < ViewComponent::Base
|
||||
def initialize(href:, separator: false, add_class: nil)
|
||||
@href = href
|
||||
@class = class_str(separator, add_class)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def class_str(separator, add_class)
|
||||
str = "no-underline block px-5 py-3 text-sm text-gray-900 bg-white
|
||||
hover:bg-gray-100 focus:bg-gray-100 whitespace-no-wrap"
|
||||
str = "#{str} border-t" if separator
|
||||
str = "#{str} #{add_class}" if add_class
|
||||
str
|
||||
end
|
||||
end
|
||||
@@ -6,7 +6,7 @@ module FormElements
|
||||
@tag = tag
|
||||
@positioning = :vertical
|
||||
@title = title
|
||||
@descripton = description
|
||||
@description = description
|
||||
@key = key.to_sym
|
||||
@type = type
|
||||
@resettable = is_resettable?(@key)
|
||||
|
||||
26
app/components/rs_auth_component.html.erb
Normal file
@@ -0,0 +1,26 @@
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="h-16 w-16 flex-none">
|
||||
<%= image_tag s3_image_url(@web_app.icon), class: "h-full w-full" %>
|
||||
</div>
|
||||
<div class="flex-grow">
|
||||
<h4 class="mb-1 text-lg font-bold">
|
||||
<%= @web_app.name %>
|
||||
</h4>
|
||||
<p class="text-sm text-gray-500">
|
||||
<%= @auth.client_id %>
|
||||
</p>
|
||||
</div>
|
||||
<%= render DropdownComponent.new do %>
|
||||
<%= render DropdownLinkComponent.new(
|
||||
href: launch_app_services_storage_rs_auth_url(@auth)
|
||||
) do %>
|
||||
Launch app
|
||||
<% end %>
|
||||
<%= render DropdownLinkComponent.new(
|
||||
href: revoke_services_storage_rs_auth_url(@auth),
|
||||
separator: true, add_class: "text-red-700"
|
||||
) do %>
|
||||
Revoke access
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
8
app/components/rs_auth_component.rb
Normal file
@@ -0,0 +1,8 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class RsAuthComponent < ViewComponent::Base
|
||||
def initialize(auth:)
|
||||
@auth = auth
|
||||
@web_app = auth.web_app
|
||||
end
|
||||
end
|
||||
@@ -1,4 +1,8 @@
|
||||
<%= link_to @path, class: @link_class, title: (@disabled ? "Coming soon" : nil) do %>
|
||||
<% if @icon.present? %>
|
||||
<%= render partial: "icons/#{@icon}", locals: { custom_class: @icon_class } %>
|
||||
<% elsif @text_icon.present? %>
|
||||
<span class="mr-3"><%= @text_icon %></span>
|
||||
<% end %>
|
||||
<span class="truncate"><%= @name %></span>
|
||||
<% end %>
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class SidenavLinkComponent < ViewComponent::Base
|
||||
def initialize(name:, level: 1, path:, icon:, active: false, disabled: false)
|
||||
def initialize(name:, level: 1, path:, icon: nil, text_icon: nil,
|
||||
active: false, disabled: false)
|
||||
@name = name
|
||||
@level = level
|
||||
@path = path
|
||||
@icon = icon
|
||||
@text_icon = text_icon
|
||||
@active = active
|
||||
@disabled = disabled
|
||||
@link_class = class_names_link(path)
|
||||
|
||||
9
app/controllers/admin/app_catalog/web_apps_controller.rb
Normal file
@@ -0,0 +1,9 @@
|
||||
class Admin::AppCatalog::WebAppsController < Admin::AppCatalogController
|
||||
def index
|
||||
@pagy, @web_apps = pagy(AppCatalog::WebApp.order('created_at desc'))
|
||||
|
||||
@stats = {
|
||||
known_apps: AppCatalog::WebApp.count
|
||||
}
|
||||
end
|
||||
end
|
||||
9
app/controllers/admin/app_catalog_controller.rb
Normal file
@@ -0,0 +1,9 @@
|
||||
class Admin::AppCatalogController < Admin::BaseController
|
||||
before_action :set_current_section
|
||||
|
||||
private
|
||||
|
||||
def set_current_section
|
||||
@current_section = :app_catalog
|
||||
end
|
||||
end
|
||||
@@ -3,7 +3,7 @@ class Admin::Settings::ServicesController < Admin::SettingsController
|
||||
@service = params[:s]
|
||||
|
||||
if @service.blank?
|
||||
redirect_to admin_settings_services_path(params: { s: "discourse" })
|
||||
redirect_to admin_settings_services_path(params: { s: "btcpay" })
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ class Admin::UsersController < Admin::BaseController
|
||||
end
|
||||
|
||||
@services_enabled = @user.services_enabled
|
||||
|
||||
@avatar = LdapManager::FetchAvatar.call(cn: @user.cn, ou: @user.ou)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
29
app/controllers/api/btcpay_controller.rb
Normal file
@@ -0,0 +1,29 @@
|
||||
class Api::BtcpayController < Api::BaseController
|
||||
before_action :require_feature_enabled
|
||||
|
||||
def onchain_btc_balance
|
||||
balance = BtcpayManager::FetchOnchainWalletBalance.call
|
||||
render json: balance
|
||||
rescue => error
|
||||
Rails.logger.warn "Failed to fetch BTC wallet balance: #{error.message}"
|
||||
render json: { error: 'Failed to fetch wallet balance' },
|
||||
status: 500
|
||||
end
|
||||
|
||||
def lightning_btc_balance
|
||||
balance = BtcpayManager::FetchLightningWalletBalance.call
|
||||
render json: balance
|
||||
rescue => error
|
||||
Rails.logger.warn "Failed to fetch BTC lightning balance: #{error.message}"
|
||||
render json: { error: 'Failed to fetch wallet balance' },
|
||||
status: 500
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def require_feature_enabled
|
||||
unless Setting.btcpay_publish_wallet_balances
|
||||
http_status :not_found and return
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,13 +0,0 @@
|
||||
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
|
||||
@@ -37,4 +37,8 @@ class ApplicationController < ActionController::Base
|
||||
format.any { head status }
|
||||
end
|
||||
end
|
||||
|
||||
def after_sign_in_path_for(user)
|
||||
session[:user_return_to] || root_path
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,8 +3,7 @@ class Rs::OauthController < ApplicationController
|
||||
before_action :authenticate_user!, only: :create
|
||||
|
||||
def new
|
||||
username, org = params[:useraddress].split("@")
|
||||
@user = User.where(cn: username.downcase, ou: org).first
|
||||
@user = User.where(cn: params[:username].downcase, ou: Setting.primary_domain).first
|
||||
@scopes = parse_scopes params[:scope]
|
||||
@redirect_uri = params[:redirect_uri]
|
||||
@client_id = params[:client_id]
|
||||
@@ -22,7 +21,7 @@ class Rs::OauthController < ApplicationController
|
||||
unless current_user == @user
|
||||
sign_out :user
|
||||
|
||||
redirect_to new_rs_oauth_url(@user.address,
|
||||
redirect_to new_rs_oauth_url(@user.cn,
|
||||
scope: params[:scope],
|
||||
redirect_uri: params[:redirect_uri],
|
||||
client_id: params[:client_id],
|
||||
@@ -88,7 +87,7 @@ class Rs::OauthController < ApplicationController
|
||||
permissions: permissions,
|
||||
client_id: client_id,
|
||||
redirect_uri: redirect_uri,
|
||||
app_name: client_id, #TODO use user-defined name
|
||||
app_name: client_id,
|
||||
expire_at: expire_at
|
||||
)
|
||||
|
||||
@@ -96,28 +95,15 @@ class Rs::OauthController < ApplicationController
|
||||
allow_other_host: true
|
||||
end
|
||||
|
||||
# GET /rs/oauth/token/:id/launch_app
|
||||
def launch_app
|
||||
auth = current_user.remote_storage_authorizations.find(params[:id])
|
||||
|
||||
redirect_to app_auth_url(auth), allow_other_host: true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def require_signed_in_with_username
|
||||
unless user_signed_in?
|
||||
username, org = params[:useraddress].split("@")
|
||||
redirect_to new_user_session_path(cn: username, ou: org)
|
||||
session[:user_return_to] = request.url
|
||||
redirect_to new_user_session_path(cn: params[:username], ou: Setting.primary_domain)
|
||||
end
|
||||
end
|
||||
|
||||
def app_auth_url(auth)
|
||||
url = "#{auth.url}#remotestorage=#{current_user.address}"
|
||||
url += "&access_token=#{auth.token}"
|
||||
url
|
||||
end
|
||||
|
||||
def hostname_of(uri)
|
||||
uri.gsub(/http(s)?:\/\//, "").split(":")[0].split("/")[0]
|
||||
end
|
||||
|
||||
@@ -3,10 +3,13 @@ class Services::RemotestorageController < Services::BaseController
|
||||
before_action :require_feature_enabled
|
||||
before_action :require_service_available
|
||||
|
||||
def dashboard
|
||||
# Dashboard
|
||||
def show
|
||||
# unless current_user.services_enabled.include?(:remotestorage)
|
||||
# redirect_to service_remotestorage_info_path
|
||||
# end
|
||||
@rs_auths = current_user.remote_storage_authorizations
|
||||
# TODO sort by app name
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
42
app/controllers/services/rs_auths_controller.rb
Normal file
@@ -0,0 +1,42 @@
|
||||
class Services::RsAuthsController < Services::BaseController
|
||||
before_action :authenticate_user!
|
||||
before_action :require_feature_enabled
|
||||
before_action :require_service_available
|
||||
# before_action :require_service_enabled
|
||||
before_action :find_rs_auth
|
||||
|
||||
def destroy
|
||||
@auth.destroy!
|
||||
|
||||
respond_to do |format|
|
||||
format.html do redirect_to services_storage_url, flash: {
|
||||
success: 'App authorization revoked'
|
||||
}
|
||||
end
|
||||
format.json { head :no_content }
|
||||
end
|
||||
end
|
||||
|
||||
def launch_app
|
||||
launch_url = "#{@auth.launch_url}#remotestorage=#{current_user.address}&access_token=#{@auth.token}"
|
||||
|
||||
redirect_to launch_url, allow_other_host: true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def require_feature_enabled
|
||||
unless Flipper.enabled?(:remotestorage, current_user)
|
||||
http_status :forbidden
|
||||
end
|
||||
end
|
||||
|
||||
def require_service_available
|
||||
http_status :not_found unless Setting.remotestorage_enabled?
|
||||
end
|
||||
|
||||
def find_rs_auth
|
||||
@auth = current_user.remote_storage_authorizations.find(params[:id])
|
||||
http_status :not_found unless @auth.present?
|
||||
end
|
||||
end
|
||||
@@ -19,10 +19,15 @@ class SettingsController < ApplicationController
|
||||
def update
|
||||
@user.preferences.merge!(user_params[:preferences] || {})
|
||||
@user.display_name = user_params[:display_name]
|
||||
@user.avatar_new = user_params[:avatar]
|
||||
|
||||
if @user.save
|
||||
if @user.display_name && (@user.display_name != @user.ldap_entry[:display_name])
|
||||
LdapManager::UpdateDisplayName.call(@user.dn, user_params[:display_name])
|
||||
LdapManager::UpdateDisplayName.call(@user.dn, @user.display_name)
|
||||
end
|
||||
|
||||
if @user.avatar_new.present?
|
||||
LdapManager::UpdateAvatar.call(@user.dn, @user.avatar_new)
|
||||
end
|
||||
|
||||
redirect_to setting_path(@settings_section), flash: {
|
||||
@@ -105,7 +110,9 @@ class SettingsController < ApplicationController
|
||||
|
||||
def set_settings_section
|
||||
@settings_section = params[:section]
|
||||
allowed_sections = [:profile, :account, :lightning, :xmpp, :experiments]
|
||||
allowed_sections = [
|
||||
:profile, :account, :lightning, :remotestorage, :xmpp, :experiments
|
||||
]
|
||||
|
||||
unless allowed_sections.include?(@settings_section.to_sym)
|
||||
redirect_to setting_path(:profile)
|
||||
@@ -117,8 +124,9 @@ class SettingsController < ApplicationController
|
||||
end
|
||||
|
||||
def user_params
|
||||
params.require(:user).permit(:display_name, preferences: [
|
||||
params.require(:user).permit(:display_name, :avatar, preferences: [
|
||||
:lightning_notify_sats_received,
|
||||
:remotestorage_notify_auth_created,
|
||||
:xmpp_exchange_contacts_with_invitees
|
||||
])
|
||||
end
|
||||
|
||||
@@ -6,15 +6,19 @@ class WebfingerController < ApplicationController
|
||||
def show
|
||||
resource = params[:resource]
|
||||
|
||||
if resource && resource.match(/acct:\w+/)
|
||||
useraddress = resource.split(":").last
|
||||
username, org = useraddress.split("@")
|
||||
username.downcase!
|
||||
unless User.where(cn: username, ou: org).any?
|
||||
if resource && @useraddress = resource.match(/acct:(.+)/)&.[](1)
|
||||
@username, @org = @useraddress.split("@")
|
||||
|
||||
unless Rails.env.development?
|
||||
# Allow different domains (e.g. localhost:3000) in development only
|
||||
head 404 and return unless @org == Setting.primary_domain
|
||||
end
|
||||
|
||||
unless User.where(cn: @username.downcase, ou: Setting.primary_domain).any?
|
||||
head 404 and return
|
||||
end
|
||||
|
||||
render json: webfinger(useraddress).to_json,
|
||||
render json: webfinger.to_json,
|
||||
content_type: "application/jrd+json"
|
||||
else
|
||||
head 422 and return
|
||||
@@ -23,19 +27,18 @@ class WebfingerController < ApplicationController
|
||||
|
||||
private
|
||||
|
||||
def webfinger(useraddress)
|
||||
def webfinger
|
||||
links = [];
|
||||
|
||||
links << remotestorage_link(useraddress) if Setting.remotestorage_enabled
|
||||
# TODO check if storage service is enabled for user, not just globally
|
||||
links << remotestorage_link if Setting.remotestorage_enabled
|
||||
|
||||
{ "links" => links }
|
||||
end
|
||||
|
||||
def remotestorage_link(useraddress)
|
||||
# TODO use when OAuth routes are available
|
||||
# auth_url = new_rs_oauth_url(useraddress)
|
||||
auth_url = "https://example.com/rs/oauth"
|
||||
storage_url = "#{Setting.rs_storage_url}/#{useraddress}"
|
||||
def remotestorage_link
|
||||
auth_url = new_rs_oauth_url(@username)
|
||||
storage_url = "#{Setting.rs_storage_url}/#{@username}"
|
||||
|
||||
{
|
||||
"rel" => "http://tools.ietf.org/id/draft-dejong-remotestorage",
|
||||
|
||||
@@ -30,7 +30,7 @@ class WebhooksController < ApplicationController
|
||||
def notify_xmpp(address, amt_sats, memo)
|
||||
payload = {
|
||||
type: "normal",
|
||||
from: Setting.primary_domain,
|
||||
from: Setting.xmpp_notifications_from_address,
|
||||
to: address,
|
||||
subject: "Sats received!",
|
||||
body: "#{helpers.number_with_delimiter amt_sats} sats received in your Lightning wallet:\n> #{memo}"
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Application } from "@hotwired/stimulus"
|
||||
import { Modal, Tabs } from "tailwindcss-stimulus-components"
|
||||
import { Dropdown, Modal, Tabs } from "tailwindcss-stimulus-components"
|
||||
|
||||
const application = Application.start()
|
||||
|
||||
application.register('dropdown', Dropdown)
|
||||
application.register('modal', Modal)
|
||||
application.register('tabs', Tabs)
|
||||
|
||||
|
||||
@@ -5,4 +5,16 @@ class NotificationMailer < ApplicationMailer
|
||||
@subject = "Sats received"
|
||||
mail to: @user.email, subject: @subject
|
||||
end
|
||||
|
||||
def remotestorage_auth_created
|
||||
@user = params[:user]
|
||||
@auth = params[:auth]
|
||||
@permissions = @auth.permissions.map do |p|
|
||||
access = p.split(":")[1] == 'r' ? 'read' : 'read/write'
|
||||
directory = p.split(':')[0] == '' ? 'all folders and files' : p.split(':')[0]
|
||||
"#{access} #{directory}"
|
||||
end
|
||||
@subject = "New app connected to your storage"
|
||||
mail to: @user.email, subject: @subject
|
||||
end
|
||||
end
|
||||
|
||||
5
app/models/app_catalog.rb
Normal file
@@ -0,0 +1,5 @@
|
||||
module AppCatalog
|
||||
def self.table_name_prefix
|
||||
"app_catalog_"
|
||||
end
|
||||
end
|
||||
16
app/models/app_catalog/web_app.rb
Normal file
@@ -0,0 +1,16 @@
|
||||
class AppCatalog::WebApp < ApplicationRecord
|
||||
store :metadata, coder: JSON
|
||||
|
||||
has_many :remote_storage_authorizations
|
||||
|
||||
has_one_attached :icon
|
||||
has_one_attached :apple_touch_icon
|
||||
|
||||
validates :url, presence: true, uniqueness: true
|
||||
validates :url, format: { with: URI.regexp },
|
||||
if: Proc.new { |a| a.url.present? }
|
||||
|
||||
def update_metadata
|
||||
AppCatalogManager::UpdateMetadata.call(self)
|
||||
end
|
||||
end
|
||||
@@ -1,5 +1,6 @@
|
||||
class RemoteStorageAuthorization < ApplicationRecord
|
||||
belongs_to :user
|
||||
belongs_to :web_app, class_name: "AppCatalog::WebApp", optional: true
|
||||
|
||||
serialize :permissions
|
||||
|
||||
@@ -15,22 +16,36 @@ class RemoteStorageAuthorization < ApplicationRecord
|
||||
|
||||
before_create :generate_token
|
||||
before_create :store_token_in_redis
|
||||
before_create :find_or_create_web_app
|
||||
after_create :schedule_token_expiry
|
||||
after_create :notify_user
|
||||
before_destroy :delete_token_from_redis
|
||||
after_destroy :remove_token_expiry_job
|
||||
|
||||
def url
|
||||
if self.redirect_uri
|
||||
uri = URI.parse self.redirect_uri
|
||||
"#{uri.scheme}://#{client_id}"
|
||||
uri = URI.parse self.redirect_uri
|
||||
"#{uri.scheme}://#{client_id}"
|
||||
end
|
||||
|
||||
def launch_url
|
||||
return url unless web_app && web_app.metadata[:start_url].present?
|
||||
|
||||
start_url = web_app.metadata[:start_url]
|
||||
|
||||
if start_url.match("^https?:\/\/")
|
||||
return start_url.start_with?(url) ? start_url : url
|
||||
else
|
||||
"http://#{client_id}"
|
||||
path = start_url.gsub(/^\.\.\//, "").gsub(/^\.\//, "").gsub(/^\//, "")
|
||||
"#{url}/#{path}"
|
||||
end
|
||||
end
|
||||
|
||||
def delete_token_from_redis
|
||||
key = "rs:authorizations:#{user.address}:#{token}"
|
||||
key = "authorizations:#{user.cn}:#{token}"
|
||||
redis.srem? key, redis.smembers(key)
|
||||
rescue => e
|
||||
Rails.logger.error e
|
||||
Sentry.capture_exception(e) if Setting.sentry_enabled?
|
||||
end
|
||||
|
||||
private
|
||||
@@ -44,7 +59,7 @@ class RemoteStorageAuthorization < ApplicationRecord
|
||||
end
|
||||
|
||||
def store_token_in_redis
|
||||
redis.sadd "rs:authorizations:#{user.address}:#{token}", permissions
|
||||
redis.sadd "authorizations:#{user.cn}:#{token}", permissions
|
||||
end
|
||||
|
||||
def schedule_token_expiry
|
||||
@@ -60,4 +75,40 @@ class RemoteStorageAuthorization < ApplicationRecord
|
||||
job.delete if job.display_args == [id]
|
||||
end
|
||||
end
|
||||
|
||||
def find_or_create_web_app
|
||||
if looks_like_hosted_origin?
|
||||
web_app = AppCatalog::WebApp.find_or_create_by!(url: self.url)
|
||||
web_app.update_metadata unless web_app.name.present?
|
||||
self.web_app = web_app
|
||||
self.app_name = web_app.name.presence || client_id
|
||||
else
|
||||
self.app_name = client_id
|
||||
end
|
||||
end
|
||||
|
||||
def looks_like_hosted_origin?
|
||||
uri = URI.parse self.redirect_uri
|
||||
!!(uri.host =~ /(?=^.{4,253}$)(^((?!-)[a-zA-Z0-9-]{0,62}[a-zA-Z0-9]\.)+[a-zA-Z]{2,63}$)/)
|
||||
rescue URI::InvalidURIError
|
||||
false
|
||||
end
|
||||
|
||||
def notify_user
|
||||
notify = user.preferences[:remotestorage_notify_auth_created]
|
||||
|
||||
case notify
|
||||
when "xmpp"
|
||||
router = Router.new
|
||||
payload = {
|
||||
type: "normal", to: user.address,
|
||||
from: Setting.xmpp_notifications_from_address,
|
||||
body: "You have just granted '#{self.client_id}' access to your Kosmos Storage. Visit your Storage dashboard to check on your connected apps and revoke permissions anytime: #{router.services_storage_url}"
|
||||
}
|
||||
XmppSendMessageJob.perform_later(payload)
|
||||
when "email"
|
||||
NotificationMailer.with(user: user, auth: self)
|
||||
.remotestorage_auth_created.deliver_later
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -36,7 +36,25 @@ class Setting < RailsSettings::Base
|
||||
#
|
||||
|
||||
field :sentry_enabled, type: :boolean, readonly: true,
|
||||
default: (ENV["SENTRY_DSN"].present?.to_s || false)
|
||||
default: ENV["SENTRY_DSN"].present?
|
||||
|
||||
#
|
||||
# BTCPay Server
|
||||
#
|
||||
|
||||
field :btcpay_api_url, type: :string,
|
||||
default: ENV["BTCPAY_API_URL"].presence
|
||||
|
||||
field :btcpay_enabled, type: :boolean,
|
||||
default: ENV["BTCPAY_API_URL"].present?
|
||||
|
||||
field :btcpay_store_id, type: :string,
|
||||
default: ENV["BTCPAY_STORE_ID"].presence
|
||||
|
||||
field :btcpay_auth_token, type: :string,
|
||||
default: ENV["BTCPAY_AUTH_TOKEN"].presence
|
||||
|
||||
field :btcpay_publish_wallet_balances, type: :boolean, default: true
|
||||
|
||||
#
|
||||
# Discourse
|
||||
@@ -46,7 +64,7 @@ class Setting < RailsSettings::Base
|
||||
default: ENV["DISCOURSE_PUBLIC_URL"].presence
|
||||
|
||||
field :discourse_enabled, type: :boolean,
|
||||
default: (ENV["DISCOURSE_PUBLIC_URL"].present?.to_s || false)
|
||||
default: ENV["DISCOURSE_PUBLIC_URL"].present?
|
||||
|
||||
field :discourse_connect_secret, type: :string,
|
||||
default: ENV["DISCOURSE_CONNECT_SECRET"].presence
|
||||
@@ -59,14 +77,14 @@ class Setting < RailsSettings::Base
|
||||
default: ENV["DRONECI_PUBLIC_URL"].presence
|
||||
|
||||
field :droneci_enabled, type: :boolean,
|
||||
default: (ENV["DRONECI_PUBLIC_URL"].present?.to_s || false)
|
||||
default: ENV["DRONECI_PUBLIC_URL"].present?
|
||||
|
||||
#
|
||||
# ejabberd
|
||||
#
|
||||
|
||||
field :ejabberd_enabled, type: :boolean,
|
||||
default: (ENV["EJABBERD_API_URL"].present?.to_s || false)
|
||||
default: ENV["EJABBERD_API_URL"].present?
|
||||
|
||||
field :ejabberd_api_url, type: :string,
|
||||
default: ENV["EJABBERD_API_URL"].presence
|
||||
@@ -85,7 +103,7 @@ class Setting < RailsSettings::Base
|
||||
default: ENV["GITEA_PUBLIC_URL"].presence
|
||||
|
||||
field :gitea_enabled, type: :boolean,
|
||||
default: (ENV["GITEA_PUBLIC_URL"].present?.to_s || false)
|
||||
default: ENV["GITEA_PUBLIC_URL"].present?
|
||||
|
||||
#
|
||||
# Lightning Network
|
||||
@@ -95,16 +113,19 @@ class Setting < RailsSettings::Base
|
||||
default: ENV["LNDHUB_API_URL"].presence
|
||||
|
||||
field :lndhub_enabled, type: :boolean,
|
||||
default: (ENV["LNDHUB_API_URL"].present?.to_s || false)
|
||||
default: ENV["LNDHUB_API_URL"].present?
|
||||
|
||||
field :lndhub_admin_token, type: :string,
|
||||
default: ENV["LNDHUB_ADMIN_TOKEN"].presence
|
||||
|
||||
field :lndhub_admin_enabled, type: :boolean,
|
||||
default: (ENV["LNDHUB_ADMIN_UI"] || false)
|
||||
default: ENV["LNDHUB_ADMIN_UI"] || false
|
||||
|
||||
field :lndhub_public_key, type: :string,
|
||||
default: (ENV["LNDHUB_PUBLIC_KEY"] || "")
|
||||
|
||||
field :lndhub_keysend_enabled, type: :boolean,
|
||||
default: -> { self.lndhub_public_key.present?.to_s || false }
|
||||
default: -> { self.lndhub_public_key.present? }
|
||||
|
||||
#
|
||||
# Mastodon
|
||||
@@ -114,7 +135,7 @@ class Setting < RailsSettings::Base
|
||||
default: ENV["MASTODON_PUBLIC_URL"].presence
|
||||
|
||||
field :mastodon_enabled, type: :boolean,
|
||||
default: (ENV["MASTODON_PUBLIC_URL"].present?.to_s || false)
|
||||
default: ENV["MASTODON_PUBLIC_URL"].present?
|
||||
|
||||
field :mastodon_address_domain, type: :string,
|
||||
default: ENV["MASTODON_ADDRESS_DOMAIN"].presence || self.primary_domain
|
||||
@@ -127,7 +148,7 @@ class Setting < RailsSettings::Base
|
||||
default: ENV["MEDIAWIKI_PUBLIC_URL"].presence
|
||||
|
||||
field :mediawiki_enabled, type: :boolean,
|
||||
default: (ENV["MEDIAWIKI_PUBLIC_URL"].present?.to_s || false)
|
||||
default: ENV["MEDIAWIKI_PUBLIC_URL"].present?
|
||||
|
||||
#
|
||||
# Nostr
|
||||
@@ -140,7 +161,7 @@ class Setting < RailsSettings::Base
|
||||
#
|
||||
|
||||
field :remotestorage_enabled, type: :boolean,
|
||||
default: (ENV["RS_STORAGE_URL"].present?.to_s || false)
|
||||
default: ENV["RS_STORAGE_URL"].present?
|
||||
|
||||
field :rs_storage_url, type: :string,
|
||||
default: ENV["RS_STORAGE_URL"].presence
|
||||
|
||||
@@ -2,10 +2,14 @@ class User < ApplicationRecord
|
||||
include EmailValidatable
|
||||
|
||||
attr_accessor :display_name
|
||||
attr_accessor :avatar_new
|
||||
|
||||
serialize :preferences, UserPreferences
|
||||
|
||||
#
|
||||
# Relations
|
||||
#
|
||||
|
||||
has_many :invitations, dependent: :destroy
|
||||
has_one :invitation, inverse_of: :invitee, foreign_key: 'invited_user_id'
|
||||
has_one :inviter, through: :invitation, source: :user
|
||||
@@ -20,6 +24,10 @@ class User < ApplicationRecord
|
||||
|
||||
has_many :remote_storage_authorizations
|
||||
|
||||
#
|
||||
# Validations
|
||||
#
|
||||
|
||||
validates_uniqueness_of :cn, scope: :ou
|
||||
validates_length_of :cn, minimum: 3
|
||||
validates_format_of :cn, with: /\A([a-z0-9\-])*\z/,
|
||||
@@ -40,10 +48,20 @@ class User < ApplicationRecord
|
||||
|
||||
validates_uniqueness_of :nostr_pubkey, allow_blank: true
|
||||
|
||||
validate :acceptable_avatar
|
||||
|
||||
#
|
||||
# Scopes
|
||||
#
|
||||
|
||||
scope :confirmed, -> { where.not(confirmed_at: nil) }
|
||||
scope :pending, -> { where(confirmed_at: nil) }
|
||||
scope :all_except, -> (user) { where.not(id: user) }
|
||||
|
||||
#
|
||||
# Encrypted database columns
|
||||
#
|
||||
|
||||
has_encrypted :ln_login, :ln_password
|
||||
|
||||
# Include default devise modules. Others available are:
|
||||
@@ -140,6 +158,10 @@ class User < ApplicationRecord
|
||||
@display_name ||= ldap_entry[:display_name]
|
||||
end
|
||||
|
||||
def avatar
|
||||
@avatar_base64 ||= LdapManager::FetchAvatar.call(cn: cn, ou: ou)
|
||||
end
|
||||
|
||||
def services_enabled
|
||||
ldap_entry[:service] || []
|
||||
end
|
||||
@@ -168,4 +190,17 @@ class User < ApplicationRecord
|
||||
return @ldap_service if defined?(@ldap_service)
|
||||
@ldap_service = LdapService.new
|
||||
end
|
||||
|
||||
def acceptable_avatar
|
||||
return unless avatar_new.present?
|
||||
|
||||
if avatar_new.size > 1.megabyte
|
||||
errors.add(:avatar, "file size is too large")
|
||||
end
|
||||
|
||||
acceptable_types = ["image/jpeg", "image/png"]
|
||||
unless acceptable_types.include?(avatar_new.content_type)
|
||||
errors.add(:avatar, "must be a JPEG or PNG file")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
52
app/services/app_catalog_manager/update_metadata.rb
Normal file
@@ -0,0 +1,52 @@
|
||||
require "manifique"
|
||||
require "down"
|
||||
|
||||
module AppCatalogManager
|
||||
class UpdateMetadata < AppCatalogManagerService
|
||||
def initialize(app)
|
||||
@app = app
|
||||
end
|
||||
|
||||
def call
|
||||
agent = Manifique::Agent.new(url: @app.url)
|
||||
metadata = agent.fetch_metadata
|
||||
|
||||
@app.name = metadata.name
|
||||
|
||||
[:name, :short_name, :description, :theme_color, :background_color,
|
||||
:display, :start_url, :scope, :share_target, :icons].each do |prop|
|
||||
@app.metadata[prop] = metadata.send(prop) if prop
|
||||
end
|
||||
|
||||
if icon = metadata.select_icon(sizes: "256x256") ||
|
||||
icon = metadata.select_icon(sizes: "192x192")
|
||||
attach_remote_image(:icon, icon)
|
||||
# TODO elsif get whatever is available
|
||||
end
|
||||
|
||||
if apple_touch_icon = metadata.select_icon(purpose: "apple-touch-icon")
|
||||
attach_remote_image(:apple_touch_icon, apple_touch_icon)
|
||||
end
|
||||
|
||||
@app.save!
|
||||
rescue Manifique::Error => e
|
||||
msg = "Fetching web app manifest failed for #{e.url}: #{e.type}"
|
||||
Rails.logger.warn(msg)
|
||||
Sentry.capture_message(msg) if Setting.sentry_enabled?
|
||||
false
|
||||
end
|
||||
|
||||
def attach_remote_image(attachment_name, icon)
|
||||
if icon['src'].start_with?("http")
|
||||
download_url = icon['src']
|
||||
else
|
||||
download_url = "#{@app.url}/#{icon["src"].gsub(/^\//,'')}"
|
||||
end
|
||||
filename = "#{attachment_name}.png"
|
||||
key = "web_apps/#{@app.id}/icons/#{attachment_name}.png"
|
||||
|
||||
tempfile = Down.download(download_url)
|
||||
@app.send(attachment_name).attach(key: key, io: tempfile, filename: filename)
|
||||
end
|
||||
end
|
||||
end
|
||||
2
app/services/app_catalog_manager_service.rb
Normal file
@@ -0,0 +1,2 @@
|
||||
class AppCatalogManagerService < ApplicationService
|
||||
end
|
||||
@@ -1,32 +0,0 @@
|
||||
#
|
||||
# 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
|
||||
@@ -0,0 +1,11 @@
|
||||
module BtcpayManager
|
||||
class FetchLightningWalletBalance < BtcpayManagerService
|
||||
def call
|
||||
res = get "stores/#{store_id}/lightning/BTC/balance"
|
||||
|
||||
{
|
||||
balance: res["offchain"]["local"].to_i / 1000 # msats to sats
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
13
app/services/btcpay_manager/fetch_onchain_wallet_balance.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
module BtcpayManager
|
||||
class FetchOnchainWalletBalance < BtcpayManagerService
|
||||
def call
|
||||
res = get "stores/#{store_id}/payment-methods/onchain/BTC/wallet"
|
||||
|
||||
{
|
||||
balance: (res["balance"].to_f * 100000000).to_i, # BTC to sats
|
||||
unconfirmed_balance: (res["unconfirmedBalance"].to_f * 100000000).to_i,
|
||||
confirmed_balance: (res["confirmedBalance"].to_f * 100000000).to_i
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
24
app/services/btcpay_manager_service.rb
Normal file
@@ -0,0 +1,24 @@
|
||||
#
|
||||
# API Docs: https://docs.btcpayserver.org/API/Greenfield/v1/
|
||||
#
|
||||
class BtcpayManagerService < ApplicationService
|
||||
attr_reader :base_url, :store_id, :auth_token
|
||||
|
||||
def initialize
|
||||
@base_url = Setting.btcpay_api_url
|
||||
@store_id = Setting.btcpay_store_id
|
||||
@auth_token = Setting.btcpay_auth_token
|
||||
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
|
||||
17
app/services/ldap_manager/fetch_avatar.rb
Normal file
@@ -0,0 +1,17 @@
|
||||
module LdapManager
|
||||
class FetchAvatar < LdapManagerService
|
||||
def initialize(cn:, ou: nil)
|
||||
@cn = cn
|
||||
@ou = ou
|
||||
end
|
||||
|
||||
def call
|
||||
treebase = @ou ? "ou=#{@ou},cn=users,#{suffix}" : ldap_config["base"]
|
||||
attributes = %w{ jpegPhoto }
|
||||
filter = Net::LDAP::Filter.eq("cn", @cn)
|
||||
|
||||
entry = ldap_client.search(base: treebase, filter: filter, attributes: attributes).first
|
||||
entry.try(:jpegPhoto) ? entry.jpegPhoto.first : nil
|
||||
end
|
||||
end
|
||||
end
|
||||
27
app/services/ldap_manager/update_avatar.rb
Normal file
@@ -0,0 +1,27 @@
|
||||
require "image_processing/vips"
|
||||
|
||||
module LdapManager
|
||||
class UpdateAvatar < LdapManagerService
|
||||
def initialize(dn, file)
|
||||
@dn = dn
|
||||
@img_data = process(file)
|
||||
end
|
||||
|
||||
def call
|
||||
replace_attribute @dn, :jpegPhoto, @img_data
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def process(file)
|
||||
processed = ImageProcessing::Vips
|
||||
.resize_to_fill(512, 512)
|
||||
.source(file)
|
||||
.convert("jpeg")
|
||||
.saver(strip: true)
|
||||
.call
|
||||
|
||||
Base64.strict_encode64 processed.read
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,2 +1,5 @@
|
||||
class LdapManagerService < LdapService
|
||||
def suffix
|
||||
@suffix ||= ENV["LDAP_SUFFIX"] || "dc=kosmos,dc=org"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -14,7 +14,7 @@ class LndhubV2 < Lndhub
|
||||
end
|
||||
|
||||
def create_account(payload={})
|
||||
post "v2/users", payload, admin_token: Rails.application.credentials.lndhub[:admin_token]
|
||||
post "v2/users", payload, admin_token: Setting.lndhub_admin_token
|
||||
end
|
||||
|
||||
def create_invoice(payload)
|
||||
|
||||
7
app/services/router.rb
Normal file
@@ -0,0 +1,7 @@
|
||||
class Router
|
||||
include Rails.application.routes.url_helpers
|
||||
|
||||
def self.default_url_options
|
||||
ActionMailer::Base.default_url_options
|
||||
end
|
||||
end
|
||||
56
app/views/admin/app_catalog/web_apps/index.html.erb
Normal file
@@ -0,0 +1,56 @@
|
||||
<%= render HeaderComponent.new(title: "App Catalog") %>
|
||||
|
||||
<%= render MainWithSidenavComponent.new(sidenav_partial: 'shared/admin_sidenav_app_catalog') do %>
|
||||
<section>
|
||||
<%= render QuickstatsContainerComponent.new do %>
|
||||
<%= render QuickstatsItemComponent.new(
|
||||
type: :number,
|
||||
title: 'Known Web Apps',
|
||||
value: @stats[:known_apps],
|
||||
) %>
|
||||
<%# <%= render QuickstatsItemComponent.new(
|
||||
<%# type: :number,
|
||||
<%# title: 'Accepted',
|
||||
<%# value: @stats[:accepted],
|
||||
<%# ) %>
|
||||
<%# <%= render QuickstatsItemComponent.new(
|
||||
<%# type: :number,
|
||||
<%# title: 'Users with referrals',
|
||||
<%# value: @stats[:users_with_referrals],
|
||||
<%# meta: "/ #{User.count}"
|
||||
<%# ) %>
|
||||
<% end %>
|
||||
</section>
|
||||
<% if @web_apps.any? %>
|
||||
<section>
|
||||
<h3>Web Apps</h3>
|
||||
<table class="divided mb-8">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>URL</th>
|
||||
<th class="hidden md:table-cell">RS Auths</th>
|
||||
<th class="hidden md:table-cell">Created at</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% @web_apps.each do |web_app| %>
|
||||
<tr>
|
||||
<td><%= web_app.name %></td>
|
||||
<td><%= link_to web_app.url, web_app.url,
|
||||
target: "_blank", rel: "nofollow noopener",
|
||||
class: "ks-text-link" %></td>
|
||||
<td class="hidden md:table-cell"><%= web_app.remote_storage_authorizations.count %></td>
|
||||
<td class="hidden md:table-cell">
|
||||
<span title="<%= web_app.created_at %>" class="cursor-help">
|
||||
<%= time_ago_in_words web_app.created_at, include_seconds: false %> ago
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
<%== pagy_nav @pagy %>
|
||||
</section>
|
||||
<% end %>
|
||||
<% end %>
|
||||
37
app/views/admin/settings/services/_btcpay.html.erb
Normal file
@@ -0,0 +1,37 @@
|
||||
<h3>BTCPay Server</h3>
|
||||
<ul role="list">
|
||||
<%= render FormElements::FieldsetToggleComponent.new(
|
||||
form: f,
|
||||
attribute: :btcpay_enabled,
|
||||
enabled: Setting.btcpay_enabled?,
|
||||
title: "Enable BTCPay integration",
|
||||
description: "BTCPay configuration present and features enabled"
|
||||
) %>
|
||||
<% if Setting.btcpay_enabled? %>
|
||||
<%= render FormElements::FieldsetResettableSettingComponent.new(
|
||||
key: :btcpay_api_url,
|
||||
title: "API URL"
|
||||
) %>
|
||||
<%= render FormElements::FieldsetResettableSettingComponent.new(
|
||||
key: :btcpay_store_id,
|
||||
title: "Store ID"
|
||||
) %>
|
||||
<%= render FormElements::FieldsetResettableSettingComponent.new(
|
||||
key: :btcpay_auth_token,
|
||||
type: :password,
|
||||
title: "Auth Token"
|
||||
) %>
|
||||
</ul>
|
||||
</section>
|
||||
<section>
|
||||
<h3>REST API</h3>
|
||||
<ul role="list">
|
||||
<%= render FormElements::FieldsetToggleComponent.new(
|
||||
form: f,
|
||||
attribute: :btcpay_publish_wallet_balances,
|
||||
enabled: Setting.btcpay_publish_wallet_balances?,
|
||||
title: "Publish wallet balances",
|
||||
description: "Publish the store's on-chain and Lightning wallet balances"
|
||||
) %>
|
||||
<% end %>
|
||||
</ul>
|
||||
@@ -9,29 +9,35 @@
|
||||
) %>
|
||||
<% if Setting.lndhub_enabled? %>
|
||||
<%= render FormElements::FieldsetResettableSettingComponent.new(
|
||||
key: :lndhub_api_url,
|
||||
title: "API URL"
|
||||
) %>
|
||||
<% end %>
|
||||
<%= render FormElements::FieldsetToggleComponent.new(
|
||||
form: f,
|
||||
attribute: :lndhub_admin_enabled,
|
||||
enabled: Setting.lndhub_admin_enabled?,
|
||||
title: "Enable LNDHub admin panel",
|
||||
description: "LNDHub database configuration present and admin panel enabled"
|
||||
) %>
|
||||
<%= render FormElements::FieldsetToggleComponent.new(
|
||||
form: f,
|
||||
attribute: :lndhub_keysend_enabled,
|
||||
enabled: Setting.lndhub_keysend_enabled?,
|
||||
title: "Enable keysend payments",
|
||||
description: "Allow users to receive invoice-less payments to their Lightning Address"
|
||||
) %>
|
||||
<% if Setting.lndhub_keysend_enabled? %>
|
||||
key: :lndhub_api_url,
|
||||
title: "API URL"
|
||||
) %>
|
||||
<%= render FormElements::FieldsetResettableSettingComponent.new(
|
||||
key: :lndhub_public_key,
|
||||
title: "Public key",
|
||||
description: "The public key of the Lightning node used by LNDHub"
|
||||
) %>
|
||||
key: :lndhub_admin_token,
|
||||
type: :password,
|
||||
title: "Admin token",
|
||||
description: "Auth token for creating new lndhub accounts"
|
||||
) %>
|
||||
<%= render FormElements::FieldsetToggleComponent.new(
|
||||
form: f,
|
||||
attribute: :lndhub_admin_enabled,
|
||||
enabled: Setting.lndhub_admin_enabled?,
|
||||
title: "Enable LNDHub admin panel",
|
||||
description: "LNDHub database configuration present and admin panel enabled"
|
||||
) %>
|
||||
<%= render FormElements::FieldsetToggleComponent.new(
|
||||
form: f,
|
||||
attribute: :lndhub_keysend_enabled,
|
||||
enabled: Setting.lndhub_keysend_enabled?,
|
||||
title: "Enable keysend payments",
|
||||
description: "Allow users to receive invoice-less payments to their Lightning Address"
|
||||
) %>
|
||||
<% if Setting.lndhub_keysend_enabled? %>
|
||||
<%= render FormElements::FieldsetResettableSettingComponent.new(
|
||||
key: :lndhub_public_key,
|
||||
title: "Public key",
|
||||
description: "The public key of the Lightning node used by LNDHub"
|
||||
) %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</ul>
|
||||
|
||||
@@ -63,6 +63,12 @@
|
||||
</section>
|
||||
|
||||
<section class="sm:flex-1 sm:pt-0">
|
||||
<% if @avatar.present? %>
|
||||
<h3>LDAP<h3>
|
||||
<p>
|
||||
<img src="data:image/jpeg;base64,<%= @avatar %>" class="h-48 w-48" />
|
||||
</p>
|
||||
<% end %>
|
||||
<!-- <h3>Actions</h3> -->
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -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-globe"><circle cx="12" cy="12" r="10"></circle><line x1="2" y1="12" x2="22" y2="12"></line><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></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-globe <%= custom_class %>"><circle cx="12" cy="12" r="10"></circle><line x1="2" y1="12" x2="22" y2="12"></line><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 409 B After Width: | Height: | Size: 430 B |
10
app/views/icons/_kebab-menu.html.erb
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="800px" height="800px" viewBox="0 0 24 24" class="<%= custom_class %>" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Menu</title>
|
||||
<g id="kebap-menu" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<rect id="Container" x="0" y="0" width="24" height="24"></rect>
|
||||
<path d="M12,6 C12.5522847,6 13,5.55228475 13,5 C13,4.44771525 12.5522847,4 12,4 C11.4477153,4 11,4.44771525 11,5 C11,5.55228475 11.4477153,6 12,6 Z" stroke="#030819" stroke-width="2" stroke-linecap="round" stroke-dasharray="0,0"></path>
|
||||
<path d="M12,13 C12.5522847,13 13,12.5522847 13,12 C13,11.4477153 12.5522847,11 12,11 C11.4477153,11 11,11.4477153 11,12 C11,12.5522847 11.4477153,13 12,13 Z" stroke="#030819" stroke-width="2" stroke-linecap="round" stroke-dasharray="0,0"></path>
|
||||
<path d="M12,20 C12.5522847,20 13,19.5522847 13,19 C13,18.4477153 12.5522847,18 12,18 C11.4477153,18 11,18.4477153 11,19 C11,19.5522847 11.4477153,20 12,20 Z" stroke="#030819" stroke-width="2" stroke-linecap="round" stroke-dasharray="0,0"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
6
app/views/icons/_remotestorage.html.erb
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24" height="24" class="<%= custom_class %>" clip-rule="evenodd" fill-rule="evenodd" image-rendering="optimizeQuality" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" version="1.1" viewBox="0 0 250 249.9" xml:space="preserve" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="translate(-66.822 -.16484)">
|
||||
<polygon id="polygon1" fill="currentColor" transform="matrix(.29308 0 0 .29308 83.528 -.028385)" points="228 181 370 100 511 181 652 263 370 425 87 263 87 263 0 213 0 213 0 311 0 378 0 427 0 476 86 525 185 582 370 689 554 582 653 525 653 590 653 592 370 754 0 542 0 640 185 747 370 853 554 747 739 640 739 525 739 476 739 427 739 378 653 427 370 589 86 427 86 361 185 418 370 524 554 418 653 361 739 311 739 213 554 107 370 0 185 107 58 180 144 230"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 848 B |
@@ -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-star"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-star <%= custom_class %>"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 339 B After Width: | Height: | Size: 360 B |
@@ -0,0 +1,23 @@
|
||||
Hi <%= @user.display_name.presence || @user.cn %>,
|
||||
|
||||
You have just granted '<%= @auth.client_id %>' access to your Kosmos Storage, with the following permissions:
|
||||
|
||||
<% @permissions.each do |p| %>
|
||||
* <%= p %>
|
||||
<% end %>
|
||||
|
||||
Visit your Storage dashboard to check on your connected apps and revoke permissions anytime:
|
||||
|
||||
<%= services_storage_url %>
|
||||
|
||||
Have fun!
|
||||
|
||||
---
|
||||
|
||||
You can disable email notifications for new app authorizations in your account settings:
|
||||
<%= setting_url(:remotestorage) %>
|
||||
<% if Setting.discourse_enabled %>
|
||||
|
||||
If you have any questions, please visit our community forums:
|
||||
<%= Setting.discourse_public_url %>
|
||||
<% end %>
|
||||
@@ -38,7 +38,7 @@
|
||||
<h3>Chat Apps</h3>
|
||||
<p>
|
||||
Use your account with many different apps, and on any devices you wish!
|
||||
When opening an app for the first time, just enter your user address and
|
||||
When opening an app for the first time, just enter your address and
|
||||
password to log in.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
<%= render HeaderComponent.new(title: "Storage") %>
|
||||
|
||||
<%= render MainSimpleComponent.new do %>
|
||||
<section>
|
||||
<h3>Feature enabled</h3>
|
||||
</section>
|
||||
<% end %>
|
||||
16
app/views/services/remotestorage/show.html.erb
Normal file
@@ -0,0 +1,16 @@
|
||||
<%= render HeaderComponent.new(title: "Storage") %>
|
||||
|
||||
<%= render MainSimpleComponent.new do %>
|
||||
<section>
|
||||
<h3 class="mb-10">Connected Apps</h3>
|
||||
<% if @rs_auths.any? %>
|
||||
<div class="w-full grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-y-10 gap-x-12">
|
||||
<% @rs_auths.each do |auth| %>
|
||||
<%= render RsAuthComponent.new(auth: auth) %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% else %>
|
||||
<p>No apps connected yet.</p>
|
||||
<% end %>
|
||||
</section>
|
||||
<% end %>
|
||||
@@ -1,33 +1,62 @@
|
||||
<section>
|
||||
<h3>Profile</h3>
|
||||
<p class="mb-2">
|
||||
<%= label :user_address, 'User address', class: 'font-bold' %>
|
||||
</p>
|
||||
<p data-controller="clipboard" class="flex gap-1 mb-2 sm:w-3/5">
|
||||
<input type="text" id="user_address" class="grow"
|
||||
value=<%= @user.address %> disabled="disabled"
|
||||
data-clipboard-target="source" />
|
||||
<button id="copy-user-address" class="btn-md btn-icon btn-outline shrink-0"
|
||||
data-clipboard-target="trigger" data-action="clipboard#copy"
|
||||
title="Copy to clipboard">
|
||||
<span class="content-initial">
|
||||
<%= render partial: "icons/copy", locals: { custom_class: "text-blue-600 h-4 w-4 inline" } %>
|
||||
</span>
|
||||
<span class="content-active hidden">
|
||||
<%= render partial: "icons/check", locals: { custom_class: "text-blue-600 h-4 w-4 inline" } %>
|
||||
</span>
|
||||
</button>
|
||||
</p>
|
||||
<p class="text-sm text-gray-500">
|
||||
Your user address for Chat and Lightning Network.
|
||||
</p>
|
||||
<div class="mb-6">
|
||||
<p class="mb-2">
|
||||
<%= label :user_address, 'User address', class: 'font-bold' %>
|
||||
</p>
|
||||
<p data-controller="clipboard" class="flex gap-1 mb-2 sm:w-3/5">
|
||||
<input type="text" id="user_address" class="grow"
|
||||
value=<%= @user.address %> disabled="disabled"
|
||||
data-clipboard-target="source" />
|
||||
<button id="copy-user-address" class="btn-md btn-icon btn-outline shrink-0"
|
||||
data-clipboard-target="trigger" data-action="clipboard#copy"
|
||||
title="Copy to clipboard">
|
||||
<span class="content-initial">
|
||||
<%= render partial: "icons/copy", locals: { custom_class: "text-blue-600 h-4 w-4 inline" } %>
|
||||
</span>
|
||||
<span class="content-active hidden">
|
||||
<%= render partial: "icons/check", locals: { custom_class: "text-blue-600 h-4 w-4 inline" } %>
|
||||
</span>
|
||||
</button>
|
||||
</p>
|
||||
<p class="text-sm text-gray-500">
|
||||
Your user address for Chat and Lightning Network.
|
||||
</p>
|
||||
</div>
|
||||
<%= form_for(@user, url: setting_path(:profile), html: { :method => :put }) do |f| %>
|
||||
<%= render FormElements::FieldsetComponent.new(tag: "div", title: "Display name") do %>
|
||||
<%= f.text_field :display_name, class: "w-full sm:w-3/5 mb-2" %>
|
||||
<%= f.text_field :display_name, class: "w-full sm:w-3/5" %>
|
||||
<% if @validation_errors.present? && @validation_errors[:display_name].present? %>
|
||||
<p class="error-msg"><%= @validation_errors[:display_name].first %></p>
|
||||
<p class="error-msg mt-2"><%= @validation_errors[:display_name].first %></p>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<label class="block">
|
||||
<p class="font-bold mb-1">
|
||||
Avatar
|
||||
</p>
|
||||
<p class="text-gray-500">
|
||||
Default profile picture
|
||||
</p>
|
||||
<div class="flex items-center gap-6">
|
||||
<% if current_user.avatar.present? %>
|
||||
<p class="flex-none">
|
||||
<%= image_tag "data:image/jpeg;base64,#{current_user.avatar}", class: "h-24 w-24 rounded-lg" %>
|
||||
</p>
|
||||
<% end %>
|
||||
<div class="grow">
|
||||
<p class="mb-2">
|
||||
<%= f.file_field :avatar, class: "" %>
|
||||
<p class="text-sm text-gray-500">
|
||||
JPEG or PNG image, not larger than 1 megabyte
|
||||
</p>
|
||||
<% if @validation_errors.present? && @validation_errors[:avatar].present? %>
|
||||
<p class="error-msg mb-2"><%= @validation_errors[:avatar].first %></p>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<p class="mt-8 pt-6 border-t border-gray-200 text-right">
|
||||
<%= f.submit 'Save', class: "btn-md btn-blue w-full md:w-auto" %>
|
||||
</p>
|
||||
|
||||
25
app/views/settings/_remotestorage.html.erb
Normal file
@@ -0,0 +1,25 @@
|
||||
<%= form_for @user, url: setting_path(:remotestorage), html: { :method => :put } do |f| %>
|
||||
<section>
|
||||
<h3>Notifications</h3>
|
||||
<ul role="list">
|
||||
<%= render FormElements::FieldsetComponent.new(
|
||||
positioning: :horizontal,
|
||||
title: "New connection authorized",
|
||||
description: "Notify me when my storage is connected to a new app"
|
||||
) do %>
|
||||
<% f.fields_for :preferences do |p| %>
|
||||
<%= p.select :remotestorage_notify_auth_created, options_for_select([
|
||||
["off", "disabled"],
|
||||
["Chat (Jabber)", "xmpp"], # TODO make DRY, check for XMPP enabled
|
||||
["E-Mail", "email"]
|
||||
], selected: @user.preferences[:remotestorage_notify_auth_created]) %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</ul>
|
||||
</section>
|
||||
<section>
|
||||
<p class="pt-6 border-t border-gray-200 text-right">
|
||||
<%= f.submit 'Save', class: "btn-md btn-blue w-full md:w-auto" %>
|
||||
</p>
|
||||
</section>
|
||||
<% end %>
|
||||
@@ -10,5 +10,9 @@
|
||||
<%= link_to "Lightning", admin_lightning_path,
|
||||
class: main_nav_class(@current_section, :lightning) %>
|
||||
<% end %>
|
||||
<% if Setting.remotestorage_enabled? %>
|
||||
<%= link_to "Apps", admin_app_catalog_web_apps_path,
|
||||
class: main_nav_class(@current_section, :app_catalog) %>
|
||||
<% end %>
|
||||
<%= link_to "Settings", admin_settings_registrations_path,
|
||||
class: main_nav_class(@current_section, :settings) %>
|
||||
|
||||
10
app/views/shared/_admin_sidenav_app_catalog.html.erb
Normal file
@@ -0,0 +1,10 @@
|
||||
<%= render SidenavLinkComponent.new(
|
||||
name: "Web Apps", path: admin_app_catalog_web_apps_path, icon: "globe",
|
||||
active: current_page?(admin_app_catalog_web_apps_path)
|
||||
) %>
|
||||
<%= render SidenavLinkComponent.new(
|
||||
name: "Recommended Apps", path: "#", icon: "star", disabled: true
|
||||
) %>
|
||||
<%= render SidenavLinkComponent.new(
|
||||
name: "OAuth Apps", path: "#", icon: "key", disabled: true
|
||||
) %>
|
||||
@@ -1,63 +1,70 @@
|
||||
<%= render SidenavLinkComponent.new(
|
||||
level: 2,
|
||||
name: "BTCPay",
|
||||
path: admin_settings_services_path(params: { s: "btcpay" }),
|
||||
text_icon: Setting.btcpay_enabled? ? "◉" : "○",
|
||||
active: current_page?(admin_settings_services_path(params: { s: "btcpay" })),
|
||||
) %>
|
||||
<%= render SidenavLinkComponent.new(
|
||||
level: 2,
|
||||
name: "Discourse",
|
||||
path: admin_settings_services_path(params: { s: "discourse" }),
|
||||
icon: Setting.discourse_enabled? ? "check" : "x",
|
||||
text_icon: Setting.discourse_enabled? ? "◉" : "○",
|
||||
active: current_page?(admin_settings_services_path(params: { s: "discourse" })),
|
||||
) %>
|
||||
<%= render SidenavLinkComponent.new(
|
||||
level: 2,
|
||||
name: "Drone CI",
|
||||
path: admin_settings_services_path(params: { s: "droneci" }),
|
||||
icon: Setting.droneci_enabled? ? "check" : "x",
|
||||
text_icon: Setting.droneci_enabled? ? "◉" : "○",
|
||||
active: current_page?(admin_settings_services_path(params: { s: "droneci" })),
|
||||
) %>
|
||||
<%= render SidenavLinkComponent.new(
|
||||
level: 2,
|
||||
name: "ejabberd",
|
||||
path: admin_settings_services_path(params: { s: "ejabberd" }),
|
||||
icon: Setting.ejabberd_enabled? ? "check" : "x",
|
||||
text_icon: Setting.ejabberd_enabled? ? "◉" : "○",
|
||||
active: current_page?(admin_settings_services_path(params: { s: "ejabberd" })),
|
||||
) %>
|
||||
<%= render SidenavLinkComponent.new(
|
||||
level: 2,
|
||||
name: "Gitea",
|
||||
path: admin_settings_services_path(params: { s: "gitea" }),
|
||||
icon: Setting.gitea_enabled? ? "check" : "x",
|
||||
text_icon: Setting.gitea_enabled? ? "◉" : "○",
|
||||
active: current_page?(admin_settings_services_path(params: { s: "gitea" })),
|
||||
) %>
|
||||
<%= render SidenavLinkComponent.new(
|
||||
level: 2,
|
||||
name: "LNDHub",
|
||||
path: admin_settings_services_path(params: { s: "lndhub" }),
|
||||
icon: Setting.lndhub_enabled? ? "check" : "x",
|
||||
text_icon: Setting.lndhub_enabled? ? "◉" : "○",
|
||||
active: current_page?(admin_settings_services_path(params: { s: "lndhub" })),
|
||||
) %>
|
||||
<%= render SidenavLinkComponent.new(
|
||||
level: 2,
|
||||
name: "Mastodon",
|
||||
path: admin_settings_services_path(params: { s: "mastodon" }),
|
||||
icon: Setting.mastodon_enabled? ? "check" : "x",
|
||||
text_icon: Setting.mastodon_enabled? ? "◉" : "○",
|
||||
active: current_page?(admin_settings_services_path(params: { s: "mastodon" })),
|
||||
) %>
|
||||
<%= render SidenavLinkComponent.new(
|
||||
level: 2,
|
||||
name: "MediaWiki",
|
||||
path: admin_settings_services_path(params: { s: "mediawiki" }),
|
||||
icon: Setting.mediawiki_enabled? ? "check" : "x",
|
||||
text_icon: Setting.mediawiki_enabled? ? "◉" : "○",
|
||||
active: current_page?(admin_settings_services_path(params: { s: "mediawiki" })),
|
||||
) %>
|
||||
<%= render SidenavLinkComponent.new(
|
||||
level: 2,
|
||||
name: "Nostr",
|
||||
path: admin_settings_services_path(params: { s: "nostr" }),
|
||||
icon: Setting.nostr_enabled? ? "check" : "x",
|
||||
text_icon: Setting.nostr_enabled? ? "◉" : "○",
|
||||
active: current_page?(admin_settings_services_path(params: { s: "nostr" })),
|
||||
) %>
|
||||
<%= render SidenavLinkComponent.new(
|
||||
level: 2,
|
||||
name: "RemoteStorage",
|
||||
path: admin_settings_services_path(params: { s: "remotestorage" }),
|
||||
icon: Setting.remotestorage_enabled? ? "check" : "x",
|
||||
text_icon: Setting.remotestorage_enabled? ? "◉" : "○",
|
||||
active: current_page?(admin_settings_services_path(params: { s: "remotestorage" })),
|
||||
) %>
|
||||
|
||||
@@ -18,6 +18,12 @@
|
||||
active: @settings_section.to_s == "lightning"
|
||||
) %>
|
||||
<% end %>
|
||||
<% if Setting.remotestorage_enabled %>
|
||||
<%= render SidenavLinkComponent.new(
|
||||
name: "Storage", path: setting_path(:remotestorage), icon: "remotestorage",
|
||||
active: @settings_section.to_s == "remotestorage"
|
||||
) %>
|
||||
<% end %>
|
||||
<% if Setting.nostr_enabled %>
|
||||
<%= render SidenavLinkComponent.new(
|
||||
name: "Experiments", path: setting_path(:experiments), icon: "science",
|
||||
|
||||
4
ci/Dockerfile
Normal file
@@ -0,0 +1,4 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
FROM guildeducation/rails:2.7.2-14.20.0
|
||||
|
||||
RUN apt-get update -qq && apt-get install -y --no-install-recommends ldap-utils libvips
|
||||
@@ -5,7 +5,7 @@ require "rails"
|
||||
require "active_model/railtie"
|
||||
require "active_job/railtie"
|
||||
require "active_record/railtie"
|
||||
# require "active_storage/engine"
|
||||
require "active_storage/engine"
|
||||
require "action_controller/railtie"
|
||||
require "action_mailer/railtie"
|
||||
require "action_mailbox/engine"
|
||||
|
||||
@@ -1 +1 @@
|
||||
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==
|
||||
tmI5vm7qZhaigr52jEBVWkRdj+EE+9OmPh3vWXC7kA/OHuuucpr7SodychuMkQDPLM0BLk88LFsqvRIR+mqnLWpRC+P9aeUFE6ohxSWzcAd7Y4sgxUD8zpCRPndrwTw0hxXXj1WZSYeWn4BoAB34aV+gYen2MajZF3a95hJGtS5yjgWxvLVkQQKqRDfykkfX6fCS0BPo5X7sT7m4xwCATD/D4219wajm5W3TIdkriHtwt28ZLspaRWA5e0UkzKf8+/Gaj2CrW7UWcvew8R93zQ5RA2/Sp3sDTVN+kLz9I9Q095lQC0ywCAEFYHeKmc2tjrzqRaAAWu06xmWLqGIg21G+A/UU9lUJOkIpxQACWoOfS2IoXR1nXhgXMopkz3aCBXDxKw554v4H2QyOceOsuRf2C685ibMqzQkKMmJ4tcbiOJL77DUc08JTjB8Dq4Ohr8sMzXbV/hATevjYoRP0XarLekqhLv90ZLuIVY16DwB0CzACeNBKeKbeLqJF51upRRWgi+gTbYpV04yUwnXdyssF8mydWocgihrTryBi8F6PsuhBGcaYdP+0yibnGxDCC4x2rupbBfMj2OIX7pYzgtIHB3Eo954Y+bCoggqbE/Qrb9VVXNMgtKgLt8EGWU2tg6wl9QicitIq87uLDAade93zTn6rmcKPywjMDo6jbVIs653ZdUhiKdHGdpnJccbgQ/iLSPB1umNnCeaEX5jM+K9zBvl7ZMCdSk1YIQ==--ekKumqLiSlVJNwMe--K/ecXmmMT1x+WnIXMbHBDw==
|
||||
@@ -1,2 +1,3 @@
|
||||
lightning_notify_sats_received: disabled # or xmpp, email
|
||||
remotestorage_notify_auth_created: email # or xmpp, email
|
||||
xmpp_exchange_contacts_with_invitees: true
|
||||
|
||||
@@ -70,4 +70,10 @@ Rails.application.configure do
|
||||
|
||||
# Allow requests from any IP
|
||||
config.web_console.whiny_requests = false
|
||||
|
||||
if ENV["S3_ENABLED"]
|
||||
config.active_storage.service = :s3
|
||||
else
|
||||
config.active_storage.service = :local
|
||||
end
|
||||
end
|
||||
|
||||
@@ -110,6 +110,12 @@ Rails.application.configure do
|
||||
# Set this to true and configure the email server for immediate delivery to raise delivery errors.
|
||||
config.action_mailer.raise_delivery_errors = true
|
||||
|
||||
if ENV["S3_ENABLED"]
|
||||
config.active_storage.service = :s3
|
||||
else
|
||||
config.active_storage.service = :local
|
||||
end
|
||||
|
||||
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
|
||||
# the I18n.default_locale when a translation cannot be found).
|
||||
config.i18n.fallbacks = true
|
||||
|
||||
@@ -51,4 +51,11 @@ Rails.application.configure do
|
||||
}
|
||||
|
||||
config.active_job.queue_adapter = :test
|
||||
|
||||
if ENV["S3_ENABLED"]
|
||||
config.active_storage.service = :s3
|
||||
else
|
||||
# Store attachments on the local disk (in ./tmp)
|
||||
config.active_storage.service = :test
|
||||
end
|
||||
end
|
||||
|
||||
@@ -19,10 +19,10 @@ Rails.application.routes.draw do
|
||||
resources :invitations, only: ['index', 'show', 'create', 'destroy']
|
||||
|
||||
namespace :services do
|
||||
get 'storage', to: 'remotestorage#dashboard'
|
||||
|
||||
resource :chat, only: [:show], controller: 'chat'
|
||||
|
||||
resource :mastodon, only: [:show], controller: 'mastodon'
|
||||
|
||||
resources :lightning, only: [:index] do
|
||||
collection do
|
||||
get 'transactions'
|
||||
@@ -30,7 +30,14 @@ Rails.application.routes.draw do
|
||||
end
|
||||
end
|
||||
|
||||
resource :mastodon, only: [:show], controller: 'mastodon'
|
||||
resource :storage, controller: 'remotestorage', only: [:show] do
|
||||
resources :rs_auths, only: [:destroy] do
|
||||
member do
|
||||
get :revoke, to: 'rs_auths#destroy'
|
||||
get :launch_app
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
resources :settings, param: 'section', only: ['index', 'show', 'update'] do
|
||||
@@ -54,16 +61,22 @@ Rails.application.routes.draw do
|
||||
post 'webhooks/lndhub', to: 'webhooks#lndhub'
|
||||
|
||||
namespace :api do
|
||||
get 'kredits/onchain_btc_balance', to: 'kredits#onchain_btc_balance'
|
||||
get 'btcpay/onchain_btc_balance', to: 'btcpay#onchain_btc_balance'
|
||||
get 'btcpay/lightning_btc_balance', to: 'btcpay#lightning_btc_balance'
|
||||
end
|
||||
|
||||
namespace :admin do
|
||||
root to: 'dashboard#index'
|
||||
|
||||
resources 'users', param: 'address', only: ['index', 'show'], constraints: { address: /.*/ }
|
||||
get 'invitations', to: 'invitations#index'
|
||||
resources :donations
|
||||
get 'lightning', to: 'lightning#index'
|
||||
|
||||
namespace :app_catalog do
|
||||
resources 'web_apps', only: ['index']
|
||||
end
|
||||
|
||||
namespace :settings do
|
||||
resources 'registrations', only: ['index', 'create']
|
||||
resources 'services', only: ['index', 'create']
|
||||
@@ -72,9 +85,8 @@ Rails.application.routes.draw do
|
||||
|
||||
namespace :rs do
|
||||
resource :oauth, only: [:new, :create], path_names: {
|
||||
new: ':useraddress', create: ':useraddress'
|
||||
}, controller: 'oauth', constraints: { useraddress: /[^\/]+/}
|
||||
get 'oauth/token/:id/launch_app' => 'oauth#launch_app', as: :launch_app
|
||||
new: ':username', create: ':username'
|
||||
}, controller: 'oauth'
|
||||
end
|
||||
|
||||
get '.well-known/webfinger', to: 'webfinger#show'
|
||||
@@ -94,4 +106,8 @@ Rails.application.routes.draw do
|
||||
end
|
||||
|
||||
root to: 'dashboard#index'
|
||||
|
||||
direct :s3_image do |blob|
|
||||
File.join(ENV['S3_ALIAS_HOST'], blob.key)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,7 +1,17 @@
|
||||
local:
|
||||
service: Disk
|
||||
root: <%= Rails.root.join("storage") %>
|
||||
|
||||
test:
|
||||
service: Disk
|
||||
root: <%= Rails.root.join("tmp/storage") %>
|
||||
|
||||
local:
|
||||
service: Disk
|
||||
root: <%= Rails.root.join("storage") %>
|
||||
<% if ENV["S3_ENABLED"] %>
|
||||
s3:
|
||||
service: S3
|
||||
endpoint: <%= ENV["S3_ENDPOINT"] %>
|
||||
region: <%= ENV["S3_REGION"] %>
|
||||
bucket: <%= ENV["S3_BUCKET"] %>
|
||||
access_key_id: <%= ENV["S3_ACCESS_KEY"] %>
|
||||
secret_access_key: <%= ENV["S3_SECRET_KEY"] %>
|
||||
<% end %>
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
# This migration comes from active_storage (originally 20170806125915)
|
||||
class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
# Use Active Record's configured type for primary and foreign keys
|
||||
primary_key_type, foreign_key_type = primary_and_foreign_key_types
|
||||
|
||||
create_table :active_storage_blobs, id: primary_key_type do |t|
|
||||
t.string :key, null: false
|
||||
t.string :filename, null: false
|
||||
t.string :content_type
|
||||
t.text :metadata
|
||||
t.string :service_name, null: false
|
||||
t.bigint :byte_size, null: false
|
||||
t.string :checksum
|
||||
|
||||
if connection.supports_datetime_with_precision?
|
||||
t.datetime :created_at, precision: 6, null: false
|
||||
else
|
||||
t.datetime :created_at, null: false
|
||||
end
|
||||
|
||||
t.index [ :key ], unique: true
|
||||
end
|
||||
|
||||
create_table :active_storage_attachments, id: primary_key_type do |t|
|
||||
t.string :name, null: false
|
||||
t.references :record, null: false, polymorphic: true, index: false, type: foreign_key_type
|
||||
t.references :blob, null: false, type: foreign_key_type
|
||||
|
||||
if connection.supports_datetime_with_precision?
|
||||
t.datetime :created_at, precision: 6, null: false
|
||||
else
|
||||
t.datetime :created_at, null: false
|
||||
end
|
||||
|
||||
t.index [ :record_type, :record_id, :name, :blob_id ], name: :index_active_storage_attachments_uniqueness, unique: true
|
||||
t.foreign_key :active_storage_blobs, column: :blob_id
|
||||
end
|
||||
|
||||
create_table :active_storage_variant_records, id: primary_key_type do |t|
|
||||
t.belongs_to :blob, null: false, index: false, type: foreign_key_type
|
||||
t.string :variation_digest, null: false
|
||||
|
||||
t.index [ :blob_id, :variation_digest ], name: :index_active_storage_variant_records_uniqueness, unique: true
|
||||
t.foreign_key :active_storage_blobs, column: :blob_id
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def primary_and_foreign_key_types
|
||||
config = Rails.configuration.generators
|
||||
setting = config.options[config.orm][:primary_key_type]
|
||||
primary_key_type = setting || :primary_key
|
||||
foreign_key_type = setting || :bigint
|
||||
[primary_key_type, foreign_key_type]
|
||||
end
|
||||
end
|
||||
11
db/migrate/20231019125135_create_app_catalog_web_apps.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
class CreateAppCatalogWebApps < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
create_table :app_catalog_web_apps do |t|
|
||||
t.string :url
|
||||
t.string :name
|
||||
t.text :metadata
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,7 @@
|
||||
class AddWebAppIdToRemoteStorageAuthorizations < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
add_reference :remote_storage_authorizations, :web_app, foreign_key: {
|
||||
to_table: :app_catalog_web_apps
|
||||
}
|
||||
end
|
||||
end
|
||||
43
db/schema.rb
@@ -10,7 +10,43 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.0].define(version: 2023_05_23_120753) do
|
||||
ActiveRecord::Schema[7.0].define(version: 2023_10_24_104909) do
|
||||
create_table "active_storage_attachments", force: :cascade do |t|
|
||||
t.string "name", null: false
|
||||
t.string "record_type", null: false
|
||||
t.bigint "record_id", null: false
|
||||
t.bigint "blob_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
|
||||
t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
|
||||
end
|
||||
|
||||
create_table "active_storage_blobs", force: :cascade do |t|
|
||||
t.string "key", null: false
|
||||
t.string "filename", null: false
|
||||
t.string "content_type"
|
||||
t.text "metadata"
|
||||
t.string "service_name", null: false
|
||||
t.bigint "byte_size", null: false
|
||||
t.string "checksum"
|
||||
t.datetime "created_at", null: false
|
||||
t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
|
||||
end
|
||||
|
||||
create_table "active_storage_variant_records", force: :cascade do |t|
|
||||
t.bigint "blob_id", null: false
|
||||
t.string "variation_digest", null: false
|
||||
t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true
|
||||
end
|
||||
|
||||
create_table "app_catalog_web_apps", force: :cascade do |t|
|
||||
t.string "url"
|
||||
t.string "name"
|
||||
t.text "metadata"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
end
|
||||
|
||||
create_table "donations", force: :cascade do |t|
|
||||
t.integer "user_id"
|
||||
t.integer "amount_sats"
|
||||
@@ -60,8 +96,10 @@ ActiveRecord::Schema[7.0].define(version: 2023_05_23_120753) do
|
||||
t.datetime "expire_at"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.integer "web_app_id"
|
||||
t.index ["permissions"], name: "index_remote_storage_authorizations_on_permissions"
|
||||
t.index ["user_id"], name: "index_remote_storage_authorizations_on_user_id"
|
||||
t.index ["web_app_id"], name: "index_remote_storage_authorizations_on_web_app_id"
|
||||
end
|
||||
|
||||
create_table "settings", force: :cascade do |t|
|
||||
@@ -94,5 +132,8 @@ ActiveRecord::Schema[7.0].define(version: 2023_05_23_120753) do
|
||||
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
|
||||
end
|
||||
|
||||
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
|
||||
add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
|
||||
add_foreign_key "remote_storage_authorizations", "app_catalog_web_apps", column: "web_app_id"
|
||||
add_foreign_key "remote_storage_authorizations", "users"
|
||||
end
|
||||
|
||||
@@ -13,6 +13,7 @@ Sidekiq::Testing.inline! do
|
||||
35.times do |n|
|
||||
username = Faker::Name.unique.first_name.downcase
|
||||
email = Faker::Internet.unique.email
|
||||
next if username.length < 3
|
||||
|
||||
CreateAccount.call(
|
||||
username: username, domain: "kosmos.org", email: email,
|
||||
|
||||
@@ -37,12 +37,13 @@ services:
|
||||
environment:
|
||||
RAILS_ENV: development
|
||||
PRIMARY_DOMAIN: kosmos.org
|
||||
REDIS_URL: redis://redis:6379/0
|
||||
RS_REDIS_URL: redis://redis:6379/1
|
||||
LDAP_HOST: ldap
|
||||
LDAP_PORT: 3389
|
||||
LDAP_ADMIN_PASSWORD: passthebutter
|
||||
LDAP_USE_TLS: "false"
|
||||
REDIS_URL: redis://redis:6379/0
|
||||
RS_REDIS_URL: redis://redis:6379/1
|
||||
RS_STORAGE_URL: "http://localhost:4567"
|
||||
depends_on:
|
||||
- ldap
|
||||
- redis
|
||||
@@ -57,18 +58,51 @@ services:
|
||||
environment:
|
||||
RAILS_ENV: development
|
||||
PRIMARY_DOMAIN: kosmos.org
|
||||
REDIS_URL: redis://redis:6379/0
|
||||
RS_REDIS_URL: redis://redis:6379/1
|
||||
LDAP_HOST: ldap
|
||||
LDAP_PORT: 3389
|
||||
LDAP_ADMIN_PASSWORD: passthebutter
|
||||
LDAP_USE_TLS: "false"
|
||||
LAUNCHY_DRY_RUN: true
|
||||
BROWSER: /dev/null
|
||||
REDIS_URL: redis://redis:6379/0
|
||||
RS_REDIS_URL: redis://redis:6379/1
|
||||
RS_STORAGE_URL: "http://localhost:4567"
|
||||
depends_on:
|
||||
- ldap
|
||||
- redis
|
||||
|
||||
minio:
|
||||
image: quay.io/minio/minio:latest
|
||||
command: "server /data --console-address ':9001'"
|
||||
networks:
|
||||
- external_network
|
||||
- internal_network
|
||||
ports:
|
||||
- "9000:9000"
|
||||
- "9001:9001"
|
||||
volumes:
|
||||
- ./tmp/minio:/data
|
||||
|
||||
liquor-cabinet:
|
||||
image: gitea.kosmos.org/5apps/liquor-cabinet:2.0.0-beta.2
|
||||
networks:
|
||||
- external_network
|
||||
- internal_network
|
||||
ports:
|
||||
- "4567:4567"
|
||||
environment:
|
||||
RACK_ENV: staging
|
||||
REDIS_HOST: redis
|
||||
REDIS_PORT: 6379
|
||||
REDIS_DB: 1
|
||||
S3_ENDPOINT: http://minio:9000
|
||||
S3_ACCESS_KEY: dev-key
|
||||
S3_SECRET_KEY: 123456789
|
||||
S3_BUCKET: remotestorage
|
||||
depends_on:
|
||||
- minio
|
||||
- redis
|
||||
|
||||
# phpldapadmin:
|
||||
# image: osixia/phpldapadmin:0.9.0
|
||||
# ports:
|
||||
|
||||
@@ -3,7 +3,11 @@ require 'rails_helper'
|
||||
RSpec.describe Rs::OauthController, type: :controller do
|
||||
let(:user) { create :user }
|
||||
|
||||
describe "GET /rs/oauth/:useraddress" do
|
||||
before do
|
||||
allow_any_instance_of(AppCatalog::WebApp).to receive(:update_metadata).and_return(true)
|
||||
end
|
||||
|
||||
describe "GET /rs/oauth/:username" do
|
||||
context "when user is signed in" do
|
||||
before do
|
||||
sign_in user
|
||||
@@ -14,7 +18,7 @@ RSpec.describe Rs::OauthController, type: :controller do
|
||||
|
||||
before do
|
||||
get :new, params: {
|
||||
useraddress: other_user.address,
|
||||
username: other_user.cn,
|
||||
redirect_uri: "https://example.com",
|
||||
client_id: "example.com",
|
||||
scope: "examples"
|
||||
@@ -22,7 +26,7 @@ RSpec.describe Rs::OauthController, type: :controller do
|
||||
end
|
||||
|
||||
it "logs out the users and repeats the request" do
|
||||
url = new_rs_oauth_url other_user.address,
|
||||
url = new_rs_oauth_url other_user.cn,
|
||||
redirect_uri: "https://example.com",
|
||||
client_id: "example.com",
|
||||
scope: "examples"
|
||||
@@ -34,7 +38,7 @@ RSpec.describe Rs::OauthController, type: :controller do
|
||||
context "when no valid token exists" do
|
||||
before do
|
||||
get :new, params: {
|
||||
useraddress: user.address,
|
||||
username: user.cn,
|
||||
redirect_uri: "https://example.com",
|
||||
client_id: "example.com",
|
||||
scope: "documents,[photos], contacts:rw videos:r tasks/work/:r",
|
||||
@@ -61,7 +65,7 @@ RSpec.describe Rs::OauthController, type: :controller do
|
||||
context "no redirect_uri" do
|
||||
before do
|
||||
get :new, params: {
|
||||
useraddress: user.address,
|
||||
username: user.cn,
|
||||
scope: "documents,[photos], contacts:rw videos:r tasks/work/:r",
|
||||
client_id: "https://example.com"
|
||||
}
|
||||
@@ -75,7 +79,7 @@ RSpec.describe Rs::OauthController, type: :controller do
|
||||
context "no client_id" do
|
||||
before do
|
||||
get :new, params: {
|
||||
useraddress: user.address,
|
||||
username: user.cn,
|
||||
scope: "documents,[photos], contacts:rw videos:r tasks/work/:r",
|
||||
redirect_uri: "https://example.com"
|
||||
}
|
||||
@@ -89,7 +93,7 @@ RSpec.describe Rs::OauthController, type: :controller do
|
||||
context "different host for client_id and redirect_uri" do
|
||||
before do
|
||||
get :new, params: {
|
||||
useraddress: user.address,
|
||||
username: user.cn,
|
||||
scope: "documents,[photos], contacts:rw videos:r tasks/work/:r",
|
||||
redirect_uri: "https://example.com/foobar",
|
||||
client_id: "https://google.com"
|
||||
@@ -116,7 +120,7 @@ RSpec.describe Rs::OauthController, type: :controller do
|
||||
context "with same host for client_id and redirect_uri" do
|
||||
before do
|
||||
get :new, params: {
|
||||
useraddress: user.address,
|
||||
username: user.cn,
|
||||
scope: "documents,[photos], contacts:rw videos:r tasks/work/:r",
|
||||
redirect_uri: "https://example.com",
|
||||
client_id: "https://example.com"
|
||||
@@ -131,7 +135,7 @@ RSpec.describe Rs::OauthController, type: :controller do
|
||||
context "with different host for client_id and redirect_uri" do
|
||||
before do
|
||||
get :new, params: {
|
||||
useraddress: user.address,
|
||||
username: user.cn,
|
||||
scope: "documents,[photos], contacts:rw videos:r tasks/work/:r",
|
||||
redirect_uri: "https://app.example.com",
|
||||
client_id: "https://example.com"
|
||||
@@ -146,7 +150,7 @@ RSpec.describe Rs::OauthController, type: :controller do
|
||||
context "with different redirect_uri" do
|
||||
before do
|
||||
get :new, params: {
|
||||
useraddress: user.address,
|
||||
username: user.cn,
|
||||
scope: "documents,[photos], contacts:rw videos:r tasks/work/:r",
|
||||
redirect_uri: "https://example.com/a_new_route",
|
||||
client_id: "https://example.com"
|
||||
@@ -161,7 +165,7 @@ RSpec.describe Rs::OauthController, type: :controller do
|
||||
context "with state param given" do
|
||||
before do
|
||||
get :new, params: {
|
||||
useraddress: user.address,
|
||||
username: user.cn,
|
||||
scope: "documents,[photos], contacts:rw videos:r tasks/work/:r",
|
||||
redirect_uri: "https://example.com",
|
||||
client_id: "https://example.com",
|
||||
@@ -178,7 +182,7 @@ RSpec.describe Rs::OauthController, type: :controller do
|
||||
context "no scope" do
|
||||
before do
|
||||
get :new, params: {
|
||||
useraddress: user.address,
|
||||
username: user.cn,
|
||||
redirect_uri: "https://example.com",
|
||||
client_id: "https://example.com",
|
||||
state: "foobar123"
|
||||
@@ -193,7 +197,7 @@ RSpec.describe Rs::OauthController, type: :controller do
|
||||
context "empty scope" do
|
||||
before do
|
||||
get :new, params: {
|
||||
useraddress: user.address,
|
||||
username: user.cn,
|
||||
scope: "",
|
||||
redirect_uri: "https://example.com",
|
||||
client_id: "https://example.com",
|
||||
@@ -210,7 +214,7 @@ RSpec.describe Rs::OauthController, type: :controller do
|
||||
context "when user is not signed in" do
|
||||
it "redirects to the signin page with username pre-filled" do
|
||||
get :new, params: {
|
||||
useraddress: user.address,
|
||||
username: user.cn,
|
||||
scope: "documents,photos",
|
||||
redirect_uri: "https://example.com"
|
||||
}
|
||||
@@ -227,7 +231,7 @@ RSpec.describe Rs::OauthController, type: :controller do
|
||||
describe "full" do
|
||||
before do
|
||||
get :new, params: {
|
||||
useraddress: user.address,
|
||||
username: user.cn,
|
||||
scope: "*:rw",
|
||||
redirect_uri: "https://example.com",
|
||||
client_id: "example.com"
|
||||
@@ -243,7 +247,7 @@ RSpec.describe Rs::OauthController, type: :controller do
|
||||
describe "read-only" do
|
||||
before do
|
||||
get :new, params: {
|
||||
useraddress: user.address,
|
||||
username: user.cn,
|
||||
scope: "*:r",
|
||||
redirect_uri: "https://example.com",
|
||||
client_id: "example.com"
|
||||
@@ -258,7 +262,7 @@ RSpec.describe Rs::OauthController, type: :controller do
|
||||
end
|
||||
end
|
||||
|
||||
describe "POST /rs/oauth/:useraddress" do
|
||||
describe "POST /rs/oauth/:username" do
|
||||
context "when user is signed in" do
|
||||
before do
|
||||
sign_in user
|
||||
@@ -433,33 +437,4 @@ RSpec.describe Rs::OauthController, type: :controller do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /rs/oauth/token/:id/launch_app" do
|
||||
context "when user is signed in" do
|
||||
before do
|
||||
sign_in user
|
||||
end
|
||||
|
||||
context "token exists" do
|
||||
before do
|
||||
@auth = user.remote_storage_authorizations.create!(
|
||||
permissions: %w(documents), client_id: "app.example.com",
|
||||
redirect_uri: "https://app.example.com",
|
||||
expire_at: 2.days.from_now
|
||||
)
|
||||
|
||||
get :launch_app, params: { id: @auth.id }
|
||||
end
|
||||
|
||||
after do
|
||||
@auth.destroy
|
||||
end
|
||||
|
||||
it "redirects to the given URL with the correct RS URL fragment params" do
|
||||
launch_url = "https://app.example.com#remotestorage=#{user.address}&access_token=#{@auth.token}"
|
||||
expect(response).to redirect_to(launch_url)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
39
spec/controllers/services/rs_auths_controller_spec.rb
Normal file
@@ -0,0 +1,39 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Services::RsAuthsController, type: :controller do
|
||||
let(:user) { create :user }
|
||||
|
||||
before do
|
||||
allow_any_instance_of(AppCatalog::WebApp).to receive(:update_metadata).and_return(true)
|
||||
allow_any_instance_of(Flipper).to receive(:enabled?).and_return(true)
|
||||
end
|
||||
|
||||
describe "GET /services/storage/rs_auths/:id/launch_app" do
|
||||
context "when user is signed in" do
|
||||
before do
|
||||
sign_in user
|
||||
end
|
||||
|
||||
context "token exists" do
|
||||
before do
|
||||
@auth = user.remote_storage_authorizations.create!(
|
||||
permissions: %w(documents), client_id: "app.example.com",
|
||||
redirect_uri: "https://app.example.com",
|
||||
expire_at: 2.days.from_now
|
||||
)
|
||||
|
||||
get :launch_app, params: { id: @auth.id }
|
||||
end
|
||||
|
||||
after do
|
||||
@auth.destroy
|
||||
end
|
||||
|
||||
it "redirects to the given URL with the correct RS URL fragment params" do
|
||||
launch_url = "https://app.example.com#remotestorage=#{user.address}&access_token=#{@auth.token}"
|
||||
expect(response).to redirect_to(launch_url)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
6
spec/factories/app_catalog/web_apps.rb
Normal file
@@ -0,0 +1,6 @@
|
||||
FactoryBot.define do
|
||||
factory :web_app, class: 'AppCatalog::WebApp' do
|
||||
url { "https://myfavoritedrinks.remotestorage.io/" }
|
||||
name { "My Favorite Drinks" }
|
||||
end
|
||||
end
|
||||
@@ -1,9 +1,10 @@
|
||||
FactoryBot.define do
|
||||
factory :remote_storage_authorization do
|
||||
permissions { ["documents:rw"] }
|
||||
client_id { "some-fancy-app" }
|
||||
redirect_uri { "https://example.com/some-fancy-app" }
|
||||
client_id { "app.example.com" }
|
||||
redirect_uri { "https://app.example.com" }
|
||||
app_name { "Fancy App" }
|
||||
expire_at { nil }
|
||||
expire_at { 1.month.from_now }
|
||||
web_app
|
||||
end
|
||||
end
|
||||
|
||||
@@ -23,7 +23,7 @@ RSpec.describe 'Admin/global settings', type: :feature do
|
||||
scenario "Opening service settings shows page for first service" do
|
||||
visit admin_settings_services_path
|
||||
|
||||
expect(current_url).to eq(admin_settings_services_url(params: { s: "discourse" }))
|
||||
expect(current_url).to eq(admin_settings_services_url(params: { s: "btcpay" }))
|
||||
end
|
||||
|
||||
scenario "View service settings" do
|
||||
|
||||
@@ -10,7 +10,7 @@ RSpec.describe 'remoteStorage OAuth Dialog', type: :feature do
|
||||
|
||||
context "with normal permissions" do
|
||||
before do
|
||||
visit new_rs_oauth_path(useraddress: user.address,
|
||||
visit new_rs_oauth_path(username: user.cn,
|
||||
redirect_uri: "http://example.com",
|
||||
client_id: "http://example.com",
|
||||
scope: "documents,[photos], contacts:r")
|
||||
@@ -36,7 +36,7 @@ RSpec.describe 'remoteStorage OAuth Dialog', type: :feature do
|
||||
context "root access" do
|
||||
context "full" do
|
||||
before do
|
||||
visit new_rs_oauth_path(useraddress: user.address,
|
||||
visit new_rs_oauth_path(username: user.cn,
|
||||
redirect_uri: "http://example.com",
|
||||
client_id: "http://example.com",
|
||||
scope: ":rw")
|
||||
@@ -54,13 +54,32 @@ RSpec.describe 'remoteStorage OAuth Dialog', type: :feature do
|
||||
context "when signed out" do
|
||||
let(:user) { create :user }
|
||||
|
||||
before do
|
||||
allow_any_instance_of(User).to receive(:valid_ldap_authentication?)
|
||||
.with(user.password).and_return(true)
|
||||
end
|
||||
|
||||
it "prefills the username field in the signin form" do
|
||||
visit new_rs_oauth_path(useraddress: user.address,
|
||||
visit new_rs_oauth_path(username: user.cn,
|
||||
redirect_uri: "http://example.com",
|
||||
client_id: "http://example.com",
|
||||
scope: "documents,[photos], contacts:r")
|
||||
|
||||
expect(find("#user_cn").value).to eq(user.cn)
|
||||
end
|
||||
|
||||
it "redirects to the OAuth dialog after sign-in" do
|
||||
auth_url = new_rs_oauth_url(username: user.cn,
|
||||
redirect_uri: "http://example.com",
|
||||
client_id: "http://example.com",
|
||||
scope: "documents,[photos], contacts:r")
|
||||
visit auth_url
|
||||
|
||||
fill_in "User", with: user.cn
|
||||
fill_in "Password", with: user.password
|
||||
click_button "Log in"
|
||||
|
||||
expect(current_url).to eq(auth_url)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,20 +2,19 @@ require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Profile settings', type: :feature do
|
||||
let(:user) { create :user, cn: "mwahlberg" }
|
||||
let(:avatar_base64) { File.read("#{Rails.root}/spec/fixtures/files/avatar-base64.txt") }
|
||||
|
||||
before do
|
||||
login_as user, :scope => :user
|
||||
allow(user).to receive(:display_name).and_return("Mark")
|
||||
allow_any_instance_of(User).to receive(:dn).and_return("cn=mwahlberg,ou=kosmos.org,cn=users,dc=kosmos,dc=org")
|
||||
allow_any_instance_of(User).to receive(:ldap_entry).and_return({
|
||||
uid: user.cn, ou: user.ou, display_name: "Mark"
|
||||
})
|
||||
allow_any_instance_of(User).to receive(:avatar).and_return(avatar_base64)
|
||||
end
|
||||
|
||||
feature "Update display name" do
|
||||
before do
|
||||
allow(user).to receive(:display_name).and_return("Mark")
|
||||
allow_any_instance_of(User).to receive(:dn).and_return("cn=mwahlberg,ou=kosmos.org,cn=users,dc=kosmos,dc=org")
|
||||
allow_any_instance_of(User).to receive(:ldap_entry).and_return({
|
||||
uid: user.cn, ou: user.ou, display_name: "Mark"
|
||||
})
|
||||
end
|
||||
|
||||
scenario 'fails with validation error' do
|
||||
visit setting_path(:profile)
|
||||
fill_in 'Display name', with: "M"
|
||||
@@ -42,4 +41,59 @@ RSpec.describe 'Profile settings', type: :feature do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
feature "Update avatar" do
|
||||
scenario "fails with validation error for wrong content type" do
|
||||
visit setting_path(:profile)
|
||||
attach_file "Avatar", "#{Rails.root}/spec/fixtures/files/bitcoin.pdf"
|
||||
click_button "Save"
|
||||
|
||||
expect(current_url).to eq(setting_url(:profile))
|
||||
within ".error-msg" do
|
||||
expect(page).to have_content("must be a JPEG or PNG file")
|
||||
end
|
||||
end
|
||||
|
||||
scenario "fails with validation error for file size too large" do
|
||||
visit setting_path(:profile)
|
||||
attach_file "Avatar", "#{Rails.root}/spec/fixtures/files/fsociety-irc.png"
|
||||
click_button "Save"
|
||||
|
||||
expect(current_url).to eq(setting_url(:profile))
|
||||
within ".error-msg" do
|
||||
expect(page).to have_content("file size is too large")
|
||||
end
|
||||
end
|
||||
|
||||
scenario 'works with valid JPG file' do
|
||||
file_path = "#{Rails.root}/spec/fixtures/files/taipei.jpg"
|
||||
|
||||
expect_any_instance_of(LdapManager::UpdateAvatar).to receive(:replace_attribute)
|
||||
.with(user.dn, :jpegPhoto, avatar_base64).and_return(true)
|
||||
|
||||
visit setting_path(:profile)
|
||||
attach_file "Avatar", file_path
|
||||
click_button "Save"
|
||||
|
||||
expect(current_url).to eq(setting_url(:profile))
|
||||
within ".flash-msg" do
|
||||
expect(page).to have_content("Settings saved")
|
||||
end
|
||||
end
|
||||
|
||||
scenario 'works with valid PNG file' do
|
||||
file_path = "#{Rails.root}/spec/fixtures/files/bender.png"
|
||||
|
||||
expect(LdapManager::UpdateAvatar).to receive(:call).and_return(true)
|
||||
|
||||
visit setting_path(:profile)
|
||||
attach_file "Avatar", file_path
|
||||
click_button "Save"
|
||||
|
||||
expect(current_url).to eq(setting_url(:profile))
|
||||
within ".flash-msg" do
|
||||
expect(page).to have_content("Settings saved")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
1
spec/fixtures/files/avatar-base64.txt
vendored
Normal file
BIN
spec/fixtures/files/bender.png
vendored
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
spec/fixtures/files/bitcoin.pdf
vendored
Normal file
BIN
spec/fixtures/files/fsociety-irc.png
vendored
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
spec/fixtures/files/taipei.jpg
vendored
Normal file
|
After Width: | Height: | Size: 228 KiB |
@@ -2,8 +2,13 @@ require 'rails_helper'
|
||||
|
||||
RSpec.describe RemoteStorageExpireAuthorizationJob, type: :job do
|
||||
before do
|
||||
allow_any_instance_of(AppCatalog::WebApp).to(
|
||||
receive(:update_metadata).and_return(true)
|
||||
)
|
||||
|
||||
@user = create :user, cn: "ronald", ou: "kosmos.org"
|
||||
@rs_authorization = create :remote_storage_authorization, user: @user, expire_at: 1.day.ago
|
||||
@rs_authorization = create :remote_storage_authorization,
|
||||
user: @user, expire_at: 1.day.ago
|
||||
end
|
||||
|
||||
after do
|
||||
@@ -20,7 +25,7 @@ RSpec.describe RemoteStorageExpireAuthorizationJob, type: :job do
|
||||
}
|
||||
|
||||
it "removes the RS authorization from redis" do
|
||||
redis_key = "rs:authorizations:#{@user.address}:#{@rs_authorization.token}"
|
||||
redis_key = "authorizations:#{@user.cn}:#{@rs_authorization.token}"
|
||||
expect(redis.keys(redis_key)).to_not be_empty
|
||||
|
||||
perform_enqueued_jobs { job }
|
||||
|
||||
@@ -5,9 +5,13 @@ RSpec.describe RemoteStorageAuthorization, type: :model do
|
||||
|
||||
let(:user) { create :user }
|
||||
|
||||
before do
|
||||
allow_any_instance_of(AppCatalog::WebApp).to receive(:update_metadata).and_return(true)
|
||||
end
|
||||
|
||||
describe "#create" do
|
||||
after(:each) { clear_enqueued_jobs }
|
||||
after(:all) { redis_rs_delete_keys("rs:authorizations:*") }
|
||||
after(:all) { redis_rs_delete_keys("authorizations:*") }
|
||||
|
||||
let(:auth) do
|
||||
user.remote_storage_authorizations.create!(
|
||||
@@ -22,7 +26,7 @@ RSpec.describe RemoteStorageAuthorization, type: :model do
|
||||
end
|
||||
|
||||
it "stores a token in redis" do
|
||||
user_auth_keys = redis_rs.keys("rs:authorizations:#{user.address}:*")
|
||||
user_auth_keys = redis_rs.keys("authorizations:#{user.cn}:*")
|
||||
expect(user_auth_keys.length).to eq(1)
|
||||
|
||||
authorizations = redis_rs.smembers(user_auth_keys.first)
|
||||
@@ -44,7 +48,7 @@ RSpec.describe RemoteStorageAuthorization, type: :model do
|
||||
|
||||
describe "#destroy" do
|
||||
after(:each) { clear_enqueued_jobs }
|
||||
after(:all) { redis_rs_delete_keys("rs:authorizations:*") }
|
||||
after(:all) { redis_rs_delete_keys("authorizations:*") }
|
||||
|
||||
it "removes the token from redis" do
|
||||
auth = user.remote_storage_authorizations.create!(
|
||||
@@ -54,7 +58,7 @@ RSpec.describe RemoteStorageAuthorization, type: :model do
|
||||
)
|
||||
auth.destroy!
|
||||
|
||||
expect(redis_rs.keys("rs:authorizations:#{user.address}:*")).to be_empty
|
||||
expect(redis_rs.keys("authorizations:#{user.address}:*")).to be_empty
|
||||
end
|
||||
|
||||
context "with expiry set" do
|
||||
@@ -72,141 +76,238 @@ RSpec.describe RemoteStorageAuthorization, type: :model do
|
||||
end
|
||||
end
|
||||
|
||||
# describe "#find_or_create_web_app" do
|
||||
# context "with origin that looks hosted" do
|
||||
# before do
|
||||
# auth = user.remote_storage_authorizations.create!(
|
||||
# permissions: %w(documents photos contacts:rw videos:r tasks/work:r),
|
||||
# client_id: "example.com",
|
||||
# redirect_uri: "https://example.com",
|
||||
# expire_at: 1.month.from_now
|
||||
# )
|
||||
# end
|
||||
#
|
||||
# it "generates a web_app" do
|
||||
# expect(auth.web_app).to be_a(AppCatalog::WebApp)
|
||||
# end
|
||||
#
|
||||
# it "uses the Web App's name as app name" do
|
||||
# expect(auth.app_name).to eq("Example Domain")
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# context "when creating two authorizations for the same app" do
|
||||
# before do
|
||||
# user_2 = create :user
|
||||
# ResqueSpec.reset!
|
||||
# auth_1 = user.remote_storage_authorizations.create!(
|
||||
# permissions: %w(documents photos contacts:rw videos:r tasks/work:r),
|
||||
# client_id: "example.com",
|
||||
# redirect_uri: "https://example.com",
|
||||
# expire_at: 1.month.from_now
|
||||
# )
|
||||
# auth_2 = user_2.remote_storage_authorizations.create!(
|
||||
# permissions: %w(documents photos contacts:rw videos:r tasks/work:r),
|
||||
# client_id: "example.com",
|
||||
# redirect_uri: "https://example.com",
|
||||
# expire_at: 1.month.from_now
|
||||
# )
|
||||
# end
|
||||
#
|
||||
# after do
|
||||
# auth_1.destroy
|
||||
# auth_2.destroy
|
||||
# user_2.destroy
|
||||
# end
|
||||
#
|
||||
# it "uses the same web app instance for both authorizations" do
|
||||
# expect(auth_1.web_app).to be_a(AppCatalog::WebApp)
|
||||
# expect(auth_1.web_app).to eq(auth_2.web_app)
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# describe "non-production app origins" do
|
||||
# context "when host is not an FQDN" do
|
||||
# before do
|
||||
# auth = user.remote_storage_authorizations.create!(
|
||||
# permissions: %w(recipes),
|
||||
# client_id: "localhost:4200",
|
||||
# redirect_uri: "http://localhost:4200"
|
||||
# )
|
||||
# end
|
||||
#
|
||||
# it "does not create a web app" do
|
||||
# expect(auth.web_app).to be_nil
|
||||
# expect(auth.app_name).to eq("localhost:4200")
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# context "when host is an IP address" do
|
||||
# before do
|
||||
# auth = user.remote_storage_authorizations.create!(
|
||||
# permissions: %w(recipes),
|
||||
# client_id: "192.168.0.23:3000",
|
||||
# redirect_uri: "http://192.168.0.23:3000"
|
||||
# )
|
||||
# end
|
||||
#
|
||||
# it "does not create a web app" do
|
||||
# expect(auth.web_app).to be_nil
|
||||
# expect(auth.app_name).to eq("192.168.0.23:3000")
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# context "when host is an extension URL" do # before do
|
||||
# auth = user.remote_storage_authorizations.create!(
|
||||
# permissions: %w(bookmarks),
|
||||
# client_id: "123.addons.allizom.org",
|
||||
# redirect_uri: "123.addons.allizom.org/foo"
|
||||
# )
|
||||
# end
|
||||
#
|
||||
# it "does not create a web app" do
|
||||
# expect(auth.web_app).to be_nil
|
||||
# expect(auth.app_name).to eq("123.addons.allizom.org")
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
describe "#find_or_create_web_app" do
|
||||
context "with origin that looks hosted" do
|
||||
after(:all) { redis_rs_delete_keys("authorizations:*") }
|
||||
|
||||
# describe "auth notifications" do
|
||||
# context "with auth notifications enabled" do
|
||||
# before do
|
||||
# ResqueSpec.reset!
|
||||
# user.push(mailing_lists: "rs-auth-notifications-#{Rails.env}")
|
||||
# auth = user.remote_storage_authorizations.create!(
|
||||
# :permissions => %w(documents photos contacts:rw videos:r tasks/work:r),
|
||||
# :client_id => "example.com",
|
||||
# :redirect_uri => "https://example.com"
|
||||
# )
|
||||
# end
|
||||
#
|
||||
# it "notifies the user via email" do
|
||||
# expect(enqueued_jobs.size).to eq(1)
|
||||
# job = enqueued_jobs.first
|
||||
# expect(job).to eq(
|
||||
# job: ActionMailer::DeliveryJob,
|
||||
# args: ['StorageAuthorizationMailer', 'authorized_rs_app', 'deliver_now',
|
||||
# auth.id.to_s],
|
||||
# queue: 'mailers'
|
||||
# )
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# context "with auth notifications disabled" do
|
||||
# before do
|
||||
# ResqueSpec.reset!
|
||||
# user.pull(mailing_lists: "rs-auth-notifications-#{Rails.env}")
|
||||
# auth = user.remote_storage_authorizations.create!(
|
||||
# :permissions => %w(documents photos contacts:rw videos:r tasks/work:r),
|
||||
# :client_id => "example.com",
|
||||
# :redirect_uri => "https://example.com"
|
||||
# )
|
||||
# end
|
||||
#
|
||||
# it "does not notify the user via email about new RS app" do
|
||||
# expect(enqueued_jobs.size).to eq(0)
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
let(:auth) do
|
||||
user.remote_storage_authorizations.create!(
|
||||
permissions: %w(documents:rw),
|
||||
client_id: "example.com",
|
||||
redirect_uri: "https://example.com"
|
||||
)
|
||||
end
|
||||
|
||||
it "generates a web_app" do
|
||||
expect(auth.web_app).to be_a(AppCatalog::WebApp)
|
||||
end
|
||||
end
|
||||
|
||||
context "when creating two authorizations for the same app" do
|
||||
let(:user_2) { create :user, id: 23, cn: "michiel", email: "michiel@example.com" }
|
||||
|
||||
let(:auth_1) do
|
||||
user.remote_storage_authorizations.create!(
|
||||
permissions: %w(documents photos contacts:rw videos:r tasks/work:r),
|
||||
client_id: "example.com",
|
||||
redirect_uri: "https://example.com"
|
||||
)
|
||||
end
|
||||
|
||||
let(:auth_2) do
|
||||
user_2.remote_storage_authorizations.create!(
|
||||
permissions: %w(documents photos contacts:rw videos:r tasks/work:r),
|
||||
client_id: "example.com",
|
||||
redirect_uri: "https://example.com"
|
||||
)
|
||||
end
|
||||
|
||||
after do
|
||||
auth_1.destroy
|
||||
auth_2.destroy
|
||||
user_2.destroy
|
||||
end
|
||||
|
||||
it "uses the same web app for both authorizations" do
|
||||
expect(auth_1.web_app).to eq(auth_2.web_app)
|
||||
end
|
||||
end
|
||||
|
||||
describe "non-production app origins" do
|
||||
context "when host is not an FQDN" do
|
||||
let(:auth) do
|
||||
user.remote_storage_authorizations.create!(
|
||||
permissions: %w(recipes),
|
||||
client_id: "localhost:4200",
|
||||
redirect_uri: "http://localhost:4200"
|
||||
)
|
||||
end
|
||||
|
||||
it "does not create a web app" do
|
||||
expect(auth.web_app).to be_nil
|
||||
expect(auth.app_name).to eq("localhost:4200")
|
||||
end
|
||||
end
|
||||
|
||||
context "when host is an IP address" do
|
||||
let(:auth) do
|
||||
user.remote_storage_authorizations.create!(
|
||||
permissions: %w(recipes),
|
||||
client_id: "192.168.0.23:3000",
|
||||
redirect_uri: "http://192.168.0.23:3000"
|
||||
)
|
||||
end
|
||||
|
||||
it "does not create a web app" do
|
||||
expect(auth.web_app).to be_nil
|
||||
expect(auth.app_name).to eq("192.168.0.23:3000")
|
||||
end
|
||||
end
|
||||
|
||||
context "when host is an extension URL" do
|
||||
let(:auth) do
|
||||
user.remote_storage_authorizations.create!(
|
||||
permissions: %w(bookmarks),
|
||||
client_id: "123.addons.allizom.org",
|
||||
redirect_uri: "123.addons.allizom.org/foo"
|
||||
)
|
||||
end
|
||||
|
||||
it "does not create a web app" do
|
||||
expect(auth.web_app).to be_nil
|
||||
expect(auth.app_name).to eq("123.addons.allizom.org")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#launch_url" do
|
||||
after(:all) { redis_rs_delete_keys("authorizations:*") }
|
||||
|
||||
context "without start URL" do
|
||||
before do
|
||||
AppCatalog::WebApp.create!(
|
||||
url: "https://webmarks.5apps.com", name: "Webmarks",
|
||||
metadata: { name: "Webmarks", start_url: nil, scope: nil }
|
||||
)
|
||||
end
|
||||
|
||||
let(:auth) do
|
||||
user.remote_storage_authorizations.create!(
|
||||
permissions: %w(bookmarks:rw), client_id: "webmarks.5apps.com",
|
||||
redirect_uri: "https://webmarks.5apps.com/connect"
|
||||
)
|
||||
end
|
||||
|
||||
it "uses the base URL (from client ID)" do
|
||||
expect(auth.launch_url).to eq("https://webmarks.5apps.com")
|
||||
end
|
||||
end
|
||||
|
||||
context "with start URL" do
|
||||
before do
|
||||
AppCatalog::WebApp.create!(
|
||||
url: "https://hyperdraft.rosano.ca", name: "Hyperdraft",
|
||||
metadata: {
|
||||
name: "Hyperdraft", scope: nil,
|
||||
start_url: "https://hyperdraft.rosano.ca/start"
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
let(:auth) do
|
||||
user.remote_storage_authorizations.create!(
|
||||
permissions: %w(notes:rw), client_id: "hyperdraft.rosano.ca",
|
||||
redirect_uri: "https://hyperdraft.rosano.ca/write/foo"
|
||||
)
|
||||
end
|
||||
|
||||
describe "full URL" do
|
||||
it "respects the start URL" do
|
||||
expect(auth.launch_url).to eq("https://hyperdraft.rosano.ca/start")
|
||||
end
|
||||
|
||||
it "does not respect URLs outside of the client ID scope" do
|
||||
auth.web_app.metadata[:start_url] = "https://uberdraft.rosano.ca/write"
|
||||
expect(auth.launch_url).to eq("https://hyperdraft.rosano.ca")
|
||||
end
|
||||
end
|
||||
|
||||
describe "relative paths" do
|
||||
it "includes the path relative from the base URL" do
|
||||
auth.web_app.metadata[:start_url] = "start.html"
|
||||
expect(auth.launch_url).to eq("https://hyperdraft.rosano.ca/start.html")
|
||||
|
||||
auth.web_app.metadata[:start_url] = "./start.html"
|
||||
expect(auth.launch_url).to eq("https://hyperdraft.rosano.ca/start.html")
|
||||
|
||||
auth.web_app.metadata[:start_url] = "../start.html"
|
||||
expect(auth.launch_url).to eq("https://hyperdraft.rosano.ca/start.html")
|
||||
end
|
||||
end
|
||||
|
||||
describe "absolute path" do
|
||||
it "includes the path relative from the base URL" do
|
||||
auth.web_app.metadata[:start_url] = "/write"
|
||||
expect(auth.launch_url).to eq("https://hyperdraft.rosano.ca/write")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "notifications" do
|
||||
include ActiveJob::TestHelper
|
||||
|
||||
after(:each) { clear_enqueued_jobs }
|
||||
after(:all) { redis_rs_delete_keys("authorizations:*") }
|
||||
|
||||
before { allow(user).to receive(:display_name).and_return("Jimmy") }
|
||||
|
||||
context "with notifications disabled" do
|
||||
before do
|
||||
user.preferences.merge!({ remotestorage_notify_auth_created: "off" })
|
||||
user.save!
|
||||
user.remote_storage_authorizations.create!(
|
||||
:permissions => %w(photos), :client_id => "app.example.com",
|
||||
:redirect_uri => "https://app.example.com"
|
||||
)
|
||||
end
|
||||
|
||||
it "does not notify the user via email about new RS app" do
|
||||
expect(enqueued_jobs.size).to eq(0)
|
||||
end
|
||||
end
|
||||
|
||||
context "with email notifications enabled" do
|
||||
before do
|
||||
user.preferences.merge!({ remotestorage_notify_auth_created: "email" })
|
||||
user.save!
|
||||
user.remote_storage_authorizations.create!(
|
||||
:permissions => %w(photos), :client_id => "app.example.com",
|
||||
:redirect_uri => "https://app.example.com"
|
||||
)
|
||||
end
|
||||
|
||||
it "notifies the user via email" do
|
||||
expect(enqueued_jobs.size).to eq(1)
|
||||
job = enqueued_jobs.select{|j| j['job_class'] == "ActionMailer::MailDeliveryJob"}.first
|
||||
expect(job['arguments'][0]).to eq('NotificationMailer')
|
||||
expect(job['arguments'][1]).to eq('remotestorage_auth_created')
|
||||
expect(job['arguments'][3]['params']['user']['_aj_globalid']).to eq('gid://akkounts/User/1')
|
||||
expect(job['arguments'][3]['params']['auth']['_aj_globalid']).to eq('gid://akkounts/RemoteStorageAuthorization/1')
|
||||
end
|
||||
end
|
||||
|
||||
context "with XMPP notifications enabled" do
|
||||
before do
|
||||
Setting.xmpp_notifications_from_address = "botka@kosmos.org"
|
||||
user.preferences.merge!({ remotestorage_notify_auth_created: "xmpp" })
|
||||
user.save!
|
||||
user.remote_storage_authorizations.create!(
|
||||
:permissions => %w(photos), :client_id => "app.example.com",
|
||||
:redirect_uri => "https://app.example.com"
|
||||
)
|
||||
end
|
||||
|
||||
it "sends an XMPP message to the account owner's JID" do
|
||||
expect(enqueued_jobs.size).to eq(1)
|
||||
expect(enqueued_jobs.first["job_class"]).to eq("XmppSendMessageJob")
|
||||
msg = enqueued_jobs.first["arguments"].first
|
||||
expect(msg["type"]).to eq("normal")
|
||||
expect(msg["from"]).to eq("botka@kosmos.org")
|
||||
expect(msg["to"]).to eq(user.address)
|
||||
expect(msg["body"]).to match(/granted 'app\.example\.com' access to your Kosmos Storage/)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
103
spec/requests/api/btcpay_spec.rb
Normal file
@@ -0,0 +1,103 @@
|
||||
require 'rails_helper'
|
||||
require 'webmock/rspec'
|
||||
|
||||
RSpec.describe "/api/btcpay", 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_btcpay_onchain_btc_balance_path
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
|
||||
res = JSON.parse(response.body)
|
||||
expect(res["balance"]).to eq(91108606)
|
||||
expect(res["unconfirmed_balance"]).to eq(0)
|
||||
expect(res["confirmed_balance"]).to eq(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_btcpay_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
|
||||
|
||||
context "feature disabled" do
|
||||
before do
|
||||
Setting.btcpay_publish_wallet_balances = false
|
||||
end
|
||||
|
||||
it "returns a 404 status" do
|
||||
get api_btcpay_onchain_btc_balance_path
|
||||
|
||||
expect(response).to have_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /lightning_btc_balance" do
|
||||
before do
|
||||
stub_request(:get, "http://btcpay.example.com/api/v1/stores/123456/lightning/BTC/balance")
|
||||
.to_return(status: 200, headers: {}, body: {
|
||||
offchain: {
|
||||
local: 4200000000
|
||||
},
|
||||
}.to_json)
|
||||
end
|
||||
|
||||
it "returns a formatted result for the onchain wallet balance" do
|
||||
get api_btcpay_lightning_btc_balance_path
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
|
||||
res = JSON.parse(response.body)
|
||||
expect(res["balance"]).to eq(4200000)
|
||||
end
|
||||
|
||||
context "upstream request error" do
|
||||
before do
|
||||
stub_request(:get, "http://btcpay.example.com/api/v1/stores/123456/lightning/BTC/balance")
|
||||
.to_return(status: 500, headers: {}, body: "")
|
||||
end
|
||||
|
||||
it "returns a formatted error" do
|
||||
get api_btcpay_lightning_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
|
||||
|
||||
context "feature disabled" do
|
||||
before do
|
||||
Setting.btcpay_publish_wallet_balances = false
|
||||
end
|
||||
|
||||
it "returns a 404 status" do
|
||||
get api_btcpay_lightning_btc_balance_path
|
||||
|
||||
expect(response).to have_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,43 +0,0 @@
|
||||
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
|
||||
@@ -15,10 +15,10 @@ RSpec.describe "WebFinger", type: :request do
|
||||
res = JSON.parse(response.body)
|
||||
rs_link = res["links"].find {|l| l["rel"] == "http://tools.ietf.org/id/draft-dejong-remotestorage"}
|
||||
|
||||
expect(rs_link["href"]).to eql("https://storage.kosmos.org/tony@kosmos.org")
|
||||
expect(rs_link["href"]).to eql("https://storage.kosmos.org/tony")
|
||||
|
||||
oauth_url = rs_link["properties"]["http://tools.ietf.org/html/rfc6749#section-4.2"]
|
||||
expect(oauth_url).to eql("https://example.com/rs/oauth")
|
||||
expect(oauth_url).to eql("http://www.example.com/rs/oauth/tony")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -68,6 +68,7 @@ RSpec.describe "Webhooks", type: :request do
|
||||
|
||||
context "notification preference set to 'xmpp'" do
|
||||
before do
|
||||
Setting.xmpp_notifications_from_address = "botka@kosmos.org"
|
||||
user.update! preferences: { lightning_notify_sats_received: "xmpp" }
|
||||
post "/webhooks/lndhub", params: payload.to_json
|
||||
end
|
||||
@@ -78,7 +79,7 @@ RSpec.describe "Webhooks", type: :request do
|
||||
|
||||
msg = enqueued_jobs.first["arguments"].first
|
||||
expect(msg["type"]).to eq("normal")
|
||||
expect(msg["from"]).to eq("kosmos.org")
|
||||
expect(msg["from"]).to eq("botka@kosmos.org")
|
||||
expect(msg["to"]).to eq(user.address)
|
||||
expect(msg["subject"]).to eq("Sats received!")
|
||||
expect(msg["body"]).to match(/^12,300 sats received/)
|
||||
|
||||