423 Commits

Author SHA1 Message Date
raucao 8b3243af6b Sort API methods alphabetically
continuous-integration/drone/push Build is passing
2025-05-15 12:19:09 +04:00
raucao fc36fbf10c Add get_vcard2 to ejabberd client
continuous-integration/drone/push Build is passing
2025-05-15 12:16:53 +04:00
raucao 06d2705c4c Add private_get to ejabberd service
continuous-integration/drone/push Build is passing
2025-05-15 12:01:10 +04:00
raucao 03be2e09e6 Merge pull request 'User avatars' (#223) from feature/user_avatars into master
continuous-integration/drone/push Build is passing
Reviewed-on: #223
Reviewed-by: Greg <greg@noreply.kosmos.org>
2025-05-14 14:58:15 +00:00
raucao 582d339c0a Remove feature gate for avatar upload
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
2025-05-14 18:55:26 +04:00
raucao a098ea43bb Add avatar URL to Webfinger when available
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-05-14 15:39:50 +04:00
raucao 417e346074 Do not use ActiveStorage variants, process original avatar
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Variants are currently broken. So we process the original file with the
most common avatar dimensions and stripping metadata, then hash and
upload only that version.
2025-05-14 14:42:03 +04:00
raucao 1884f082ee Add note about variants not working when not generated ad-hoc
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-05-12 18:07:10 +04:00
raucao 51a3652fc8 Fix S3 keys/paths for user avatars
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Also fixes the avatars controller to work with all back-ends
2025-05-12 16:39:53 +04:00
raucao 46b908839d Add avatar URL to Discourse Connect
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Discourse should download and set the avatar if the user doesn't have
one set yet.
2025-05-12 15:04:56 +04:00
raucao 512f0ccca1 Add controller for rendering avatars on simple URL 2025-05-12 15:04:01 +04:00
raucao 17ffbde03a WIP Store avatars as ActiveStorage attachments
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Also push to LDAP as jpegPhoto
2025-05-11 18:43:21 +04:00
raucao 9e2210c45b Store avatars as binary instead of base64
continuous-integration/drone/push Build is passing
2025-05-10 20:58:36 +04:00
raucao 6d7d722c5d Add inetOrgPerson objectclass to user entries
refs #174
2025-05-08 16:52:54 +04:00
greg ae5d63c613 Merge pull request 'Move remaining credentials from Rails credentials store to ENV' (#221) from chore/215-configs into master
continuous-integration/drone/push Build is passing
Reviewed-on: #221
Reviewed-by: Greg <greg@noreply.kosmos.org>
2025-05-06 17:16:32 +00:00
raucao 93aa26f430 Remove lockbox column
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
2025-05-06 20:14:25 +04:00
raucao 50110c12b9 Remove lockbox gem
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2025-05-06 20:01:01 +04:00
raucao 95843aee6d Remove credentials files
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-05-06 19:50:27 +04:00
raucao 84ed4b2de2 Remove old ln columns from users table
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-05-06 19:47:58 +04:00
raucao 931624cf95 Add encryption credentials to test env
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-05-06 18:14:26 +04:00
raucao eae370b737 Migrate from lockbox to ActiveRecord encryption (1/2)
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2025-05-06 18:09:27 +04:00
raucao 15a9fdec3e Make RS auth work by default in dev with Docker Compose 2025-05-06 18:07:52 +04:00
raucao 3d8619532b Refactor LDAP config
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
* Move credentials to ENV vars in prod
* Use same configs in dev and prod
* Make UID attribute and admin DN configurable
2025-05-06 15:32:59 +04:00
raucao d56edb34f1 Remove SMTP credentials from Rails credentials
Already unused
2025-05-06 15:08:46 +04:00
raucao a97bbf61a8 Fix postgresql query for deleting auth expiry job
continuous-integration/drone/push Build is passing
Solid Queue uses a text column, instead of a jsonb, so we need to cast
it as jsonb on the fly.
2025-05-05 17:37:58 +04:00
raucao 5a523fd220 Merge pull request 'Refactor database configs' (#220) from chore/db_configs into master
continuous-integration/drone/push Build is passing
Reviewed-on: #220
2025-05-05 12:54:22 +00:00
raucao 889c9ae824 Refactor database configs
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
* Move postgres credentials to ENV vars
* Allow postgres in development
* Allow SQlite in production
* Refactor optional lndhub db config

Co-authored-by: Greg Karékinian <greg@karekinian.com>
2025-05-05 15:25:25 +04:00
raucao e686cf42e8 Merge pull request 'Switch from Sidekiq to Solid Queue' (#219) from dev/sidekiq_to_solidqueue into master
continuous-integration/drone/push Build is passing
Reviewed-on: #219
Reviewed-by: Greg <greg@noreply.kosmos.org>
2025-05-05 11:24:56 +00:00
raucao 906468d156 Allow to immediately expire auth via job
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
When running the job before its schedule
2025-05-05 12:46:46 +04:00
raucao ee5c6d86d0 Port RS auth job removal to Solid Queue
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-05-05 11:07:30 +04:00
raucao d1eea85b04 Add Redis gem explicitly, remove sidekiq require
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2025-05-04 18:14:49 +04:00
raucao ecd814641a Remove Sidekiq initializer
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2025-05-04 17:44:37 +04:00
raucao b1dd5800b2 Update lockfile
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2025-05-04 17:42:31 +04:00
raucao 0cad4cdcfe WIP Switch from Sidekiq to Solid Queue
continuous-integration/drone/push Build is running
continuous-integration/drone/pr Build is failing
2025-05-04 17:40:33 +04:00
greg b61906059c Merge pull request 'Upgrade Rails to 8.0' (#216) from chore/upgrade_rails into master
continuous-integration/drone/push Build is passing
Reviewed-on: #216
Reviewed-by: Greg <greg@noreply.kosmos.org>
2025-04-30 08:36:16 +00:00
raucao aef779a59c Switch from Sprockets to Propshaft
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 1s
2025-04-29 17:11:21 +04:00
raucao 1ddecab2c3 Upgrade Rails to 8.0
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-04-28 17:49:54 +04:00
raucao 74b4bc3875 Upgrade Rails to 7.2
continuous-integration/drone/push Build is passing
2025-04-28 00:17:25 +04:00
raucao 646c95ecc2 Fix local/development RS auth URL
continuous-integration/drone/push Build is passing
2025-04-27 16:09:32 +04:00
raucao fb054ae455 Add task for generating ctags
continuous-integration/drone/push Build is passing
2025-04-26 12:37:10 +04:00
raucao 536052e9bf Merge pull request 'Upgrade strfry/deno, port strfry policies to @nostrify/policies' (#214) from chore/upgrade_strfry_deno into master
continuous-integration/drone/push Build is passing
Reviewed-on: #214
2025-04-18 10:51:35 +00:00
raucao b29a0abb0b Document strfry integration
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
2025-04-16 17:34:10 +04:00
raucao 29ff486683 Port strfry policies to @nostrify/policies
Use packages from JSR and adapt code for new policy APIs
2025-04-15 19:01:22 +04:00
raucao e53b9dd186 Upgrade strfry docker image
Contains latest strfry (1.0.4) and deno (2.2.10)
2025-04-15 19:00:52 +04:00
raucao a2921297fe Fix seeds
The CreateAccount service has moved to a namespace
2025-04-11 16:14:44 +04:00
raucao 7df56479a4 Fix 500 when pubkey is nil 2025-01-02 08:30:58 -05:00
raucao 8aa3ca9e23 Merge pull request 'Let users upload their OpenPGP public key, and serve WKD response' (#205) from feature/191-gpg_keys_wkd into master
continuous-integration/drone/push Build is passing
Reviewed-on: #205
Reviewed-by: galfert <garret.alfert@gmail.com>
2024-10-14 14:08:31 +00:00
raucao 3ad1d03785 Merge pull request 'Encrypt all system emails for users with PGP key' (#207) from feature/encrypted_system_emails into feature/191-gpg_keys_wkd
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 4s
Reviewed-on: #207
Reviewed-by: galfert <garret.alfert@gmail.com>
2024-10-14 13:39:01 +00:00
raucao e258a8bd27 Merge pull request 'Use ASCII format for nostrKey LDAP schema' (#206) from chore/nostr_key_ldap_schema into master
continuous-integration/drone/push Build is passing
Reviewed-on: #206
Reviewed-by: galfert <garret.alfert@gmail.com>
2024-10-10 14:18:31 +00:00
raucao 339462f320 Refactor mailer options usage
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 5s
2024-10-08 14:06:10 +02:00
raucao c4c2d16342 Encrypt outgoing emails when possible 2024-10-08 14:05:50 +02:00
raucao 3ee76e26ab Re-import user's pubkey on access
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Sometimes, the pubkey might not be imported in the local keychain
(anymore), but at this point in the code it had been successfully
imported at least once before. So we just (re-)import every time for it
to never fail.
2024-10-08 11:34:18 +02:00
raucao 729e4fd566 Add WKD policy endpoint
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-09-26 23:11:21 +02:00
raucao 8ad6adbaeb Use ASCII format for nostrKey LDAP schema
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Failing after 10m11s
No need for UTF-8
2024-09-25 18:35:48 +02:00
raucao 534e5a9d3c Gracefully handle wrong capitalization of username
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-09-25 00:20:30 +02:00
raucao 1b72c97f42 Remove obsolete code 2024-09-25 00:17:30 +02:00
raucao bfd8ca16a9 Merge branch 'master' into feature/191-gpg_keys_wkd 2024-09-25 00:16:39 +02:00
raucao 64de4deddd Fix serviceEnabled indicator on admin page
continuous-integration/drone/push Build is passing
2024-09-24 21:38:01 +02:00
raucao 9f6fa6deba Remove example link
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Until we have a live example on kosmos.org
2024-09-23 20:36:05 +02:00
raucao 37b106e73c Whitespace
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-09-23 19:22:52 +02:00
raucao c3f1f97e1a Add display name and PGP key to admin user page
Link the key to the ASCII Armor WKD endpoint, if it contains the user's
account address
2024-09-23 19:21:59 +02:00
raucao 4a677178e8 Add Web Key Directory endpoint
Serve public keys in binary and armored text, if they contain a user's
account address.
2024-09-23 19:20:10 +02:00
raucao 3042a02a17 Allow users to update their OpenPGP pubkey
continuous-integration/drone/push Build is passing
2024-09-23 18:13:39 +02:00
raucao 118fddb497 Document URLs for settings controller actions
No need to read the route sources all the time
2024-09-23 16:07:02 +02:00
raucao ba683a7b95 Move some Rails app services to UserManager namespace
continuous-integration/drone/push Build is passing
2024-09-23 16:03:02 +02:00
raucao 90a8a70c15 Add OpenPGP key to LDAP directory and User model
continuous-integration/drone/push Build is passing
2024-09-23 15:20:00 +02:00
raucao 8f7994d82e 0.10.0
continuous-integration/drone/tag Build is passing
continuous-integration/drone/push Build is passing
2024-09-18 15:49:07 +02:00
raucao a7d0e71ab6 Fix spec
continuous-integration/drone/push Build is passing
2024-09-18 14:46:46 +02:00
raucao 27d9f73c61 Set host for RS auth url
continuous-integration/drone/push Build is failing
With X-Forwarded-Host set on the proxied request, Rails uses that host
for URLs. But we need it to be the accounts domain.
2024-09-14 17:17:09 +02:00
raucao ed3de8b16f Allow CORS for all LNURL endpoints
continuous-integration/drone/push Build is passing
2024-09-14 16:46:14 +02:00
raucao d7b4c67953 Fix config when set to empty string
continuous-integration/drone/push Build is passing
2024-09-14 16:40:22 +02:00
greg 7489d4a32f Merge pull request 'Add config for separate primary domain Nostr pubkey' (#204) from feature/nostr_pubkey_primary_domain into master
continuous-integration/drone/push Build is passing
Reviewed-on: #204
Reviewed-by: Greg <greg@noreply.kosmos.org>
2024-09-13 12:33:11 +00:00
raucao ac77e5b7c1 Allow ENV var for new setting
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 5s
2024-09-11 16:31:04 +02:00
raucao e544c28105 Config for separate primary domain Nostr pubkey
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Allow to configure a separate key for the NIP-05 address of the primary
domain vs the accounts domain.
2024-09-11 16:28:12 +02:00
raucao 4909dac5c2 Fix typo
continuous-integration/drone/push Build is passing
The return value of `strip!` is `nil`
2024-09-11 16:26:48 +02:00
raucao 3cf4348695 Merge pull request 'Make default user services configurable by admins' (#203) from feature/default_service_settings into master
continuous-integration/drone/push Build is passing
Reviewed-on: #203
Reviewed-by: galfert <garret.alfert@gmail.com>
2024-09-11 11:21:38 +00:00
raucao af3da0a26c Set CORS headers for all .well-known responses
continuous-integration/drone/push Build is passing
So we don't have to consider it for reverse proxies etc.
2024-09-10 16:06:11 +02:00
raucao 2d32320c7d Style check boxes
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 5s
2024-09-05 11:24:38 +02:00
raucao fc2bec6246 Make default user services configurable by admin
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-09-05 11:11:32 +02:00
raucao 5addd25186 Add service details config, use for known services 2024-09-05 11:10:54 +02:00
raucao 215d178e69 Remove empty spec files 2024-09-05 11:10:10 +02:00
raucao 5474bf66e7 Turn default services into a configurable setting
With the default value being all enabled services
2024-09-04 13:06:32 +02:00
raucao ef2a37e2bf Sort user services in LDAP entry
Makes it predictable for programmatic comparisons (e.g. tests)
2024-09-04 13:05:36 +02:00
raucao 0e3180602c Rename "xmpp" user service back to "ejabberd"
If we ever add support for others, we can combine them as "xmpp" in
helper methods
2024-09-04 13:03:45 +02:00
raucao 15e2f9b962 Remove "in development" note
continuous-integration/drone/push Build is passing
2024-08-28 14:55:34 +02:00
raucao 4ae10c9b53 Refactor settings model
continuous-integration/drone/push Build is passing
Move the various sections to their own concerns, so they're easier to
find and maintain
2024-08-28 14:39:08 +02:00
raucao 45137e0cfe Merge pull request 'Fix Ruby issue on Apple silicon (without compiling a patched Ruby)' (#201) from chore/update_docker_image into master
continuous-integration/drone/push Build is passing
Reviewed-on: #201
Reviewed-by: galfert <garret.alfert@gmail.com>
2024-08-28 08:12:31 +00:00
raucao 717fe93104 Fix spec
continuous-integration/drone/push Build is passing
2024-08-22 14:07:54 +02:00
raucao fdac789ccb Add compatibility section to RS service page
continuous-integration/drone/push Build is failing
2024-08-19 15:13:19 +02:00
raucao 9355dab6b6 Enable RS service for all new users for now
continuous-integration/drone/push Build is failing
2024-08-19 14:48:24 +02:00
raucao f3676949d2 Fix redirect
continuous-integration/drone/push Build is passing
2024-08-17 14:49:19 +02:00
raucao 79952b73c5 Fix link descriptions
continuous-integration/drone/push Build is passing
2024-08-17 14:45:31 +02:00
greg 17c419403e Merge pull request 'Finish MVP of remoteStorage service pages/UI' (#202) from feature/rs_service_page into master
continuous-integration/drone/push Build is passing
Reviewed-on: #202
Reviewed-by: Greg <greg@noreply.kosmos.org>
2024-08-17 12:33:48 +00:00
raucao 6d06312a5c Update manifique gem
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 6s
Fixes a bug with some manifest files
2024-08-14 18:07:27 +02:00
raucao acb399b0b7 Add app recommendation for Notes Together
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is passing
2024-08-14 16:32:06 +02:00
raucao bf20b6467e Re-order services on dashboard
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-08-14 13:37:22 +02:00
raucao b91d90d75c Fix some specs, improve config
Allow empty string to unset nostr relay URL config
2024-08-14 13:37:15 +02:00
raucao 3284bbf6ca Add recommended apps for RS 2024-08-14 13:35:49 +02:00
raucao 171b84ee81 Add tabnav, dedicated auths view to RS service page
Includes a nicer view and illustration for when no auths exist yet
2024-08-14 13:35:02 +02:00
raucao 54b01dd282 Drive-by content update 2024-08-12 11:14:12 +02:00
raucao e08ea64f47 Update Docker base image
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 5s
Fixes the bug with Ruby on Apple silicon
2024-08-12 10:34:02 +02:00
raucao 8cc2c9554f Revert "Fix Ruby in Docker container on Apple silicon"
This reverts commit bbf3fb91a0.
2024-08-12 10:15:18 +02:00
raucao 32dff9c67f Merge pull request 'Add dashboard icons for remoteStorage and email' (#200) from chore/dashboard_service_icons into master
continuous-integration/drone/push Build is passing
Reviewed-on: #200
2024-08-12 07:03:22 +00:00
raucao 126b8b20e0 Improve dashboard icon opacity, layout
continuous-integration/drone Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 5s
2024-08-10 12:44:49 +02:00
raucao 5abf69f356 Add email service icon to dashboard 2024-08-10 12:44:25 +02:00
raucao 210a69bd9b Add Geary app recommendation to email page 2024-08-09 14:19:49 +02:00
raucao bbed3cd367 Add RS logo to service grid, resize others 2024-08-09 12:37:18 +02:00
raucao 7943da0f17 Add note 2024-08-09 12:34:10 +02:00
raucao 620167eedf Merge pull request 'Admin pages: fix more user links, add missing services to user page' (#199) from feature/admin_pages into master
Reviewed-on: #199
2024-08-09 10:33:29 +00:00
raucao e077debfc2 Use npub for njump link
continuous-integration/drone/push Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 4s
2024-06-23 17:30:03 +02:00
raucao 531b2c3002 Fix more links 2024-06-23 17:29:48 +02:00
raucao 6d2bc729b8 Add new services to admin user page
continuous-integration/drone/push Build is passing
2024-06-23 17:26:33 +02:00
raucao 2630ec2af4 Fix admin user links
continuous-integration/drone/push Build is passing
refs #166
2024-06-23 17:24:48 +02:00
raucao daed5c1eea Merge pull request 'Allow non-members to publish zap receipts for members' (#197) from feature/strfry_zap_receipts into master
continuous-integration/drone/push Build is passing
Reviewed-on: #197
Reviewed-by: bumi <bumi@noreply.kosmos.org>
2024-06-22 17:52:03 +00:00
raucao 2e9429bb32 Merge pull request 'Add support for integrated Nostr relay service' (#198) from feature/own_relay into feature/strfry_zap_receipts
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 6s
Reviewed-on: #198
Reviewed-by: bumi <bumi@noreply.kosmos.org>
2024-06-22 17:51:40 +00:00
raucao 37c15c7a62 Check in deno lockfile
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 6s
2024-06-20 15:51:40 +02:00
raucao 01ecea74ff Add pubkey whitelist to strfry policy
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
And allow the local akkounts instance to publish on the local relay
2024-06-20 15:28:17 +02:00
raucao f401a03590 Fix exception for NIP-05 JSON of "_" with relay configured
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-06-20 14:50:02 +02:00
raucao fff6dea100 Add support for placeholder attribute to component
continuous-integration/drone/push Build is passing
2024-06-20 13:54:59 +02:00
raucao 48ab96dda9 Support "_" placeholder username for domain's own NIP-05
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-06-19 20:57:22 +02:00
raucao 7ac3130c18 Consistent formatting
continuous-integration/drone/push Build is passing
2024-06-19 20:31:31 +02:00
raucao cbfa148051 Publish zap receipts to own relay in addition to requested ones
continuous-integration/drone/push Build is passing
2024-06-19 20:26:24 +02:00
raucao 87d900b627 Add own relay to NIP-05 relay list if configured 2024-06-19 20:06:07 +02:00
raucao 926dc06294 Add global setting for own nostr relay 2024-06-19 19:57:09 +02:00
raucao 00b73b06d7 Remove obsolete variable
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-06-19 15:56:45 +02:00
raucao 0daac33915 Allow non-members to publish zap receipts for members
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-06-19 15:43:56 +02:00
raucao 0e472bc311 Improve strfry extras usage 2024-06-19 15:43:24 +02:00
raucao 40b34d0935 Merge pull request 'Add strfry policies and members-only LDAP policy' (#196) from feature/strfry_policies into master
continuous-integration/drone/push Build is passing
Reviewed-on: #196
2024-06-11 20:10:34 +00:00
raucao 61cb8f4941 Add script for syncing notes from remote relays
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 4s
2024-06-11 22:06:51 +02:00
raucao 433ac4dc8e Use new strfry Docker image 2024-06-11 22:06:12 +02:00
raucao 62fe0d8fac Add nostrKey to default org service ACI 2024-06-11 22:05:07 +02:00
raucao 2a675fd135 Hand LDAP config to policy from main policy file
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Deployments will differ in production. The policy itself just needs the
configs, but should not care where credentials are fetched from.
2024-06-09 23:15:56 +02:00
raucao c2c3ebc2e1 Add strfry policies and members-only LDAP policy
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
This will look up nostr pubkeys in the LDAP directory to allow or deny
publishing notes to the relay.
2024-06-09 22:49:44 +02:00
raucao 5a5c316c14 Fix time format in migration
continuous-integration/drone/push Build is passing
2024-06-09 13:29:39 +02:00
raucao f0d5457ec1 Merge pull request 'Zap model improvements' (#195) from chore/zap_model_improvements into master
continuous-integration/drone/push Build is passing
Reviewed-on: #195
Reviewed-by: bumi <bumi@noreply.kosmos.org>
2024-06-09 11:16:34 +00:00
raucao 5588e3b3e8 Add settled_at to zaps, scope by settlement status
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 4s
2024-06-07 15:11:06 +02:00
raucao 8949d76d26 Fix zap receipt not being stored correctly
continuous-integration/drone/push Build is passing
fixes #194
2024-06-07 13:40:49 +02:00
raucao 8bc9bbdc33 Merge pull request 'Add new Lightning notification settings' (#193) from feature/ln_notification_settings into master
continuous-integration/drone/push Build is passing
Reviewed-on: #193
Reviewed-by: bumi <bumi@noreply.kosmos.org>
2024-06-04 10:39:07 +00:00
raucao d6d09b57b8 Merge pull request 'Add support for Lightning Zaps' (#190) from feature/170-nostr_zaps into master
continuous-integration/drone/push Build is passing
Reviewed-on: #190
2024-06-03 16:44:48 +00:00
raucao 1685d6ecf8 Respect new Lightning notification settings
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 4s
2024-06-01 17:51:20 +02:00
raucao 5348a229a6 WIP Add new lightning notification settings 2024-05-29 15:12:07 +01:00
raucao bad3b7a2be Use dynamic list for allowed user preference params 2024-05-23 00:23:42 +02:00
raucao b541e95bb7 Change default for lightning notifications 2024-05-23 00:22:38 +02:00
raucao 3f43fe8101 Fix missing description for FieldsetToggleComponent 2024-05-23 00:01:25 +02:00
raucao 231dfc8404 Log correct publish status
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 4s
2024-05-21 18:28:46 +02:00
raucao eeb9b0a331 Improve NostrManager::PublishEvent
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
* Use URI hostname as relay name
* Log relay name/URL for every websocket event
* Fix variable assignment for nostr event
* Fix Sidekiq job finishing too early, by creating a new thread waiting
  for it to be closed from a callback
2024-05-21 18:08:14 +02:00
raucao 08e783d185 Remove default nil values
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-05-19 17:07:27 +02:00
raucao fa5dc8ca46 Fix argument name
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-05-19 16:54:51 +02:00
raucao bc34e9c5e0 Allow CORS requests for lnurlp invoice
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-05-19 16:48:09 +02:00
raucao f388bd0237 Merge branch 'master' into feature/170-nostr_zaps
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-05-10 12:01:27 +00:00
raucao 48041630ca Limit number of relays to publish zap receipts to
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-05-10 13:57:25 +02:00
raucao 2d1ff29eca Improve nostr settings, fix allowsNostr property name
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-05-10 13:19:09 +02:00
raucao 46fa42e387 Merge pull request 'Refactor Nostr auth, add login via Nostr (web extension)' (#188) from feature/nostr_login into master
continuous-integration/drone/push Build is passing
Reviewed-on: #188
Reviewed-by: bumi <bumi@noreply.kosmos.org>
2024-05-10 11:01:00 +00:00
raucao c6c5d80fb4 WIP Persist zaps, create and send zap receipts
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-05-09 14:31:37 +02:00
raucao c0f4e7925e Use zap comment for description/memo
continuous-integration/drone/push Build is failing
But use the hashed zap request event for the description hash.
2024-05-04 17:07:23 +02:00
raucao 49d24990b4 Add zap model, user relation 2024-05-04 17:05:34 +02:00
raucao 619bd954b7 WIP 2024-04-21 10:51:41 +02:00
raucao e27c64b5f1 WIP Check for zaps, send zap receipt on incoming zap tx 2024-04-21 10:35:30 +02:00
raucao b36baf26eb Refactor WebhooksController 2024-04-21 10:02:17 +02:00
raucao adedaa5f7b Add task for easily creating test invoices 2024-04-21 10:01:54 +02:00
raucao 596ed7fccc Use lndhub.go v2 endpoint for invoice creation 2024-04-21 10:01:18 +02:00
raucao 5685e1b7bc Move lndhub invoice creation to service 2024-04-16 20:19:15 +02:00
raucao c3b82fc2a9 WIP Verify and respond to zap requests
continuous-integration/drone/push Build is passing
2024-04-16 19:13:10 +02:00
raucao 77e2fe5792 Add helper method for parsing nostr event tags 2024-04-16 19:10:48 +02:00
raucao bc43082839 Add admin settings for nostr keys 2024-04-16 19:07:52 +02:00
raucao b09225543b Add Nostr relay service to Docker Compose config 2024-04-15 14:03:37 +02:00
raucao f2507409a3 Announce nostr pubkey on lnurlp endpoint 2024-04-15 14:03:37 +02:00
raucao 46b4723999 Add global settings for account service's Nostr keys 2024-04-15 14:03:37 +02:00
raucao 3f90a011c4 Document URLs 2024-04-15 14:03:37 +02:00
raucao 3ba333e802 Indentation 2024-04-15 14:03:37 +02:00
raucao d9dff3e872 Merge branch 'master' into feature/nostr_login
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 4s
2024-04-15 12:03:12 +00:00
raucao 6ddeacb779 Merge pull request 'Add Mastodon aliases and links to Webfinger when enabled' (#189) from feature/mastodon_webfinger into master
continuous-integration/drone/push Build is passing
Reviewed-on: #189
Reviewed-by: galfert <garret.alfert@gmail.com>
2024-04-14 10:18:15 +00:00
raucao 78aff3d796 Fix spec
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
The test env has Mastodon enabled now
2024-04-04 17:22:57 +03:00
raucao 8f600f44bd Add Mastodon aliases and links to Webfinger when enabled
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
Also requires "remotestorage" service to be enabled via attribute
2024-04-04 17:17:57 +03:00
raucao 819ecf6ad8 Add #service_enabled? method to user model 2024-04-04 13:28:09 +03:00
raucao 945eaba5e1 Add login via nostr (web extension)
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-04-01 19:04:48 +03:00
raucao 22d362e1a0 Refactor Nostr settings/connect
* Use NIP-42 auth event instead of short text note
* Verify event ID and signature using the nostr gem instead of custom code
2024-04-01 18:27:08 +03:00
raucao d4e67a830c Update nostr gem 2024-04-01 18:27:08 +03:00
raucao 670b2da1ef Ad-hoc content update
continuous-integration/drone/push Build is passing
Before #186 is implemented
2024-03-29 10:33:28 +04:00
raucao ed5c5b3081 Add remotestorage queue to Sidekiq config
continuous-integration/drone/push Build is passing
2024-03-29 09:47:30 +04:00
raucao 4ee6bfddfa Merge pull request 'Improvements/adjustments for Mastodon integration' (#185) from chore/mastodon into master
continuous-integration/drone/push Build is passing
Reviewed-on: #185
2024-03-29 05:24:10 +00:00
raucao 8b60890061 Add Phanpy to recommended Mastodon apps
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 4s
It's too good not to.
2024-03-29 09:21:17 +04:00
raucao 0367450c4b Replace hyphen with underscore in Mastodon address
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Unfortunately, Mastodon only allows underscores for usernames, and
reversely, akkounts only allows hyphens and no underscores.
2024-03-29 09:08:15 +04:00
raucao e6f5623c7f Enable Mastodon service by default (for now) 2024-03-29 09:06:41 +04:00
raucao 367f566ccb Merge pull request 'Add global setting for default services, enable for preconfirmed accounts' (#184) from feature/preconfirmed_accounts into master
continuous-integration/drone/push Build is passing
Reviewed-on: #184
2024-03-28 13:23:22 +00:00
raucao 80e69df75c Add global setting for default services, enable for preconfirmed accounts
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 4s
Co-authored-by: Greg Karékinian <greg@karekinian.com>
2024-03-28 17:21:20 +04:00
raucao 02af69b055 Add missing env var to example config
continuous-integration/drone/push Build is passing
2024-03-28 10:56:42 +04:00
raucao 5d459e7e7d Fix LDAP attribute name
continuous-integration/drone/push Build is passing
2024-03-19 18:18:06 +01:00
raucao 51a3cb60ec Merge pull request 'Add custom LDAP attributes to schema' (#181) from feature/custom_ldap_attributes into master
continuous-integration/drone/push Build is passing
Reviewed-on: #181
Reviewed-by: greg <greg@noreply.kosmos.org>
2024-03-19 14:46:44 +00:00
raucao 43c57c128f Merge pull request 'Move nostr pubkeys to LDAP attribute' (#183) from feature/173-nostr_ldap into feature/custom_ldap_attributes
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
Reviewed-on: #183
Reviewed-by: greg <greg@noreply.kosmos.org>
2024-03-19 14:43:02 +00:00
raucao 5a3adba603 Move nostr pubkeys to LDAP attribute
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 4s
closes #173
2024-03-17 11:04:11 +01:00
raucao 3715cb518b User Settings: Rename Experiments to Nostr
continuous-integration/drone/push Build is passing
And use a nostr icon
2024-03-16 16:03:15 +01:00
raucao 2c9ecc1fef Add nostr icons 2024-03-16 16:03:00 +01:00
raucao 095747e89b Fix broken admin links
continuous-integration/drone/push Build is passing
2024-03-13 18:19:25 +01:00
raucao 2130369604 Update db schema
continuous-integration/drone/push Build is passing
2024-03-13 18:15:42 +01:00
raucao c996351930 Fix PostgreSQL query issue 2024-03-13 18:13:17 +01:00
raucao 8b897168cc Merge pull request 'Let users donate sats via BTCPay Server' (#176) from feature/donations_btcpay into master
continuous-integration/drone/push Build is passing
Reviewed-on: #176
Reviewed-by: galfert <garret.alfert@gmail.com>
2024-03-13 16:31:54 +00:00
raucao 4217ba52e0 Switch service LDAP attribute to serviceEnabled
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Improve internal naming on the way
2024-03-13 16:41:49 +01:00
raucao de20931d30 Add tasks for modifying schema, first custom attributes
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
refs #172, #173
2024-03-13 14:30:03 +01:00
raucao 8de0a2e26e Improve seed output 2024-03-13 14:28:31 +01:00
raucao 06521d1c34 LDAP: add delete_all_users method, use in seeds 2024-03-13 14:27:39 +01:00
raucao 38b3d68fd5 LDAP: Rename client method, add modify method 2024-03-13 14:26:44 +01:00
raucao eac8fa6edb 0.9.0
continuous-integration/drone/push Build is passing
2024-03-07 14:48:27 +01:00
raucao 43f918a074 Update liquor-cabinet image, fix LC/redis networking issue on Linux
continuous-integration/drone/push Build is passing
2024-03-06 22:07:35 +01:00
raucao e322867d79 Merge pull request 'Fix login redirect for existing RS auth' (#180) from bugfix/178-rs_login_redirect into master
continuous-integration/drone/push Build is passing
Reviewed-on: #180
2024-03-06 21:06:27 +00:00
raucao 4d6fa318b7 Fix login redirect for existing RS auth
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
raucao 7f2df3b025 Fix donation record for amounts given in sats
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 4s
2024-03-06 11:22:53 +01:00
raucao da22a9d448 Add spec for reported regression
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2024-03-06 11:20:43 +01:00
raucao e3b96d5cff Merge branch 'master' into feature/donations_btcpay
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-03-03 12:50:16 +01:00
raucao 4e8878a4b5 Merge pull request 'Allow running specs in Docker container, update README' (#177) from dev/docker_rspec into master
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
raucao e65b890880 Update db schema
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
raucao f57edd4d3b Update README to account for Docker Compose everywhere
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-03-02 16:57:07 +01:00
raucao 1afd56fb80 Allow running specs in Docker (Web) container 2024-03-02 16:56:07 +01:00
raucao 71669a4b96 Merge pull request 'Refactor admin settings routes' (#156) from feature/content_settings into master
continuous-integration/drone/push Build is passing
Reviewed-on: #156
2024-03-02 14:30:21 +00:00
raucao c312e30c17 Fix link in admin settings/services sidenav
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
raucao 51f4556ede Refactor admin settings routes
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
raucao c36cf5eee6 Merge branch 'master' into feature/donations_btcpay
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-03-02 15:07:40 +01:00
raucao 54220019bb Send email confirmation when BTC payment is confirmed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2024-03-02 14:31:48 +01:00
raucao 079ee8833c Implement bitcoin donations via BTCPay 2024-03-02 14:31:48 +01:00
raucao 26d613bdca Allow other controllers to access lndhub user balance 2024-03-02 14:31:48 +01:00
raucao 69b3afb8f7 DRY up btcpay and lndhub services
Removing initialize methods from the main/manager class also allows for
different iniitalizers in specific task services
2024-03-02 14:31:48 +01:00
raucao fee951c05c Move past donations to partial 2024-03-02 14:31:45 +01:00
raucao 4fa4ae6b54 Merge pull request 'Comment out settings in .env.example' (#175) from task/env-example into master
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
galfert 869ff4691b Comment out settings in .env.example
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
raucao 822a2dc018 Fix specs
continuous-integration/drone/push Build is passing
2024-03-01 17:15:02 +01:00
raucao 5b7fc3707b Hide avatar settings behind feature flag
continuous-integration/drone/push Build is failing
In favor of #157
2024-03-01 11:13:49 +01:00
raucao 0e2dc54dc6 Merge pull request 'Upgrade Rails to 7.1, update dependencies, require Ruby 3.x' (#160) from chore/update_dependencies into master
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
greg 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
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
raucao b33b8104a8 Fix typo
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
raucao 4a4a222973 Merge branch 'chore/update_dependencies' into bugfix/local_web_app_icons
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-02-23 18:25:23 +00:00
raucao 8c524abcf5 Merge pull request 'Fix Docker volume permissions on some host platforms' (#171) from bugfix/macos_docker_volumes into chore/update_dependencies
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
raucao a852ab75ae Fix Docker volume permissions on some host platforms
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
raucao de1f234c15 Merge branch 'chore/update_dependencies' into bugfix/local_web_app_icons
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-02-22 15:13:18 +01:00
raucao 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
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
raucao 56d91083e5 Fix seeds for new keyword argument
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
raucao ba7c3795f8 Add pkg-config
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-02-22 11:29:56 +01:00
raucao bbf3fb91a0 Fix Ruby in Docker container on Apple silicon
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-02-22 10:47:21 +01:00
raucao 1754df73cb Merge pull request 'Allow admins to add and remove invitations per account' (#167) from feature/164-invites into chore/update_dependencies
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
raucao 9a1f9abf84 Formatting
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
raucao 2753388e1e Add specs for admin user management 2024-02-10 12:53:11 +01:00
raucao f3159d30f1 Allow admins to add and remove invitations per account
continuous-integration/drone/push Build is passing
2024-02-10 11:21:45 +01:00
raucao ca238be6f4 Add option for hiding close button in modal windows 2024-02-10 10:24:09 +01:00
raucao 8747ce4eb0 Remove multi-domain support on admin user pages
continuous-integration/drone/push Build is passing
refs #166
2024-02-10 08:55:15 +01:00
raucao fcda3b9c8c WIP Make dropdowns more configurable, add invitations menu to admin page 2024-02-09 18:57:07 +01:00
raucao 67689dcce3 Add service for creating invites
continuous-integration/drone/push Build is passing
2024-02-09 17:59:07 +01:00
raucao 22ffcd54db Patch away a deprecation warning caused by Devise
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-02-09 17:58:28 +01:00
raucao bd1b177993 Rescue all icon download/upload errors, send to Sentry
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-02-08 13:36:17 +01:00
raucao 3f110995a4 Add timestamp to icon filenames
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
raucao a7410058fa Save WebApp before fetching icons 2024-02-08 13:02:08 +01:00
raucao 411587456b Destroy dependent RS auths when destroying a WebApp 2024-02-08 13:01:19 +01:00
raucao 84e915ece9 Allow custom path for ActiveStorage local/disk backend 2024-02-08 13:01:07 +01:00
raucao 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
raucao 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
raucao c9052b35f6 Database update for Flipper
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-02-08 12:29:11 +01:00
raucao 3b96130491 Upgrade web-console, fix it for Docker
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
raucao 176b1a10c6 Remove obsolete closing tag 2024-02-08 12:10:14 +01:00
raucao 1c54e4c0b5 New CI image Dockerfile
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-02-03 11:36:06 +02:00
raucao 7796a22491 Switch to newly published manifique gem
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-02-02 17:55:20 +02:00
raucao 7e6e917ae1 Use new CI image with Ruby 3.3.0
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-02-02 17:28:33 +02:00
raucao 28cfe4b1e7 Fix deprecation warning 2024-02-02 16:58:04 +02:00
raucao 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
raucao 420442c1c0 Update Ruby for Dockerfile/Compose 2024-02-02 14:34:09 +02:00
raucao 68c5758ecc Update dependencies, upgrade to Rails 7.1, require Ruby 3.x 2024-02-02 14:25:47 +02:00
raucao c5dd3c30a6 Use full URL for S3 alias host
continuous-integration/drone/push Build is passing
2024-02-02 14:01:47 +02:00
raucao 422d5c7cd2 Fix address missing in lightning address receive notifications
continuous-integration/drone/push Build is passing
2024-02-01 16:22:20 +02:00
raucao 5a23d523a8 Add fallback icons for apps on RS app dashboard
continuous-integration/drone/push Build is passing
2024-01-29 18:33:06 +02:00
raucao f8da034e66 Fail gracefully when remote icon is 404
continuous-integration/drone/push Build is passing
2024-01-29 14:54:18 +02:00
raucao b0b56fcf92 Fix lnurlp route
continuous-integration/drone/push Build is passing
2024-01-29 11:18:51 +02:00
raucao 0cf000c1b8 Merge pull request 'Only support primary domain for Lightning Address' (#158) from chore/well-known_routes into master
continuous-integration/drone/push Build is passing
Reviewed-on: #158
2024-01-29 09:03:37 +00:00
raucao fa9a924b0a Merge pull request 'Fix RS auth array usage in production' (#159) from bugfix/postgresql_arrays into master
continuous-integration/drone/push Build is passing
Reviewed-on: #159
2024-01-29 08:58:02 +00:00
raucao 50f91cc7d7 Fix RS auth array usage in production
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
raucao a628a03f84 Only support primary domain for Lightning Address
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
raucao eaf41e0835 Adjust spec for c32fc51
continuous-integration/drone/push Build is passing
2024-01-26 16:02:47 +02:00
raucao 243cf9c08d Don't add CORS headers for Webfinger in production
continuous-integration/drone/push Build is failing
The reverse proxy should handle it.
2024-01-26 11:01:45 +03:00
raucao c32fc51aab Do not enable email service by default
continuous-integration/drone/push Build is failing
2024-01-26 09:38:38 +03:00
raucao aa9178d569 Sort service ENV vars alphabetically, add missing lndhub var
continuous-integration/drone/push Build is passing
2024-01-26 08:36:58 +03:00
raucao 281938dd64 Only set API CORS headers in development
continuous-integration/drone/push Build is passing
In production, this is the reverse proxy's responsibility
2024-01-22 15:35:13 +03:00
raucao fafc5d8f6f Improve copy
continuous-integration/drone/push Build is passing
2024-01-22 12:10:17 +03:00
raucao 1238359b5f Remove superfluous header text
continuous-integration/drone/push Build is passing
2024-01-22 12:04:55 +03:00
raucao 84220beb1c Merge pull request 'Add email service and settings' (#154) from feature/email_service into master
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
raucao 1e9ec9bb76 Fix wrong prefix for email QR code
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
raucao 21e51a7c40 Merge pull request 'Update nostr gem, switch to Ruby for bech32 encoding' (#155) from chore/bech32_handling into master
continuous-integration/drone/push Build is passing
Reviewed-on: #155
2024-01-21 09:31:51 +00:00
raucao e3c30f7b16 Remove obsolete function
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
raucao b4f0c60ea0 Update nostr gem, switch to Ruby for bech32 encoding
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-01-15 12:54:58 +03:00
raucao 1a5a2177b4 Update spec
continuous-integration/drone/push Build is failing
2024-01-15 12:38:27 +03:00
raucao 7e8443c598 Change Lightning balance property
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
raucao 7b71f2cf76 Revert "Fix fixture file"
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
raucao c7b137e5eb Fix fixture file
continuous-integration/drone Build is failing
2024-01-10 18:30:19 +03:00
raucao 958d18d61a Add email service and settings 2024-01-10 18:30:05 +03:00
raucao 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
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 2a70bf2fb9 Small refactoring
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-11-20 13:40:56 +01:00
Râu Cao 9a9947f9ad Respect "start_url" from manifest when launching web apps
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-11-20 13:32:40 +01:00
Râu Cao bdf5a18ad4 Re-add more specs
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-11-20 12:21:57 +01:00
Râu Cao aa399b862a Allow to launch RS apps from dashboard
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-11-19 19:10:13 +01:00
Râu Cao 713e91a720 Implement RS auth revocation
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-11-19 18:49:17 +01:00
Râu Cao 8ec2a6d7e4 Remove obsolete spec file 2023-11-19 18:49:06 +01:00
Râu Cao 4ecf2c4246 Improve app list 2023-11-19 18:48:44 +01:00
Râu Cao 4fdf8accd6 Add note
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-11-18 17:36:18 +01:00
Râu Cao f451adcb53 Try smaller icons if 256px not available 2023-11-18 17:35:57 +01:00
Râu Cao 721dccb499 Add dropdown components, menus for RS auth items 2023-11-18 17:13:55 +01:00
Râu Cao 27bb7d1bfe Finish working liquor-cabinet setup for Docker Compose
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-11-16 12:46:19 +01:00
Râu Cao 1d44181fb5 Wording 2023-11-16 12:46:05 +01:00
Râu Cao de67f59d5c Fail gracefully and log error when token missing in Redis 2023-11-16 12:45:26 +01:00
Râu Cao 1995e6dda2 Fix RS OAuth URL in Webfinger record
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-11-16 12:44:59 +01:00
Râu Cao 600cfe0f78 Update lockfile 2023-11-16 12:42:39 +01:00
Râu Cao e301ac8e2e Fix title
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
continuous-integration/drone/push Build is running
2023-11-01 22:26:53 +01:00
Râu Cao 00049f3743 Add info for running Minio/RS to README
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-11-01 22:01:14 +01:00
Râu Cao 60c0a43f33 Add minio to Docker Compose setup, configure Liquor Cabinet 2023-11-01 21:51:29 +01:00
Râu Cao 0c1b1b4afe Adjust specs for web app metadata fetching 2023-11-01 21:49:08 +01:00
Râu Cao 92310d434a Remove rs namespace from Redis keys
Superfluous, since the whole db should be RS only
2023-11-01 21:48:16 +01:00
Râu Cao 56c127ca0c Only allow primary domain for RS
Replace user addresses with usernames in the respective URLs
2023-11-01 21:46:38 +01:00
Râu Cao 5075fef616 Only show avatar when available on admin user page
continuous-integration/drone/push Build is failing
2023-10-25 22:16:16 +02:00
Râu Cao 8e090daa9c Fetch web app metadata when creating RS auth 2023-10-25 22:16:16 +02:00
Râu Cao def87a1621 Remove variants from attachment 2023-10-25 22:16:16 +02:00
Râu Cao 00ec7fa21c WIP Add RS auths/apps to Storage dashboard 2023-10-25 22:16:13 +02:00
Râu Cao 2b8bfaaca8 Add admin page for web apps
continuous-integration/drone/push Build is passing
2023-10-24 22:42:16 +02:00
Râu Cao 3e9a08a266 Remove (long) obsolete edge case 2023-10-24 17:29:24 +02:00
Râu Cao fcea11f0e5 Associate RS authorizations with web apps 2023-10-24 17:29:24 +02:00
Râu Cao 261a782963 Only complete icon URLs when given relative or absolute paths 2023-10-24 17:29:24 +02:00
Râu Cao e964e7e52c Save web app metadata explicitly 2023-10-24 17:29:24 +02:00
Râu Cao e508407df4 Remove debug statement 2023-10-24 17:29:23 +02:00
Râu Cao bec827acb1 Store web app icons with proper folder paths 2023-10-24 17:29:23 +02:00
Râu Cao 0a69603643 Update web app metadata when first creating a record 2023-10-24 17:29:23 +02:00
Râu Cao d4f71e98ed Download and attach icons for web apps 2023-10-24 17:29:23 +02:00
Râu Cao e56c9bd0d5 Add web app model, service to fetch metadata 2023-10-24 17:29:23 +02:00
Râu Cao e1b7e1b2ef Update dependencies, add manifique 2023-10-24 17:29:23 +02:00
Râu Cao 1056ffd08e Add optional S3 config/backend for ActiveStorage 2023-10-24 17:29:23 +02:00
raucao be5fe00f20 Merge pull request 'Fix XMPP from-address config not being used' (#150) from bugfix/xmpp_from_address into master
continuous-integration/drone/push Build is passing
Reviewed-on: #150
2023-10-19 10:47:45 +00:00
Râu Cao e9c4929726 Fix XMPP from-address config not being used
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-10-17 15:21:57 +02:00
raucao 14ff0c0e16 Merge pull request 'BTCPay settings, admin page, and new Lightning balance API' (#147) from feature/btcpay_configs into master
continuous-integration/drone/push Build is passing
Reviewed-on: #147
Reviewed-by: galfert <garret.alfert@gmail.com>
2023-09-26 10:13:09 +00:00
Râu Cao d939f5d649 Merge branch 'master' into feature/btcpay_configs
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 4s
2023-09-20 19:12:24 +02:00
Râu Cao 69fffb29d8 Make publishing of BTCPay wallet balances optional
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2023-09-20 18:36:53 +02:00
Râu Cao 91d3b977e9 Fix spec 2023-09-20 18:26:50 +02:00
raucao 7a5fd46835 Merge pull request 'Add user avatars to LDAP, upload on profile settings page' (#148) from feature/123-user_avatars into master
continuous-integration/drone/push Build is passing
Reviewed-on: #148
Reviewed-by: galfert <garret.alfert@gmail.com>
2023-09-13 13:01:25 +00:00
Râu Cao 9c4c5c2553 Use correct content type for image
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-09-13 14:49:16 +02:00
Râu Cao 8f819d12c0 Remove debug output 2023-09-13 14:48:51 +02:00
Râu Cao b810e27480 Use custom docker image with libvips installed in CI
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-09-07 19:40:43 +02:00
Râu Cao 1949f1876f Use attr_reader instead of shared instance variables
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-09-07 19:22:15 +02:00
Râu Cao 2ba0116ca6 Fix wrong inheritance
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-09-07 19:17:46 +02:00
Râu Cao 2c2ddabdff Fix code being silly
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-09-07 19:15:14 +02:00
Râu Cao dfcdbec0dd Add specs for avatar upload
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2023-09-07 11:42:42 +02:00
Râu Cao 3b67a8791c Add libvips package to Docker container 2023-09-07 11:42:24 +02:00
Râu Cao d5ab532947 Store and retrieve avatars in/from LDAP exclusively
continuous-integration/drone/push Build is failing
No need to keep them in two places at the same time. We can fetch them
from LDAP whenever we want to do something with them.
2023-09-06 20:42:26 +02:00
Râu Cao 50c63d5c38 Update user avatar in LDAP 2023-09-06 19:02:07 +02:00
Râu Cao 64d09cfb7f Use variant declarations instead of custom methods 2023-09-06 12:38:47 +02:00
Râu Cao def44618ef Comments
continuous-integration/drone/push Build is passing
2023-09-06 12:16:00 +02:00
Râu Cao 9e5aeaf572 Add user avatars 2023-09-06 12:15:53 +02:00
Râu Cao 86f85a90f4 Add/configure ActiveStorage 2023-09-06 12:14:28 +02:00
raucao d8a35ac3fd Merge pull request 'Fix wrong redirect after sign-in for RS OAuth' (#146) from bugfix/rs_oauth_login into master
continuous-integration/drone/push Build is passing
Reviewed-on: #146
Reviewed-by: galfert <garret.alfert@gmail.com>
2023-09-05 11:03:02 +00:00
Râu Cao 5a5f62e98a Refactor BTCPay service and API, add lightning balance
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-09-04 16:02:54 +02:00
Râu Cao 074f9afcbb Fix descriptions not being shown for resettable form fields 2023-09-04 15:37:02 +02:00
Râu Cao 725fd2e5ea Move lndhub admin token to env var/setting 2023-09-04 15:36:22 +02:00
Râu Cao 8349ca5e12 Add admin settings page for BTCPay 2023-09-04 15:25:20 +02:00
Râu Cao 46d59e3371 Improve icons in admin service settings sidenav 2023-09-04 15:24:35 +02:00
Râu Cao e8e6ee0bc4 Add configurable settings for BTCPay 2023-09-04 15:23:27 +02:00
Râu Cao a91ee2bd0a Fix generated usernames in seeds potentially being too short
continuous-integration/drone/push Build is passing
2023-09-04 11:35:51 +02:00
Râu Cao fcb6923c92 Fix wrong redirect after sign-in for RS OAuth
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
We use a custom auth method to pre-fill the username when reaching the
RS OAuth while signed out. However, it needs to redirect back to the RS
OAuth page after sign-in, and not to the root path.
2023-09-04 11:33:16 +02:00
Râu Cao 0f3b9f176e 0.8.1
continuous-integration/drone/push Build is passing
2023-09-03 15:35:46 +02:00
raucao 822ae2f945 Merge pull request 'Fix migration failing with PostgreSQL' (#145) from bugfix/144-postgres_migration into master
continuous-integration/drone/push Build is passing
Reviewed-on: #145
2023-09-03 13:32:36 +00:00
Râu Cao 96c669ab4e Update database schema, fix spec
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
2023-09-03 15:29:09 +02:00
Râu Cao 558100c35e Fix migration failing with PostgreSQL 2023-09-03 15:28:32 +02:00
Râu Cao 6739b38f4c 0.8.0
continuous-integration/drone/push Build is passing
2023-09-01 12:18:26 +02:00
raucao 7e1272c936 Merge pull request 'Service pages for Chat and Social' (#143) from feature/service_pages into master
continuous-integration/drone/push Build is passing
Reviewed-on: #143
Reviewed-by: galfert <garret.alfert@gmail.com>
2023-09-01 08:36:09 +00:00
Râu Cao ecdeb4c122 Fix copypasta
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-09-01 10:32:11 +02:00
Râu Cao 8614e2f12b Use service configs on dashboard
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Only show enabled services, and use the URLs from the various configs.
2023-08-13 17:24:10 +02:00
Râu Cao a038a857d9 Make Drone CI configurable 2023-08-13 17:23:57 +02:00
Râu Cao eee81d0cf1 Small link improvement
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-08-13 15:41:57 +02:00
Râu Cao b7fa4b012a Allow Mastodon address domain to be different from primary domain
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-08-13 12:27:05 +02:00
Râu Cao 10bcd5c32b Ignore .env.development 2023-08-13 12:26:56 +02:00
Râu Cao f79d5d4724 Use select element instead of tabs on mobile
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-08-11 14:17:12 +02:00
Râu Cao 866ffbe615 Upgrade tailwindcss-stimulus-components to latest version
continuous-integration/drone/push Build is passing
The latest one offers more tabs features. Required some changes to the
modals and tabs code.
2023-08-11 13:58:57 +02:00
Râu Cao 3c1fe3396d Add Mastodon service page 2023-08-11 13:58:53 +02:00
Râu Cao e4242333d9 Add recommended apps for Chat/XMPP
continuous-integration/drone/push Build is passing
2023-08-08 19:59:29 +02:00
Râu Cao 138f13c1a0 Add note
continuous-integration/drone/push Build is passing
2023-08-07 18:16:40 +02:00
Râu Cao ad5e515200 Update README 2023-08-07 18:16:34 +02:00
Râu Cao 1ea8b22a59 WIP Add service page for Chat
continuous-integration/drone/push Build is running
2023-08-07 18:16:14 +02:00
Râu Cao f49aff262c Add base controller for service controllers
continuous-integration/drone/push Build is running
2023-08-07 18:15:17 +02:00
raucao 852e2fea1e Merge pull request 'remoteStorage OAuth' (#109) from feature/rs-oauth into master
continuous-integration/drone/push Build is passing
Reviewed-on: #109
2023-08-04 08:55:28 +00:00
Râu Cao 353b55fe1a Add RS OAuth controller specs
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-08-01 14:29:24 +02:00
Râu Cao ba0cbba96b Add feature spec for RS OAuth dialog
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-08-01 13:01:41 +02:00
Râu Cao 5f921f1b53 RS OAuth pre-fills username for login 2023-08-01 13:01:03 +02:00
Râu Cao a2d27bf575 Support pre-filling of username in login form 2023-08-01 13:00:22 +02:00
Râu Cao fcf9a065e1 Fix specs
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-07-14 15:56:28 +02:00
Râu Cao ec9bcacd46 Add specs for RemoteStorageAuthorization model
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2023-07-14 15:31:20 +02:00
Râu Cao 645abac810 Rename RS token expiry job 2023-07-14 15:29:29 +02:00
Râu Cao e11be727a1 Indentation 2023-07-14 15:29:04 +02:00
Râu Cao 12b24337e7 Fix typo 2023-07-14 15:28:45 +02:00
Râu Cao b0bfc290c4 Refactor code for newer Redis 2023-07-14 15:28:09 +02:00
Râu Cao 4c6c81171b Fix typo 2023-07-14 15:27:57 +02:00
Râu Cao 4d88a40109 Add separate config for RS Redis 2023-07-14 15:27:30 +02:00
Râu Cao d9b39b36fb Merge branch 'master' into feature/rs-oauth
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-07-13 15:31:28 +02:00
Râu Cao 06aed8c33d Count up minor version on feature merge
continuous-integration/drone/push Build is passing
2023-07-13 15:26:35 +02:00
raucao 0a778e92d8 Merge pull request 'Add modal component, QR codes for invite links' (#140) from feature/139-qr_codes into master
continuous-integration/drone/push Build is passing
Reviewed-on: #140
2023-07-13 13:24:12 +00:00
Râu Cao e5a5633e44 Add Redis config for dev with Redis on localhost
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-07-04 17:00:04 +02:00
Râu Cao a68825493f Add Redis config in CI
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-07-04 16:44:11 +02:00
Râu Cao e1e83386a8 Merge branch 'master' into feature/rs-oauth 2023-07-04 16:43:32 +02:00
Râu Cao 3adc1917f6 Improve outline button style, use everywhere
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 4s
2023-06-27 19:23:20 +02:00
Râu Cao 8a570ce724 Use modal component for LndHub setup code 2023-06-27 19:23:20 +02:00
Râu Cao c78df9e5f1 Add QR code icon, button, modal for invites
Using https://excid3.github.io/tailwindcss-stimulus-components/
2023-06-27 19:23:20 +02:00
galfert 5c2df3df07 Add Redis service to Drone config
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2023-06-27 15:07:28 +02:00
greg 83e3e2ecd8 Merge pull request 'Allow editing and resetting of all admin setting strings' (#137) from feature/admin_settings into master
continuous-integration/drone/push Build is passing
Reviewed-on: #137
Reviewed-by: greg <greg@noreply.kosmos.org>
2023-06-24 14:57:08 +00:00
raucao b32e2fcb7b Merge pull request 'Fix docker volume mappings for node_modules, improve docker-compose usage' (#138) from bugfix/fix-docker-mapping into master
continuous-integration/drone/push Build is passing
Reviewed-on: #138
Reviewed-by: raucao <raucao@noreply.kosmos.org>
2023-06-23 15:38:30 +00:00
slvrbckt 96a4db5bae improve sentence
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 4s
2023-06-23 17:32:39 +02:00
slvrbckt c7925f132e formatting
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-06-23 17:30:00 +02:00
slvrbckt e4406bf6ff use PRIMARY_DOMAIN for both web and sidekiq directives 2023-06-23 17:29:42 +02:00
slvrbckt ee7769c8c7 Update readme with simplified usage
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-06-23 17:21:52 +02:00
slvrbckt fdf3218f88 leave services uncommented, add /akkounts/node_modules to volume mapping as a directory to explicitly exclude 2023-06-23 17:21:43 +02:00
slvrbckt 652ed5f7e3 copy files as list 2023-06-23 17:21:17 +02:00
Râu Cao e4ed797920 Adjust specs
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-06-22 13:57:55 +02:00
Râu Cao 93740f17ef Allow editing and resetting of all admin setting strings
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2023-06-22 13:48:29 +02:00
Râu Cao affb058671 Add config for XMPP notifications from-address 2023-06-21 16:44:06 +02:00
galfert 716d4b944a Merge branch 'master' into feature/rs-oauth
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
# Conflicts:
#	app/models/user.rb
#	config/routes.rb
#	db/schema.rb
2023-06-20 14:07:46 +02:00
galfert 42af148168 Persist RS auth tokens in Redis 2023-06-20 14:02:48 +02:00
Râu Cao dabd892a25 Improve RS OAuth UI
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-04-13 16:21:48 +02:00
Râu Cao eeabbdb7df Merge branch 'master' into feature/rs-oauth
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-04-13 15:02:51 +02:00
galfert ee42d68471 Add RemoteStorageAuthorization model
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-03-28 01:21:28 +02:00
galfert 7acc3b2106 RemoteStorage OAuth dialog 2023-03-28 01:21:28 +02:00
galfert 20c014607c Basic RemoteStorage settings 2023-03-27 22:52:01 +02:00
414 changed files with 13307 additions and 2202 deletions
+9 -1
View File
@@ -17,15 +17,19 @@ steps:
branch: branch:
- master - master
- name: rspec - name: rspec
image: guildeducation/rails:2.7.2-14.20.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
RS_REDIS_URL: redis://redis:6379/1
commands: commands:
- bundle config unset deployment - bundle config unset deployment
- bundle config set cache_all 'true' - bundle config set cache_all 'true'
- 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
@@ -42,6 +46,10 @@ steps:
branch: branch:
- master - master
services:
- name: redis
image: redis
volumes: volumes:
- name: cache - name: cache
host: host:
+83 -34
View File
@@ -1,43 +1,92 @@
PRIMARY_DOMAIN=kosmos.org # PRIMARY_DOMAIN=kosmos.org
AKKOUNTS_DOMAIN=accounts.example.com # AKKOUNTS_DOMAIN=accounts.example.com
SMTP_SERVER=smtp.example.com # Generate this using `rails secret`
SMTP_PORT=587 # SECRET_KEY_BASE=
SMTP_LOGIN=accounts
SMTP_PASSWORD=123abc
SMTP_FROM_ADDRESS=accounts@example.com
SMTP_DOMAIN=example.com
SMTP_AUTH_METHOD=plain
SMTP_ENABLE_STARTTLS=auto
LDAP_HOST=localhost # Generate these using `rails db:encryption:init`
LDAP_PORT=389 # (Optional, needed for LndHub integration)
LDAP_ADMIN_PASSWORD=passthebutter # ENCRYPTION_PRIMARY_KEY=
LDAP_SUFFIX='dc=kosmos,dc=org' # ENCRYPTION_KEY_DERIVATION_SALT=
REDIS_URL='redis://localhost:6379/1' # The default backend is SQLite
# DB_ADAPTER=postgresql
# PG_HOST=localhost
# PG_PORT=5432
# PG_DATABASE=akkounts
# PG_DATABASE_QUEUE=akkounts_queue
# PG_USERNAME=akkounts
# PG_PASSWORD=
WEBHOOKS_ALLOWED_IPS='10.1.1.163' # SMTP_SERVER=smtp.example.com
# SMTP_PORT=587
# SMTP_LOGIN=accounts
# SMTP_PASSWORD=123abc
# SMTP_FROM_ADDRESS=accounts@example.com
# SMTP_DOMAIN=example.com
# SMTP_AUTH_METHOD=plain
# SMTP_ENABLE_STARTTLS=auto
DISCOURSE_PUBLIC_URL='https://community.kosmos.org' # S3_ENABLED=true
DISCOURSE_CONNECT_SECRET='discourse_connect_ftw' # S3_ENDPOINT=https://s3.kosmos.org
# S3_REGION=garage
# S3_BUCKET=akkounts-production
# S3_ALIAS_HOST=https://accounts.web.s3.kosmos.org
# S3_ACCESS_KEY=123456abcdefg
# S3_SECRET_KEY=123456789123456789123456789
GITEA_PUBLIC_URL='https://gitea.kosmos.org' # LDAP_HOST=localhost
MASTODON_PUBLIC_URL='https://kosmos.social' # LDAP_PORT=389
MEDIAWIKI_PUBLIC_URL='https://wiki.kosmos.org' # LDAP_USE_TLS=false
RS_STORAGE_URL='https://storage.kosmos.org' # LDAP_UID_ATTR=cn
# LDAP_BASE="ou=kosmos.org,cn=users,dc=kosmos,dc=org"
# LDAP_ADMIN_USER="cn=Directory Manager"
# LDAP_ADMIN_PASSWORD=passthebutter
# LDAP_SUFFIX="dc=kosmos,dc=org"
EJABBERD_ADMIN_URL='https://xmpp.kosmos.org/admin' # REDIS_URL='redis://localhost:6379/1'
EJABBERD_API_URL='https://xmpp.kosmos.org/api'
BTCPAY_API_URL='http://localhost:23001/api/v1' # WEBHOOKS_ALLOWED_IPS='10.1.1.163'
LNDHUB_API_URL='http://localhost:3023' #
LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org' # Service Integrations
LNDHUB_PUBLIC_KEY='0123d3be18617f39cf645851e3ba63f51fc13f0bb09e3bb25e6fd4de556486d946' # (sorted alphabetically by service name)
LNDHUB_ADMIN_UI=true #
LNDHUB_PG_HOST=localhost
LNDHUB_PG_PORT=5432 # BTCPAY_PUBLIC_URL='https://btcpay.example.com'
LNDHUB_PG_DATABASE=lndhub # BTCPAY_API_URL='http://localhost:23001/api/v1'
LNDHUB_PG_USERNAME=lndhub # BTCPAY_STORE_ID=''
LNDHUB_PG_PASSWORD='' # BTCPAY_AUTH_TOKEN=''
# DISCOURSE_PUBLIC_URL='https://community.kosmos.org'
# DISCOURSE_CONNECT_SECRET='discourse_connect_ftw'
# DRONECI_PUBLIC_URL='https://drone.kosmos.org'
# EJABBERD_ADMIN_URL='https://xmpp.kosmos.org/admin'
# EJABBERD_API_URL='https://xmpp.kosmos.org/api'
# GITEA_PUBLIC_URL='https://gitea.kosmos.org'
# LNDHUB_API_URL='http://localhost:3023'
# LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org'
# LNDHUB_PUBLIC_KEY='0123d3be18617f39cf645851e3ba63f51fc13f0bb09e3bb25e6fd4de556486d946'
# LNDHUB_ADMIN_UI=true
# LNDHUB_ADMIN_TOKEN=123456789
# LNDHUB_PG_HOST=localhost
# LNDHUB_PG_PORT=5432
# LNDHUB_PG_DATABASE=lndhub
# LNDHUB_PG_USERNAME=lndhub
# LNDHUB_PG_PASSWORD=''
# MASTODON_PUBLIC_URL='https://kosmos.social'
# MASTODON_ADDRESS_DOMAIN='https://kosmos.org'
# MEDIAWIKI_PUBLIC_URL='https://wiki.kosmos.org'
# NOSTR_PRIVATE_KEY='123456abcdef...'
# NOSTR_PUBLIC_KEY='123456abcdef...'
# NOSTR_RELAY_URL='wss://nostr.kosmos.org'
# RS_STORAGE_URL='https://storage.kosmos.org'
# RS_REDIS_URL='redis://localhost:6379/2'
+16 -1
View File
@@ -1,16 +1,31 @@
PRIMARY_DOMAIN=kosmos.org PRIMARY_DOMAIN=kosmos.org
AKKOUNTS_DOMAIN=accounts.kosmos.org
ENCRYPTION_PRIMARY_KEY=YhNLBgCFMAzw5dV3gISxnGrhNDMQwRdn
ENCRYPTION_KEY_DERIVATION_SALT=h28g16MRZ1sghF2jTCos1DiLZXUswinR
REDIS_URL='redis://localhost:6379/0'
BTCPAY_PUBLIC_URL='https://btcpay.example.com'
BTCPAY_API_URL='http://btcpay.example.com/api/v1'
BTCPAY_STORE_ID='123456'
DISCOURSE_PUBLIC_URL='http://discourse.example.com' DISCOURSE_PUBLIC_URL='http://discourse.example.com'
DISCOURSE_CONNECT_SECRET='discourse_connect_ftw' DISCOURSE_CONNECT_SECRET='discourse_connect_ftw'
EJABBERD_API_URL='http://xmpp.example.com/api' EJABBERD_API_URL='http://xmpp.example.com/api'
BTCPAY_API_URL='http://btcpay.example.com/api/v1' MASTODON_PUBLIC_URL='http://example.social'
LNDHUB_API_URL='http://localhost:3026' LNDHUB_API_URL='http://localhost:3026'
LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org' LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org'
LNDHUB_PUBLIC_KEY='024cd3be18617f39cf645851e3ba63f51fc13f0bb09e3bb25e6fd4de556486d946' LNDHUB_PUBLIC_KEY='024cd3be18617f39cf645851e3ba63f51fc13f0bb09e3bb25e6fd4de556486d946'
NOSTR_PRIVATE_KEY='7c3ef7e448505f0615137af38569d01807d3b05b5005d5ecf8aaafcd40323cea'
NOSTR_PUBLIC_KEY='bdd76ce2934b2f591f9fad2ebe9da18f20d2921de527494ba00eeaa0a0efadcf'
RS_REDIS_URL='redis://localhost:6379/1'
RS_STORAGE_URL='https://storage.kosmos.org' RS_STORAGE_URL='https://storage.kosmos.org'
RS_AKKOUNTS_DOMAIN=localhost
WEBHOOKS_ALLOWED_IPS='10.1.1.23' WEBHOOKS_ALLOWED_IPS='10.1.1.23'
+1
View File
@@ -7,6 +7,7 @@ version-resolver:
minor: minor:
labels: labels:
- 'release/minor' - 'release/minor'
- 'feature'
patch: patch:
labels: labels:
- 'release/patch' - 'release/patch'
+6
View File
@@ -23,6 +23,7 @@
!/tmp/pids/ !/tmp/pids/
!/tmp/pids/.keep !/tmp/pids/.keep
/storage
/public/assets /public/assets
.byebug_history .byebug_history
@@ -36,12 +37,17 @@
/yarn-error.log /yarn-error.log
yarn-debug.log* yarn-debug.log*
.yarn-integrity .yarn-integrity
bun.lock
# Ignore local dotenv config file # Ignore local dotenv config file
.env .env
.env.development
# Ignore redis dumps from sidekiq # Ignore redis dumps from sidekiq
dump.rdb dump.rdb
/app/assets/builds/* /app/assets/builds/*
!/app/assets/builds/.keep !/app/assets/builds/.keep
# Ignore generated ctags
*.tags
+1 -1
View File
@@ -1 +1 @@
2.7.2 3.3.0
+6 -5
View File
@@ -1,17 +1,18 @@
# syntax=docker/dockerfile:1 # syntax=docker/dockerfile:1
FROM ruby:2.7.6 FROM ruby:3.3.4
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 \ RUN apt-get update -qq && apt-get install -y --no-install-recommends curl \
ldap-utils tini ldap-utils tini libvips
RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - RUN 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
WORKDIR /akkounts WORKDIR /akkounts
COPY Gemfile /akkounts/Gemfile
COPY Gemfile.lock /akkounts/Gemfile.lock COPY ["Gemfile", "Gemfile.lock", "package.json", "./"]
COPY package.json /akkounts/package.json
RUN bundle install RUN bundle install
RUN gem install foreman RUN gem install foreman
RUN npm install -g yarn RUN npm install -g yarn
+20 -18
View File
@@ -2,13 +2,13 @@ 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', '~> 8.0'
# Use Puma as the app server # Use Puma as the app server
gem 'puma', '~> 4.1' gem 'puma', '~> 6.6'
# View components # View components
gem "view_component" gem "view_component"
# Separate dependency since Rails 7.0 # Asset bundler
gem 'sprockets-rails' gem 'propshaft'
# Allows custom JS build tasks to integrate with the asset pipeline # Allows custom JS build tasks to integrate with the asset pipeline
gem 'cssbundling-rails' gem 'cssbundling-rails'
# Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails] # Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails]
@@ -19,17 +19,12 @@ gem "turbo-rails"
gem "stimulus-rails" gem "stimulus-rails"
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 2.7' gem 'jbuilder', '~> 2.7'
# Use Redis adapter to run Action Cable in production
# 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'
# Security
gem 'lockbox'
# Authentication # Authentication
gem 'warden' gem 'warden'
gem 'devise', '~> 4.9.0' gem 'devise', '~> 4.9.0'
@@ -37,19 +32,24 @@ gem 'devise_ldap_authenticatable'
gem 'net-ldap' gem 'net-ldap'
# Utilities # Utilities
gem "image_processing", "~> 1.12.2"
gem "rqrcode", "~> 2.0" gem "rqrcode", "~> 2.0"
gem 'rails-settings-cached', '~> 2.8.3' gem 'rails-settings-cached', '~> 2.8.3'
gem 'pagy', '~> 6.0', '>= 6.0.2' gem 'pagy', '~> 6.0', '>= 6.0.2'
gem 'flipper' gem 'flipper'
gem 'flipper-active_record' gem 'flipper-active_record'
gem 'flipper-ui' gem 'flipper-ui'
gem 'gpgme', '~> 2.0.24'
gem 'zbase32', '~> 0.1.1'
# HTTP requests # HTTP requests
gem 'faraday' gem 'faraday'
gem 'down'
gem 'aws-sdk-s3', require: false
# Background/scheduled jobs # Background/scheduled jobs
gem 'sidekiq', '< 7' gem 'solid_queue'
gem 'sidekiq-scheduler' gem "mission_control-jobs"
# Monitoring # Monitoring
gem "sentry-ruby" gem "sentry-ruby"
@@ -58,18 +58,20 @@ gem "sentry-rails"
# Services # Services
gem 'discourse_api' gem 'discourse_api'
gem "lnurl" gem "lnurl"
gem 'nostr', git: 'https://gitea.kosmos.org/kosmos/nostr-gem.git', branch: 'feature/ruby_2.7_compat' gem 'manifique', '~> 1.1.0'
gem 'nostr', '~> 0.6.0'
gem "redis", "~> 5.4"
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', '>= 2.1'
gem 'rspec-rails' gem 'rspec-rails'
gem "byebug", "~> 11.1" gem 'rails-controller-testing'
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'
@@ -85,8 +87,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]
+396 -276
View File
@@ -1,122 +1,137 @@
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.5) actioncable (8.0.2)
actionpack (= 7.0.5) actionpack (= 8.0.2)
activesupport (= 7.0.5) activesupport (= 8.0.2)
nio4r (~> 2.0) nio4r (~> 2.0)
websocket-driver (>= 0.6.1) websocket-driver (>= 0.6.1)
actionmailbox (7.0.5) zeitwerk (~> 2.6)
actionpack (= 7.0.5) actionmailbox (8.0.2)
activejob (= 7.0.5) actionpack (= 8.0.2)
activerecord (= 7.0.5) activejob (= 8.0.2)
activestorage (= 7.0.5) activerecord (= 8.0.2)
activesupport (= 7.0.5) activestorage (= 8.0.2)
mail (>= 2.7.1) activesupport (= 8.0.2)
net-imap mail (>= 2.8.0)
net-pop actionmailer (8.0.2)
net-smtp actionpack (= 8.0.2)
actionmailer (7.0.5) actionview (= 8.0.2)
actionpack (= 7.0.5) activejob (= 8.0.2)
actionview (= 7.0.5) activesupport (= 8.0.2)
activejob (= 7.0.5) mail (>= 2.8.0)
activesupport (= 7.0.5) rails-dom-testing (~> 2.2)
mail (~> 2.5, >= 2.5.4) actionpack (8.0.2)
net-imap actionview (= 8.0.2)
net-pop activesupport (= 8.0.2)
net-smtp nokogiri (>= 1.8.5)
rails-dom-testing (~> 2.0) rack (>= 2.2.4)
actionpack (7.0.5) rack-session (>= 1.0.1)
actionview (= 7.0.5)
activesupport (= 7.0.5)
rack (~> 2.0, >= 2.2.4)
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.5) useragent (~> 0.16)
actionpack (= 7.0.5) actiontext (8.0.2)
activerecord (= 7.0.5) actionpack (= 8.0.2)
activestorage (= 7.0.5) activerecord (= 8.0.2)
activesupport (= 7.0.5) activestorage (= 8.0.2)
activesupport (= 8.0.2)
globalid (>= 0.6.0) globalid (>= 0.6.0)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
actionview (7.0.5) actionview (8.0.2)
activesupport (= 7.0.5) activesupport (= 8.0.2)
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.5) activejob (8.0.2)
activesupport (= 7.0.5) activesupport (= 8.0.2)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (7.0.5) activemodel (8.0.2)
activesupport (= 7.0.5) activesupport (= 8.0.2)
activerecord (7.0.5) activerecord (8.0.2)
activemodel (= 7.0.5) activemodel (= 8.0.2)
activesupport (= 7.0.5) activesupport (= 8.0.2)
activestorage (7.0.5) timeout (>= 0.4.0)
actionpack (= 7.0.5) activestorage (8.0.2)
activejob (= 7.0.5) actionpack (= 8.0.2)
activerecord (= 7.0.5) activejob (= 8.0.2)
activesupport (= 7.0.5) activerecord (= 8.0.2)
activesupport (= 8.0.2)
marcel (~> 1.0) marcel (~> 1.0)
mini_mime (>= 1.1.0) activesupport (8.0.2)
activesupport (7.0.5) base64
concurrent-ruby (~> 1.0, >= 1.0.2) benchmark (>= 0.3)
bigdecimal
concurrent-ruby (~> 1.0, >= 1.3.1)
connection_pool (>= 2.2.5)
drb
i18n (>= 1.6, < 2) i18n (>= 1.6, < 2)
logger (>= 1.4.2)
minitest (>= 5.1) minitest (>= 5.1)
tzinfo (~> 2.0) securerandom (>= 0.3)
addressable (2.8.4) tzinfo (~> 2.0, >= 2.0.5)
public_suffix (>= 2.0.2, < 6.0) uri (>= 0.13.1)
ast (2.4.2) addressable (2.8.7)
public_suffix (>= 2.0.2, < 7.0)
ast (2.4.3)
aws-eventstream (1.3.2)
aws-partitions (1.1092.0)
aws-sdk-core (3.222.2)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.992.0)
aws-sigv4 (~> 1.9)
base64
jmespath (~> 1, >= 1.6.1)
logger
aws-sdk-kms (1.99.0)
aws-sdk-core (~> 3, >= 3.216.0)
aws-sigv4 (~> 1.5)
aws-sdk-s3 (1.183.0)
aws-sdk-core (~> 3, >= 3.216.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.5)
aws-sigv4 (1.11.0)
aws-eventstream (~> 1, >= 1.0.2)
backport (1.2.0) backport (1.2.0)
bcrypt (3.1.18) base64 (0.2.0)
bech32 (1.3.0) bcrypt (3.1.20)
bech32 (1.5.0)
thor (>= 1.1.0) thor (>= 1.1.0)
benchmark (0.2.1) benchmark (0.4.0)
bigdecimal (3.1.9)
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)
builder (3.2.4) builder (3.3.0)
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)
childprocess (5.1.0)
logger (~> 1.5)
chunky_png (1.4.0) chunky_png (1.4.0)
concurrent-ruby (1.2.2) concurrent-ruby (1.3.4)
connection_pool (2.4.1) connection_pool (2.5.2)
crack (0.4.5) crack (1.0.0)
bigdecimal
rexml rexml
crass (1.0.6) crass (1.0.6)
cssbundling-rails (1.1.2) cssbundling-rails (1.4.3)
railties (>= 6.0.0) railties (>= 6.0.0)
database_cleaner (2.0.2) database_cleaner (2.1.0)
database_cleaner-active_record (>= 2, < 3) database_cleaner-active_record (>= 2, < 3)
database_cleaner-active_record (2.1.0) database_cleaner-active_record (2.2.0)
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.4.1)
devise (4.9.2) devise (4.9.4)
bcrypt (~> 3.0) bcrypt (~> 3.0)
orm_adapter (~> 0.1) orm_adapter (~> 0.1)
railties (>= 4.1.0) railties (>= 4.1.0)
@@ -125,91 +140,112 @@ 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.6.1)
discourse_api (2.0.1) discourse_api (2.0.1)
faraday (~> 2.7) faraday (~> 2.7)
faraday-follow_redirects faraday-follow_redirects
faraday-multipart faraday-multipart
rack (>= 1.6) rack (>= 1.6)
dotenv (2.8.1) dotenv (3.1.8)
dotenv-rails (2.8.1) dotenv-rails (3.1.8)
dotenv (= 2.8.1) dotenv (= 3.1.8)
railties (>= 3.2) railties (>= 6.1)
e2mmap (0.1.0) down (5.4.2)
addressable (~> 2.8)
drb (2.2.1)
ecdsa (1.2.0) ecdsa (1.2.0)
ecdsa_ext (0.5.0) ecdsa_ext (0.5.1)
ecdsa (~> 1.2.0) ecdsa (~> 1.2.0)
erubi (1.12.0) erubi (1.13.1)
et-orbi (1.2.7) et-orbi (1.2.11)
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.5.1)
activesupport (>= 5.0.0) activesupport (>= 6.1.0)
factory_bot_rails (6.2.0) factory_bot_rails (6.4.4)
factory_bot (~> 6.2.0) factory_bot (~> 6.5)
railties (>= 5.0.0) railties (>= 5.0.0)
faker (3.2.0) faker (3.5.1)
i18n (>= 1.8.11, < 2) i18n (>= 1.8.11, < 2)
faraday (2.7.6) faraday (2.9.2)
faraday-net_http (>= 2.0, < 3.1) faraday-net_http (>= 2.0, < 3.2)
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.1.0)
multipart-post (~> 2) multipart-post (~> 2.0)
faraday-net_http (3.0.2) faraday-net_http (3.1.1)
faye-websocket (0.11.2) net-http
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.15.5) ffi (1.17.2)
flipper (0.28.0) ffi (1.17.2-arm64-darwin)
ffi (1.17.2-x86_64-linux-gnu)
flipper (1.3.4)
concurrent-ruby (< 2) concurrent-ruby (< 2)
flipper-active_record (0.28.0) flipper-active_record (1.3.4)
activerecord (>= 4.2, < 8) activerecord (>= 4.2, < 9)
flipper (~> 0.28.0) flipper (~> 1.3.4)
flipper-ui (0.28.0) flipper-ui (1.3.4)
erubi (>= 1.0.0, < 2.0.0) erubi (>= 1.0.0, < 2.0.0)
flipper (~> 0.28.0) flipper (~> 1.3.4)
rack (>= 1.4, < 3) rack (>= 1.4, < 4)
rack-protection (>= 1.5.3, <= 4.0.0) rack-protection (>= 1.5.3, < 5.0.0)
sanitize (< 7) rack-session (>= 1.0.2, < 3.0.0)
fugit (1.8.1) sanitize (< 8)
et-orbi (~> 1, >= 1.2.7) fugit (1.11.1)
et-orbi (~> 1, >= 1.2.11)
raabro (~> 1.4) raabro (~> 1.4)
globalid (1.1.0) globalid (1.2.1)
activesupport (>= 5.0) activesupport (>= 6.1)
hashdiff (1.0.1) gpgme (2.0.24)
i18n (1.14.1) mini_portile2 (~> 2.7)
hashdiff (1.1.2)
i18n (1.14.7)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
importmap-rails (1.1.6) image_processing (1.12.2)
mini_magick (>= 4.9.5, < 5)
ruby-vips (>= 2.0.17, < 3)
importmap-rails (2.1.0)
actionpack (>= 6.0.0) actionpack (>= 6.0.0)
activesupport (>= 6.0.0)
railties (>= 6.0.0) railties (>= 6.0.0)
jaro_winkler (1.5.6) io-console (0.8.0)
jbuilder (2.11.5) irb (1.15.2)
pp (>= 0.6.0)
rdoc (>= 4.0.0)
reline (>= 0.4.2)
jaro_winkler (1.6.0)
jbuilder (2.13.0)
actionview (>= 5.0.0) actionview (>= 5.0.0)
activesupport (>= 5.0.0) activesupport (>= 5.0.0)
json (2.6.3) jmespath (1.6.2)
kramdown (2.4.0) json (2.11.3)
rexml kramdown (2.5.1)
rexml (>= 3.3.9)
kramdown-parser-gfm (1.1.0) kramdown-parser-gfm (1.1.0)
kramdown (~> 2.0) kramdown (~> 2.0)
launchy (2.5.2) language_server-protocol (3.17.0.4)
launchy (3.1.1)
addressable (~> 2.8) addressable (~> 2.8)
letter_opener (1.8.1) childprocess (~> 5.0)
launchy (>= 2.2, < 3) logger (~> 1.6)
letter_opener_web (2.0.0) letter_opener (1.10.0)
actionmailer (>= 5.2) launchy (>= 2.2, < 4)
letter_opener (~> 1.7) letter_opener_web (3.0.0)
railties (>= 5.2) actionmailer (>= 6.1)
letter_opener (~> 1.9)
railties (>= 6.1)
rexml rexml
listen (3.8.0) lint_roller (1.1.0)
listen (3.9.0)
rb-fsevent (~> 0.10, >= 0.10.3) rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10) rb-inotify (~> 0.9, >= 0.9.10)
lnurl (1.0.1) lnurl (1.1.1)
bech32 (~> 1.1) bech32 (~> 1.1)
lockbox (1.2.0) logger (1.7.0)
loofah (2.21.3) loofah (2.24.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)
@@ -217,203 +253,281 @@ GEM
net-imap net-imap
net-pop net-pop
net-smtp net-smtp
marcel (1.0.2) manifique (1.1.0)
faraday (~> 2.9.0)
faraday-follow_redirects (= 0.3.0)
nokogiri (~> 1.16.0)
marcel (1.0.4)
matrix (0.4.2) matrix (0.4.2)
method_source (1.0.0) method_source (1.1.0)
mini_mime (1.1.2) mini_magick (4.13.2)
minitest (5.18.0) mini_mime (1.1.5)
multipart-post (2.3.0) mini_portile2 (2.8.8)
net-imap (0.3.6) minitest (5.25.5)
mission_control-jobs (1.0.2)
actioncable (>= 7.1)
actionpack (>= 7.1)
activejob (>= 7.1)
activerecord (>= 7.1)
importmap-rails (>= 1.2.1)
irb (~> 1.13)
railties (>= 7.1)
stimulus-rails
turbo-rails
multipart-post (2.4.1)
net-http (0.6.0)
uri
net-imap (0.5.7)
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.3.3) net-smtp (0.5.1)
net-protocol net-protocol
nio4r (2.5.9) nio4r (2.7.4)
nokogiri (1.15.2-x86_64-linux) nokogiri (1.16.8)
mini_portile2 (~> 2.8.2)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.16.8-arm64-darwin)
racc (~> 1.4)
nokogiri (1.16.8-x86_64-linux)
racc (~> 1.4)
nostr (0.6.0)
bech32 (~> 1.4)
bip-schnorr (~> 0.7)
ecdsa (~> 1.2)
event_emitter (~> 0.2)
faye-websocket (~> 0.11)
json (~> 2.6)
observer (0.1.2)
orm_adapter (0.5.0) orm_adapter (0.5.0)
pagy (6.0.4) ostruct (0.6.1)
parallel (1.23.0) pagy (6.5.0)
parser (3.2.2.3) parallel (1.27.0)
parser (3.3.8.0)
ast (~> 2.4.1) ast (~> 2.4.1)
racc racc
pg (1.2.3) pg (1.5.9)
public_suffix (5.0.1) pp (0.6.2)
puma (4.3.12) prettyprint
prettyprint (0.2.0)
prism (1.4.0)
propshaft (1.1.0)
actionpack (>= 7.0.0)
activesupport (>= 7.0.0)
rack
railties (>= 7.0.0)
psych (5.2.3)
date
stringio
public_suffix (6.0.1)
puma (6.6.0)
nio4r (~> 2.0) nio4r (~> 2.0)
raabro (1.4.0) raabro (1.4.0)
racc (1.7.1) racc (1.8.1)
rack (2.2.7) rack (2.2.13)
rack-protection (3.0.6) rack-protection (3.2.0)
rack base64 (>= 0.1.0)
rack-test (2.1.0) rack (~> 2.2, >= 2.2.4)
rack-session (1.0.2)
rack (< 3)
rack-test (2.2.0)
rack (>= 1.3) rack (>= 1.3)
rails (7.0.5) rackup (1.0.1)
actioncable (= 7.0.5) rack (< 3)
actionmailbox (= 7.0.5) webrick
actionmailer (= 7.0.5) rails (8.0.2)
actionpack (= 7.0.5) actioncable (= 8.0.2)
actiontext (= 7.0.5) actionmailbox (= 8.0.2)
actionview (= 7.0.5) actionmailer (= 8.0.2)
activejob (= 7.0.5) actionpack (= 8.0.2)
activemodel (= 7.0.5) actiontext (= 8.0.2)
activerecord (= 7.0.5) actionview (= 8.0.2)
activestorage (= 7.0.5) activejob (= 8.0.2)
activesupport (= 7.0.5) activemodel (= 8.0.2)
activerecord (= 8.0.2)
activestorage (= 8.0.2)
activesupport (= 8.0.2)
bundler (>= 1.15.0) bundler (>= 1.15.0)
railties (= 7.0.5) railties (= 8.0.2)
rails-dom-testing (2.0.3) rails-controller-testing (1.0.5)
activesupport (>= 4.2.0) actionpack (>= 5.0.1.rc1)
actionview (>= 5.0.1.rc1)
activesupport (>= 5.0.1.rc1)
rails-dom-testing (2.2.0)
activesupport (>= 5.0.0)
minitest
nokogiri (>= 1.6) nokogiri (>= 1.6)
rails-html-sanitizer (1.6.0) rails-html-sanitizer (1.6.2)
loofah (~> 2.21) loofah (~> 2.21)
nokogiri (~> 1.14) nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
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.5) railties (8.0.2)
actionpack (= 7.0.5) actionpack (= 8.0.2)
activesupport (= 7.0.5) activesupport (= 8.0.2)
method_source irb (~> 1.13)
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.2.1)
rb-fsevent (0.11.2) rb-fsevent (0.11.2)
rb-inotify (0.10.1) rb-inotify (0.11.1)
ffi (~> 1.0) ffi (~> 1.0)
rbs (2.8.4) rbs (3.9.2)
redis (4.8.1) logger
regexp_parser (2.8.1) rdoc (6.13.1)
responders (3.1.0) psych (>= 4.0.0)
redis (5.4.0)
redis-client (>= 0.22.0)
redis-client (0.24.0)
connection_pool
regexp_parser (2.10.0)
reline (0.6.1)
io-console (~> 0.5)
responders (3.1.1)
actionpack (>= 5.2) actionpack (>= 5.2)
railties (>= 5.2) railties (>= 5.2)
reverse_markdown (2.1.1) reverse_markdown (3.0.0)
nokogiri nokogiri
rexml (3.2.5) rexml (3.4.1)
rqrcode (2.2.0) rqrcode (2.2.0)
chunky_png (~> 1.0) chunky_png (~> 1.0)
rqrcode_core (~> 1.0) rqrcode_core (~> 1.0)
rqrcode_core (1.2.0) rqrcode_core (1.2.0)
rspec-core (3.12.2) rspec-core (3.13.3)
rspec-support (~> 3.12.0) rspec-support (~> 3.13.0)
rspec-expectations (3.12.3) rspec-expectations (3.13.3)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.12.0) rspec-support (~> 3.13.0)
rspec-mocks (3.12.5) rspec-mocks (3.13.2)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.12.0) rspec-support (~> 3.13.0)
rspec-rails (6.0.3) rspec-rails (7.1.1)
actionpack (>= 6.1) actionpack (>= 7.0)
activesupport (>= 6.1) activesupport (>= 7.0)
railties (>= 6.1) railties (>= 7.0)
rspec-core (~> 3.12) rspec-core (~> 3.13)
rspec-expectations (~> 3.12) rspec-expectations (~> 3.13)
rspec-mocks (~> 3.12) rspec-mocks (~> 3.13)
rspec-support (~> 3.12) rspec-support (~> 3.13)
rspec-support (3.12.0) rspec-support (3.13.2)
rubocop (1.52.1) rubocop (1.75.3)
json (~> 2.3) json (~> 2.3)
language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.1.0)
parallel (~> 1.10) parallel (~> 1.10)
parser (>= 3.2.2.3) 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 (>= 2.9.3, < 3.0)
rexml (>= 3.2.5, < 4.0) rubocop-ast (>= 1.44.0, < 2.0)
rubocop-ast (>= 1.28.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, < 4.0)
rubocop-ast (1.29.0) rubocop-ast (1.44.1)
parser (>= 3.2.1.0) parser (>= 3.3.7.2)
prism (~> 1.4)
ruby-progressbar (1.13.0) ruby-progressbar (1.13.0)
ruby2_keywords (0.0.5) ruby-vips (2.2.3)
rufus-scheduler (3.9.1) ffi (~> 1.12)
fugit (~> 1.1, >= 1.1.6) logger
sanitize (6.0.1) sanitize (7.0.0)
crass (~> 1.0.2) crass (~> 1.0.2)
nokogiri (>= 1.12.0) nokogiri (>= 1.16.8)
sentry-rails (5.9.0) securerandom (0.4.1)
sentry-rails (5.23.0)
railties (>= 5.0) railties (>= 5.0)
sentry-ruby (~> 5.9.0) sentry-ruby (~> 5.23.0)
sentry-ruby (5.9.0) sentry-ruby (5.23.0)
bigdecimal
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
sidekiq (6.5.9) solargraph (0.54.2)
connection_pool (>= 2.2.5, < 3)
rack (~> 2.0)
redis (>= 4.5.0, < 5)
sidekiq-scheduler (5.0.3)
rufus-scheduler (~> 3.2)
sidekiq (>= 6, < 8)
tilt (>= 1.4.0)
solargraph (0.49.0)
backport (~> 1.2) backport (~> 1.2)
benchmark benchmark (~> 0.4)
bundler (~> 2.0) bundler (~> 2.0)
diff-lcs (~> 1.4) diff-lcs (~> 1.4)
e2mmap jaro_winkler (~> 1.6)
jaro_winkler (~> 1.5)
kramdown (~> 2.3) kramdown (~> 2.3)
kramdown-parser-gfm (~> 1.1) kramdown-parser-gfm (~> 1.1)
logger (~> 1.6)
observer (~> 0.1)
ostruct (~> 0.6)
parser (~> 3.0) parser (~> 3.0)
rbs (~> 2.0) rbs (~> 3.3)
reverse_markdown (~> 2.0) reverse_markdown (~> 3.0)
rubocop (~> 1.38) rubocop (~> 1.38)
thor (~> 1.0) thor (~> 1.0)
tilt (~> 2.0) tilt (~> 2.0)
yard (~> 0.9, >= 0.9.24) yard (~> 0.9, >= 0.9.24)
sprockets (4.2.0) yard-solargraph (~> 0.1)
concurrent-ruby (~> 1.0) solid_queue (1.1.5)
rack (>= 2.2.4, < 4) activejob (>= 7.1)
sprockets-rails (3.4.2) activerecord (>= 7.1)
actionpack (>= 5.2) concurrent-ruby (>= 1.3.1)
activesupport (>= 5.2) fugit (~> 1.11.0)
sprockets (>= 3.0.0) railties (>= 7.1)
sqlite3 (1.6.3-x86_64-linux) thor (~> 1.3.1)
stimulus-rails (1.2.1) sqlite3 (2.6.0)
railties (>= 6.0.0) mini_portile2 (~> 2.8.0)
thor (1.2.2) sqlite3 (2.6.0-arm64-darwin)
tilt (2.2.0) sqlite3 (2.6.0-x86_64-linux-gnu)
timeout (0.3.2) stimulus-rails (1.3.4)
turbo-rails (1.4.0)
actionpack (>= 6.0.0)
activejob (>= 6.0.0)
railties (>= 6.0.0) railties (>= 6.0.0)
stringio (3.1.7)
thor (1.3.2)
tilt (2.6.0)
timeout (0.4.3)
turbo-rails (2.0.13)
actionpack (>= 7.1.0)
railties (>= 7.1.0)
tzinfo (2.0.6) tzinfo (2.0.6)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
unicode-display_width (2.4.2) unicode-display_width (3.1.4)
view_component (3.2.0) unicode-emoji (~> 4.0, >= 4.0.4)
activesupport (>= 5.2.0, < 8.0) unicode-emoji (4.0.4)
concurrent-ruby (~> 1.0) uri (1.0.3)
useragent (0.16.11)
view_component (3.22.0)
activesupport (>= 5.2.0, < 8.1)
concurrent-ruby (= 1.3.4)
method_source (~> 1.0) method_source (~> 1.0)
warden (1.2.9) warden (1.2.9)
rack (>= 2.0.9) rack (>= 2.0.9)
web-console (4.2.0) web-console (4.2.1)
actionview (>= 6.0.0) actionview (>= 6.0.0)
activemodel (>= 6.0.0) activemodel (>= 6.0.0)
bindex (>= 0.4.0) bindex (>= 0.4.0)
railties (>= 6.0.0) railties (>= 6.0.0)
webmock (3.18.1) webmock (3.25.1)
addressable (>= 2.8.0) addressable (>= 2.8.0)
crack (>= 0.3.2) crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0) hashdiff (>= 0.4.0, < 2.0.0)
websocket-driver (0.7.5) webrick (1.9.1)
websocket-driver (0.7.7)
base64
websocket-extensions (>= 0.1.0) websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5) websocket-extensions (0.1.5)
xpath (3.2.0) xpath (3.2.0)
nokogiri (~> 1.8) nokogiri (~> 1.8)
yard (0.9.34) yard (0.9.37)
zeitwerk (2.6.8) yard-solargraph (0.1.0)
yard (~> 0.9)
zbase32 (0.1.1)
zeitwerk (2.7.2)
PLATFORMS PLATFORMS
arm64-darwin-22
ruby
x86_64-linux x86_64-linux
DEPENDENCIES DEPENDENCIES
byebug (~> 11.1) aws-sdk-s3
bcrypt (~> 3.1)
capybara capybara
cssbundling-rails cssbundling-rails
database_cleaner database_cleaner
@@ -421,42 +535,48 @@ DEPENDENCIES
devise_ldap_authenticatable devise_ldap_authenticatable
discourse_api discourse_api
dotenv-rails dotenv-rails
down
factory_bot_rails factory_bot_rails
faker faker
faraday faraday
flipper flipper
flipper-active_record flipper-active_record
flipper-ui flipper-ui
gpgme (~> 2.0.24)
image_processing (~> 1.12.2)
importmap-rails importmap-rails
jbuilder (~> 2.7) jbuilder (~> 2.7)
letter_opener letter_opener
letter_opener_web letter_opener_web
listen (~> 3.2) listen (~> 3.2)
lnurl lnurl
lockbox manifique (~> 1.1.0)
mission_control-jobs
net-ldap net-ldap
nostr! nostr (~> 0.6.0)
pagy (~> 6.0, >= 6.0.2) pagy (~> 6.0, >= 6.0.2)
pg (~> 1.2.3) pg (~> 1.5)
puma (~> 4.1) propshaft
rails (~> 7.0.2) puma (~> 6.6)
rails (~> 8.0)
rails-controller-testing
rails-settings-cached (~> 2.8.3) rails-settings-cached (~> 2.8.3)
redis (~> 5.4)
rqrcode (~> 2.0) rqrcode (~> 2.0)
rspec-rails rspec-rails
sentry-rails sentry-rails
sentry-ruby sentry-ruby
sidekiq (< 7)
sidekiq-scheduler
solargraph solargraph
sprockets-rails solid_queue
sqlite3 (~> 1.4) sqlite3 (>= 2.1)
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
zbase32 (~> 0.1.1)
BUNDLED WITH BUNDLED WITH
2.3.7 2.5.5
+42 -16
View File
@@ -14,9 +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)
2. Uncomment the `redis`, `web`, and `sidekiq` sections in `docker-compose.yml` 3. Run `docker compose up --build` and wait until all services have started
3. Run `docker compose up` and wait until 389ds announces its successful start (389ds might take an extra minute to be ready). This will take a while when
in the log output 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`
@@ -29,36 +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 allows you to spin up both docker-compose exec -e "RAILS_ENV=test" web rspec
an app server for Rails as well as a local 389ds (LDAP) server.
By default, `docker-compose up` will only start the LDAP server, listening on ### Docker Compose
port 389 on your machine. Uncomment other services in `docker-compose.yml` if
you want to use them. Services/containers are configured in `docker-compose.yml`.
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
@@ -75,8 +84,24 @@ 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 / remoteStorage
If you want to run remoteStorage accounts locally, you will have to create the
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`
* Head to http://localhost:9001 and log in with user `minioadmin`, password
`minioadmin`
* Create a new bucket called `remotestorage` (or whatever you
change the `S3_BUCKET` config to)
* Create a new key with ID "dev-key" and secret "123456789" (or whatever you
change `S3_ACCESS_KEY` and `S3_SECRET_KEY` to). Leave the policy field empty,
as it will automatically allow access to the bucket you created.
### Adding npm modules to use with Stimulus controllers ### Adding npm modules to use with Stimulus controllers
@@ -106,6 +131,7 @@ command:
* [Tailwind CSS](https://tailwindcss.com/) * [Tailwind CSS](https://tailwindcss.com/)
* [Sass](https://sass-lang.com/documentation) * [Sass](https://sass-lang.com/documentation)
* [Stimulus](https://stimulus.hotwired.dev/handbook/) * [Stimulus](https://stimulus.hotwired.dev/handbook/)
* [Tailwind Stimulus Components](https://github.com/excid3/tailwindcss-stimulus-components)
### Testing ### Testing
-4
View File
@@ -1,4 +0,0 @@
//= link_tree ../images
//= link_tree ../../javascript .js
//= link_tree ../builds
//= link_tree ../../../vendor/javascript .js
@@ -2,6 +2,7 @@
@import "tailwindcss/components"; @import "tailwindcss/components";
@import "tailwindcss/utilities"; @import "tailwindcss/utilities";
@import "components/animations";
@import "components/base"; @import "components/base";
@import "components/buttons"; @import "components/buttons";
@import "components/dashboard_services"; @import "components/dashboard_services";
@@ -0,0 +1,16 @@
@keyframes scaleIn {
from {
transform: scale(0.5);
opacity: 0;
}
to {
transform: scale(1);
opacity: 1;
}
}
.animate-scale-in {
animation-name: scaleIn;
animation-duration: 0.15s;
animation-timing-function: cubic-bezier(0.2, 0, 0.13, 1);
}
+14 -4
View File
@@ -14,12 +14,12 @@
@apply py-1 px-2 text-sm; @apply py-1 px-2 text-sm;
} }
.btn-outline { .btn-icon {
@apply border-2 border-gray-100 hover:bg-gray-100; @apply py-2 px-3;
} }
.btn-icon { .btn-outline {
@apply px-3; @apply py-2 border-2 border-gray-100 hover:bg-gray-100;
} }
.btn-gray { .btn-gray {
@@ -32,11 +32,21 @@
focus:ring-blue-400 focus:ring-opacity-75; focus:ring-blue-400 focus:ring-opacity-75;
} }
.btn-emerald {
@apply bg-emerald-500 hover:bg-emerald-600 text-white
focus:ring-emerald-400 focus:ring-opacity-75;
}
.btn-red { .btn-red {
@apply bg-red-600 hover:bg-red-700 text-white @apply bg-red-600 hover:bg-red-700 text-white
focus:ring-red-500 focus:ring-opacity-75; focus:ring-red-500 focus:ring-opacity-75;
} }
.btn-outline-purple {
@apply border-2 border-purple-500 hover:bg-purple-100
focus:ring-purple-400 focus:ring-opacity-75;
}
.btn:disabled { .btn:disabled {
@apply bg-gray-100 hover:bg-gray-200 text-gray-400 @apply bg-gray-100 hover:bg-gray-200 text-gray-400
focus:ring-gray-300 focus:ring-opacity-75; focus:ring-gray-300 focus:ring-opacity-75;
@@ -1,5 +1,5 @@
@layer components { @layer components {
.services > div > a { .services > div > a {
background-image: linear-gradient(110deg, rgba(255,255,255,0.99) 0, rgba(255,255,255,0.88) 100%); background-image: linear-gradient(110deg, rgba(255,255,255,0.99) 20%, rgba(255,255,255,0.88) 100%);
} }
} }
@@ -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 %>
@@ -0,0 +1,13 @@
# 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
end
end
@@ -0,0 +1,15 @@
<div class="flex">
<div class="<%= @icon_container_class %>">
<%= image_tag(@icon_path, class: 'h-full w-full') %>
</div>
<div class="flex-1 px-4">
<h4 class="sm:pt-2 mb-2 text-lg font-bold"><%= @name %></h4>
<p class="leading-snug"><%= @description %></p>
<p class="leading-snug flex flex-wrap gap-3">
<% @links.each do |link| %>
<a href="<%= link[1] %>" target="_blank"
class="flex-0 btn-sm btn-gray"><%= link[0] %></a>
<% end %>
</p>
</div>
</div>
+19
View File
@@ -0,0 +1,19 @@
# frozen_string_literal: true
class AppInfoComponent < ViewComponent::Base
def initialize(name:, description:, icon_path: , icon_fill_box: false, links: [])
@name = name
@description = description
@icon_path = icon_path
@icon_container_class = icon_container_class(icon_fill_box)
@links = links
end
def icon_container_class(icon_fill_box)
str = "flex-0 h-16 w-16 sm:h-28 sm:w-28 bg-white rounded-3xl overflow-hidden"
unless icon_fill_box
str += " p-2 border border-gray-200"
end
str
end
end
@@ -0,0 +1,34 @@
<div data-controller="dropdown" data-action="click->dropdown#toggle click@window->dropdown#hide">
<div class="relative inline-block">
<div role="button" tabindex="0" data-dropdown-target="button"
class="inline-block select-none">
<% if @size == :large %>
<span class="appearance-none flex items-center inline-block">
<span class="p-2 bg-gray-50 hover:bg-gray-100 rounded-full">
<%= render partial: "icons/#{@icon_name}",
locals: { custom_class: "inline text-gray-500 h-6 w-6" } %>
</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 data-dropdown-target="menu"
data-transition-enter="transition ease-out duration-200"
data-transition-enter-from="opacity-0 translate-y-1"
data-transition-enter-to="opacity-100 translate-y-0"
data-transition-leave="transition ease-in duration-150"
data-transition-leave-from="opacity-100 translate-y-0"
data-transition-leave-to="opacity-0 translate-y-1"
class="hidden absolute top-4 right-0 z-10 mt-5 flex w-screen max-w-max">
<div class="bg-white shadow-lg rounded border overflow-hidden w-auto">
<%= content %>
</div>
</div>
</div>
</div>
+8
View File
@@ -0,0 +1,8 @@
# frozen_string_literal: true
class DropdownComponent < ViewComponent::Base
def initialize(size: :large, icon_name: "kebap-menu")
@size = size.to_sym
@icon_name = icon_name
end
end
@@ -0,0 +1,6 @@
<%= link_to @href, class: @class, data: {
'dropdown-target': "menuItem",
'action': "keydown.up->dropdown#previousItem:prevent keydown.down->dropdown#nextItem:prevent"
} do %>
<%= content %>
<% end %>
+18
View File
@@ -0,0 +1,18 @@
# frozen_string_literal: true
class DropdownLinkComponent < ViewComponent::Base
def initialize(href:, separator: false, add_class: nil)
@href = href
@class = class_str(separator, add_class)
end
private
def class_str(separator, add_class)
str = "no-underline block px-5 py-3 text-sm text-gray-900 bg-white
hover:bg-gray-100 focus:bg-gray-100 whitespace-no-wrap"
str = "#{str} border-t" if separator
str = "#{str} #{add_class}" if add_class
str
end
end
@@ -1,4 +1,6 @@
<%= tag.public_send(@tag, class: "mb-6 last:mb-0") do %> <%= tag.public_send(@tag, class: "mb-6 last:mb-0", data: {
:'field-name' => @field_name
}) do %>
<% if @positioning == :vertical %> <% if @positioning == :vertical %>
<label class="block"> <label class="block">
<p class="font-bold <%= @descripton.present? ? "mb-1" : "mb-2" %>"> <p class="font-bold <%= @descripton.present? ? "mb-1" : "mb-2" %>">
@@ -9,7 +11,21 @@
<%= @descripton %> <%= @descripton %>
</p> </p>
<% end %> <% end %>
<%= content %>
<%= tag.p class: "flex gap-x-1", data: {
controller: @resettable ? "settings--resettable-field" : nil,
} do %>
<%= content %>
<% if @resettable %>
<button type="button"
class="relative grow-0 shrink-0 btn-md btn-outline text-red-700"
title="Reset to default value"
data-settings--resettable-field-target="resetButton"
data-action="settings--resettable-field#resetField">
Reset
</button>
<% end %>
<% end %>
</label> </label>
<% elsif @positioning == :horizontal %> <% elsif @positioning == :horizontal %>
<label class="block flex items-center justify-between"> <label class="block flex items-center justify-between">
@@ -2,11 +2,15 @@
module FormElements module FormElements
class FieldsetComponent < ViewComponent::Base class FieldsetComponent < ViewComponent::Base
def initialize(tag: "li", positioning: :vertical, title:, description: nil) def initialize(tag: "li", positioning: :vertical,
title:, description: nil,
field_name: nil, resettable: false)
@tag = tag @tag = tag
@positioning = positioning @positioning = positioning
@title = title @title = title
@descripton = description @descripton = description
@field_name = field_name
@resettable = resettable
end end
end end
end end
@@ -0,0 +1,14 @@
<%= render FormElements::FieldsetComponent.new(
title: @title,
description: @description,
field_name: "setting_#{@key.to_s}",
resettable: @resettable
) do %>
<%= method("#{@type}_field").call :setting, @key,
value: Setting.public_send(@key),
placeholder: @placeholder,
data: {
:'default-value' => Setting.get_field(@key)[:default]
},
class: "w-full" %>
<% end %>
@@ -0,0 +1,21 @@
# frozen_string_literal: true
module FormElements
class FieldsetResettableSettingComponent < ViewComponent::Base
def initialize(tag: "li", key:, type: :text, title:, description: nil, placeholder: nil)
@tag = tag
@positioning = :vertical
@title = title
@description = description
@key = key.to_sym
@type = type
@resettable = is_resettable?(@key)
@placeholder = placeholder
end
def is_resettable?(key)
default_value = Setting.get_field(key)[:default]
default_value.present? && (default_value != Setting.send(key))
end
end
end
@@ -5,7 +5,9 @@
} : nil do %> } : nil do %>
<div class="flex flex-col"> <div class="flex flex-col">
<label class="font-bold mb-1"><%= @title %></label> <label class="font-bold mb-1"><%= @title %></label>
<p class="text-gray-500"><%= @descripton %></p> <% if @description.present? %>
<p class="text-gray-500"><%= @description %></p>
<% end %>
</div> </div>
<div class="relative ml-4 inline-flex flex-shrink-0"> <div class="relative ml-4 inline-flex flex-shrink-0">
<%= render FormElements::ToggleComponent.new( <%= render FormElements::ToggleComponent.new(
@@ -3,7 +3,7 @@
module FormElements module FormElements
class FieldsetToggleComponent < ViewComponent::Base class FieldsetToggleComponent < ViewComponent::Base
def initialize(tag: "li", form: nil, attribute: nil, field_name: nil, def initialize(tag: "li", form: nil, attribute: nil, field_name: nil,
enabled: false, input_enabled: true, title:, description:) enabled: false, input_enabled: true, title:, description: nil)
@tag = tag @tag = tag
@form = form @form = form
@attribute = attribute @attribute = attribute
@@ -12,7 +12,7 @@ module FormElements
@enabled = enabled @enabled = enabled
@input_enabled = input_enabled @input_enabled = input_enabled
@title = title @title = title
@descripton = description @description = description
@button_text = @enabled ? "Switch off" : "Switch on" @button_text = @enabled ? "Switch off" : "Switch on"
end end
end end
@@ -1,5 +1,5 @@
<main class="w-full max-w-6xl mx-auto pb-12 px-4 md:px-6 lg:px-8"> <main class="w-full max-w-6xl mx-auto pb-12 px-4 md:px-6 lg:px-8">
<div class="bg-white rounded-lg shadow"> <div class="md:min-h-[50vh] bg-white rounded-lg shadow">
<div class="px-6 sm:px-12 pt-2 sm:pt-4"> <div class="px-6 sm:px-12 pt-2 sm:pt-4">
<%= render partial: @tabnav_partial %> <%= render partial: @tabnav_partial %>
</div> </div>
+30
View File
@@ -0,0 +1,30 @@
<div tabindex="-1" class="relative z-10">
<!-- Modal Background -->
<div class="hidden fixed inset-0 bg-black bg-opacity-80 overflow-y-auto flex items-center justify-center"
data-modal-target="background"
data-action="click->modal#closeBackground"
data-transition-enter="transition-all ease-in-out duration-100"
data-transition-enter-from="bg-opacity-0"
data-transition-enter-to="bg-opacity-80"
data-transition-leave="transition-all ease-in-out duration-100"
data-transition-leave-from="bg-opacity-80"
data-transition-leave-to="bg-opacity-0">
<!-- Modal Container -->
<div data-modal-target="container"
class="relative m-4 max-h-screen w-auto max-w-full
hidden animate-scale-in fixed inset-0 overflow-y-auto flex items-center justify-center">
<!-- Modal Card -->
<div class="m-1 bg-white rounded shadow">
<div class="p-8">
<%= content %>
<% if @show_close_button %>
<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>
</div>
<% end %>
</div>
</div>
</div>
</div>
</div>
+5
View File
@@ -0,0 +1,5 @@
class ModalComponent < ViewComponent::Base
def initialize(show_close_button: true)
@show_close_button = show_close_button
end
end
+2
View File
@@ -34,6 +34,8 @@ class NotificationComponent < ViewComponent::Base
'alert-octagon' 'alert-octagon'
when 'alert' when 'alert'
'alert-octagon' 'alert-octagon'
when 'warning'
'alert-octagon'
else else
'info' 'info'
end end
@@ -0,0 +1,6 @@
<%= render ModalComponent.new do %>
<% if @descripton.present? %>
<p class="mb-6"><%= @description %></p>
<% end %>
<p><%= raw @qr_code_svg %></p>
<% end %>
+24
View File
@@ -0,0 +1,24 @@
require "rqrcode"
class QrCodeModalComponent < ViewComponent::Base
def initialize(qr_content:, description: nil)
@description = description
@qr_code_svg = qr_code_svg(qr_content)
end
private
def qr_code_svg(content)
qr_code = RQRCode::QRCode.new(content)
qr_code.as_svg(
color: "000",
shape_rendering: "crispEdges",
module_size: 6,
standalone: true,
use_path: true,
svg_attributes: {
class: 'inline-block'
}
)
end
end
+26
View File
@@ -0,0 +1,26 @@
<div class="flex items-center gap-4">
<div class="h-16 w-16 flex-none">
<%= render AppCatalog::WebAppIconComponent.new(web_app: @web_app) %>
</div>
<div class="flex-grow">
<h4 class="mb-1 text-lg font-bold">
<%= @web_app&.name || @auth.app_name %>
</h4>
<p class="text-sm text-gray-500">
<%= @auth.client_id %>
</p>
</div>
<%= render DropdownComponent.new do %>
<%= render DropdownLinkComponent.new(
href: launch_app_services_storage_rs_auth_url(@auth)
) do %>
Launch app
<% end %>
<%= render DropdownLinkComponent.new(
href: revoke_services_storage_rs_auth_url(@auth),
separator: true, add_class: "text-red-700"
) do %>
Revoke access
<% end %>
<% end %>
</div>
+8
View File
@@ -0,0 +1,8 @@
# frozen_string_literal: true
class RsAuthComponent < ViewComponent::Base
def initialize(auth:)
@auth = auth
@web_app = auth.web_app
end
end
@@ -1,4 +1,8 @@
<%= link_to @path, class: @link_class, title: (@disabled ? "Coming soon" : nil) do %> <%= link_to @path, class: @link_class, title: (@disabled ? "Coming soon" : nil) do %>
<% if @icon.present? %>
<%= render partial: "icons/#{@icon}", locals: { custom_class: @icon_class } %> <%= render partial: "icons/#{@icon}", locals: { custom_class: @icon_class } %>
<% elsif @text_icon.present? %>
<span class="mr-3"><%= @text_icon %></span>
<% end %>
<span class="truncate"><%= @name %></span> <span class="truncate"><%= @name %></span>
<% end %> <% end %>
+3 -1
View File
@@ -1,11 +1,13 @@
# frozen_string_literal: true # frozen_string_literal: true
class SidenavLinkComponent < ViewComponent::Base class SidenavLinkComponent < ViewComponent::Base
def initialize(name:, level: 1, path:, icon:, active: false, disabled: false) def initialize(name:, level: 1, path:, icon: nil, text_icon: nil,
active: false, disabled: false)
@name = name @name = name
@level = level @level = level
@path = path @path = path
@icon = icon @icon = icon
@text_icon = text_icon
@active = active @active = active
@disabled = disabled @disabled = disabled
@link_class = class_names_link(path) @link_class = class_names_link(path)
@@ -0,0 +1,9 @@
class Admin::AppCatalog::WebAppsController < Admin::AppCatalogController
def index
@pagy, @web_apps = pagy(AppCatalog::WebApp.order('created_at desc'))
@stats = {
known_apps: AppCatalog::WebApp.count
}
end
end
@@ -0,0 +1,9 @@
class Admin::AppCatalogController < Admin::BaseController
before_action :set_current_section
private
def set_current_section
@current_section = :app_catalog
end
end
+29 -41
View File
@@ -3,18 +3,16 @@ class Admin::DonationsController < Admin::BaseController
before_action :set_current_section, only: [:index, :show, :new, :edit] before_action :set_current_section, only: [:index, :show, :new, :edit]
# GET /donations # GET /donations
# GET /donations.json
def index def index
@pagy, @donations = pagy(Donation.all.order('created_at desc')) @pagy, @donations = pagy(Donation.completed.order('paid_at desc'))
@stats = { @stats = {
overall_sats: @donations.all.sum("amount_sats"), overall_sats: @donations.sum("amount_sats"),
donor_count: Donation.distinct.count(:user_id) donor_count: Donation.completed.count(:user_id)
} }
end end
# GET /donations/1 # GET /donations/1
# GET /donations/1.json
def show def show
end end
@@ -28,54 +26,41 @@ class Admin::DonationsController < Admin::BaseController
end end
# POST /donations # POST /donations
# POST /donations.json
def create def create
@donation = Donation.new(donation_params) @donation = Donation.new(donation_params)
respond_to do |format| if @donation.paid_at == nil
if @donation.save @donation.errors.add(:paid_at, message: "is required")
format.html do render :new, status: :unprocessable_entity and return
redirect_to admin_donation_url(@donation), flash: { end
success: 'Donation was successfully created.'
} if @donation.save
end redirect_to admin_donation_url(@donation), flash: {
format.json { render :show, status: :created, location: @donation } success: 'Donation was successfully created.'
else }
format.html { render :new, status: :unprocessable_entity } else
format.json { render json: @donation.errors, status: :unprocessable_entity } render :new, status: :unprocessable_entity
end
end end
end end
# PATCH/PUT /donations/1 # PUT /donations/1
# PATCH/PUT /donations/1.json
def update def update
respond_to do |format| if @donation.update(donation_params)
if @donation.update(donation_params) redirect_to admin_donation_url(@donation), flash: {
format.html do success: 'Donation was successfully updated.'
redirect_to admin_donation_url(@donation), flash: { }
success: 'Donation was successfully updated.' else
} render :edit, status: :unprocessable_entity
end
format.json { render :show, status: :ok, location: @donation }
else
format.html { render :edit, status: :unprocessable_entity }
format.json { render json: @donation.errors, status: :unprocessable_entity }
end
end end
end end
# DELETE /donations/1 # DELETE /donations/1
# DELETE /donations/1.json
def destroy def destroy
@donation.destroy @donation.destroy
respond_to do |format|
format.html do redirect_to admin_donations_url, flash: { redirect_to admin_donations_url, flash: {
success: 'Donation was successfully destroyed.' success: 'Donation was successfully destroyed.'
} }
end
format.json { head :no_content }
end
end end
private private
@@ -86,7 +71,10 @@ class Admin::DonationsController < Admin::BaseController
# Only allow a list of trusted parameters through. # Only allow a list of trusted parameters through.
def donation_params def donation_params
params.require(:donation).permit(:user_id, :amount_sats, :amount_eur, :amount_usd, :public_name, :paid_at) params.require(:donation).permit(
:user_id, :donation_method,
:amount_sats, :fiat_amount, :fiat_currency,
:public_name, :paid_at)
end end
def set_current_section def set_current_section
@@ -4,7 +4,7 @@ class Admin::LightningController < Admin::BaseController
def index def index
@current_section = :lightning @current_section = :lightning
@users = User.pluck(:cn, :ou, :ln_account) @users = User.pluck(:cn, :ou, :lndhub_username)
@accounts = LndhubAccount.with_balances.order(balance: :desc).to_a @accounts = LndhubAccount.with_balances.order(balance: :desc).to_a
@ln = {} @ln = {}
@@ -1,12 +1,20 @@
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: {
success: "Settings saved" success: "Settings saved"
} }
end end
private
def setting_params
params.require(:setting).permit([
:reserved_usernames, default_services: []
])
end
end end
@@ -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: "discourse" }) 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
+14 -5
View File
@@ -9,22 +9,23 @@ class Admin::SettingsController < Admin::BaseController
changed_keys = [] changed_keys = []
setting_params.keys.each do |key| setting_params.keys.each do |key|
next if setting_params[key].nil? || next if clean_param(key).nil? ||
(Setting.send(key).to_s == setting_params[key].strip) (Setting.send(key).to_s == clean_param(key))
changed_keys.push(key) changed_keys.push(key)
setting = Setting.new(var: key) setting = Setting.new(var: key)
setting.value = setting_params[key].strip setting.value = clean_param(key)
unless setting.valid? unless setting.valid?
@errors.merge!(setting.errors) @errors.merge!(setting.errors)
end end
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|
Setting.send("#{key}=", setting_params[key].strip) Setting.send("#{key}=", clean_param(key))
end end
end end
@@ -37,4 +38,12 @@ class Admin::SettingsController < Admin::BaseController
def setting_params def setting_params
params.require(:setting).permit(Setting.editable_keys.map(&:to_sym)) params.require(:setting).permit(Setting.editable_keys.map(&:to_sym))
end end
def clean_param(key)
if Setting.get_field(key)[:type] == :string
setting_params[key].strip
else
setting_params[key]
end
end
end end
+32 -5
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,19 +14,46 @@ 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
end end
@services_enabled = @user.services_enabled @services_enabled = @user.services_enabled
@ldap_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])
UserManager::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
+37
View File
@@ -0,0 +1,37 @@
class Api::BtcpayController < Api::BaseController
before_action :require_feature_enabled
before_action :set_cors_access_control_headers
def onchain_btc_balance
balance = BtcpayManager::FetchOnchainWalletBalance.call
render json: balance
rescue => error
Rails.logger.warn "Failed to fetch BTC wallet balance: #{error.message}"
render json: { error: 'Failed to fetch wallet balance' },
status: 500
end
def lightning_btc_balance
balance = BtcpayManager::FetchLightningWalletBalance.call
render json: balance
rescue => error
Rails.logger.warn "Failed to fetch BTC lightning balance: #{error.message}"
render json: { error: 'Failed to fetch wallet balance' },
status: 500
end
private
def require_feature_enabled
unless Setting.btcpay_publish_wallet_balances
http_status :not_found and return
end
end
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
-13
View File
@@ -1,13 +0,0 @@
class Api::KreditsController < Api::BaseController
def onchain_btc_balance
btcpay = BtcPay.new
balance = btcpay.onchain_wallet_balance
render json: balance
rescue => error
Rails.logger.warn "Failed to fetch kredits BTC wallet balance: #{error.message}"
render json: { error: 'Failed to fetch wallet balance' },
status: 500
end
end
+31
View File
@@ -37,4 +37,35 @@ class ApplicationController < ActionController::Base
format.any { head status } format.any { head status }
end end
end end
def after_sign_in_path_for(user)
session[:user_return_to] || root_path
end
def lndhub_authenticate(options={})
if session[:ln_auth_token].present? && !options[:force_reauth]
@ln_auth_token = session[:ln_auth_token]
else
lndhub = Lndhub.new
auth_token = lndhub.authenticate(current_user)
session[:ln_auth_token] = auth_token
@ln_auth_token = auth_token
end
rescue => e
Sentry.capture_exception(e) if Setting.sentry_enabled?
end
def lndhub_fetch_balance
@balance = LndhubManager::FetchUserBalance.call(auth_token: @ln_auth_token)
rescue AuthError
lndhub_authenticate(force_reauth: true)
raise if @fetch_balance_retried
@fetch_balance_retried = true
lndhub_fetch_balance
end
def nostr_event_from_params
params.permit!
params[:signed_event].to_h.symbolize_keys
end
end end
+27
View File
@@ -0,0 +1,27 @@
class AvatarsController < ApplicationController
def show
if user = User.find_by(cn: params[:username])
http_status :not_found and return unless user.avatar.attached?
sha256_hash = params[:hash]
format = params[:format]&.to_sym || :png
# size = params[:size]&.to_sym || :original
unless user.avatar.filename.to_s == "#{sha256_hash}.#{format}"
http_status :not_found and return
end
# TODO See note for avatar_variant in user model
# blob = if size == :original
# user.avatar.blob
# else
# user.avatar_variant(size: size)&.blob
# end
data = user.avatar.blob.download
send_data data, type: "image/#{format}", disposition: "inline"
else
http_status :not_found
end
end
end
@@ -1,10 +1,129 @@
class Contributions::DonationsController < ApplicationController class Contributions::DonationsController < ApplicationController
before_action :authenticate_user! include BtcpayHelper
# GET /donations before_action :authenticate_user!
# GET /donations.json before_action :set_donation_methods, only: [:index, :create]
before_action :require_donation_method_enabled, only: [:create]
before_action :validate_donation_params, only: [:create]
before_action :set_donation, only: [:confirm_btcpay]
# GET /contributions/donations
def index def index
@donations = current_user.donations.completed
@current_section = :contributions @current_section = :contributions
@donations_completed = current_user.donations.completed.order('paid_at desc')
@donations_pending = current_user.donations.processing.order('created_at desc')
if Setting.lndhub_enabled?
begin
lndhub_authenticate
lndhub_fetch_balance
rescue
@balance = 0
end
end
end end
# POST /contributions/donations
def create
if params[:currency] == "sats"
fiat_amount = nil
fiat_currency = nil
amount_sats = params[:amount]
else
fiat_amount = params[:amount].to_i
fiat_currency = params[:currency]
amount_sats = nil
end
@donation = current_user.donations.create!(
donation_method: params[:donation_method],
payment_method: nil,
paid_at: nil,
amount_sats: amount_sats,
fiat_amount: (fiat_amount.nil? ? nil : fiat_amount * 100), # store in cents
fiat_currency: fiat_currency,
public_name: params[:public_name]
)
case params[:donation_method]
when "btcpay"
res = BtcpayManager::CreateInvoice.call(
amount: fiat_amount || (amount_sats.to_f / 100000000),
currency: fiat_currency || "BTC",
redirect_url: confirm_btcpay_contributions_donation_url(@donation)
)
@donation.update! btcpay_invoice_id: res["id"]
redirect_to btcpay_checkout_url(res["id"]), allow_other_host: true
else
redirect_to contributions_donations_url, flash: {
error: "Donation method currently not available"
}
end
end
def confirm_btcpay
redirect_to contributions_donations_url and return if @donation.completed?
invoice = BtcpayManager::FetchInvoice.call(invoice_id: @donation.btcpay_invoice_id)
if @donation.amount_sats.present?
# TODO make default fiat currency configurable and/or determine from user's
# i18n browser settings
@donation.fiat_currency = "EUR"
exchange_rate = BtcpayManager::FetchExchangeRate.call(fiat_currency: @donation.fiat_currency)
@donation.fiat_amount = (((@donation.amount_sats.to_f / 100000000) * exchange_rate) * 100).to_i
else
amt_str = invoice["paymentMethods"].first["amount"]
@donation.amount_sats = amt_str.tr(".","").sub(/0*$/, "").to_i
end
case invoice["status"]
when "Settled"
@donation.paid_at = DateTime.now
@donation.payment_status = "settled"
@donation.save!
flash_message = { success: "Thank you!" }
when "Processing"
unless @donation.processing?
@donation.payment_status = "processing"
@donation.save!
flash_message = { success: "Thank you! We will send you an email when the payment is confirmed." }
BtcpayCheckDonationJob.set(wait: 20.seconds).perform_later(@donation)
end
when "Expired"
flash_message = { warning: "The payment request for this donation has expired" }
else
flash_message = { warning: "Could not determine status of payment" }
end
redirect_to contributions_donations_url, flash: flash_message
end
private
def set_donation
@donation = current_user.donations.find_by(id: params[:id])
http_status :not_found unless @donation.present?
end
def set_donation_methods
@donation_methods = []
@donation_methods.push :btcpay if Setting.btcpay_enabled?
@donation_methods.push :lndhub if Setting.lndhub_enabled?
@donation_methods.push :opencollective if Setting.opencollective_enabled?
end
def require_donation_method_enabled
http_status :forbidden unless @donation_methods.include?(
params[:donation_method].to_sym
)
end
def validate_donation_params
if !%w[EUR USD sats].include?(params[:currency]) || (params[:amount].to_i <= 0)
http_status :unprocessable_entity
end
end
end end
@@ -8,6 +8,9 @@ class Discourse::SsoController < ApplicationController
sso.email = current_user.email sso.email = current_user.email
sso.username = current_user.cn sso.username = current_user.cn
sso.name = current_user.display_name sso.name = current_user.display_name
if current_user.avatar.attached?
sso.avatar_url = helpers.image_url_for(current_user.avatar)
end
sso.admin = current_user.is_admin? sso.admin = current_user.is_admin?
sso.sso_secret = secret sso.sso_secret = secret
+112 -46
View File
@@ -1,23 +1,33 @@
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
before_action :set_cors_access_control_headers
MIN_SATS = 10 MIN_SATS = 10
MAX_SATS = 1_000_000 MAX_SATS = 1_000_000
MAX_COMMENT_CHARS = 100 MAX_COMMENT_CHARS = 100
# GET /.well-known/lnurlp/:username
def index def index
render json: { res = {
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
metadata: metadata(@user.address), metadata: metadata(@user.address),
commentAllowed: MAX_COMMENT_CHARS commentAllowed: MAX_COMMENT_CHARS
} }
if Setting.nostr_enabled?
res[:allowsNostr] = true
res[:nostrPubkey] = Setting.nostr_public_key
end
render json: res
end end
# GET /.well-known/keysend/:username
def keysend def keysend
http_status :not_found and return unless Setting.lndhub_keysend_enabled? http_status :not_found and return unless Setting.lndhub_keysend_enabled?
@@ -27,69 +37,125 @@ class LnurlpayController < ApplicationController
pubkey: Setting.lndhub_public_key, pubkey: Setting.lndhub_public_key,
customData: [{ customData: [{
customKey: "696969", customKey: "696969",
customValue: @user.ln_account customValue: @user.lndhub_username
}] }]
} }
end end
# GET /lnurlpay/:username/invoice
def invoice def invoice
amount = params[:amount].to_i / 1000 # msats amount = params[:amount].to_i / 1000 # msats to sats
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" }
return return
end end
if !valid_comment?(comment) if params[:nostr].present? && Setting.nostr_enabled?
render json: { status: "ERROR", reason: "Comment too long" } handle_zap_request amount, params[:nostr], params[:lnurl]
return else
handle_pay_request address, amount, comment
end
end
private
def set_cors_access_control_headers
headers['Access-Control-Allow-Origin'] = "*"
headers['Access-Control-Allow-Headers'] = "*"
headers['Access-Control-Allow-Methods'] = "GET"
end end
memo = "To #{address}" def check_service_available
memo = "#{memo}: \"#{comment}\"" if comment.present? http_status :not_found unless Setting.lndhub_enabled?
end
payment_request = @user.ln_create_invoice({ def find_user
amount: amount, # we create invoices in sats @user = User.where(cn: params[:username], ou: Setting.primary_domain).first
memo: memo, http_status :not_found if @user.nil?
description_hash: Digest::SHA2.hexdigest(metadata(address)), end
})
render json: { def metadata(address)
status: "OK", "[[\"text/identifier\",\"#{address}\"],[\"text/plain\",\"Sats for #{address}\"]]"
successAction: { end
tag: "message",
message: "Sats received. Thank you!"
},
routes: [],
pr: payment_request
}
end
private def valid_amount?(amount_in_sats)
amount_in_sats <= MAX_SATS && amount_in_sats >= MIN_SATS
end
def find_user_by_address def valid_comment?(comment)
address = params[:address].split("@") comment.length <= MAX_COMMENT_CHARS
@user = User.where(cn: address.first, ou: address.last).first end
http_status :not_found if @user.nil?
end
def metadata(address) def handle_pay_request(address, amount, comment)
"[[\"text/identifier\", \"#{address}\"], [\"text/plain\", \"Send sats, receive thanks.\"]]" if !valid_comment?(comment)
end render json: { status: "ERROR", reason: "Comment too long" }
return
end
def valid_amount?(amount_in_sats) desc = "To #{address}"
amount_in_sats <= MAX_SATS && amount_in_sats >= MIN_SATS desc = "#{desc}: \"#{comment}\"" if comment.present?
end
def valid_comment?(comment) invoice = LndhubManager::CreateUserInvoice.call(
comment.length <= MAX_COMMENT_CHARS user: @user, payload: {
end amount: amount, # sats
description: desc,
description_hash: Digest::SHA256.hexdigest(metadata(address)),
}
)
private render json: {
status: "OK",
successAction: {
tag: "message",
message: "Sats received. Thank you!"
},
routes: [],
pr: invoice["payment_request"]
}
end
def check_feature_enabled def nostr_event_from_payload(nostr_param)
http_status :not_found unless Setting.lndhub_enabled? event_obj = JSON.parse(nostr_param).transform_keys(&:to_sym)
end Nostr::Event.new(**event_obj)
rescue => e
return nil
end
def valid_zap_request?(amount, event, lnurl)
NostrManager::VerifyZapRequest.call(
amount: amount, event: event, lnurl: lnurl
)
end
def handle_zap_request(amount, nostr_param, lnurl_param)
event = nostr_event_from_payload(nostr_param)
unless event.present? && valid_zap_request?(amount*1000, event, lnurl_param)
render json: { status: "ERROR", reason: "Invalid zap request" }
return
end
# TODO might want to use the existing invoice and zap record if there are
# multiple calls with the same zap request
desc = "Zap for #{@user.address}"
desc = "#{desc}: \"#{event.content}\"" if event.content.present?
invoice = LndhubManager::CreateUserInvoice.call(
user: @user, payload: {
amount: amount, # sats
description: desc,
description_hash: Digest::SHA256.hexdigest(event.to_json),
}
)
@user.zaps.create! request: event,
payment_request: invoice["payment_request"],
amount: amount
render json: { status: "OK", pr: invoice["payment_request"] }
end
end end
+131
View File
@@ -0,0 +1,131 @@
class Rs::OauthController < ApplicationController
before_action :require_signed_in_with_username, only: :new
before_action :authenticate_user!, only: :create
def new
@user = User.where(cn: params[:username].downcase, ou: Setting.primary_domain).first
@scopes = parse_scopes params[:scope]
@redirect_uri = params[:redirect_uri]
@client_id = params[:client_id]
@state = params[:state]
@root_access_requested = (@scopes & [":r",":rw"]).any?
@denial_url = url_with_state("#{@redirect_uri}#error=access_denied", @state)
@expire_at_dates = [["Never", nil],
["In 1 month", 1.month.from_now],
["In 1 day", 1.day.from_now]]
http_status :bad_request and return unless @redirect_uri.present?
unless current_user == @user
sign_out :user
redirect_to new_rs_oauth_url(@user.cn,
scope: params[:scope],
redirect_uri: params[:redirect_uri],
client_id: params[:client_id],
state: params[:state])
return
end
unless @client_id.present?
redirect_to(url_with_state("#{@redirect_uri}#error=invalid_request", @state),
allow_other_host: true) and return
end
if @scopes.empty?
redirect_to(url_with_state("#{@redirect_uri}#error=invalid_scope", @state),
allow_other_host: true) and return
end
unless hostname_of(@client_id) == hostname_of(@redirect_uri)
redirect_to(url_with_state("#{@redirect_uri}#error=invalid_client", @state),
allow_other_host: true) and return
end
@client_id.gsub!(/http(s)?:\/\//, "")
if auth = current_user.remote_storage_authorizations.valid.where(permissions: @scopes, client_id: @client_id).first
redirect_to(url_with_state("#{@redirect_uri}#access_token=#{auth.token}", @state),
allow_other_host: true) and return
end
end
def create
unless current_user.id.to_s == params[:user_id]
Rails.logger.info("NO MATCH: #{params[:user_id]}, #{current_user.id}")
http_status :forbidden and return
end
permissions = parse_scopes params[:scope]
redirect_uri = params[:redirect_uri].presence
client_id = params[:client_id].presence
state = params[:state].presence
expire_at = params[:expire_at].presence
http_status :bad_request and return unless redirect_uri.present?
if permissions.empty?
redirect_to(url_with_state("#{redirect_uri}#error=invalid_scope", state),
allow_other_host: true) and return
end
unless client_id.present?
redirect_to(url_with_state("#{redirect_uri}#error=invalid_request", state),
allow_other_host: true) and return
end
unless hostname_of(client_id) == hostname_of(redirect_uri)
redirect_to(url_with_state("#{redirect_uri}#error=invalid_client", state),
allow_other_host: true) and return
end
client_id.gsub!(/http(s)?:\/\//, "")
auth = current_user.remote_storage_authorizations.create!(
permissions: permissions,
client_id: client_id,
redirect_uri: redirect_uri,
app_name: client_id,
expire_at: expire_at
)
redirect_to url_with_state("#{redirect_uri}#access_token=#{auth.token}", state),
allow_other_host: true
end
private
def require_signed_in_with_username
unless user_signed_in?
session[:user_return_to] = request.url
redirect_to new_user_session_path(cn: params[:username], ou: Setting.primary_domain)
end
end
def hostname_of(uri)
uri.gsub(/http(s)?:\/\//, "").split(":")[0].split("/")[0]
end
def parse_scopes(scope_string)
return [] if scope_string.blank?
scopes = scope_string.
gsub(/\[|\]/, "").
gsub(/\,/, " ").
gsub(/\/:/, ":").
split(/\s/).map(&:strip).
reject(&:empty?)
scopes = [":r"] if scopes.include?("*:r")
scopes = [":rw"] if scopes.include?("*:rw")
scopes
end
def url_with_state(url, state)
state ? "#{url}&state=#{CGI.escape(state)}" : url
end
end
@@ -0,0 +1,9 @@
class Services::BaseController < ApplicationController
before_action :set_current_section
private
def set_current_section
@current_section = :services
end
end
@@ -0,0 +1,14 @@
class Services::ChatController < Services::BaseController
before_action :authenticate_user!
before_action :require_service_available
def show
@service_enabled = current_user.service_enabled?(:ejabberd)
end
private
def require_service_available
http_status :not_found unless Setting.ejabberd_enabled?
end
end
@@ -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
@@ -2,14 +2,14 @@ require "rqrcode"
require "lnurl" require "lnurl"
class Services::LightningController < ApplicationController class Services::LightningController < ApplicationController
before_action :authenticate_user!
before_action :authenticate_with_lndhub
before_action :set_current_section before_action :set_current_section
before_action :fetch_balance before_action :require_service_available
before_action :authenticate_user!
before_action :lndhub_authenticate
before_action :lndhub_fetch_balance
def index def index
@wallet_url = "lndhub://#{current_user.ln_account}:#{current_user.ln_password}@#{ENV['LNDHUB_PUBLIC_URL']}" @wallet_setup_url = "lndhub://#{current_user.lndhub_username}:#{current_user.lndhub_password}@#{ENV['LNDHUB_PUBLIC_URL']}"
initialize_lndhub_qr_code
end end
def transactions def transactions
@@ -56,46 +56,12 @@ class Services::LightningController < ApplicationController
private private
def initialize_lndhub_qr_code
qr_code = RQRCode::QRCode.new(@wallet_url)
@lndhub_qr_svg = qr_code.as_svg(
color: "000",
shape_rendering: "crispEdges",
module_size: 6,
standalone: true,
use_path: true,
svg_attributes: {
class: 'inline-block'
}
)
end
def authenticate_with_lndhub(options={})
if session[:ln_auth_token].present? && !options[:force_reauth]
@ln_auth_token = session[:ln_auth_token]
else
lndhub = Lndhub.new
auth_token = lndhub.authenticate(current_user)
session[:ln_auth_token] = auth_token
@ln_auth_token = auth_token
end
rescue => e
Sentry.capture_exception(e) if Setting.sentry_enabled?
end
def set_current_section def set_current_section
@current_section = :services @current_section = :services
end end
def fetch_balance def require_service_available
lndhub = Lndhub.new http_status :not_found unless Setting.lndhub_enabled?
data = lndhub.balance @ln_auth_token
@balance = data["BTC"]["AvailableBalance"] rescue nil
rescue AuthError
authenticate_with_lndhub(force_reauth: true)
raise if @fetch_balance_retried
@fetch_balance_retried = true
fetch_balance
end end
def fetch_transactions def fetch_transactions
@@ -0,0 +1,14 @@
class Services::MastodonController < Services::BaseController
before_action :authenticate_user!
before_action :require_service_available
def show
@service_enabled = current_user.service_enabled?(:mastodon)
end
private
def require_service_available
http_status :not_found unless Setting.mastodon_enabled?
end
end
@@ -1,30 +1,25 @@
class Services::RemotestorageController < ApplicationController class Services::RemotestorageController < Services::BaseController
before_action :require_user_signed_in before_action :authenticate_user!
before_action :require_service_enabled before_action :require_service_available
before_action :require_feature_enabled before_action :require_feature_enabled
before_action :set_current_section
def dashboard # Dashboard
# unless current_user.services_enabled.include?(:remotestorage) def show
# unless current_user.service_enabled?(:remotestorage)
# redirect_to service_remotestorage_info_path # redirect_to service_remotestorage_info_path
# end # end
# @rs_apps_connected = current_user.remote_storage_authorizations.any?
end end
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_enabled
unless Setting.remotestorage_enabled?
http_status :not_found
end
end
def set_current_section
@current_section = :services
end
end end
@@ -0,0 +1,47 @@
class Services::RsAuthsController < Services::BaseController
before_action :authenticate_user!
before_action :require_feature_enabled
before_action :require_service_available
# before_action :require_service_enabled
before_action :find_rs_auth, only: [:destroy, :launch_app]
def index
@rs_auths = current_user.remote_storage_authorizations
# TODO sort by app name?
end
def destroy
@auth.destroy!
respond_to do |format|
format.html do redirect_to apps_services_storage_url, flash: {
success: 'App authorization revoked'
}
end
format.json { head :no_content }
end
end
def launch_app
launch_url = "#{@auth.launch_url}#remotestorage=#{current_user.address}&access_token=#{@auth.token}"
redirect_to launch_url, allow_other_host: true
end
private
def require_feature_enabled
unless Flipper.enabled?(:remotestorage, current_user)
http_status :forbidden
end
end
def require_service_available
http_status :not_found unless Setting.remotestorage_enabled?
end
def find_rs_auth
@auth = current_user.remote_storage_authorizations.find(params[:id])
http_status :not_found unless @auth.present?
end
end
+124 -31
View File
@@ -1,28 +1,49 @@
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)
end end
def show def show
if @settings_section == "experiments" case @settings_section
when "lightning"
@notifications_enabled = @user.preferences[:lightning_notify_sats_received] != "disabled" ||
@user.preferences[:lightning_notify_zap_received] != "disabled"
when "nostr"
session[:shared_secret] ||= SecureRandom.base64(12) session[:shared_secret] ||= SecureRandom.base64(12)
end end
end end
# PUT /settings/:section
def update def update
@user.preferences.merge!(user_params[:preferences] || {}) @user.preferences.merge!(user_params[:preferences] || {})
@user.display_name = user_params[:display_name] @user.display_name = user_params[:display_name]
@user.avatar_new = user_params[:avatar_new]
@user.pgp_pubkey = user_params[:pgp_pubkey]
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_params[:display_name]) LdapManager::UpdateDisplayName.call(dn: @user.dn, display_name: @user.display_name)
end
if @user.avatar_new.present?
if store_user_avatar
LdapManager::UpdateAvatar.call(user: @user)
else
@validation_errors = @user.errors
render :show, status: :unprocessable_entity and return
end
end
if @user.pgp_pubkey && (@user.pgp_pubkey != @user.ldap_entry[:pgp_key])
UserManager::UpdatePgpKey.call(user: @user)
end end
redirect_to setting_path(@settings_section), flash: { redirect_to setting_path(@settings_section), flash: {
@@ -34,8 +55,9 @@ class SettingsController < ApplicationController
end end
end end
# POST /settings/update_email
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.'
@@ -51,6 +73,30 @@ class SettingsController < ApplicationController
end end
end end
# POST /settings/reset_email_password
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
# POST /settings/reset_password
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
@@ -58,41 +104,41 @@ class SettingsController < ApplicationController
redirect_to check_your_email_path, notice: msg redirect_to check_your_email_path, notice: msg
end end
# POST /settings/set_nostr_pubkey
def set_nostr_pubkey def set_nostr_pubkey
signed_event = nostr_event_params[:signed_event].to_h.symbolize_keys signed_event = Nostr::Event.new(**nostr_event_from_params)
is_valid_id = NostrManager::ValidateId.call(signed_event)
is_valid_sig = NostrManager::VerifySignature.call(signed_event)
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 is_valid_sig = signed_event.verify_signature
is_valid_auth = NostrManager::VerifyAuth.call(
event: signed_event,
challenge: session[:shared_secret]
)
unless is_valid_sig && is_valid_auth
flash[:alert] = "Public key could not be verified" flash[:alert] = "Public key could not be verified"
http_status :unprocessable_entity and return http_status :unprocessable_entity and return
end end
pubkey_taken = User.all_except(current_user).where( user_with_pubkey = LdapManager::FetchUserByNostrKey.call(pubkey: signed_event.pubkey)
ou: current_user.ou, nostr_pubkey: signed_event[:pubkey]
).any?
if pubkey_taken if user_with_pubkey.present? && (user_with_pubkey != current_user)
flash[:alert] = "Public key already in use for a different account" flash[:alert] = "Public key already in use for a different account"
http_status :unprocessable_entity and return http_status :unprocessable_entity and return
end end
current_user.update! nostr_pubkey: signed_event[:pubkey] LdapManager::UpdateNostrKey.call(dn: current_user.dn, pubkey: signed_event.pubkey)
session[:shared_secret] = nil session[:shared_secret] = nil
flash[:success] = "Public key verification successful" flash[:success] = "Public key verification successful"
http_status :ok http_status :ok
rescue
flash[:alert] = "Public key could not be verified"
http_status :unprocessable_entity and return
end end
# DELETE /settings/nostr_pubkey # DELETE /settings/nostr_pubkey
def remove_nostr_pubkey def remove_nostr_pubkey
current_user.update! nostr_pubkey: nil # TODO require current pubkey or password to delete
LdapManager::UpdateNostrKey.call(dn: current_user.dn, pubkey: nil)
redirect_to setting_path(:experiments), flash: { redirect_to setting_path(:nostr), flash: {
success: 'Public key removed from account' success: 'Public key removed from account'
} }
end end
@@ -105,7 +151,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, :nostr
]
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)
@@ -117,19 +166,63 @@ class SettingsController < ApplicationController
end end
def user_params def user_params
params.require(:user).permit(:display_name, preferences: [ params.require(:user).permit(
:lightning_notify_sats_received, :display_name, :avatar_new, :pgp_pubkey,
:xmpp_exchange_contacts_with_invitees preferences: UserPreferences.pref_keys
]) )
end end
def email_params def email_params
params.require(:user).permit(:email, :current_password) params.require(:user).permit(:email)
end end
def nostr_event_params def security_params
params.permit(signed_event: [ params.require(:user).permit(:current_password)
:id, :pubkey, :created_at, :kind, :tags, :content, :sig 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
def store_user_avatar
io = @user.avatar_new.tempfile
img_data = process_avatar(io)
tempfile = Tempfile.create
tempfile.binmode
tempfile.write(img_data)
tempfile.rewind
hash = Digest::SHA256.hexdigest(img_data)
ext = @user.avatar_new.content_type == "image/png" ? "png" : "jpg"
filename = "#{hash}.#{ext}"
if filename == @user.avatar.filename.to_s
@user.errors.add(:avatar, "must be a new file/picture")
false
else
key = "users/#{@user.cn}/avatars/#{filename}"
@user.avatar.attach io: tempfile, key: key, filename: filename
@user.save
end
end
def process_avatar(io)
processed = ImageProcessing::Vips
.source(io)
.resize_to_fill(400, 400)
.saver(strip: true)
.call
io.rewind
processed.read
rescue Vips::Error => e
Sentry.capture_exception(e) if Setting.sentry_enabled?
Rails.logger.error { "Image processing failed for avatar: #{e.message}" }
nil
end end
end end
+2 -2
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( UserManager::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
@@ -0,0 +1,62 @@
# frozen_string_literal: true
class Users::SessionsController < Devise::SessionsController
# before_action :configure_sign_in_params, only: [:create]
# GET /resource/sign_in
def new
session[:shared_secret] = SecureRandom.base64(12)
super
end
# POST /resource/sign_in
# def create
# super
# end
# DELETE /resource/sign_out
# def destroy
# super
# end
# POST /users/nostr_login
def nostr_login
signed_event = Nostr::Event.new(**nostr_event_from_params)
is_valid_sig = signed_event.verify_signature
is_valid_auth = NostrManager::VerifyAuth.call(
event: signed_event,
challenge: session[:shared_secret]
)
session[:shared_secret] = nil
unless is_valid_sig && is_valid_auth
flash[:alert] = "Login verification failed"
http_status :unauthorized and return
end
user = LdapManager::FetchUserByNostrKey.call(pubkey: signed_event.pubkey)
if user.present?
set_flash_message!(:notice, :signed_in)
sign_in("user", user)
render json: { redirect_url: after_sign_in_path_for(user) }, status: :ok
else
flash[:alert] = "Failed to find your account. Nostr login may be disabled."
http_status :unauthorized
end
end
protected
def set_flash_message(key, kind, options = {})
# Hide flash message after redirecting from a signin route while logged in
super unless key == :alert && kind == "already_authenticated"
end
# If you have extra params to permit, append them to the sanitizer.
# def configure_sign_in_params
# devise_parameter_sanitizer.permit(:sign_in, keys: [:attribute])
# end
end
@@ -0,0 +1,35 @@
class WebKeyDirectoryController < WellKnownController
before_action :allow_cross_origin_requests
# /.well-known/openpgpkey/hu/:hashed_username(.txt)
def show
@user = User.find_by(cn: params[:l].downcase)
if @user.nil? ||
@user.pgp_pubkey.blank? ||
!@user.pgp_pubkey_contains_user_address?
http_status :not_found and return
end
if params[:hashed_username] != @user.wkd_hash
http_status :unprocessable_entity and return
end
respond_to do |format|
format.text do
response.headers['Content-Type'] = 'text/plain'
render plain: @user.pgp_pubkey
end
format.any do
key = @user.gnupg_key.export
send_data key, filename: "#{@user.wkd_hash}.pem",
type: "application/octet-stream"
end
end
end
def policy
head :ok
end
end
+75 -26
View File
@@ -1,20 +1,23 @@
class WebfingerController < ApplicationController class WebfingerController < WellKnownController
before_action :allow_cross_origin_requests, only: [:show] before_action :allow_cross_origin_requests, only: [:show]
layout false
def show def show
resource = params[:resource] resource = params[:resource]
if resource && resource.match(/acct:\w+/) if resource && @useraddress = resource.match(/acct:(.+)/)&.[](1)
useraddress = resource.split(":").last @username, @domain = @useraddress.split("@")
username, org = useraddress.split("@")
username.downcase! unless Rails.env.development?
unless User.where(cn: username, ou: org).any? # Allow different domains (e.g. localhost:3000) in development only
head 404 and return unless @domain == Setting.primary_domain
end
unless @user = User.where(ou: Setting.primary_domain)
.find_by(cn: @username.downcase)
head 404 and return head 404 and return
end end
render json: webfinger(useraddress).to_json, render json: webfinger.to_json,
content_type: "application/jrd+json" content_type: "application/jrd+json"
else else
head 422 and return head 422 and return
@@ -23,24 +26,75 @@ class WebfingerController < ApplicationController
private private
def webfinger(useraddress) def webfinger
links = []; jrd = {
subject: "acct:#{@user.address}",
aliases: [],
links: []
}
links << remotestorage_link(useraddress) if Setting.remotestorage_enabled if @user.avatar.attached?
jrd[:links] += avatar_link
end
{ "links" => links } if Setting.mastodon_enabled && @user.service_enabled?(:mastodon)
# https://docs.joinmastodon.org/spec/webfinger/
jrd[:aliases] += mastodon_aliases
jrd[:links] += mastodon_links
end
if Setting.remotestorage_enabled && @user.service_enabled?(:remotestorage)
# https://datatracker.ietf.org/doc/draft-dejong-remotestorage/
jrd[:links] << remotestorage_link
end
jrd
end end
def remotestorage_link(useraddress) def avatar_link
# TODO use when OAuth routes are available [
# auth_url = new_rs_oauth_url(useraddress) {
auth_url = "https://example.com/rs/oauth" rel: "http://webfinger.net/rel/avatar",
storage_url = "#{Setting.rs_storage_url}/#{useraddress}" type: @user.avatar.content_type,
href: helpers.image_url_for(@user.avatar)
}
]
end
def mastodon_aliases
[
"#{Setting.mastodon_public_url}/@#{@user.cn}",
"#{Setting.mastodon_public_url}/users/#{@user.cn}"
]
end
def mastodon_links
[
{
rel: "http://webfinger.net/rel/profile-page",
type: "text/html",
href: "#{Setting.mastodon_public_url}/@#{@user.cn}"
},
{
rel: "self",
type: "application/activity+json",
href: "#{Setting.mastodon_public_url}/users/#{@user.cn}"
},
{
rel: "http://ostatus.org/schema/1.0/subscribe",
template: "#{Setting.mastodon_public_url}/authorize_interaction?uri={uri}"
}
]
end
def remotestorage_link
auth_url = new_rs_oauth_url(@username, host: Setting.rs_accounts_domain)
storage_url = "#{Setting.rs_storage_url}/#{@username}"
{ {
"rel" => "http://tools.ietf.org/id/draft-dejong-remotestorage", rel: "http://tools.ietf.org/id/draft-dejong-remotestorage",
"href" => storage_url, href: storage_url,
"properties" => { properties: {
"http://remotestorage.io/spec/version" => "draft-dejong-remotestorage-13", "http://remotestorage.io/spec/version" => "draft-dejong-remotestorage-13",
"http://tools.ietf.org/html/rfc6749#section-4.2" => auth_url, "http://tools.ietf.org/html/rfc6749#section-4.2" => auth_url,
"http://tools.ietf.org/html/rfc6750#section-2.3" => nil, # access token via a HTTP query parameter "http://tools.ietf.org/html/rfc6750#section-2.3" => nil, # access token via a HTTP query parameter
@@ -49,9 +103,4 @@ class WebfingerController < ApplicationController
} }
} }
end end
def allow_cross_origin_requests
headers['Access-Control-Allow-Origin'] = '*'
headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, OPTIONS'
end
end end
+57 -26
View File
@@ -2,45 +2,76 @@ class WebhooksController < ApplicationController
skip_forgery_protection skip_forgery_protection
before_action :authorize_request before_action :authorize_request
before_action :process_payload
def lndhub def lndhub
begin @user = User.find_by!(lndhub_username: @payload[:user_login])
payload = JSON.parse(request.body.read, symbolize_names: true)
head :no_content and return unless payload[:type] == "incoming" if @zap = @user.zaps.find_by(payment_request: @payload[:payment_request])
rescue settled_at = Time.parse(@payload[:settled_at])
head :unprocessable_entity and return zap_receipt = NostrManager::CreateZapReceipt.call(
zap: @zap,
paid_at: settled_at.to_i,
preimage: @payload[:preimage]
)
@zap.update! settled_at: settled_at, receipt: zap_receipt.to_h
NostrManager::PublishZapReceipt.call(zap: @zap)
end end
user = User.find_by!(ln_account: payload[:user_login]) send_notifications
notify = user.preferences[:lightning_notify_sats_received]
case notify
when "xmpp"
notify_xmpp(user.address, payload[:amount], payload[:memo])
when "email"
NotificationMailer.with(user: user, amount_sats: payload[:amount])
.lightning_sats_received.deliver_later
end
head :ok head :ok
end end
private private
# TODO refactor into mailer-like generic class/service
def notify_xmpp(address, amt_sats, memo)
payload = {
type: "normal",
from: Setting.primary_domain,
to: address,
subject: "Sats received!",
body: "#{helpers.number_with_delimiter amt_sats} sats received in your Lightning wallet:\n> #{memo}"
}
XmppSendMessageJob.perform_later(payload)
end
def authorize_request def authorize_request
if !ENV['WEBHOOKS_ALLOWED_IPS'].split(',').include?(request.remote_ip) if !ENV['WEBHOOKS_ALLOWED_IPS'].split(',').include?(request.remote_ip)
head :forbidden and return head :forbidden and return
end end
end end
def process_payload
@payload = JSON.parse(request.body.read, symbolize_names: true)
unless @payload[:type] == "incoming" &&
@payload[:state] == "settled"
head :no_content and return
end
rescue
head :unprocessable_entity and return
end
def send_notifications
return if @payload[:amount] < @user.preferences[:lightning_notify_min_sats]
if @user.preferences[:lightning_notify_only_with_message]
return if @payload[:memo].blank?
end
target = @zap.present? ? @user.preferences[:lightning_notify_zap_received] :
@user.preferences[:lightning_notify_sats_received]
case target
when "xmpp"
notify_xmpp
when "email"
notify_email
end
end
# TODO refactor into mailer-like generic class/service
def notify_xmpp
XmppSendMessageJob.perform_later({
type: "normal",
from: Setting.xmpp_notifications_from_address,
to: @user.address,
subject: "Sats received!",
body: "#{helpers.number_with_delimiter @payload[:amount]} sats received in your Lightning wallet:\n> #{@payload[:memo]}"
})
end
def notify_email
NotificationMailer.with(user: @user, amount_sats: @payload[:amount])
.lightning_sats_received.deliver_later
end
end end
+36 -5
View File
@@ -1,16 +1,47 @@
class WellKnownController < ApplicationController class WellKnownController < ApplicationController
before_action :require_nostr_enabled, only: [ :nostr ]
before_action :allow_cross_origin_requests, only: [ :nostr ]
layout false
def nostr def nostr
http_status :unprocessable_entity and return if params[:name].blank? http_status :unprocessable_entity and return if params[:name].blank?
domain = request.headers["X-Forwarded-Host"].presence || Setting.primary_domain domain = request.headers["X-Forwarded-Host"].presence || Setting.primary_domain
@user = User.where(cn: params[:name], ou: domain).first relay_url = Setting.nostr_relay_url.presence
http_status :not_found and return if @user.nil? || @user.nostr_pubkey.blank?
if params[:name] == "_"
if domain == Setting.primary_domain
# pubkey for the primary domain without a username (e.g. kosmos.org)
res = { names: { "_": Setting.nostr_public_key_primary_domain.presence || Setting.nostr_public_key } }
else
# pubkey for the akkounts domain without a username (e.g. accounts.kosmos.org)
res = { names: { "_": Setting.nostr_public_key } }
end
res[:relays] = { "_" => [ relay_url ] } if relay_url
else
@user = User.where(cn: params[:name], ou: domain).first
http_status :not_found and return if @user.nil? || @user.nostr_pubkey.blank?
res = { names: { @user.cn => @user.nostr_pubkey } }
res[:relays] = { @user.nostr_pubkey => [ relay_url ] } if relay_url
end
respond_to do |format| respond_to do |format|
format.json do format.json do
render json: { render json: res.to_json
names: { "#{@user.cn}": @user.nostr_pubkey }
}.to_json
end end
end end
end end
private
def require_nostr_enabled
http_status :not_found unless Setting.nostr_enabled?
end
def allow_cross_origin_requests
headers['Access-Control-Allow-Origin'] = "*"
headers['Access-Control-Allow-Methods'] = "GET"
end
end end
+15 -4
View File
@@ -1,10 +1,6 @@
module ApplicationHelper module ApplicationHelper
include Pagy::Frontend include Pagy::Frontend
def sats_to_btc(sats)
sats.to_f / 100000000
end
def main_nav_class(current_section, link_to_section) def main_nav_class(current_section, link_to_section)
if current_section == link_to_section if current_section == link_to_section
"bg-gray-900/50 text-white px-3 py-2 rounded-md font-medium text-base md:text-sm block md:inline-block" "bg-gray-900/50 text-white px-3 py-2 rounded-md font-medium text-base md:text-sm block md:inline-block"
@@ -18,4 +14,19 @@ module ApplicationHelper
def badge(text, color) def badge(text, color)
tag.span text, class: "inline-flex items-center rounded-full bg-#{color}-100 px-2.5 py-0.5 text-xs font-medium text-#{color}-800" tag.span text, class: "inline-flex items-center rounded-full bg-#{color}-100 px-2.5 py-0.5 text-xs font-medium text-#{color}-800"
end end
def image_url_for(attachment)
return s3_image_url(attachment) if Setting.s3_enabled?
if attachment.record.is_a?(User) && attachment.name == "avatar"
hash, format = attachment.blob.filename.to_s.split(".", 2)
user_avatar_url(
username: attachment.record.cn,
hash: hash,
format: format
)
else
Rails.application.routes.url_helpers.rails_blob_path(attachment, only_path: true)
end
end
end end
+7
View File
@@ -0,0 +1,7 @@
module BtcpayHelper
def btcpay_checkout_url(invoice_id)
"#{Setting.btcpay_public_url}/i/#{invoice_id}"
end
end
-2
View File
@@ -1,2 +0,0 @@
module DashboardHelper
end
-2
View File
@@ -1,2 +0,0 @@
module DonationsHelper
end
-2
View File
@@ -1,2 +0,0 @@
module InvitationsHelper
end
-2
View File
@@ -1,2 +0,0 @@
module LnurlpayHelper
end
+11
View File
@@ -0,0 +1,11 @@
module OauthHelper
def scope_name(scope)
scope.gsub(/(\:.+)/, '')
end
def scope_permissions(scope)
scope.match(/\:r$/) ? "r" : "rw"
end
end
+12
View File
@@ -0,0 +1,12 @@
module ServicesHelper
def service_human_name(key, category = :external)
SERVICES[category][key][:name] || key.to_s
end
def service_display_name(key, category = :external)
SERVICES[category][key][:display_name] ||
service_human_name(key, category)
end
end
-2
View File
@@ -1,2 +0,0 @@
module SettingsHelper
end
-2
View File
@@ -1,2 +0,0 @@
module SignupHelper
end
-2
View File
@@ -1,2 +0,0 @@
module UsersHelper
end
-2
View File
@@ -1,2 +0,0 @@
module WalletHelper
end
-2
View File
@@ -1,2 +0,0 @@
module WelcomeHelper
end
@@ -1,7 +1,12 @@
import { Application } from "@hotwired/stimulus" import { Application } from "@hotwired/stimulus"
import { Dropdown, Modal, Tabs } from "tailwindcss-stimulus-components"
const application = Application.start() const application = Application.start()
application.register('dropdown', Dropdown)
application.register('modal', Modal)
application.register('tabs', Tabs)
// Configure Stimulus development experience // Configure Stimulus development experience
application.debug = false application.debug = false
window.Stimulus = application window.Stimulus = application
@@ -0,0 +1,53 @@
import { Controller } from "@hotwired/stimulus"
// Connects to data-controller="nostr-login"
export default class extends Controller {
static targets = [ "loginForm", "loginButton" ]
static values = { site: String, sharedSecret: String }
connect() {
if (window.nostr) {
this.loginButtonTarget.disabled = false
this.loginFormTarget.classList.remove("hidden")
}
}
async login () {
this.loginButtonTarget.disabled = true
try {
// Auth based on NIP-42
const signedEvent = await window.nostr.signEvent({
created_at: Math.floor(Date.now() / 1000),
kind: 22242,
tags: [
["site", this.siteValue],
["challenge", this.sharedSecretValue]
],
content: ""
})
const res = await fetch("/users/nostr_login", {
method: "POST", credentials: "include", headers: {
"Accept": "application/json", 'Content-Type': 'application/json',
"X-CSRF-Token": this.csrfToken
}, body: JSON.stringify({ signed_event: signedEvent })
})
if (res.status === 200) {
res.json().then(r => { window.location.href = r.redirect_url })
} else {
window.location.reload()
}
} catch (error) {
console.warn('Unable to authenticate:', error.message)
} finally {
this.loginButtonTarget.disabled = false
}
}
get csrfToken () {
const element = document.head.querySelector('meta[name="csrf-token"]')
return element.getAttribute("content")
}
}
@@ -0,0 +1,27 @@
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = [ "resetPasswordButton", "currentPasswordField" ]
static values = { validationFailed: Boolean }
connect () {
if (this.validationFailedValue) return;
this.element.querySelectorAll(".initial-hidden").forEach(el => {
el.classList.add("hidden");
})
this.element.querySelectorAll(".initial-visible").forEach(el => {
el.classList.remove("hidden");
})
}
showPasswordReset () {
this.element.querySelectorAll(".initial-visible").forEach(el => {
el.classList.add("hidden");
})
this.element.querySelectorAll(".initial-hidden").forEach(el => {
el.classList.remove("hidden");
})
this.currentPasswordFieldTarget.select();
}
}
@@ -1,24 +1,16 @@
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 {
static targets = [ "noExtension", "setPubkey", "pubkeyBech32Input" ] static targets = [ "noExtension", "setPubkey", "pubkeyBech32Input" ]
static values = { userAddress: String, pubkeyHex: String, sharedSecret: String } static values = {
userAddress: String,
pubkeyHex: String,
site: 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
@@ -32,11 +24,15 @@ export default class extends Controller {
this.setPubkeyTarget.disabled = true this.setPubkeyTarget.disabled = true
try { try {
// Auth based on NIP-42
const signedEvent = await window.nostr.signEvent({ const signedEvent = await window.nostr.signEvent({
created_at: Math.floor(Date.now() / 1000), created_at: Math.floor(Date.now() / 1000),
kind: 1, kind: 22242,
tags: [], tags: [
content: `Connect my public key to ${this.userAddressValue} (confirmation ${this.sharedSecretValue})` ["site", this.siteValue],
["challenge", this.sharedSecretValue]
],
content: ""
}) })
const res = await fetch("/settings/set_nostr_pubkey", { const res = await fetch("/settings/set_nostr_pubkey", {
@@ -53,11 +49,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")
@@ -0,0 +1,10 @@
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = [ "resetButton" ]
resetField () {
const inputEl = this.element.querySelector('input')
inputEl.value = inputEl.dataset.defaultValue
}
}
+28
View File
@@ -0,0 +1,28 @@
class BtcpayCheckDonationJob < ApplicationJob
queue_as :default
def perform(donation)
return if donation.completed?
invoice = BtcpayManager::FetchInvoice.call(
invoice_id: donation.btcpay_invoice_id
)
case invoice["status"]
when "Settled"
donation.paid_at = DateTime.now
donation.payment_status = "settled"
donation.save!
NotificationMailer.with(user: donation.user)
.bitcoin_donation_confirmed
.deliver_later
when "Processing"
re_enqueue_job(donation)
end
end
def re_enqueue_job(donation)
self.class.set(wait: 20.seconds).perform_later(donation)
end
end
+6 -2
View File
@@ -1,10 +1,10 @@
class CreateLdapUserJob < ApplicationJob class CreateLdapUserJob < ApplicationJob
queue_as :default queue_as :default
def perform(username, domain, email, hashed_pw) def perform(username:, domain:, email:, hashed_pw:, confirmed: false)
dn = "cn=#{username},ou=#{domain},cn=users,dc=kosmos,dc=org" dn = "cn=#{username},ou=#{domain},cn=users,dc=kosmos,dc=org"
attr = { attr = {
objectclass: ["top", "account", "person", "extensibleObject"], objectclass: ["top", "account", "person", "inetOrgPerson", "extensibleObject"],
cn: username, cn: username,
sn: username, sn: username,
uid: username, uid: username,
@@ -12,6 +12,10 @@ class CreateLdapUserJob < ApplicationJob
userPassword: hashed_pw userPassword: hashed_pw
} }
if confirmed
attr[:serviceEnabled] = Setting.default_services
end
ldap_client.add(dn: dn, attributes: attr) ldap_client.add(dn: dn, attributes: attr)
end end
+3 -3
View File
@@ -2,12 +2,12 @@ class CreateLndhubAccountJob < ApplicationJob
queue_as :default queue_as :default
def perform(user) def perform(user)
return if user.ln_account.present? && user.ln_password.present? return if user.lndhub_username.present? && user.lndhub_password.present?
lndhub = LndhubV2.new lndhub = LndhubV2.new
credentials = lndhub.create_account credentials = lndhub.create_account
user.update! ln_account: credentials["login"], user.update! lndhub_username: credentials["login"],
ln_password: credentials["password"] lndhub_password: credentials["password"]
end end
end end
+7
View File
@@ -0,0 +1,7 @@
class NostrPublishEventJob < ApplicationJob
queue_as :nostr
def perform(event:, relay_url:)
NostrManager::PublishEvent.call(event: event, relay_url: relay_url)
end
end
@@ -0,0 +1,8 @@
class RemoteStorageExpireAuthorizationJob < ApplicationJob
queue_as :remotestorage
def perform(rs_auth_id)
rs_auth = RemoteStorageAuthorization.find rs_auth_id
rs_auth.destroy!
end
end
+2 -2
View File
@@ -2,8 +2,8 @@ class XmppExchangeContactsJob < ApplicationJob
queue_as :default queue_as :default
def perform(inviter, invitee) def perform(inviter, invitee)
return unless inviter.services_enabled.include?("xmpp") && return unless inviter.service_enabled?(:ejabberd) &&
invitee.services_enabled.include?("xmpp") && invitee.service_enabled?(:ejabberd) &&
inviter.preferences[:xmpp_exchange_contacts_with_invitees] inviter.preferences[:xmpp_exchange_contacts_with_invitees]
ejabberd = EjabberdApiClient.new ejabberd = EjabberdApiClient.new
+87
View File
@@ -1,3 +1,90 @@
class ApplicationMailer < ActionMailer::Base class ApplicationMailer < ActionMailer::Base
default Rails.application.config.action_mailer.default_options
layout 'mailer' layout 'mailer'
private
def send_mail
@template ||= "#{self.class.name.underscore}/#{caller[0][/`([^']*)'/, 1]}"
headers['Message-ID'] = message_id
if @user.pgp_pubkey.present?
mail(to: @user.email, subject: "...", content_type: pgp_content_type) do |format|
format.text { render plain: pgp_content }
end
else
mail(to: @user.email, subject: @subject) do |format|
format.text { render @template }
end
end
end
def from_address
self.class.default[:from]
end
def from_domain
Mail::Address.new(from_address).domain
end
def message_id
@message_id ||= "#{SecureRandom.uuid}@#{from_domain}"
end
def boundary
@boundary ||= SecureRandom.hex(8)
end
def pgp_content_type
"multipart/encrypted; protocol=\"application/pgp-encrypted\"; boundary=\"------------#{boundary}\""
end
def pgp_nested_content
message_content = render_to_string(template: @template)
message_content_base64 = Base64.encode64(message_content)
nested_boundary = SecureRandom.hex(8)
<<~NESTED_CONTENT
Content-Type: multipart/mixed; boundary="------------#{nested_boundary}"; protected-headers="v1"
Subject: #{@subject}
From: <#{from_address}>
To: #{@user.display_name || @user.cn} <#{@user.email}>
Message-ID: <#{message_id}>
--------------#{nested_boundary}
Content-Type: text/plain; charset=UTF-8; format=flowed
Content-Transfer-Encoding: base64
#{message_content_base64}
--------------#{nested_boundary}--
NESTED_CONTENT
end
def pgp_content
encrypted_content = UserManager::PgpEncrypt.call(user: @user, text: pgp_nested_content)
encrypted_base64 = Base64.encode64(encrypted_content.to_s)
<<~EMAIL_CONTENT
This is an OpenPGP/MIME encrypted message (RFC 4880 and 3156)
--------------#{boundary}
Content-Type: application/pgp-encrypted
Content-Description: PGP/MIME version identification
Version: 1
--------------#{boundary}
Content-Type: application/octet-stream; name="encrypted.asc"
Content-Description: OpenPGP encrypted message
Content-Disposition: inline; filename="encrypted.asc"
-----BEGIN PGP MESSAGE-----
#{encrypted_base64}
-----END PGP MESSAGE-----
--------------#{boundary}--
EMAIL_CONTENT
end
end end
+1 -1
View File
@@ -18,6 +18,6 @@ class CustomMailer < ApplicationMailer
@user = params[:user] @user = params[:user]
@subject = params[:subject] @subject = params[:subject]
@body = params[:body] @body = params[:body]
mail(to: @user.email, subject: @subject) send_mail
end end
end end
+26 -1
View File
@@ -3,6 +3,31 @@ class NotificationMailer < ApplicationMailer
@user = params[:user] @user = params[:user]
@amount_sats = params[:amount_sats] @amount_sats = params[:amount_sats]
@subject = "Sats received" @subject = "Sats received"
mail to: @user.email, subject: @subject send_mail
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"
send_mail
end
def new_invitations_available
@user = params[:user]
@subject = "New invitations added to your account"
send_mail
end
def bitcoin_donation_confirmed
@user = params[:user]
@donation = params[:donation]
@subject = "Donation confirmed"
send_mail
end end
end end
+5
View File
@@ -0,0 +1,5 @@
module AppCatalog
def self.table_name_prefix
"app_catalog_"
end
end
+16
View File
@@ -0,0 +1,16 @@
class AppCatalog::WebApp < ApplicationRecord
store :metadata, coder: JSON
has_many :remote_storage_authorizations, dependent: :destroy
has_one_attached :icon
has_one_attached :apple_touch_icon
validates :url, presence: true, uniqueness: true
validates :url, format: { with: URI.regexp },
if: Proc.new { |a| a.url.present? }
def update_metadata
AppCatalogManager::UpdateMetadata.call(app: self)
end
end
@@ -0,0 +1,24 @@
module Settings
module BtcpaySettings
extend ActiveSupport::Concern
included do
field :btcpay_api_url, type: :string,
default: ENV["BTCPAY_API_URL"].presence
field :btcpay_enabled, type: :boolean,
default: ENV["BTCPAY_API_URL"].present?
field :btcpay_public_url, type: :string,
default: ENV["BTCPAY_PUBLIC_URL"].presence
field :btcpay_store_id, type: :string,
default: ENV["BTCPAY_STORE_ID"].presence
field :btcpay_auth_token, type: :string,
default: ENV["BTCPAY_AUTH_TOKEN"].presence
field :btcpay_publish_wallet_balances, type: :boolean, default: true
end
end
end
@@ -0,0 +1,16 @@
module Settings
module DiscourseSettings
extend ActiveSupport::Concern
included do
field :discourse_public_url, type: :string,
default: ENV["DISCOURSE_PUBLIC_URL"].presence
field :discourse_enabled, type: :boolean,
default: ENV["DISCOURSE_PUBLIC_URL"].present?
field :discourse_connect_secret, type: :string,
default: ENV["DISCOURSE_CONNECT_SECRET"].presence
end
end
end

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