From e2618de7c645ebb208153eb54ce8663987adea5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Sun, 19 Feb 2023 13:44:48 +0800 Subject: [PATCH 01/34] Add time limit for inactive sessions closes #8 --- app/javascript/controllers/notification_controller.js | 4 ++++ app/models/user.rb | 3 ++- config/initializers/devise.rb | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/javascript/controllers/notification_controller.js b/app/javascript/controllers/notification_controller.js index e0291ce..9e09dc4 100644 --- a/app/javascript/controllers/notification_controller.js +++ b/app/javascript/controllers/notification_controller.js @@ -4,6 +4,10 @@ export default class extends Controller { static targets = ["buttons", "countdown"] connect() { + // Devise timeoutable ends up adding a second flash message without content + // TODO investigate bug + if (this.element.textContent.trim() == "true") return; + const timeoutSeconds = parseInt(this.data.get("timeout")); setTimeout(() => { diff --git a/app/models/user.rb b/app/models/user.rb index 5734369..a718f36 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -38,7 +38,8 @@ class User < ApplicationRecord devise :ldap_authenticatable, :confirmable, :recoverable, - :validatable + :validatable, + :timeoutable def ldap_before_save self.email = Devise::LDAP::Adapter.get_ldap_param(self.cn, "mail").first diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 3b0ffeb..e1df960 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -210,7 +210,7 @@ Devise.setup do |config| # ==> Configuration for :timeoutable # The time you want to timeout the user session without activity. After this # time the user will be asked for credentials again. Default is 30 minutes. - # config.timeout_in = 30.minutes + config.timeout_in = 24.hours # ==> Configuration for :lockable # Defines which strategy will be used to lock an account. From 8a7016a30b0ff3b266778b5ec084d1d6aead7718 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Sun, 19 Mar 2023 18:06:18 +0700 Subject: [PATCH 02/34] Add remember-me function for sign-in When checked, remember user for 2 weeks. Otherwise expire session after 30 minutes. --- .../form_elements/toggle_component.html.erb | 2 +- .../form_elements/toggle_component.rb | 3 +- app/models/user.rb | 3 +- app/views/devise/sessions/new.html.erb | 38 +++++++++++++++---- config/initializers/devise.rb | 6 +-- ...101128_add_remember_created_at_to_users.rb | 6 +++ db/schema.rb | 4 +- 7 files changed, 48 insertions(+), 14 deletions(-) create mode 100644 db/migrate/20230319101128_add_remember_created_at_to_users.rb diff --git a/app/components/form_elements/toggle_component.html.erb b/app/components/form_elements/toggle_component.html.erb index 41a6708..8d6faf9 100644 --- a/app/components/form_elements/toggle_component.html.erb +++ b/app/components/form_elements/toggle_component.html.erb @@ -1,6 +1,6 @@ <%= button_tag type: "button", name: "toggle", data: @data, role: "switch", aria: { checked: @enabled.to_s }, - disabled: !@input_enabled, + tabindex: @tabindex, disabled: !@input_enabled, class: "#{ @enabled ? 'bg-blue-600' : 'bg-gray-200' } #{ @class_names.present? ? @class_names : '' } relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer diff --git a/app/components/form_elements/toggle_component.rb b/app/components/form_elements/toggle_component.rb index d2f5eae..8bb7181 100644 --- a/app/components/form_elements/toggle_component.rb +++ b/app/components/form_elements/toggle_component.rb @@ -2,11 +2,12 @@ module FormElements class ToggleComponent < ViewComponent::Base - def initialize(enabled:, input_enabled: true, data: nil, class_names: nil) + def initialize(enabled:, input_enabled: true, data: nil, class_names: nil, tabindex: nil) @enabled = !!enabled @input_enabled = input_enabled @data = data @class_names = class_names + @tabindex = tabindex end end end diff --git a/app/models/user.rb b/app/models/user.rb index a718f36..a518d27 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -39,7 +39,8 @@ class User < ApplicationRecord :confirmable, :recoverable, :validatable, - :timeoutable + :timeoutable, + :rememberable def ldap_before_save self.email = Devise::LDAP::Adapter.get_ldap_param(self.cn, "mail").first diff --git a/app/views/devise/sessions/new.html.erb b/app/views/devise/sessions/new.html.erb index 687d90a..f7c0a70 100644 --- a/app/views/devise/sessions/new.html.erb +++ b/app/views/devise/sessions/new.html.erb @@ -7,19 +7,43 @@ <%= f.label :cn, 'User', class: 'block mb-2 font-bold' %>

<%= f.text_field :cn, autofocus: true, autocomplete: "username", - required: true, class: "relative grow"%> + required: true, class: "relative grow", tabindex: "1" %> @ kosmos.org

-

+

<%= f.label :password, class: 'block mb-2 font-bold' %> <%= f.password_field :password, autocomplete: "current-password", - required: true, class: "w-full"%> + required: true, class: "w-full", tabindex: "2" %>

-

- <%= f.submit "Log in", class: 'btn-md btn-blue w-full' %> + + <%= tag.div class: "flex items-center mb-8 gap-x-3", data: { + controller: "settings--toggle", + :'settings--toggle-switch-enabled-value' => "false" + } do %> +

+ <%= render FormElements::ToggleComponent.new( + enabled: false, input_enabled: true, class_names: "hidden", + tabindex: "3", data: { + :'settings--toggle-target' => "button", + action: "settings--toggle#toggleSwitch" + }) %> + <%= f.check_box :remember_me, { + checked: false, + data: { :'settings--toggle-target' => "checkbox" } + }, "true", "false" %> +
+ <%= f.label :remember_me, + class: "text-gray-500 flex flex-col", + data: { action: "click->settings--toggle#toggleSwitch" } %> +

+ <%= link_to "Forgot your password?", new_password_path(resource_name), + class: "text-gray-500 underline" %>
+

+ <% end %> + +

+ <%= f.submit "Log in", class: 'btn-md btn-blue w-full', tabindex: "4" %>

