| @@ -121,7 +121,7 @@ describe("MCP AuthKit token verification", () => { · 120 unmodified lines |
| 121 | | 121 | |
| 122 | describe("access token expiry and identity boundaries", () => { | 122 | describe("access token expiry and identity boundaries", () => { |
| 123 | for (const kind of ["mcp", "user-management"] as const) { | 123 | for (const kind of ["mcp", "user-management"] as const) { |
| 124 | for (const invalidClaim of ["missing-exp", "missing-iat", "older-than-one-day"] as const) { | 124 | for (const invalidClaim of ["missing-exp", "missing-iat"] as const) { |
| 125 | it.effect(`${kind} rejects ${invalidClaim}`, () => | 125 | it.effect(`${kind} rejects ${invalidClaim}`, () => |
| 126 | Effect.gen(function* () { | 126 | Effect.gen(function* () { |
| 127 | const { publicKey, privateKey } = yield* Effect.promise(() => generateKeyPair("RS256")); | 127 | const { publicKey, privateKey } = yield* Effect.promise(() => generateKeyPair("RS256")); |
| @@ -134,9 +134,7 @@ describe("access token expiry and identity boundaries", () => { · 6 unmodified lines |
| 134 | iss: issuer, | 134 | iss: issuer, |
| 135 | aud: resource, | 135 | aud: resource, |
| 136 | ...(invalidClaim === "missing-exp" ? {} : { exp: now + 300 }), | 136 | ...(invalidClaim === "missing-exp" ? {} : { exp: now + 300 }), |
| 137 | ...(invalidClaim === "missing-iat" | 137 | ...(invalidClaim === "missing-iat" ? {} : { iat: now }), |
| 138 | ? {} | | |
| 139 | : { iat: invalidClaim === "older-than-one-day" ? now - 86401 : now }), | | |
| 140 | }; | 138 | }; |
| 141 | const token = yield* Effect.promise(() => | 139 | const token = yield* Effect.promise(() => |
| 142 | new SignJWT(claims) | 140 | new SignJWT(claims) |
| @@ -153,6 +151,30 @@ describe("access token expiry and identity boundaries", () => { · 10 unmodified lines |
| 153 | }), | 151 | }), |
| 154 | ); | 152 | ); |
| 155 | } | 153 | } |
| | 154 | it.effect(`${kind} accepts a token issued more than a day ago that has not expired`, () => |
| | 155 | Effect.gen(function* () { |
| | 156 | const { publicKey, privateKey } = yield* Effect.promise(() => generateKeyPair("RS256")); |
| | 157 | const jwk = yield* Effect.promise(() => exportJWK(publicKey)); |
| | 158 | const jwks = createLocalJWKSet({ keys: [{ ...jwk, kid: "expiry-key" }] }); |
| | 159 | const now = Math.floor(Date.now() / 1000); |
| | 160 | const token = yield* Effect.promise(() => |
| | 161 | new SignJWT({ |
| | 162 | sub: "user_test", |
| | 163 | org_id: "org_test", |
| | 164 | iss: issuer, |
| | 165 | aud: resource, |
| | 166 | iat: now - 5 * 86400, |
| | 167 | exp: now + 2 * 86400, |
| | 168 | }) |
| | 169 | .setProtectedHeader({ alg: "RS256", kid: "expiry-key" }) |
| | 170 | .sign(privateKey), |
| | 171 | ); |
| | 172 | const verified = yield* kind === "mcp" |
| | 173 | ? verifyMcpAccessToken(token, jwks, { issuer, audience: resource }) |
| | 174 | : verifyWorkosUserManagementToken(token, jwks); |
| | 175 | expect(verified).toEqual({ accountId: "user_test", organizationId: "org_test" }); |
| | 176 | }), |
| | 177 | ); |
| 156 | it.effect(`${kind} rejects a non-string organization claim`, () => | 178 | it.effect(`${kind} rejects a non-string organization claim`, () => |
| 157 | Effect.gen(function* () { | 179 | Effect.gen(function* () { |
| 158 | const { jwks, sign } = yield* Effect.promise(() => makeVerifier()); | 180 | const { jwks, sign } = yield* Effect.promise(() => makeVerifier()); |