318 Commits

Author SHA1 Message Date
raucao b82ab45c99 Merge branch 'master' into live
continuous-integration/drone/push Build is passing
2024-09-14 14:57:35 +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 d12c63db26 Merge branch 'master' into live
continuous-integration/drone/push Build is passing
2024-09-10 16:07:24 +02: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 e6a9ef84ce Merge branch 'master' into live
continuous-integration/drone/push Build is failing
2024-08-19 15:13:46 +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 b7e91344a0 Merge branch 'master' into live
continuous-integration/drone/push Build is failing
2024-08-19 14:48:35 +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 0f07e32781 Merge branch 'master' into live
continuous-integration/drone/push Build is passing
2024-08-17 14:49:29 +02:00
raucao f3676949d2 Fix redirect
continuous-integration/drone/push Build is passing
2024-08-17 14:49:19 +02:00
raucao 1311b5ed6a Merge branch 'master' into live
continuous-integration/drone/push Build is passing
2024-08-17 14:46:00 +02:00
raucao 79952b73c5 Fix link descriptions
continuous-integration/drone/push Build is passing
2024-08-17 14:45:31 +02:00
raucao 12f82061e8 Merge branch 'master' into live
continuous-integration/drone/push Build is passing
2024-08-17 14:42:32 +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 a07b4369ab Hide njump link for users without pubkey
continuous-integration/drone/push Build is passing
2024-06-23 17:33:34 +02:00
raucao 2605c06807 Merge branch 'feature/admin_pages' into live
continuous-integration/drone/push Build is passing
2024-06-23 17:30:22 +02: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 1db768fb15 Merge branch 'feature/admin_pages' into live
continuous-integration/drone/push Build is passing
2024-06-23 17:27:03 +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 8a7403df32 Merge branch 'feature/own_relay' into live
continuous-integration/drone/push Build is passing
2024-06-20 15:52:03 +02: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 f0295fef7a Merge branch 'feature/own_relay' into live
continuous-integration/drone/push Build is passing
2024-06-20 15:28:55 +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 090affd304 Merge branch 'feature/own_relay' into live
continuous-integration/drone/push Build is passing
2024-06-20 14:51:20 +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 bafddd436b Merge branch 'feature/own_relay' into live
continuous-integration/drone/push Build is passing
2024-06-20 13:59:59 +02:00
raucao 560f193c4b Merge branch 'master' into live
continuous-integration/drone/push Build is passing
2024-06-20 13:56:11 +02:00
raucao 8aabbad5bb Merge branch 'feature/strfry_zap_receipts' into live 2024-06-20 13:56:00 +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 ba8d21eb7a Merge branch 'master' into live
continuous-integration/drone/push Build is passing
2024-06-09 13:29:57 +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 53df455d53 Merge branch 'master' into live
continuous-integration/drone/push Build is passing
2024-06-09 13:17:51 +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 9f1af3a9aa Merge branch 'master' into live
continuous-integration/drone/push Build is passing
2024-06-07 14:13:53 +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 1d09008ce2 Merge branch 'master' into live
continuous-integration/drone/push Build is passing
2024-06-04 17:45:13 +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 57c5317c38 Merge branch 'feature/170-nostr_zaps' into live
continuous-integration/drone/push Build is passing
2024-05-21 18:29:13 +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 41bd920060 Merge branch 'feature/170-nostr_zaps' into live
continuous-integration/drone/push Build is passing
2024-05-21 18:09:09 +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 0815fa6040 Merge branch 'feature/170-nostr_zaps' into live
continuous-integration/drone/push Build is passing
2024-05-19 17:07:58 +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 af0e99aa50 Merge branch 'feature/170-nostr_zaps' into live
continuous-integration/drone/push Build is passing
2024-05-19 16:55:10 +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 f05eec5255 Merge branch 'feature/170-nostr_zaps' into live
continuous-integration/drone/push Build is passing
2024-05-19 16:48:28 +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 66ca2dc6b0 Merge branch 'feature/173-nostr_ldap' into live
continuous-integration/drone/push Build is passing
2024-05-19 13:44:24 +02:00
raucao 800183e9da Increase sidekiq concurrency in prod
continuous-integration/drone/push Build is passing
2024-05-19 13:44:01 +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
304 changed files with 7683 additions and 1560 deletions
+3 -1
View File
@@ -17,7 +17,7 @@ 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 REDIS_URL: redis://redis:6379/0
@@ -28,6 +28,8 @@ steps:
- bundle config set cache_path 'vendor/cache' - bundle config set cache_path 'vendor/cache'
- bundle config set with 'development test' - bundle config set with 'development test'
- bundle install --jobs=3 --retry=3 - bundle install --jobs=3 --retry=3
- bundle exec rails db:create
- bundle exec rails db:migrate
- yarn install - yarn install
- rake css:build - rake css:build
- bundle exec rspec - bundle exec rspec
+61 -36
View File
@@ -1,46 +1,71 @@
PRIMARY_DOMAIN=kosmos.org # PRIMARY_DOMAIN=kosmos.org
AKKOUNTS_DOMAIN=accounts.example.com # AKKOUNTS_DOMAIN=accounts.example.com
SMTP_SERVER=smtp.example.com # SMTP_SERVER=smtp.example.com
SMTP_PORT=587 # SMTP_PORT=587
SMTP_LOGIN=accounts # SMTP_LOGIN=accounts
SMTP_PASSWORD=123abc # SMTP_PASSWORD=123abc
SMTP_FROM_ADDRESS=accounts@example.com # SMTP_FROM_ADDRESS=accounts@example.com
SMTP_DOMAIN=example.com # SMTP_DOMAIN=example.com
SMTP_AUTH_METHOD=plain # SMTP_AUTH_METHOD=plain
SMTP_ENABLE_STARTTLS=auto # SMTP_ENABLE_STARTTLS=auto
LDAP_HOST=localhost # S3_ENABLED=true
LDAP_PORT=389 # S3_ENDPOINT=https://s3.kosmos.org
LDAP_ADMIN_PASSWORD=passthebutter # S3_REGION=garage
LDAP_SUFFIX='dc=kosmos,dc=org' # S3_BUCKET=akkounts-production
# S3_ALIAS_HOST=https://accounts.web.s3.kosmos.org
# S3_ACCESS_KEY=123456abcdefg
# S3_SECRET_KEY=123456789123456789123456789
REDIS_URL='redis://localhost:6379/1' # LDAP_HOST=localhost
# LDAP_PORT=389
# LDAP_ADMIN_PASSWORD=passthebutter
# LDAP_SUFFIX='dc=kosmos,dc=org'
WEBHOOKS_ALLOWED_IPS='10.1.1.163' # REDIS_URL='redis://localhost:6379/1'
DISCOURSE_PUBLIC_URL='https://community.kosmos.org' # WEBHOOKS_ALLOWED_IPS='10.1.1.163'
DISCOURSE_CONNECT_SECRET='discourse_connect_ftw'
DRONECI_PUBLIC_URL='https://drone.kosmos.org' #
# Service Integrations
# (sorted alphabetically by service name)
#
GITEA_PUBLIC_URL='https://gitea.kosmos.org' # BTCPAY_PUBLIC_URL='https://btcpay.example.com'
MASTODON_PUBLIC_URL='https://kosmos.social' # BTCPAY_API_URL='http://localhost:23001/api/v1'
MEDIAWIKI_PUBLIC_URL='https://wiki.kosmos.org' # BTCPAY_STORE_ID=''
RS_STORAGE_URL='https://storage.kosmos.org' # BTCPAY_AUTH_TOKEN=''
RS_REDIS_URL='redis://localhost:6379/2'
EJABBERD_ADMIN_URL='https://xmpp.kosmos.org/admin' # DISCOURSE_PUBLIC_URL='https://community.kosmos.org'
EJABBERD_API_URL='https://xmpp.kosmos.org/api' # DISCOURSE_CONNECT_SECRET='discourse_connect_ftw'
BTCPAY_API_URL='http://localhost:23001/api/v1' # DRONECI_PUBLIC_URL='https://drone.kosmos.org'
LNDHUB_API_URL='http://localhost:3023' # EJABBERD_ADMIN_URL='https://xmpp.kosmos.org/admin'
LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org' # EJABBERD_API_URL='https://xmpp.kosmos.org/api'
LNDHUB_PUBLIC_KEY='0123d3be18617f39cf645851e3ba63f51fc13f0bb09e3bb25e6fd4de556486d946'
LNDHUB_ADMIN_UI=true # GITEA_PUBLIC_URL='https://gitea.kosmos.org'
LNDHUB_PG_HOST=localhost
LNDHUB_PG_PORT=5432 # LNDHUB_API_URL='http://localhost:3023'
LNDHUB_PG_DATABASE=lndhub # LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org'
LNDHUB_PG_USERNAME=lndhub # LNDHUB_PUBLIC_KEY='0123d3be18617f39cf645851e3ba63f51fc13f0bb09e3bb25e6fd4de556486d946'
LNDHUB_PG_PASSWORD='' # 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'
+9 -1
View File
@@ -1,18 +1,26 @@
PRIMARY_DOMAIN=kosmos.org PRIMARY_DOMAIN=kosmos.org
AKKOUNTS_DOMAIN=accounts.kosmos.org
REDIS_URL='redis://localhost:6379/0' 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_STORAGE_URL='https://storage.kosmos.org' RS_STORAGE_URL='https://storage.kosmos.org'
RS_REDIS_URL='redis://localhost:6379/1' RS_REDIS_URL='redis://localhost:6379/1'
+1
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
+1 -1
View File
@@ -1 +1 @@
2.7.2 3.3.0
+3 -2
View File
@@ -1,10 +1,11 @@
# 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
+11 -8
View File
@@ -2,7 +2,7 @@ source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" } git_source(:github) { |repo| "https://github.com/#{repo}.git" }
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 7.0.2' gem 'rails', '~> 7.1'
# Use Puma as the app server # Use Puma as the app server
gem 'puma', '~> 4.1' gem 'puma', '~> 4.1'
# View components # View components
@@ -22,7 +22,7 @@ gem 'jbuilder', '~> 2.7'
# Use Redis adapter to run Action Cable in production # Use Redis adapter to run Action Cable in production
# gem 'redis', '~> 4.0' # gem 'redis', '~> 4.0'
# Use Active Model has_secure_password # Use Active Model has_secure_password
# gem 'bcrypt', '~> 3.1.7' gem 'bcrypt', '~> 3.1'
# Configuration # Configuration
gem 'dotenv-rails' gem 'dotenv-rails'
@@ -37,6 +37,7 @@ 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'
@@ -46,6 +47,8 @@ gem 'flipper-ui'
# 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 'sidekiq', '< 7'
@@ -58,19 +61,19 @@ 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'
group :development, :test do group :development, :test do
# Use sqlite3 as the database for Active Record # Use sqlite3 as the database for Active Record
gem 'sqlite3', '~> 1.4' gem 'sqlite3', '~> 1.7.2'
gem 'rspec-rails' gem 'rspec-rails'
gem 'rails-controller-testing' gem 'rails-controller-testing'
gem "byebug", "~> 11.1"
end end
group :development do group :development do
# Access an interactive console on exception pages or by calling 'console' anywhere in the code. # Access an interactive console on exception pages or by calling 'console' anywhere in the code.
gem 'web-console', '>= 3.3.0' gem 'web-console', '~> 4.2'
gem 'listen', '~> 3.2' gem 'listen', '~> 3.2'
gem 'letter_opener' gem 'letter_opener'
gem 'letter_opener_web' gem 'letter_opener_web'
@@ -86,8 +89,8 @@ group :test do
end end
group :production do group :production do
# Use postgresql as the database for Active Record gem 'pg', '~> 1.5'
gem 'pg', '~> 1.2.3'
end end
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem # Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
+249 -175
View File
@@ -1,113 +1,127 @@
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 (7.1.3)
actionpack (= 7.0.5) actionpack (= 7.1.3)
activesupport (= 7.0.5) activesupport (= 7.1.3)
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 (7.1.3)
activejob (= 7.0.5) actionpack (= 7.1.3)
activerecord (= 7.0.5) activejob (= 7.1.3)
activestorage (= 7.0.5) activerecord (= 7.1.3)
activesupport (= 7.0.5) activestorage (= 7.1.3)
activesupport (= 7.1.3)
mail (>= 2.7.1) mail (>= 2.7.1)
net-imap net-imap
net-pop net-pop
net-smtp net-smtp
actionmailer (7.0.5) actionmailer (7.1.3)
actionpack (= 7.0.5) actionpack (= 7.1.3)
actionview (= 7.0.5) actionview (= 7.1.3)
activejob (= 7.0.5) activejob (= 7.1.3)
activesupport (= 7.0.5) activesupport (= 7.1.3)
mail (~> 2.5, >= 2.5.4) mail (~> 2.5, >= 2.5.4)
net-imap net-imap
net-pop net-pop
net-smtp net-smtp
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.2)
actionpack (7.0.5) actionpack (7.1.3)
actionview (= 7.0.5) actionview (= 7.1.3)
activesupport (= 7.0.5) activesupport (= 7.1.3)
rack (~> 2.0, >= 2.2.4) nokogiri (>= 1.8.5)
racc
rack (>= 2.2.4)
rack-session (>= 1.0.1)
rack-test (>= 0.6.3) rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.0, >= 1.2.0) rails-html-sanitizer (~> 1.6)
actiontext (7.0.5) actiontext (7.1.3)
actionpack (= 7.0.5) actionpack (= 7.1.3)
activerecord (= 7.0.5) activerecord (= 7.1.3)
activestorage (= 7.0.5) activestorage (= 7.1.3)
activesupport (= 7.0.5) activesupport (= 7.1.3)
globalid (>= 0.6.0) globalid (>= 0.6.0)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
actionview (7.0.5) actionview (7.1.3)
activesupport (= 7.0.5) activesupport (= 7.1.3)
builder (~> 3.1) builder (~> 3.1)
erubi (~> 1.4) erubi (~> 1.11)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.1, >= 1.2.0) rails-html-sanitizer (~> 1.6)
activejob (7.0.5) activejob (7.1.3)
activesupport (= 7.0.5) activesupport (= 7.1.3)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (7.0.5) activemodel (7.1.3)
activesupport (= 7.0.5) activesupport (= 7.1.3)
activerecord (7.0.5) activerecord (7.1.3)
activemodel (= 7.0.5) activemodel (= 7.1.3)
activesupport (= 7.0.5) activesupport (= 7.1.3)
activestorage (7.0.5) timeout (>= 0.4.0)
actionpack (= 7.0.5) activestorage (7.1.3)
activejob (= 7.0.5) actionpack (= 7.1.3)
activerecord (= 7.0.5) activejob (= 7.1.3)
activesupport (= 7.0.5) activerecord (= 7.1.3)
activesupport (= 7.1.3)
marcel (~> 1.0) marcel (~> 1.0)
mini_mime (>= 1.1.0) activesupport (7.1.3)
activesupport (7.0.5) base64
bigdecimal
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
connection_pool (>= 2.2.5)
drb
i18n (>= 1.6, < 2) i18n (>= 1.6, < 2)
minitest (>= 5.1) minitest (>= 5.1)
mutex_m
tzinfo (~> 2.0) tzinfo (~> 2.0)
addressable (2.8.4) addressable (2.8.6)
public_suffix (>= 2.0.2, < 6.0) public_suffix (>= 2.0.2, < 6.0)
ast (2.4.2) ast (2.4.2)
aws-eventstream (1.3.0)
aws-partitions (1.886.0)
aws-sdk-core (3.191.0)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.651.0)
aws-sigv4 (~> 1.8)
jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.77.0)
aws-sdk-core (~> 3, >= 3.191.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.143.0)
aws-sdk-core (~> 3, >= 3.191.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.8)
aws-sigv4 (1.8.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.4.2)
thor (>= 1.1.0) thor (>= 1.1.0)
benchmark (0.2.1) benchmark (0.3.0)
bigdecimal (3.1.6)
bindex (0.8.1) bindex (0.8.1)
bip-schnorr (0.6.0) bip-schnorr (0.7.0)
ecdsa_ext (~> 0.5.0) ecdsa_ext (~> 0.5.0)
builder (3.2.4) builder (3.2.4)
byebug (11.1.3) capybara (3.40.0)
capybara (3.39.2)
addressable addressable
matrix matrix
mini_mime (>= 0.1.3) mini_mime (>= 0.1.3)
nokogiri (~> 1.8) nokogiri (~> 1.11)
rack (>= 1.6.0) rack (>= 1.6.0)
rack-test (>= 0.6.3) rack-test (>= 0.6.3)
regexp_parser (>= 1.5, < 3.0) regexp_parser (>= 1.5, < 3.0)
xpath (~> 3.2) xpath (~> 3.2)
chunky_png (1.4.0) chunky_png (1.4.0)
concurrent-ruby (1.2.2) concurrent-ruby (1.2.3)
connection_pool (2.4.1) connection_pool (2.4.1)
crack (0.4.5) crack (0.4.6)
bigdecimal
rexml rexml
crass (1.0.6) crass (1.0.6)
cssbundling-rails (1.1.2) cssbundling-rails (1.4.0)
railties (>= 6.0.0) railties (>= 6.0.0)
database_cleaner (2.0.2) database_cleaner (2.0.2)
database_cleaner-active_record (>= 2, < 3) database_cleaner-active_record (>= 2, < 3)
@@ -115,8 +129,8 @@ GEM
activerecord (>= 5.a) activerecord (>= 5.a)
database_cleaner-core (~> 2.0.0) database_cleaner-core (~> 2.0.0)
database_cleaner-core (2.0.1) database_cleaner-core (2.0.1)
date (3.3.3) date (3.3.4)
devise (4.9.2) devise (4.9.3)
bcrypt (~> 3.0) bcrypt (~> 3.0)
orm_adapter (~> 0.1) orm_adapter (~> 0.1)
railties (>= 4.1.0) railties (>= 4.1.0)
@@ -125,7 +139,7 @@ GEM
devise_ldap_authenticatable (0.8.7) devise_ldap_authenticatable (0.8.7)
devise (>= 3.4.1) devise (>= 3.4.1)
net-ldap (>= 0.16.0) net-ldap (>= 0.16.0)
diff-lcs (1.5.0) diff-lcs (1.5.1)
discourse_api (2.0.1) discourse_api (2.0.1)
faraday (~> 2.7) faraday (~> 2.7)
faraday-follow_redirects faraday-follow_redirects
@@ -135,65 +149,79 @@ GEM
dotenv-rails (2.8.1) dotenv-rails (2.8.1)
dotenv (= 2.8.1) dotenv (= 2.8.1)
railties (>= 3.2) railties (>= 3.2)
down (5.4.1)
addressable (~> 2.8)
drb (2.2.0)
ruby2_keywords
e2mmap (0.1.0) e2mmap (0.1.0)
ecdsa (1.2.0) ecdsa (1.2.0)
ecdsa_ext (0.5.0) ecdsa_ext (0.5.1)
ecdsa (~> 1.2.0) ecdsa (~> 1.2.0)
erubi (1.12.0) erubi (1.12.0)
et-orbi (1.2.7) et-orbi (1.2.7)
tzinfo tzinfo
event_emitter (0.2.6) event_emitter (0.2.6)
eventmachine (1.2.7) eventmachine (1.2.7)
factory_bot (6.2.1) factory_bot (6.4.6)
activesupport (>= 5.0.0) activesupport (>= 5.0.0)
factory_bot_rails (6.2.0) factory_bot_rails (6.4.3)
factory_bot (~> 6.2.0) factory_bot (~> 6.4)
railties (>= 5.0.0) railties (>= 5.0.0)
faker (3.2.0) faker (3.2.3)
i18n (>= 1.8.11, < 2) i18n (>= 1.8.11, < 2)
faraday (2.7.6) faraday (2.9.0)
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.0.4)
multipart-post (~> 2) multipart-post (~> 2)
faraday-net_http (3.0.2) faraday-net_http (3.1.0)
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.16.3)
flipper (0.28.0) flipper (1.2.2)
concurrent-ruby (< 2) concurrent-ruby (< 2)
flipper-active_record (0.28.0) flipper-active_record (1.2.2)
activerecord (>= 4.2, < 8) activerecord (>= 4.2, < 8)
flipper (~> 0.28.0) flipper (~> 1.2.2)
flipper-ui (0.28.0) flipper-ui (1.2.2)
erubi (>= 1.0.0, < 2.0.0) erubi (>= 1.0.0, < 2.0.0)
flipper (~> 0.28.0) flipper (~> 1.2.2)
rack (>= 1.4, < 3) rack (>= 1.4, < 4)
rack-protection (>= 1.5.3, <= 4.0.0) rack-protection (>= 1.5.3, <= 4.0.0)
sanitize (< 7) sanitize (< 7)
fugit (1.8.1) fugit (1.9.0)
et-orbi (~> 1, >= 1.2.7) et-orbi (~> 1, >= 1.2.7)
raabro (~> 1.4) raabro (~> 1.4)
globalid (1.1.0) globalid (1.2.1)
activesupport (>= 5.0) activesupport (>= 6.1)
hashdiff (1.0.1) hashdiff (1.1.0)
i18n (1.14.1) i18n (1.14.1)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
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.0.1)
actionpack (>= 6.0.0) actionpack (>= 6.0.0)
activesupport (>= 6.0.0)
railties (>= 6.0.0) railties (>= 6.0.0)
io-console (0.7.2)
irb (1.11.1)
rdoc
reline (>= 0.4.2)
jaro_winkler (1.5.6) jaro_winkler (1.5.6)
jbuilder (2.11.5) jbuilder (2.11.5)
actionview (>= 5.0.0) actionview (>= 5.0.0)
activesupport (>= 5.0.0) activesupport (>= 5.0.0)
json (2.6.3) jmespath (1.6.2)
json (2.7.1)
kramdown (2.4.0) kramdown (2.4.0)
rexml rexml
kramdown-parser-gfm (1.1.0) kramdown-parser-gfm (1.1.0)
kramdown (~> 2.0) kramdown (~> 2.0)
language_server-protocol (3.17.0.3)
launchy (2.5.2) launchy (2.5.2)
addressable (~> 2.8) addressable (~> 2.8)
letter_opener (1.8.1) letter_opener (1.8.1)
@@ -206,10 +234,10 @@ GEM
listen (3.8.0) listen (3.8.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.0)
bech32 (~> 1.1) bech32 (~> 1.1)
lockbox (1.2.0) lockbox (1.3.2)
loofah (2.21.3) loofah (2.22.0)
crass (~> 1.0.2) crass (~> 1.0.2)
nokogiri (>= 1.12.0) nokogiri (>= 1.12.0)
mail (2.8.1) mail (2.8.1)
@@ -217,64 +245,92 @@ GEM
net-imap net-imap
net-pop net-pop
net-smtp net-smtp
manifique (1.1.0)
faraday (~> 2.9.0)
faraday-follow_redirects (= 0.3.0)
nokogiri (~> 1.16.0)
marcel (1.0.2) marcel (1.0.2)
matrix (0.4.2) matrix (0.4.2)
method_source (1.0.0) method_source (1.0.0)
mini_mime (1.1.2) mini_magick (4.12.0)
minitest (5.18.0) mini_mime (1.1.5)
mini_portile2 (2.8.5)
minitest (5.21.2)
multipart-post (2.3.0) multipart-post (2.3.0)
net-imap (0.3.6) mutex_m (0.2.0)
net-http (0.4.1)
uri
net-imap (0.4.9.1)
date date
net-protocol net-protocol
net-ldap (0.18.0) net-ldap (0.19.0)
net-pop (0.1.2) net-pop (0.1.2)
net-protocol net-protocol
net-protocol (0.2.1) net-protocol (0.2.2)
timeout timeout
net-smtp (0.3.3) net-smtp (0.4.0.1)
net-protocol net-protocol
nio4r (2.5.9) nio4r (2.7.0)
nokogiri (1.15.2-arm64-darwin) nokogiri (1.16.0)
mini_portile2 (~> 2.8.2)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.15.2-x86_64-linux) nokogiri (1.16.0-arm64-darwin)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.16.0-x86_64-linux)
racc (~> 1.4)
nostr (0.6.0)
bech32 (~> 1.4)
bip-schnorr (~> 0.7)
ecdsa (~> 1.2)
event_emitter (~> 0.2)
faye-websocket (~> 0.11)
json (~> 2.6)
orm_adapter (0.5.0) orm_adapter (0.5.0)
pagy (6.0.4) pagy (6.4.3)
parallel (1.23.0) parallel (1.24.0)
parser (3.2.2.3) parser (3.3.0.5)
ast (~> 2.4.1) ast (~> 2.4.1)
racc racc
pg (1.2.3) pg (1.5.4)
public_suffix (5.0.1) psych (5.1.2)
stringio
public_suffix (5.0.4)
puma (4.3.12) puma (4.3.12)
nio4r (~> 2.0) nio4r (~> 2.0)
raabro (1.4.0) raabro (1.4.0)
racc (1.7.1) racc (1.7.3)
rack (2.2.7) rack (2.2.8)
rack-protection (3.0.6) rack-protection (3.2.0)
rack base64 (>= 0.1.0)
rack (~> 2.2, >= 2.2.4)
rack-session (1.0.2)
rack (< 3)
rack-test (2.1.0) rack-test (2.1.0)
rack (>= 1.3) rack (>= 1.3)
rails (7.0.5) rackup (1.0.0)
actioncable (= 7.0.5) rack (< 3)
actionmailbox (= 7.0.5) webrick
actionmailer (= 7.0.5) rails (7.1.3)
actionpack (= 7.0.5) actioncable (= 7.1.3)
actiontext (= 7.0.5) actionmailbox (= 7.1.3)
actionview (= 7.0.5) actionmailer (= 7.1.3)
activejob (= 7.0.5) actionpack (= 7.1.3)
activemodel (= 7.0.5) actiontext (= 7.1.3)
activerecord (= 7.0.5) actionview (= 7.1.3)
activestorage (= 7.0.5) activejob (= 7.1.3)
activesupport (= 7.0.5) activemodel (= 7.1.3)
activerecord (= 7.1.3)
activestorage (= 7.1.3)
activesupport (= 7.1.3)
bundler (>= 1.15.0) bundler (>= 1.15.0)
railties (= 7.0.5) railties (= 7.1.3)
rails-controller-testing (1.0.5) rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1) actionpack (>= 5.0.1.rc1)
actionview (>= 5.0.1.rc1) actionview (>= 5.0.1.rc1)
activesupport (>= 5.0.1.rc1) activesupport (>= 5.0.1.rc1)
rails-dom-testing (2.0.3) rails-dom-testing (2.2.0)
activesupport (>= 4.2.0) activesupport (>= 5.0.0)
minitest
nokogiri (>= 1.6) nokogiri (>= 1.6)
rails-html-sanitizer (1.6.0) rails-html-sanitizer (1.6.0)
loofah (~> 2.21) loofah (~> 2.21)
@@ -282,27 +338,32 @@ GEM
rails-settings-cached (2.8.3) rails-settings-cached (2.8.3)
activerecord (>= 5.0.0) activerecord (>= 5.0.0)
railties (>= 5.0.0) railties (>= 5.0.0)
railties (7.0.5) railties (7.1.3)
actionpack (= 7.0.5) actionpack (= 7.1.3)
activesupport (= 7.0.5) activesupport (= 7.1.3)
method_source irb
rackup (>= 1.0.0)
rake (>= 12.2) rake (>= 12.2)
thor (~> 1.0) thor (~> 1.0, >= 1.2.2)
zeitwerk (~> 2.5) zeitwerk (~> 2.6)
rainbow (3.1.1) rainbow (3.1.1)
rake (13.0.6) rake (13.1.0)
rb-fsevent (0.11.2) rb-fsevent (0.11.2)
rb-inotify (0.10.1) rb-inotify (0.10.1)
ffi (~> 1.0) ffi (~> 1.0)
rbs (2.8.4) rbs (2.8.4)
rdoc (6.6.2)
psych (>= 4.0.0)
redis (4.8.1) redis (4.8.1)
regexp_parser (2.8.1) regexp_parser (2.9.0)
responders (3.1.0) reline (0.4.2)
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 (2.1.1)
nokogiri nokogiri
rexml (3.2.5) rexml (3.2.6)
rqrcode (2.2.0) rqrcode (2.2.0)
chunky_png (~> 1.0) chunky_png (~> 1.0)
rqrcode_core (~> 1.0) rqrcode_core (~> 1.0)
@@ -312,10 +373,10 @@ GEM
rspec-expectations (3.12.3) rspec-expectations (3.12.3)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.12.0) rspec-support (~> 3.12.0)
rspec-mocks (3.12.5) rspec-mocks (3.12.6)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.12.0) rspec-support (~> 3.12.0)
rspec-rails (6.0.3) rspec-rails (6.1.1)
actionpack (>= 6.1) actionpack (>= 6.1)
activesupport (>= 6.1) activesupport (>= 6.1)
railties (>= 6.1) railties (>= 6.1)
@@ -323,32 +384,35 @@ GEM
rspec-expectations (~> 3.12) rspec-expectations (~> 3.12)
rspec-mocks (~> 3.12) rspec-mocks (~> 3.12)
rspec-support (~> 3.12) rspec-support (~> 3.12)
rspec-support (3.12.0) rspec-support (3.12.1)
rubocop (1.52.1) rubocop (1.60.2)
json (~> 2.3) json (~> 2.3)
language_server-protocol (>= 3.17.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 (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0) rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.28.0, < 2.0) rubocop-ast (>= 1.30.0, < 2.0)
ruby-progressbar (~> 1.7) ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 3.0) unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.29.0) rubocop-ast (1.30.0)
parser (>= 3.2.1.0) parser (>= 3.2.1.0)
ruby-progressbar (1.13.0) ruby-progressbar (1.13.0)
ruby-vips (2.2.0)
ffi (~> 1.12)
ruby2_keywords (0.0.5) ruby2_keywords (0.0.5)
rufus-scheduler (3.9.1) rufus-scheduler (3.9.1)
fugit (~> 1.1, >= 1.1.6) fugit (~> 1.1, >= 1.1.6)
sanitize (6.0.1) sanitize (6.1.0)
crass (~> 1.0.2) crass (~> 1.0.2)
nokogiri (>= 1.12.0) nokogiri (>= 1.12.0)
sentry-rails (5.9.0) sentry-rails (5.16.1)
railties (>= 5.0) railties (>= 5.0)
sentry-ruby (~> 5.9.0) sentry-ruby (~> 5.16.1)
sentry-ruby (5.9.0) sentry-ruby (5.16.1)
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
sidekiq (6.5.9) sidekiq (6.5.12)
connection_pool (>= 2.2.5, < 3) connection_pool (>= 2.2.5, < 3)
rack (~> 2.0) rack (~> 2.0)
redis (>= 4.5.0, < 5) redis (>= 4.5.0, < 5)
@@ -356,7 +420,7 @@ GEM
rufus-scheduler (~> 3.2) rufus-scheduler (~> 3.2)
sidekiq (>= 6, < 8) sidekiq (>= 6, < 8)
tilt (>= 1.4.0) tilt (>= 1.4.0)
solargraph (0.49.0) solargraph (0.50.0)
backport (~> 1.2) backport (~> 1.2)
benchmark benchmark
bundler (~> 2.0) bundler (~> 2.0)
@@ -372,56 +436,63 @@ GEM
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) sprockets (4.2.1)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
rack (>= 2.2.4, < 4) rack (>= 2.2.4, < 4)
sprockets-rails (3.4.2) sprockets-rails (3.4.2)
actionpack (>= 5.2) actionpack (>= 5.2)
activesupport (>= 5.2) activesupport (>= 5.2)
sprockets (>= 3.0.0) sprockets (>= 3.0.0)
sqlite3 (1.6.3-arm64-darwin) sqlite3 (1.7.2)
sqlite3 (1.6.3-x86_64-linux) mini_portile2 (~> 2.8.0)
stimulus-rails (1.2.1) sqlite3 (1.7.2-arm64-darwin)
sqlite3 (1.7.2-x86_64-linux)
stimulus-rails (1.3.3)
railties (>= 6.0.0) railties (>= 6.0.0)
thor (1.2.2) stringio (3.1.0)
tilt (2.2.0) thor (1.3.0)
timeout (0.3.2) tilt (2.3.0)
turbo-rails (1.4.0) timeout (0.4.1)
turbo-rails (1.5.0)
actionpack (>= 6.0.0) actionpack (>= 6.0.0)
activejob (>= 6.0.0) activejob (>= 6.0.0)
railties (>= 6.0.0) railties (>= 6.0.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 (2.5.0)
view_component (3.2.0) uri (0.13.0)
view_component (3.10.0)
activesupport (>= 5.2.0, < 8.0) activesupport (>= 5.2.0, < 8.0)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
method_source (~> 1.0) method_source (~> 1.0)
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.19.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.8.1)
websocket-driver (0.7.6)
websocket-extensions (>= 0.1.0) websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5) websocket-extensions (0.1.5)
xpath (3.2.0) xpath (3.2.0)
nokogiri (~> 1.8) nokogiri (~> 1.8)
yard (0.9.34) yard (0.9.34)
zeitwerk (2.6.8) zeitwerk (2.6.12)
PLATFORMS PLATFORMS
arm64-darwin-22 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
@@ -429,12 +500,14 @@ 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
image_processing (~> 1.12.2)
importmap-rails importmap-rails
jbuilder (~> 2.7) jbuilder (~> 2.7)
letter_opener letter_opener
@@ -442,12 +515,13 @@ DEPENDENCIES
listen (~> 3.2) listen (~> 3.2)
lnurl lnurl
lockbox lockbox
manifique (~> 1.1.0)
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) puma (~> 4.1)
rails (~> 7.0.2) rails (~> 7.1)
rails-controller-testing rails-controller-testing
rails-settings-cached (~> 2.8.3) rails-settings-cached (~> 2.8.3)
rqrcode (~> 2.0) rqrcode (~> 2.0)
@@ -458,14 +532,14 @@ DEPENDENCIES
sidekiq-scheduler sidekiq-scheduler
solargraph solargraph
sprockets-rails sprockets-rails
sqlite3 (~> 1.4) sqlite3 (~> 1.7.2)
stimulus-rails stimulus-rails
turbo-rails turbo-rails
tzinfo-data tzinfo-data
view_component view_component
warden warden
web-console (>= 3.3.0) web-console (~> 4.2)
webmock webmock
BUNDLED WITH BUNDLED WITH
2.3.7 2.5.5
+40 -16
View File
@@ -14,8 +14,10 @@ so:
1. Make sure [Docker Compose is installed][1] and Docker is running (included in 1. Make sure [Docker Compose is installed][1] and Docker is running (included in
Docker Desktop) Docker Desktop)
3. Run `docker compose up` and wait until 389ds announces its successful start 3. Run `docker compose up --build` and wait until all services have started
in the log output (389ds might take an extra minute to be ready). This will take a while when
running for the first time, so you might want to do something else in the
meantime.
4. `docker-compose exec ldap dsconf localhost backend create --suffix="dc=kosmos,dc=org" --be-name="dev"` 4. `docker-compose exec ldap dsconf localhost backend create --suffix="dc=kosmos,dc=org" --be-name="dev"`
5. `docker compose run web rails ldap:setup` 5. `docker compose run web rails ldap:setup`
6. `docker compose run web rails db:setup` 6. `docker compose run web rails db:setup`
@@ -28,38 +30,44 @@ have the password "user is user".
### Rails app ### Rails app
_Note: when using Docker Compose, prefix the following commands with `docker-compose
run web`._
Installing dependencies: Installing dependencies:
bundle install bundle install
yarn install yarn install
Setting up local database (SQLite): Migrating the local database (after schema changes):
bundle exec rails db:create
bundle exec rails db:migrate bundle exec rails db:migrate
Running the dev server and auto-building CSS files on change: Running the dev server, and auto-building CSS files on change _(automatic with Docker Compose)_:
bin/dev bin/dev
Running the background workers (requires Redis): Running the background workers (requires Redis) _(automatic with Docker Compose)_:
bundle exec sidekiq -C config/sidekiq.yml bundle exec sidekiq -C config/sidekiq.yml
Running all specs: Running the test suite:
bundle exec rspec bundle exec rspec
### Docker (Compose) Running the test suite with Docker Compose requires overriding the Rails
environment:
There is a working Docker Compose config file, which define a number of services including docker-compose run -e "RAILS_ENV=test" web rspec
an app server for Rails as well as a local 389ds (LDAP) server.
For Rails developers, you probably just want to start the LDAP server: `docker-compose up ldap`, ### Docker Compose
listening on port 389 on your machine.
You can pick and choose your services adding them by name (listed in `docker-compose.yml`) at Services/containers are configured in `docker-compose.yml`.
the end of the docker compose command. eg. `docker compose up ldap redis`
You can run services selectively, for example if you want to run the Rails app
and test suite on the host machine. Just add the service names of the
containers you want to run to the `up` command, like so:
docker-compose up ldap redis
#### LDAP server #### LDAP server
@@ -76,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
@@ -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,21 @@
# frozen_string_literal: true
module AppCatalog
class WebAppIconComponent < ViewComponent::Base
def initialize(web_app:)
if web_app&.icon&.attached?
@image_url = image_url_for(web_app.icon)
elsif web_app&.apple_touch_icon&.attached?
@image_url = image_url_for(web_app.apple_touch_icon)
end
end
def image_url_for(attachment)
if Setting.s3_enabled?
s3_image_url(attachment)
else
Rails.application.routes.url_helpers.rails_blob_path(attachment, only_path: true)
end
end
end
end
@@ -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
@@ -6,6 +6,7 @@
) do %> ) do %>
<%= method("#{@type}_field").call :setting, @key, <%= method("#{@type}_field").call :setting, @key,
value: Setting.public_send(@key), value: Setting.public_send(@key),
placeholder: @placeholder,
data: { data: {
:'default-value' => Setting.get_field(@key)[:default] :'default-value' => Setting.get_field(@key)[:default]
}, },
@@ -2,14 +2,15 @@
module FormElements module FormElements
class FieldsetResettableSettingComponent < ViewComponent::Base class FieldsetResettableSettingComponent < ViewComponent::Base
def initialize(tag: "li", key:, type: :text, title:, description: nil) def initialize(tag: "li", key:, type: :text, title:, description: nil, placeholder: nil)
@tag = tag @tag = tag
@positioning = :vertical @positioning = :vertical
@title = title @title = title
@descripton = description @description = description
@key = key.to_sym @key = key.to_sym
@type = type @type = type
@resettable = is_resettable?(@key) @resettable = is_resettable?(@key)
@placeholder = placeholder
end end
def is_resettable?(key) def is_resettable?(key)
@@ -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>
+3 -1
View File
@@ -12,15 +12,17 @@
<!-- Modal Container --> <!-- Modal Container -->
<div data-modal-target="container" <div data-modal-target="container"
class="max-h-screen w-auto max-w-lg relative 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"> hidden animate-scale-in fixed inset-0 overflow-y-auto flex items-center justify-center">
<!-- Modal Card --> <!-- Modal Card -->
<div class="m-1 bg-white rounded shadow"> <div class="m-1 bg-white rounded shadow">
<div class="p-8"> <div class="p-8">
<%= content %> <%= content %>
<% if @show_close_button %>
<div class="flex justify-end items-center flex-wrap mt-6"> <div class="flex justify-end items-center flex-wrap mt-6">
<button class="btn-md btn-blue" data-action="click->modal#close:prevent">Close</button> <button class="btn-md btn-blue" data-action="click->modal#close:prevent">Close</button>
</div> </div>
<% end %>
</div> </div>
</div> </div>
</div> </div>
+3
View File
@@ -1,2 +1,5 @@
class ModalComponent < ViewComponent::Base class ModalComponent < ViewComponent::Base
def initialize(show_close_button: true)
@show_close_button = show_close_button
end
end end
+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
+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
@@ -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
@avatar = LdapManager::FetchAvatar.call(cn: @user.cn)
end
# POST /admin/users/:username/invitations
def create_invitations
amount = params[:amount].to_i
notify_user = ActiveRecord::Type::Boolean.new.cast(params[:notify_user])
CreateInvitations.call(user: @user, amount: amount, notify: notify_user)
redirect_to admin_user_path(@user.cn), flash: {
success: "Added #{amount} invitations to #{@user.cn}'s account"
}
end
# DELETE /admin/users/:username/invitations
def delete_invitations
invitations = @user.invitations.unused
amount = invitations.count
invitations.destroy_all
redirect_to admin_user_path(@user.cn), flash: {
success: "Removed #{amount} invitations from #{@user.cn}'s account"
}
end end
private private
def set_user def set_user
address = params[:address].split("@") @user = User.find_by(cn: params[:username], ou: Setting.primary_domain)
@user = User.where(cn: address.first, ou: address.last).first http_status :not_found unless @user
end end
def set_current_section def set_current_section
+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
@@ -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
+111 -45
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, only: [:invoice]
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?
@@ -32,64 +42,120 @@ class LnurlpayController < ApplicationController
} }
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
+5 -19
View File
@@ -3,8 +3,7 @@ class Rs::OauthController < ApplicationController
before_action :authenticate_user!, only: :create before_action :authenticate_user!, only: :create
def new def new
username, org = params[:useraddress].split("@") @user = User.where(cn: params[:username].downcase, ou: Setting.primary_domain).first
@user = User.where(cn: username.downcase, ou: org).first
@scopes = parse_scopes params[:scope] @scopes = parse_scopes params[:scope]
@redirect_uri = params[:redirect_uri] @redirect_uri = params[:redirect_uri]
@client_id = params[:client_id] @client_id = params[:client_id]
@@ -22,7 +21,7 @@ class Rs::OauthController < ApplicationController
unless current_user == @user unless current_user == @user
sign_out :user sign_out :user
redirect_to new_rs_oauth_url(@user.address, redirect_to new_rs_oauth_url(@user.cn,
scope: params[:scope], scope: params[:scope],
redirect_uri: params[:redirect_uri], redirect_uri: params[:redirect_uri],
client_id: params[:client_id], client_id: params[:client_id],
@@ -88,7 +87,7 @@ class Rs::OauthController < ApplicationController
permissions: permissions, permissions: permissions,
client_id: client_id, client_id: client_id,
redirect_uri: redirect_uri, redirect_uri: redirect_uri,
app_name: client_id, #TODO use user-defined name app_name: client_id,
expire_at: expire_at expire_at: expire_at
) )
@@ -96,28 +95,15 @@ class Rs::OauthController < ApplicationController
allow_other_host: true allow_other_host: true
end end
# GET /rs/oauth/token/:id/launch_app
def launch_app
auth = current_user.remote_storage_authorizations.find(params[:id])
redirect_to app_auth_url(auth), allow_other_host: true
end
private private
def require_signed_in_with_username def require_signed_in_with_username
unless user_signed_in? unless user_signed_in?
username, org = params[:useraddress].split("@") session[:user_return_to] = request.url
redirect_to new_user_session_path(cn: username, ou: org) redirect_to new_user_session_path(cn: params[:username], ou: Setting.primary_domain)
end end
end end
def app_auth_url(auth)
url = "#{auth.url}#remotestorage=#{current_user.address}"
url += "&access_token=#{auth.token}"
url
end
def hostname_of(uri) def hostname_of(uri)
uri.gsub(/http(s)?:\/\//, "").split(":")[0].split("/")[0] uri.gsub(/http(s)?:\/\//, "").split(":")[0].split("/")[0]
end end
+1 -1
View File
@@ -3,7 +3,7 @@ class Services::ChatController < Services::BaseController
before_action :require_service_available before_action :require_service_available
def show def show
@service_enabled = current_user.services_enabled.include?(:xmpp) @service_enabled = current_user.service_enabled?(:ejabberd)
end end
private private
@@ -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,10 +2,11 @@ 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_setup_url = "lndhub://#{current_user.ln_account}:#{current_user.ln_password}@#{ENV['LNDHUB_PUBLIC_URL']}" @wallet_setup_url = "lndhub://#{current_user.ln_account}:#{current_user.ln_password}@#{ENV['LNDHUB_PUBLIC_URL']}"
@@ -55,32 +56,12 @@ class Services::LightningController < ApplicationController
private private
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
@@ -3,7 +3,7 @@ class Services::MastodonController < Services::BaseController
before_action :require_service_available before_action :require_service_available
def show def show
@service_enabled = current_user.services_enabled.include?(:mastodon) @service_enabled = current_user.service_enabled?(:mastodon)
end end
private private
@@ -1,23 +1,25 @@
class Services::RemotestorageController < Services::BaseController class Services::RemotestorageController < Services::BaseController
before_action :authenticate_user! before_action :authenticate_user!
before_action :require_feature_enabled
before_action :require_service_available before_action :require_service_available
before_action :require_feature_enabled
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_available
http_status :not_found unless Setting.remotestorage_enabled?
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
+72 -31
View File
@@ -1,17 +1,22 @@
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
@@ -19,10 +24,15 @@ class SettingsController < ApplicationController
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]
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?
LdapManager::UpdateAvatar.call(dn: @user.dn, file: @user.avatar_new)
end end
redirect_to setting_path(@settings_section), flash: { redirect_to setting_path(@settings_section), flash: {
@@ -35,7 +45,7 @@ class SettingsController < ApplicationController
end end
def update_email def update_email
if @user.valid_ldap_authentication?(email_params[:current_password]) if @user.valid_ldap_authentication?(security_params[:current_password])
if @user.update email: email_params[:email] if @user.update email: email_params[:email]
redirect_to setting_path(:account), flash: { redirect_to setting_path(:account), flash: {
notice: 'Please confirm your new address using the confirmation link we just sent you.' notice: 'Please confirm your new address using the confirmation link we just sent you.'
@@ -51,6 +61,28 @@ class SettingsController < ApplicationController
end end
end end
def reset_email_password
@user.current_password = security_params[:current_password]
if @user.valid_ldap_authentication?(@user.current_password)
@user.current_password = nil
session[:new_email_password] = generate_email_password
hashed_password = hash_email_password(session[:new_email_password])
LdapManager::UpdateEmailPassword.call(dn: @user.dn, password_hash: hashed_password)
if @user.ldap_entry[:email_maildrop] != @user.address
LdapManager::UpdateEmailMaildrop.call(dn: @user.dn, address: @user.address)
end
redirect_to new_password_services_email_path
else
@validation_errors = {
current_password: [ "Wrong password. Try again!" ]
}
render :show, status: :forbidden
end
end
def reset_password def reset_password
current_user.send_reset_password_instructions current_user.send_reset_password_instructions
sign_out current_user sign_out current_user
@@ -59,40 +91,39 @@ class SettingsController < ApplicationController
end end
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 +136,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 +151,26 @@ 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, preferences: UserPreferences.pref_keys
:xmpp_exchange_contacts_with_invitees )
])
end end
def email_params def email_params
params.require(:user).permit(:email, :current_password) params.require(:user).permit(:email)
end 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 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( 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
+61 -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,61 @@ 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 Setting.mastodon_enabled && @user.service_enabled?(:mastodon)
# https://docs.joinmastodon.org/spec/webfinger/
jrd[:aliases] += mastodon_aliases
jrd[:links] += mastodon_links
end
{ "links" => links } 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 mastodon_aliases
# TODO use when OAuth routes are available [
# auth_url = new_rs_oauth_url(useraddress) "#{Setting.mastodon_public_url}/@#{@user.cn}",
auth_url = "https://example.com/rs/oauth" "#{Setting.mastodon_public_url}/users/#{@user.cn}"
storage_url = "#{Setting.rs_storage_url}/#{useraddress}" ]
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)
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 +89,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!(ln_account: @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 || 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
-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"
+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
+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
+2 -1
View File
@@ -1,8 +1,9 @@
import { Application } from "@hotwired/stimulus" import { Application } from "@hotwired/stimulus"
import { Modal, Tabs } from "tailwindcss-stimulus-components" 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('modal', Modal)
application.register('tabs', Tabs) application.register('tabs', Tabs)
@@ -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")
+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
+5 -1
View File
@@ -1,7 +1,7 @@
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", "extensibleObject"],
@@ -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
+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
+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
+25
View File
@@ -5,4 +5,29 @@ class NotificationMailer < ApplicationMailer
@subject = "Sats received" @subject = "Sats received"
mail to: @user.email, subject: @subject mail to: @user.email, subject: @subject
end end
def remotestorage_auth_created
@user = params[:user]
@auth = params[:auth]
@permissions = @auth.permissions.map do |p|
access = p.split(":")[1] == 'r' ? 'read' : 'read/write'
directory = p.split(':')[0] == '' ? 'all folders and files' : p.split(':')[0]
"#{access} #{directory}"
end
@subject = "New app connected to your storage"
mail to: @user.email, subject: @subject
end
def new_invitations_available
@user = params[:user]
@subject = "New invitations added to your account"
mail to: @user.email, subject: @subject
end
def bitcoin_donation_confirmed
@user = params[:user]
@donation = params[:donation]
@subject = "Donation confirmed"
mail to: @user.email, subject: @subject
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
@@ -0,0 +1,13 @@
module Settings
module DroneCiSettings
extend ActiveSupport::Concern
included do
field :droneci_public_url, type: :string,
default: ENV["DRONECI_PUBLIC_URL"].presence
field :droneci_enabled, type: :boolean,
default: ENV["DRONECI_PUBLIC_URL"].present?
end
end
end
@@ -0,0 +1,19 @@
module Settings
module EjabberdSettings
extend ActiveSupport::Concern
included do
field :ejabberd_enabled, type: :boolean,
default: ENV["EJABBERD_API_URL"].present?
field :ejabberd_api_url, type: :string,
default: ENV["EJABBERD_API_URL"].presence
field :ejabberd_admin_url, type: :string,
default: ENV["EJABBERD_ADMIN_URL"].presence
field :ejabberd_buddy_roster, type: :string,
default: "Buddies"
end
end
end
@@ -0,0 +1,28 @@
module Settings
module EmailSettings
extend ActiveSupport::Concern
included do
field :email_enabled, type: :boolean,
default: ENV["EMAIL_SMTP_HOST"].present?
# field :email_smtp_host, type: :string,
# default: ENV["EMAIL_SMTP_HOST"].presence
#
# field :email_smtp_port, type: :string,
# default: ENV["EMAIL_SMTP_PORT"].presence || 587
#
# field :email_smtp_enable_starttls, type: :string,
# default: ENV["EMAIL_SMTP_PORT"].presence || true
#
# field :email_auth_method, type: :string,
# default: ENV["EMAIL_AUTH_METHOD"].presence || "plain"
#
# field :email_imap_host, type: :string,
# default: ENV["EMAIL_IMAP_HOST"].presence
#
# field :email_imap_port, type: :string,
# default: ENV["EMAIL_IMAP_PORT"].presence || 993
end
end
end
@@ -0,0 +1,34 @@
module Settings
module GeneralSettings
extend ActiveSupport::Concern
included do
field :primary_domain, type: :string,
default: ENV["PRIMARY_DOMAIN"].presence
field :accounts_domain, type: :string,
default: ENV["AKKOUNTS_DOMAIN"].presence
#
# Internal services
#
field :redis_url, type: :string,
default: ENV["REDIS_URL"] || "redis://localhost:6379/0"
field :s3_enabled, type: :boolean,
default: ENV["S3_ENABLED"] && ENV["S3_ENABLED"].to_s != "false"
field :sentry_enabled, type: :boolean, readonly: true,
default: ENV["SENTRY_DSN"].present?
#
# Registrations
#
field :reserved_usernames, type: :array, default: %w[
account accounts donations mail webmaster support
]
end
end
end
@@ -0,0 +1,13 @@
module Settings
module GiteaSettings
extend ActiveSupport::Concern
included do
field :gitea_public_url, type: :string,
default: ENV["GITEA_PUBLIC_URL"].presence
field :gitea_enabled, type: :boolean,
default: ENV["GITEA_PUBLIC_URL"].present?
end
end
end
@@ -0,0 +1,25 @@
module Settings
module LightningNetworkSettings
extend ActiveSupport::Concern
included do
field :lndhub_api_url, type: :string,
default: ENV["LNDHUB_API_URL"].presence
field :lndhub_enabled, type: :boolean,
default: ENV["LNDHUB_API_URL"].present?
field :lndhub_admin_token, type: :string,
default: ENV["LNDHUB_ADMIN_TOKEN"].presence
field :lndhub_admin_enabled, type: :boolean,
default: ENV["LNDHUB_ADMIN_UI"] || false
field :lndhub_public_key, type: :string,
default: (ENV["LNDHUB_PUBLIC_KEY"] || "")
field :lndhub_keysend_enabled, type: :boolean,
default: -> { self.lndhub_public_key.present? }
end
end
end
@@ -0,0 +1,16 @@
module Settings
module MastodonSettings
extend ActiveSupport::Concern
included do
field :mastodon_public_url, type: :string,
default: ENV["MASTODON_PUBLIC_URL"].presence
field :mastodon_enabled, type: :boolean,
default: ENV["MASTODON_PUBLIC_URL"].present?
field :mastodon_address_domain, type: :string,
default: ENV["MASTODON_ADDRESS_DOMAIN"].presence || self.primary_domain
end
end
end
@@ -0,0 +1,13 @@
module Settings
module MediaWikiSettings
extend ActiveSupport::Concern
included do
field :mediawiki_public_url, type: :string,
default: ENV["MEDIAWIKI_PUBLIC_URL"].presence
field :mediawiki_enabled, type: :boolean,
default: ENV["MEDIAWIKI_PUBLIC_URL"].present?
end
end
end
@@ -0,0 +1,25 @@
module Settings
module NostrSettings
extend ActiveSupport::Concern
included do
field :nostr_enabled, type: :boolean,
default: ENV["NOSTR_PRIVATE_KEY"].present?
field :nostr_private_key, type: :string,
default: ENV["NOSTR_PRIVATE_KEY"].presence
field :nostr_public_key, type: :string,
default: ENV["NOSTR_PUBLIC_KEY"].presence
field :nostr_public_key_primary_domain, type: :string,
default: ENV["NOSTR_PUBLIC_KEY_PRIMARY_DOMAIN"].presence
field :nostr_relay_url, type: :string,
default: ENV["NOSTR_RELAY_URL"].presence
field :nostr_zaps_relay_limit, type: :integer,
default: 12
end
end
end
@@ -0,0 +1,9 @@
module Settings
module OpenCollectiveSettings
extend ActiveSupport::Concern
included do
field :opencollective_enabled, type: :boolean, default: true
end
end
end
@@ -0,0 +1,16 @@
module Settings
module RemoteStorageSettings
extend ActiveSupport::Concern
included do
field :remotestorage_enabled, type: :boolean,
default: ENV["RS_STORAGE_URL"].present?
field :rs_storage_url, type: :string,
default: ENV["RS_STORAGE_URL"].presence
field :rs_redis_url, type: :string,
default: ENV["RS_REDIS_URL"] || "redis://localhost:6379/1"
end
end
end
@@ -0,0 +1,11 @@
module Settings
module XmppSettings
extend ActiveSupport::Concern
included do
field :xmpp_default_rooms, type: :array, default: []
field :xmpp_autojoin_default_rooms, type: :boolean, default: false
field :xmpp_notifications_from_address, type: :string, default: primary_domain
end
end
end
+19 -6
View File
@@ -4,12 +4,25 @@ class Donation < ApplicationRecord
# Validations # Validations
validates_presence_of :user validates_presence_of :user
validates_presence_of :amount_sats validates_presence_of :donation_method,
validates_presence_of :paid_at inclusion: { in: %w[ custom btcpay lndhub ] }
validates_presence_of :payment_status, allow_nil: true,
# Hooks inclusion: { in: %w[ processing settled ] }
# TODO before_create :store_fiat_value validates_presence_of :paid_at, allow_nil: true
validates_presence_of :amount_sats, allow_nil: true
validates_presence_of :fiat_amount, allow_nil: true
validates_presence_of :fiat_currency, allow_nil: true,
inclusion: { in: %w[ EUR USD ] }
#Scopes #Scopes
scope :completed, -> { where.not(paid_at: nil) } scope :processing, -> { where(payment_status: "processing") }
scope :completed, -> { where(payment_status: "settled") }
def processing?
payment_status == "processing"
end
def completed?
payment_status == "settled"
end
end end
+58 -7
View File
@@ -1,7 +1,8 @@
class RemoteStorageAuthorization < ApplicationRecord class RemoteStorageAuthorization < ApplicationRecord
belongs_to :user belongs_to :user
belongs_to :web_app, class_name: "AppCatalog::WebApp", optional: true
serialize :permissions serialize :permissions unless Rails.env.production?
validates_presence_of :permissions validates_presence_of :permissions
validates_presence_of :client_id validates_presence_of :client_id
@@ -15,22 +16,36 @@ class RemoteStorageAuthorization < ApplicationRecord
before_create :generate_token before_create :generate_token
before_create :store_token_in_redis before_create :store_token_in_redis
before_create :find_or_create_web_app
after_create :schedule_token_expiry after_create :schedule_token_expiry
after_create :notify_user
before_destroy :delete_token_from_redis before_destroy :delete_token_from_redis
after_destroy :remove_token_expiry_job after_destroy :remove_token_expiry_job
def url def url
if self.redirect_uri uri = URI.parse self.redirect_uri
uri = URI.parse self.redirect_uri "#{uri.scheme}://#{client_id}"
"#{uri.scheme}://#{client_id}" end
def launch_url
return url unless web_app && web_app.metadata[:start_url].present?
start_url = web_app.metadata[:start_url]
if start_url.match("^https?:\/\/")
return start_url.start_with?(url) ? start_url : url
else else
"http://#{client_id}" path = start_url.gsub(/^\.\.\//, "").gsub(/^\.\//, "").gsub(/^\//, "")
"#{url}/#{path}"
end end
end end
def delete_token_from_redis def delete_token_from_redis
key = "rs:authorizations:#{user.address}:#{token}" key = "authorizations:#{user.cn}:#{token}"
redis.srem? key, redis.smembers(key) redis.srem? key, redis.smembers(key)
rescue => e
Rails.logger.error e
Sentry.capture_exception(e) if Setting.sentry_enabled?
end end
private private
@@ -44,7 +59,7 @@ class RemoteStorageAuthorization < ApplicationRecord
end end
def store_token_in_redis def store_token_in_redis
redis.sadd "rs:authorizations:#{user.address}:#{token}", permissions redis.sadd "authorizations:#{user.cn}:#{token}", permissions
end end
def schedule_token_expiry def schedule_token_expiry
@@ -60,4 +75,40 @@ class RemoteStorageAuthorization < ApplicationRecord
job.delete if job.display_args == [id] job.delete if job.display_args == [id]
end end
end end
def find_or_create_web_app
if looks_like_hosted_origin?
web_app = AppCatalog::WebApp.find_or_create_by!(url: self.url)
web_app.update_metadata unless web_app.name.present?
self.web_app = web_app
self.app_name = web_app.name.presence || client_id
else
self.app_name = client_id
end
end
def looks_like_hosted_origin?
uri = URI.parse self.redirect_uri
!!(uri.host =~ /(?=^.{4,253}$)(^((?!-)[a-zA-Z0-9-]{0,62}[a-zA-Z0-9]\.)+[a-zA-Z]{2,63}$)/)
rescue URI::InvalidURIError
false
end
def notify_user
notify = user.preferences[:remotestorage_notify_auth_created]
case notify
when "xmpp"
router = Router.new
payload = {
type: "normal", to: user.address,
from: Setting.xmpp_notifications_from_address,
body: "You have just granted '#{self.client_id}' access to your Kosmos Storage. Visit your Storage dashboard to check on your connected apps and revoke permissions anytime: #{router.services_storage_url}"
}
XmppSendMessageJob.perform_later(payload)
when "email"
NotificationMailer.with(user: user, auth: self)
.remotestorage_auth_created.deliver_later
end
end
end end
+23 -142
View File
@@ -2,149 +2,30 @@
class Setting < RailsSettings::Base class Setting < RailsSettings::Base
cache_prefix { "v1" } cache_prefix { "v1" }
field :primary_domain, type: :string, Dir[Rails.root.join('app', 'models', 'concerns', 'settings', '*.rb')].each do |file|
default: ENV["PRIMARY_DOMAIN"].presence require file
end
field :accounts_domain, type: :string, include Settings::GeneralSettings
default: ENV["AKKOUNTS_DOMAIN"].presence include Settings::BtcpaySettings
include Settings::DiscourseSettings
include Settings::DroneCiSettings
include Settings::EjabberdSettings
include Settings::EmailSettings
include Settings::GiteaSettings
include Settings::LightningNetworkSettings
include Settings::MastodonSettings
include Settings::MediaWikiSettings
include Settings::NostrSettings
include Settings::OpenCollectiveSettings
include Settings::RemoteStorageSettings
include Settings::XmppSettings
# def self.available_services
# Internal services known_services = SERVICES[:external].keys
# known_services.select {|s| Setting.send "#{s}_enabled?" }
end
field :redis_url, type: :string, field :default_services, type: :array,
default: ENV["REDIS_URL"] || "redis://localhost:6379/0" default: self.available_services
#
# Registrations
#
field :reserved_usernames, type: :array, default: %w[
account accounts donations mail webmaster support
]
#
# XMPP
#
field :xmpp_default_rooms, type: :array, default: []
field :xmpp_autojoin_default_rooms, type: :boolean, default: false
field :xmpp_notifications_from_address, type: :string, default: primary_domain
#
# Sentry
#
field :sentry_enabled, type: :boolean, readonly: true,
default: (ENV["SENTRY_DSN"].present?.to_s || false)
#
# Discourse
#
field :discourse_public_url, type: :string,
default: ENV["DISCOURSE_PUBLIC_URL"].presence
field :discourse_enabled, type: :boolean,
default: (ENV["DISCOURSE_PUBLIC_URL"].present?.to_s || false)
field :discourse_connect_secret, type: :string,
default: ENV["DISCOURSE_CONNECT_SECRET"].presence
#
# Drone CI
#
field :droneci_public_url, type: :string,
default: ENV["DRONECI_PUBLIC_URL"].presence
field :droneci_enabled, type: :boolean,
default: (ENV["DRONECI_PUBLIC_URL"].present?.to_s || false)
#
# ejabberd
#
field :ejabberd_enabled, type: :boolean,
default: (ENV["EJABBERD_API_URL"].present?.to_s || false)
field :ejabberd_api_url, type: :string,
default: ENV["EJABBERD_API_URL"].presence
field :ejabberd_admin_url, type: :string,
default: ENV["EJABBERD_ADMIN_URL"].presence
field :ejabberd_buddy_roster, type: :string,
default: "Buddies"
#
# Gitea
#
field :gitea_public_url, type: :string,
default: ENV["GITEA_PUBLIC_URL"].presence
field :gitea_enabled, type: :boolean,
default: (ENV["GITEA_PUBLIC_URL"].present?.to_s || false)
#
# Lightning Network
#
field :lndhub_api_url, type: :string,
default: ENV["LNDHUB_API_URL"].presence
field :lndhub_enabled, type: :boolean,
default: (ENV["LNDHUB_API_URL"].present?.to_s || false)
field :lndhub_admin_enabled, type: :boolean,
default: (ENV["LNDHUB_ADMIN_UI"] || false)
field :lndhub_public_key, type: :string,
default: (ENV["LNDHUB_PUBLIC_KEY"] || "")
field :lndhub_keysend_enabled, type: :boolean,
default: -> { self.lndhub_public_key.present?.to_s || false }
#
# Mastodon
#
field :mastodon_public_url, type: :string,
default: ENV["MASTODON_PUBLIC_URL"].presence
field :mastodon_enabled, type: :boolean,
default: (ENV["MASTODON_PUBLIC_URL"].present?.to_s || false)
field :mastodon_address_domain, type: :string,
default: ENV["MASTODON_ADDRESS_DOMAIN"].presence || self.primary_domain
#
# MediaWiki
#
field :mediawiki_public_url, type: :string,
default: ENV["MEDIAWIKI_PUBLIC_URL"].presence
field :mediawiki_enabled, type: :boolean,
default: (ENV["MEDIAWIKI_PUBLIC_URL"].present?.to_s || false)
#
# Nostr
#
field :nostr_enabled, type: :boolean, default: true
#
# RemoteStorage
#
field :remotestorage_enabled, type: :boolean,
default: (ENV["RS_STORAGE_URL"].present?.to_s || false)
field :rs_storage_url, type: :string,
default: ENV["RS_STORAGE_URL"].presence
field :rs_redis_url, type: :string,
default: ENV["RS_REDIS_URL"] || "redis://localhost:6379/1"
end end
+65 -19
View File
@@ -1,24 +1,34 @@
require 'nostr'
class User < ApplicationRecord class User < ApplicationRecord
include EmailValidatable include EmailValidatable
attr_accessor :display_name attr_accessor :display_name
attr_accessor :avatar_new
attr_accessor :current_password
serialize :preferences, UserPreferences serialize :preferences, coder: UserPreferences
#
# Relations # Relations
#
has_many :invitations, dependent: :destroy has_many :invitations, dependent: :destroy
has_one :invitation, inverse_of: :invitee, foreign_key: 'invited_user_id' has_one :invitation, inverse_of: :invitee, foreign_key: 'invited_user_id'
has_one :inviter, through: :invitation, source: :user has_one :inviter, through: :invitation, source: :user
has_many :invitees, through: :invitations has_many :invitees, through: :invitations
has_many :donations, dependent: :nullify has_many :donations, dependent: :nullify
has_many :remote_storage_authorizations
has_many :zaps
has_one :lndhub_user, class_name: "LndhubUser", inverse_of: "user", has_one :lndhub_user, class_name: "LndhubUser", inverse_of: "user",
primary_key: "ln_account", foreign_key: "login" primary_key: "ln_account", foreign_key: "login"
has_many :accounts, through: :lndhub_user has_many :accounts, through: :lndhub_user
has_many :remote_storage_authorizations #
# Validations
#
validates_uniqueness_of :cn, scope: :ou validates_uniqueness_of :cn, scope: :ou
validates_length_of :cn, minimum: 3 validates_length_of :cn, minimum: 3
@@ -30,7 +40,8 @@ class User < ApplicationRecord
message: "is invalid. Usernames need to start with a letter." message: "is invalid. Usernames need to start with a letter."
# FIXME This needs a server restart to apply values # FIXME This needs a server restart to apply values
validates_format_of :cn, without: /\A(#{Setting.reserved_usernames.join('|')})\z/i, validates_format_of :cn, without: /\A(#{Setting.reserved_usernames.join('|')})\z/i,
message: "has already been taken" message: "has already been taken",
unless: Proc.new{ |u| u.persisted? }
validates_uniqueness_of :email validates_uniqueness_of :email
validates :email, email: true validates :email, email: true
@@ -38,12 +49,20 @@ class User < ApplicationRecord
validates_length_of :display_name, minimum: 3, maximum: 35, allow_blank: true, validates_length_of :display_name, minimum: 3, maximum: 35, allow_blank: true,
if: -> { defined?(@display_name) } if: -> { defined?(@display_name) }
validates_uniqueness_of :nostr_pubkey, allow_blank: true validate :acceptable_avatar
#
# Scopes
#
scope :confirmed, -> { where.not(confirmed_at: nil) } scope :confirmed, -> { where.not(confirmed_at: nil) }
scope :pending, -> { where(confirmed_at: nil) } scope :pending, -> { where(confirmed_at: nil) }
scope :all_except, -> (user) { where.not(id: user) } scope :all_except, -> (user) { where.not(id: user) }
#
# Encrypted database columns
#
has_encrypted :ln_login, :ln_password has_encrypted :ln_login, :ln_password
# Include default devise modules. Others available are: # Include default devise modules. Others available are:
@@ -70,13 +89,12 @@ class User < ApplicationRecord
def devise_after_confirmation def devise_after_confirmation
if ldap_entry[:mail] != self.email if ldap_entry[:mail] != self.email
# E-Mail update confirmed # E-Mail update confirmed
LdapManager::UpdateEmail.call(self.dn, self.email) LdapManager::UpdateEmail.call(dn: self.dn, address: self.email)
else else
# TODO Make configurable
# E-Mail from signup confirmed (i.e. account activation) # E-Mail from signup confirmed (i.e. account activation)
enable_service %w[ discourse gitea mediawiki xmpp ] enable_default_services
#TODO enable in development when we have easy setup of ejabberd etc. # TODO enable in development when we have easy setup of ejabberd etc.
return if Rails.env.development? || !Setting.ejabberd_enabled? return if Rails.env.development? || !Setting.ejabberd_enabled?
XmppExchangeContactsJob.perform_later(inviter, self) if inviter.present? XmppExchangeContactsJob.perform_later(inviter, self) if inviter.present?
@@ -112,7 +130,7 @@ class User < ApplicationRecord
def mastodon_address def mastodon_address
return nil unless Setting.mastodon_enabled? return nil unless Setting.mastodon_enabled?
"#{self.cn}@#{Setting.mastodon_address_domain}" "#{self.cn.gsub("-", "_")}@#{Setting.mastodon_address_domain}"
end end
def valid_attribute?(attribute_name) def valid_attribute?(attribute_name)
@@ -120,10 +138,8 @@ class User < ApplicationRecord
self.errors[attribute_name].blank? self.errors[attribute_name].blank?
end end
def ln_create_invoice(payload) def enable_default_services
lndhub = Lndhub.new enable_service Setting.default_services
lndhub.authenticate self
lndhub.addinvoice payload
end end
def dn def dn
@@ -140,22 +156,39 @@ class User < ApplicationRecord
@display_name ||= ldap_entry[:display_name] @display_name ||= ldap_entry[:display_name]
end end
def nostr_pubkey
@nostr_pubkey ||= ldap_entry[:nostr_key]
end
def nostr_pubkey_bech32
return nil unless nostr_pubkey.present?
Nostr::PublicKey.new(nostr_pubkey).to_bech32
end
def avatar
@avatar_base64 ||= LdapManager::FetchAvatar.call(cn: cn)
end
def services_enabled def services_enabled
ldap_entry[:service] || [] ldap_entry[:services_enabled] || []
end
def service_enabled?(name)
services_enabled.map(&:to_sym).include?(name.to_sym)
end end
def enable_service(service) def enable_service(service)
current_services = services_enabled current_services = services_enabled
new_services = Array(service).map(&:to_s) new_services = Array(service).map(&:to_s)
services = (current_services + new_services).uniq services = (current_services + new_services).uniq.sort
ldap.replace_attribute(dn, :service, services) ldap.replace_attribute(dn, :serviceEnabled, services)
end end
def disable_service(service) def disable_service(service)
current_services = services_enabled current_services = services_enabled
disabled_services = Array(service).map(&:to_s) disabled_services = Array(service).map(&:to_s)
services = (current_services - disabled_services).uniq services = (current_services - disabled_services).uniq.sort
ldap.replace_attribute(dn, :service, services) ldap.replace_attribute(dn, :serviceEnabled, services)
end end
def disable_all_services def disable_all_services
@@ -168,4 +201,17 @@ class User < ApplicationRecord
return @ldap_service if defined?(@ldap_service) return @ldap_service if defined?(@ldap_service)
@ldap_service = LdapService.new @ldap_service = LdapService.new
end end
def acceptable_avatar
return unless avatar_new.present?
if avatar_new.size > 1.megabyte
errors.add(:avatar, "file size is too large")
end
acceptable_types = ["image/jpeg", "image/png"]
unless acceptable_types.include?(avatar_new.content_type)
errors.add(:avatar, "must be a JPEG or PNG file")
end
end
end end
+4
View File
@@ -26,4 +26,8 @@ class UserPreferences
end end
hash.stringify_keys!.to_h hash.stringify_keys!.to_h
end end
def self.pref_keys
DEFAULT_PREFS.keys.map(&:to_sym)
end
end end
+20
View File
@@ -0,0 +1,20 @@
class Zap < ApplicationRecord
belongs_to :user
scope :settled, -> { where.not(settled_at: nil) }
scope :unpaid, -> { where(settled_at: nil) }
def request_event
nostr_event_from_hash(request)
end
def receipt_event
nostr_event_from_hash(receipt)
end
private
def nostr_event_from_hash(hash)
Nostr::Event.new(**hash.symbolize_keys)
end
end
@@ -0,0 +1,63 @@
require "manifique"
require "down"
module AppCatalogManager
class UpdateMetadata < AppCatalogManagerService
def initialize(app:)
@app = app
end
def call
agent = Manifique::Agent.new(url: @app.url)
metadata = agent.fetch_metadata
@app.name = metadata.name
[:name, :short_name, :description, :theme_color, :background_color,
:display, :start_url, :scope, :share_target, :icons].each do |prop|
@app.metadata[prop] = metadata.send(prop) if prop
end
@app.save!
# TODO move icon downloads to separate, async job
if icon = metadata.select_icon(sizes: "256x256") ||
icon = metadata.select_icon(sizes: "192x192")
attach_remote_image(:icon, icon)
# TODO elsif get whatever is available
end
if apple_touch_icon = metadata.select_icon(purpose: "apple-touch-icon")
attach_remote_image(:apple_touch_icon, apple_touch_icon)
end
rescue Manifique::Error => e
msg = "Fetching web app manifest failed for #{e.url}: #{e.type}"
Rails.logger.warn(msg)
Sentry.capture_message(msg) if Setting.sentry_enabled?
false
end
def attach_remote_image(attachment_name, icon)
if icon['src'].start_with?("http")
download_url = icon['src']
else
download_url = "#{@app.url}/#{icon["src"].gsub(/^\//,'')}"
end
filename = "#{attachment_name}-#{Time.now.to_i}.png"
key = "web_apps/#{@app.id}/icons/#{filename}"
begin
tempfile = Down.download(download_url)
@app.send(attachment_name).attach(key: key, io: tempfile, filename: filename)
rescue Down::NotFound
msg = "Download of \"#{attachment_name}\" failed: NotFound error for #{download_url}"
Rails.logger.warn(msg)
Sentry.capture_message(msg)
rescue => e
Rails.logger.warn "Saving attachment \"#{attachment_name}\" failed: \"#{e.message}\""
Sentry.capture_exception(e) if Setting.sentry_enabled?
end
end
end
end
@@ -0,0 +1,2 @@
class AppCatalogManagerService < ApplicationService
end
+2 -2
View File
@@ -1,7 +1,7 @@
class ApplicationService class ApplicationService
# This enables executing a service's `#call` method directly via # This enables executing a service's `#call` method directly via
# `MyService.call(args)`, without creating a class instance it first. # `MyService.call(args)`, without creating a class instance it first.
def self.call(*args, &block) def self.call(**args, &block)
new(*args, &block).call new(**args, &block).call
end end
end end

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