<% end %> - - <%= render "devise/shared/links" %> <% end %> diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index e1df960..bb264bf 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -186,13 +186,13 @@ Devise.setup do |config| # ==> Configuration for :rememberable # The time the user will be remembered without asking for credentials again. - # config.remember_for = 2.weeks + config.remember_for = 2.weeks # Invalidates all the remember me tokens when the user signs out. config.expire_all_remember_me_on_sign_out = true # If true, extends the user's remember period when remembered via cookie. - # config.extend_remember_period = false + config.extend_remember_period = true # Options to be passed to the created cookie. For instance, you can set # secure: true in order to force SSL only cookies. @@ -210,7 +210,7 @@ Devise.setup do |config| # ==> Configuration for :timeoutable # The time you want to timeout the user session without activity. After this # time the user will be asked for credentials again. Default is 30 minutes. - config.timeout_in = 24.hours + config.timeout_in = 30.minutes # ==> Configuration for :lockable # Defines which strategy will be used to lock an account. diff --git a/db/migrate/20230319101128_add_remember_created_at_to_users.rb b/db/migrate/20230319101128_add_remember_created_at_to_users.rb new file mode 100644 index 0000000..6457dab --- /dev/null +++ b/db/migrate/20230319101128_add_remember_created_at_to_users.rb @@ -0,0 +1,6 @@ +class AddRememberCreatedAtToUsers < ActiveRecord::Migration[7.0] + def change + add_column :users, :remember_created_at, :datetime + add_column :users, :remember_token, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index f87e1b0..a292eed 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2023_02_23_115536) do +ActiveRecord::Schema[7.0].define(version: 2023_03_19_101128) do create_table "donations", force: :cascade do |t| t.integer "user_id" t.integer "amount_sats" @@ -57,6 +57,8 @@ ActiveRecord::Schema[7.0].define(version: 2023_02_23_115536) do t.text "ln_login_ciphertext" t.text "ln_password_ciphertext" t.string "ln_account" + t.datetime "remember_created_at" + t.string "remember_token" t.index ["email"], name: "index_users_on_email", unique: true t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true end From a8a8fba14c96fbf0663f95d7d2e92c2adb20e295 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Sun, 19 Mar 2023 18:07:09 +0700 Subject: [PATCH 03/34] Change styling of Devise shared links --- app/assets/stylesheets/components/links.css | 6 ------ app/views/devise/shared/_links.html.erb | 22 ++++++++++++--------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/app/assets/stylesheets/components/links.css b/app/assets/stylesheets/components/links.css index c46f68b..92c9c75 100644 --- a/app/assets/stylesheets/components/links.css +++ b/app/assets/stylesheets/components/links.css @@ -5,10 +5,4 @@ &:visited { @apply text-indigo-600; } &:active { @apply text-red-600; } } - - .devise-links { - a { - @apply ks-text-link; - } - } } diff --git a/app/views/devise/shared/_links.html.erb b/app/views/devise/shared/_links.html.erb index b1d1ccd..2499950 100644 --- a/app/views/devise/shared/_links.html.erb +++ b/app/views/devise/shared/_links.html.erb @@ -1,25 +1,29 @@ From f199d5d12ae48a768f7876fb9577de9da75e272c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Mon, 27 Mar 2023 12:46:14 +0200 Subject: [PATCH 04/34] Add (optional) Sentry integration A Sentry DSN can be set via `SENTRY_DSN` and authenticated users will be tagged with ID and username (cn) in events. --- Gemfile | 4 ++++ Gemfile.lock | 7 +++++++ app/controllers/application_controller.rb | 12 ++++++++++++ app/models/setting.rb | 7 +++++++ config/initializers/sentry.rb | 9 +++++++++ config/routes.rb | 2 +- 6 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 config/initializers/sentry.rb diff --git a/Gemfile b/Gemfile index a1918c1..52045dd 100644 --- a/Gemfile +++ b/Gemfile @@ -48,6 +48,10 @@ gem 'faraday' gem 'sidekiq', '< 7' gem 'sidekiq-scheduler' +# Monitoring +gem "sentry-ruby" +gem "sentry-rails" + group :development, :test do # Use sqlite3 as the database for Active Record gem 'sqlite3', '~> 1.4' diff --git a/Gemfile.lock b/Gemfile.lock index e788bef..7c655ba 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -254,6 +254,11 @@ GEM ruby2_keywords (0.0.5) rufus-scheduler (3.8.2) fugit (~> 1.1, >= 1.1.6) + sentry-rails (5.8.0) + railties (>= 5.0) + sentry-ruby (~> 5.8.0) + sentry-ruby (5.8.0) + concurrent-ruby (~> 1.0, >= 1.0.2) sidekiq (6.5.5) connection_pool (>= 2.2.2) rack (~> 2.0) @@ -335,6 +340,8 @@ DEPENDENCIES rails-settings-cached (~> 2.8.3) rqrcode (~> 2.0) rspec-rails + sentry-rails + sentry-ruby sidekiq (< 7) sidekiq-scheduler sprockets-rails diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index c672886..ee049bc 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -3,6 +3,18 @@ class ApplicationController < ActionController::Base render :text => exception, :status => 500 end + before_action :sentry_set_user + + def sentry_set_user + return unless Setting.sentry_enabled + + if user_signed_in? + Sentry.set_user(id: current_user.id, username: current_user.cn) + else + Sentry.set_user({}) + end + end + def require_user_signed_in unless user_signed_in? redirect_to welcome_path and return diff --git a/app/models/setting.rb b/app/models/setting.rb index 56aa785..e86a79c 100644 --- a/app/models/setting.rb +++ b/app/models/setting.rb @@ -10,6 +10,13 @@ class Setting < RailsSettings::Base account accounts donations mail webmaster support ] + # + # Sentry + # + + field :sentry_enabled, type: :boolean, readonly: true, + default: (ENV["SENTRY_DSN"].present?.to_s || false) + # # Discourse # diff --git a/config/initializers/sentry.rb b/config/initializers/sentry.rb new file mode 100644 index 0000000..202fe75 --- /dev/null +++ b/config/initializers/sentry.rb @@ -0,0 +1,9 @@ +if ENV["SENTRY_DSN"].present? + Sentry.init do |config| + config.dsn = ENV["SENTRY_DSN"] + config.breadcrumbs_logger = [:active_support_logger, :http_logger] + config.traces_sampler = lambda do |context| + true + end + end +end diff --git a/config/routes.rb b/config/routes.rb index 8b4866b..e729019 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,7 +1,7 @@ require 'sidekiq/web' Rails.application.routes.draw do - devise_for :users, :controllers => { :confirmations => "users/confirmations" } + devise_for :users, controllers: { confirmations: "users/confirmations" } get 'welcome', to: 'welcome#index' get 'check_your_email', to: 'welcome#check_your_email' From c17c980b690c24f8633d7c1c43c4b879f51ae449 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Tue, 28 Mar 2023 11:24:35 +0200 Subject: [PATCH 05/34] Prepare for multiple akkounts containers Initially "web" and "sidekiq" --- Dockerfile | 15 +++++++-------- docker-compose.yml | 32 ++++++++++++++++---------------- docker/entrypoint.sh | 8 -------- 3 files changed, 23 insertions(+), 32 deletions(-) delete mode 100644 docker/entrypoint.sh diff --git a/Dockerfile b/Dockerfile index 0eead4c..f2692e7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,13 @@ # syntax=docker/dockerfile:1 FROM ruby:2.7.6 -RUN apt-get update -qq && apt-get install -y curl ldap-utils + +SHELL ["/bin/bash", "-o", "pipefail", "-c"] + +RUN apt-get update -qq && apt-get install -y --no-install-recommends curl \ + ldap-utils tini RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - RUN apt-get update && apt-get install -y nodejs + WORKDIR /akkounts COPY Gemfile /akkounts/Gemfile COPY Gemfile.lock /akkounts/Gemfile.lock @@ -12,11 +17,5 @@ RUN gem install foreman RUN npm install -g yarn RUN yarn install -# Add a script to be executed every time the container starts. -COPY docker/entrypoint.sh /usr/bin/ -RUN chmod +x /usr/bin/entrypoint.sh -ENTRYPOINT ["entrypoint.sh"] +ENTRYPOINT ["/usr/bin/tini", "--"] EXPOSE 3000 - -# Configure the main process to run when running the image -CMD ["bin", "dev"] diff --git a/docker-compose.yml b/docker-compose.yml index a4d5a29..c4864b7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,19 +16,19 @@ services: # PHPLDAPADMIN_HTTPS: false # PHPLDAPADMIN_LDAP_HOSTS: "#PYTHON2BASH:[{'ldap': [{'server': [{'tls': False}, {'port': 3389}]}, {'login': [{'bind_id': 'cn=Directory Manager'}, {'bind_pass': 'passthebutter'}]}]}]" # PHPLDAPADMIN_LDAP_CLIENT_TLS: false - # web: - # build: . - # tty: true - # command: bash -c "sleep 5 && rm -f tmp/pids/server.pid && bin/dev" - # volumes: - # - .:/akkounts - # ports: - # - "3000:3000" - # environment: - # RAILS_ENV: development - # LDAP_HOST: ldap - # LDAP_PORT: 3389 - # LDAP_ADMIN_PASSWORD: passthebutter - # LDAP_USE_TLS: "false" - # depends_on: - # - ldap + web: + build: . + tty: true + command: bash -c "rm -f /akkounts/tmp/pids/server.pid; bin/dev" + volumes: + - .:/akkounts + ports: + - "3000:3000" + environment: + RAILS_ENV: development + LDAP_HOST: ldap + LDAP_PORT: 3389 + LDAP_ADMIN_PASSWORD: passthebutter + LDAP_USE_TLS: "false" + depends_on: + - ldap diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh deleted file mode 100644 index 3af18f7..0000000 --- a/docker/entrypoint.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash -set -e - -# Remove a potentially pre-existing server.pid for Rails. -rm -f /myapp/tmp/pids/server.pid - -# Then exec the container's main process (what's set as CMD in the Dockerfile). -exec "$@" From 42b9b2756125d3088bbc1a520c49b4aaf25c4cbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Tue, 28 Mar 2023 11:38:56 +0200 Subject: [PATCH 06/34] Allow external network access Useful for connecting to services on private networks for example. --- docker-compose.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index c4864b7..895b329 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,6 +3,9 @@ services: image: 4teamwork/389ds:latest volumes: - ./tmp/389ds:/data + networks: + - external_network + - internal_network ports: - "389:3389" environment: @@ -22,6 +25,9 @@ services: command: bash -c "rm -f /akkounts/tmp/pids/server.pid; bin/dev" volumes: - .:/akkounts + networks: + - external_network + - internal_network ports: - "3000:3000" environment: @@ -32,3 +38,8 @@ services: LDAP_USE_TLS: "false" depends_on: - ldap + +networks: + external_network: + internal_network: + internal: true From 6b7a80e23a92972fee685bae0006407d9dfc16f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Tue, 28 Mar 2023 12:21:54 +0200 Subject: [PATCH 07/34] Make Redis URL configurable --- .env.example | 2 ++ app/models/setting.rb | 7 +++++++ config/initializers/sidekiq.rb | 5 +++++ 3 files changed, 14 insertions(+) create mode 100644 config/initializers/sidekiq.rb diff --git a/.env.example b/.env.example index b667033..b522814 100644 --- a/.env.example +++ b/.env.example @@ -7,6 +7,8 @@ SMTP_DOMAIN=example.com SMTP_AUTH_METHOD=plain SMTP_ENABLE_STARTTLS=auto +REDIS_URL='redis://localhost:6379/1' + LDAP_HOST=localhost LDAP_PORT=389 LDAP_ADMIN_PASSWORD=passthebutter diff --git a/app/models/setting.rb b/app/models/setting.rb index 56aa785..2787786 100644 --- a/app/models/setting.rb +++ b/app/models/setting.rb @@ -2,6 +2,13 @@ class Setting < RailsSettings::Base cache_prefix { "v1" } + # + # Internal services + # + + field :redis_url, type: :string, readonly: true, + default: ENV["REDIS_URL"] || "redis://localhost:6379/0" + # # Registrations # diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb new file mode 100644 index 0000000..044cec0 --- /dev/null +++ b/config/initializers/sidekiq.rb @@ -0,0 +1,5 @@ +require_relative "../../app/models/setting" + +Sidekiq.configure_server do |config| + config.redis = { url: Setting.redis_url } +end From d2987da70a208fe9052d837c6e82613272934300 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Tue, 28 Mar 2023 12:22:17 +0200 Subject: [PATCH 08/34] Send Devise emails via Sidekiq --- app/models/user.rb | 4 ++++ config/sidekiq.yml | 1 + 2 files changed, 5 insertions(+) diff --git a/app/models/user.rb b/app/models/user.rb index ebe76cb..0fb5f82 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -63,6 +63,10 @@ class User < ApplicationRecord end end + def send_devise_notification(notification, *args) + devise_mailer.send(notification, self, *args).deliver_later + end + def reset_password(new_password, new_password_confirmation) self.password = new_password self.password_confirmation = new_password_confirmation diff --git a/config/sidekiq.yml b/config/sidekiq.yml index 615bb16..adc65b2 100644 --- a/config/sidekiq.yml +++ b/config/sidekiq.yml @@ -1,3 +1,4 @@ :concurrency: 2 :queues: - default + - mailers From 9f9bf6fd80473bd9c396344b5a6d77cde49c213e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Tue, 28 Mar 2023 12:24:58 +0200 Subject: [PATCH 09/34] Add Redis and Sidekiq to Docker Compose setup --- README.md | 2 +- docker-compose.yml | 72 ++++++++++++++++++++++++++++++++++------------ 2 files changed, 54 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index b2e7205..6ff6c17 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ so: 1. Make sure [Docker Compose is installed][1] and Docker is running (included in Docker Desktop) -2. Uncomment the `web` section in `docker-compose.yml` +2. Uncomment the `redis`, `web`, and `sidekiq` sections in `docker-compose.yml` 3. Run `docker compose up` and wait until 389ds announces its successful start in the log output 4. `docker-compose exec ldap dsconf localhost backend create --suffix="dc=kosmos,dc=org" --be-name="dev"` diff --git a/docker-compose.yml b/docker-compose.yml index 895b329..8a0de47 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,6 +11,59 @@ services: environment: DS_DM_PASSWORD: passthebutter SUFFIX_NAME: "dc=kosmos,dc=org" + + # redis: + # restart: always + # image: redis:7-alpine + # networks: + # - internal_network + # healthcheck: + # test: ['CMD', 'redis-cli', 'ping'] + # volumes: + # - ./tmp/redis:/data + + # web: + # build: . + # tty: true + # command: bash -c "rm -f /akkounts/tmp/pids/server.pid; bin/dev" + # volumes: + # - .:/akkounts + # networks: + # - external_network + # - internal_network + # ports: + # - "3000:3000" + # environment: + # RAILS_ENV: development + # REDIS_URL: redis://redis:6379/0 + # LDAP_HOST: ldap + # LDAP_PORT: 3389 + # LDAP_ADMIN_PASSWORD: passthebutter + # LDAP_USE_TLS: "false" + # depends_on: + # - ldap + # - redis + + # sidekiq: + # build: . + # command: bash -c "bundle exec sidekiq -C config/sidekiq.yml" + # volumes: + # - .:/akkounts + # networks: + # - internal_network + # environment: + # RAILS_ENV: development + # REDIS_URL: redis://redis:6379/0 + # LDAP_HOST: ldap + # LDAP_PORT: 3389 + # LDAP_ADMIN_PASSWORD: passthebutter + # LDAP_USE_TLS: "false" + # LAUNCHY_DRY_RUN: true + # BROWSER: /dev/null + # depends_on: + # - ldap + # - redis + # phpldapadmin: # image: osixia/phpldapadmin:0.9.0 # ports: @@ -19,25 +72,6 @@ services: # PHPLDAPADMIN_HTTPS: false # PHPLDAPADMIN_LDAP_HOSTS: "#PYTHON2BASH:[{'ldap': [{'server': [{'tls': False}, {'port': 3389}]}, {'login': [{'bind_id': 'cn=Directory Manager'}, {'bind_pass': 'passthebutter'}]}]}]" # PHPLDAPADMIN_LDAP_CLIENT_TLS: false - web: - build: . - tty: true - command: bash -c "rm -f /akkounts/tmp/pids/server.pid; bin/dev" - volumes: - - .:/akkounts - networks: - - external_network - - internal_network - ports: - - "3000:3000" - environment: - RAILS_ENV: development - LDAP_HOST: ldap - LDAP_PORT: 3389 - LDAP_ADMIN_PASSWORD: passthebutter - LDAP_USE_TLS: "false" - depends_on: - - ldap networks: external_network: From 22a7bbe6eb0ad3a0606dc4416cba4b66d5db9ba1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Tue, 28 Mar 2023 15:35:01 +0200 Subject: [PATCH 10/34] Add Gitea Release Drafter as Gitea Action --- .gitea/release-drafter.yml | 13 +++++++++++++ .gitea/workflows/release_drafter.yml | 11 +++++++++++ 2 files changed, 24 insertions(+) create mode 100644 .gitea/release-drafter.yml create mode 100644 .gitea/workflows/release_drafter.yml diff --git a/.gitea/release-drafter.yml b/.gitea/release-drafter.yml new file mode 100644 index 0000000..9fcce2b --- /dev/null +++ b/.gitea/release-drafter.yml @@ -0,0 +1,13 @@ +name-template: 'v$RESOLVED_VERSION' +tag-template: 'v$RESOLVED_VERSION' +version-resolver: + major: + labels: + - 'release/major' + minor: + labels: + - 'release/minor' + patch: + labels: + - 'release/patch' + default: patch diff --git a/.gitea/workflows/release_drafter.yml b/.gitea/workflows/release_drafter.yml new file mode 100644 index 0000000..c3cdce8 --- /dev/null +++ b/.gitea/workflows/release_drafter.yml @@ -0,0 +1,11 @@ +name: Release Drafter +on: + pull_request: + types: [closed] +jobs: + release_drafter_job: + name: Update release notes draft + runs-on: ubuntu-latest + steps: + - name: Release Drafter + uses: https://gitea.kosmos.org/raucao/gitea-release-drafter@efdf01c0717156366f5c67ab22dbef821d1a60dd From 986eb5387c52431785b812cf7364f9c598760a22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Tue, 28 Mar 2023 17:13:39 +0200 Subject: [PATCH 11/34] Use release drafter fork with PR ID fix --- .gitea/workflows/release_drafter.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/release_drafter.yml b/.gitea/workflows/release_drafter.yml index c3cdce8..54e0eda 100644 --- a/.gitea/workflows/release_drafter.yml +++ b/.gitea/workflows/release_drafter.yml @@ -8,4 +8,4 @@ jobs: runs-on: ubuntu-latest steps: - name: Release Drafter - uses: https://gitea.kosmos.org/raucao/gitea-release-drafter@efdf01c0717156366f5c67ab22dbef821d1a60dd + uses: https://github.com/raucao/gitea-release-drafter@de9367738b1299ecaa709677b8b9b432d37bc138 From f9b07bcb0123de2a7cd012aea4676ff7f5b70b5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Tue, 28 Mar 2023 17:27:31 +0200 Subject: [PATCH 12/34] Use development branch of release drafter action --- .gitea/workflows/release_drafter.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/release_drafter.yml b/.gitea/workflows/release_drafter.yml index 54e0eda..ba9dc39 100644 --- a/.gitea/workflows/release_drafter.yml +++ b/.gitea/workflows/release_drafter.yml @@ -8,4 +8,4 @@ jobs: runs-on: ubuntu-latest steps: - name: Release Drafter - uses: https://github.com/raucao/gitea-release-drafter@de9367738b1299ecaa709677b8b9b432d37bc138 + uses: https://github.com/raucao/gitea-release-drafter@dev From fe1dfd8ec830e2923d31d6eef740fe68cc1e5b65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Fri, 31 Mar 2023 18:07:38 +0200 Subject: [PATCH 13/34] Add solargraph in development, document usage with bundled gems --- Gemfile | 1 + Gemfile.lock | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 10 +++++++++- 3 files changed, 59 insertions(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 52045dd..1b6704e 100644 --- a/Gemfile +++ b/Gemfile @@ -66,6 +66,7 @@ group :development do gem 'letter_opener' gem 'letter_opener_web' gem 'faker' + gem 'solargraph' end group :test do diff --git a/Gemfile.lock b/Gemfile.lock index 7c655ba..2d92414 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -68,7 +68,10 @@ GEM tzinfo (~> 2.0) addressable (2.8.1) public_suffix (>= 2.0.2, < 6.0) + ast (2.4.2) + backport (1.2.0) bcrypt (3.1.18) + benchmark (0.2.1) bindex (0.8.1) builder (3.2.4) byebug (11.1.3) @@ -109,6 +112,7 @@ GEM dotenv-rails (2.8.1) dotenv (= 2.8.1) railties (>= 3.2) + e2mmap (0.1.0) erubi (1.11.0) et-orbi (1.2.7) tzinfo @@ -135,9 +139,15 @@ GEM importmap-rails (1.1.5) actionpack (>= 6.0.0) railties (>= 6.0.0) + jaro_winkler (1.5.4) jbuilder (2.11.5) actionview (>= 5.0.0) activesupport (>= 5.0.0) + json (2.6.3) + kramdown (2.4.0) + rexml + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) launchy (2.5.0) addressable (~> 2.7) letter_opener (1.8.1) @@ -179,6 +189,9 @@ GEM racc (~> 1.4) orm_adapter (0.5.0) pagy (6.0.2) + parallel (1.22.1) + parser (3.2.1.1) + ast (~> 2.4.1) pg (1.2.3) public_suffix (5.0.0) puma (4.3.12) @@ -217,6 +230,7 @@ GEM rake (>= 12.2) thor (~> 1.0) zeitwerk (~> 2.5) + rainbow (3.1.1) rake (13.0.6) rb-fsevent (0.11.2) rb-inotify (0.10.1) @@ -229,6 +243,8 @@ GEM responders (3.1.0) actionpack (>= 5.2) railties (>= 5.2) + reverse_markdown (2.1.1) + nokogiri rexml (3.2.5) rqrcode (2.1.2) chunky_png (~> 1.0) @@ -251,6 +267,19 @@ GEM rspec-mocks (~> 3.11) rspec-support (~> 3.11) rspec-support (3.12.0) + rubocop (1.48.1) + json (~> 2.3) + parallel (~> 1.10) + parser (>= 3.2.0.0) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 1.8, < 3.0) + rexml (>= 3.2.5, < 4.0) + rubocop-ast (>= 1.26.0, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 3.0) + rubocop-ast (1.28.0) + parser (>= 3.2.1.0) + ruby-progressbar (1.13.0) ruby2_keywords (0.0.5) rufus-scheduler (3.8.2) fugit (~> 1.1, >= 1.1.6) @@ -268,6 +297,21 @@ GEM rufus-scheduler (~> 3.2) sidekiq (>= 4, < 7) tilt (>= 1.4.0) + solargraph (0.48.0) + backport (~> 1.2) + benchmark + bundler (>= 1.17.2) + diff-lcs (~> 1.4) + e2mmap + jaro_winkler (~> 1.5) + kramdown (~> 2.3) + kramdown-parser-gfm (~> 1.1) + parser (~> 3.0) + reverse_markdown (>= 1.0.5, < 3) + rubocop (>= 0.52) + thor (~> 1.0) + tilt (~> 2.0) + yard (~> 0.9, >= 0.9.24) sprockets (4.1.1) concurrent-ruby (~> 1.0) rack (> 1, < 3) @@ -289,6 +333,7 @@ GEM railties (>= 6.0.0) tzinfo (2.0.5) concurrent-ruby (~> 1.0) + unicode-display_width (2.4.2) view_component (2.78.0) activesupport (>= 5.0.0, < 8.0) concurrent-ruby (~> 1.0) @@ -304,11 +349,14 @@ GEM addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) + webrick (1.7.0) websocket-driver (0.7.5) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) xpath (3.2.0) nokogiri (~> 1.8) + yard (0.9.28) + webrick (~> 1.7.0) zeitwerk (2.6.6) PLATFORMS @@ -344,6 +392,7 @@ DEPENDENCIES sentry-ruby sidekiq (< 7) sidekiq-scheduler + solargraph sprockets-rails sqlite3 (~> 1.4) stimulus-rails diff --git a/README.md b/README.md index 6ff6c17..6e55882 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,6 @@ so: After these steps, you should have a working Rails app with a handful of test users running on [http://localhost:3000](http://localhost:3000). - Log in with username "admin" and password "admin is admin". All users listed on [http://localhost:3000/admin/ldap_users](http://localhost:3000/admin/ldap_users) have the password "user is user". @@ -79,6 +78,15 @@ The setup task will first delete any existing entries in the directory tree Note that all 389ds data is stored in `tmp/389ds`. So if you want to start over with a fresh installation, delete both that directory as well as the container. +#### Solargraph + +[Solargraph](https://solargraph.org/) is a Ruby language server, which you may +use with your editor to add features like auto-completion and syntax +validation. You can add inline documentation for bundled gems with this +command: + + bundle exec yard gems + ## Documentation ### Rails From f08bb56a7ac04f151932725fe4506642c17323a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Sat, 1 Apr 2023 11:44:25 +0200 Subject: [PATCH 14/34] 0.5.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 066231b..60a5ede 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "postcss-preset-env": "^7.8.3", "tailwindcss": "^3.2.4" }, - "version": "0.4.0", + "version": "0.5.0", "scripts": { "build:css:tailwind": "tailwindcss --postcss -i ./app/assets/stylesheets/application.tailwind.css -o ./app/assets/builds/application.css", "build:css": "yarn run build:css:tailwind" From 7f77ad55285259ef9c39899377afdbaf96e9965e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Mon, 3 Apr 2023 13:19:07 +0200 Subject: [PATCH 15/34] Refactor user settings Use resources instead of custom controllers, following the Rails way and making things much cleaner in the process. --- .../settings/account_controller.rb | 13 ------- .../settings/profile_controller.rb | 11 ------ app/controllers/settings_controller.rb | 30 ++++++++++++++-- app/views/settings/_account.html.erb | 19 +++++++++++ app/views/settings/_profile.html.erb | 30 ++++++++++++++++ app/views/settings/account/index.html.erb | 23 ------------- app/views/settings/profile/index.html.erb | 34 ------------------- app/views/settings/show.html.erb | 5 +++ app/views/shared/_main_nav.html.erb | 2 +- app/views/shared/_sidenav_settings.html.erb | 8 ++--- config/routes.rb | 13 ++++--- 11 files changed, 92 insertions(+), 96 deletions(-) delete mode 100644 app/controllers/settings/account_controller.rb delete mode 100644 app/controllers/settings/profile_controller.rb create mode 100644 app/views/settings/_account.html.erb create mode 100644 app/views/settings/_profile.html.erb delete mode 100644 app/views/settings/account/index.html.erb delete mode 100644 app/views/settings/profile/index.html.erb create mode 100644 app/views/settings/show.html.erb diff --git a/app/controllers/settings/account_controller.rb b/app/controllers/settings/account_controller.rb deleted file mode 100644 index 385a9ff..0000000 --- a/app/controllers/settings/account_controller.rb +++ /dev/null @@ -1,13 +0,0 @@ -class Settings::AccountController < SettingsController - - def index - end - - def reset_password - current_user.send_reset_password_instructions - sign_out current_user - msg = "We have sent you an email with a link to reset your password." - redirect_to check_your_email_path, notice: msg - end - -end diff --git a/app/controllers/settings/profile_controller.rb b/app/controllers/settings/profile_controller.rb deleted file mode 100644 index 645bd20..0000000 --- a/app/controllers/settings/profile_controller.rb +++ /dev/null @@ -1,11 +0,0 @@ -class Settings::ProfileController < SettingsController - - def index - @user = current_user - end - - def update - - end - -end diff --git a/app/controllers/settings_controller.rb b/app/controllers/settings_controller.rb index d2fc3e5..85627a6 100644 --- a/app/controllers/settings_controller.rb +++ b/app/controllers/settings_controller.rb @@ -1,13 +1,37 @@ class SettingsController < ApplicationController - before_action :require_user_signed_in - before_action :set_current_section + before_action :authenticate_user! + before_action :set_main_nav_section + before_action :set_settings_section, only: ['show', 'update'] def index + redirect_to setting_path(:profile) + end + + def show + @user = current_user + end + + def update + end + + def reset_password + current_user.send_reset_password_instructions + sign_out current_user + msg = "We have sent you an email with a link to reset your password." + redirect_to check_your_email_path, notice: msg end private - def set_current_section + def set_main_nav_section @current_section = :settings end + + def set_settings_section + @settings_section = params[:section] + + unless [:profile, :account].include?(@settings_section.to_sym) + redirect_to setting_path(:profile) + end + end end diff --git a/app/views/settings/_account.html.erb b/app/views/settings/_account.html.erb new file mode 100644 index 0000000..df0a2ad --- /dev/null +++ b/app/views/settings/_account.html.erb @@ -0,0 +1,19 @@ +
+

