Files
Jyotisha/frontend/tests/database-admin-owner-recovery.test.ts
T
Jesse_Chen 66a6c96a5c fix(admin): fail safe across migration and auth boundaries
The recovery migration crossed the identity and RBAC ledgers without guarding schema prerequisites, while unknown configuration, provider, and database failures escaped the admin authorization boundary as 500s. Keep recovery in the DB ledger with explicit prerequisite no-ops, and sanitize unknown authorization failures to the existing 503 path.
2026-08-07 12:08:58 +08:00

170 lines
6.5 KiB
TypeScript

import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import { readFileSync } from "node:fs";
import test from "node:test";
import { fileURLToPath } from "node:url";
import { startPostgresFixture } from "./helpers/postgres-fixture.ts";
const runnerPath = fileURLToPath(new URL("../scripts/db-migrate.mjs", import.meta.url));
const recoveryMigration = readFileSync(
new URL("../db/migrations/20260807010000_recover_initial_admin_owner.sql", import.meta.url),
"utf8",
);
const ids = {
first: "30000000-0000-4000-8000-000000000001",
second: "30000000-0000-4000-8000-000000000002",
third: "30000000-0000-4000-8000-000000000003",
blocked: "30000000-0000-4000-8000-000000000004",
unsynced: "30000000-0000-4000-8000-000000000005",
};
const ownerCountSql = `
select 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'
`;
test("Owner recovery grants only one currently loginable synced identity admin", () => {
const fixture = startPostgresFixture();
const schemaSql = (sql: string) => fixture.psqlAs(
"schema_owner",
"schema-owner-test-password",
sql,
);
try {
const migration = spawnSync(process.execPath, [runnerPath], {
encoding: "utf8",
env: {
...process.env,
SCHEMA_DATABASE_URL: fixture.connectionUrl(
"schema_owner",
"schema-owner-test-password",
),
},
});
assert.equal(migration.status, 0, migration.stderr);
assert.match(migration.stdout, /applied 20260807010000_recover_initial_admin_owner\.sql/);
assert.equal(fixture.psql(ownerCountSql), "0", "a truly empty database stays empty");
schemaSql(`
alter table identity.users disable trigger identity_user_business_auth_sync;
insert into identity.users (id, name, email, email_verified, email_verified_at, role)
values ('${ids.unsynced}', 'Unsynced Admin', 'unsynced-admin@example.com', true, now(), 'admin');
alter table identity.users enable trigger identity_user_business_auth_sync;
`);
assert.equal(
fixture.psql(`select count(*) from auth.users where id = '${ids.unsynced}'`),
"0",
);
assert.throws(
() => schemaSql(recoveryMigration),
/admin_owner_recovery_requires_exactly_one_active_identity_admin: found 0/,
"an unsynced identity account is not a truly empty database",
);
fixture.psqlAs("identity_runtime", "identity-runtime-test-password", `
insert into identity.users
(id, name, email, email_verified, email_verified_at, role, banned, ban_expires)
values
('${ids.first}', 'First Admin', 'first-admin@example.com', true, now(), ' user , admin ', true, now() - interval '1 hour'),
('${ids.blocked}', 'Blocked Admin', 'blocked-admin@example.com', true, now(), 'admin', true, null)
`);
schemaSql(recoveryMigration);
assert.equal(
fixture.psql(`
select 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.user_id = '${ids.first}' and au.revoked_at is null and ar.code = 'owner'
`),
"1",
);
assert.equal(fixture.psql(ownerCountSql), "1");
fixture.psqlAs("identity_runtime", "identity-runtime-test-password", `
insert into identity.users (id, name, email, email_verified, email_verified_at, role)
values ('${ids.second}', 'Second Admin', 'second-admin@example.com', true, now(), 'admin')
`);
schemaSql(recoveryMigration);
assert.equal(
fixture.psql(`
select string_agg(aur.admin_user_id::text, ',' order by aur.admin_user_id)
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'
`),
ids.first,
"an existing active Owner makes recovery a no-op",
);
schemaSql(`
alter table public.admin_users disable trigger admin_users_require_active_owner;
update public.admin_users
set revoked_at = timestamptz '2026-08-07 01:23:45+00',
revoked_by = '${ids.first}'
where user_id = '${ids.first}';
alter table public.admin_users enable trigger admin_users_require_active_owner;
`);
schemaSql(recoveryMigration);
assert.equal(
fixture.psql(`
select string_agg(aur.admin_user_id::text, ',' order by aur.admin_user_id)
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'
`),
ids.second,
"a revoked historical admin is excluded instead of being restored",
);
assert.equal(
fixture.psql(`
select count(*)
from public.admin_users
where user_id = '${ids.first}'
and revoked_at = timestamptz '2026-08-07 01:23:45+00'
and revoked_by = '${ids.first}'
`),
"1",
"recovery preserves the historical revocation fields",
);
fixture.psqlAs("identity_runtime", "identity-runtime-test-password", `
insert into identity.users (id, name, email, email_verified, email_verified_at, role)
values ('${ids.third}', 'Third Admin', 'third-admin@example.com', true, now(), 'admin')
`);
schemaSql(`
alter table public.admin_user_roles disable trigger admin_user_roles_require_active_owner;
delete from public.admin_user_roles
where role_id = (select id from public.admin_roles where code = 'owner');
alter table public.admin_user_roles enable trigger admin_user_roles_require_active_owner;
`);
assert.throws(
() => schemaSql(recoveryMigration),
/admin_owner_recovery_requires_exactly_one_active_identity_admin: found 2/,
);
assert.equal(fixture.psql(ownerCountSql), "0");
fixture.psqlAs("identity_runtime", "identity-runtime-test-password", `
update identity.users set role = 'user' where id in ('${ids.second}', '${ids.third}')
`);
assert.throws(
() => schemaSql(recoveryMigration),
/admin_owner_recovery_requires_exactly_one_active_identity_admin: found 0/,
"a revoked identity admin remains ineligible even when it is the only admin role",
);
} finally {
fixture.stop();
}
});