fix(super-admin): prefill confirmed_at in new user form (#13662)

On self-hosted instances without email configured, users created from
Super Admin can get stuck in an unconfirmed state. This PR implements
the default at the Super Admin frontend form layer, not in backend
creation logic.

What changed:
- Added a custom `ConfirmedAtField` for Super Admin user forms.
- Prefills `confirmed_at` with current time on the **New User** form
(`GET /super_admin/users/new`).
- Kept backend create behavior unchanged
(`resource_class.new(resource_params)`), so API/manual payloads still
behave normally.

Behavior:
- In Super Admin UI, `confirmed_at` is prefilled by default.
- If someone wants an unconfirmed user, they can clear the
`confirmed_at` field before saving.
- If `confirmed_at` is omitted from payload entirely, the created user
remains unconfirmed.

Scope note: external signup flows are intentionally unchanged in this PR
(`/api/v1/accounts`, `/api/v2/accounts`, and social/omniauth signup
behavior are not modified).

## Demo 





https://github.com/user-attachments/assets/436abbb0-d4cf-49a6-a1b8-4b6aa85aa09f
This commit is contained in:
Sojan Jose
2026-03-09 23:44:58 -07:00
committed by GitHub
parent 19683fae74
commit 52cd70dfa3
4 changed files with 60 additions and 1 deletions

View File

@@ -25,7 +25,7 @@ class UserDashboard < Administrate::BaseDashboard
current_sign_in_ip: Field::String,
last_sign_in_ip: Field::String,
confirmation_token: Field::String,
confirmed_at: Field::DateTime,
confirmed_at: ConfirmedAtField,
confirmation_sent_at: Field::DateTime,
unconfirmed_email: Field::String,
name: Field::String.with_options(searchable: true),

View File

@@ -0,0 +1,4 @@
require 'administrate/field/base'
class ConfirmedAtField < Administrate::Field::DateTime
end

View File

@@ -0,0 +1,8 @@
<div class="field-unit__label">
<%= f.label field.attribute %>
</div>
<div class="field-unit__field">
<% value = field.data %>
<% value = Time.current if value.blank? && action_name == 'new' %>
<%= f.datetime_local_field field.attribute, step: 1, value: value %>
</div>

View File

@@ -23,6 +23,25 @@ RSpec.describe 'Super Admin Users API', type: :request do
type: 'SuperAdmin'
} }
end
let!(:params_without_confirmed_at) do
{ user: {
name: 'agent@example.com',
display_name: 'agent@example.com',
email: 'agent@example.com',
password: 'Password1!',
type: 'SuperAdmin'
} }
end
let!(:params_with_blank_confirmed_at) do
{ user: {
name: 'agent-2@example.com',
display_name: 'agent-2@example.com',
email: 'agent-2@example.com',
password: 'Password1!',
confirmed_at: '',
type: 'SuperAdmin'
} }
end
it 'shows the list of users' do
sign_in(super_admin, scope: :super_admin)
@@ -36,6 +55,16 @@ RSpec.describe 'Super Admin Users API', type: :request do
expect(header_texts).not_to include('MFA')
end
it 'prefills confirmed_at on new user form' do
sign_in(super_admin, scope: :super_admin)
get '/super_admin/users/new'
expect(response).to have_http_status(:success)
expect(response.body).to include('name="user[confirmed_at]"')
confirmed_at_value = response.body[/name="user\[confirmed_at\]".*?value="([^"]+)"/m, 1]
expect(confirmed_at_value).to be_present
end
it 'creates the new super_admin record' do
sign_in(super_admin, scope: :super_admin)
@@ -47,6 +76,24 @@ RSpec.describe 'Super Admin Users API', type: :request do
post '/super_admin/users', params: params
expect(response).to redirect_to('http://www.example.com/super_admin/users/new')
end
it 'creates unconfirmed users when confirmed_at is not provided in payload' do
sign_in(super_admin, scope: :super_admin)
post '/super_admin/users', params: params_without_confirmed_at
expect(response).to redirect_to("http://www.example.com/super_admin/users/#{User.last.id}")
expect(User.last).not_to be_confirmed
end
it 'creates unconfirmed users when confirmed_at is explicitly cleared' do
sign_in(super_admin, scope: :super_admin)
post '/super_admin/users', params: params_with_blank_confirmed_at
expect(response).to redirect_to("http://www.example.com/super_admin/users/#{User.last.id}")
expect(User.last).not_to be_confirmed
end
end
end