E-Mail

+

+ <%= label :email, 'Address', class: 'font-bold' %> +

+

+ disabled="disabled" /> +

+
+
+

Password

+

Use the following button to request an email with a password reset link:

+ <%= form_with(url: reset_password_settings_path, method: :post) do %> +

+ <%= submit_tag("Send me a password reset link", class: 'btn-md btn-gray w-full sm:w-auto') %> +

+ <% end %> +
diff --git a/app/views/settings/_profile.html.erb b/app/views/settings/_profile.html.erb new file mode 100644 index 0000000..f1d14ae --- /dev/null +++ b/app/views/settings/_profile.html.erb @@ -0,0 +1,30 @@ +
+

Profile

+

+ <%= label :user_address, 'User address', class: 'font-bold' %> +

+

+ disabled="disabled" + data-clipboard-target="source" /> + +

+

+ Your user address for Chat and Lightning Network. +

+ + <%# <%= form_for(@user, as: "profile", url: settings_profile_path) do |f| %> + <%#

+ <%# <%= f.submit "Save changes", class: 'btn-md btn-blue w-full sm:w-auto' %> + <%#

+ <%# <% end %> +
diff --git a/app/views/settings/account/index.html.erb b/app/views/settings/account/index.html.erb deleted file mode 100644 index effe9c2..0000000 --- a/app/views/settings/account/index.html.erb +++ /dev/null @@ -1,23 +0,0 @@ -<%= render HeaderComponent.new(title: "Settings") %> - -<%= render MainWithSidenavComponent.new(sidenav_partial: 'shared/sidenav_settings') do %> -
-

