Compare commits
85 Commits
2a70bf2fb9
...
v0.9.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
eac8fa6edb
|
|||
|
43f918a074
|
|||
| e322867d79 | |||
|
4d6fa318b7
|
|||
| 4e8878a4b5 | |||
|
e65b890880
|
|||
|
f57edd4d3b
|
|||
|
1afd56fb80
|
|||
| 71669a4b96 | |||
|
c312e30c17
|
|||
| 51f4556ede | |||
| 4fa4ae6b54 | |||
| 869ff4691b | |||
|
822a2dc018
|
|||
|
5b7fc3707b
|
|||
| 0e2dc54dc6 | |||
| 87f09c94d0 | |||
|
b33b8104a8
|
|||
| 4a4a222973 | |||
| 8c524abcf5 | |||
|
a852ab75ae
|
|||
|
de1f234c15
|
|||
| 4581900427 | |||
|
56d91083e5
|
|||
|
ba7c3795f8
|
|||
|
bbf3fb91a0
|
|||
| 1754df73cb | |||
|
9a1f9abf84
|
|||
|
2753388e1e
|
|||
|
f3159d30f1
|
|||
|
ca238be6f4
|
|||
|
8747ce4eb0
|
|||
|
fcda3b9c8c
|
|||
|
67689dcce3
|
|||
|
22ffcd54db
|
|||
|
bd1b177993
|
|||
|
3f110995a4
|
|||
|
a7410058fa
|
|||
|
411587456b
|
|||
|
84e915ece9
|
|||
|
70ac3b0a70
|
|||
|
a7cbd8ce36
|
|||
|
c9052b35f6
|
|||
|
3b96130491
|
|||
|
176b1a10c6
|
|||
|
1c54e4c0b5
|
|||
|
7796a22491
|
|||
|
7e6e917ae1
|
|||
|
28cfe4b1e7
|
|||
|
179a82d2dd
|
|||
|
420442c1c0
|
|||
|
68c5758ecc
|
|||
|
c5dd3c30a6
|
|||
|
422d5c7cd2
|
|||
|
5a23d523a8
|
|||
|
f8da034e66
|
|||
|
b0b56fcf92
|
|||
| 0cf000c1b8 | |||
| fa9a924b0a | |||
|
50f91cc7d7
|
|||
|
a628a03f84
|
|||
|
eaf41e0835
|
|||
|
243cf9c08d
|
|||
|
c32fc51aab
|
|||
|
aa9178d569
|
|||
|
281938dd64
|
|||
|
fafc5d8f6f
|
|||
|
1238359b5f
|
|||
| 84220beb1c | |||
|
1e9ec9bb76
|
|||
| 21e51a7c40 | |||
|
e3c30f7b16
|
|||
|
b4f0c60ea0
|
|||
|
1a5a2177b4
|
|||
|
7e8443c598
|
|||
|
7b71f2cf76
|
|||
|
c7b137e5eb
|
|||
|
958d18d61a
|
|||
|
3aa0c49507
|
|||
|
|
4e566a0607 | ||
|
|
aab6793b86
|
||
|
|
cfd0935bdc
|
||
|
|
c2dae105ff
|
||
|
|
e301ac8e2e
|
||
|
|
03a1d9f277
|
@@ -17,7 +17,7 @@ steps:
|
|||||||
branch:
|
branch:
|
||||||
- master
|
- master
|
||||||
- name: rspec
|
- name: rspec
|
||||||
image: gitea.kosmos.org/kosmos/akkounts-ci:0.1.0
|
image: gitea.kosmos.org/kosmos/akkounts-ci:0.9.1
|
||||||
environment:
|
environment:
|
||||||
RAILS_ENV: test
|
RAILS_ENV: test
|
||||||
REDIS_URL: redis://redis:6379/0
|
REDIS_URL: redis://redis:6379/0
|
||||||
@@ -28,6 +28,8 @@ steps:
|
|||||||
- bundle config set cache_path 'vendor/cache'
|
- bundle config set cache_path 'vendor/cache'
|
||||||
- bundle config set with 'development test'
|
- bundle config set with 'development test'
|
||||||
- bundle install --jobs=3 --retry=3
|
- bundle install --jobs=3 --retry=3
|
||||||
|
- bundle exec rails db:create
|
||||||
|
- bundle exec rails db:migrate
|
||||||
- yarn install
|
- yarn install
|
||||||
- rake css:build
|
- rake css:build
|
||||||
- bundle exec rspec
|
- bundle exec rspec
|
||||||
|
|||||||
86
.env.example
@@ -1,56 +1,64 @@
|
|||||||
PRIMARY_DOMAIN=kosmos.org
|
# PRIMARY_DOMAIN=kosmos.org
|
||||||
AKKOUNTS_DOMAIN=accounts.example.com
|
# AKKOUNTS_DOMAIN=accounts.example.com
|
||||||
|
|
||||||
SMTP_SERVER=smtp.example.com
|
# SMTP_SERVER=smtp.example.com
|
||||||
SMTP_PORT=587
|
# SMTP_PORT=587
|
||||||
SMTP_LOGIN=accounts
|
# SMTP_LOGIN=accounts
|
||||||
SMTP_PASSWORD=123abc
|
# SMTP_PASSWORD=123abc
|
||||||
SMTP_FROM_ADDRESS=accounts@example.com
|
# SMTP_FROM_ADDRESS=accounts@example.com
|
||||||
SMTP_DOMAIN=example.com
|
# SMTP_DOMAIN=example.com
|
||||||
SMTP_AUTH_METHOD=plain
|
# SMTP_AUTH_METHOD=plain
|
||||||
SMTP_ENABLE_STARTTLS=auto
|
# SMTP_ENABLE_STARTTLS=auto
|
||||||
|
|
||||||
# S3_ENABLED=true
|
# S3_ENABLED=true
|
||||||
# S3_ENDPOINT=https://s3.kosmos.org
|
# S3_ENDPOINT=https://s3.kosmos.org
|
||||||
# S3_REGION=garage
|
# S3_REGION=garage
|
||||||
# S3_BUCKET=akkounts-production
|
# S3_BUCKET=akkounts-production
|
||||||
# S3_ALIAS_HOST=accounts.s3.kosmos.org
|
# S3_ALIAS_HOST=https://accounts.web.s3.kosmos.org
|
||||||
# S3_ACCESS_KEY=123456abcdefg
|
# S3_ACCESS_KEY=123456abcdefg
|
||||||
# S3_SECRET_KEY=123456789123456789123456789
|
# S3_SECRET_KEY=123456789123456789123456789
|
||||||
|
|
||||||
LDAP_HOST=localhost
|
# LDAP_HOST=localhost
|
||||||
LDAP_PORT=389
|
# LDAP_PORT=389
|
||||||
LDAP_ADMIN_PASSWORD=passthebutter
|
# LDAP_ADMIN_PASSWORD=passthebutter
|
||||||
LDAP_SUFFIX='dc=kosmos,dc=org'
|
# LDAP_SUFFIX='dc=kosmos,dc=org'
|
||||||
|
|
||||||
REDIS_URL='redis://localhost:6379/1'
|
# REDIS_URL='redis://localhost:6379/1'
|
||||||
|
|
||||||
WEBHOOKS_ALLOWED_IPS='10.1.1.163'
|
# WEBHOOKS_ALLOWED_IPS='10.1.1.163'
|
||||||
|
|
||||||
DISCOURSE_PUBLIC_URL='https://community.kosmos.org'
|
#
|
||||||
DISCOURSE_CONNECT_SECRET='discourse_connect_ftw'
|
# Service Integrations
|
||||||
|
#
|
||||||
|
|
||||||
DRONECI_PUBLIC_URL='https://drone.kosmos.org'
|
# BTCPAY_API_URL='http://localhost:23001/api/v1'
|
||||||
|
# BTCPAY_STORE_ID=''
|
||||||
|
# BTCPAY_AUTH_TOKEN=''
|
||||||
|
|
||||||
GITEA_PUBLIC_URL='https://gitea.kosmos.org'
|
# DISCOURSE_PUBLIC_URL='https://community.kosmos.org'
|
||||||
MASTODON_PUBLIC_URL='https://kosmos.social'
|
# DISCOURSE_CONNECT_SECRET='discourse_connect_ftw'
|
||||||
MEDIAWIKI_PUBLIC_URL='https://wiki.kosmos.org'
|
|
||||||
RS_STORAGE_URL='https://storage.kosmos.org'
|
|
||||||
RS_REDIS_URL='redis://localhost:6379/2'
|
|
||||||
|
|
||||||
EJABBERD_ADMIN_URL='https://xmpp.kosmos.org/admin'
|
# DRONECI_PUBLIC_URL='https://drone.kosmos.org'
|
||||||
EJABBERD_API_URL='https://xmpp.kosmos.org/api'
|
|
||||||
|
|
||||||
BTCPAY_API_URL='http://localhost:23001/api/v1'
|
# EJABBERD_ADMIN_URL='https://xmpp.kosmos.org/admin'
|
||||||
BTCPAY_STORE_ID=''
|
# EJABBERD_API_URL='https://xmpp.kosmos.org/api'
|
||||||
BTCPAY_AUTH_TOKEN=''
|
|
||||||
|
|
||||||
LNDHUB_API_URL='http://localhost:3023'
|
# GITEA_PUBLIC_URL='https://gitea.kosmos.org'
|
||||||
LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org'
|
|
||||||
LNDHUB_PUBLIC_KEY='0123d3be18617f39cf645851e3ba63f51fc13f0bb09e3bb25e6fd4de556486d946'
|
# LNDHUB_API_URL='http://localhost:3023'
|
||||||
LNDHUB_ADMIN_UI=true
|
# LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org'
|
||||||
LNDHUB_PG_HOST=localhost
|
# LNDHUB_PUBLIC_KEY='0123d3be18617f39cf645851e3ba63f51fc13f0bb09e3bb25e6fd4de556486d946'
|
||||||
LNDHUB_PG_PORT=5432
|
# LNDHUB_ADMIN_UI=true
|
||||||
LNDHUB_PG_DATABASE=lndhub
|
# LNDHUB_ADMIN_TOKEN=123456789
|
||||||
LNDHUB_PG_USERNAME=lndhub
|
# LNDHUB_PG_HOST=localhost
|
||||||
LNDHUB_PG_PASSWORD=''
|
# LNDHUB_PG_PORT=5432
|
||||||
|
# LNDHUB_PG_DATABASE=lndhub
|
||||||
|
# LNDHUB_PG_USERNAME=lndhub
|
||||||
|
# LNDHUB_PG_PASSWORD=''
|
||||||
|
|
||||||
|
# MASTODON_PUBLIC_URL='https://kosmos.social'
|
||||||
|
|
||||||
|
# MEDIAWIKI_PUBLIC_URL='https://wiki.kosmos.org'
|
||||||
|
|
||||||
|
# RS_STORAGE_URL='https://storage.kosmos.org'
|
||||||
|
# RS_REDIS_URL='redis://localhost:6379/2'
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
2.7.2
|
3.3.0
|
||||||
|
|||||||
14
Dockerfile
@@ -1,10 +1,18 @@
|
|||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
FROM ruby:2.7.6
|
FROM debian:bullseye-slim as base
|
||||||
|
|
||||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||||
|
|
||||||
RUN apt-get update -qq && apt-get install -y --no-install-recommends curl \
|
# TODO Remove when upstream Ruby works properly on Apple silicon
|
||||||
ldap-utils tini libvips
|
RUN apt update && apt install -y build-essential wget autoconf libpq-dev pkg-config
|
||||||
|
RUN wget https://github.com/postmodern/ruby-install/releases/download/v0.9.3/ruby-install-0.9.3.tar.gz \
|
||||||
|
&& tar -xzvf ruby-install-0.9.3.tar.gz \
|
||||||
|
&& cd ruby-install-0.9.3/ \
|
||||||
|
&& make install
|
||||||
|
RUN ruby-install -p https://github.com/ruby/ruby/pull/9371.diff ruby 3.3.0
|
||||||
|
ENV PATH="/opt/rubies/ruby-3.3.0/bin:${PATH}"
|
||||||
|
|
||||||
|
RUN apt-get install -y --no-install-recommends curl ldap-utils tini libvips
|
||||||
RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -
|
RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -
|
||||||
RUN apt-get update && apt-get install -y nodejs
|
RUN apt-get update && apt-get install -y nodejs
|
||||||
|
|
||||||
|
|||||||
17
Gemfile
@@ -2,7 +2,7 @@ source 'https://rubygems.org'
|
|||||||
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
|
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
|
||||||
|
|
||||||
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
|
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
|
||||||
gem 'rails', '~> 7.0.2'
|
gem 'rails', '~> 7.1'
|
||||||
# Use Puma as the app server
|
# Use Puma as the app server
|
||||||
gem 'puma', '~> 4.1'
|
gem 'puma', '~> 4.1'
|
||||||
# View components
|
# View components
|
||||||
@@ -22,7 +22,7 @@ gem 'jbuilder', '~> 2.7'
|
|||||||
# Use Redis adapter to run Action Cable in production
|
# Use Redis adapter to run Action Cable in production
|
||||||
# gem 'redis', '~> 4.0'
|
# gem 'redis', '~> 4.0'
|
||||||
# Use Active Model has_secure_password
|
# Use Active Model has_secure_password
|
||||||
# gem 'bcrypt', '~> 3.1.7'
|
gem 'bcrypt', '~> 3.1'
|
||||||
|
|
||||||
# Configuration
|
# Configuration
|
||||||
gem 'dotenv-rails'
|
gem 'dotenv-rails'
|
||||||
@@ -61,20 +61,19 @@ gem "sentry-rails"
|
|||||||
# Services
|
# Services
|
||||||
gem 'discourse_api'
|
gem 'discourse_api'
|
||||||
gem "lnurl"
|
gem "lnurl"
|
||||||
gem 'manifique', git: 'https://gitea.kosmos.org/5apps/manifique.git', branch: 'master'
|
gem 'manifique'
|
||||||
gem 'nostr', git: 'https://gitea.kosmos.org/kosmos/nostr-gem.git', branch: 'feature/ruby_2.7_compat'
|
gem 'nostr'
|
||||||
|
|
||||||
group :development, :test do
|
group :development, :test do
|
||||||
# Use sqlite3 as the database for Active Record
|
# Use sqlite3 as the database for Active Record
|
||||||
gem 'sqlite3', '~> 1.4'
|
gem 'sqlite3', '~> 1.7.2'
|
||||||
gem 'rspec-rails'
|
gem 'rspec-rails'
|
||||||
gem 'rails-controller-testing'
|
gem 'rails-controller-testing'
|
||||||
gem "byebug", "~> 11.1"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
group :development do
|
group :development do
|
||||||
# Access an interactive console on exception pages or by calling 'console' anywhere in the code.
|
# Access an interactive console on exception pages or by calling 'console' anywhere in the code.
|
||||||
gem 'web-console', '>= 3.3.0'
|
gem 'web-console', '~> 4.2'
|
||||||
gem 'listen', '~> 3.2'
|
gem 'listen', '~> 3.2'
|
||||||
gem 'letter_opener'
|
gem 'letter_opener'
|
||||||
gem 'letter_opener_web'
|
gem 'letter_opener_web'
|
||||||
@@ -90,8 +89,8 @@ group :test do
|
|||||||
end
|
end
|
||||||
|
|
||||||
group :production do
|
group :production do
|
||||||
# Use postgresql as the database for Active Record
|
gem 'pg', '~> 1.5'
|
||||||
gem 'pg', '~> 1.2.3'
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
|
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
|
||||||
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
|
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
|
||||||
|
|||||||
372
Gemfile.lock
@@ -1,141 +1,127 @@
|
|||||||
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
|
|
||||||
branch: feature/ruby_2.7_compat
|
|
||||||
specs:
|
|
||||||
nostr (0.4.0)
|
|
||||||
bech32 (~> 1.3)
|
|
||||||
bip-schnorr (~> 0.4)
|
|
||||||
ecdsa (~> 1.2)
|
|
||||||
event_emitter (~> 0.2)
|
|
||||||
faye-websocket (~> 0.11)
|
|
||||||
json (~> 2.6)
|
|
||||||
|
|
||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
actioncable (7.0.8)
|
actioncable (7.1.3)
|
||||||
actionpack (= 7.0.8)
|
actionpack (= 7.1.3)
|
||||||
activesupport (= 7.0.8)
|
activesupport (= 7.1.3)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
websocket-driver (>= 0.6.1)
|
websocket-driver (>= 0.6.1)
|
||||||
actionmailbox (7.0.8)
|
zeitwerk (~> 2.6)
|
||||||
actionpack (= 7.0.8)
|
actionmailbox (7.1.3)
|
||||||
activejob (= 7.0.8)
|
actionpack (= 7.1.3)
|
||||||
activerecord (= 7.0.8)
|
activejob (= 7.1.3)
|
||||||
activestorage (= 7.0.8)
|
activerecord (= 7.1.3)
|
||||||
activesupport (= 7.0.8)
|
activestorage (= 7.1.3)
|
||||||
|
activesupport (= 7.1.3)
|
||||||
mail (>= 2.7.1)
|
mail (>= 2.7.1)
|
||||||
net-imap
|
net-imap
|
||||||
net-pop
|
net-pop
|
||||||
net-smtp
|
net-smtp
|
||||||
actionmailer (7.0.8)
|
actionmailer (7.1.3)
|
||||||
actionpack (= 7.0.8)
|
actionpack (= 7.1.3)
|
||||||
actionview (= 7.0.8)
|
actionview (= 7.1.3)
|
||||||
activejob (= 7.0.8)
|
activejob (= 7.1.3)
|
||||||
activesupport (= 7.0.8)
|
activesupport (= 7.1.3)
|
||||||
mail (~> 2.5, >= 2.5.4)
|
mail (~> 2.5, >= 2.5.4)
|
||||||
net-imap
|
net-imap
|
||||||
net-pop
|
net-pop
|
||||||
net-smtp
|
net-smtp
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.2)
|
||||||
actionpack (7.0.8)
|
actionpack (7.1.3)
|
||||||
actionview (= 7.0.8)
|
actionview (= 7.1.3)
|
||||||
activesupport (= 7.0.8)
|
activesupport (= 7.1.3)
|
||||||
rack (~> 2.0, >= 2.2.4)
|
nokogiri (>= 1.8.5)
|
||||||
|
racc
|
||||||
|
rack (>= 2.2.4)
|
||||||
|
rack-session (>= 1.0.1)
|
||||||
rack-test (>= 0.6.3)
|
rack-test (>= 0.6.3)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.2)
|
||||||
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
rails-html-sanitizer (~> 1.6)
|
||||||
actiontext (7.0.8)
|
actiontext (7.1.3)
|
||||||
actionpack (= 7.0.8)
|
actionpack (= 7.1.3)
|
||||||
activerecord (= 7.0.8)
|
activerecord (= 7.1.3)
|
||||||
activestorage (= 7.0.8)
|
activestorage (= 7.1.3)
|
||||||
activesupport (= 7.0.8)
|
activesupport (= 7.1.3)
|
||||||
globalid (>= 0.6.0)
|
globalid (>= 0.6.0)
|
||||||
nokogiri (>= 1.8.5)
|
nokogiri (>= 1.8.5)
|
||||||
actionview (7.0.8)
|
actionview (7.1.3)
|
||||||
activesupport (= 7.0.8)
|
activesupport (= 7.1.3)
|
||||||
builder (~> 3.1)
|
builder (~> 3.1)
|
||||||
erubi (~> 1.4)
|
erubi (~> 1.11)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.2)
|
||||||
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
rails-html-sanitizer (~> 1.6)
|
||||||
activejob (7.0.8)
|
activejob (7.1.3)
|
||||||
activesupport (= 7.0.8)
|
activesupport (= 7.1.3)
|
||||||
globalid (>= 0.3.6)
|
globalid (>= 0.3.6)
|
||||||
activemodel (7.0.8)
|
activemodel (7.1.3)
|
||||||
activesupport (= 7.0.8)
|
activesupport (= 7.1.3)
|
||||||
activerecord (7.0.8)
|
activerecord (7.1.3)
|
||||||
activemodel (= 7.0.8)
|
activemodel (= 7.1.3)
|
||||||
activesupport (= 7.0.8)
|
activesupport (= 7.1.3)
|
||||||
activestorage (7.0.8)
|
timeout (>= 0.4.0)
|
||||||
actionpack (= 7.0.8)
|
activestorage (7.1.3)
|
||||||
activejob (= 7.0.8)
|
actionpack (= 7.1.3)
|
||||||
activerecord (= 7.0.8)
|
activejob (= 7.1.3)
|
||||||
activesupport (= 7.0.8)
|
activerecord (= 7.1.3)
|
||||||
|
activesupport (= 7.1.3)
|
||||||
marcel (~> 1.0)
|
marcel (~> 1.0)
|
||||||
mini_mime (>= 1.1.0)
|
activesupport (7.1.3)
|
||||||
activesupport (7.0.8)
|
base64
|
||||||
|
bigdecimal
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||||
|
connection_pool (>= 2.2.5)
|
||||||
|
drb
|
||||||
i18n (>= 1.6, < 2)
|
i18n (>= 1.6, < 2)
|
||||||
minitest (>= 5.1)
|
minitest (>= 5.1)
|
||||||
|
mutex_m
|
||||||
tzinfo (~> 2.0)
|
tzinfo (~> 2.0)
|
||||||
addressable (2.8.5)
|
addressable (2.8.6)
|
||||||
public_suffix (>= 2.0.2, < 6.0)
|
public_suffix (>= 2.0.2, < 6.0)
|
||||||
ast (2.4.2)
|
ast (2.4.2)
|
||||||
aws-eventstream (1.2.0)
|
aws-eventstream (1.3.0)
|
||||||
aws-partitions (1.839.0)
|
aws-partitions (1.886.0)
|
||||||
aws-sdk-core (3.185.1)
|
aws-sdk-core (3.191.0)
|
||||||
aws-eventstream (~> 1, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.3.0)
|
||||||
aws-partitions (~> 1, >= 1.651.0)
|
aws-partitions (~> 1, >= 1.651.0)
|
||||||
aws-sigv4 (~> 1.5)
|
aws-sigv4 (~> 1.8)
|
||||||
jmespath (~> 1, >= 1.6.1)
|
jmespath (~> 1, >= 1.6.1)
|
||||||
aws-sdk-kms (1.72.0)
|
aws-sdk-kms (1.77.0)
|
||||||
aws-sdk-core (~> 3, >= 3.184.0)
|
aws-sdk-core (~> 3, >= 3.191.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
aws-sdk-s3 (1.136.0)
|
aws-sdk-s3 (1.143.0)
|
||||||
aws-sdk-core (~> 3, >= 3.181.0)
|
aws-sdk-core (~> 3, >= 3.191.0)
|
||||||
aws-sdk-kms (~> 1)
|
aws-sdk-kms (~> 1)
|
||||||
aws-sigv4 (~> 1.6)
|
aws-sigv4 (~> 1.8)
|
||||||
aws-sigv4 (1.6.0)
|
aws-sigv4 (1.8.0)
|
||||||
aws-eventstream (~> 1, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
backport (1.2.0)
|
backport (1.2.0)
|
||||||
base64 (0.1.1)
|
base64 (0.2.0)
|
||||||
bcrypt (3.1.19)
|
bcrypt (3.1.20)
|
||||||
bech32 (1.4.2)
|
bech32 (1.4.2)
|
||||||
thor (>= 1.1.0)
|
thor (>= 1.1.0)
|
||||||
benchmark (0.2.1)
|
benchmark (0.3.0)
|
||||||
|
bigdecimal (3.1.6)
|
||||||
bindex (0.8.1)
|
bindex (0.8.1)
|
||||||
bip-schnorr (0.6.0)
|
bip-schnorr (0.7.0)
|
||||||
ecdsa_ext (~> 0.5.0)
|
ecdsa_ext (~> 0.5.0)
|
||||||
brow (0.4.1)
|
|
||||||
builder (3.2.4)
|
builder (3.2.4)
|
||||||
byebug (11.1.3)
|
capybara (3.40.0)
|
||||||
capybara (3.39.2)
|
|
||||||
addressable
|
addressable
|
||||||
matrix
|
matrix
|
||||||
mini_mime (>= 0.1.3)
|
mini_mime (>= 0.1.3)
|
||||||
nokogiri (~> 1.8)
|
nokogiri (~> 1.11)
|
||||||
rack (>= 1.6.0)
|
rack (>= 1.6.0)
|
||||||
rack-test (>= 0.6.3)
|
rack-test (>= 0.6.3)
|
||||||
regexp_parser (>= 1.5, < 3.0)
|
regexp_parser (>= 1.5, < 3.0)
|
||||||
xpath (~> 3.2)
|
xpath (~> 3.2)
|
||||||
chunky_png (1.4.0)
|
chunky_png (1.4.0)
|
||||||
concurrent-ruby (1.2.2)
|
concurrent-ruby (1.2.3)
|
||||||
connection_pool (2.4.1)
|
connection_pool (2.4.1)
|
||||||
crack (0.4.5)
|
crack (0.4.6)
|
||||||
|
bigdecimal
|
||||||
rexml
|
rexml
|
||||||
crass (1.0.6)
|
crass (1.0.6)
|
||||||
cssbundling-rails (1.3.3)
|
cssbundling-rails (1.4.0)
|
||||||
railties (>= 6.0.0)
|
railties (>= 6.0.0)
|
||||||
database_cleaner (2.0.2)
|
database_cleaner (2.0.2)
|
||||||
database_cleaner-active_record (>= 2, < 3)
|
database_cleaner-active_record (>= 2, < 3)
|
||||||
@@ -143,7 +129,7 @@ GEM
|
|||||||
activerecord (>= 5.a)
|
activerecord (>= 5.a)
|
||||||
database_cleaner-core (~> 2.0.0)
|
database_cleaner-core (~> 2.0.0)
|
||||||
database_cleaner-core (2.0.1)
|
database_cleaner-core (2.0.1)
|
||||||
date (3.3.3)
|
date (3.3.4)
|
||||||
devise (4.9.3)
|
devise (4.9.3)
|
||||||
bcrypt (~> 3.0)
|
bcrypt (~> 3.0)
|
||||||
orm_adapter (~> 0.1)
|
orm_adapter (~> 0.1)
|
||||||
@@ -153,7 +139,7 @@ GEM
|
|||||||
devise_ldap_authenticatable (0.8.7)
|
devise_ldap_authenticatable (0.8.7)
|
||||||
devise (>= 3.4.1)
|
devise (>= 3.4.1)
|
||||||
net-ldap (>= 0.16.0)
|
net-ldap (>= 0.16.0)
|
||||||
diff-lcs (1.5.0)
|
diff-lcs (1.5.1)
|
||||||
discourse_api (2.0.1)
|
discourse_api (2.0.1)
|
||||||
faraday (~> 2.7)
|
faraday (~> 2.7)
|
||||||
faraday-follow_redirects
|
faraday-follow_redirects
|
||||||
@@ -165,6 +151,8 @@ GEM
|
|||||||
railties (>= 3.2)
|
railties (>= 3.2)
|
||||||
down (5.4.1)
|
down (5.4.1)
|
||||||
addressable (~> 2.8)
|
addressable (~> 2.8)
|
||||||
|
drb (2.2.0)
|
||||||
|
ruby2_keywords
|
||||||
e2mmap (0.1.0)
|
e2mmap (0.1.0)
|
||||||
ecdsa (1.2.0)
|
ecdsa (1.2.0)
|
||||||
ecdsa_ext (0.5.0)
|
ecdsa_ext (0.5.0)
|
||||||
@@ -174,58 +162,61 @@ GEM
|
|||||||
tzinfo
|
tzinfo
|
||||||
event_emitter (0.2.6)
|
event_emitter (0.2.6)
|
||||||
eventmachine (1.2.7)
|
eventmachine (1.2.7)
|
||||||
factory_bot (6.2.1)
|
factory_bot (6.4.6)
|
||||||
activesupport (>= 5.0.0)
|
activesupport (>= 5.0.0)
|
||||||
factory_bot_rails (6.2.0)
|
factory_bot_rails (6.4.3)
|
||||||
factory_bot (~> 6.2.0)
|
factory_bot (~> 6.4)
|
||||||
railties (>= 5.0.0)
|
railties (>= 5.0.0)
|
||||||
faker (3.2.1)
|
faker (3.2.3)
|
||||||
i18n (>= 1.8.11, < 2)
|
i18n (>= 1.8.11, < 2)
|
||||||
faraday (2.7.11)
|
faraday (2.9.0)
|
||||||
base64
|
faraday-net_http (>= 2.0, < 3.2)
|
||||||
faraday-net_http (>= 2.0, < 3.1)
|
|
||||||
ruby2_keywords (>= 0.0.4)
|
|
||||||
faraday-follow_redirects (0.3.0)
|
faraday-follow_redirects (0.3.0)
|
||||||
faraday (>= 1, < 3)
|
faraday (>= 1, < 3)
|
||||||
faraday-multipart (1.0.4)
|
faraday-multipart (1.0.4)
|
||||||
multipart-post (~> 2)
|
multipart-post (~> 2)
|
||||||
faraday-net_http (3.0.2)
|
faraday-net_http (3.1.0)
|
||||||
|
net-http
|
||||||
faye-websocket (0.11.3)
|
faye-websocket (0.11.3)
|
||||||
eventmachine (>= 0.12.0)
|
eventmachine (>= 0.12.0)
|
||||||
websocket-driver (>= 0.5.1)
|
websocket-driver (>= 0.5.1)
|
||||||
ffi (1.16.3)
|
ffi (1.16.3)
|
||||||
flipper (1.0.0)
|
flipper (1.2.2)
|
||||||
brow (~> 0.4.1)
|
|
||||||
concurrent-ruby (< 2)
|
concurrent-ruby (< 2)
|
||||||
flipper-active_record (1.0.0)
|
flipper-active_record (1.2.2)
|
||||||
activerecord (>= 4.2, < 8)
|
activerecord (>= 4.2, < 8)
|
||||||
flipper (~> 1.0.0)
|
flipper (~> 1.2.2)
|
||||||
flipper-ui (1.0.0)
|
flipper-ui (1.2.2)
|
||||||
erubi (>= 1.0.0, < 2.0.0)
|
erubi (>= 1.0.0, < 2.0.0)
|
||||||
flipper (~> 1.0.0)
|
flipper (~> 1.2.2)
|
||||||
rack (>= 1.4, < 4)
|
rack (>= 1.4, < 4)
|
||||||
rack-protection (>= 1.5.3, <= 4.0.0)
|
rack-protection (>= 1.5.3, <= 4.0.0)
|
||||||
sanitize (< 7)
|
sanitize (< 7)
|
||||||
fugit (1.8.1)
|
fugit (1.9.0)
|
||||||
et-orbi (~> 1, >= 1.2.7)
|
et-orbi (~> 1, >= 1.2.7)
|
||||||
raabro (~> 1.4)
|
raabro (~> 1.4)
|
||||||
globalid (1.2.1)
|
globalid (1.2.1)
|
||||||
activesupport (>= 6.1)
|
activesupport (>= 6.1)
|
||||||
hashdiff (1.0.1)
|
hashdiff (1.1.0)
|
||||||
i18n (1.14.1)
|
i18n (1.14.1)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
image_processing (1.12.2)
|
image_processing (1.12.2)
|
||||||
mini_magick (>= 4.9.5, < 5)
|
mini_magick (>= 4.9.5, < 5)
|
||||||
ruby-vips (>= 2.0.17, < 3)
|
ruby-vips (>= 2.0.17, < 3)
|
||||||
importmap-rails (1.2.1)
|
importmap-rails (2.0.1)
|
||||||
actionpack (>= 6.0.0)
|
actionpack (>= 6.0.0)
|
||||||
|
activesupport (>= 6.0.0)
|
||||||
railties (>= 6.0.0)
|
railties (>= 6.0.0)
|
||||||
|
io-console (0.7.2)
|
||||||
|
irb (1.11.1)
|
||||||
|
rdoc
|
||||||
|
reline (>= 0.4.2)
|
||||||
jaro_winkler (1.5.6)
|
jaro_winkler (1.5.6)
|
||||||
jbuilder (2.11.5)
|
jbuilder (2.11.5)
|
||||||
actionview (>= 5.0.0)
|
actionview (>= 5.0.0)
|
||||||
activesupport (>= 5.0.0)
|
activesupport (>= 5.0.0)
|
||||||
jmespath (1.6.2)
|
jmespath (1.6.2)
|
||||||
json (2.6.3)
|
json (2.7.1)
|
||||||
kramdown (2.4.0)
|
kramdown (2.4.0)
|
||||||
rexml
|
rexml
|
||||||
kramdown-parser-gfm (1.1.0)
|
kramdown-parser-gfm (1.1.0)
|
||||||
@@ -245,8 +236,8 @@ GEM
|
|||||||
rb-inotify (~> 0.9, >= 0.9.10)
|
rb-inotify (~> 0.9, >= 0.9.10)
|
||||||
lnurl (1.1.0)
|
lnurl (1.1.0)
|
||||||
bech32 (~> 1.1)
|
bech32 (~> 1.1)
|
||||||
lockbox (1.3.0)
|
lockbox (1.3.2)
|
||||||
loofah (2.21.4)
|
loofah (2.22.0)
|
||||||
crass (~> 1.0.2)
|
crass (~> 1.0.2)
|
||||||
nokogiri (>= 1.12.0)
|
nokogiri (>= 1.12.0)
|
||||||
mail (2.8.1)
|
mail (2.8.1)
|
||||||
@@ -254,59 +245,85 @@ GEM
|
|||||||
net-imap
|
net-imap
|
||||||
net-pop
|
net-pop
|
||||||
net-smtp
|
net-smtp
|
||||||
|
manifique (1.0.1)
|
||||||
|
faraday (~> 2.9.0)
|
||||||
|
faraday-follow_redirects (= 0.3.0)
|
||||||
|
nokogiri (~> 1.16.0)
|
||||||
marcel (1.0.2)
|
marcel (1.0.2)
|
||||||
matrix (0.4.2)
|
matrix (0.4.2)
|
||||||
method_source (1.0.0)
|
method_source (1.0.0)
|
||||||
mini_magick (4.12.0)
|
mini_magick (4.12.0)
|
||||||
mini_mime (1.1.5)
|
mini_mime (1.1.5)
|
||||||
minitest (5.20.0)
|
mini_portile2 (2.8.5)
|
||||||
|
minitest (5.21.2)
|
||||||
multipart-post (2.3.0)
|
multipart-post (2.3.0)
|
||||||
net-imap (0.3.7)
|
mutex_m (0.2.0)
|
||||||
|
net-http (0.4.1)
|
||||||
|
uri
|
||||||
|
net-imap (0.4.9.1)
|
||||||
date
|
date
|
||||||
net-protocol
|
net-protocol
|
||||||
net-ldap (0.18.0)
|
net-ldap (0.19.0)
|
||||||
net-pop (0.1.2)
|
net-pop (0.1.2)
|
||||||
net-protocol
|
net-protocol
|
||||||
net-protocol (0.2.1)
|
net-protocol (0.2.2)
|
||||||
timeout
|
timeout
|
||||||
net-smtp (0.4.0)
|
net-smtp (0.4.0.1)
|
||||||
net-protocol
|
net-protocol
|
||||||
nio4r (2.5.9)
|
nio4r (2.7.0)
|
||||||
nokogiri (1.15.4-arm64-darwin)
|
nokogiri (1.16.0)
|
||||||
|
mini_portile2 (~> 2.8.2)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.15.4-x86_64-linux)
|
nokogiri (1.16.0-arm64-darwin)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
|
nokogiri (1.16.0-x86_64-linux)
|
||||||
|
racc (~> 1.4)
|
||||||
|
nostr (0.5.0)
|
||||||
|
bech32 (~> 1.4)
|
||||||
|
bip-schnorr (~> 0.6)
|
||||||
|
ecdsa (~> 1.2)
|
||||||
|
event_emitter (~> 0.2)
|
||||||
|
faye-websocket (~> 0.11)
|
||||||
|
json (~> 2.6)
|
||||||
orm_adapter (0.5.0)
|
orm_adapter (0.5.0)
|
||||||
pagy (6.1.0)
|
pagy (6.4.3)
|
||||||
parallel (1.23.0)
|
parallel (1.24.0)
|
||||||
parser (3.2.2.4)
|
parser (3.3.0.5)
|
||||||
ast (~> 2.4.1)
|
ast (~> 2.4.1)
|
||||||
racc
|
racc
|
||||||
pg (1.2.3)
|
pg (1.5.4)
|
||||||
public_suffix (5.0.3)
|
psych (5.1.2)
|
||||||
|
stringio
|
||||||
|
public_suffix (5.0.4)
|
||||||
puma (4.3.12)
|
puma (4.3.12)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
raabro (1.4.0)
|
raabro (1.4.0)
|
||||||
racc (1.7.1)
|
racc (1.7.3)
|
||||||
rack (2.2.8)
|
rack (2.2.8)
|
||||||
rack-protection (3.1.0)
|
rack-protection (3.2.0)
|
||||||
|
base64 (>= 0.1.0)
|
||||||
rack (~> 2.2, >= 2.2.4)
|
rack (~> 2.2, >= 2.2.4)
|
||||||
|
rack-session (1.0.2)
|
||||||
|
rack (< 3)
|
||||||
rack-test (2.1.0)
|
rack-test (2.1.0)
|
||||||
rack (>= 1.3)
|
rack (>= 1.3)
|
||||||
rails (7.0.8)
|
rackup (1.0.0)
|
||||||
actioncable (= 7.0.8)
|
rack (< 3)
|
||||||
actionmailbox (= 7.0.8)
|
webrick
|
||||||
actionmailer (= 7.0.8)
|
rails (7.1.3)
|
||||||
actionpack (= 7.0.8)
|
actioncable (= 7.1.3)
|
||||||
actiontext (= 7.0.8)
|
actionmailbox (= 7.1.3)
|
||||||
actionview (= 7.0.8)
|
actionmailer (= 7.1.3)
|
||||||
activejob (= 7.0.8)
|
actionpack (= 7.1.3)
|
||||||
activemodel (= 7.0.8)
|
actiontext (= 7.1.3)
|
||||||
activerecord (= 7.0.8)
|
actionview (= 7.1.3)
|
||||||
activestorage (= 7.0.8)
|
activejob (= 7.1.3)
|
||||||
activesupport (= 7.0.8)
|
activemodel (= 7.1.3)
|
||||||
|
activerecord (= 7.1.3)
|
||||||
|
activestorage (= 7.1.3)
|
||||||
|
activesupport (= 7.1.3)
|
||||||
bundler (>= 1.15.0)
|
bundler (>= 1.15.0)
|
||||||
railties (= 7.0.8)
|
railties (= 7.1.3)
|
||||||
rails-controller-testing (1.0.5)
|
rails-controller-testing (1.0.5)
|
||||||
actionpack (>= 5.0.1.rc1)
|
actionpack (>= 5.0.1.rc1)
|
||||||
actionview (>= 5.0.1.rc1)
|
actionview (>= 5.0.1.rc1)
|
||||||
@@ -321,21 +338,26 @@ GEM
|
|||||||
rails-settings-cached (2.8.3)
|
rails-settings-cached (2.8.3)
|
||||||
activerecord (>= 5.0.0)
|
activerecord (>= 5.0.0)
|
||||||
railties (>= 5.0.0)
|
railties (>= 5.0.0)
|
||||||
railties (7.0.8)
|
railties (7.1.3)
|
||||||
actionpack (= 7.0.8)
|
actionpack (= 7.1.3)
|
||||||
activesupport (= 7.0.8)
|
activesupport (= 7.1.3)
|
||||||
method_source
|
irb
|
||||||
|
rackup (>= 1.0.0)
|
||||||
rake (>= 12.2)
|
rake (>= 12.2)
|
||||||
thor (~> 1.0)
|
thor (~> 1.0, >= 1.2.2)
|
||||||
zeitwerk (~> 2.5)
|
zeitwerk (~> 2.6)
|
||||||
rainbow (3.1.1)
|
rainbow (3.1.1)
|
||||||
rake (13.0.6)
|
rake (13.1.0)
|
||||||
rb-fsevent (0.11.2)
|
rb-fsevent (0.11.2)
|
||||||
rb-inotify (0.10.1)
|
rb-inotify (0.10.1)
|
||||||
ffi (~> 1.0)
|
ffi (~> 1.0)
|
||||||
rbs (2.8.4)
|
rbs (2.8.4)
|
||||||
|
rdoc (6.6.2)
|
||||||
|
psych (>= 4.0.0)
|
||||||
redis (4.8.1)
|
redis (4.8.1)
|
||||||
regexp_parser (2.8.2)
|
regexp_parser (2.9.0)
|
||||||
|
reline (0.4.2)
|
||||||
|
io-console (~> 0.5)
|
||||||
responders (3.1.1)
|
responders (3.1.1)
|
||||||
actionpack (>= 5.2)
|
actionpack (>= 5.2)
|
||||||
railties (>= 5.2)
|
railties (>= 5.2)
|
||||||
@@ -354,7 +376,7 @@ GEM
|
|||||||
rspec-mocks (3.12.6)
|
rspec-mocks (3.12.6)
|
||||||
diff-lcs (>= 1.2.0, < 2.0)
|
diff-lcs (>= 1.2.0, < 2.0)
|
||||||
rspec-support (~> 3.12.0)
|
rspec-support (~> 3.12.0)
|
||||||
rspec-rails (6.0.3)
|
rspec-rails (6.1.1)
|
||||||
actionpack (>= 6.1)
|
actionpack (>= 6.1)
|
||||||
activesupport (>= 6.1)
|
activesupport (>= 6.1)
|
||||||
railties (>= 6.1)
|
railties (>= 6.1)
|
||||||
@@ -363,19 +385,18 @@ GEM
|
|||||||
rspec-mocks (~> 3.12)
|
rspec-mocks (~> 3.12)
|
||||||
rspec-support (~> 3.12)
|
rspec-support (~> 3.12)
|
||||||
rspec-support (3.12.1)
|
rspec-support (3.12.1)
|
||||||
rubocop (1.57.1)
|
rubocop (1.60.2)
|
||||||
base64 (~> 0.1.1)
|
|
||||||
json (~> 2.3)
|
json (~> 2.3)
|
||||||
language_server-protocol (>= 3.17.0)
|
language_server-protocol (>= 3.17.0)
|
||||||
parallel (~> 1.10)
|
parallel (~> 1.10)
|
||||||
parser (>= 3.2.2.4)
|
parser (>= 3.3.0.2)
|
||||||
rainbow (>= 2.2.2, < 4.0)
|
rainbow (>= 2.2.2, < 4.0)
|
||||||
regexp_parser (>= 1.8, < 3.0)
|
regexp_parser (>= 1.8, < 3.0)
|
||||||
rexml (>= 3.2.5, < 4.0)
|
rexml (>= 3.2.5, < 4.0)
|
||||||
rubocop-ast (>= 1.28.1, < 2.0)
|
rubocop-ast (>= 1.30.0, < 2.0)
|
||||||
ruby-progressbar (~> 1.7)
|
ruby-progressbar (~> 1.7)
|
||||||
unicode-display_width (>= 2.4.0, < 3.0)
|
unicode-display_width (>= 2.4.0, < 3.0)
|
||||||
rubocop-ast (1.29.0)
|
rubocop-ast (1.30.0)
|
||||||
parser (>= 3.2.1.0)
|
parser (>= 3.2.1.0)
|
||||||
ruby-progressbar (1.13.0)
|
ruby-progressbar (1.13.0)
|
||||||
ruby-vips (2.2.0)
|
ruby-vips (2.2.0)
|
||||||
@@ -386,10 +407,10 @@ GEM
|
|||||||
sanitize (6.1.0)
|
sanitize (6.1.0)
|
||||||
crass (~> 1.0.2)
|
crass (~> 1.0.2)
|
||||||
nokogiri (>= 1.12.0)
|
nokogiri (>= 1.12.0)
|
||||||
sentry-rails (5.12.0)
|
sentry-rails (5.16.1)
|
||||||
railties (>= 5.0)
|
railties (>= 5.0)
|
||||||
sentry-ruby (~> 5.12.0)
|
sentry-ruby (~> 5.16.1)
|
||||||
sentry-ruby (5.12.0)
|
sentry-ruby (5.16.1)
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||||
sidekiq (6.5.12)
|
sidekiq (6.5.12)
|
||||||
connection_pool (>= 2.2.5, < 3)
|
connection_pool (>= 2.2.5, < 3)
|
||||||
@@ -399,7 +420,7 @@ GEM
|
|||||||
rufus-scheduler (~> 3.2)
|
rufus-scheduler (~> 3.2)
|
||||||
sidekiq (>= 6, < 8)
|
sidekiq (>= 6, < 8)
|
||||||
tilt (>= 1.4.0)
|
tilt (>= 1.4.0)
|
||||||
solargraph (0.49.0)
|
solargraph (0.50.0)
|
||||||
backport (~> 1.2)
|
backport (~> 1.2)
|
||||||
benchmark
|
benchmark
|
||||||
bundler (~> 2.0)
|
bundler (~> 2.0)
|
||||||
@@ -422,13 +443,16 @@ GEM
|
|||||||
actionpack (>= 5.2)
|
actionpack (>= 5.2)
|
||||||
activesupport (>= 5.2)
|
activesupport (>= 5.2)
|
||||||
sprockets (>= 3.0.0)
|
sprockets (>= 3.0.0)
|
||||||
sqlite3 (1.6.7-arm64-darwin)
|
sqlite3 (1.7.2)
|
||||||
sqlite3 (1.6.7-x86_64-linux)
|
mini_portile2 (~> 2.8.0)
|
||||||
stimulus-rails (1.3.0)
|
sqlite3 (1.7.2-arm64-darwin)
|
||||||
|
sqlite3 (1.7.2-x86_64-linux)
|
||||||
|
stimulus-rails (1.3.3)
|
||||||
railties (>= 6.0.0)
|
railties (>= 6.0.0)
|
||||||
|
stringio (3.1.0)
|
||||||
thor (1.3.0)
|
thor (1.3.0)
|
||||||
tilt (2.3.0)
|
tilt (2.3.0)
|
||||||
timeout (0.4.0)
|
timeout (0.4.1)
|
||||||
turbo-rails (1.5.0)
|
turbo-rails (1.5.0)
|
||||||
actionpack (>= 6.0.0)
|
actionpack (>= 6.0.0)
|
||||||
activejob (>= 6.0.0)
|
activejob (>= 6.0.0)
|
||||||
@@ -436,7 +460,8 @@ GEM
|
|||||||
tzinfo (2.0.6)
|
tzinfo (2.0.6)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
unicode-display_width (2.5.0)
|
unicode-display_width (2.5.0)
|
||||||
view_component (3.6.0)
|
uri (0.13.0)
|
||||||
|
view_component (3.10.0)
|
||||||
activesupport (>= 5.2.0, < 8.0)
|
activesupport (>= 5.2.0, < 8.0)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
method_source (~> 1.0)
|
method_source (~> 1.0)
|
||||||
@@ -451,6 +476,7 @@ GEM
|
|||||||
addressable (>= 2.8.0)
|
addressable (>= 2.8.0)
|
||||||
crack (>= 0.3.2)
|
crack (>= 0.3.2)
|
||||||
hashdiff (>= 0.4.0, < 2.0.0)
|
hashdiff (>= 0.4.0, < 2.0.0)
|
||||||
|
webrick (1.8.1)
|
||||||
websocket-driver (0.7.6)
|
websocket-driver (0.7.6)
|
||||||
websocket-extensions (>= 0.1.0)
|
websocket-extensions (>= 0.1.0)
|
||||||
websocket-extensions (0.1.5)
|
websocket-extensions (0.1.5)
|
||||||
@@ -466,7 +492,7 @@ PLATFORMS
|
|||||||
|
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
aws-sdk-s3
|
aws-sdk-s3
|
||||||
byebug (~> 11.1)
|
bcrypt (~> 3.1)
|
||||||
capybara
|
capybara
|
||||||
cssbundling-rails
|
cssbundling-rails
|
||||||
database_cleaner
|
database_cleaner
|
||||||
@@ -489,13 +515,13 @@ DEPENDENCIES
|
|||||||
listen (~> 3.2)
|
listen (~> 3.2)
|
||||||
lnurl
|
lnurl
|
||||||
lockbox
|
lockbox
|
||||||
manifique!
|
manifique
|
||||||
net-ldap
|
net-ldap
|
||||||
nostr!
|
nostr
|
||||||
pagy (~> 6.0, >= 6.0.2)
|
pagy (~> 6.0, >= 6.0.2)
|
||||||
pg (~> 1.2.3)
|
pg (~> 1.5)
|
||||||
puma (~> 4.1)
|
puma (~> 4.1)
|
||||||
rails (~> 7.0.2)
|
rails (~> 7.1)
|
||||||
rails-controller-testing
|
rails-controller-testing
|
||||||
rails-settings-cached (~> 2.8.3)
|
rails-settings-cached (~> 2.8.3)
|
||||||
rqrcode (~> 2.0)
|
rqrcode (~> 2.0)
|
||||||
@@ -506,14 +532,14 @@ DEPENDENCIES
|
|||||||
sidekiq-scheduler
|
sidekiq-scheduler
|
||||||
solargraph
|
solargraph
|
||||||
sprockets-rails
|
sprockets-rails
|
||||||
sqlite3 (~> 1.4)
|
sqlite3 (~> 1.7.2)
|
||||||
stimulus-rails
|
stimulus-rails
|
||||||
turbo-rails
|
turbo-rails
|
||||||
tzinfo-data
|
tzinfo-data
|
||||||
view_component
|
view_component
|
||||||
warden
|
warden
|
||||||
web-console (>= 3.3.0)
|
web-console (~> 4.2)
|
||||||
webmock
|
webmock
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
2.3.7
|
2.5.5
|
||||||
|
|||||||
46
README.md
@@ -14,8 +14,10 @@ so:
|
|||||||
|
|
||||||
1. Make sure [Docker Compose is installed][1] and Docker is running (included in
|
1. Make sure [Docker Compose is installed][1] and Docker is running (included in
|
||||||
Docker Desktop)
|
Docker Desktop)
|
||||||
3. Run `docker compose up` and wait until 389ds announces its successful start
|
3. Run `docker compose up --build` and wait until all services have started
|
||||||
in the log output
|
(389ds might take an extra minute to be ready). This will take a while when
|
||||||
|
running for the first time, so you might want to do something else in the
|
||||||
|
meantime.
|
||||||
4. `docker-compose exec ldap dsconf localhost backend create --suffix="dc=kosmos,dc=org" --be-name="dev"`
|
4. `docker-compose exec ldap dsconf localhost backend create --suffix="dc=kosmos,dc=org" --be-name="dev"`
|
||||||
5. `docker compose run web rails ldap:setup`
|
5. `docker compose run web rails ldap:setup`
|
||||||
6. `docker compose run web rails db:setup`
|
6. `docker compose run web rails db:setup`
|
||||||
@@ -28,38 +30,44 @@ have the password "user is user".
|
|||||||
|
|
||||||
### Rails app
|
### Rails app
|
||||||
|
|
||||||
|
_Note: when using Docker Compose, prefix the following commands with `docker-compose
|
||||||
|
run web`._
|
||||||
|
|
||||||
Installing dependencies:
|
Installing dependencies:
|
||||||
|
|
||||||
bundle install
|
bundle install
|
||||||
yarn install
|
yarn install
|
||||||
|
|
||||||
Setting up local database (SQLite):
|
Migrating the local database (after schema changes):
|
||||||
|
|
||||||
bundle exec rails db:create
|
|
||||||
bundle exec rails db:migrate
|
bundle exec rails db:migrate
|
||||||
|
|
||||||
Running the dev server and auto-building CSS files on change:
|
Running the dev server, and auto-building CSS files on change _(automatic with Docker Compose)_:
|
||||||
|
|
||||||
bin/dev
|
bin/dev
|
||||||
|
|
||||||
Running the background workers (requires Redis):
|
Running the background workers (requires Redis) _(automatic with Docker Compose)_:
|
||||||
|
|
||||||
bundle exec sidekiq -C config/sidekiq.yml
|
bundle exec sidekiq -C config/sidekiq.yml
|
||||||
|
|
||||||
Running all specs:
|
Running the test suite:
|
||||||
|
|
||||||
bundle exec rspec
|
bundle exec rspec
|
||||||
|
|
||||||
### Docker (Compose)
|
Running the test suite with Docker Compose requires overriding the Rails
|
||||||
|
environment:
|
||||||
|
|
||||||
There is a working Docker Compose config file, which define a number of services including
|
docker-compose run -e "RAILS_ENV=test" web rspec
|
||||||
an app server for Rails as well as a local 389ds (LDAP) server.
|
|
||||||
|
|
||||||
For Rails developers, you probably just want to start the LDAP server: `docker-compose up ldap`,
|
### Docker Compose
|
||||||
listening on port 389 on your machine.
|
|
||||||
|
|
||||||
You can pick and choose your services adding them by name (listed in `docker-compose.yml`) at
|
Services/containers are configured in `docker-compose.yml`.
|
||||||
the end of the docker compose command. eg. `docker compose up ldap redis`
|
|
||||||
|
You can run services selectively, for example if you want to run the Rails app
|
||||||
|
and test suite on the host machine. Just add the service names of the
|
||||||
|
containers you want to run to the `up` command, like so:
|
||||||
|
|
||||||
|
docker-compose up ldap redis
|
||||||
|
|
||||||
#### LDAP server
|
#### LDAP server
|
||||||
|
|
||||||
@@ -76,13 +84,15 @@ Now you can seed the back-end with data using this Rails task:
|
|||||||
The setup task will first delete any existing entries in the directory tree
|
The setup task will first delete any existing entries in the directory tree
|
||||||
("dc=kosmos,dc=org"), and then create our development entries.
|
("dc=kosmos,dc=org"), and then create our development entries.
|
||||||
|
|
||||||
Note that all 389ds data is stored in `tmp/389ds`. So if you want to start over
|
Note that all 389ds data is stored in the `389ds-data` volume. So if you want
|
||||||
with a fresh installation, delete both that directory as well as the container.
|
to start over with a fresh installation, delete both that volume as well as the
|
||||||
|
container.
|
||||||
|
|
||||||
#### Minio / RS
|
#### Minio / remoteStorage
|
||||||
|
|
||||||
If you want to run remoteStorage accounts locally, you will have to create the
|
If you want to run remoteStorage accounts locally, you will have to create the
|
||||||
respective bucket first:
|
respective bucket first. With the `minio` container running (run by default
|
||||||
|
when using Docker Compose), follow these steps:
|
||||||
|
|
||||||
* `docker compose up web redis minio liquor-cabinet`
|
* `docker compose up web redis minio liquor-cabinet`
|
||||||
* Head to http://localhost:9001 and log in with user `minioadmin`, password
|
* Head to http://localhost:9001 and log in with user `minioadmin`, password
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<% if @image_url %>
|
||||||
|
<%= image_tag @image_url, class: "h-full w-full" %>
|
||||||
|
<% else %>
|
||||||
|
<%= render partial: "icons/remotestorage", locals: { custom_class: "h-full w-full p-0.5 text-gray-200" } %>
|
||||||
|
<% end %>
|
||||||
21
app/components/app_catalog/web_app_icon_component.rb
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module AppCatalog
|
||||||
|
class WebAppIconComponent < ViewComponent::Base
|
||||||
|
def initialize(web_app:)
|
||||||
|
if web_app&.icon&.attached?
|
||||||
|
@image_url = image_url_for(web_app.icon)
|
||||||
|
elsif web_app&.apple_touch_icon&.attached?
|
||||||
|
@image_url = image_url_for(web_app.apple_touch_icon)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def image_url_for(attachment)
|
||||||
|
if Setting.s3_enabled?
|
||||||
|
s3_image_url(attachment)
|
||||||
|
else
|
||||||
|
Rails.application.routes.url_helpers.rails_blob_path(attachment, only_path: true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -2,13 +2,21 @@
|
|||||||
<div class="relative inline-block">
|
<div class="relative inline-block">
|
||||||
<div role="button" tabindex="0" data-dropdown-target="button"
|
<div role="button" tabindex="0" data-dropdown-target="button"
|
||||||
class="inline-block select-none">
|
class="inline-block select-none">
|
||||||
|
<% if @size == :large %>
|
||||||
<span class="appearance-none flex items-center inline-block">
|
<span class="appearance-none flex items-center inline-block">
|
||||||
<span class="p-2 bg-gray-50 hover:bg-gray-100 rounded-full">
|
<span class="p-2 bg-gray-50 hover:bg-gray-100 rounded-full">
|
||||||
<%= render partial: "icons/kebab-menu", locals: {
|
<%= render partial: "icons/#{@icon_name}",
|
||||||
custom_class: "inline text-gray-500 h-6 w-6"
|
locals: { custom_class: "inline text-gray-500 h-6 w-6" } %>
|
||||||
} %>
|
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
<% elsif @size == :small %>
|
||||||
|
<span class="appearance-none flex items-center inline-block">
|
||||||
|
<span class="text-gray-500 hover:text-blue-600">
|
||||||
|
<%= render partial: "icons/#{@icon_name}",
|
||||||
|
locals: { custom_class: "inline h-4 w-4" } %>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<div data-dropdown-target="menu"
|
<div data-dropdown-target="menu"
|
||||||
data-transition-enter="transition ease-out duration-200"
|
data-transition-enter="transition ease-out duration-200"
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class DropdownComponent < ViewComponent::Base
|
class DropdownComponent < ViewComponent::Base
|
||||||
|
def initialize(size: :large, icon_name: "kebap-menu")
|
||||||
|
@size = size.to_sym
|
||||||
|
@icon_name = icon_name
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,7 +5,9 @@
|
|||||||
} : nil do %>
|
} : nil do %>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<label class="font-bold mb-1"><%= @title %></label>
|
<label class="font-bold mb-1"><%= @title %></label>
|
||||||
|
<% if @description.present? %>
|
||||||
<p class="text-gray-500"><%= @descripton %></p>
|
<p class="text-gray-500"><%= @descripton %></p>
|
||||||
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<div class="relative ml-4 inline-flex flex-shrink-0">
|
<div class="relative ml-4 inline-flex flex-shrink-0">
|
||||||
<%= render FormElements::ToggleComponent.new(
|
<%= render FormElements::ToggleComponent.new(
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
module FormElements
|
module FormElements
|
||||||
class FieldsetToggleComponent < ViewComponent::Base
|
class FieldsetToggleComponent < ViewComponent::Base
|
||||||
def initialize(tag: "li", form: nil, attribute: nil, field_name: nil,
|
def initialize(tag: "li", form: nil, attribute: nil, field_name: nil,
|
||||||
enabled: false, input_enabled: true, title:, description:)
|
enabled: false, input_enabled: true, title:, description: nil)
|
||||||
@tag = tag
|
@tag = tag
|
||||||
@form = form
|
@form = form
|
||||||
@attribute = attribute
|
@attribute = attribute
|
||||||
|
|||||||
@@ -18,9 +18,11 @@
|
|||||||
<div class="m-1 bg-white rounded shadow">
|
<div class="m-1 bg-white rounded shadow">
|
||||||
<div class="p-8">
|
<div class="p-8">
|
||||||
<%= content %>
|
<%= content %>
|
||||||
|
<% if @show_close_button %>
|
||||||
<div class="flex justify-end items-center flex-wrap mt-6">
|
<div class="flex justify-end items-center flex-wrap mt-6">
|
||||||
<button class="btn-md btn-blue" data-action="click->modal#close:prevent">Close</button>
|
<button class="btn-md btn-blue" data-action="click->modal#close:prevent">Close</button>
|
||||||
</div>
|
</div>
|
||||||
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,2 +1,5 @@
|
|||||||
class ModalComponent < ViewComponent::Base
|
class ModalComponent < ViewComponent::Base
|
||||||
|
def initialize(show_close_button: true)
|
||||||
|
@show_close_button = show_close_button
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-4">
|
||||||
<div class="h-16 w-16 flex-none">
|
<div class="h-16 w-16 flex-none">
|
||||||
<%= image_tag s3_image_url(@web_app.icon), class: "h-full w-full" %>
|
<%= render AppCatalog::WebAppIconComponent.new(web_app: @web_app) %>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-grow">
|
<div class="flex-grow">
|
||||||
<h4 class="mb-1 text-lg font-bold">
|
<h4 class="mb-1 text-lg font-bold">
|
||||||
<%= @web_app.name %>
|
<%= @web_app&.name || @auth.app_name %>
|
||||||
</h4>
|
</h4>
|
||||||
<p class="text-sm text-gray-500">
|
<p class="text-sm text-gray-500">
|
||||||
<%= @auth.client_id %>
|
<%= @auth.client_id %>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
class Admin::Settings::RegistrationsController < Admin::SettingsController
|
class Admin::Settings::RegistrationsController < Admin::SettingsController
|
||||||
def index
|
def show
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def update
|
||||||
update_settings
|
update_settings
|
||||||
|
|
||||||
redirect_to admin_settings_registrations_path, flash: {
|
redirect_to admin_settings_registrations_path, flash: {
|
||||||
|
|||||||
@@ -1,19 +1,32 @@
|
|||||||
class Admin::Settings::ServicesController < Admin::SettingsController
|
class Admin::Settings::ServicesController < Admin::SettingsController
|
||||||
def index
|
before_action :set_service, only: [:show, :update]
|
||||||
@service = params[:s]
|
|
||||||
|
|
||||||
if @service.blank?
|
def index
|
||||||
redirect_to admin_settings_services_path(params: { s: "btcpay" })
|
redirect_to admin_settings_service_path("btcpay")
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def show
|
||||||
service = params.require(:service)
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
update_settings
|
update_settings
|
||||||
|
|
||||||
redirect_to admin_settings_services_path(params: { s: service }), flash: {
|
redirect_to admin_settings_service_path(@service), flash: {
|
||||||
success: "Settings saved"
|
success: "Settings saved"
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_subsection
|
||||||
|
@subsection = "services"
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_service
|
||||||
|
@service = params[:service]
|
||||||
|
|
||||||
|
if @service.blank?
|
||||||
|
redirect_to admin_settings_services_path and return
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class Admin::SettingsController < Admin::BaseController
|
|||||||
end
|
end
|
||||||
|
|
||||||
if @errors.any?
|
if @errors.any?
|
||||||
render :index and return
|
render :show and return
|
||||||
end
|
end
|
||||||
|
|
||||||
changed_keys.each do |key|
|
changed_keys.each do |key|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
class Admin::UsersController < Admin::BaseController
|
class Admin::UsersController < Admin::BaseController
|
||||||
before_action :set_user, only: [:show]
|
before_action :set_user, except: [:index]
|
||||||
before_action :set_current_section
|
before_action :set_current_section
|
||||||
|
|
||||||
|
# GET /admin/users
|
||||||
def index
|
def index
|
||||||
ldap = LdapService.new
|
ldap = LdapService.new
|
||||||
@ou = params[:ou] || Setting.primary_domain
|
@ou = Setting.primary_domain
|
||||||
@orgs = ldap.fetch_organizations
|
|
||||||
@pagy, @users = pagy(User.where(ou: @ou).order(cn: :asc))
|
@pagy, @users = pagy(User.where(ou: @ou).order(cn: :asc))
|
||||||
|
|
||||||
@stats = {
|
@stats = {
|
||||||
@@ -14,6 +14,7 @@ class Admin::UsersController < Admin::BaseController
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# GET /admin/users/:username
|
||||||
def show
|
def show
|
||||||
if Setting.lndhub_admin_enabled?
|
if Setting.lndhub_admin_enabled?
|
||||||
@lndhub_user = @user.lndhub_user
|
@lndhub_user = @user.lndhub_user
|
||||||
@@ -21,14 +22,38 @@ class Admin::UsersController < Admin::BaseController
|
|||||||
|
|
||||||
@services_enabled = @user.services_enabled
|
@services_enabled = @user.services_enabled
|
||||||
|
|
||||||
@avatar = LdapManager::FetchAvatar.call(cn: @user.cn, ou: @user.ou)
|
@avatar = LdapManager::FetchAvatar.call(cn: @user.cn)
|
||||||
|
end
|
||||||
|
|
||||||
|
# POST /admin/users/:username/invitations
|
||||||
|
def create_invitations
|
||||||
|
amount = params[:amount].to_i
|
||||||
|
notify_user = ActiveRecord::Type::Boolean.new.cast(params[:notify_user])
|
||||||
|
|
||||||
|
CreateInvitations.call(user: @user, amount: amount, notify: notify_user)
|
||||||
|
|
||||||
|
redirect_to admin_user_path(@user.cn), flash: {
|
||||||
|
success: "Added #{amount} invitations to #{@user.cn}'s account"
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
# DELETE /admin/users/:username/invitations
|
||||||
|
def delete_invitations
|
||||||
|
invitations = @user.invitations.unused
|
||||||
|
amount = invitations.count
|
||||||
|
|
||||||
|
invitations.destroy_all
|
||||||
|
|
||||||
|
redirect_to admin_user_path(@user.cn), flash: {
|
||||||
|
success: "Removed #{amount} invitations from #{@user.cn}'s account"
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def set_user
|
def set_user
|
||||||
address = params[:address].split("@")
|
@user = User.find_by(cn: params[:username], ou: Setting.primary_domain)
|
||||||
@user = User.where(cn: address.first, ou: address.last).first
|
http_status :not_found unless @user
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_current_section
|
def set_current_section
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
class Api::BtcpayController < Api::BaseController
|
class Api::BtcpayController < Api::BaseController
|
||||||
before_action :require_feature_enabled
|
before_action :require_feature_enabled
|
||||||
|
before_action :set_cors_access_control_headers
|
||||||
|
|
||||||
def onchain_btc_balance
|
def onchain_btc_balance
|
||||||
balance = BtcpayManager::FetchOnchainWalletBalance.call
|
balance = BtcpayManager::FetchOnchainWalletBalance.call
|
||||||
@@ -26,4 +27,11 @@ class Api::BtcpayController < Api::BaseController
|
|||||||
http_status :not_found and return
|
http_status :not_found and return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def set_cors_access_control_headers
|
||||||
|
return unless Rails.env.development?
|
||||||
|
headers['Access-Control-Allow-Origin'] = "*"
|
||||||
|
headers['Access-Control-Allow-Headers'] = "*"
|
||||||
|
headers['Access-Control-Allow-Methods'] = "GET"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
class LnurlpayController < ApplicationController
|
class LnurlpayController < ApplicationController
|
||||||
before_action :check_feature_enabled
|
before_action :check_service_available
|
||||||
before_action :find_user_by_address
|
before_action :find_user
|
||||||
|
|
||||||
MIN_SATS = 10
|
MIN_SATS = 10
|
||||||
MAX_SATS = 1_000_000
|
MAX_SATS = 1_000_000
|
||||||
@@ -9,7 +9,7 @@ class LnurlpayController < ApplicationController
|
|||||||
def index
|
def index
|
||||||
render json: {
|
render json: {
|
||||||
status: "OK",
|
status: "OK",
|
||||||
callback: "https://accounts.kosmos.org/lnurlpay/#{@user.address}/invoice",
|
callback: "https://#{Setting.accounts_domain}/lnurlpay/#{@user.cn}/invoice",
|
||||||
tag: "payRequest",
|
tag: "payRequest",
|
||||||
maxSendable: MAX_SATS * 1000, # msat
|
maxSendable: MAX_SATS * 1000, # msat
|
||||||
minSendable: MIN_SATS * 1000, # msat
|
minSendable: MIN_SATS * 1000, # msat
|
||||||
@@ -34,8 +34,8 @@ class LnurlpayController < ApplicationController
|
|||||||
|
|
||||||
def invoice
|
def invoice
|
||||||
amount = params[:amount].to_i / 1000 # msats
|
amount = params[:amount].to_i / 1000 # msats
|
||||||
address = params[:address]
|
|
||||||
comment = params[:comment] || ""
|
comment = params[:comment] || ""
|
||||||
|
address = @user.address
|
||||||
|
|
||||||
if !valid_amount?(amount)
|
if !valid_amount?(amount)
|
||||||
render json: { status: "ERROR", reason: "Invalid amount" }
|
render json: { status: "ERROR", reason: "Invalid amount" }
|
||||||
@@ -69,9 +69,8 @@ class LnurlpayController < ApplicationController
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def find_user_by_address
|
def find_user
|
||||||
address = params[:address].split("@")
|
@user = User.where(cn: params[:username], ou: Setting.primary_domain).first
|
||||||
@user = User.where(cn: address.first, ou: address.last).first
|
|
||||||
http_status :not_found if @user.nil?
|
http_status :not_found if @user.nil?
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -89,7 +88,7 @@ class LnurlpayController < ApplicationController
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def check_feature_enabled
|
def check_service_available
|
||||||
http_status :not_found unless Setting.lndhub_enabled?
|
http_status :not_found unless Setting.lndhub_enabled?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
34
app/controllers/services/email_controller.rb
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
class Services::EmailController < Services::BaseController
|
||||||
|
before_action :authenticate_user!
|
||||||
|
before_action :require_service_available
|
||||||
|
before_action :require_feature_enabled
|
||||||
|
|
||||||
|
def show
|
||||||
|
ldap_entry = current_user.ldap_entry
|
||||||
|
|
||||||
|
@service_enabled = ldap_entry[:email_password].present?
|
||||||
|
@maildrop = ldap_entry[:email_maildrop]
|
||||||
|
@email_forwarding_active = @maildrop.present? &&
|
||||||
|
@maildrop.split("@").first != current_user.cn
|
||||||
|
end
|
||||||
|
|
||||||
|
def new_password
|
||||||
|
if session[:new_email_password].present?
|
||||||
|
@new_password = session.delete(:new_email_password)
|
||||||
|
else
|
||||||
|
redirect_to setting_path(:email)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def require_service_available
|
||||||
|
http_status :not_found unless Setting.email_enabled?
|
||||||
|
end
|
||||||
|
|
||||||
|
def require_feature_enabled
|
||||||
|
unless Flipper.enabled?(:email, current_user)
|
||||||
|
http_status :forbidden
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
class Services::RemotestorageController < Services::BaseController
|
class Services::RemotestorageController < Services::BaseController
|
||||||
before_action :authenticate_user!
|
before_action :authenticate_user!
|
||||||
before_action :require_feature_enabled
|
|
||||||
before_action :require_service_available
|
before_action :require_service_available
|
||||||
|
before_action :require_feature_enabled
|
||||||
|
|
||||||
# Dashboard
|
# Dashboard
|
||||||
def show
|
def show
|
||||||
@@ -14,13 +14,13 @@ class Services::RemotestorageController < Services::BaseController
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def require_service_available
|
||||||
|
http_status :not_found unless Setting.remotestorage_enabled?
|
||||||
|
end
|
||||||
|
|
||||||
def require_feature_enabled
|
def require_feature_enabled
|
||||||
unless Flipper.enabled?(:remotestorage, current_user)
|
unless Flipper.enabled?(:remotestorage, current_user)
|
||||||
http_status :forbidden
|
http_status :forbidden
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def require_service_available
|
|
||||||
http_status :not_found unless Setting.remotestorage_enabled?
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
require 'securerandom'
|
require "securerandom"
|
||||||
|
require "bcrypt"
|
||||||
|
|
||||||
class SettingsController < ApplicationController
|
class SettingsController < ApplicationController
|
||||||
before_action :authenticate_user!
|
before_action :authenticate_user!
|
||||||
before_action :set_main_nav_section
|
before_action :set_main_nav_section
|
||||||
before_action :set_settings_section, only: [:show, :update, :update_email]
|
before_action :set_settings_section, only: [:show, :update, :update_email, :reset_email_password]
|
||||||
before_action :set_user, only: [:show, :update, :update_email]
|
before_action :set_user, only: [:show, :update, :update_email, :reset_email_password]
|
||||||
|
|
||||||
def index
|
def index
|
||||||
redirect_to setting_path(:profile)
|
redirect_to setting_path(:profile)
|
||||||
@@ -23,11 +24,11 @@ class SettingsController < ApplicationController
|
|||||||
|
|
||||||
if @user.save
|
if @user.save
|
||||||
if @user.display_name && (@user.display_name != @user.ldap_entry[:display_name])
|
if @user.display_name && (@user.display_name != @user.ldap_entry[:display_name])
|
||||||
LdapManager::UpdateDisplayName.call(@user.dn, @user.display_name)
|
LdapManager::UpdateDisplayName.call(dn: @user.dn, display_name: @user.display_name)
|
||||||
end
|
end
|
||||||
|
|
||||||
if @user.avatar_new.present?
|
if @user.avatar_new.present?
|
||||||
LdapManager::UpdateAvatar.call(@user.dn, @user.avatar_new)
|
LdapManager::UpdateAvatar.call(dn: @user.dn, file: @user.avatar_new)
|
||||||
end
|
end
|
||||||
|
|
||||||
redirect_to setting_path(@settings_section), flash: {
|
redirect_to setting_path(@settings_section), flash: {
|
||||||
@@ -40,7 +41,7 @@ class SettingsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def update_email
|
def update_email
|
||||||
if @user.valid_ldap_authentication?(email_params[:current_password])
|
if @user.valid_ldap_authentication?(security_params[:current_password])
|
||||||
if @user.update email: email_params[:email]
|
if @user.update email: email_params[:email]
|
||||||
redirect_to setting_path(:account), flash: {
|
redirect_to setting_path(:account), flash: {
|
||||||
notice: 'Please confirm your new address using the confirmation link we just sent you.'
|
notice: 'Please confirm your new address using the confirmation link we just sent you.'
|
||||||
@@ -56,6 +57,28 @@ class SettingsController < ApplicationController
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def reset_email_password
|
||||||
|
@user.current_password = security_params[:current_password]
|
||||||
|
|
||||||
|
if @user.valid_ldap_authentication?(@user.current_password)
|
||||||
|
@user.current_password = nil
|
||||||
|
session[:new_email_password] = generate_email_password
|
||||||
|
hashed_password = hash_email_password(session[:new_email_password])
|
||||||
|
LdapManager::UpdateEmailPassword.call(dn: @user.dn, password_hash: hashed_password)
|
||||||
|
|
||||||
|
if @user.ldap_entry[:email_maildrop] != @user.address
|
||||||
|
LdapManager::UpdateEmailMaildrop.call(dn: @user.dn, address: @user.address)
|
||||||
|
end
|
||||||
|
|
||||||
|
redirect_to new_password_services_email_path
|
||||||
|
else
|
||||||
|
@validation_errors = {
|
||||||
|
current_password: [ "Wrong password. Try again!" ]
|
||||||
|
}
|
||||||
|
render :show, status: :forbidden
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def reset_password
|
def reset_password
|
||||||
current_user.send_reset_password_instructions
|
current_user.send_reset_password_instructions
|
||||||
sign_out current_user
|
sign_out current_user
|
||||||
@@ -65,8 +88,8 @@ class SettingsController < ApplicationController
|
|||||||
|
|
||||||
def set_nostr_pubkey
|
def set_nostr_pubkey
|
||||||
signed_event = nostr_event_params[:signed_event].to_h.symbolize_keys
|
signed_event = nostr_event_params[:signed_event].to_h.symbolize_keys
|
||||||
is_valid_id = NostrManager::ValidateId.call(signed_event)
|
is_valid_id = NostrManager::ValidateId.call(event: signed_event)
|
||||||
is_valid_sig = NostrManager::VerifySignature.call(signed_event)
|
is_valid_sig = NostrManager::VerifySignature.call(event: signed_event)
|
||||||
is_correct_content = signed_event[:content] == "Connect my public key to #{current_user.address} (confirmation #{session[:shared_secret]})"
|
is_correct_content = signed_event[:content] == "Connect my public key to #{current_user.address} (confirmation #{session[:shared_secret]})"
|
||||||
|
|
||||||
unless is_valid_id && is_valid_sig && is_correct_content
|
unless is_valid_id && is_valid_sig && is_correct_content
|
||||||
@@ -110,7 +133,10 @@ class SettingsController < ApplicationController
|
|||||||
|
|
||||||
def set_settings_section
|
def set_settings_section
|
||||||
@settings_section = params[:section]
|
@settings_section = params[:section]
|
||||||
allowed_sections = [:profile, :account, :lightning, :xmpp, :experiments]
|
allowed_sections = [
|
||||||
|
:profile, :account, :xmpp, :email, :lightning, :remotestorage,
|
||||||
|
:experiments
|
||||||
|
]
|
||||||
|
|
||||||
unless allowed_sections.include?(@settings_section.to_sym)
|
unless allowed_sections.include?(@settings_section.to_sym)
|
||||||
redirect_to setting_path(:profile)
|
redirect_to setting_path(:profile)
|
||||||
@@ -124,12 +150,17 @@ class SettingsController < ApplicationController
|
|||||||
def user_params
|
def user_params
|
||||||
params.require(:user).permit(:display_name, :avatar, preferences: [
|
params.require(:user).permit(:display_name, :avatar, preferences: [
|
||||||
:lightning_notify_sats_received,
|
:lightning_notify_sats_received,
|
||||||
|
:remotestorage_notify_auth_created,
|
||||||
:xmpp_exchange_contacts_with_invitees
|
:xmpp_exchange_contacts_with_invitees
|
||||||
])
|
])
|
||||||
end
|
end
|
||||||
|
|
||||||
def email_params
|
def email_params
|
||||||
params.require(:user).permit(:email, :current_password)
|
params.require(:user).permit(:email)
|
||||||
|
end
|
||||||
|
|
||||||
|
def security_params
|
||||||
|
params.require(:user).permit(:current_password)
|
||||||
end
|
end
|
||||||
|
|
||||||
def nostr_event_params
|
def nostr_event_params
|
||||||
@@ -137,4 +168,14 @@ class SettingsController < ApplicationController
|
|||||||
:id, :pubkey, :created_at, :kind, :tags, :content, :sig
|
:id, :pubkey, :created_at, :kind, :tags, :content, :sig
|
||||||
])
|
])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def generate_email_password
|
||||||
|
characters = [('a'..'z'), ('A'..'Z'), (0..9)].map(&:to_a).flatten
|
||||||
|
SecureRandom.random_bytes(16).each_byte.map { |b| characters[b % characters.length] }.join
|
||||||
|
end
|
||||||
|
|
||||||
|
def hash_email_password(password)
|
||||||
|
salt = BCrypt::Engine.generate_salt
|
||||||
|
BCrypt::Engine.hash_secret(password, salt)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -96,13 +96,13 @@ class SignupController < ApplicationController
|
|||||||
session[:new_user] = nil
|
session[:new_user] = nil
|
||||||
session[:validation_error] = nil
|
session[:validation_error] = nil
|
||||||
|
|
||||||
CreateAccount.call(
|
CreateAccount.call(account: {
|
||||||
username: @user.cn,
|
username: @user.cn,
|
||||||
domain: Setting.primary_domain,
|
domain: Setting.primary_domain,
|
||||||
email: @user.email,
|
email: @user.email,
|
||||||
password: @user.password,
|
password: @user.password,
|
||||||
invitation: @invitation
|
invitation: @invitation
|
||||||
)
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_context
|
def set_context
|
||||||
|
|||||||
@@ -54,7 +54,8 @@ class WebfingerController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def allow_cross_origin_requests
|
def allow_cross_origin_requests
|
||||||
headers['Access-Control-Allow-Origin'] = '*'
|
return unless Rails.env.development?
|
||||||
headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, OPTIONS'
|
headers['Access-Control-Allow-Origin'] = "*"
|
||||||
|
headers['Access-Control-Allow-Methods'] = "GET"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { Controller } from "@hotwired/stimulus"
|
||||||
|
|
||||||
|
export default class extends Controller {
|
||||||
|
static targets = [ "resetPasswordButton", "currentPasswordField" ]
|
||||||
|
static values = { validationFailed: Boolean }
|
||||||
|
|
||||||
|
connect () {
|
||||||
|
if (this.validationFailedValue) return;
|
||||||
|
|
||||||
|
this.element.querySelectorAll(".initial-hidden").forEach(el => {
|
||||||
|
el.classList.add("hidden");
|
||||||
|
})
|
||||||
|
this.element.querySelectorAll(".initial-visible").forEach(el => {
|
||||||
|
el.classList.remove("hidden");
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
showPasswordReset () {
|
||||||
|
this.element.querySelectorAll(".initial-visible").forEach(el => {
|
||||||
|
el.classList.add("hidden");
|
||||||
|
})
|
||||||
|
this.element.querySelectorAll(".initial-hidden").forEach(el => {
|
||||||
|
el.classList.remove("hidden");
|
||||||
|
})
|
||||||
|
this.currentPasswordFieldTarget.select();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,4 @@
|
|||||||
import { Controller } from "@hotwired/stimulus"
|
import { Controller } from "@hotwired/stimulus"
|
||||||
import { bech32 } from "bech32"
|
|
||||||
|
|
||||||
function hexToBytes (hex) {
|
|
||||||
let bytes = []
|
|
||||||
for (let c = 0; c < hex.length; c += 2) {
|
|
||||||
bytes.push(parseInt(hex.substr(c, 2), 16))
|
|
||||||
}
|
|
||||||
return bytes
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connects to data-controller="settings--nostr-pubkey"
|
// Connects to data-controller="settings--nostr-pubkey"
|
||||||
export default class extends Controller {
|
export default class extends Controller {
|
||||||
@@ -15,10 +6,6 @@ export default class extends Controller {
|
|||||||
static values = { userAddress: String, pubkeyHex: String, sharedSecret: String }
|
static values = { userAddress: String, pubkeyHex: String, sharedSecret: String }
|
||||||
|
|
||||||
connect () {
|
connect () {
|
||||||
if (this.hasPubkeyHexValue && this.pubkeyHexValue.length > 0) {
|
|
||||||
this.pubkeyBech32InputTarget.value = this.pubkeyBech32
|
|
||||||
}
|
|
||||||
|
|
||||||
if (window.nostr) {
|
if (window.nostr) {
|
||||||
if (this.hasSetPubkeyTarget) {
|
if (this.hasSetPubkeyTarget) {
|
||||||
this.setPubkeyTarget.disabled = false
|
this.setPubkeyTarget.disabled = false
|
||||||
@@ -53,11 +40,6 @@ export default class extends Controller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get pubkeyBech32 () {
|
|
||||||
const words = bech32.toWords(hexToBytes(this.pubkeyHexValue))
|
|
||||||
return bech32.encode('npub', words)
|
|
||||||
}
|
|
||||||
|
|
||||||
get csrfToken () {
|
get csrfToken () {
|
||||||
const element = document.head.querySelector('meta[name="csrf-token"]')
|
const element = document.head.querySelector('meta[name="csrf-token"]')
|
||||||
return element.getAttribute("content")
|
return element.getAttribute("content")
|
||||||
|
|||||||
@@ -5,4 +5,22 @@ class NotificationMailer < ApplicationMailer
|
|||||||
@subject = "Sats received"
|
@subject = "Sats received"
|
||||||
mail to: @user.email, subject: @subject
|
mail to: @user.email, subject: @subject
|
||||||
end
|
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
|
||||||
|
|
||||||
|
def new_invitations_available
|
||||||
|
@user = params[:user]
|
||||||
|
@subject = "New invitations added to your account"
|
||||||
|
mail to: @user.email, subject: @subject
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
class AppCatalog::WebApp < ApplicationRecord
|
class AppCatalog::WebApp < ApplicationRecord
|
||||||
store :metadata, coder: JSON
|
store :metadata, coder: JSON
|
||||||
|
|
||||||
has_many :remote_storage_authorizations
|
has_many :remote_storage_authorizations, dependent: :destroy
|
||||||
|
|
||||||
has_one_attached :icon
|
has_one_attached :icon
|
||||||
has_one_attached :apple_touch_icon
|
has_one_attached :apple_touch_icon
|
||||||
@@ -11,6 +11,6 @@ class AppCatalog::WebApp < ApplicationRecord
|
|||||||
if: Proc.new { |a| a.url.present? }
|
if: Proc.new { |a| a.url.present? }
|
||||||
|
|
||||||
def update_metadata
|
def update_metadata
|
||||||
AppCatalogManager::UpdateMetadata.call(self)
|
AppCatalogManager::UpdateMetadata.call(app: self)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ class RemoteStorageAuthorization < ApplicationRecord
|
|||||||
belongs_to :user
|
belongs_to :user
|
||||||
belongs_to :web_app, class_name: "AppCatalog::WebApp", optional: true
|
belongs_to :web_app, class_name: "AppCatalog::WebApp", optional: true
|
||||||
|
|
||||||
serialize :permissions
|
serialize :permissions unless Rails.env.production?
|
||||||
|
|
||||||
validates_presence_of :permissions
|
validates_presence_of :permissions
|
||||||
validates_presence_of :client_id
|
validates_presence_of :client_id
|
||||||
@@ -18,7 +18,7 @@ class RemoteStorageAuthorization < ApplicationRecord
|
|||||||
before_create :store_token_in_redis
|
before_create :store_token_in_redis
|
||||||
before_create :find_or_create_web_app
|
before_create :find_or_create_web_app
|
||||||
after_create :schedule_token_expiry
|
after_create :schedule_token_expiry
|
||||||
# after_create :notify_user
|
after_create :notify_user
|
||||||
before_destroy :delete_token_from_redis
|
before_destroy :delete_token_from_redis
|
||||||
after_destroy :remove_token_expiry_job
|
after_destroy :remove_token_expiry_job
|
||||||
|
|
||||||
@@ -93,4 +93,22 @@ class RemoteStorageAuthorization < ApplicationRecord
|
|||||||
rescue URI::InvalidURIError
|
rescue URI::InvalidURIError
|
||||||
false
|
false
|
||||||
end
|
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
|
end
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ class Setting < RailsSettings::Base
|
|||||||
field :redis_url, type: :string,
|
field :redis_url, type: :string,
|
||||||
default: ENV["REDIS_URL"] || "redis://localhost:6379/0"
|
default: ENV["REDIS_URL"] || "redis://localhost:6379/0"
|
||||||
|
|
||||||
|
field :s3_enabled, type: :boolean,
|
||||||
|
default: ENV["S3_ENABLED"] && ENV["S3_ENABLED"].to_s != "false"
|
||||||
|
|
||||||
#
|
#
|
||||||
# Registrations
|
# Registrations
|
||||||
#
|
#
|
||||||
@@ -168,4 +171,30 @@ class Setting < RailsSettings::Base
|
|||||||
|
|
||||||
field :rs_redis_url, type: :string,
|
field :rs_redis_url, type: :string,
|
||||||
default: ENV["RS_REDIS_URL"] || "redis://localhost:6379/1"
|
default: ENV["RS_REDIS_URL"] || "redis://localhost:6379/1"
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# E-Mail Service
|
||||||
|
#
|
||||||
|
|
||||||
|
field :email_enabled, type: :boolean,
|
||||||
|
default: ENV["EMAIL_SMTP_HOST"].present?
|
||||||
|
|
||||||
|
# field :email_smtp_host, type: :string,
|
||||||
|
# default: ENV["EMAIL_SMTP_HOST"].presence
|
||||||
|
#
|
||||||
|
# field :email_smtp_port, type: :string,
|
||||||
|
# default: ENV["EMAIL_SMTP_PORT"].presence || 587
|
||||||
|
#
|
||||||
|
# field :email_smtp_enable_starttls, type: :string,
|
||||||
|
# default: ENV["EMAIL_SMTP_PORT"].presence || true
|
||||||
|
#
|
||||||
|
# field :email_auth_method, type: :string,
|
||||||
|
# default: ENV["EMAIL_AUTH_METHOD"].presence || "plain"
|
||||||
|
#
|
||||||
|
# field :email_imap_host, type: :string,
|
||||||
|
# default: ENV["EMAIL_IMAP_HOST"].presence
|
||||||
|
#
|
||||||
|
# field :email_imap_port, type: :string,
|
||||||
|
# default: ENV["EMAIL_IMAP_PORT"].presence || 993
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
|
require 'nostr'
|
||||||
|
|
||||||
class User < ApplicationRecord
|
class User < ApplicationRecord
|
||||||
include EmailValidatable
|
include EmailValidatable
|
||||||
|
|
||||||
attr_accessor :display_name
|
attr_accessor :display_name
|
||||||
attr_accessor :avatar_new
|
attr_accessor :avatar_new
|
||||||
|
attr_accessor :current_password
|
||||||
|
|
||||||
serialize :preferences, UserPreferences
|
serialize :preferences, coder: UserPreferences
|
||||||
|
|
||||||
#
|
#
|
||||||
# Relations
|
# Relations
|
||||||
@@ -38,7 +41,8 @@ class User < ApplicationRecord
|
|||||||
message: "is invalid. Usernames need to start with a letter."
|
message: "is invalid. Usernames need to start with a letter."
|
||||||
# FIXME This needs a server restart to apply values
|
# FIXME This needs a server restart to apply values
|
||||||
validates_format_of :cn, without: /\A(#{Setting.reserved_usernames.join('|')})\z/i,
|
validates_format_of :cn, without: /\A(#{Setting.reserved_usernames.join('|')})\z/i,
|
||||||
message: "has already been taken"
|
message: "has already been taken",
|
||||||
|
unless: Proc.new{ |u| u.persisted? }
|
||||||
|
|
||||||
validates_uniqueness_of :email
|
validates_uniqueness_of :email
|
||||||
validates :email, email: true
|
validates :email, email: true
|
||||||
@@ -88,13 +92,14 @@ class User < ApplicationRecord
|
|||||||
def devise_after_confirmation
|
def devise_after_confirmation
|
||||||
if ldap_entry[:mail] != self.email
|
if ldap_entry[:mail] != self.email
|
||||||
# E-Mail update confirmed
|
# E-Mail update confirmed
|
||||||
LdapManager::UpdateEmail.call(self.dn, self.email)
|
LdapManager::UpdateEmail.call(dn: self.dn, address: self.email)
|
||||||
else
|
else
|
||||||
# TODO Make configurable
|
|
||||||
# E-Mail from signup confirmed (i.e. account activation)
|
# E-Mail from signup confirmed (i.e. account activation)
|
||||||
|
|
||||||
|
# TODO Make configurable, only activate globally enabled services
|
||||||
enable_service %w[ discourse gitea mediawiki xmpp ]
|
enable_service %w[ discourse gitea mediawiki xmpp ]
|
||||||
|
|
||||||
#TODO enable in development when we have easy setup of ejabberd etc.
|
# TODO enable in development when we have easy setup of ejabberd etc.
|
||||||
return if Rails.env.development? || !Setting.ejabberd_enabled?
|
return if Rails.env.development? || !Setting.ejabberd_enabled?
|
||||||
|
|
||||||
XmppExchangeContactsJob.perform_later(inviter, self) if inviter.present?
|
XmppExchangeContactsJob.perform_later(inviter, self) if inviter.present?
|
||||||
@@ -159,7 +164,7 @@ class User < ApplicationRecord
|
|||||||
end
|
end
|
||||||
|
|
||||||
def avatar
|
def avatar
|
||||||
@avatar_base64 ||= LdapManager::FetchAvatar.call(cn: cn, ou: ou)
|
@avatar_base64 ||= LdapManager::FetchAvatar.call(cn: cn)
|
||||||
end
|
end
|
||||||
|
|
||||||
def services_enabled
|
def services_enabled
|
||||||
@@ -184,6 +189,11 @@ class User < ApplicationRecord
|
|||||||
ldap.delete_attribute(dn,:service)
|
ldap.delete_attribute(dn,:service)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def nostr_pubkey_bech32
|
||||||
|
return nil unless nostr_pubkey.present?
|
||||||
|
Nostr::PublicKey.new(nostr_pubkey).to_bech32
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def ldap
|
def ldap
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ require "down"
|
|||||||
|
|
||||||
module AppCatalogManager
|
module AppCatalogManager
|
||||||
class UpdateMetadata < AppCatalogManagerService
|
class UpdateMetadata < AppCatalogManagerService
|
||||||
def initialize(app)
|
def initialize(app:)
|
||||||
@app = app
|
@app = app
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -18,6 +18,10 @@ module AppCatalogManager
|
|||||||
@app.metadata[prop] = metadata.send(prop) if prop
|
@app.metadata[prop] = metadata.send(prop) if prop
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@app.save!
|
||||||
|
|
||||||
|
# TODO move icon downloads to separate, async job
|
||||||
|
|
||||||
if icon = metadata.select_icon(sizes: "256x256") ||
|
if icon = metadata.select_icon(sizes: "256x256") ||
|
||||||
icon = metadata.select_icon(sizes: "192x192")
|
icon = metadata.select_icon(sizes: "192x192")
|
||||||
attach_remote_image(:icon, icon)
|
attach_remote_image(:icon, icon)
|
||||||
@@ -27,8 +31,6 @@ module AppCatalogManager
|
|||||||
if apple_touch_icon = metadata.select_icon(purpose: "apple-touch-icon")
|
if apple_touch_icon = metadata.select_icon(purpose: "apple-touch-icon")
|
||||||
attach_remote_image(:apple_touch_icon, apple_touch_icon)
|
attach_remote_image(:apple_touch_icon, apple_touch_icon)
|
||||||
end
|
end
|
||||||
|
|
||||||
@app.save!
|
|
||||||
rescue Manifique::Error => e
|
rescue Manifique::Error => e
|
||||||
msg = "Fetching web app manifest failed for #{e.url}: #{e.type}"
|
msg = "Fetching web app manifest failed for #{e.url}: #{e.type}"
|
||||||
Rails.logger.warn(msg)
|
Rails.logger.warn(msg)
|
||||||
@@ -42,11 +44,20 @@ module AppCatalogManager
|
|||||||
else
|
else
|
||||||
download_url = "#{@app.url}/#{icon["src"].gsub(/^\//,'')}"
|
download_url = "#{@app.url}/#{icon["src"].gsub(/^\//,'')}"
|
||||||
end
|
end
|
||||||
filename = "#{attachment_name}.png"
|
filename = "#{attachment_name}-#{Time.now.to_i}.png"
|
||||||
key = "web_apps/#{@app.id}/icons/#{attachment_name}.png"
|
key = "web_apps/#{@app.id}/icons/#{filename}"
|
||||||
|
|
||||||
tempfile = Down.download(download_url)
|
begin
|
||||||
@app.send(attachment_name).attach(key: key, io: tempfile, filename: filename)
|
tempfile = Down.download(download_url)
|
||||||
|
@app.send(attachment_name).attach(key: key, io: tempfile, filename: filename)
|
||||||
|
rescue Down::NotFound
|
||||||
|
msg = "Download of \"#{attachment_name}\" failed: NotFound error for #{download_url}"
|
||||||
|
Rails.logger.warn(msg)
|
||||||
|
Sentry.capture_message(msg)
|
||||||
|
rescue => e
|
||||||
|
Rails.logger.warn "Saving attachment \"#{attachment_name}\" failed: \"#{e.message}\""
|
||||||
|
Sentry.capture_exception(e) if Setting.sentry_enabled?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
class ApplicationService
|
class ApplicationService
|
||||||
# This enables executing a service's `#call` method directly via
|
# This enables executing a service's `#call` method directly via
|
||||||
# `MyService.call(args)`, without creating a class instance it first.
|
# `MyService.call(args)`, without creating a class instance it first.
|
||||||
def self.call(*args, &block)
|
def self.call(**args, &block)
|
||||||
new(*args, &block).call
|
new(**args, &block).call
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ module BtcpayManager
|
|||||||
res = get "stores/#{store_id}/lightning/BTC/balance"
|
res = get "stores/#{store_id}/lightning/BTC/balance"
|
||||||
|
|
||||||
{
|
{
|
||||||
balance: res["offchain"]["local"].to_i / 1000 # msats to sats
|
confirmed_balance: res["offchain"]["local"].to_i / 1000 # msats to sats
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
class CreateAccount < ApplicationService
|
class CreateAccount < ApplicationService
|
||||||
def initialize(args)
|
def initialize(account:)
|
||||||
@username = args[:username]
|
@username = account[:username]
|
||||||
@domain = args[:ou] || Setting.primary_domain
|
@domain = account[:ou] || Setting.primary_domain
|
||||||
@email = args[:email]
|
@email = account[:email]
|
||||||
@password = args[:password]
|
@password = account[:password]
|
||||||
@invitation = args[:invitation]
|
@invitation = account[:invitation]
|
||||||
@confirmed = args[:confirmed]
|
@confirmed = account[:confirmed]
|
||||||
end
|
end
|
||||||
|
|
||||||
def call
|
def call
|
||||||
|
|||||||
17
app/services/create_invitations.rb
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
class CreateInvitations < ApplicationService
|
||||||
|
def initialize(user:, amount:, notify: true)
|
||||||
|
@user = user
|
||||||
|
@amount = amount
|
||||||
|
@notify = notify
|
||||||
|
end
|
||||||
|
|
||||||
|
def call
|
||||||
|
@amount.times do
|
||||||
|
Invitation.create(user: @user)
|
||||||
|
end
|
||||||
|
|
||||||
|
if @notify
|
||||||
|
NotificationMailer.with(user: @user).new_invitations_available.deliver_later
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
module LdapManager
|
module LdapManager
|
||||||
class FetchAvatar < LdapManagerService
|
class FetchAvatar < LdapManagerService
|
||||||
def initialize(cn:, ou: nil)
|
def initialize(cn:)
|
||||||
@cn = cn
|
@cn = cn
|
||||||
@ou = ou
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def call
|
def call
|
||||||
treebase = @ou ? "ou=#{@ou},cn=users,#{suffix}" : ldap_config["base"]
|
treebase = ldap_config["base"]
|
||||||
attributes = %w{ jpegPhoto }
|
attributes = %w{ jpegPhoto }
|
||||||
filter = Net::LDAP::Filter.eq("cn", @cn)
|
filter = Net::LDAP::Filter.eq("cn", @cn)
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ require "image_processing/vips"
|
|||||||
|
|
||||||
module LdapManager
|
module LdapManager
|
||||||
class UpdateAvatar < LdapManagerService
|
class UpdateAvatar < LdapManagerService
|
||||||
def initialize(dn, file)
|
def initialize(dn:, file:)
|
||||||
@dn = dn
|
@dn = dn
|
||||||
@img_data = process(file)
|
@img_data = process(file)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
module LdapManager
|
module LdapManager
|
||||||
class UpdateDisplayName < LdapManagerService
|
class UpdateDisplayName < LdapManagerService
|
||||||
def initialize(dn, display_name)
|
def initialize(dn:, display_name:)
|
||||||
@dn = dn
|
@dn = dn
|
||||||
@display_name = display_name
|
@display_name = display_name
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
module LdapManager
|
module LdapManager
|
||||||
class UpdateEmail < LdapManagerService
|
class UpdateEmail < LdapManagerService
|
||||||
def initialize(dn, address)
|
def initialize(dn:, address:)
|
||||||
@dn = dn
|
@dn = dn
|
||||||
@address = address
|
@address = address
|
||||||
end
|
end
|
||||||
|
|||||||
12
app/services/ldap_manager/update_email_maildrop.rb
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
module LdapManager
|
||||||
|
class UpdateEmailMaildrop < LdapManagerService
|
||||||
|
def initialize(dn:, address:)
|
||||||
|
@dn = dn
|
||||||
|
@address = address
|
||||||
|
end
|
||||||
|
|
||||||
|
def call
|
||||||
|
replace_attribute @dn, :mailRoutingAddress, @address
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
12
app/services/ldap_manager/update_email_password.rb
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
module LdapManager
|
||||||
|
class UpdateEmailPassword < LdapManagerService
|
||||||
|
def initialize(dn:, password_hash:)
|
||||||
|
@dn = dn
|
||||||
|
@password_hash = password_hash
|
||||||
|
end
|
||||||
|
|
||||||
|
def call
|
||||||
|
replace_attribute @dn, :mailpassword, @password_hash
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -50,8 +50,11 @@ class LdapService < ApplicationService
|
|||||||
treebase = ldap_config["base"]
|
treebase = ldap_config["base"]
|
||||||
end
|
end
|
||||||
|
|
||||||
attributes = %w{dn cn uid mail displayName admin service}
|
attributes = %w[
|
||||||
filter = Net::LDAP::Filter.eq("uid", args[:uid] || "*")
|
dn cn uid mail displayName admin service
|
||||||
|
mailRoutingAddress mailpassword
|
||||||
|
]
|
||||||
|
filter = Net::LDAP::Filter.eq("uid", args[:uid] || "*")
|
||||||
|
|
||||||
entries = ldap_client.search(base: treebase, filter: filter, attributes: attributes)
|
entries = ldap_client.search(base: treebase, filter: filter, attributes: attributes)
|
||||||
entries.sort_by! { |e| e.cn[0] }
|
entries.sort_by! { |e| e.cn[0] }
|
||||||
@@ -61,7 +64,9 @@ class LdapService < ApplicationService
|
|||||||
mail: e.try(:mail) ? e.mail.first : nil,
|
mail: e.try(:mail) ? e.mail.first : nil,
|
||||||
display_name: e.try(:displayName) ? e.displayName.first : nil,
|
display_name: e.try(:displayName) ? e.displayName.first : nil,
|
||||||
admin: e.try(:admin) ? 'admin' : nil,
|
admin: e.try(:admin) ? 'admin' : nil,
|
||||||
service: e.try(:service)
|
service: e.try(:service),
|
||||||
|
email_maildrop: e.try(:mailRoutingAddress),
|
||||||
|
email_password: e.try(:mailpassword)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
module NostrManager
|
module NostrManager
|
||||||
class ValidateId < NostrManagerService
|
class ValidateId < NostrManagerService
|
||||||
def initialize(event)
|
def initialize(event:)
|
||||||
@event = Nostr::Event.new(**event)
|
@event = Nostr::Event.new(**event)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
module NostrManager
|
module NostrManager
|
||||||
class VerifySignature < NostrManagerService
|
class VerifySignature < NostrManagerService
|
||||||
def initialize(event)
|
def initialize(event:)
|
||||||
@event = Nostr::Event.new(**event)
|
@event = Nostr::Event.new(**event)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
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
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<%= render HeaderComponent.new(title: "Settings") %>
|
<%= render HeaderComponent.new(title: "Settings") %>
|
||||||
|
|
||||||
<%= render MainWithSidenavComponent.new(sidenav_partial: 'shared/admin_sidenav_settings') do %>
|
<%= render MainWithSidenavComponent.new(sidenav_partial: 'shared/admin_sidenav_settings') do %>
|
||||||
<%= form_for(Setting.new, url: admin_settings_registrations_path) do |f| %>
|
<%= form_for(Setting.new, url: admin_settings_registrations_path, method: :put) do |f| %>
|
||||||
<section>
|
<section>
|
||||||
<h3>Registrations</h3>
|
<h3>Registrations</h3>
|
||||||
|
|
||||||
16
app/views/admin/settings/services/_email.html.erb
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<h3>E-Mail</h3>
|
||||||
|
<ul role="list">
|
||||||
|
<%= render FormElements::FieldsetToggleComponent.new(
|
||||||
|
form: f,
|
||||||
|
attribute: :email_enabled,
|
||||||
|
enabled: Setting.email_enabled?,
|
||||||
|
title: "Enable E-Mail service integration",
|
||||||
|
description: "Enable/configure LDAP attributes for use with a mail server"
|
||||||
|
) %>
|
||||||
|
<%# <% if Setting.email_enabled? %>
|
||||||
|
<%# <%= render FormElements::FieldsetResettableSettingComponent.new(
|
||||||
|
<%# key: :gitea_public_url,
|
||||||
|
<%# title: "Public URL"
|
||||||
|
<%# ) %>
|
||||||
|
<%# <% end %>
|
||||||
|
</ul>
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
<%= render HeaderComponent.new(title: "Settings") %>
|
<%= render HeaderComponent.new(title: "Settings") %>
|
||||||
|
|
||||||
<%= render MainWithSidenavComponent.new(sidenav_partial: 'shared/admin_sidenav_settings') do %>
|
<%= render MainWithSidenavComponent.new(sidenav_partial: 'shared/admin_sidenav_settings') do %>
|
||||||
<%= form_for(Setting.new, url: admin_settings_services_path) do |f| %>
|
<%= form_for(Setting.new, url: admin_settings_service_path(@service), method: :put) do |f| %>
|
||||||
<%= hidden_field_tag :service, @service %>
|
|
||||||
|
|
||||||
<% if @errors && @errors.any? %>
|
<% if @errors && @errors.any? %>
|
||||||
<section>
|
<section>
|
||||||
<%= render partial: "admin/settings/errors", locals: { errors: @errors } %>
|
<%= render partial: "admin/settings/errors", locals: { errors: @errors } %>
|
||||||
21
app/views/admin/users/_create_invitations.html.erb
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<h3>Add new invitations to <%= @user.cn %>'s account</h3>
|
||||||
|
<%= form_with(url: invitations_admin_user_path, method: :post) do |form| %>
|
||||||
|
<ul role="list">
|
||||||
|
<%= render FormElements::FieldsetComponent.new(
|
||||||
|
positioning: :horizontal,
|
||||||
|
title: "Amount"
|
||||||
|
) do %>
|
||||||
|
<%= form.select :amount, options_for_select([
|
||||||
|
["3", "3"], ["5", "5"], ["10", "10"], ["20", "20"]
|
||||||
|
]) %>
|
||||||
|
<% end %>
|
||||||
|
<%= render FormElements::FieldsetToggleComponent.new(
|
||||||
|
field_name: "notify_user",
|
||||||
|
enabled: true,
|
||||||
|
title: "Notify user via email"
|
||||||
|
) %>
|
||||||
|
</ul>
|
||||||
|
<p class="pt-6 border-t border-gray-200 text-right">
|
||||||
|
<%= form.submit 'Add', class: "btn-md btn-blue w-full" %>
|
||||||
|
</p>
|
||||||
|
<% end %>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<%= render HeaderComponent.new(title: "Users: #{@ou}") %>
|
<%= render HeaderComponent.new(title: "Users") %>
|
||||||
|
|
||||||
<%= render MainSimpleComponent.new do %>
|
<%= render MainSimpleComponent.new do %>
|
||||||
<section>
|
<section>
|
||||||
@@ -16,19 +16,6 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<% if @orgs.length > 1 %>
|
|
||||||
<section>
|
|
||||||
<h3 class="hidden">Domains</h3>
|
|
||||||
<ul>
|
|
||||||
<% @orgs.each do |org| %>
|
|
||||||
<li class="inline-block">
|
|
||||||
<%= link_to org[:ou], admin_users_path(ou: org[:ou]), class: "ks-text-link" %>
|
|
||||||
</li>
|
|
||||||
<% end %>
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<table class="divided mb-8">
|
<table class="divided mb-8">
|
||||||
<thead>
|
<thead>
|
||||||
@@ -36,13 +23,12 @@
|
|||||||
<th>UID</th>
|
<th>UID</th>
|
||||||
<th>Status</th>
|
<th>Status</th>
|
||||||
<th>Roles</th>
|
<th>Roles</th>
|
||||||
<!-- <th>Password</th> -->
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<% @users.each do |user| %>
|
<% @users.each do |user| %>
|
||||||
<tr>
|
<tr>
|
||||||
<td><%= link_to(user.cn, admin_user_path(user.address), class: 'ks-text-link') %></td>
|
<td><%= link_to(user.cn, admin_user_path(user.cn), class: 'ks-text-link') %></td>
|
||||||
<td><%= user.confirmed_at.nil? ? badge("pending", :yellow) : "" %></td>
|
<td><%= user.confirmed_at.nil? ? badge("pending", :yellow) : "" %></td>
|
||||||
<td><%= user.is_admin? ? badge("admin", :red) : "" %></td>
|
<td><%= user.is_admin? ? badge("admin", :red) : "" %></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<%= render HeaderComponent.new(title: "User: #{@user.address}") %>
|
<%= render HeaderComponent.new(title: "User: #{@user.cn}") %>
|
||||||
|
|
||||||
<%= render MainSimpleComponent.new do %>
|
<%= render MainSimpleComponent.new do %>
|
||||||
<div class="mb-12 sm:flex sm:flex-row sm:gap-x-8">
|
<div class="mb-12 sm:flex sm:flex-row sm:gap-x-8">
|
||||||
@@ -42,8 +42,34 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Invitations available</th>
|
<th>Invitations available</th>
|
||||||
<td>
|
<td data-controller="modal" data-action="keydown.esc->modal#close">
|
||||||
<%= @user.invitations.count %>
|
<div class="flex justify-between">
|
||||||
|
<span>
|
||||||
|
<%= @user.invitations.count %>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<button id="add-invitations" data-action="click->modal#open">
|
||||||
|
<%= render partial: "icons/plus-circle", locals: {
|
||||||
|
custom_class: "text-green-600 hover:text-green-500 -mt-2 -mb-1 h-6 w-6 inline-block"
|
||||||
|
} %>
|
||||||
|
</button>
|
||||||
|
<% if @user.invitations.unused.count > 0 %>
|
||||||
|
<%= link_to invitations_admin_user_path(@user.cn),
|
||||||
|
id: "remove-invitations", data: {
|
||||||
|
turbo_method: :delete,
|
||||||
|
turbo_confirm: "Delete all of #{@user.cn}'s available invitations?"
|
||||||
|
} do %>
|
||||||
|
<%= render partial: "icons/x-circle", locals: {
|
||||||
|
custom_class: "text-red-600 hover:text-red-500 -mt-2 -mb-1 h-6 w-6 inline-block"
|
||||||
|
} %>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<%= render ModalComponent.new(show_close_button: false) do %>
|
||||||
|
<%= render partial: "admin/users/create_invitations",
|
||||||
|
locals: { user: @user } %>
|
||||||
|
<% end %>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -21,17 +21,17 @@
|
|||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
A good way to get started is to join one of our
|
A good way to get started is to join one of our
|
||||||
<a href="https://community.kosmos.org/t/kosmos-weekly-call/36" target="_blank" class="ks-text-link">weekly calls</a>
|
<a href="https://wiki.kosmos.org/Main_Page#Chat" target="_blank" class="ks-text-link">chat rooms</a>
|
||||||
and introduce yourself. Alternatively, you can also ping us on any other
|
and introduce yourself. Alternatively, you can also ping us on any other
|
||||||
medium, or even just grab an open issue on
|
medium, or even just grab an open issue on our
|
||||||
<a href="https://github.com/67P/" target="_blank" class="ks-text-link">GitHub</a>
|
|
||||||
or our
|
|
||||||
<a href="https://gitea.kosmos.org/kosmos/" target="_blank" class="ks-text-link">Gitea</a>
|
<a href="https://gitea.kosmos.org/kosmos/" target="_blank" class="ks-text-link">Gitea</a>
|
||||||
and dive right in (be sure to comment first, to prevent double efforts).
|
or on
|
||||||
|
<a href="https://github.com/67P/" target="_blank" class="ks-text-link">GitHub</a>
|
||||||
|
and dive right in.
|
||||||
</p>
|
</p>
|
||||||
<p class="mb-8">
|
<p class="mb-8">
|
||||||
Last but not least, if you want to help by proposing new features or
|
Last but not least, if you want to help by proposing new features or
|
||||||
services, head over to the
|
services, or by giving feedback on existing ones, head over to the
|
||||||
<a href="https://community.kosmos.org/" target="_blank" class="ks-text-link">community forums</a>,
|
<a href="https://community.kosmos.org/" target="_blank" class="ks-text-link">community forums</a>,
|
||||||
where you can do just that.
|
where you can do just that.
|
||||||
</p>
|
</p>
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
We have run two 6-month trials so far, with the next trial period
|
We have run two 6-month trials so far, with the next trial period
|
||||||
starting sometime in Q1 2023. Watch your email for notifications about it!
|
starting sometime in Q1 2024. Watch your email for notifications about it!
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -2,10 +2,6 @@
|
|||||||
|
|
||||||
<%= render MainSimpleComponent.new do %>
|
<%= render MainSimpleComponent.new do %>
|
||||||
<section>
|
<section>
|
||||||
<p class="mb-8">
|
|
||||||
Your Kosmos account and password currently give you access to these
|
|
||||||
services:
|
|
||||||
</p>
|
|
||||||
<div class="services grid grid-cols-1 sm:grid-cols-2 gap-4 sm:gap-6">
|
<div class="services grid grid-cols-1 sm:grid-cols-2 gap-4 sm:gap-6">
|
||||||
<% if Setting.ejabberd_enabled? %>
|
<% if Setting.ejabberd_enabled? %>
|
||||||
<div class="border border-gray-300 rounded-md hover:border-gray-400
|
<div class="border border-gray-300 rounded-md hover:border-gray-400
|
||||||
@@ -32,6 +28,17 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<% if Setting.email_enabled? &&
|
||||||
|
Flipper.enabled?(:email, current_user) %>
|
||||||
|
<div class="border border-gray-300 rounded-md hover:border-gray-400">
|
||||||
|
<%= link_to services_email_path, class: "block h-full px-6 py-6 rounded-md" do %>
|
||||||
|
<h3 class="mb-3.5">E-Mail</h3>
|
||||||
|
<p class="text-gray-600">
|
||||||
|
A no-bullshit email account
|
||||||
|
</p>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
<% if Setting.discourse_enabled? %>
|
<% if Setting.discourse_enabled? %>
|
||||||
<div class="border border-gray-300 rounded-md hover:border-gray-400
|
<div class="border border-gray-300 rounded-md hover:border-gray-400
|
||||||
bg-[length:95%] bg-center bg-no-repeat
|
bg-[length:95%] bg-center bg-no-repeat
|
||||||
@@ -58,6 +65,18 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<% if Setting.remotestorage_enabled? &&
|
||||||
|
Flipper.enabled?(:remotestorage, current_user) %>
|
||||||
|
<div class="border border-gray-300 rounded-md hover:border-gray-400">
|
||||||
|
<%= link_to services_storage_path,
|
||||||
|
class: "block h-full px-6 py-6 rounded-md" do %>
|
||||||
|
<h3 class="mb-3.5">Storage</h3>
|
||||||
|
<p class="text-gray-600">
|
||||||
|
Sync your data between apps and devices
|
||||||
|
</p>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
<% if Setting.gitea_enabled? %>
|
<% if Setting.gitea_enabled? %>
|
||||||
<div class="border border-gray-300 rounded-md hover:border-gray-400
|
<div class="border border-gray-300 rounded-md hover:border-gray-400
|
||||||
bg-cover bg-center bg-no-repeat
|
bg-cover bg-center bg-no-repeat
|
||||||
@@ -84,18 +103,6 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if Setting.remotestorage_enabled? &&
|
|
||||||
Flipper.enabled?(:remotestorage, current_user) %>
|
|
||||||
<div class="border border-gray-300 rounded-md hover:border-gray-400">
|
|
||||||
<%= link_to services_storage_path,
|
|
||||||
class: "block h-full px-6 py-6 rounded-md" do %>
|
|
||||||
<h3 class="mb-3.5">Storage</h3>
|
|
||||||
<p class="text-gray-600">
|
|
||||||
Sync your data between apps and devices
|
|
||||||
</p>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
<% if Setting.mediawiki_enabled? %>
|
<% if Setting.mediawiki_enabled? %>
|
||||||
<div class="border border-gray-300 rounded-md hover:border-gray-400
|
<div class="border border-gray-300 rounded-md hover:border-gray-400
|
||||||
bg-cover bg-[center_top_-20px] bg-no-repeat
|
bg-cover bg-[center_top_-20px] bg-no-repeat
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<%
|
<%
|
||||||
# TODO remove when https://github.com/hotwired/turbo/issues/203 is fixed
|
# TODO remove when https://github.com/hotwired/turbo/issues/203 is fixed
|
||||||
enable_turbo = !session[:user_return_to] || !session[:user_return_to].match?('/discourse/connect')
|
enable_turbo = session[:user_return_to].blank? ||
|
||||||
|
['/discourse/connect', '/rs/oauth'].none? { |s| session[:user_return_to].match(s) }
|
||||||
%>
|
%>
|
||||||
|
|
||||||
<%= render HeaderCompactComponent.new(title: "Log in") %>
|
<%= render HeaderCompactComponent.new(title: "Log in") %>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -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-mail"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path><polyline points="22,6 12,13 2,6"></polyline></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-mail <%= custom_class %>"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path><polyline points="22,6 12,13 2,6"></polyline></svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 354 B After Width: | Height: | Size: 375 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-plus-circle"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="16"></line><line x1="8" y1="12" x2="16" y2="12"></line></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-plus-circle <%= custom_class %>"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="16"></line><line x1="8" y1="12" x2="16" y2="12"></line></svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 351 B After Width: | Height: | Size: 372 B |
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="icon-remotestorage <%= 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: 867 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-x-circle"><circle cx="12" cy="12" r="10"></circle><line x1="15" y1="9" x2="9" y2="15"></line><line x1="9" y1="9" x2="15" y2="15"></line></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-x-circle <%= custom_class %>"><circle cx="12" cy="12" r="10"></circle><line x1="15" y1="9" x2="9" y2="15"></line><line x1="9" y1="9" x2="15" y2="15"></line></svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 346 B After Width: | Height: | Size: 367 B |
@@ -77,7 +77,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="pt-4 pb-3 border-t border-gray-200/10">
|
<div class="pt-4 pb-3 border-t border-gray-200/10">
|
||||||
<div class="px-5 text-base font-normal text-white">
|
<div class="px-5 text-base font-normal text-white">
|
||||||
<%= current_user.address %></strong>
|
<%= current_user.address %>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-3 px-2 space-y-1">
|
<div class="mt-3 px-2 space-y-1">
|
||||||
<%= link_to "Log out", destroy_user_session_path,
|
<%= link_to "Log out", destroy_user_session_path,
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
Hi <%= @user.display_name.presence || @user.cn %>,
|
||||||
|
|
||||||
|
New invitations have just been added to your Kosmos account, so you can invite more people to our cooperative services:
|
||||||
|
|
||||||
|
<%= invitations_url %>
|
||||||
|
|
||||||
|
Have a nice day!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Tip: if you want to invite someone you're meeting in person, log into your account panel on a mobile device and let people scan the invitation QR code from theirs.
|
||||||
@@ -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 %>
|
||||||
35
app/views/services/email/new_password.html.erb
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<%= render HeaderCompactComponent.new(title: "New E-Mail Password") %>
|
||||||
|
|
||||||
|
<%= render MainCompactComponent.new do %>
|
||||||
|
<section data-controller="modal" data-action="keydown.esc->modal#close">
|
||||||
|
<p class="font-bold">
|
||||||
|
Your email password has been updated.
|
||||||
|
</p>
|
||||||
|
<p class="mb-8">
|
||||||
|
Please store the new one in a password manager or write it down somewhere:
|
||||||
|
</p>
|
||||||
|
<p data-controller="clipboard" class="flex gap-1 w-full mb-10">
|
||||||
|
<%= label_tag :new_password, 'New password', class: 'hidden' %>
|
||||||
|
<%= text_field_tag :new_password, @new_password, disabled: true, class: 'text-xl grow',
|
||||||
|
data: { "clipboard-target": "source"} %>
|
||||||
|
<button id="copy-new-password" 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>
|
||||||
|
<button class="btn-md btn-icon btn-outline shrink-0 w-auto"
|
||||||
|
data-action="click->modal#open" title="Show QR code">
|
||||||
|
<%= render partial: "icons/qr_code", locals: { custom_class: "text-blue-600 h-4 w-4 inline" } %>
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
<p class="mb-0">
|
||||||
|
<%= link_to "Done", services_email_path, class: "btn-md btn-blue w-full" %>
|
||||||
|
</p>
|
||||||
|
<%= render QrCodeModalComponent.new(qr_content: @new_password) %>
|
||||||
|
</section>
|
||||||
|
<% end %>
|
||||||
128
app/views/services/email/show.html.erb
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
<%= render HeaderComponent.new(title: "E-Mail") %>
|
||||||
|
|
||||||
|
<%= render MainSimpleComponent.new do %>
|
||||||
|
<section>
|
||||||
|
<p class="mb-6">
|
||||||
|
Send and receive electronic mail.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
<section data-controller="modal" data-action="keydown.esc->modal#close">
|
||||||
|
<h3>Your E-Mail Address</h3>
|
||||||
|
<p data-controller="clipboard" class="flex gap-1 sm:w-2/5">
|
||||||
|
<input type="text" id="user_address" class="grow"
|
||||||
|
value=<%= current_user.address %> disabled="disabled"
|
||||||
|
data-clipboard-target="source" />
|
||||||
|
<button id="copy-user-address" class="btn-md btn-icon btn-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>
|
||||||
|
<button class="btn-md btn-icon btn-outline shrink-0 w-auto"
|
||||||
|
data-action="click->modal#open" title="Show QR code">
|
||||||
|
<%= render partial: "icons/qr_code", locals: { custom_class: "text-blue-600 h-4 w-4 inline" } %>
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
<%= render QrCodeModalComponent.new(qr_content: current_user.address) %>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<h3>E-Mail Password</h3>
|
||||||
|
<p>
|
||||||
|
Your email password is different from your main account password. You can
|
||||||
|
reset your email password in the
|
||||||
|
<%= link_to "email settings", setting_path(:email), class: "ks-text-link" %>.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<h3>Recommended Apps</h3>
|
||||||
|
<div data-controller="tabs"
|
||||||
|
data-tabs-active-tab-class="-mb-px border-gray-200 border-l border-t border-r rounded-t text-indigo-600 hover:text-indigo-600"
|
||||||
|
data-tabs-inactive-tab-class="text-gray-500 hover:text-gray-700"
|
||||||
|
class="mb-12">
|
||||||
|
<select data-action="tabs#change" data-tabs-target="select"
|
||||||
|
class="block w-full mb-8 sm:hidden">
|
||||||
|
<optgroup label="Mobile">
|
||||||
|
<option>Android</option>
|
||||||
|
</optgroup>
|
||||||
|
<optgroup label="Desktop">
|
||||||
|
<option>Linux</option>
|
||||||
|
<option>Windows</option>
|
||||||
|
<option>macOS</option>
|
||||||
|
</optgroup>
|
||||||
|
</select>
|
||||||
|
<ul class="hidden sm:flex list-reset mb-8 border-gray-200 border-b">
|
||||||
|
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
|
||||||
|
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
|
||||||
|
Android
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
|
||||||
|
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
|
||||||
|
Linux
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
|
||||||
|
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
|
||||||
|
Windows
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
|
||||||
|
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
|
||||||
|
macOS
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div id="apps-android" class="hidden grid grid-cols-1 gap-6"
|
||||||
|
data-tabs-target="panel">
|
||||||
|
<%= render AppInfoComponent.new(
|
||||||
|
name: "K-9 Mail",
|
||||||
|
description: "Soon to become Thunderbird Mobile",
|
||||||
|
icon_path: "/img/logos/icon_k9-mail.png",
|
||||||
|
links: [
|
||||||
|
["Website", "https://k9mail.app"],
|
||||||
|
["Google Play", "https://play.google.com/store/apps/details?id=com.fsck.k9"],
|
||||||
|
["F-Droid", "https://f-droid.org/en/packages/com.fsck.k9/"],
|
||||||
|
]
|
||||||
|
) %>
|
||||||
|
</div>
|
||||||
|
<div id="apps-linux" class="hidden grid grid-cols-1 gap-6"
|
||||||
|
data-tabs-target="panel">
|
||||||
|
<%= render AppInfoComponent.new(
|
||||||
|
name: "Thunderbird",
|
||||||
|
description: "The most popular open-source email app",
|
||||||
|
icon_path: "/img/logos/icon_thunderbird.png",
|
||||||
|
links: [
|
||||||
|
["Website", "https://www.thunderbird.net"]
|
||||||
|
]
|
||||||
|
) %>
|
||||||
|
</div>
|
||||||
|
<div id="apps-windows" class="hidden grid grid-cols-1 gap-6"
|
||||||
|
data-tabs-target="panel">
|
||||||
|
<%= render AppInfoComponent.new(
|
||||||
|
name: "Thunderbird",
|
||||||
|
description: "The most popular open-source email app",
|
||||||
|
icon_path: "/img/logos/icon_thunderbird.png",
|
||||||
|
links: [
|
||||||
|
["Website", "https://www.thunderbird.net"]
|
||||||
|
]
|
||||||
|
) %>
|
||||||
|
</div>
|
||||||
|
<div id="apps-mac" class="hidden grid grid-cols-1 gap-6"
|
||||||
|
data-tabs-target="panel">
|
||||||
|
<%= render AppInfoComponent.new(
|
||||||
|
name: "Thunderbird",
|
||||||
|
description: "The most popular open-source email app",
|
||||||
|
icon_path: "/img/logos/icon_thunderbird.png",
|
||||||
|
links: [
|
||||||
|
["Website", "https://www.thunderbird.net"]
|
||||||
|
]
|
||||||
|
) %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<% end %>
|
||||||
34
app/views/settings/_email.html.erb
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<%= tag.section data: {
|
||||||
|
controller: "settings--email--password",
|
||||||
|
"settings--email--password-validation-failed-value": @validation_errors.present?
|
||||||
|
} do %>
|
||||||
|
<h3>E-Mail Password</h3>
|
||||||
|
<%= form_for(@user, url: reset_email_password_settings_path, method: "post") do |f| %>
|
||||||
|
<%= hidden_field_tag :section, "email" %>
|
||||||
|
<p class="mb-8">
|
||||||
|
Use the following button to generate a new email password:
|
||||||
|
</p>
|
||||||
|
<p class="hidden initial-visible">
|
||||||
|
<button type="button" id="edit-email" class="btn-md btn-gray"
|
||||||
|
data-settings--email--password-target="resetPasswordButton"
|
||||||
|
data-action="settings--email--password#showPasswordReset">
|
||||||
|
Reset email password
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
<div class="initial-hidden">
|
||||||
|
<p class="mt-4 mb-2">
|
||||||
|
<%= f.label :current_password, 'Current account password', class: 'font-bold' %>
|
||||||
|
</p>
|
||||||
|
<p class="sm:w-3/5">
|
||||||
|
<%= f.password_field :current_password, class: "w-full", required: true,
|
||||||
|
data: { 'settings--email--password-target': "currentPasswordField" } %>
|
||||||
|
</p>
|
||||||
|
<% if @validation_errors.present? && @validation_errors[:current_password].present? %>
|
||||||
|
<p class="error-msg"><%= @validation_errors[:current_password].first %></p>
|
||||||
|
<% end %>
|
||||||
|
<p class="mt-6">
|
||||||
|
<%= f.submit "Create new email password", class: "btn-md btn-blue w-full md:w-auto" %>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
data-settings--nostr-pubkey-pubkey-hex-value="<%= current_user.nostr_pubkey %>">
|
data-settings--nostr-pubkey-pubkey-hex-value="<%= current_user.nostr_pubkey %>">
|
||||||
|
|
||||||
<p class="<%= current_user.nostr_pubkey.present? ? '' : 'hidden' %> mt-2 flex gap-1">
|
<p class="<%= current_user.nostr_pubkey.present? ? '' : 'hidden' %> mt-2 flex gap-1">
|
||||||
<input type="text" value="<%= current_user.nostr_pubkey %>" disabled
|
<input type="text" value="<%= current_user.nostr_pubkey_bech32 %>" disabled
|
||||||
data-settings--nostr-pubkey-target="pubkeyBech32Input"
|
data-settings--nostr-pubkey-target="pubkeyBech32Input"
|
||||||
name="nostr_public_key" class="relative grow" />
|
name="nostr_public_key" class="relative grow" />
|
||||||
<%= link_to nostr_pubkey_settings_path,
|
<%= link_to nostr_pubkey_settings_path,
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
<% if Flipper.enabled?(:avatar_upload, current_user) %>
|
||||||
<label class="block">
|
<label class="block">
|
||||||
<p class="font-bold mb-1">
|
<p class="font-bold mb-1">
|
||||||
Avatar
|
Avatar
|
||||||
@@ -56,6 +57,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<p class="mt-8 pt-6 border-t border-gray-200 text-right">
|
<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" %>
|
<%= f.submit 'Save', class: "btn-md btn-blue w-full md:w-auto" %>
|
||||||
|
|||||||
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 %>
|
||||||
@@ -4,9 +4,9 @@
|
|||||||
) %>
|
) %>
|
||||||
<%= render SidenavLinkComponent.new(
|
<%= render SidenavLinkComponent.new(
|
||||||
name: "Services", path: admin_settings_services_path, icon: "grid",
|
name: "Services", path: admin_settings_services_path, icon: "grid",
|
||||||
active: current_page?(admin_settings_services_path)
|
active: controller_name == "services"
|
||||||
) %>
|
) %>
|
||||||
<% if current_page?(admin_settings_services_path) %>
|
<% if controller_name == "services" %>
|
||||||
<%= render partial: "shared/admin_sidenav_settings_services" %>
|
<%= render partial: "shared/admin_sidenav_settings_services" %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= render SidenavLinkComponent.new(
|
<%= render SidenavLinkComponent.new(
|
||||||
|
|||||||
@@ -1,70 +1,77 @@
|
|||||||
<%= render SidenavLinkComponent.new(
|
<%= render SidenavLinkComponent.new(
|
||||||
level: 2,
|
level: 2,
|
||||||
name: "BTCPay",
|
name: "BTCPay",
|
||||||
path: admin_settings_services_path(params: { s: "btcpay" }),
|
path: admin_settings_service_path("btcpay"),
|
||||||
text_icon: Setting.btcpay_enabled? ? "◉" : "○",
|
text_icon: Setting.btcpay_enabled? ? "◉" : "○",
|
||||||
active: current_page?(admin_settings_services_path(params: { s: "btcpay" })),
|
active: current_page?(admin_settings_service_path("btcpay")),
|
||||||
) %>
|
) %>
|
||||||
<%= render SidenavLinkComponent.new(
|
<%= render SidenavLinkComponent.new(
|
||||||
level: 2,
|
level: 2,
|
||||||
name: "Discourse",
|
name: "Discourse",
|
||||||
path: admin_settings_services_path(params: { s: "discourse" }),
|
path: admin_settings_service_path("discourse"),
|
||||||
text_icon: Setting.discourse_enabled? ? "◉" : "○",
|
text_icon: Setting.discourse_enabled? ? "◉" : "○",
|
||||||
active: current_page?(admin_settings_services_path(params: { s: "discourse" })),
|
active: current_page?(admin_settings_service_path("discourse")),
|
||||||
) %>
|
) %>
|
||||||
<%= render SidenavLinkComponent.new(
|
<%= render SidenavLinkComponent.new(
|
||||||
level: 2,
|
level: 2,
|
||||||
name: "Drone CI",
|
name: "Drone CI",
|
||||||
path: admin_settings_services_path(params: { s: "droneci" }),
|
path: admin_settings_service_path("droneci"),
|
||||||
text_icon: Setting.droneci_enabled? ? "◉" : "○",
|
text_icon: Setting.droneci_enabled? ? "◉" : "○",
|
||||||
active: current_page?(admin_settings_services_path(params: { s: "droneci" })),
|
active: current_page?(admin_settings_service_path("droneci")),
|
||||||
|
) %>
|
||||||
|
<%= render SidenavLinkComponent.new(
|
||||||
|
level: 2,
|
||||||
|
name: "E-Mail",
|
||||||
|
path: admin_settings_service_path("email"),
|
||||||
|
text_icon: Setting.email_enabled? ? "◉" : "○",
|
||||||
|
active: current_page?(admin_settings_services_path(params: { s: "email" })),
|
||||||
) %>
|
) %>
|
||||||
<%= render SidenavLinkComponent.new(
|
<%= render SidenavLinkComponent.new(
|
||||||
level: 2,
|
level: 2,
|
||||||
name: "ejabberd",
|
name: "ejabberd",
|
||||||
path: admin_settings_services_path(params: { s: "ejabberd" }),
|
path: admin_settings_service_path("ejabberd"),
|
||||||
text_icon: Setting.ejabberd_enabled? ? "◉" : "○",
|
text_icon: Setting.ejabberd_enabled? ? "◉" : "○",
|
||||||
active: current_page?(admin_settings_services_path(params: { s: "ejabberd" })),
|
active: current_page?(admin_settings_service_path("ejabberd")),
|
||||||
) %>
|
) %>
|
||||||
<%= render SidenavLinkComponent.new(
|
<%= render SidenavLinkComponent.new(
|
||||||
level: 2,
|
level: 2,
|
||||||
name: "Gitea",
|
name: "Gitea",
|
||||||
path: admin_settings_services_path(params: { s: "gitea" }),
|
path: admin_settings_service_path("gitea"),
|
||||||
text_icon: Setting.gitea_enabled? ? "◉" : "○",
|
text_icon: Setting.gitea_enabled? ? "◉" : "○",
|
||||||
active: current_page?(admin_settings_services_path(params: { s: "gitea" })),
|
active: current_page?(admin_settings_service_path("gitea")),
|
||||||
) %>
|
) %>
|
||||||
<%= render SidenavLinkComponent.new(
|
<%= render SidenavLinkComponent.new(
|
||||||
level: 2,
|
level: 2,
|
||||||
name: "LNDHub",
|
name: "LNDHub",
|
||||||
path: admin_settings_services_path(params: { s: "lndhub" }),
|
path: admin_settings_service_path("lndhub"),
|
||||||
text_icon: Setting.lndhub_enabled? ? "◉" : "○",
|
text_icon: Setting.lndhub_enabled? ? "◉" : "○",
|
||||||
active: current_page?(admin_settings_services_path(params: { s: "lndhub" })),
|
active: current_page?(admin_settings_service_path("lndhub")),
|
||||||
) %>
|
) %>
|
||||||
<%= render SidenavLinkComponent.new(
|
<%= render SidenavLinkComponent.new(
|
||||||
level: 2,
|
level: 2,
|
||||||
name: "Mastodon",
|
name: "Mastodon",
|
||||||
path: admin_settings_services_path(params: { s: "mastodon" }),
|
path: admin_settings_service_path("mastodon"),
|
||||||
text_icon: Setting.mastodon_enabled? ? "◉" : "○",
|
text_icon: Setting.mastodon_enabled? ? "◉" : "○",
|
||||||
active: current_page?(admin_settings_services_path(params: { s: "mastodon" })),
|
active: current_page?(admin_settings_service_path("mastodon")),
|
||||||
) %>
|
) %>
|
||||||
<%= render SidenavLinkComponent.new(
|
<%= render SidenavLinkComponent.new(
|
||||||
level: 2,
|
level: 2,
|
||||||
name: "MediaWiki",
|
name: "MediaWiki",
|
||||||
path: admin_settings_services_path(params: { s: "mediawiki" }),
|
path: admin_settings_service_path("mediawiki"),
|
||||||
text_icon: Setting.mediawiki_enabled? ? "◉" : "○",
|
text_icon: Setting.mediawiki_enabled? ? "◉" : "○",
|
||||||
active: current_page?(admin_settings_services_path(params: { s: "mediawiki" })),
|
active: current_page?(admin_settings_service_path("mediawiki")),
|
||||||
) %>
|
) %>
|
||||||
<%= render SidenavLinkComponent.new(
|
<%= render SidenavLinkComponent.new(
|
||||||
level: 2,
|
level: 2,
|
||||||
name: "Nostr",
|
name: "Nostr",
|
||||||
path: admin_settings_services_path(params: { s: "nostr" }),
|
path: admin_settings_service_path("nostr"),
|
||||||
text_icon: Setting.nostr_enabled? ? "◉" : "○",
|
text_icon: Setting.nostr_enabled? ? "◉" : "○",
|
||||||
active: current_page?(admin_settings_services_path(params: { s: "nostr" })),
|
active: current_page?(admin_settings_service_path("nostr")),
|
||||||
) %>
|
) %>
|
||||||
<%= render SidenavLinkComponent.new(
|
<%= render SidenavLinkComponent.new(
|
||||||
level: 2,
|
level: 2,
|
||||||
name: "RemoteStorage",
|
name: "RemoteStorage",
|
||||||
path: admin_settings_services_path(params: { s: "remotestorage" }),
|
path: admin_settings_service_path("remotestorage"),
|
||||||
text_icon: Setting.remotestorage_enabled? ? "◉" : "○",
|
text_icon: Setting.remotestorage_enabled? ? "◉" : "○",
|
||||||
active: current_page?(admin_settings_services_path(params: { s: "remotestorage" })),
|
active: current_page?(admin_settings_service_path("remotestorage")),
|
||||||
) %>
|
) %>
|
||||||
|
|||||||
@@ -12,12 +12,26 @@
|
|||||||
active: @settings_section.to_s == "xmpp"
|
active: @settings_section.to_s == "xmpp"
|
||||||
) %>
|
) %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<% if Setting.email_enabled? &&
|
||||||
|
Flipper.enabled?(:email, current_user) %>
|
||||||
|
<%= render SidenavLinkComponent.new(
|
||||||
|
name: "E-Mail", path: setting_path(:email), icon: "mail",
|
||||||
|
active: @settings_section.to_s == "email"
|
||||||
|
) %>
|
||||||
|
<% end %>
|
||||||
<% if Setting.lndhub_enabled %>
|
<% if Setting.lndhub_enabled %>
|
||||||
<%= render SidenavLinkComponent.new(
|
<%= render SidenavLinkComponent.new(
|
||||||
name: "Lightning", path: setting_path(:lightning), icon: "zap",
|
name: "Lightning", path: setting_path(:lightning), icon: "zap",
|
||||||
active: @settings_section.to_s == "lightning"
|
active: @settings_section.to_s == "lightning"
|
||||||
) %>
|
) %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<% if Setting.remotestorage_enabled? &&
|
||||||
|
Flipper.enabled?(:remotestorage, current_user) %>
|
||||||
|
<%= render SidenavLinkComponent.new(
|
||||||
|
name: "Storage", path: setting_path(:remotestorage), icon: "remotestorage",
|
||||||
|
active: @settings_section.to_s == "remotestorage"
|
||||||
|
) %>
|
||||||
|
<% end %>
|
||||||
<% if Setting.nostr_enabled %>
|
<% if Setting.nostr_enabled %>
|
||||||
<%= render SidenavLinkComponent.new(
|
<%= render SidenavLinkComponent.new(
|
||||||
name: "Experiments", path: setting_path(:experiments), icon: "science",
|
name: "Experiments", path: setting_path(:experiments), icon: "science",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<%= render HeaderCompactComponent.new(title: "404") %>
|
<%= render HeaderCompactComponent.new(title: "400") %>
|
||||||
|
|
||||||
<%= render MainCompactComponent.new do %>
|
<%= render MainCompactComponent.new do %>
|
||||||
<h2>Bad request</h2>
|
<h2>Bad request</h2>
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
FROM guildeducation/rails:2.7.2-14.20.0
|
FROM ruby:3.3.0
|
||||||
|
|
||||||
RUN apt-get update -qq && apt-get install -y --no-install-recommends ldap-utils libvips
|
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||||
|
|
||||||
|
RUN apt-get update -qq && apt-get install -y --no-install-recommends curl \
|
||||||
|
ldap-utils tini libvips
|
||||||
|
RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -
|
||||||
|
RUN apt-get update && apt-get install -y nodejs
|
||||||
|
RUN npm install -g yarn
|
||||||
|
|||||||
@@ -42,7 +42,5 @@ module Akkounts
|
|||||||
|
|
||||||
config.active_job.queue_adapter = :sidekiq
|
config.active_job.queue_adapter = :sidekiq
|
||||||
config.action_mailer.deliver_later_queue_name = nil # use "default" queue
|
config.action_mailer.deliver_later_queue_name = nil # use "default" queue
|
||||||
|
|
||||||
config.active_record.legacy_connection_handling = false
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
lightning_notify_sats_received: disabled # or xmpp, email
|
lightning_notify_sats_received: disabled # or xmpp, email
|
||||||
|
remotestorage_notify_auth_created: email # or xmpp, email
|
||||||
xmpp_exchange_contacts_with_invitees: true
|
xmpp_exchange_contacts_with_invitees: true
|
||||||
|
|||||||
@@ -69,9 +69,9 @@ Rails.application.configure do
|
|||||||
config.action_mailer.default_url_options = { host: "localhost:3000", protocol: "http" }
|
config.action_mailer.default_url_options = { host: "localhost:3000", protocol: "http" }
|
||||||
|
|
||||||
# Allow requests from any IP
|
# Allow requests from any IP
|
||||||
config.web_console.whiny_requests = false
|
config.web_console.permissions = '0.0.0.0/0'
|
||||||
|
|
||||||
if ENV["S3_ENABLED"]
|
if ENV["S3_ENABLED"] && ENV["S3_ENABLED"].to_s != "false"
|
||||||
config.active_storage.service = :s3
|
config.active_storage.service = :s3
|
||||||
else
|
else
|
||||||
config.active_storage.service = :local
|
config.active_storage.service = :local
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ Rails.application.configure do
|
|||||||
# Set this to true and configure the email server for immediate delivery to raise delivery errors.
|
# Set this to true and configure the email server for immediate delivery to raise delivery errors.
|
||||||
config.action_mailer.raise_delivery_errors = true
|
config.action_mailer.raise_delivery_errors = true
|
||||||
|
|
||||||
if ENV["S3_ENABLED"]
|
if ENV["S3_ENABLED"] && ENV["S3_ENABLED"].to_s != "false"
|
||||||
config.active_storage.service = :s3
|
config.active_storage.service = :s3
|
||||||
else
|
else
|
||||||
config.active_storage.service = :local
|
config.active_storage.service = :local
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ Rails.application.configure do
|
|||||||
config.cache_store = :null_store
|
config.cache_store = :null_store
|
||||||
|
|
||||||
# Raise exceptions instead of rendering exception templates.
|
# Raise exceptions instead of rendering exception templates.
|
||||||
config.action_dispatch.show_exceptions = false
|
config.action_dispatch.show_exceptions = :none
|
||||||
|
|
||||||
# Disable request forgery protection in test environment.
|
# Disable request forgery protection in test environment.
|
||||||
config.action_controller.allow_forgery_protection = false
|
config.action_controller.allow_forgery_protection = false
|
||||||
@@ -52,10 +52,9 @@ Rails.application.configure do
|
|||||||
|
|
||||||
config.active_job.queue_adapter = :test
|
config.active_job.queue_adapter = :test
|
||||||
|
|
||||||
if ENV["S3_ENABLED"]
|
if ENV["S3_ENABLED"] && ENV["S3_ENABLED"].to_s != "false"
|
||||||
config.active_storage.service = :s3
|
config.active_storage.service = :s3
|
||||||
else
|
else
|
||||||
# Store attachments on the local disk (in ./tmp)
|
config.active_storage.service = :local
|
||||||
config.active_storage.service = :test
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,5 +5,4 @@ pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true
|
|||||||
pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true
|
pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true
|
||||||
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
|
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
|
||||||
pin_all_from "app/javascript/controllers", under: "controllers"
|
pin_all_from "app/javascript/controllers", under: "controllers"
|
||||||
pin "bech32" # @2.0.0
|
|
||||||
pin "tailwindcss-stimulus-components" # @4.0.3
|
pin "tailwindcss-stimulus-components" # @4.0.3
|
||||||
|
|||||||
@@ -325,3 +325,10 @@ Devise.setup do |config|
|
|||||||
# changed. Defaults to true, so a user is signed in automatically after changing a password.
|
# changed. Defaults to true, so a user is signed in automatically after changing a password.
|
||||||
# config.sign_in_after_change_password = true
|
# config.sign_in_after_change_password = true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# https://github.com/heartcombo/devise/issues/5644
|
||||||
|
class Devise::SecretKeyFinder
|
||||||
|
def find
|
||||||
|
@application.secret_key_base
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|||||||
@@ -23,6 +23,12 @@ Rails.application.routes.draw do
|
|||||||
|
|
||||||
resource :mastodon, only: [:show], controller: 'mastodon'
|
resource :mastodon, only: [:show], controller: 'mastodon'
|
||||||
|
|
||||||
|
resource :email, only: [:show], controller: 'email' do
|
||||||
|
member do
|
||||||
|
get 'new_password'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
resources :lightning, only: [:index] do
|
resources :lightning, only: [:index] do
|
||||||
collection do
|
collection do
|
||||||
get 'transactions'
|
get 'transactions'
|
||||||
@@ -44,19 +50,18 @@ Rails.application.routes.draw do
|
|||||||
collection do
|
collection do
|
||||||
post 'update_email'
|
post 'update_email'
|
||||||
post 'reset_password'
|
post 'reset_password'
|
||||||
|
post 'reset_email_password'
|
||||||
post 'set_nostr_pubkey'
|
post 'set_nostr_pubkey'
|
||||||
delete 'nostr_pubkey', to: 'settings#remove_nostr_pubkey'
|
delete 'nostr_pubkey', to: 'settings#remove_nostr_pubkey'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
get 'lnurlpay/:address', to: 'lnurlpay#index',
|
get '.well-known/webfinger', to: 'webfinger#show'
|
||||||
as: 'lightning_address', constraints: { address: /[^\/]+/}
|
|
||||||
get 'lnurlpay/:address/invoice', to: 'lnurlpay#invoice',
|
|
||||||
as: 'lnurlpay_invoice', constraints: { address: /[^\/]+/}
|
|
||||||
get 'keysend/:address', to: 'lnurlpay#keysend',
|
|
||||||
as: 'lightning_address_keysend', constraints: { address: /[^\/]+/}
|
|
||||||
|
|
||||||
get '.well-known/nostr', to: 'well_known#nostr'
|
get '.well-known/nostr', to: 'well_known#nostr'
|
||||||
|
get '.well-known/lnurlp/:username', to: 'lnurlpay#index', as: 'lightning_address'
|
||||||
|
get '.well-known/keysend/:username', to: 'lnurlpay#keysend', as: 'lightning_address_keysend'
|
||||||
|
|
||||||
|
get 'lnurlpay/:username/invoice', to: 'lnurlpay#invoice', as: 'lnurlpay_invoice'
|
||||||
|
|
||||||
post 'webhooks/lndhub', to: 'webhooks#lndhub'
|
post 'webhooks/lndhub', to: 'webhooks#lndhub'
|
||||||
|
|
||||||
@@ -68,9 +73,19 @@ Rails.application.routes.draw do
|
|||||||
namespace :admin do
|
namespace :admin do
|
||||||
root to: 'dashboard#index'
|
root to: 'dashboard#index'
|
||||||
|
|
||||||
resources 'users', param: 'address', only: ['index', 'show'], constraints: { address: /.*/ }
|
resources 'users', param: 'username', only: ['index', 'show'] do
|
||||||
|
member do
|
||||||
|
post 'invitations', to: 'users#create_invitations'
|
||||||
|
delete 'invitations', to: 'users#delete_invitations'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# post 'users/:username/invitations', to: 'users#create_invitations'
|
||||||
|
|
||||||
get 'invitations', to: 'invitations#index'
|
get 'invitations', to: 'invitations#index'
|
||||||
|
|
||||||
resources :donations
|
resources :donations
|
||||||
|
|
||||||
get 'lightning', to: 'lightning#index'
|
get 'lightning', to: 'lightning#index'
|
||||||
|
|
||||||
namespace :app_catalog do
|
namespace :app_catalog do
|
||||||
@@ -78,8 +93,8 @@ Rails.application.routes.draw do
|
|||||||
end
|
end
|
||||||
|
|
||||||
namespace :settings do
|
namespace :settings do
|
||||||
resources 'registrations', only: ['index', 'create']
|
resource 'registrations', only: ['show', 'update']
|
||||||
resources 'services', only: ['index', 'create']
|
resources 'services', param: 'service', only: ['index', 'show', 'update']
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -89,8 +104,6 @@ Rails.application.routes.draw do
|
|||||||
}, controller: 'oauth'
|
}, controller: 'oauth'
|
||||||
end
|
end
|
||||||
|
|
||||||
get '.well-known/webfinger', to: 'webfinger#show'
|
|
||||||
|
|
||||||
namespace :discourse do
|
namespace :discourse do
|
||||||
get "connect", to: 'sso#connect'
|
get "connect", to: 'sso#connect'
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
local:
|
local:
|
||||||
service: Disk
|
service: Disk
|
||||||
root: <%= Rails.root.join("storage") %>
|
root: <%= ENV["ACTIVE_STORAGE_PATH"] || Rails.root.join("storage") %>
|
||||||
|
|
||||||
test:
|
test:
|
||||||
service: Disk
|
service: Disk
|
||||||
root: <%= Rails.root.join("tmp/storage") %>
|
root: <%= Rails.root.join("tmp/storage") %>
|
||||||
|
|
||||||
<% if ENV["S3_ENABLED"] %>
|
<% if ENV["S3_ENABLED"] && ENV["S3_ENABLED"].to_s != "false" %>
|
||||||
s3:
|
s3:
|
||||||
service: S3
|
service: S3
|
||||||
endpoint: <%= ENV["S3_ENDPOINT"] %>
|
endpoint: <%= ENV["S3_ENDPOINT"] %>
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class ChangeFlipperGatesValueToText < ActiveRecord::Migration[7.1]
|
||||||
|
def up
|
||||||
|
# Ensure this incremental update migration is idempotent
|
||||||
|
return unless connection.column_exists? :flipper_gates, :value, :string
|
||||||
|
|
||||||
|
if index_exists? :flipper_gates, [:feature_key, :key, :value]
|
||||||
|
remove_index :flipper_gates, [:feature_key, :key, :value]
|
||||||
|
end
|
||||||
|
change_column :flipper_gates, :value, :text
|
||||||
|
add_index :flipper_gates, [:feature_key, :key, :value], unique: true, length: { value: 255 }
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
change_column :flipper_gates, :value, :string
|
||||||
|
end
|
||||||
|
end
|
||||||
19
db/schema.rb
@@ -10,12 +10,12 @@
|
|||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema[7.0].define(version: 2023_10_24_104909) do
|
ActiveRecord::Schema[7.1].define(version: 2024_02_16_124640) do
|
||||||
create_table "active_storage_attachments", force: :cascade do |t|
|
create_table "active_storage_attachments", force: :cascade do |t|
|
||||||
t.string "name", null: false
|
t.string "name", null: false
|
||||||
t.string "record_type", null: false
|
t.string "record_type", null: false
|
||||||
t.bigint "record_id", null: false
|
t.integer "record_id", null: false
|
||||||
t.bigint "blob_id", null: false
|
t.integer "blob_id", null: false
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
|
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
|
t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
|
||||||
@@ -34,7 +34,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_10_24_104909) do
|
|||||||
end
|
end
|
||||||
|
|
||||||
create_table "active_storage_variant_records", force: :cascade do |t|
|
create_table "active_storage_variant_records", force: :cascade do |t|
|
||||||
t.bigint "blob_id", null: false
|
t.integer "blob_id", null: false
|
||||||
t.string "variation_digest", null: false
|
t.string "variation_digest", null: false
|
||||||
t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true
|
t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true
|
||||||
end
|
end
|
||||||
@@ -50,12 +50,17 @@ ActiveRecord::Schema[7.0].define(version: 2023_10_24_104909) do
|
|||||||
create_table "donations", force: :cascade do |t|
|
create_table "donations", force: :cascade do |t|
|
||||||
t.integer "user_id"
|
t.integer "user_id"
|
||||||
t.integer "amount_sats"
|
t.integer "amount_sats"
|
||||||
t.integer "amount_eur"
|
t.integer "fiat_amount"
|
||||||
t.integer "amount_usd"
|
|
||||||
t.string "public_name"
|
t.string "public_name"
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
t.datetime "paid_at", precision: nil
|
t.datetime "paid_at", precision: nil
|
||||||
|
t.string "fiat_currency", default: "USD"
|
||||||
|
t.string "donation_method"
|
||||||
|
t.string "payment_method"
|
||||||
|
t.string "btcpay_invoice_id"
|
||||||
|
t.string "payment_status"
|
||||||
|
t.index ["payment_status"], name: "index_donations_on_payment_status"
|
||||||
t.index ["user_id"], name: "index_donations_on_user_id"
|
t.index ["user_id"], name: "index_donations_on_user_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -69,7 +74,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_10_24_104909) do
|
|||||||
create_table "flipper_gates", force: :cascade do |t|
|
create_table "flipper_gates", force: :cascade do |t|
|
||||||
t.string "feature_key", null: false
|
t.string "feature_key", null: false
|
||||||
t.string "key", null: false
|
t.string "key", null: false
|
||||||
t.string "value"
|
t.text "value"
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
t.index ["feature_key", "key", "value"], name: "index_flipper_gates_on_feature_key_and_key_and_value", unique: true
|
t.index ["feature_key", "key", "value"], name: "index_flipper_gates_on_feature_key_and_key_and_value", unique: true
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ require 'sidekiq/testing'
|
|||||||
ldap = LdapService.new
|
ldap = LdapService.new
|
||||||
|
|
||||||
Sidekiq::Testing.inline! do
|
Sidekiq::Testing.inline! do
|
||||||
CreateAccount.call(
|
CreateAccount.call(account: {
|
||||||
username: "admin", domain: "kosmos.org", email: "admin@example.com",
|
username: "admin", domain: "kosmos.org", email: "admin@example.com",
|
||||||
password: "admin is admin", confirmed: true
|
password: "admin is admin", confirmed: true
|
||||||
)
|
})
|
||||||
|
|
||||||
ldap.add_attribute "cn=admin,ou=kosmos.org,cn=users,dc=kosmos,dc=org", :admin, "true"
|
ldap.add_attribute "cn=admin,ou=kosmos.org,cn=users,dc=kosmos,dc=org", :admin, "true"
|
||||||
|
|
||||||
@@ -15,9 +15,9 @@ Sidekiq::Testing.inline! do
|
|||||||
email = Faker::Internet.unique.email
|
email = Faker::Internet.unique.email
|
||||||
next if username.length < 3
|
next if username.length < 3
|
||||||
|
|
||||||
CreateAccount.call(
|
CreateAccount.call(account: {
|
||||||
username: username, domain: "kosmos.org", email: email,
|
username: username, domain: "kosmos.org", email: email,
|
||||||
password: "user is user", confirmed: true
|
password: "user is user", confirmed: true
|
||||||
)
|
})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ services:
|
|||||||
ldap:
|
ldap:
|
||||||
image: 4teamwork/389ds:latest
|
image: 4teamwork/389ds:latest
|
||||||
volumes:
|
volumes:
|
||||||
- ./tmp/389ds:/data
|
- 389ds-data:/data
|
||||||
networks:
|
networks:
|
||||||
- external_network
|
- external_network
|
||||||
- internal_network
|
- internal_network
|
||||||
@@ -16,11 +16,12 @@ services:
|
|||||||
restart: always
|
restart: always
|
||||||
image: redis:7-alpine
|
image: redis:7-alpine
|
||||||
networks:
|
networks:
|
||||||
|
- external_network
|
||||||
- internal_network
|
- internal_network
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ['CMD', 'redis-cli', 'ping']
|
test: ['CMD', 'redis-cli', 'ping']
|
||||||
volumes:
|
volumes:
|
||||||
- ./tmp/redis:/data
|
- redis-data:/data
|
||||||
|
|
||||||
web:
|
web:
|
||||||
build: .
|
build: .
|
||||||
@@ -42,8 +43,10 @@ services:
|
|||||||
LDAP_ADMIN_PASSWORD: passthebutter
|
LDAP_ADMIN_PASSWORD: passthebutter
|
||||||
LDAP_USE_TLS: "false"
|
LDAP_USE_TLS: "false"
|
||||||
REDIS_URL: redis://redis:6379/0
|
REDIS_URL: redis://redis:6379/0
|
||||||
|
ACTIVE_STORAGE_PATH: "/akkounts/tmp/attachments"
|
||||||
RS_REDIS_URL: redis://redis:6379/1
|
RS_REDIS_URL: redis://redis:6379/1
|
||||||
RS_STORAGE_URL: "http://localhost:4567"
|
RS_STORAGE_URL: "http://localhost:4567"
|
||||||
|
S3_ENABLED: false
|
||||||
depends_on:
|
depends_on:
|
||||||
- ldap
|
- ldap
|
||||||
- redis
|
- redis
|
||||||
@@ -67,6 +70,7 @@ services:
|
|||||||
REDIS_URL: redis://redis:6379/0
|
REDIS_URL: redis://redis:6379/0
|
||||||
RS_REDIS_URL: redis://redis:6379/1
|
RS_REDIS_URL: redis://redis:6379/1
|
||||||
RS_STORAGE_URL: "http://localhost:4567"
|
RS_STORAGE_URL: "http://localhost:4567"
|
||||||
|
S3_ENABLED: false
|
||||||
depends_on:
|
depends_on:
|
||||||
- ldap
|
- ldap
|
||||||
- redis
|
- redis
|
||||||
@@ -81,10 +85,10 @@ services:
|
|||||||
- "9000:9000"
|
- "9000:9000"
|
||||||
- "9001:9001"
|
- "9001:9001"
|
||||||
volumes:
|
volumes:
|
||||||
- ./tmp/minio:/data
|
- minio-data:/data
|
||||||
|
|
||||||
liquor-cabinet:
|
liquor-cabinet:
|
||||||
image: gitea.kosmos.org/5apps/liquor-cabinet:2.0.0-beta.2
|
image: gitea.kosmos.org/5apps/liquor-cabinet:2.0.0-rc.1
|
||||||
networks:
|
networks:
|
||||||
- external_network
|
- external_network
|
||||||
- internal_network
|
- internal_network
|
||||||
@@ -116,3 +120,11 @@ networks:
|
|||||||
external_network:
|
external_network:
|
||||||
internal_network:
|
internal_network:
|
||||||
internal: true
|
internal: true
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
389ds-data:
|
||||||
|
driver: local
|
||||||
|
minio-data:
|
||||||
|
driver: local
|
||||||
|
redis-data:
|
||||||
|
driver: local
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
"postcss-preset-env": "^7.8.3",
|
"postcss-preset-env": "^7.8.3",
|
||||||
"tailwindcss": "^3.2.4"
|
"tailwindcss": "^3.2.4"
|
||||||
},
|
},
|
||||||
"version": "0.8.1",
|
"version": "0.9.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build:css:tailwind": "tailwindcss --postcss -i ./app/assets/stylesheets/application.tailwind.css -o ./app/assets/builds/application.css",
|
"build:css:tailwind": "tailwindcss --postcss -i ./app/assets/stylesheets/application.tailwind.css -o ./app/assets/builds/application.css",
|
||||||
"build:css": "yarn run build:css:tailwind"
|
"build:css": "yarn run build:css:tailwind"
|
||||||
|
|||||||
BIN
public/img/logos/icon_k9-mail.png
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
public/img/logos/icon_thunderbird.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
11
spec/components/app_catalog/web_app_icon_component_spec.rb
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
require "rails_helper"
|
||||||
|
|
||||||
|
RSpec.describe AppCatalog::WebAppIconComponent, type: :component do
|
||||||
|
describe "No web app given" do
|
||||||
|
it "renders the default icon" do
|
||||||
|
expect(
|
||||||
|
render_inline(described_class.new(web_app: nil)) {}.to_html
|
||||||
|
).to include("icon-remotestorage")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -23,35 +23,35 @@ RSpec.describe 'Admin/global settings', type: :feature do
|
|||||||
scenario "Opening service settings shows page for first service" do
|
scenario "Opening service settings shows page for first service" do
|
||||||
visit admin_settings_services_path
|
visit admin_settings_services_path
|
||||||
|
|
||||||
expect(current_url).to eq(admin_settings_services_url(params: { s: "btcpay" }))
|
expect(current_url).to eq(admin_settings_service_url("btcpay"))
|
||||||
end
|
end
|
||||||
|
|
||||||
scenario "View service settings" do
|
scenario "View service settings" do
|
||||||
visit admin_settings_services_path(params: { s: "ejabberd" })
|
visit admin_settings_service_path("ejabberd")
|
||||||
|
|
||||||
expect(page).to have_content("Enable ejabberd integration")
|
expect(page).to have_content("Enable ejabberd integration")
|
||||||
expect(page).to have_field("API URL", with: "http://xmpp.example.com/api")
|
expect(page).to have_field("API URL", with: "http://xmpp.example.com/api")
|
||||||
end
|
end
|
||||||
|
|
||||||
scenario "Disable a service integration" do
|
scenario "Disable a service integration" do
|
||||||
visit admin_settings_services_path(params: { s: "ejabberd" })
|
visit admin_settings_service_path("ejabberd")
|
||||||
expect(page).to have_checked_field("setting[ejabberd_enabled]")
|
expect(page).to have_checked_field("setting[ejabberd_enabled]")
|
||||||
|
|
||||||
uncheck "setting[ejabberd_enabled]"
|
uncheck "setting[ejabberd_enabled]"
|
||||||
click_button "Save"
|
click_button "Save"
|
||||||
|
|
||||||
expect(current_url).to eq(admin_settings_services_url(params: { s: "ejabberd" }))
|
expect(current_url).to eq(admin_settings_service_url("ejabberd"))
|
||||||
expect(page).to_not have_checked_field("setting[ejabberd_enabled]")
|
expect(page).to_not have_checked_field("setting[ejabberd_enabled]")
|
||||||
expect(page).to_not have_field("API URL", disabled: true)
|
expect(page).to_not have_field("API URL", disabled: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
scenario "Resettable fields" do
|
scenario "Resettable fields" do
|
||||||
visit admin_settings_services_path(params: { s: "ejabberd" })
|
visit admin_settings_service_path("ejabberd")
|
||||||
expect(page).to have_field("API URL", with: "http://xmpp.example.com/api")
|
expect(page).to have_field("API URL", with: "http://xmpp.example.com/api")
|
||||||
expect(page).to_not have_css('input#setting_ejabberd_api_url+button')
|
expect(page).to_not have_css('input#setting_ejabberd_api_url+button')
|
||||||
|
|
||||||
Setting.ejabberd_api_url = "http://example.com/foo"
|
Setting.ejabberd_api_url = "http://example.com/foo"
|
||||||
visit admin_settings_services_path(params: { s: "ejabberd" })
|
visit admin_settings_service_path("ejabberd")
|
||||||
expect(page).to have_field("API URL", with: "http://example.com/foo")
|
expect(page).to have_field("API URL", with: "http://example.com/foo")
|
||||||
expect(page).to have_css('input#setting_ejabberd_api_url+button')
|
expect(page).to have_css('input#setting_ejabberd_api_url+button')
|
||||||
end
|
end
|
||||||
|
|||||||
55
spec/features/admin/users_spec.rb
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
require "rails_helper"
|
||||||
|
|
||||||
|
RSpec.describe "Admin: User management", type: :feature do
|
||||||
|
let(:admin) { create :user }
|
||||||
|
let(:user) { create :user, id: 2, cn: "alfred", email: "alfred@example.com" }
|
||||||
|
|
||||||
|
before do
|
||||||
|
user.save!
|
||||||
|
|
||||||
|
allow(Devise::LDAP::Adapter).to receive(:get_ldap_param)
|
||||||
|
.with(admin.cn, :admin).and_return(["true"])
|
||||||
|
allow(Devise::LDAP::Adapter).to receive(:get_ldap_param)
|
||||||
|
.with(user.cn, :admin).and_return(nil)
|
||||||
|
allow_any_instance_of(User).to receive(:ldap_entry)
|
||||||
|
.and_return({ uid: user.cn, mail: user.email, display_name: "Freddy" })
|
||||||
|
allow_any_instance_of(LdapManager::FetchAvatar).to receive(:call)
|
||||||
|
.and_return(nil)
|
||||||
|
|
||||||
|
login_as admin, :scope => :user
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "User details page" do
|
||||||
|
before do
|
||||||
|
visit admin_user_path("alfred")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "shows the user info" do
|
||||||
|
within "h1" do
|
||||||
|
expect(page).to have_content("User: alfred")
|
||||||
|
end
|
||||||
|
expect(page).to have_content("alfred@example.com")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
scenario 'Add invitations to account' do
|
||||||
|
visit admin_user_path("alfred")
|
||||||
|
find("#add-invitations").click
|
||||||
|
|
||||||
|
select "5", :from => "amount"
|
||||||
|
uncheck "notify_user"
|
||||||
|
click_button "Add"
|
||||||
|
|
||||||
|
expect(user.invitations.count).to eq(5)
|
||||||
|
end
|
||||||
|
|
||||||
|
scenario 'Remove invitations from account' do
|
||||||
|
3.times { Invitation.create(user: user) }
|
||||||
|
expect(user.invitations.count).to eq(3)
|
||||||
|
|
||||||
|
visit admin_user_path("alfred")
|
||||||
|
find("#remove-invitations").click
|
||||||
|
|
||||||
|
expect(user.invitations.count).to eq(0)
|
||||||
|
end
|
||||||
|
end
|
||||||
62
spec/features/settings/email_spec.rb
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe 'E-Mail settings', type: :feature do
|
||||||
|
let(:user) { create :user }
|
||||||
|
|
||||||
|
feature "Reset email password" do
|
||||||
|
before do
|
||||||
|
login_as user, :scope => :user
|
||||||
|
|
||||||
|
allow_any_instance_of(User).to receive(:valid_ldap_authentication?)
|
||||||
|
.with("invalid password").and_return(false)
|
||||||
|
allow_any_instance_of(User).to receive(:valid_ldap_authentication?)
|
||||||
|
.with("valid password").and_return(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
scenario 'fails with invalid password' do
|
||||||
|
expect(LdapManager::UpdateEmailPassword).not_to receive(:call)
|
||||||
|
expect(LdapManager::UpdateEmailMaildrop).not_to receive(:call)
|
||||||
|
|
||||||
|
visit setting_path(:email)
|
||||||
|
fill_in 'Current account password', with: "invalid password"
|
||||||
|
click_button "Create new email password"
|
||||||
|
|
||||||
|
within ".error-msg" do
|
||||||
|
expect(page).to have_content("Wrong password")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
scenario 'works with valid password' do
|
||||||
|
allow_any_instance_of(User).to receive(:dn)
|
||||||
|
.and_return("cn=#{user.cn},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, email_maildrop: user.address })
|
||||||
|
|
||||||
|
expect(LdapManager::UpdateEmailPassword).to receive(:call).and_return(true)
|
||||||
|
expect(LdapManager::UpdateEmailMaildrop).not_to receive(:call)
|
||||||
|
|
||||||
|
visit setting_path(:email)
|
||||||
|
fill_in 'Current account password', with: "valid password"
|
||||||
|
click_button "Create new email password"
|
||||||
|
|
||||||
|
expect(current_url).to eq(new_password_services_email_url)
|
||||||
|
end
|
||||||
|
|
||||||
|
scenario 'updates the maildrop attribute if necessary' do
|
||||||
|
allow_any_instance_of(User).to receive(:dn)
|
||||||
|
.and_return("cn=#{user.cn},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, email_maildrop: "mahafaly@example.com" })
|
||||||
|
|
||||||
|
expect(LdapManager::UpdateEmailPassword).to receive(:call).and_return(true)
|
||||||
|
expect(LdapManager::UpdateEmailMaildrop).to receive(:call)
|
||||||
|
.with(dn: user.dn, address: user.address).and_return(true)
|
||||||
|
|
||||||
|
visit setting_path(:email)
|
||||||
|
fill_in 'Current account password', with: "valid password"
|
||||||
|
click_button "Create new email password"
|
||||||
|
|
||||||
|
expect(current_url).to eq(new_password_services_email_url)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -28,7 +28,7 @@ RSpec.describe 'Experimental Settings', type: :feature do
|
|||||||
scenario 'Remove nostr pubkey from account' do
|
scenario 'Remove nostr pubkey from account' do
|
||||||
visit setting_path(:experiments)
|
visit setting_path(:experiments)
|
||||||
expect(page).to have_field("nostr_public_key",
|
expect(page).to have_field("nostr_public_key",
|
||||||
with: "07e188a1ff87ce171d517b8ed2bb7a31b1d3453a0db3b15379ec07b724d232f3",
|
with: "npub1qlsc3g0lsl8pw8230w8d9wm6xxcax3f6pkemz5measrmwfxjxteslf2hac",
|
||||||
disabled: true)
|
disabled: true)
|
||||||
|
|
||||||
click_link "Remove"
|
click_link "Remove"
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ RSpec.describe 'Profile settings', type: :feature do
|
|||||||
uid: user.cn, ou: user.ou, display_name: "Mark"
|
uid: user.cn, ou: user.ou, display_name: "Mark"
|
||||||
})
|
})
|
||||||
allow_any_instance_of(User).to receive(:avatar).and_return(avatar_base64)
|
allow_any_instance_of(User).to receive(:avatar).and_return(avatar_base64)
|
||||||
|
|
||||||
|
Flipper.enable "avatar_upload"
|
||||||
end
|
end
|
||||||
|
|
||||||
feature "Update display name" do
|
feature "Update display name" do
|
||||||
@@ -29,7 +31,7 @@ RSpec.describe 'Profile settings', type: :feature do
|
|||||||
|
|
||||||
scenario 'works with valid input' do
|
scenario 'works with valid input' do
|
||||||
expect(LdapManager::UpdateDisplayName).to receive(:call)
|
expect(LdapManager::UpdateDisplayName).to receive(:call)
|
||||||
.with(user.dn, "Marky Mark").and_return(true)
|
.with(dn: user.dn, display_name: "Marky Mark").and_return(true)
|
||||||
|
|
||||||
visit setting_path(:profile)
|
visit setting_path(:profile)
|
||||||
fill_in 'Display name', with: "Marky Mark"
|
fill_in 'Display name', with: "Marky Mark"
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ RSpec.describe "Signup", type: :feature do
|
|||||||
expect(page).to have_content("Choose a password")
|
expect(page).to have_content("Choose a password")
|
||||||
|
|
||||||
expect(CreateAccount).to receive(:call)
|
expect(CreateAccount).to receive(:call)
|
||||||
.with({
|
.with(account: {
|
||||||
username: "tony", domain: "kosmos.org",
|
username: "tony", domain: "kosmos.org",
|
||||||
email: "tony@example.com", password: "a-valid-password",
|
email: "tony@example.com", password: "a-valid-password",
|
||||||
invitation: Invitation.last
|
invitation: Invitation.last
|
||||||
@@ -97,7 +97,7 @@ RSpec.describe "Signup", type: :feature do
|
|||||||
expect(page).to have_content("Password is too short")
|
expect(page).to have_content("Password is too short")
|
||||||
|
|
||||||
expect(CreateAccount).to receive(:call)
|
expect(CreateAccount).to receive(:call)
|
||||||
.with({
|
.with(account: {
|
||||||
username: "tony", domain: "kosmos.org",
|
username: "tony", domain: "kosmos.org",
|
||||||
email: "tony@example.com", password: "a-valid-password",
|
email: "tony@example.com", password: "a-valid-password",
|
||||||
invitation: Invitation.last
|
invitation: Invitation.last
|
||||||
|
|||||||