mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-15 15:02:04 +00:00
docs(api): publish the factor-dependent reach of a step-up elevation
The contract now names which factors satisfy the fresh-factor routes and which do not, carries `satisfiesFreshFactor` on the mint response so a client can pick the ceremony before spending a proof, and describes the options endpoint's two ceremony kinds. The second-factor status read is documented as plain authentication — it carries no credential material, so it needs no elevation.
This commit is contained in:
1 parent
0991d754e4
commit
4cd0adc46d
3 files changed
+198
-32
No files matched your search
@@ -817,6 +817,25 @@ describe("E10 — a password proof cannot reach the destructive routes", () => {
|
||||
expect(await getPrismaClient().webauthnMfaCredential.count()).toBe(1);
|
||||
});
|
||||
|
||||
it("refuses at the CLAIM too, not only at the validation", async () => {
|
||||
// Defence in depth, and it needs its own test: the gate validates before it
|
||||
// claims, so removing the check inside `claimStepUpElevation` leaves every
|
||||
// route-level case above green. Driven straight at the primitive.
|
||||
const { raw, id } = await mintToken("e10i");
|
||||
useToken(raw);
|
||||
const elevation = elevationOf(await mintElevation(PASSWORD));
|
||||
|
||||
const { claimStepUpElevation } = await import("@/lib/auth/step-up");
|
||||
const claimed = await claimStepUpElevation({
|
||||
rawToken: elevation,
|
||||
userId: USER_ID,
|
||||
apiTokenId: id,
|
||||
requireFreshFactor: true,
|
||||
});
|
||||
|
||||
expect(claimed).toEqual({ ok: false, reason: "insufficient_factor" });
|
||||
});
|
||||
|
||||
it("does NOT burn the elevation when the factor is too weak", async () => {
|
||||
// The refusal is about reach, not validity. The same elevation must still
|
||||
// work on the routes it was always entitled to.
|
||||
@@ -1035,6 +1054,37 @@ describe("E12 — a rejected request keeps the elevation spendable", () => {
|
||||
expect(good.status).toBe(200);
|
||||
});
|
||||
|
||||
it("IS spent once the route actually acts", async () => {
|
||||
// The other half of the two-phase split. Deferring the claim buys nothing if
|
||||
// the route then forgets to make it — the elevation would stay redeemable
|
||||
// for the rest of its five minutes.
|
||||
//
|
||||
// Driven through recovery-code regeneration rather than disable: disable
|
||||
// calls `destroyOtherSessions`, which deletes the account's elevations
|
||||
// outright, so the row is gone afterwards either way and the assertion
|
||||
// would pass whether or not the route committed. Regeneration leaves the
|
||||
// row in place, so `consumedAt` is a real observation.
|
||||
const secret = await enrolTotp();
|
||||
const { raw } = await mintToken("e12c");
|
||||
useToken(raw);
|
||||
const elevation = elevationOf(await mintTotpElevation(secret));
|
||||
|
||||
useElevation(elevation);
|
||||
const { POST } =
|
||||
await import("@/app/api/auth/me/mfa/recovery-codes/regenerate/route");
|
||||
const ok = await POST(
|
||||
req("/api/auth/me/mfa/recovery-codes/regenerate", "POST"),
|
||||
);
|
||||
expect(ok.status).toBe(200);
|
||||
|
||||
const row = await getPrismaClient().stepUpElevation.findFirst();
|
||||
expect(row).not.toBeNull();
|
||||
expect(row?.consumedAt).not.toBeNull();
|
||||
|
||||
// And the spent value opens nothing else.
|
||||
expect((await callMfaSetup()).status).toBe(401);
|
||||
});
|
||||
|
||||
it("survives a malformed body", async () => {
|
||||
const { raw } = await mintToken("e12b");
|
||||
useToken(raw);
|
||||
|
||||
Reference in new issue
Block a user