E-Mail

-

- <%= label :email, 'Address', class: 'font-bold' %> -

-

- disabled="disabled" /> -

-
-
-

Password

-

Use the following button to request an email with a password reset link:

- <%= form_with(url: settings_reset_password_path, method: :post) do %> -

- <%= submit_tag("Send me a password reset link", class: 'btn-md btn-gray w-full sm:w-auto') %> -

- <% end %> -
-<% end %> diff --git a/app/views/settings/profile/index.html.erb b/app/views/settings/profile/index.html.erb deleted file mode 100644 index 3e91709..0000000 --- a/app/views/settings/profile/index.html.erb +++ /dev/null @@ -1,34 +0,0 @@ -<%= render HeaderComponent.new(title: "Settings") %> - -<%= render MainWithSidenavComponent.new(sidenav_partial: 'shared/sidenav_settings') do %> -
-

Profile

-

- <%= label :user_address, 'User address', class: 'font-bold' %> -

-

- disabled="disabled" - data-clipboard-target="source" /> - -

-

- Your user address for Chat and Lightning Network. -

- - <%# <%= form_for(@user, as: "profile", url: settings_profile_path) do |f| %> - <%#

- <%# <%= f.submit "Save changes", class: 'btn-md btn-blue w-full sm:w-auto' %> - <%#

