|
1 | 1 | import * as crypto from "node:crypto"; |
2 | 2 | import { ApolloServer, HeaderMap } from "@apollo/server"; |
3 | 3 | import { |
| 4 | + CompositeTokenSource, |
4 | 5 | KeyManager, |
5 | 6 | PublicFederatedToken, |
6 | 7 | TokenSigner, |
7 | 8 | } from "@labdigital/federated-token"; |
8 | | -import { HeaderTokenSource } from "@labdigital/federated-token-express-adapter"; |
| 9 | +import { |
| 10 | + CookieTokenSource, |
| 11 | + HeaderTokenSource, |
| 12 | +} from "@labdigital/federated-token-express-adapter"; |
9 | 13 | import type { Request, Response } from "express"; |
10 | 14 | import httpMocks from "node-mocks-http"; |
11 | 15 | import { assert, describe, expect, it } from "vitest"; |
@@ -272,6 +276,69 @@ describe("GatewayAuthPlugin", async () => { |
272 | 276 | assert.notEqual(newAccessToken, accessToken); |
273 | 277 | }); |
274 | 278 |
|
| 279 | + it("should clear invalid refresh token and let request reach resolver", async () => { |
| 280 | + const wrongAudienceSigner = new TokenSigner({ |
| 281 | + ...signOptions, |
| 282 | + audience: "wrongAudience", |
| 283 | + }); |
| 284 | + |
| 285 | + const token = new PublicFederatedToken(); |
| 286 | + token.setRefreshToken("commercetools", "stale-refresh-value"); |
| 287 | + const staleRefreshToken = await token.createRefreshJWT(wrongAudienceSigner); |
| 288 | + |
| 289 | + const cookieSource = new CookieTokenSource({ |
| 290 | + refreshTokenPath: "/auth/graphql", |
| 291 | + secure: false, |
| 292 | + sameSite: "lax", |
| 293 | + }); |
| 294 | + |
| 295 | + const cookiePlugin = new GatewayAuthPlugin({ |
| 296 | + signer: signer, |
| 297 | + source: new CompositeTokenSource([cookieSource]), |
| 298 | + }); |
| 299 | + |
| 300 | + const cookieServer = new ApolloServer({ |
| 301 | + typeDefs, |
| 302 | + resolvers, |
| 303 | + plugins: [cookiePlugin], |
| 304 | + }); |
| 305 | + |
| 306 | + const context = { |
| 307 | + federatedToken: new PublicFederatedToken(), |
| 308 | + res: httpMocks.createResponse(), |
| 309 | + req: httpMocks.createRequest({ |
| 310 | + cookies: { |
| 311 | + refreshToken: staleRefreshToken, |
| 312 | + }, |
| 313 | + }), |
| 314 | + }; |
| 315 | + |
| 316 | + const response = await cookieServer.executeOperation( |
| 317 | + { |
| 318 | + query: 'query hello { hello(name: "world") }', |
| 319 | + http: { |
| 320 | + headers: new HeaderMap(), |
| 321 | + method: "POST", |
| 322 | + search: "", |
| 323 | + body: "", |
| 324 | + }, |
| 325 | + }, |
| 326 | + { |
| 327 | + contextValue: context, |
| 328 | + }, |
| 329 | + ); |
| 330 | + |
| 331 | + // The request should reach the resolver instead of being blocked with a 401 |
| 332 | + assert(response.body.kind === "single"); |
| 333 | + expect(response.body.singleResult.data?.hello).toBe("Hello world"); |
| 334 | + expect(response.body.singleResult.errors).toBeUndefined(); |
| 335 | + |
| 336 | + // The refresh token cookie should be cleared |
| 337 | + expect(context.res.cookies.refreshToken).toBeDefined(); |
| 338 | + expect(context.res.cookies.refreshToken.value).toBe(""); |
| 339 | + expect(context.res.cookies.refreshToken.options.expires).toBeDefined(); |
| 340 | + }); |
| 341 | + |
275 | 342 | it("should return GraphQLError when token expired", async () => { |
276 | 343 | const context = { |
277 | 344 | federatedToken: new PublicFederatedToken(), |
|
0 commit comments