diff --git a/app/controllers/api/v1/auth_controller.rb b/app/controllers/api/v1/auth_controller.rb index 4f20b9346..a12e290ad 100644 --- a/app/controllers/api/v1/auth_controller.rb +++ b/app/controllers/api/v1/auth_controller.rb @@ -36,9 +36,10 @@ module Api user = User.new(user_signup_params) # Create family for new user + # First user of an instance becomes super_admin family = Family.new user.family = family - user.role = :admin + user.role = User.role_for_new_family_creator if user.save # Claim invite code if provided diff --git a/app/controllers/oidc_accounts_controller.rb b/app/controllers/oidc_accounts_controller.rb index 4e7c9275f..15a5df757 100644 --- a/app/controllers/oidc_accounts_controller.rb +++ b/app/controllers/oidc_accounts_controller.rb @@ -114,10 +114,11 @@ class OidcAccountsController < ApplicationController # Create new family for this user @user.family = Family.new - # Use provider-configured default role, or fall back to member (not admin) + # Use provider-configured default role, or fall back to admin for family creators + # First user of an instance always becomes super_admin regardless of provider config provider_config = Rails.configuration.x.auth.sso_providers&.find { |p| p[:name] == @pending_auth["provider"] } - default_role = provider_config&.dig(:settings, :default_role) || "member" - @user.role = default_role + provider_default_role = provider_config&.dig(:settings, :default_role) + @user.role = User.role_for_new_family_creator(fallback_role: provider_default_role || :admin) if @user.save # Create the OIDC (or other SSO) identity diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index 83287e708..93cc303bd 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -21,7 +21,7 @@ class RegistrationsController < ApplicationController else family = Family.new @user.family = family - @user.role = :admin + @user.role = User.role_for_new_family_creator end if @user.save diff --git a/app/models/user.rb b/app/models/user.rb index c38debf17..98f7d983b 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -32,6 +32,13 @@ class User < ApplicationRecord enum :role, { member: "member", admin: "admin", super_admin: "super_admin" }, validate: true + # Returns the appropriate role for a new user creating a family. + # The very first user of an instance becomes super_admin; subsequent users + # get the specified fallback role (typically :admin for family creators). + def self.role_for_new_family_creator(fallback_role: :admin) + User.exists? ? fallback_role : :super_admin + end + has_one_attached :profile_image do |attachable| attachable.variant :thumbnail, resize_to_fill: [ 300, 300 ], convert: :webp, saver: { quality: 80 } attachable.variant :small, resize_to_fill: [ 72, 72 ], convert: :webp, saver: { quality: 80 }, preprocessed: true diff --git a/test/controllers/oidc_accounts_controller_test.rb b/test/controllers/oidc_accounts_controller_test.rb index a26975176..bd38aedcc 100644 --- a/test/controllers/oidc_accounts_controller_test.rb +++ b/test/controllers/oidc_accounts_controller_test.rb @@ -178,7 +178,7 @@ class OidcAccountsControllerTest < ActionController::TestCase assert_not_nil new_user assert_equal new_user_auth["first_name"], new_user.first_name assert_equal new_user_auth["last_name"], new_user.last_name - assert_equal "member", new_user.role + assert_equal "admin", new_user.role # Family creators should be admin # Verify OIDC identity was created oidc_identity = new_user.oidc_identities.first diff --git a/test/controllers/registrations_controller_test.rb b/test/controllers/registrations_controller_test.rb index 1050627ef..70dcef9ef 100644 --- a/test/controllers/registrations_controller_test.rb +++ b/test/controllers/registrations_controller_test.rb @@ -14,6 +14,35 @@ class RegistrationsControllerTest < ActionDispatch::IntegrationTest assert_redirected_to root_url end + test "first user of instance becomes super_admin" do + # Clear all users to simulate fresh instance + User.destroy_all + + assert_difference "User.count", +1 do + post registration_url, params: { user: { + email: "firstuser@example.com", + password: "Password1!" } } + end + + first_user = User.find_by(email: "firstuser@example.com") + assert first_user.super_admin?, "First user should be super_admin" + end + + test "subsequent users become admin not super_admin" do + # Ensure users exist from fixtures + assert User.exists? + + assert_difference "User.count", +1 do + post registration_url, params: { user: { + email: "seconduser@example.com", + password: "Password1!" } } + end + + new_user = User.find_by(email: "seconduser@example.com") + assert new_user.admin?, "Subsequent user should be admin" + assert_not new_user.super_admin?, "Subsequent user should not be super_admin" + end + test "create when hosted requires an invite code" do with_env_overrides REQUIRE_INVITE_CODE: "true" do assert_no_difference "User.count" do diff --git a/test/models/user_test.rb b/test/models/user_test.rb index b4558744e..a1c0c496e 100644 --- a/test/models/user_test.rb +++ b/test/models/user_test.rb @@ -331,4 +331,21 @@ class UserTest < ActiveSupport::TestCase assert_not user.valid? assert_includes user.errors[:password], "can't be blank" end + + # First user role assignment tests + test "role_for_new_family_creator returns super_admin when no users exist" do + # Delete all users to simulate fresh instance + User.destroy_all + + assert_equal :super_admin, User.role_for_new_family_creator + end + + test "role_for_new_family_creator returns fallback role when users exist" do + # Users exist from fixtures + assert User.exists? + + assert_equal :admin, User.role_for_new_family_creator + assert_equal :member, User.role_for_new_family_creator(fallback_role: :member) + assert_equal "custom_role", User.role_for_new_family_creator(fallback_role: "custom_role") + end end