实现(08-10-production-employee-smoke-break-glass-reset): 增加员工Smoke受控重置工具
ci-e2e / e2e (push) Canceled after 0s
ci-minimal / check-minimal (push) Canceled after 0s

这个提交包含在:
2026-08-10 21:01:59 +08:00
父节点 b807762c52
当前提交 ec68256588
共修改 10 个文件,包含 1310 行新增和 0 行删除
@@ -646,6 +646,7 @@ if (require.main === module) {
module.exports = {
DEFAULT_TARGETS,
applyTarget,
execute,
isSafeOaOnlySmokeTarget,
isSmokeLikeValue,
@@ -0,0 +1,503 @@
#!/usr/bin/env node
const crypto = require("node:crypto");
const { createPrismaClient } = require("../prismaClient");
const { resolvePermissionsForRoles } = require("../constants/permissions");
const { hashPassword } = require("../utils/crypto");
const { readPasswordFromStdin } = require("./reset-active-admin-password");
const { isSmokeLikeValue } = require("./repair-oa-only-smoke-accounts");
const APPLY_CONFIRMATION = "EMPLOYEE_SMOKE_BREAK_GLASS";
const AUDIT_ACTION = "SYSTEM_EMPLOYEE_SMOKE_BREAK_GLASS_PASSWORD_RESET";
const PASSWORD_CHANGED_ACTION = "AUTH_PASSWORD_CHANGED";
const EXPECTED_ROLES = Object.freeze(["employee"]);
const FORBIDDEN_PERMISSIONS = Object.freeze([
"*",
"project.manage",
"project.view_all",
"system.settings",
]);
const PRIVACY =
"target count/status/role/permission booleans only; no password/token/secret/identifier/full email/full phone";
function writeResult(result) {
process.stdout.write(`${JSON.stringify({ ...result, privacy: PRIVACY })}\n`);
}
function sortedRoleCodes(row) {
const roles = Array.isArray(row?.roles) ? row.roles : [];
return roles
.map((item) => (typeof item === "string" ? item : item?.role_code))
.filter((item) => typeof item === "string")
.sort();
}
function rolesAreExactEmployee(row) {
const roles = sortedRoleCodes(row);
return roles.length === EXPECTED_ROLES.length && roles[0] === EXPECTED_ROLES[0];
}
function isSmokeRow(row) {
return [row?.employee_no, row?.email, row?.name].some(isSmokeLikeValue);
}
function isIdentifierReady(row) {
return [row?.email, row?.phone].some(
(value) => typeof value === "string" && value.trim().length > 0,
);
}
function buildPermissionState(row) {
const permissionCodes = resolvePermissionsForRoles(sortedRoleCodes(row));
return {
hasWildcard: permissionCodes.includes("*"),
hasProjectManage: permissionCodes.includes("project.manage"),
hasProjectViewAll: permissionCodes.includes("project.view_all"),
hasSystemSettings: permissionCodes.includes("system.settings"),
};
}
function allManagementPermissionsAbsent(permissionState) {
return FORBIDDEN_PERMISSIONS.every((permission) => {
if (permission === "*") return !permissionState.hasWildcard;
if (permission === "project.manage") return !permissionState.hasProjectManage;
if (permission === "project.view_all") return !permissionState.hasProjectViewAll;
return !permissionState.hasSystemSettings;
});
}
function buildTargetState(row, { expectedMustChangePassword }) {
const permissionState = buildPermissionState(row);
const locksCleared = Boolean(
row &&
row.login_failed_count === 0 &&
row.login_failed_first_at === null &&
row.login_locked_until === null &&
row.email_login_code_failed_count === 0 &&
row.email_login_code_failed_first_at === null &&
row.email_login_code_locked_until === null &&
row.password_reset_failed_count === 0 &&
row.password_reset_failed_first_at === null &&
row.password_reset_locked_until === null,
);
const state = {
activeEmployment: Boolean(
row &&
row.status === "active" &&
row.employment_status === "active" &&
row.archived_at === null,
),
smokeTarget: Boolean(row && isSmokeRow(row)),
identifierReady: Boolean(row && isIdentifierReady(row)),
mustChangePassword: Boolean(row?.must_change_password),
mustChangePasswordMatches:
Boolean(row) && row.must_change_password === expectedMustChangePassword,
locksCleared,
rolesExact: Boolean(row && rolesAreExactEmployee(row)),
...permissionState,
};
return {
...state,
contractManagePermissionsAbsent: allManagementPermissionsAbsent(state),
};
}
function targetStatePasses(state) {
return Boolean(
state.activeEmployment &&
state.smokeTarget &&
state.identifierReady &&
state.mustChangePasswordMatches &&
state.locksCleared &&
state.rolesExact &&
state.contractManagePermissionsAbsent,
);
}
const TARGET_SELECT = Object.freeze({
id: true,
token_version: true,
employee_no: true,
name: true,
email: true,
phone: true,
status: true,
employment_status: true,
archived_at: true,
must_change_password: true,
login_failed_count: true,
login_failed_first_at: true,
login_locked_until: true,
email_login_code_failed_count: true,
email_login_code_failed_first_at: true,
email_login_code_locked_until: true,
password_reset_failed_count: true,
password_reset_failed_first_at: true,
password_reset_locked_until: true,
roles: { select: { role_code: true } },
});
async function findPreflightCandidates(prisma) {
const rows = await prisma.employee.findMany({
where: {
must_change_password: false,
roles: { some: { role_code: "employee" } },
},
select: TARGET_SELECT,
});
return rows.filter((row) => isSmokeRow(row) && rolesAreExactEmployee(row));
}
async function findAuditTargets(prisma) {
const auditTargets = await prisma.auditLog.findMany({
where: {
action: AUDIT_ACTION,
target_type: "employee",
target_id: { not: null },
},
select: { target_id: true },
distinct: ["target_id"],
take: 2,
});
const targetIds = auditTargets.map((item) => item.target_id).filter(Boolean);
if (targetIds.length === 0) return [];
return prisma.employee.findMany({
where: { id: { in: targetIds } },
select: TARGET_SELECT,
});
}
function summarizeTarget(rows, expectedMustChangePassword) {
const exactTarget = rows.length === 1;
const targetState = buildTargetState(exactTarget ? rows[0] : null, {
expectedMustChangePassword,
});
return {
targetMatches: rows.length,
exactTarget,
...targetState,
pass: exactTarget && targetStatePasses(targetState),
};
}
async function preflight(prisma) {
const summary = summarizeTarget(await findPreflightCandidates(prisma), false);
return {
status: summary.pass ? "PASS" : "BLOCKED",
...summary,
};
}
async function countTargetAuthenticationState(prisma, targetId) {
const [activeSessions, activePasswordResetCodes, activeEmailLoginCodes, auditEntries] =
await Promise.all([
prisma.authSession.count({
where: { employee_id: targetId, revoked_at: null },
}),
prisma.passwordResetCode.count({
where: { employee_id: targetId, invalidated_at: null },
}),
prisma.emailLoginCode.count({
where: { employee_id: targetId, used_at: null },
}),
prisma.auditLog.count({
where: {
actor_id: targetId,
target_id: targetId,
action: AUDIT_ACTION,
},
}),
]);
return {
activeSessions,
activePasswordResetCodes,
activeEmailLoginCodes,
auditPresent: auditEntries > 0,
};
}
async function verifyState(prisma) {
const rows = await findAuditTargets(prisma);
const summary = summarizeTarget(rows, true);
if (!summary.exactTarget) {
return { status: "BLOCKED", ...summary };
}
const authState = await countTargetAuthenticationState(prisma, rows[0].id);
const pass = Boolean(
summary.pass &&
authState.activeSessions === 0 &&
authState.activePasswordResetCodes === 0 &&
authState.activeEmailLoginCodes === 0 &&
authState.auditPresent,
);
return {
status: pass ? "PASS" : "BLOCKED",
...summary,
...authState,
pass,
};
}
async function verifyCompletedPasswordChange(prisma) {
const rows = await findAuditTargets(prisma);
const summary = summarizeTarget(rows, false);
if (!summary.exactTarget) {
return { status: "BLOCKED", ...summary };
}
const targetId = rows[0].id;
const [activeSessions, passwordChangeAudits, breakGlassAudits] = await Promise.all([
prisma.authSession.count({
where: { employee_id: targetId, revoked_at: null },
}),
prisma.auditLog.count({
where: {
actor_id: targetId,
target_id: targetId,
action: PASSWORD_CHANGED_ACTION,
},
}),
prisma.auditLog.count({
where: {
actor_id: targetId,
target_id: targetId,
action: AUDIT_ACTION,
},
}),
]);
const pass = Boolean(
summary.pass &&
activeSessions === 1 &&
passwordChangeAudits > 0 &&
breakGlassAudits > 0,
);
return {
status: pass ? "PASS" : "BLOCKED",
...summary,
activeSessions,
passwordChangeAuditPresent: passwordChangeAudits > 0,
breakGlassAuditPresent: breakGlassAudits > 0,
pass,
};
}
async function lockTargetSnapshot(tx, targetId) {
const employeeRows = await tx.$queryRaw`
SELECT
id,
token_version,
employee_no,
name,
email,
phone,
status,
employment_status,
archived_at,
must_change_password,
login_failed_count,
login_failed_first_at,
login_locked_until,
email_login_code_failed_count,
email_login_code_failed_first_at,
email_login_code_locked_until,
password_reset_failed_count,
password_reset_failed_first_at,
password_reset_locked_until
FROM "employees"
WHERE id = ${targetId}::uuid
FOR UPDATE
`;
if (!Array.isArray(employeeRows) || employeeRows.length !== 1) return null;
const roleRows = await tx.$queryRaw`
SELECT role_code::text
FROM "employee_roles"
WHERE employee_id = ${targetId}::uuid
ORDER BY role_code::text
FOR UPDATE
`;
return {
...employeeRows[0],
roles: Array.isArray(roleRows) ? roleRows : [],
};
}
function assertLockedTargetUnchanged(current, target) {
const state = buildTargetState(current, { expectedMustChangePassword: false });
if (
!current ||
current.id !== target.id ||
current.token_version !== target.token_version ||
!targetStatePasses(state)
) {
throw new Error("employee_smoke_target_changed");
}
}
async function applyReset(prisma, password) {
const candidates = await findPreflightCandidates(prisma);
const summary = summarizeTarget(candidates, false);
if (!summary.pass) {
throw new Error("employee_smoke_target_not_unique_or_safe");
}
const target = candidates[0];
const passwordHash = await hashPassword(password);
const now = new Date();
const requestId = `ops-employee-smoke-break-glass-${crypto.randomUUID()}`;
return prisma.$transaction(
async (tx) => {
const current = await lockTargetSnapshot(tx, target.id);
assertLockedTargetUnchanged(current, target);
const employee = await tx.employee.update({
where: { id: target.id },
data: {
password_hash: passwordHash,
must_change_password: true,
login_failed_count: 0,
login_failed_first_at: null,
login_locked_until: null,
email_login_code_failed_count: 0,
email_login_code_failed_first_at: null,
email_login_code_locked_until: null,
password_reset_failed_count: 0,
password_reset_failed_first_at: null,
password_reset_locked_until: null,
password_reset_request_count: 0,
password_reset_request_first_at: null,
password_reset_last_requested_at: null,
token_version: { increment: 1 },
},
select: { must_change_password: true, token_version: true },
});
const revokedSessions = await tx.authSession.updateMany({
where: { employee_id: target.id, revoked_at: null },
data: { revoked_at: now, last_seen_at: now },
});
const invalidatedPasswordResetCodes = await tx.passwordResetCode.updateMany({
where: { employee_id: target.id, invalidated_at: null },
data: { invalidated_at: now },
});
const consumedEmailLoginCodes = await tx.emailLoginCode.updateMany({
where: { employee_id: target.id, used_at: null },
data: { used_at: now },
});
await tx.auditLog.create({
data: {
request_id: requestId,
actor_id: target.id,
action: AUDIT_ACTION,
target_type: "employee",
target_id: target.id,
ip_address: null,
user_agent: "controlled-ops-script",
extra: { credentialRecorded: false },
},
});
return {
status: "PASS",
targetMatches: 1,
exactTarget: true,
passwordUpdated: true,
mustChangePassword: employee.must_change_password === true,
tokenVersionIncremented:
employee.token_version === target.token_version + 1,
locksCleared: true,
rolesExact: true,
contractManagePermissionsAbsent: true,
revokedSessions: revokedSessions.count,
invalidatedPasswordResetCodes: invalidatedPasswordResetCodes.count,
consumedEmailLoginCodes: consumedEmailLoginCodes.count,
auditCreated: true,
};
},
{ isolationLevel: "Serializable" },
);
}
function resolveMode(argv) {
if (argv.length === 0 || (argv.length === 1 && ["--dry-run", "--preflight"].includes(argv[0]))) {
return { mode: "preflight" };
}
if (argv.length === 1 && argv[0] === "--verify") {
return { mode: "verify" };
}
if (argv.length === 1 && argv[0] === "--verify-password-changed") {
return { mode: "verify_password_changed" };
}
if (argv[0] === "--apply") {
if (argv.length !== 2 || argv[1] !== APPLY_CONFIRMATION) {
throw new Error("explicit_confirmation_required");
}
return { mode: "apply" };
}
throw new Error("unsupported_arguments");
}
async function main() {
const operation = resolveMode(process.argv.slice(2));
const { prisma, pool } = createPrismaClient();
try {
if (operation.mode === "preflight") {
writeResult(await preflight(prisma));
return;
}
if (operation.mode === "verify") {
writeResult(await verifyState(prisma));
return;
}
if (operation.mode === "verify_password_changed") {
writeResult(await verifyCompletedPasswordChange(prisma));
return;
}
const password = await readPasswordFromStdin();
writeResult(await applyReset(prisma, password));
} finally {
await prisma.$disconnect();
await pool.end();
}
}
if (require.main === module) {
main().catch((error) => {
const allowedCategories = new Set([
"password_stdin_required",
"password_input_too_long",
"password_input_invalid",
"password_policy_failed",
"employee_smoke_target_not_unique_or_safe",
"employee_smoke_target_changed",
"explicit_confirmation_required",
"unsupported_arguments",
]);
const category = allowedCategories.has(error?.message)
? error.message
: typeof error?.code === "string"
? error.code
: typeof error?.name === "string"
? error.name
: "runtime_error";
writeResult({
status: "BLOCKED",
error: "employee_smoke_break_glass_operation_failed",
category,
});
process.exitCode = 1;
});
}
module.exports = {
APPLY_CONFIRMATION,
AUDIT_ACTION,
applyReset,
buildTargetState,
findAuditTargets,
findPreflightCandidates,
preflight,
resolveMode,
rolesAreExactEmployee,
verifyCompletedPasswordChange,
verifyState,
};
@@ -1,4 +1,5 @@
const {
applyTarget,
isSafeOaOnlySmokeTarget,
isSmokeLikeValue,
normalizeRoleList,
@@ -85,4 +86,56 @@ describe("repair-oa-only-smoke-accounts", () => {
expect(normalizeRoleList(["admin", "admin", "invalid", "employee"])).toEqual(["admin", "employee"]);
expect(process.env.OA_ONLY_SMOKE_REPAIR_APPLY).toBeUndefined();
});
it("applies only lifecycle, login-lock, token, missing-role, and audit repairs", async () => {
const current = row({
status: "archived",
employmentStatus: "inactive",
archived: true,
roles: ["employee"],
});
const planned = planForTarget(target(), current);
const client = {
query: vi.fn(async (sql) => {
const text = String(sql);
if (text.includes("SELECT")) {
return {
rows: [
{
id: current.id,
employee_no: current.employeeNo,
name: current.name,
email: current.email,
phone: current.phone,
status: "active",
employment_status: "active",
archived: false,
login_failed_count: 0,
has_login_failed_first_at: false,
login_locked_until: null,
roles: ["admin", "employee"],
active_auth_sessions: 0,
},
],
};
}
return { rows: [] };
}),
};
await expect(
applyTarget(client, target(), planned, new Date("2026-08-10T00:00:00.000Z")),
).resolves.toMatchObject({ result: "applied" });
const sql = client.query.mock.calls.map(([statement]) => String(statement)).join("\n");
expect(sql).toContain("token_version = token_version + 1");
expect(sql).toContain("INSERT INTO employee_roles");
expect(sql).toContain("ON CONFLICT (employee_id, role_code) DO NOTHING");
expect(sql).toContain("INSERT INTO employee_lifecycle_events");
expect(sql).not.toContain("DELETE FROM employee_roles");
expect(sql).not.toContain("password_hash");
expect(sql).not.toMatch(/(?:UPDATE|INSERT\s+INTO|DELETE\s+FROM)\s+auth_sessions/iu);
expect(sql).not.toMatch(/(?:UPDATE|INSERT\s+INTO|DELETE\s+FROM)\s+password_reset_codes/iu);
expect(sql).not.toMatch(/(?:UPDATE|INSERT\s+INTO|DELETE\s+FROM)\s+email_login_codes/iu);
});
});
@@ -0,0 +1,329 @@
const {
AUDIT_ACTION,
applyReset,
preflight,
resolveMode,
verifyCompletedPasswordChange,
verifyState,
} = require("../../../src/scripts/reset-employee-smoke-password");
const { ROLE_PERMISSIONS } = require("../../../src/constants/permissions");
const TARGET_ID = "00000000-0000-4000-8000-000000000101";
function employeeRow(overrides = {}) {
return {
id: TARGET_ID,
token_version: 7,
employee_no: "SMOKE-EMPLOYEE",
name: "OA试运行账号",
email: "[email protected]",
phone: null,
status: "active",
employment_status: "active",
archived_at: null,
must_change_password: false,
login_failed_count: 0,
login_failed_first_at: null,
login_locked_until: null,
email_login_code_failed_count: 0,
email_login_code_failed_first_at: null,
email_login_code_locked_until: null,
password_reset_failed_count: 0,
password_reset_failed_first_at: null,
password_reset_locked_until: null,
roles: [{ role_code: "employee" }],
...overrides,
};
}
function rawEmployeeRow(row) {
const { roles: _roles, ...raw } = row;
return raw;
}
function createFixture({ rows = [employeeRow()], auditTargetIds = [TARGET_ID] } = {}) {
const tx = {
$queryRaw: vi.fn(async (strings) => {
const sql = strings.join(" ");
if (sql.includes('FROM "employees"')) {
return rows.length === 1 ? [rawEmployeeRow(rows[0])] : [];
}
if (sql.includes('FROM "employee_roles"')) {
return rows.length === 1 ? rows[0].roles : [];
}
throw new Error("unexpected_query");
}),
employee: {
update: vi
.fn()
.mockResolvedValue({ must_change_password: true, token_version: 8 }),
},
authSession: { updateMany: vi.fn().mockResolvedValue({ count: 2 }) },
passwordResetCode: { updateMany: vi.fn().mockResolvedValue({ count: 1 }) },
emailLoginCode: { updateMany: vi.fn().mockResolvedValue({ count: 3 }) },
auditLog: { create: vi.fn().mockResolvedValue({}) },
};
const prisma = {
employee: {
findMany: vi.fn(async (query) => {
if (query?.where?.id?.in) {
return rows.filter((row) => query.where.id.in.includes(row.id));
}
return rows;
}),
},
authSession: { count: vi.fn().mockResolvedValue(0) },
passwordResetCode: { count: vi.fn().mockResolvedValue(0) },
emailLoginCode: { count: vi.fn().mockResolvedValue(0) },
auditLog: {
findMany: vi
.fn()
.mockResolvedValue(auditTargetIds.map((target_id) => ({ target_id }))),
count: vi.fn().mockResolvedValue(1),
},
$transaction: vi.fn(async (callback, options) => {
expect(options).toEqual({ isolationLevel: "Serializable" });
return callback(tx);
}),
};
return { prisma, tx };
}
describe("employee smoke break-glass password reset", () => {
it("defaults to read-only preflight and requires the exact apply confirmation", () => {
expect(resolveMode([])).toEqual({ mode: "preflight" });
expect(resolveMode(["--dry-run"])).toEqual({ mode: "preflight" });
expect(resolveMode(["--preflight"])).toEqual({ mode: "preflight" });
expect(resolveMode(["--apply", "EMPLOYEE_SMOKE_BREAK_GLASS"])).toEqual({
mode: "apply",
});
expect(() => resolveMode(["--apply"])).toThrow("explicit_confirmation_required");
expect(() => resolveMode(["--apply", "wrong"])).toThrow(
"explicit_confirmation_required",
);
});
it("passes preflight only for one exact employee smoke role without management permissions", async () => {
const { prisma } = createFixture();
await expect(preflight(prisma)).resolves.toMatchObject({
status: "PASS",
targetMatches: 1,
exactTarget: true,
mustChangePassword: false,
mustChangePasswordMatches: true,
rolesExact: true,
hasWildcard: false,
hasProjectManage: false,
hasProjectViewAll: false,
hasSystemSettings: false,
contractManagePermissionsAbsent: true,
});
});
it.each([
["no target", []],
["multiple targets", [employeeRow(), employeeRow({ id: "00000000-0000-4000-8000-000000000102" })]],
["non smoke", [employeeRow({ employee_no: "E100", name: "普通员工", email: "[email protected]" })]],
["identifier missing", [employeeRow({ email: null, phone: null })]],
["status inactive", [employeeRow({ status: "inactive" })]],
["employment inactive", [employeeRow({ employment_status: "inactive" })]],
["archived", [employeeRow({ archived_at: new Date() })]],
["locked", [employeeRow({ login_locked_until: new Date() })]],
["must change already", [employeeRow({ must_change_password: true })]],
["extra role", [employeeRow({ roles: [{ role_code: "employee" }, { role_code: "manager" }] })]],
])("blocks preflight for %s", async (_label, rows) => {
const { prisma } = createFixture({ rows });
await expect(preflight(prisma)).resolves.toMatchObject({ status: "BLOCKED" });
});
it("blocks when the employee role permission mapping gains contract management", async () => {
ROLE_PERMISSIONS.employee.push("project.manage");
try {
const { prisma } = createFixture();
await expect(preflight(prisma)).resolves.toMatchObject({
status: "BLOCKED",
rolesExact: true,
hasProjectManage: true,
contractManagePermissionsAbsent: false,
});
} finally {
ROLE_PERMISSIONS.employee.pop();
}
});
it("writes only target authentication state and never writes roles or employee identity", async () => {
const { prisma, tx } = createFixture();
await expect(applyReset(prisma, "UniqueBreakGlass9Qz!")).resolves.toEqual({
status: "PASS",
targetMatches: 1,
exactTarget: true,
passwordUpdated: true,
mustChangePassword: true,
tokenVersionIncremented: true,
locksCleared: true,
rolesExact: true,
contractManagePermissionsAbsent: true,
revokedSessions: 2,
invalidatedPasswordResetCodes: 1,
consumedEmailLoginCodes: 3,
auditCreated: true,
});
const update = tx.employee.update.mock.calls[0][0];
expect(update.where).toEqual({ id: TARGET_ID });
expect(Object.keys(update.data).sort()).toEqual(
[
"email_login_code_failed_count",
"email_login_code_failed_first_at",
"email_login_code_locked_until",
"login_failed_count",
"login_failed_first_at",
"login_locked_until",
"must_change_password",
"password_hash",
"password_reset_failed_count",
"password_reset_failed_first_at",
"password_reset_last_requested_at",
"password_reset_locked_until",
"password_reset_request_count",
"password_reset_request_first_at",
"token_version",
].sort(),
);
expect(update.data).not.toHaveProperty("roles");
expect(update.data).not.toHaveProperty("status");
expect(update.data).not.toHaveProperty("employment_status");
expect(update.data).not.toHaveProperty("archived_at");
expect(update.data).not.toHaveProperty("email");
expect(update.data).not.toHaveProperty("phone");
expect(tx.authSession.updateMany).toHaveBeenCalledWith({
where: { employee_id: TARGET_ID, revoked_at: null },
data: { revoked_at: expect.any(Date), last_seen_at: expect.any(Date) },
});
expect(tx.passwordResetCode.updateMany).toHaveBeenCalledWith({
where: { employee_id: TARGET_ID, invalidated_at: null },
data: { invalidated_at: expect.any(Date) },
});
expect(tx.emailLoginCode.updateMany).toHaveBeenCalledWith({
where: { employee_id: TARGET_ID, used_at: null },
data: { used_at: expect.any(Date) },
});
expect(tx.auditLog.create).toHaveBeenCalledWith({
data: expect.objectContaining({
actor_id: TARGET_ID,
target_id: TARGET_ID,
action: AUDIT_ACTION,
extra: { credentialRecorded: false },
}),
});
});
it("fails closed when the exact role changes after preflight", async () => {
const { prisma, tx } = createFixture();
tx.$queryRaw.mockImplementation(async (strings) => {
const sql = strings.join(" ");
if (sql.includes('FROM "employees"')) return [rawEmployeeRow(employeeRow())];
return [{ role_code: "employee" }, { role_code: "manager" }];
});
await expect(applyReset(prisma, "UniqueBreakGlass9Qz!")).rejects.toThrow(
"employee_smoke_target_changed",
);
expect(tx.employee.update).not.toHaveBeenCalled();
expect(tx.auditLog.create).not.toHaveBeenCalled();
});
it("fails closed when the token version changes after preflight", async () => {
const { prisma, tx } = createFixture();
tx.$queryRaw.mockImplementation(async (strings) => {
const sql = strings.join(" ");
if (sql.includes('FROM "employees"')) {
return [rawEmployeeRow(employeeRow({ token_version: 8 }))];
}
return [{ role_code: "employee" }];
});
await expect(applyReset(prisma, "UniqueBreakGlass9Qz!")).rejects.toThrow(
"employee_smoke_target_changed",
);
expect(tx.employee.update).not.toHaveBeenCalled();
expect(tx.auditLog.create).not.toHaveBeenCalled();
});
it("verifies reset state with exact role and all four management permissions absent", async () => {
const row = employeeRow({ must_change_password: true });
const { prisma } = createFixture({ rows: [row] });
await expect(verifyState(prisma)).resolves.toMatchObject({
status: "PASS",
mustChangePassword: true,
mustChangePasswordMatches: true,
targetMatches: 1,
rolesExact: true,
hasWildcard: false,
hasProjectManage: false,
hasProjectViewAll: false,
hasSystemSettings: false,
contractManagePermissionsAbsent: true,
activeSessions: 0,
activePasswordResetCodes: 0,
activeEmailLoginCodes: 0,
auditPresent: true,
});
});
it("blocks verify when the reset target has any extra role", async () => {
const row = employeeRow({
must_change_password: true,
roles: [{ role_code: "employee" }, { role_code: "manager" }],
});
const { prisma } = createFixture({ rows: [row] });
await expect(verifyState(prisma)).resolves.toMatchObject({
status: "BLOCKED",
targetMatches: 1,
rolesExact: false,
});
});
it.each(["*", "project.manage", "project.view_all", "system.settings"])(
"blocks verify when employee permissions contain %s",
async (permission) => {
ROLE_PERMISSIONS.employee.push(permission);
try {
const row = employeeRow({ must_change_password: true });
const { prisma } = createFixture({ rows: [row] });
const result = await verifyState(prisma);
expect(result).toMatchObject({
status: "BLOCKED",
rolesExact: true,
contractManagePermissionsAbsent: false,
});
} finally {
ROLE_PERMISSIONS.employee.pop();
}
},
);
it("verifies the completed forced password change without exposing the target", async () => {
const { prisma } = createFixture();
prisma.authSession.count.mockResolvedValue(1);
prisma.auditLog.count.mockResolvedValue(1);
const result = await verifyCompletedPasswordChange(prisma);
expect(result).toMatchObject({
status: "PASS",
mustChangePassword: false,
mustChangePasswordMatches: true,
rolesExact: true,
contractManagePermissionsAbsent: true,
activeSessions: 1,
passwordChangeAuditPresent: true,
breakGlassAuditPresent: true,
});
expect(JSON.stringify(result)).not.toContain("[email protected]");
expect(JSON.stringify(result)).not.toContain(TARGET_ID);
});
});