diff --git a/app/controllers/settings/users_controller.rb b/app/controllers/settings/users_controller.rb index 11247cbc9..c637b9990 100644 --- a/app/controllers/settings/users_controller.rb +++ b/app/controllers/settings/users_controller.rb @@ -42,6 +42,10 @@ def update if params[:reset] @user.send_reset_password_instructions redirect_to [:settings, @user], notice: t(".reset_link_sent") + elsif params[:approve] + @user.update(approved: true) + UserMailer.with(user: @user).account_approved.deliver_later if SiteSettings.email_configured? + redirect_to [:settings, @user], notice: t(".approved") elsif @user.update(user_params) redirect_to [:settings, @user], notice: t(".success") else diff --git a/app/controllers/settings_controller.rb b/app/controllers/settings_controller.rb index 9cc38361d..b25dcbe0b 100644 --- a/app/controllers/settings_controller.rb +++ b/app/controllers/settings_controller.rb @@ -36,6 +36,7 @@ def update_analysis_settings(settings) def update_multiuser_settings(settings) return unless settings + SiteSettings.approve_signups = (settings[:approve_signups]) SiteSettings.default_viewer_role = (settings[:default_viewer_role].presence) end diff --git a/app/controllers/users/registrations_controller.rb b/app/controllers/users/registrations_controller.rb index e04e5c744..6f8490866 100644 --- a/app/controllers/users/registrations_controller.rb +++ b/app/controllers/users/registrations_controller.rb @@ -27,7 +27,9 @@ def edit # POST /users def create authorize User - super + super do |user| + user.update(approved: false) if SiteSettings.approve_signups + end end # PUT /resource diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb new file mode 100644 index 000000000..4bb8ff05a --- /dev/null +++ b/app/mailers/user_mailer.rb @@ -0,0 +1,6 @@ +class UserMailer < ApplicationMailer + def account_approved + @user = params[:user] + mail to: @user.email + end +end diff --git a/app/models/site_settings.rb b/app/models/site_settings.rb index 6d2d09d4a..8d6e60bd3 100644 --- a/app/models/site_settings.rb +++ b/app/models/site_settings.rb @@ -13,6 +13,7 @@ class SiteSettings < RailsSettings::Base field :analyse_manifold, type: :boolean, default: false field :anonymous_usage_id, type: :string, default: nil field :default_viewer_role, type: :string, default: "member" + field :approve_signups, type: :boolean, default: false def self.registration_enabled? Rails.application.config.manyfold_features[:registration] diff --git a/app/models/user.rb b/app/models/user.rb index 371d63e78..e65c0416b 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -117,6 +117,27 @@ def self.user_count(range) User.where(updated_at: range).count end + # Devise approval checks + def active_for_authentication? + super && approved? + end + + def inactive_message + approved? ? super : :not_approved + end + + def self.send_reset_password_instructions(attributes = {}) + recoverable = find_or_initialize_with_errors(reset_password_keys, attributes, :not_found) + if recoverable.persisted? + if recoverable.approved? + recoverable.send_reset_password_instructions + else + recoverable.errors.add(:base, :not_approved) + end + end + recoverable + end + private def has_any_role_of?(*args) diff --git a/app/views/devise/registrations/new.html.erb b/app/views/devise/registrations/new.html.erb index 54d19a138..92cc0b29d 100644 --- a/app/views/devise/registrations/new.html.erb +++ b/app/views/devise/registrations/new.html.erb @@ -5,6 +5,7 @@ <%= text_input_row form, :username, autocomplete: "username" %> <%= password_input_row form, :password, autocomplete: "new-password" %> <%= password_input_row form, :password_confirmation, autocomplete: "new-password" %> + <%= content_tag :div, t(".approval_help"), class: "alert alert-info" if SiteSettings.approve_signups %> <%= form.submit translate(".sign_up"), class: "btn btn-primary" %> <% end %> diff --git a/app/views/settings/multiuser.html.erb b/app/views/settings/multiuser.html.erb index edb63f235..13e22ba7d 100644 --- a/app/views/settings/multiuser.html.erb +++ b/app/views/settings/multiuser.html.erb @@ -9,7 +9,7 @@
%{endpoint}
. The id
is randomly generated when you enable tracking.
@@ -88,9 +92,11 @@ en:
new:
title: Create user
show:
+ approve: Approve user
confirm_destroy: Are you sure you want to remove this user account immediately? This cannot be undone!
reset_password: Reset password
title: 'User details: %{username}'
update:
+ approved: User was approved successfully.
reset_link_sent: A link has been sent to the user for them to set a new password.
success: User updated successfully.
diff --git a/db/migrate/20241122121621_add_approved_to_user.rb b/db/migrate/20241122121621_add_approved_to_user.rb
new file mode 100644
index 000000000..be9d1087e
--- /dev/null
+++ b/db/migrate/20241122121621_add_approved_to_user.rb
@@ -0,0 +1,8 @@
+class AddApprovedToUser < ActiveRecord::Migration[7.2]
+ def change
+ change_table :users do |t|
+ t.boolean :approved, default: true, null: false
+ t.index :approved
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 0f1d8932e..dfa396a99 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.2].define(version: 2024_11_05_121830) do
+ActiveRecord::Schema[7.2].define(version: 2024_11_22_121621) do
create_table "caber_relations", force: :cascade do |t|
t.string "subject_type"
t.integer "subject_id"
@@ -309,6 +309,8 @@
t.string "auth_uid"
t.string "sensitive_content_handling"
t.string "public_id"
+ t.boolean "approved", default: true, null: false
+ t.index ["approved"], name: "index_users_on_approved"
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["public_id"], name: "index_users_on_public_id"
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
diff --git a/spec/mailers/user_mailer_spec.rb b/spec/mailers/user_mailer_spec.rb
new file mode 100644
index 000000000..1f4fd9dae
--- /dev/null
+++ b/spec/mailers/user_mailer_spec.rb
@@ -0,0 +1,20 @@
+require "rails_helper"
+
+RSpec.describe UserMailer do
+ describe "account_approved" do
+ let(:user) { create(:user) }
+ let(:mail) { described_class.with(user: user).account_approved }
+
+ it "sets correct subject" do
+ expect(mail.subject).to eq("Account approved")
+ end
+
+ it "sends to user" do
+ expect(mail.to).to eq([user.email])
+ end
+
+ it "renders the body" do
+ expect(mail.body.encoded).to match("Hi")
+ end
+ end
+end
diff --git a/spec/requests/users/registrations_spec.rb b/spec/requests/users/registrations_spec.rb
index f584e718b..5b57f3e57 100644
--- a/spec/requests/users/registrations_spec.rb
+++ b/spec/requests/users/registrations_spec.rb
@@ -270,13 +270,17 @@
end
end
- describe "POST /users" do
+ describe "POST /users with approval disabled" do
before { post "/users", params: post_options }
it "creates a new user" do
expect(User.count).to eq 2
end
+ it "creates user in approved state" do
+ expect(User.last).to be_approved
+ end
+
it "redirects to root" do
expect(response).to redirect_to("/")
end
@@ -286,6 +290,29 @@
end
end
+ describe "POST /users with approval enabled" do
+ before {
+ allow(SiteSettings).to receive(:approve_signups).and_return(true)
+ post "/users", params: post_options
+ }
+
+ it "creates a new user" do
+ expect(User.count).to eq 2
+ end
+
+ it "creates user in pending state" do
+ expect(User.last).not_to be_approved
+ end
+
+ it "redirects to root" do
+ expect(response).to redirect_to("/")
+ end
+
+ it "does not sign in the user" do
+ expect(controller.current_user).to be_nil
+ end
+ end
+
describe "GET /users/cancel without a signup in progress" do
before { get "/users/cancel" }