session-contract.test.ts 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. import { afterEach, describe, expect, it, vi } from "vitest";
  2. const ORIGINAL_SESSION_TOKEN_MODE = process.env.SESSION_TOKEN_MODE;
  3. function restoreSessionTokenModeEnv() {
  4. if (ORIGINAL_SESSION_TOKEN_MODE === undefined) {
  5. delete process.env.SESSION_TOKEN_MODE;
  6. return;
  7. }
  8. process.env.SESSION_TOKEN_MODE = ORIGINAL_SESSION_TOKEN_MODE;
  9. }
  10. describe("session token contract and migration flags", () => {
  11. afterEach(() => {
  12. restoreSessionTokenModeEnv();
  13. vi.resetModules();
  14. });
  15. it("SESSION_TOKEN_MODE defaults to opaque", async () => {
  16. delete process.env.SESSION_TOKEN_MODE;
  17. vi.resetModules();
  18. const { getSessionTokenMode } = await import("@/lib/auth");
  19. expect(getSessionTokenMode()).toBe("opaque");
  20. });
  21. it("getSessionTokenMode returns configured mode values", async () => {
  22. const modes = ["legacy", "dual", "opaque"] as const;
  23. for (const mode of modes) {
  24. process.env.SESSION_TOKEN_MODE = mode;
  25. vi.resetModules();
  26. const { getSessionTokenMode } = await import("@/lib/auth");
  27. expect(getSessionTokenMode()).toBe(mode);
  28. }
  29. });
  30. it("validates OpaqueSessionContract runtime shape strictly", async () => {
  31. vi.resetModules();
  32. const { isOpaqueSessionContract } = await import("@/lib/auth");
  33. const validContract = {
  34. sessionId: "sid_opaque_session_123",
  35. keyFingerprint: "sha256:abc123",
  36. createdAt: 1_700_000_000,
  37. expiresAt: 1_700_000_300,
  38. userId: 42,
  39. userRole: "admin",
  40. };
  41. expect(isOpaqueSessionContract(validContract)).toBe(true);
  42. expect(
  43. isOpaqueSessionContract({
  44. ...validContract,
  45. keyFingerprint: "",
  46. })
  47. ).toBe(false);
  48. expect(
  49. isOpaqueSessionContract({
  50. ...validContract,
  51. expiresAt: validContract.createdAt,
  52. })
  53. ).toBe(false);
  54. expect(
  55. isOpaqueSessionContract({
  56. ...validContract,
  57. userId: 3.14,
  58. })
  59. ).toBe(false);
  60. });
  61. it("accepts both legacy cookie and opaque session in dual mode", async () => {
  62. process.env.SESSION_TOKEN_MODE = "dual";
  63. vi.resetModules();
  64. const { getSessionTokenMode, getSessionTokenMigrationFlags, isSessionTokenAccepted } =
  65. await import("@/lib/auth");
  66. const mode = getSessionTokenMode();
  67. expect(mode).toBe("dual");
  68. expect(getSessionTokenMigrationFlags(mode)).toEqual({
  69. dualReadWindowEnabled: true,
  70. hardCutoverEnabled: false,
  71. emergencyRollbackEnabled: false,
  72. });
  73. expect(isSessionTokenAccepted("sk-legacy-cookie", mode)).toBe(true);
  74. expect(isSessionTokenAccepted("sid_opaque_session_cookie", mode)).toBe(true);
  75. });
  76. it("accepts only legacy cookie in legacy mode", async () => {
  77. process.env.SESSION_TOKEN_MODE = "legacy";
  78. vi.resetModules();
  79. const { getSessionTokenMode, getSessionTokenMigrationFlags, isSessionTokenAccepted } =
  80. await import("@/lib/auth");
  81. const mode = getSessionTokenMode();
  82. expect(mode).toBe("legacy");
  83. expect(getSessionTokenMigrationFlags(mode)).toEqual({
  84. dualReadWindowEnabled: false,
  85. hardCutoverEnabled: false,
  86. emergencyRollbackEnabled: true,
  87. });
  88. expect(isSessionTokenAccepted("sk-legacy-cookie", mode)).toBe(true);
  89. expect(isSessionTokenAccepted("sid_opaque_session_cookie", mode)).toBe(false);
  90. });
  91. });