- <%# <% end %> -
-<% end %> diff --git a/app/views/settings/show.html.erb b/app/views/settings/show.html.erb new file mode 100644 index 0000000..38ddcb5 --- /dev/null +++ b/app/views/settings/show.html.erb @@ -0,0 +1,5 @@ +<%= render HeaderComponent.new(title: "Settings") %> + +<%= render MainWithSidenavComponent.new(sidenav_partial: 'shared/sidenav_settings') do %> + <%= render partial: @settings_section %> +<% end %> diff --git a/app/views/shared/_main_nav.html.erb b/app/views/shared/_main_nav.html.erb index de5b8eb..7f5ee02 100644 --- a/app/views/shared/_main_nav.html.erb +++ b/app/views/shared/_main_nav.html.erb @@ -6,5 +6,5 @@ class: main_nav_class(@current_section, :invitations) %> <%= link_to "Wallet", wallet_path, class: main_nav_class(@current_section, :wallet) %> -<%= link_to "Settings", settings_profile_path, +<%= link_to "Settings", settings_path, class: main_nav_class(@current_section, :settings) %> diff --git a/app/views/shared/_sidenav_settings.html.erb b/app/views/shared/_sidenav_settings.html.erb index 25d345a..d33c555 100644 --- a/app/views/shared/_sidenav_settings.html.erb +++ b/app/views/shared/_sidenav_settings.html.erb @@ -1,10 +1,10 @@ <%= render SidenavLinkComponent.new( - name: "Profile", path: settings_profile_path, icon: "user", - active: current_page?(settings_profile_path) + name: "Profile", path: setting_path(:profile), icon: "user", + active: current_page?(setting_path(:profile)) ) %> <%= render SidenavLinkComponent.new( - name: "Account", path: settings_account_path, icon: "key", - active: current_page?(settings_account_path) + name: "Account", path: setting_path(:account), icon: "key", + active: current_page?(setting_path(:account)) ) %> <%= render SidenavLinkComponent.new( name: "Security", path: "#", icon: "shield", disabled: true diff --git a/config/routes.rb b/config/routes.rb index e729019..e384d1c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -10,13 +10,6 @@ Rails.application.routes.draw do match 'signup/:step', to: 'signup#steps', as: :signup_steps, via: [:get, :post] post 'signup_validate', to: 'signup#validate' - namespace :settings do - get 'profile', to: 'profile#index' - post 'profile', to: 'profile#update' - get 'account', to: 'account#index' - post 'reset_password', to: 'account#reset_password' - end - namespace :contributions do root to: 'donations#index' get 'projects', to: 'projects#index' @@ -28,6 +21,12 @@ Rails.application.routes.draw do get 'wallet', to: 'wallet#index' get 'wallet/transactions', to: 'wallet#transactions' + resources :settings, param: 'section', only: ['index', 'show', 'update'] do + collection do + post 'reset_password' + end + end + get 'lnurlpay/:address', to: 'lnurlpay#index', as: 'lightning_address', constraints: { address: /[^\/]+/} get 'lnurlpay/:address/invoice', to: 'lnurlpay#invoice', From 6848bd739cd73fd03a2042da032da3c65da25c16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Mon, 3 Apr 2023 13:55:39 +0200 Subject: [PATCH 16/34] Add horizontal layout option for fieldset component --- .../form_elements/fieldset_component.html.erb | 16 ++++++++++++++++ .../form_elements/fieldset_component.rb | 9 +++++---- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/app/components/form_elements/fieldset_component.html.erb b/app/components/form_elements/fieldset_component.html.erb index 4d82a56..2bde8ce 100644 --- a/app/components/form_elements/fieldset_component.html.erb +++ b/app/components/form_elements/fieldset_component.html.erb @@ -1,4 +1,5 @@ <%= tag.public_send(@tag, class: "mb-6 last:mb-0") do %> + <% if @positioning == :vertical %> + <% else %> +

Invalid positioning argument for FieldsetComponent.

+ <% end %> <% end %> diff --git a/app/components/form_elements/fieldset_component.rb b/app/components/form_elements/fieldset_component.rb index 8896137..23fad5b 100644 --- a/app/components/form_elements/fieldset_component.rb +++ b/app/components/form_elements/fieldset_component.rb @@ -2,10 +2,11 @@ module FormElements class FieldsetComponent < ViewComponent::Base - def initialize(tag: "li", title:, description: nil) - @tag = tag - @title = title - @descripton = description + def initialize(tag: "li", positioning: :vertical, title:, description: nil) + @tag = tag + @positioning = positioning + @title = title + @descripton = description end end end From 334b47353ecaff6048107a259269dda314daba25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Mon, 3 Apr 2023 13:55:58 +0200 Subject: [PATCH 17/34] WIP Add notifications preferences page --- app/controllers/settings_controller.rb | 3 ++- app/views/icons/_bell.html.erb | 2 +- app/views/settings/_notifications.html.erb | 16 ++++++++++++++++ app/views/shared/_sidenav_settings.html.erb | 3 ++- 4 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 app/views/settings/_notifications.html.erb diff --git a/app/controllers/settings_controller.rb b/app/controllers/settings_controller.rb index 85627a6..b083543 100644 --- a/app/controllers/settings_controller.rb +++ b/app/controllers/settings_controller.rb @@ -29,8 +29,9 @@ class SettingsController < ApplicationController def set_settings_section @settings_section = params[:section] + allowed_sections = [:profile, :account, :notifications] - unless [:profile, :account].include?(@settings_section.to_sym) + unless allowed_sections.include?(@settings_section.to_sym) redirect_to setting_path(:profile) end end diff --git a/app/views/icons/_bell.html.erb b/app/views/icons/_bell.html.erb index bba561c..3bb750f 100644 --- a/app/views/icons/_bell.html.erb +++ b/app/views/icons/_bell.html.erb @@ -1 +1 @@ - \ No newline at end of file + diff --git a/app/views/settings/_notifications.html.erb b/app/views/settings/_notifications.html.erb new file mode 100644 index 0000000..ff879d3 --- /dev/null +++ b/app/views/settings/_notifications.html.erb @@ -0,0 +1,16 @@ +
+

Lightning Wallet

+ +
    + <%= render FormElements::FieldsetComponent.new( + positioning: :horizontal, + title: "Sats received", + description: "Notify when sats are sent to my Lightning Address" + ) do %> + <%= select_tag :sats_received, options_for_select([ + ["off", "off"], + ["Chat (Jabber)", "xmpp"] + ]) %> + <% end %> +
+
diff --git a/app/views/shared/_sidenav_settings.html.erb b/app/views/shared/_sidenav_settings.html.erb index d33c555..625e08c 100644 --- a/app/views/shared/_sidenav_settings.html.erb +++ b/app/views/shared/_sidenav_settings.html.erb @@ -7,5 +7,6 @@ active: current_page?(setting_path(:account)) ) %> <%= render SidenavLinkComponent.new( - name: "Security", path: "#", icon: "shield", disabled: true + name: "Notifications", path: setting_path(:notifications), icon: "bell", + active: current_page?(setting_path(:notifications)) ) %> From a1b238e86b3d0e3300fa45296ff0693e8d42c1ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Tue, 4 Apr 2023 09:11:06 +0200 Subject: [PATCH 18/34] Fix email default URL options missing --- .env.example | 2 ++ config/environments/production.rb | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/.env.example b/.env.example index b522814..6cff8b9 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,5 @@ +AKKOUNTS_DOMAIN=accounts.example.com + SMTP_SERVER=smtp.example.com SMTP_PORT=587 SMTP_LOGIN=accounts diff --git a/config/environments/production.rb b/config/environments/production.rb index 1f22161..c51028f 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -62,6 +62,11 @@ Rails.application.configure do outgoing_email_address = ENV.fetch('SMTP_FROM_ADDRESS', 'accounts@localhost') outgoing_email_domain = Mail::Address.new(outgoing_email_address).domain + config.action_mailer.default_url_options = { + host: ENV['AKKOUNTS_DOMAIN'], + protocol: "https", + } + config.action_mailer.default_options = { from: outgoing_email_address, message_id: -> { "<#{Mail.random_tag}@#{outgoing_email_domain}>" }, From a33410eeb421652416d472d9d20f58cd64da6b53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Tue, 4 Apr 2023 12:03:00 +0200 Subject: [PATCH 19/34] Allow handing custom field names to toggle fieldset component --- .../fieldset_toggle_component.html.erb | 21 ++++++++++++------- .../fieldset_toggle_component.rb | 8 ++++--- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/app/components/form_elements/fieldset_toggle_component.html.erb b/app/components/form_elements/fieldset_toggle_component.html.erb index 504a5b5..f4acd3d 100644 --- a/app/components/form_elements/fieldset_toggle_component.html.erb +++ b/app/components/form_elements/fieldset_toggle_component.html.erb @@ -1,5 +1,5 @@ <%= tag.public_send @tag, class: "flex items-center justify-between mb-6 last:mb-0", - data: @form.present? ? { + data: @form_enabled ? { controller: "settings--toggle", :'settings--toggle-switch-enabled-value' => @enabled.to_s } : nil do %> @@ -11,16 +11,23 @@ <%= render FormElements::ToggleComponent.new( enabled: @enabled, input_enabled: @input_enabled, - class_names: @form.present? ? "hidden" : nil, + class_names: @form_enabled ? "hidden" : nil, data: { :'settings--toggle-target' => "button", action: "settings--toggle#toggleSwitch" }) %> - <% if @form.present? %> - <%= @form.check_box @attribute, { - checked: @enabled, - data: { :'settings--toggle-target' => "checkbox" } - }, "true", "false" %> + <% if @form_enabled %> + <% if @attribute.present? %> + <%= @form.check_box @attribute, { + checked: @enabled, + data: { :'settings--toggle-target' => "checkbox" } + }, "true", "false" %> + <% else %> + + <%= check_box_tag @field_name, "true", @enabled, { + data: { :'settings--toggle-target' => "checkbox" } + } %> + <% end %> <% end %> <% end %> diff --git a/app/components/form_elements/fieldset_toggle_component.rb b/app/components/form_elements/fieldset_toggle_component.rb index b38bee7..686f5f1 100644 --- a/app/components/form_elements/fieldset_toggle_component.rb +++ b/app/components/form_elements/fieldset_toggle_component.rb @@ -2,11 +2,13 @@ module FormElements class FieldsetToggleComponent < ViewComponent::Base - def initialize(form: nil, attribute: nil, tag: "li", enabled: false, - input_enabled: true, title:, description:) + def initialize(tag: "li", form: nil, attribute: nil, field_name: nil, + enabled: false, input_enabled: true, title:, description:) + @tag = tag @form = form @attribute = attribute - @tag = tag + @field_name = field_name + @form_enabled = @form.present? || @field_name.present? @enabled = enabled @input_enabled = input_enabled @title = title From 23821f9e65bb256422a8017a02931411936f2ffd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Tue, 4 Apr 2023 12:27:49 +0200 Subject: [PATCH 20/34] Add preferences to user model --- app/models/user.rb | 7 +++ ...20230403135149_add_preferences_to_users.rb | 5 ++ db/schema.rb | 4 +- spec/models/user_spec.rb | 46 +++++++++++++++++++ 4 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20230403135149_add_preferences_to_users.rb diff --git a/app/models/user.rb b/app/models/user.rb index 5b40929..c1851a4 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,6 +1,8 @@ class User < ApplicationRecord include EmailValidatable + serialize :preferences, Hash, default: {} + # Relations has_many :invitations, dependent: :destroy has_one :invitation, inverse_of: :invitee, foreign_key: 'invited_user_id' @@ -133,6 +135,11 @@ class User < ApplicationRecord ldap.delete_attribute(dn,:service) end + def pref_enabled?(key) + value = preferences.dig(*key.split(":")) + [true, "true", 1, "enabled"].include?(value) + end + def exchange_xmpp_contact_with_inviter return unless inviter.services_enabled.include?("ejabberd") && services_enabled.include?("ejabberd") diff --git a/db/migrate/20230403135149_add_preferences_to_users.rb b/db/migrate/20230403135149_add_preferences_to_users.rb new file mode 100644 index 0000000..2defcb3 --- /dev/null +++ b/db/migrate/20230403135149_add_preferences_to_users.rb @@ -0,0 +1,5 @@ +class AddPreferencesToUsers < ActiveRecord::Migration[7.0] + def change + add_column :users, :preferences, :text + end +end diff --git a/db/schema.rb b/db/schema.rb index a292eed..1a85f04 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2023_03_19_101128) do +ActiveRecord::Schema[7.0].define(version: 2023_04_03_135149) do create_table "donations", force: :cascade do |t| t.integer "user_id" t.integer "amount_sats" @@ -57,8 +57,10 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_19_101128) do t.text "ln_login_ciphertext" t.text "ln_password_ciphertext" t.string "ln_account" + t.string "nostr_pubkey" t.datetime "remember_created_at" t.string "remember_token" + t.text "preferences" t.index ["email"], name: "index_users_on_email", unique: true t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 1dfc1ac..37db8ae 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -149,4 +149,50 @@ RSpec.describe User, type: :model do end end end + + describe "#pref_enabled?" do + describe "preference not set" do + # TODO return default value + it "returns false" do + expect(user.pref_enabled?("lightning:notify_sats_received")).to be(false) + end + end + + describe "preference is set" do + it "returns true for boolean true" do + user.preferences.merge!({"lightning" => {"notify_sats_received" => true}}) + expect(user.pref_enabled?("lightning:notify_sats_received")).to be(true) + end + + it "returns true for string 'true'" do + user.preferences.merge!({"lightning" => {"notify_sats_received" => "true"}}) + expect(user.pref_enabled?("lightning:notify_sats_received")).to be(true) + end + + it "returns true for string 'enabled'" do + user.preferences.merge!({"lightning" => {"notify_sats_received" => "enabled"}}) + expect(user.pref_enabled?("lightning:notify_sats_received")).to be(true) + end + + it "returns true for integer 1" do + user.preferences.merge!({"lightning" => {"notify_sats_received" => 1}}) + expect(user.pref_enabled?("lightning:notify_sats_received")).to be(true) + end + + it "returns false for boolean false" do + user.preferences.merge!({"lightning" => {"notify_sats_received" => false}}) + expect(user.pref_enabled?("lightning:notify_sats_received")).to be(false) + end + + it "returns false for string 'false'" do + user.preferences.merge!({"lightning" => {"notify_sats_received" => "false"}}) + expect(user.pref_enabled?("lightning:notify_sats_received")).to be(false) + end + + it "returns false for integer 0" do + user.preferences.merge!({"lightning" => {"notify_sats_received" => 0}}) + expect(user.pref_enabled?("lightning:notify_sats_received")).to be(false) + end + end + end end From f19baaf22a27084b90676d88d5a4b449c443d4b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Tue, 4 Apr 2023 12:28:32 +0200 Subject: [PATCH 21/34] Add new user settings pages for Chat and Wallet --- app/controllers/settings_controller.rb | 16 +++++++++++- app/views/icons/_message-circle.html.erb | 2 +- app/views/settings/_lightning.html.erb | 27 +++++++++++++++++++++ app/views/settings/_xmpp.html.erb | 18 ++++++++++++++ app/views/shared/_sidenav_settings.html.erb | 12 +++++++-- 5 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 app/views/settings/_lightning.html.erb create mode 100644 app/views/settings/_xmpp.html.erb diff --git a/app/controllers/settings_controller.rb b/app/controllers/settings_controller.rb index b083543..f50235a 100644 --- a/app/controllers/settings_controller.rb +++ b/app/controllers/settings_controller.rb @@ -12,6 +12,13 @@ class SettingsController < ApplicationController end def update + @user = current_user + @user.preferences.merge! user_params[:preferences] + @user.save! + + redirect_to setting_path(@settings_section), flash: { + success: 'Settings saved.' + } end def reset_password @@ -29,10 +36,17 @@ class SettingsController < ApplicationController def set_settings_section @settings_section = params[:section] - allowed_sections = [:profile, :account, :notifications] + allowed_sections = [:profile, :account, :lightning, :xmpp] unless allowed_sections.include?(@settings_section.to_sym) redirect_to setting_path(:profile) end end + + def user_params + params.require(:user).permit(preferences: [ + lightning: [:notify_sats_received], + xmpp: [:exchange_contacts_with_invitees] + ]) + end end diff --git a/app/views/icons/_message-circle.html.erb b/app/views/icons/_message-circle.html.erb index 4b21b32..5ff6406 100644 --- a/app/views/icons/_message-circle.html.erb +++ b/app/views/icons/_message-circle.html.erb @@ -1 +1 @@ - \ No newline at end of file + diff --git a/app/views/settings/_lightning.html.erb b/app/views/settings/_lightning.html.erb new file mode 100644 index 0000000..b7ad6e2 --- /dev/null +++ b/app/views/settings/_lightning.html.erb @@ -0,0 +1,27 @@ +<%= form_for @user, url: setting_path(:lightning), html: { :method => :put } do |f| %> +
+

Notifications

+
    + <%= render FormElements::FieldsetComponent.new( + positioning: :horizontal, + title: "Sats received", + description: "Notify me when sats are sent to my Lightning Address" + ) do %> + <% f.fields_for :preferences do |p| %> + <% p.fields_for :lightning do |l| %> + <%= l.select :notify_sats_received, options_for_select([ + ["off", "off"], + ["Chat (Jabber)", "xmpp"], + ["E-Mail", "email"] + ], selected: @user.preferences.dig('lightning', 'notify_sats_received')) %> + <% end %> + <% end %> + <% end %> +
+
+
+

+ <%= f.submit 'Save', class: "btn-md btn-blue w-full md:w-auto" %> +

+
+<% end %> diff --git a/app/views/settings/_xmpp.html.erb b/app/views/settings/_xmpp.html.erb new file mode 100644 index 0000000..3ce4e91 --- /dev/null +++ b/app/views/settings/_xmpp.html.erb @@ -0,0 +1,18 @@ +<%= form_for @user, url: setting_path(:xmpp), html: { :method => :put } do |f| %> +
+

Contacts

+
    + <%= render FormElements::FieldsetToggleComponent.new( + field_name: "user[preferences][xmpp][exchange_contacts_with_invitees]", + enabled: @user.pref_enabled?("xmpp:exchange_contacts_with_invitees"), + title: "Exchange contacts when invited user signs up", + description: "Add each others contacts, so you can chat with them immediately" + ) %> +
+
+
+

+ <%= f.submit 'Save', class: "btn-md btn-blue w-full md:w-auto" %> +

+
+<% end %> diff --git a/app/views/shared/_sidenav_settings.html.erb b/app/views/shared/_sidenav_settings.html.erb index 625e08c..3d6c17e 100644 --- a/app/views/shared/_sidenav_settings.html.erb +++ b/app/views/shared/_sidenav_settings.html.erb @@ -6,7 +6,15 @@ name: "Account", path: setting_path(:account), icon: "key", active: current_page?(setting_path(:account)) ) %> +<% if Setting.ejabberd_enabled %> <%= render SidenavLinkComponent.new( - name: "Notifications", path: setting_path(:notifications), icon: "bell", - active: current_page?(setting_path(:notifications)) + name: "Chat", path: setting_path(:xmpp), icon: "message-circle", + active: current_page?(setting_path(:xmpp)) ) %> +<% end %> +<% if Setting.lndhub_enabled %> +<%= render SidenavLinkComponent.new( + name: "Wallet", path: setting_path(:lightning), icon: "zap", + active: current_page?(setting_path(:lightning)) +) %> +<% end %> From 62cd0eb7d1d5c430b999c324e8cf9c47553a11dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Tue, 4 Apr 2023 12:29:39 +0200 Subject: [PATCH 22/34] Re-rename "ejabberd" service to "xmpp" Shouldn't matter which implementation is integrated if someone adds another one --- app/models/user.rb | 8 ++++---- app/views/admin/users/show.html.erb | 2 +- spec/models/user_spec.rb | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index c1851a4..835e174 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -57,7 +57,7 @@ class User < ApplicationRecord end def devise_after_confirmation - enable_service %w[ discourse ejabberd gitea mediawiki ] + enable_service %w[ discourse gitea mediawiki xmpp ] #TODO enable in development when we have easy setup of ejabberd etc. return if Rails.env.development? @@ -137,12 +137,12 @@ class User < ApplicationRecord def pref_enabled?(key) value = preferences.dig(*key.split(":")) - [true, "true", 1, "enabled"].include?(value) + [true, "true", "enabled", 1].include?(value) end def exchange_xmpp_contact_with_inviter - return unless inviter.services_enabled.include?("ejabberd") && - services_enabled.include?("ejabberd") + return unless inviter.services_enabled.include?("xmpp") && + services_enabled.include?("xmpp") XmppExchangeContactsJob.perform_later(inviter, self.cn, self.ou) end diff --git a/app/views/admin/users/show.html.erb b/app/views/admin/users/show.html.erb index 5d89827..42c7963 100644 --- a/app/views/admin/users/show.html.erb +++ b/app/views/admin/users/show.html.erb @@ -135,7 +135,7 @@ XMPP (ejabberd) <%= render FormElements::ToggleComponent.new( - enabled: @services_enabled.include?("ejabberd"), + enabled: @services_enabled.include?("xmpp"), input_enabled: false ) %> diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 37db8ae..d72c2e5 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -109,7 +109,7 @@ RSpec.describe User, type: :model do before do Invitation.create! user: user, invited_user_id: guest.id, used_at: DateTime.now - allow_any_instance_of(User).to receive(:services_enabled).and_return(%w[ ejabberd ]) + allow_any_instance_of(User).to receive(:services_enabled).and_return(%w[ xmpp ]) end it "enqueues a job to exchange XMPP contacts between inviter and invitee" do @@ -131,11 +131,11 @@ RSpec.describe User, type: :model do let(:user) { create :user, cn: "willherschel", ou: "kosmos.org" } it "enables default services" do - expect(user).to receive(:enable_service).with(%w[ discourse ejabberd gitea mediawiki ]) + expect(user).to receive(:enable_service).with(%w[ discourse gitea mediawiki xmpp ]) user.send(:devise_after_confirmation) end - context "for invited user with ejabberd enabled" do + context "for invited user with xmpp enabled" do let(:guest) { create :user, id: 2, cn: "isaacnewton", ou: "kosmos.org", email: "newt@example.com" } before do From 595bb03c5a5a03c1af05e29b7b0a5d62b9a72e81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Tue, 4 Apr 2023 12:45:13 +0200 Subject: [PATCH 23/34] Do not exchange XMPP contacts when turned off by inviter --- app/models/user.rb | 5 ++++- spec/models/user_spec.rb | 13 +++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/app/models/user.rb b/app/models/user.rb index 835e174..f0f595f 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -63,7 +63,10 @@ class User < ApplicationRecord return if Rails.env.development? if inviter.present? - exchange_xmpp_contact_with_inviter if Setting.ejabberd_enabled? + if Setting.ejabberd_enabled? && + inviter.pref_enabled?("xmpp:exchange_contacts_with_invitees") + exchange_xmpp_contact_with_inviter + end end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index d72c2e5..a36458f 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -139,6 +139,8 @@ RSpec.describe User, type: :model do let(:guest) { create :user, id: 2, cn: "isaacnewton", ou: "kosmos.org", email: "newt@example.com" } before do + # TODO remove when defaults are implemented + user.update! preferences: {"xmpp" => { "exchange_contacts_with_invitees" => true }} Invitation.create! user: user, invited_user_id: guest.id, used_at: DateTime.now allow_any_instance_of(User).to receive(:enable_service).and_return(true) end @@ -147,6 +149,17 @@ RSpec.describe User, type: :model do expect(guest).to receive(:exchange_xmpp_contact_with_inviter) guest.send(:devise_after_confirmation) end + + context "automatic contact exchange disabled" do + before do + user.update! preferences: {"xmpp" => { "exchange_contacts_with_invitees" => false }} + end + + it "does not exchange XMPP contacts with the inviter" do + expect(guest).to_not receive(:exchange_xmpp_contact_with_inviter) + guest.send(:devise_after_confirmation) + end + end end end From 43a43e1a2c25d738d94ea747112d6416fed29d19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Tue, 4 Apr 2023 12:46:09 +0200 Subject: [PATCH 24/34] Use setting instead of ENV var --- app/services/ejabberd_api_client.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/ejabberd_api_client.rb b/app/services/ejabberd_api_client.rb index 7930aa1..bac501e 100644 --- a/app/services/ejabberd_api_client.rb +++ b/app/services/ejabberd_api_client.rb @@ -1,6 +1,6 @@ class EjabberdApiClient def initialize - @base_url = ENV["EJABBERD_API_URL"] + @base_url = Setting.ejabberd_api_url end def post(endpoint, payload) From ca7475dca21ba722bcff7ee802db1dc0ded4c581 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Tue, 4 Apr 2023 13:39:32 +0200 Subject: [PATCH 25/34] Add notification mailer, make wallet notifications configurable --- app/controllers/webhooks_controller.rb | 14 ++++-- app/mailers/notification_mailer.rb | 8 ++++ .../lightning_sats_received.text.erb | 3 ++ spec/requests/webhooks_spec.rb | 47 +++++++++++++++---- 4 files changed, 59 insertions(+), 13 deletions(-) create mode 100644 app/mailers/notification_mailer.rb create mode 100644 app/views/notification_mailer/lightning_sats_received.text.erb diff --git a/app/controllers/webhooks_controller.rb b/app/controllers/webhooks_controller.rb index 74f880a..9db08d9 100644 --- a/app/controllers/webhooks_controller.rb +++ b/app/controllers/webhooks_controller.rb @@ -12,22 +12,28 @@ class WebhooksController < ApplicationController end user = User.find_by!(ln_account: payload[:user_login]) - - # TODO make configurable - notify_xmpp(user.address, payload[:amount], payload[:memo]) + notify = user.preferences.dig("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 end private + # TODO refactor into mailer-like generic class/service def notify_xmpp(address, amt_sats, memo) payload = { type: "normal", from: "kosmos.org", # TODO domain config to: address, subject: "Sats received!", - body: "#{amt_sats} sats received in your Lightning wallet:\n> #{memo}" + body: "#{helpers.number_with_delimiter amt_sats} sats received in your Lightning wallet:\n> #{memo}" } XmppSendMessageJob.perform_later(payload) end diff --git a/app/mailers/notification_mailer.rb b/app/mailers/notification_mailer.rb new file mode 100644 index 0000000..84f7dd5 --- /dev/null +++ b/app/mailers/notification_mailer.rb @@ -0,0 +1,8 @@ +class NotificationMailer < ApplicationMailer + def lightning_sats_received + @user = params[:user] + @amount_sats = params[:amount_sats] + @subject = "Sats received" + mail to: @user.email, subject: @subject + end +end diff --git a/app/views/notification_mailer/lightning_sats_received.text.erb b/app/views/notification_mailer/lightning_sats_received.text.erb new file mode 100644 index 0000000..1e122d4 --- /dev/null +++ b/app/views/notification_mailer/lightning_sats_received.text.erb @@ -0,0 +1,3 @@ +You just received <%= number_with_delimiter @amount_sats %> sats in your Lightning account (<%= @user.address %>). Check your wallet app, or open the account page for details: + +<%= wallet_transactions_url %> diff --git a/spec/requests/webhooks_spec.rb b/spec/requests/webhooks_spec.rb index 06c834e..d38cf7c 100644 --- a/spec/requests/webhooks_spec.rb +++ b/spec/requests/webhooks_spec.rb @@ -55,22 +55,51 @@ RSpec.describe "Webhooks", type: :request do before do user.save! #FIXME this should not be necessary - post "/webhooks/lndhub", params: payload.to_json end it "returns a 200 status" do + post "/webhooks/lndhub", params: payload.to_json expect(response).to have_http_status(:ok) end - it "sends an XMPP message to the account owner's JID" do - expect(enqueued_jobs.size).to eq(1) + it "does not send notifications by default" do + expect(enqueued_jobs.size).to eq(0) + end - msg = enqueued_jobs.first['arguments'].first - expect(msg["type"]).to eq('normal') - expect(msg["from"]).to eq('kosmos.org') - expect(msg["to"]).to eq(user.address) - expect(msg["subject"]).to eq('Sats received!') - expect(msg["body"]).to match(/^12300 sats received/) + context "notification preference set to 'xmpp'" do + before do + user.update! preferences: { "lightning" => { "notify_sats_received" => "xmpp" }} + post "/webhooks/lndhub", params: payload.to_json + end + + it "sends an XMPP message to the account owner's JID" do + expect(enqueued_jobs.size).to eq(1) + expect(enqueued_jobs.first["job_class"]).to eq("XmppSendMessageJob") + + msg = enqueued_jobs.first["arguments"].first + expect(msg["type"]).to eq("normal") + expect(msg["from"]).to eq("kosmos.org") + expect(msg["to"]).to eq(user.address) + expect(msg["subject"]).to eq("Sats received!") + expect(msg["body"]).to match(/^12,300 sats received/) + end + end + + context "notification preference set to 'email'" do + before do + user.update! preferences: { "lightning" => { "notify_sats_received" => "email" }} + post "/webhooks/lndhub", params: payload.to_json + end + + it "sends an email notification to the account owner" do + expect(enqueued_jobs.size).to eq(1) + expect(enqueued_jobs.first["job_class"]).to eq("ActionMailer::MailDeliveryJob") + args = enqueued_jobs.first['arguments'] + expect(args[0]).to eq("NotificationMailer") + expect(args[1]).to eq("lightning_sats_received") + expect(args[3]["params"]["user"]["_aj_globalid"]).to eq("gid://akkounts/User/1") + expect(args[3]["params"]["amount_sats"]).to eq(12300) + end end end end From 4e2e13108c3e174bceae0903bad36807926a7bd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Wed, 5 Apr 2023 16:56:14 +0200 Subject: [PATCH 26/34] Refactor user preferences, add defaults from file * Turn prefs into a flat hash structure, since nesting is not worth the trouble * Add a custom serializer class for prefs * Add a config file for defaults and merge set prefs with unset ones * Use booleans for "true" and "false", and integers where appropriate --- app/controllers/settings_controller.rb | 4 +-- app/controllers/webhooks_controller.rb | 2 +- app/models/user.rb | 9 ++--- app/models/user_preferences.rb | 29 +++++++++++++++ app/views/settings/_lightning.html.erb | 12 +++---- app/views/settings/_xmpp.html.erb | 4 +-- config/default_preferences.yml | 2 ++ spec/models/user_preferences_spec.rb | 41 +++++++++++++++++++++ spec/models/user_spec.rb | 50 ++------------------------ spec/requests/webhooks_spec.rb | 4 +-- 10 files changed, 88 insertions(+), 69 deletions(-) create mode 100644 app/models/user_preferences.rb create mode 100644 config/default_preferences.yml create mode 100644 spec/models/user_preferences_spec.rb diff --git a/app/controllers/settings_controller.rb b/app/controllers/settings_controller.rb index f50235a..c5bd8f8 100644 --- a/app/controllers/settings_controller.rb +++ b/app/controllers/settings_controller.rb @@ -45,8 +45,8 @@ class SettingsController < ApplicationController def user_params params.require(:user).permit(preferences: [ - lightning: [:notify_sats_received], - xmpp: [:exchange_contacts_with_invitees] + :lightning_notify_sats_received, + :xmpp_exchange_contacts_with_invitees ]) end end diff --git a/app/controllers/webhooks_controller.rb b/app/controllers/webhooks_controller.rb index 9db08d9..7025580 100644 --- a/app/controllers/webhooks_controller.rb +++ b/app/controllers/webhooks_controller.rb @@ -12,7 +12,7 @@ class WebhooksController < ApplicationController end user = User.find_by!(ln_account: payload[:user_login]) - notify = user.preferences.dig("lightning", "notify_sats_received") + notify = user.preferences[:lightning_notify_sats_received] case notify when "xmpp" notify_xmpp(user.address, payload[:amount], payload[:memo]) diff --git a/app/models/user.rb b/app/models/user.rb index f0f595f..695c06c 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,7 +1,7 @@ class User < ApplicationRecord include EmailValidatable - serialize :preferences, Hash, default: {} + serialize :preferences, UserPreferences # Relations has_many :invitations, dependent: :destroy @@ -64,7 +64,7 @@ class User < ApplicationRecord if inviter.present? if Setting.ejabberd_enabled? && - inviter.pref_enabled?("xmpp:exchange_contacts_with_invitees") + inviter.preferences[:xmpp_exchange_contacts_with_invitees] exchange_xmpp_contact_with_inviter end end @@ -138,11 +138,6 @@ class User < ApplicationRecord ldap.delete_attribute(dn,:service) end - def pref_enabled?(key) - value = preferences.dig(*key.split(":")) - [true, "true", "enabled", 1].include?(value) - end - def exchange_xmpp_contact_with_inviter return unless inviter.services_enabled.include?("xmpp") && services_enabled.include?("xmpp") diff --git a/app/models/user_preferences.rb b/app/models/user_preferences.rb new file mode 100644 index 0000000..ffdf7f6 --- /dev/null +++ b/app/models/user_preferences.rb @@ -0,0 +1,29 @@ +DEFAULT_PREFS = YAML.load_file("#{Rails.root}/config/default_preferences.yml") + +class UserPreferences + def self.dump(value) + process(value).to_yaml + end + + def self.load(string) + stored_prefs = YAML.load(string || "{}") + DEFAULT_PREFS.merge(stored_prefs).with_indifferent_access + end + + def self.is_integer?(value) + value.to_i.to_s == value + end + + def self.process(hash) + hash.each do |key, value| + if value == "true" + hash[key] = true + elsif value == "false" + hash[key] = false + elsif value.is_a?(String) && is_integer?(value) + hash[key] = value.to_i + end + end + hash.stringify_keys!.to_h + end +end diff --git a/app/views/settings/_lightning.html.erb b/app/views/settings/_lightning.html.erb index b7ad6e2..6e75343 100644 --- a/app/views/settings/_lightning.html.erb +++ b/app/views/settings/_lightning.html.erb @@ -8,13 +8,11 @@ description: "Notify me when sats are sent to my Lightning Address" ) do %> <% f.fields_for :preferences do |p| %> - <% p.fields_for :lightning do |l| %> - <%= l.select :notify_sats_received, options_for_select([ - ["off", "off"], - ["Chat (Jabber)", "xmpp"], - ["E-Mail", "email"] - ], selected: @user.preferences.dig('lightning', 'notify_sats_received')) %> - <% end %> + <%= p.select :lightning_notify_sats_received, options_for_select([ + ["off", "disabled"], + ["Chat (Jabber)", "xmpp"], + ["E-Mail", "email"] + ], selected: @user.preferences[:lightning_notify_sats_received]) %> <% end %> <% end %> diff --git a/app/views/settings/_xmpp.html.erb b/app/views/settings/_xmpp.html.erb index 3ce4e91..a13ce70 100644 --- a/app/views/settings/_xmpp.html.erb +++ b/app/views/settings/_xmpp.html.erb @@ -3,8 +3,8 @@

Contacts

    <%= render FormElements::FieldsetToggleComponent.new( - field_name: "user[preferences][xmpp][exchange_contacts_with_invitees]", - enabled: @user.pref_enabled?("xmpp:exchange_contacts_with_invitees"), + field_name: "user[preferences][xmpp_exchange_contacts_with_invitees]", + enabled: @user.preferences[:xmpp_exchange_contacts_with_invitees], title: "Exchange contacts when invited user signs up", description: "Add each others contacts, so you can chat with them immediately" ) %> diff --git a/config/default_preferences.yml b/config/default_preferences.yml new file mode 100644 index 0000000..ff7f051 --- /dev/null +++ b/config/default_preferences.yml @@ -0,0 +1,2 @@ +lightning_notify_sats_received: disabled # or xmpp, email +xmpp_exchange_contacts_with_invitees: true diff --git a/spec/models/user_preferences_spec.rb b/spec/models/user_preferences_spec.rb new file mode 100644 index 0000000..4bb7b42 --- /dev/null +++ b/spec/models/user_preferences_spec.rb @@ -0,0 +1,41 @@ +require 'rails_helper' + +RSpec.describe UserPreferences, type: :model do + let(:default_prefs) { YAML.load_file("#{Rails.root}/config/default_preferences.yml") } + + describe ".load" do + it "provides default values when no preferences are stored yet" do + expect(UserPreferences.load(nil)).to eq(default_prefs) + end + + it "provides default values for unset preferences" do + prefs = UserPreferences.load("lightning_notify_sats_received: xmpp") + expect(prefs[:lightning_notify_sats_received]).to eq("xmpp") + expect(prefs[:xmpp_exchange_contacts_with_invitees]).to eq(true) + end + end + + describe ".process" do + it "turns all keys into strings" do + res = UserPreferences.process({ foo: "bar" }) + expect(res[:foo]).to be(nil) + expect(res['foo']).to eq("bar") + end + + it "converts value 'true' to boolean" do + res = UserPreferences.process({ lightning_notify_sats_received: "true" }) + expect(res['lightning_notify_sats_received']).to be(true) + end + + it "converts value 'false' to boolean" do + res = UserPreferences.process({ lightning_notify_sats_received: "false" }) + expect(res['lightning_notify_sats_received']).to be(false) + end + + it "converts value string with integer into integer" do + res = UserPreferences.process({ lightning_notify_sats_received_threshold: 1000 }) + expect(res['lightning_notify_sats_received_threshold']).to be_a(Integer) + expect(res['lightning_notify_sats_received_threshold']).to eq(1000) + end + end +end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index a36458f..c1105c9 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -140,7 +140,7 @@ RSpec.describe User, type: :model do before do # TODO remove when defaults are implemented - user.update! preferences: {"xmpp" => { "exchange_contacts_with_invitees" => true }} + user.update! preferences: { xmpp_exchange_contacts_with_invitees: true } Invitation.create! user: user, invited_user_id: guest.id, used_at: DateTime.now allow_any_instance_of(User).to receive(:enable_service).and_return(true) end @@ -152,7 +152,7 @@ RSpec.describe User, type: :model do context "automatic contact exchange disabled" do before do - user.update! preferences: {"xmpp" => { "exchange_contacts_with_invitees" => false }} + user.update! preferences: { xmpp_exchange_contacts_with_invitees: false } end it "does not exchange XMPP contacts with the inviter" do @@ -162,50 +162,4 @@ RSpec.describe User, type: :model do end end end - - describe "#pref_enabled?" do - describe "preference not set" do - # TODO return default value - it "returns false" do - expect(user.pref_enabled?("lightning:notify_sats_received")).to be(false) - end - end - - describe "preference is set" do - it "returns true for boolean true" do - user.preferences.merge!({"lightning" => {"notify_sats_received" => true}}) - expect(user.pref_enabled?("lightning:notify_sats_received")).to be(true) - end - - it "returns true for string 'true'" do - user.preferences.merge!({"lightning" => {"notify_sats_received" => "true"}}) - expect(user.pref_enabled?("lightning:notify_sats_received")).to be(true) - end - - it "returns true for string 'enabled'" do - user.preferences.merge!({"lightning" => {"notify_sats_received" => "enabled"}}) - expect(user.pref_enabled?("lightning:notify_sats_received")).to be(true) - end - - it "returns true for integer 1" do - user.preferences.merge!({"lightning" => {"notify_sats_received" => 1}}) - expect(user.pref_enabled?("lightning:notify_sats_received")).to be(true) - end - - it "returns false for boolean false" do - user.preferences.merge!({"lightning" => {"notify_sats_received" => false}}) - expect(user.pref_enabled?("lightning:notify_sats_received")).to be(false) - end - - it "returns false for string 'false'" do - user.preferences.merge!({"lightning" => {"notify_sats_received" => "false"}}) - expect(user.pref_enabled?("lightning:notify_sats_received")).to be(false) - end - - it "returns false for integer 0" do - user.preferences.merge!({"lightning" => {"notify_sats_received" => 0}}) - expect(user.pref_enabled?("lightning:notify_sats_received")).to be(false) - end - end - end end diff --git a/spec/requests/webhooks_spec.rb b/spec/requests/webhooks_spec.rb index d38cf7c..96a3b8f 100644 --- a/spec/requests/webhooks_spec.rb +++ b/spec/requests/webhooks_spec.rb @@ -68,7 +68,7 @@ RSpec.describe "Webhooks", type: :request do context "notification preference set to 'xmpp'" do before do - user.update! preferences: { "lightning" => { "notify_sats_received" => "xmpp" }} + user.update! preferences: { lightning_notify_sats_received: "xmpp" } post "/webhooks/lndhub", params: payload.to_json end @@ -87,7 +87,7 @@ RSpec.describe "Webhooks", type: :request do context "notification preference set to 'email'" do before do - user.update! preferences: { "lightning" => { "notify_sats_received" => "email" }} + user.update! preferences: { lightning_notify_sats_received: "email" } post "/webhooks/lndhub", params: payload.to_json end From bb82b6b462f036cfb72deeff3ae6349205fdf53c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Thu, 6 Apr 2023 16:24:46 +0200 Subject: [PATCH 27/34] Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6e55882..cc08fee 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ so: After these steps, you should have a working Rails app with a handful of test users running on [http://localhost:3000](http://localhost:3000). Log in with username "admin" and password "admin is admin". All users listed on -[http://localhost:3000/admin/ldap_users](http://localhost:3000/admin/ldap_users) +[http://localhost:3000/admin/users](http://localhost:3000/admin/users) have the password "user is user". ### Rails app From 7a193d664736866281420a12dcbbedc8d1fca013 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Thu, 6 Apr 2023 16:25:01 +0200 Subject: [PATCH 28/34] Add comment --- app/models/user.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/user.rb b/app/models/user.rb index 5b40929..94c3b14 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -22,6 +22,7 @@ class User < ApplicationRecord validates_format_of :cn, without: /\A-/, if: Proc.new{ |u| u.cn.present? }, message: "is invalid. Usernames need to start with a letter." + # FIXME This needs a server restart to apply values validates_format_of :cn, without: /\A(#{Setting.reserved_usernames.join('|')})\z/i, message: "has already been taken" From 83e418cdeea8b33296b1504c50175f5e44815478 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Fri, 7 Apr 2023 20:11:45 +0200 Subject: [PATCH 29/34] Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cc08fee..90fec7f 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ The setup task will first delete any existing entries in the directory tree Note that all 389ds data is stored in `tmp/389ds`. So if you want to start over with a fresh installation, delete both that directory as well as the container. -#### Solargraph +### Solargraph [Solargraph](https://solargraph.org/) is a Ruby language server, which you may use with your editor to add features like auto-completion and syntax From 9b89101afca48897a54f8a0d47a76f5fa6fbc431 Mon Sep 17 00:00:00 2001 From: Garret Alfert Date: Wed, 22 Mar 2023 00:51:33 +0100 Subject: [PATCH 30/34] Basic RemoteStorage settings --- .env.example | 1 + app/models/setting.rb | 10 +++++++++ .../settings/services/_remotestorage.html.erb | 17 +++++++++++++++ .../_admin_sidenav_settings_services.html.erb | 7 +++++++ spec/features/admin/settings_spec.rb | 21 +++++++++++++++++++ 5 files changed, 56 insertions(+) create mode 100644 app/views/admin/settings/services/_remotestorage.html.erb diff --git a/.env.example b/.env.example index 6cff8b9..155ec8a 100644 --- a/.env.example +++ b/.env.example @@ -22,6 +22,7 @@ DISCOURSE_PUBLIC_URL='https://community.kosmos.org' GITEA_PUBLIC_URL='https://gitea.kosmos.org' MASTODON_PUBLIC_URL='https://kosmos.social' MEDIAWIKI_PUBLIC_URL='https://wiki.kosmos.org' +RS_STORAGE_URL='https://storage.kosmos.org' EJABBERD_ADMIN_URL='https://xmpp.kosmos.org/admin' EJABBERD_API_URL='https://xmpp.kosmos.org/api' diff --git a/app/models/setting.rb b/app/models/setting.rb index dff6425..b1364a5 100644 --- a/app/models/setting.rb +++ b/app/models/setting.rb @@ -104,4 +104,14 @@ class Setting < RailsSettings::Base # 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 end diff --git a/app/views/admin/settings/services/_remotestorage.html.erb b/app/views/admin/settings/services/_remotestorage.html.erb new file mode 100644 index 0000000..fdfa3f4 --- /dev/null +++ b/app/views/admin/settings/services/_remotestorage.html.erb @@ -0,0 +1,17 @@ +

    RemoteStorage

    +
      + <%= render FormElements::FieldsetToggleComponent.new( + form: f, + attribute: :remotestorage_enabled, + enabled: Setting.remotestorage_enabled?, + title: "Enable RemoteStorage integration", + description: "RemoteStorage configuration present and features enabled" + ) %> + <% if Setting.remotestorage_enabled? %> + <%= render FormElements::FieldsetComponent.new(title: "Storage URL") do %> + <%= f.text_field :rs_storage_url, + value: Setting.rs_storage_url, + class: "w-full", disabled: true %> + <% end %> + <% end %> +
    diff --git a/app/views/shared/_admin_sidenav_settings_services.html.erb b/app/views/shared/_admin_sidenav_settings_services.html.erb index c897b6d..142f6fc 100644 --- a/app/views/shared/_admin_sidenav_settings_services.html.erb +++ b/app/views/shared/_admin_sidenav_settings_services.html.erb @@ -47,3 +47,10 @@ icon: Setting.nostr_enabled? ? "check" : "x", active: current_page?(admin_settings_services_path(params: { s: "nostr" })), ) %> +<%= render SidenavLinkComponent.new( + level: 2, + name: "RemoteStorage", + path: admin_settings_services_path(params: { s: "remotestorage" }), + icon: Setting.remotestorage_enabled? ? "check" : "x", + active: current_page?(admin_settings_services_path(params: { s: "remotestorage" })), +) %> diff --git a/spec/features/admin/settings_spec.rb b/spec/features/admin/settings_spec.rb index 7a394ba..a522d31 100644 --- a/spec/features/admin/settings_spec.rb +++ b/spec/features/admin/settings_spec.rb @@ -46,5 +46,26 @@ RSpec.describe 'Admin/global settings', type: :feature do expect(page).to_not have_checked_field("setting[ejabberd_enabled]") expect(page).to_not have_field("API URL", disabled: true) end + + scenario "View remoteStorage settings" do + visit admin_settings_services_path(params: { s: "remotestorage" }) + + expect(page).to have_content("Enable RemoteStorage integration") + expect(page).to have_field("Storage URL", + with: "https://storage.kosmos.org", + disabled: true) + end + + scenario "Disable remoteStorage integration" do + visit admin_settings_services_path(params: { s: "remotestorage" }) + expect(page).to have_checked_field("setting[remotestorage_enabled]") + + uncheck "setting[remotestorage_enabled]" + click_button "Save" + + expect(current_url).to eq(admin_settings_services_url(params: { s: "remotestorage" })) + expect(page).to_not have_checked_field("setting[remotestorage_enabled]") + expect(page).to_not have_field("Storage URL", disabled: true) + end end end From a47e4fc16bb8f035ef6bc408a3d3da19edc748d5 Mon Sep 17 00:00:00 2001 From: Garret Alfert Date: Sun, 9 Apr 2023 10:12:08 +0200 Subject: [PATCH 31/34] Add RS storage URL to test env --- .env.test | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.env.test b/.env.test index 0c94493..016655b 100644 --- a/.env.test +++ b/.env.test @@ -6,4 +6,6 @@ LNDHUB_API_URL='http://localhost:3026' LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org' LNDHUB_PUBLIC_KEY='024cd3be18617f39cf645851e3ba63f51fc13f0bb09e3bb25e6fd4de556486d946' +RS_STORAGE_URL='https://storage.kosmos.org' + WEBHOOKS_ALLOWED_IPS='10.1.1.23' From 0774c889182d1e7512fd55f1246d9f5e82902aba Mon Sep 17 00:00:00 2001 From: Garret Alfert Date: Mon, 10 Apr 2023 21:23:21 +0200 Subject: [PATCH 32/34] WebFinger endpoint --- app/controllers/webfinger_controller.rb | 59 +++++++++++++++++++++++++ config/routes.rb | 2 + spec/requests/webfinger_spec.rb | 49 ++++++++++++++++++++ 3 files changed, 110 insertions(+) create mode 100644 app/controllers/webfinger_controller.rb create mode 100644 spec/requests/webfinger_spec.rb diff --git a/app/controllers/webfinger_controller.rb b/app/controllers/webfinger_controller.rb new file mode 100644 index 0000000..8cae5e2 --- /dev/null +++ b/app/controllers/webfinger_controller.rb @@ -0,0 +1,59 @@ +class WebfingerController < ApplicationController + + before_action :allow_cross_origin, :only => [:show] + + layout false + + def show + resource = params[:resource] + + if resource && resource.match(/acct:\w+/) + useraddress = resource.split(":").last + username, org = useraddress.split("@") + username.downcase! + unless User.where(cn: username, ou: org).any? + head 404 and return + end + + render json: webfinger(useraddress).to_json, + content_type: "application/jrd+json" + else + head 422 and return + end + end + + private + + def webfinger(useraddress) + links = []; + + links << remotestorage_link(useraddress) if Setting.remotestorage_enabled + + { "links" => links } + end + + def remotestorage_link(useraddress) + # TODO use when OAuth routes are available + # auth_url = new_rs_oauth_url(useraddress) + auth_url = "https://example.com/rs/oauth" + storage_url = "#{Setting.rs_storage_url}/#{useraddress}" + + { + "rel" => "http://tools.ietf.org/id/draft-dejong-remotestorage", + "href" => storage_url, + "properties" => { + "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/rfc6750#section-2.3" => nil, # access token via a HTTP query parameter + "http://tools.ietf.org/html/rfc7233": "GET", # content range requests + "http://remotestorage.io/spec/web-authoring": nil + } + } + end + + def allow_cross_origin + headers['Access-Control-Allow-Origin'] = '*' + headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, OPTIONS' + end + +end diff --git a/config/routes.rb b/config/routes.rb index e729019..e75eda4 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -54,6 +54,8 @@ Rails.application.routes.draw do end end + get ".well-known/webfinger" => "webfinger#show" + authenticate :user, ->(user) { user.is_admin? } do mount Sidekiq::Web => '/sidekiq' end diff --git a/spec/requests/webfinger_spec.rb b/spec/requests/webfinger_spec.rb new file mode 100644 index 0000000..ffa9ca2 --- /dev/null +++ b/spec/requests/webfinger_spec.rb @@ -0,0 +1,49 @@ +require 'rails_helper' + +RSpec.describe "WebFinger", type: :request do + describe "remoteStorage link relation" do + context "user exists" do + before do + create :user, cn: 'tony', ou: 'kosmos.org' + end + + context "remoteStorage service enabled" do + it "includes the remoteStorage link for the user" do + get "/.well-known/webfinger?resource=acct%3Atony%40kosmos.org" + expect(response).to have_http_status(:ok) + + res = JSON.parse(response.body) + rs_link = res["links"].find {|l| l["rel"] == "http://tools.ietf.org/id/draft-dejong-remotestorage"} + + expect(rs_link["href"]).to eql("https://storage.kosmos.org/tony@kosmos.org") + + oauth_url = rs_link["properties"]["http://tools.ietf.org/html/rfc6749#section-4.2"] + expect(oauth_url).to eql("https://example.com/rs/oauth") + end + end + + context "remoteStorage service disabled" do + before do + Setting.remotestorage_enabled = false + end + + it "does not include the remoteStorage link" do + get "/.well-known/webfinger?resource=acct%3Atony%40kosmos.org" + expect(response).to have_http_status(:ok) + + res = JSON.parse(response.body) + rs_link = res["links"].find {|l| l["rel"] == "http://tools.ietf.org/id/draft-dejong-remotestorage"} + + expect(rs_link).to be_nil + end + end + end + + context "user does not exist" do + it "does return a 404 status" do + get "/.well-known/webfinger?resource=acct%3Ajane.doe%40kosmos.org" + expect(response).to have_http_status(:not_found) + end + end + end +end From 9e74c89a80b9bf38da0040fc4bebffde6b16759b Mon Sep 17 00:00:00 2001 From: Garret Alfert Date: Mon, 10 Apr 2023 23:03:59 +0200 Subject: [PATCH 33/34] Fix failing spec expectation when using Ruby 3.x --- spec/features/signup_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/features/signup_spec.rb b/spec/features/signup_spec.rb index 9959661..30511a7 100644 --- a/spec/features/signup_spec.rb +++ b/spec/features/signup_spec.rb @@ -53,11 +53,11 @@ RSpec.describe "Signup", type: :feature do expect(page).to have_content("Choose a password") expect(CreateAccount).to receive(:call) - .with( + .with({ username: "tony", domain: "kosmos.org", email: "tony@example.com", password: "a-valid-password", invitation: Invitation.last - ).and_return(true) + }).and_return(true) fill_in "user_password", with: "a-valid-password" click_button "Create account" @@ -97,11 +97,11 @@ RSpec.describe "Signup", type: :feature do expect(page).to have_content("Password is too short") expect(CreateAccount).to receive(:call) - .with( + .with({ username: "tony", domain: "kosmos.org", email: "tony@example.com", password: "a-valid-password", invitation: Invitation.last - ).and_return(true) + }).and_return(true) fill_in "user_password", with: "a-valid-password" click_button "Create account" From a2100b23a9c87f0ebadb6bc96a3ded7f4c50dd83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Tue, 11 Apr 2023 11:41:30 +0200 Subject: [PATCH 34/34] Formatting, wording --- app/controllers/webfinger_controller.rb | 6 ++---- spec/requests/webfinger_spec.rb | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/app/controllers/webfinger_controller.rb b/app/controllers/webfinger_controller.rb index 8cae5e2..5cf4012 100644 --- a/app/controllers/webfinger_controller.rb +++ b/app/controllers/webfinger_controller.rb @@ -1,6 +1,5 @@ class WebfingerController < ApplicationController - - before_action :allow_cross_origin, :only => [:show] + before_action :allow_cross_origin_requests, only: [:show] layout false @@ -51,9 +50,8 @@ class WebfingerController < ApplicationController } end - def allow_cross_origin + def allow_cross_origin_requests headers['Access-Control-Allow-Origin'] = '*' headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, OPTIONS' end - end diff --git a/spec/requests/webfinger_spec.rb b/spec/requests/webfinger_spec.rb index ffa9ca2..f944a7a 100644 --- a/spec/requests/webfinger_spec.rb +++ b/spec/requests/webfinger_spec.rb @@ -7,7 +7,7 @@ RSpec.describe "WebFinger", type: :request do create :user, cn: 'tony', ou: 'kosmos.org' end - context "remoteStorage service enabled" do + context "remoteStorage enabled globally" do it "includes the remoteStorage link for the user" do get "/.well-known/webfinger?resource=acct%3Atony%40kosmos.org" expect(response).to have_http_status(:ok) @@ -22,7 +22,7 @@ RSpec.describe "WebFinger", type: :request do end end - context "remoteStorage service disabled" do + context "remoteStorage not available" do before do Setting.remotestorage_enabled = false end