fix: oidc hardening — sid-less consent refused, rotation race closed, iss on errors, logout honesty

From the logic audit:
- consent rejects sid-less bearers and requires a LIVE hub session
  (sessionAlive) before minting codes — a logged-out bearer can no longer
  mint downstream tokens inside its TTL
- refresh rotation race closed: consume returns whether THIS call won;
  the loser revokes the family (reuse detection now holds under
  concurrency)
- error redirects carry iss (RFC 9207) on error responses too
- logout awaits the revocation and no longer returns ok for missing or
  forged bearers
This commit is contained in:
glass beadian 2026-10-18 11:54:59 +00:00
parent e486e62220
commit 74f0802f1a
10 changed files with 355 additions and 24 deletions

View file

@ -44,10 +44,27 @@ vi.mock("@aws-sdk/client-s3", () => {
};
});
import { DIRECT_SEGMENT_BYTES, S3Storage, directPlaintextSize, extensionFor, keyForHash, open, seal, sealForDirect, sha256 } from "./storage.js";
import { DIRECT_SEGMENT_BYTES, S3Storage, directSealedSize, directPlaintextSize, extensionFor, keyForHash, open, seal, sealForDirect, sha256 } from "./storage.js";
const TEST_KEK = createHash("sha256").update("test-kek").digest();
describe("SPENC2 sealed size", () => {
it.each([
[1, 168],
[DIRECT_SEGMENT_BYTES - 1, 135 + DIRECT_SEGMENT_BYTES - 1 + 32],
[DIRECT_SEGMENT_BYTES, 135 + DIRECT_SEGMENT_BYTES + 32],
[DIRECT_SEGMENT_BYTES + 1, 135 + DIRECT_SEGMENT_BYTES + 1 + 64],
[2 * 1024 ** 3, 135 + 2 * 1024 ** 3 + 205 * 32],
])("computes exact size for %i plaintext bytes", (plain, sealed) => {
expect(directSealedSize(plain)).toBe(sealed);
expect(directPlaintextSize(sealed)).toBe(plain);
});
it.each([0, -1, 1.5, NaN, Infinity, Number.MAX_SAFE_INTEGER])("rejects invalid plaintext size %s", (size) => {
expect(() => directSealedSize(size)).toThrow(RangeError);
});
});
describe("storage key scheme (pure)", () => {
it("maps content types to extensions, unknown -> bin", () => {
expect(extensionFor("image/png")).toBe("png");