diff --git a/packages/qa/dogfood/test/admin-ledger-decision-metadata.dogfood.test.ts b/packages/qa/dogfood/test/admin-ledger-decision-metadata.dogfood.test.ts index 69ed72cef8e..0826915c7b0 100644 --- a/packages/qa/dogfood/test/admin-ledger-decision-metadata.dogfood.test.ts +++ b/packages/qa/dogfood/test/admin-ledger-decision-metadata.dogfood.test.ts @@ -87,12 +87,25 @@ const FIELD: Record = { masked: 'role', gated: 'must_change_p const SUBJECT = { email: 'ald.subject.kq@example.com', name: 'Ald Subject', role: 'aldrolekq' }; const VALUE: Record = { masked: SUBJECT.role, gated: null, withheld: SUBJECT.email }; -/** The explicit rows' events, and the decision keys each may carry. */ +/** + * The explicit rows' events, and the decision keys each may carry: `required` is + * written on every row of the event, `optional` only on some (the producer's + * `AdminAuditDecisions` type, `plugin-auth/src/admin-user-endpoints.ts`). + * + * The password set (#23287, ruling C′ item 2, with the revocation ON by default): + * `revokeSessionsAndKeys` is the administrator's switch, written on every forced + * reset. `revokedSessions`, `revokedApiKeys` and `revocationComplete` are what the + * revocation landed, counts only, written only when the switch was on. This + * fixture's reset leaves the switch at its default, so its row carries all three. + */ const CREATE_EVENT = 'user.admin_created'; const PASSWORD_SET_EVENT = 'user.admin_password_set'; const DECISIONS: Record = { [CREATE_EVENT]: { required: ['event', 'membershipCreated', 'passwordGenerated', 'placeholderEmail'], optional: ['organizationId'] }, - [PASSWORD_SET_EVENT]: { required: ['event', 'passwordGenerated'], optional: [] }, + [PASSWORD_SET_EVENT]: { + required: ['event', 'passwordGenerated', 'revokeSessionsAndKeys'], + optional: ['revokedSessions', 'revokedApiKeys', 'revocationComplete'], + }, }; const Anchor = ObjectSchema.create({ diff --git a/packages/qa/dogfood/test/bearer-lane-password-change.dogfood.test.ts b/packages/qa/dogfood/test/bearer-lane-password-change.dogfood.test.ts index c9ede3ff6c4..c2170a9eaf6 100644 --- a/packages/qa/dogfood/test/bearer-lane-password-change.dogfood.test.ts +++ b/packages/qa/dogfood/test/bearer-lane-password-change.dogfood.test.ts @@ -6,8 +6,11 @@ * ## The defect this pins * * An admin-provisioned user (`mustChangePassword` defaults to true) is gated out - * of every protected route with `403 PASSWORD_EXPIRED` until they rotate their - * password. On the COOKIE lane the escape hatch worked. On the BEARER lane — + * of every protected route with `403 PASSWORD_RESET_REQUIRED` until they rotate + * their password. (#23287, ruling C′ item 2: the administrator-forced change has + * its own code, scope `credential`; it reused `PASSWORD_EXPIRED`, which stays + * natural expiry under `passwordExpiryDays`, until then.) On the COOKIE lane the + * escape hatch worked. On the BEARER lane — * the documented API/agent/CLI lane — `/auth/change-password` answered **200**, * the password really rotated, and nothing else happened: `must_change_password` * stayed `true`, `password_changed_at` stayed `null`, and the caller stayed @@ -225,7 +228,7 @@ describe('#8049: /auth/change-password clears the force-change flag and enforces const email = `bearer.lane.8049.${lane.name.replace(/[^a-z]/gi, '').toLowerCase()}@example.com`; let credential = ''; - it('an admin-provisioned user is gated out with 403 PASSWORD_EXPIRED', async () => { + it('an admin-provisioned user is gated out with 403 PASSWORD_RESET_REQUIRED', async () => { const created = await stack.apiAs(adminToken, 'POST', '/auth/admin/create-user', { email, name: `Bearer Lane ${lane.name}`, @@ -241,9 +244,12 @@ describe('#8049: /auth/change-password clears the force-change flag and enforces const read = await stack.api('/data/showcase_task?$top=1', { headers: lane.headers(credential) }); // The gate refusal — asserted as code AND status, and distinctly from - // the reuse refusal below (different envelope, different outcome). + // the reuse refusal below (different envelope, different outcome). The + // flag is the administrator's forced change, not expiry: no + // `passwordExpiryDays` is armed here, so the one ruled answer is + // `PASSWORD_RESET_REQUIRED` (ruling C′ item 2). expect(read.status).toBe(403); - expect((await read.json())?.error?.code).toBe('PASSWORD_EXPIRED'); + expect((await read.json())?.error?.code).toBe('PASSWORD_RESET_REQUIRED'); }, 120_000); it('POST /auth/change-password rotates the password AND clears the force-change flag', async () => {