fix(admin): preserve revoked owners and 503 status

Owner recovery lived in the identity-only migration path and could clear historical revocations on conflict. Admin layout also rethrew authorization 503s, turning service-unavailable failures into 500 responses.\n\nMove recovery behind the RBAC migration sequence, exclude revoked candidates without mutating their history, and terminate layout redirects at a no-store 503 route.
This commit is contained in:
Jesse_Chen
2026-08-07 11:33:48 +08:00
parent 0d7e5a26d5
commit 754505d40a
7 changed files with 95 additions and 15 deletions
@@ -1,88 +0,0 @@
begin;
do $$
declare
v_active_owner_count integer;
v_candidate_count integer;
v_candidate_id uuid;
v_owner_role_id uuid;
begin
-- Serialize recovery with the last-Owner protection used by the RBAC functions.
perform pg_catalog.pg_advisory_xact_lock(1096040772, 1);
select count(*)
into v_active_owner_count
from public.admin_users au
join public.admin_user_roles aur on aur.admin_user_id = au.user_id
join public.admin_roles ar on ar.id = aur.role_id
where au.revoked_at is null
and ar.code = 'owner';
-- Once an active Owner exists, role management remains the only authority.
if v_active_owner_count > 0 then
return;
end if;
-- Keep the candidate set stable while recovery decides and writes.
lock table identity.users, auth.users in share mode;
-- A pristine database has no account to recover yet. Any populated system
-- without an Owner must have exactly one active identity-admin candidate.
if not exists (select 1 from identity.users)
and not exists (select 1 from auth.users) then
return;
end if;
select count(*), (array_agg(u.id order by u.id))[1]
into v_candidate_count, v_candidate_id
from identity.users u
join auth.users a on a.id = u.id
where (not u.banned or (u.ban_expires is not null and u.ban_expires <= clock_timestamp()))
and exists (
select 1
from unnest(string_to_array(u.role, ',')) as role_part(value)
where btrim(role_part.value) = 'admin'
);
if v_candidate_count <> 1 then
raise exception 'admin_owner_recovery_requires_exactly_one_active_identity_admin: found %',
v_candidate_count
using errcode = '23514';
end if;
select id
into v_owner_role_id
from public.admin_roles
where code = 'owner';
if v_owner_role_id is null then
raise exception 'admin_owner_recovery_owner_role_missing'
using errcode = '23514';
end if;
insert into public.admin_users (user_id, created_by)
values (v_candidate_id, v_candidate_id)
on conflict on constraint admin_users_pkey do update set
revoked_at = null,
revoked_by = null,
updated_at = now();
insert into public.admin_user_roles (admin_user_id, role_id, assigned_by)
values (v_candidate_id, v_owner_role_id, v_candidate_id)
on conflict do nothing;
if not exists (
select 1
from public.admin_users au
join public.admin_user_roles aur on aur.admin_user_id = au.user_id
where au.user_id = v_candidate_id
and au.revoked_at is null
and aur.role_id = v_owner_role_id
) then
raise exception 'admin_owner_recovery_failed'
using errcode = '23514';
end if;
end;
$$;
commit;