85 Commits

Author SHA1 Message Date
eac8fa6edb 0.9.0
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-07 14:48:27 +01:00
43f918a074 Update liquor-cabinet image, fix LC/redis networking issue on Linux
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-06 22:07:35 +01:00
e322867d79 Merge pull request 'Fix login redirect for existing RS auth' (#180) from bugfix/178-rs_login_redirect into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #180
2024-03-06 21:06:27 +00:00
4d6fa318b7 Fix login redirect for existing RS auth
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 3s
fixes #178
2024-03-06 22:00:15 +01:00
4e8878a4b5 Merge pull request 'Allow running specs in Docker container, update README' (#177) from dev/docker_rspec into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #177
Reviewed-by: galfert <garret.alfert@gmail.com>
2024-03-03 11:47:53 +00:00
e65b890880 Update db schema
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 3s
2024-03-02 17:31:44 +01:00
f57edd4d3b Update README to account for Docker Compose everywhere
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-03-02 16:57:07 +01:00
1afd56fb80 Allow running specs in Docker (Web) container 2024-03-02 16:56:07 +01:00
71669a4b96 Merge pull request 'Refactor admin settings routes' (#156) from feature/content_settings into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #156
2024-03-02 14:30:21 +00:00
c312e30c17 Fix link in admin settings/services sidenav
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 2s
2024-03-02 15:26:12 +01:00
51f4556ede Refactor admin settings routes
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
This is much cleaner, and semantically more correct.
2024-03-02 14:22:08 +00:00
4fa4ae6b54 Merge pull request 'Comment out settings in .env.example' (#175) from task/env-example into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #175
Reviewed-by: Râu Cao <raucao@kosmos.org>
2024-03-02 13:30:18 +00:00
869ff4691b Comment out settings in .env.example
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 2s
2024-03-02 12:43:59 +01:00
822a2dc018 Fix specs
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-01 17:15:02 +01:00
5b7fc3707b Hide avatar settings behind feature flag
Some checks failed
continuous-integration/drone/push Build is failing
In favor of #157
2024-03-01 11:13:49 +01:00
0e2dc54dc6 Merge pull request 'Upgrade Rails to 7.1, update dependencies, require Ruby 3.x' (#160) from chore/update_dependencies into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #160
Reviewed-by: slvrbckt <slvrbckt@noreply.kosmos.org>
2024-02-27 18:56:59 +00:00
87f09c94d0 Merge pull request 'Fix/improve local ActiveStorage backend usage and handling of WebApp icons' (#162) from bugfix/local_web_app_icons into chore/update_dependencies
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 2s
Reviewed-on: #162
Reviewed-by: greg <greg@noreply.kosmos.org>
2024-02-27 16:07:55 +00:00
b33b8104a8 Fix typo
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 3s
2024-02-27 14:33:37 +01:00
4a4a222973 Merge branch 'chore/update_dependencies' into bugfix/local_web_app_icons
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-02-23 18:25:23 +00:00
8c524abcf5 Merge pull request 'Fix Docker volume permissions on some host platforms' (#171) from bugfix/macos_docker_volumes into chore/update_dependencies
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Reviewed-on: #171
Reviewed-by: galfert <garret.alfert@gmail.com>
2024-02-23 18:24:10 +00:00
a852ab75ae Fix Docker volume permissions on some host platforms
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 2s
Use named volumes instead of bind mounts.
2024-02-23 16:43:56 +01:00
de1f234c15 Merge branch 'chore/update_dependencies' into bugfix/local_web_app_icons
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-02-22 15:13:18 +01:00
4581900427 Merge pull request 'Fix Ruby in Docker container on Apple silicon' (#168) from chore/fix_docker_ruby_on_apple_silicon into chore/update_dependencies
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Reviewed-on: #168
Reviewed-by: slvrbckt <slvrbckt@noreply.kosmos.org>
2024-02-22 14:12:05 +00:00
56d91083e5 Fix seeds for new keyword argument
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 3s
2024-02-22 13:24:41 +01:00
ba7c3795f8 Add pkg-config
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-02-22 11:29:56 +01:00
bbf3fb91a0 Fix Ruby in Docker container on Apple silicon
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-02-22 10:47:21 +01:00
1754df73cb Merge pull request 'Allow admins to add and remove invitations per account' (#167) from feature/164-invites into chore/update_dependencies
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Reviewed-on: #167
Reviewed-by: galfert <garret.alfert@gmail.com>
2024-02-17 10:17:47 +00:00
9a1f9abf84 Formatting
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 3s
2024-02-10 12:53:26 +01:00
2753388e1e Add specs for admin user management 2024-02-10 12:53:11 +01:00
f3159d30f1 Allow admins to add and remove invitations per account
All checks were successful
continuous-integration/drone/push Build is passing
2024-02-10 11:21:45 +01:00
ca238be6f4 Add option for hiding close button in modal windows 2024-02-10 10:24:09 +01:00
8747ce4eb0 Remove multi-domain support on admin user pages
All checks were successful
continuous-integration/drone/push Build is passing
refs #166
2024-02-10 08:55:15 +01:00
fcda3b9c8c WIP Make dropdowns more configurable, add invitations menu to admin page 2024-02-09 18:57:07 +01:00
67689dcce3 Add service for creating invites
All checks were successful
continuous-integration/drone/push Build is passing
2024-02-09 17:59:07 +01:00
22ffcd54db Patch away a deprecation warning caused by Devise
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-02-09 17:58:28 +01:00
bd1b177993 Rescue all icon download/upload errors, send to Sentry
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-02-08 13:36:17 +01:00
3f110995a4 Add timestamp to icon filenames
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
There can be race condition when a background job is supposed to delete
an icon while there is a new one being attached. Also, this encodes the
date/time when the icon has been added, for inspection and convenience.
2024-02-08 13:03:32 +01:00
a7410058fa Save WebApp before fetching icons 2024-02-08 13:02:08 +01:00
411587456b Destroy dependent RS auths when destroying a WebApp 2024-02-08 13:01:19 +01:00
84e915ece9 Allow custom path for ActiveStorage local/disk backend 2024-02-08 13:01:07 +01:00
70ac3b0a70 Fix RS dashboard for auths without Web App
RS auths without a valid domain name will not fetch any metadata and
therefore not create a WebApp record. This fixes icons being looked up
anyway, resulting in exceptions
2024-02-08 12:51:53 +01:00
a7cbd8ce36 Allow disabling S3 explicitly, disable in Docker Compose
For example when there is a .env.development for running the app on a
host machine directly, but as a developer you also want to run it with
Docker Compose from time to time.
2024-02-08 12:50:34 +01:00
c9052b35f6 Database update for Flipper
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-02-08 12:29:11 +01:00
3b96130491 Upgrade web-console, fix it for Docker
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Was failing silently in Docker, because the warnings were turned off.
2024-02-08 12:26:28 +01:00
176b1a10c6 Remove obsolete closing tag 2024-02-08 12:10:14 +01:00
1c54e4c0b5 New CI image Dockerfile
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-02-03 11:36:06 +02:00
7796a22491 Switch to newly published manifique gem
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-02-02 17:55:20 +02:00
7e6e917ae1 Use new CI image with Ruby 3.3.0
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-02-02 17:28:33 +02:00
28cfe4b1e7 Fix deprecation warning 2024-02-02 16:58:04 +02:00
179a82d2dd Use keyword arguments for ApplicationService calls
Not all services are using keywords, which breaks those calls in Ruby 3
2024-02-02 15:50:25 +02:00
420442c1c0 Update Ruby for Dockerfile/Compose 2024-02-02 14:34:09 +02:00
68c5758ecc Update dependencies, upgrade to Rails 7.1, require Ruby 3.x 2024-02-02 14:25:47 +02:00
c5dd3c30a6 Use full URL for S3 alias host
All checks were successful
continuous-integration/drone/push Build is passing
2024-02-02 14:01:47 +02:00
422d5c7cd2 Fix address missing in lightning address receive notifications
All checks were successful
continuous-integration/drone/push Build is passing
2024-02-01 16:22:20 +02:00
5a23d523a8 Add fallback icons for apps on RS app dashboard
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-29 18:33:06 +02:00
f8da034e66 Fail gracefully when remote icon is 404
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-29 14:54:18 +02:00
b0b56fcf92 Fix lnurlp route
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-29 11:18:51 +02:00
0cf000c1b8 Merge pull request 'Only support primary domain for Lightning Address' (#158) from chore/well-known_routes into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #158
2024-01-29 09:03:37 +00:00
fa9a924b0a Merge pull request 'Fix RS auth array usage in production' (#159) from bugfix/postgresql_arrays into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #159
2024-01-29 08:58:02 +00:00
50f91cc7d7 Fix RS auth array usage in production
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 3s
Serialization into YAML breaks the native PostgreSQL array usage.

Needs to be adjusted later to not use the environment, but database
adapter (issue #149).
2024-01-29 10:52:52 +02:00
a628a03f84 Only support primary domain for Lightning Address
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 3s
Part of the process of removing support for serving multiple domains
from a single akkounts instance.

Also puts the Lightning Address discovery routes under the .well-known
path. Combined, these changes simplify reverse-proxying to the
.well-known endpoints.
2024-01-26 16:08:21 +02:00
eaf41e0835 Adjust spec for c32fc51
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-26 16:02:47 +02:00
243cf9c08d Don't add CORS headers for Webfinger in production
Some checks failed
continuous-integration/drone/push Build is failing
The reverse proxy should handle it.
2024-01-26 11:01:45 +03:00
c32fc51aab Do not enable email service by default
Some checks failed
continuous-integration/drone/push Build is failing
2024-01-26 09:38:38 +03:00
aa9178d569 Sort service ENV vars alphabetically, add missing lndhub var
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-26 08:36:58 +03:00
281938dd64 Only set API CORS headers in development
All checks were successful
continuous-integration/drone/push Build is passing
In production, this is the reverse proxy's responsibility
2024-01-22 15:35:13 +03:00
fafc5d8f6f Improve copy
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-22 12:10:17 +03:00
1238359b5f Remove superfluous header text
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-22 12:04:55 +03:00
84220beb1c Merge pull request 'Add email service and settings' (#154) from feature/email_service into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #154
Reviewed-by: galfert <garret.alfert@gmail.com>
2024-01-22 09:01:18 +00:00
1e9ec9bb76 Fix wrong prefix for email QR code
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 3s
2024-01-22 11:52:45 +03:00
21e51a7c40 Merge pull request 'Update nostr gem, switch to Ruby for bech32 encoding' (#155) from chore/bech32_handling into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #155
2024-01-21 09:31:51 +00:00
e3c30f7b16 Remove obsolete function
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 3s
2024-01-15 13:00:48 +03:00
b4f0c60ea0 Update nostr gem, switch to Ruby for bech32 encoding
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-01-15 12:54:58 +03:00
1a5a2177b4 Update spec
Some checks failed
continuous-integration/drone/push Build is failing
2024-01-15 12:38:27 +03:00
7e8443c598 Change Lightning balance property
Some checks failed
continuous-integration/drone/push Build is failing
... so that clients can use the same property with all balances
2024-01-15 11:39:24 +03:00
7b71f2cf76 Revert "Fix fixture file"
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
This reverts commit c7b137e5eb.
2024-01-10 18:35:04 +03:00
c7b137e5eb Fix fixture file
Some checks failed
continuous-integration/drone Build is failing
2024-01-10 18:30:19 +03:00
958d18d61a Add email service and settings 2024-01-10 18:30:05 +03:00
3aa0c49507 Set CORS headers for BTCPay API endpoints 2024-01-02 09:49:09 +03:00
Râu Cao
4e566a0607 Merge pull request 'Fetch/store Web App metadata and icons, finish RS integration' (#153) from feature/142-webapp_database into master
Reviewed-on: #153
Reviewed-by: galfert <garret.alfert@gmail.com>
2024-01-01 13:18:47 +00:00
Râu Cao
aab6793b86 Improve permission list in RS emails
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 3s
2023-11-20 18:32:52 +01:00
Râu Cao
cfd0935bdc Notify user about new RS authorizations 2023-11-20 18:24:34 +01:00
Râu Cao
c2dae105ff Add settings page for Storage, add notification prefs 2023-11-20 18:22:06 +01:00
Râu Cao
e301ac8e2e Fix title
All checks were successful
continuous-integration/drone/push Build is passing
2023-11-01 22:47:59 +01:00
Râu Cao
03a1d9f277 Allow existing user records with reserved usernames to be saved
Some checks are pending
continuous-integration/drone/push Build is running
2023-11-01 22:26:53 +01:00
109 changed files with 1517 additions and 551 deletions

View File

@@ -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

View File

@@ -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'

View File

@@ -1 +1 @@
2.7.2 3.3.0

View File

@@ -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
View File

@@ -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]

View File

@@ -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

View File

@@ -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

View File

@@ -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 %>

View 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

View File

@@ -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"

View File

@@ -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

View File

@@ -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(

View File

@@ -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

View File

@@ -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>

View File

@@ -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

View File

@@ -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 %>

View File

@@ -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: {

View File

@@ -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

View File

@@ -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|

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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();
}
}

View File

@@ -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")

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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
View 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

View File

@@ -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>

View 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>

View File

@@ -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 } %>

View 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 %>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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 %>

View File

@@ -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

View File

@@ -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") %>

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-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

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-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

View 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

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-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

View File

@@ -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,

View File

@@ -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.

View File

@@ -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 %>

View 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 %>

View 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 %>

View 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 %>

View File

@@ -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,

View File

@@ -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" %>

View 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 %>

View File

@@ -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(

View File

@@ -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")),
) %> ) %>

View File

@@ -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",

View File

@@ -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>

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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"] %>

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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"

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View 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

View File

@@ -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

View 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

View 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

View File

@@ -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"

View File

@@ -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"

View File

@@ -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

Some files were not shown because too many files have changed in this diff Show More