Skip to content

Commit b13077b

Browse files
committed
feat: add refresh token and auth token path option
1 parent c4380a4 commit b13077b

File tree

5 files changed

+56
-17
lines changed

5 files changed

+56
-17
lines changed

packages/core/src/tokensource/cookies-base.test.ts

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { parse, type SerializeOptions, serialize } from "cookie";
2-
import { describe, expect, it } from "vitest";
2+
import { describe, expect, it, } from "vitest";
33
import {
44
type BaseCookieSourceOptions,
55
BaseCookieTokenSource,
@@ -13,7 +13,7 @@ import {
1313
* support multiple Set-Cookie headers, so we just 'join' them with a comma.
1414
*/
1515
class TestAdapter implements CookieAdapter<Request, Response> {
16-
constructor(private options: BaseCookieSourceOptions) {}
16+
constructor(private options: BaseCookieSourceOptions<Request>) {}
1717

1818
getCookie(request: Request, name: string): string | undefined {
1919
const header = request.headers.get("cookie");
@@ -55,7 +55,7 @@ class TestAdapter implements CookieAdapter<Request, Response> {
5555
class TestCookieTokenSource extends BaseCookieTokenSource<Request, Response> {
5656
protected adapter: CookieAdapter<Request, Response>;
5757

58-
constructor(options: BaseCookieSourceOptions) {
58+
constructor(options: BaseCookieSourceOptions<Request>) {
5959
super(options);
6060
this.adapter = new TestAdapter(options);
6161
}
@@ -282,7 +282,7 @@ describe("CookieTokenSource", () => {
282282
cookieTokenSource.deleteAccessToken(request, response);
283283

284284
const cookies = getCookies(response);
285-
expect(cookies).toEqual([{ userToken: "" }, { guestToken: "" }]);
285+
expect(cookies).toEqual([{ userToken: "", Path: "/" }, { guestToken: "", Path: "/" }]);
286286
});
287287

288288
// Test for deleting refresh tokens
@@ -305,8 +305,8 @@ describe("CookieTokenSource", () => {
305305
const cookies = getCookies(response);
306306
expect(cookies).toEqual([
307307
{ refreshToken: "", Path: "/refresh" },
308-
{ guestRefreshTokenExists: "" },
309-
{ userRefreshTokenExists: "" },
308+
{ guestRefreshTokenExists: "" , Path: "/"},
309+
{ userRefreshTokenExists: "" , Path: "/"},
310310
]);
311311
});
312312

@@ -330,4 +330,31 @@ describe("CookieTokenSource", () => {
330330
const cookies = getCookies(response);
331331
expect(cookies).toEqual([{ refreshToken: "", Path: "/refresh" }]);
332332
});
333+
334+
it("should get the refresh path from the refresh path function", () => {
335+
const request: Request = new Request("http://localhost");
336+
337+
const cookieTokenSource = new TestCookieTokenSource({
338+
secure: true,
339+
sameSite: "strict",
340+
refreshTokenPath: () => "/refresh",
341+
});
342+
343+
const result = cookieTokenSource["_getRefreshTokenPath"](request);
344+
expect(result).toBe("/refresh");
345+
});
346+
347+
it("should get the cookiePath from the cookiePath function", () => {
348+
const request: Request = new Request("http://localhost");
349+
350+
const cookieTokenSource = new TestCookieTokenSource({
351+
secure: true,
352+
sameSite: "strict",
353+
refreshTokenPath: "/refresh",
354+
cookiePathFn: () => "/cookie",
355+
});
356+
357+
const result = cookieTokenSource["options"].cookiePathFn?.(request);
358+
expect(result).toBe("/cookie");
359+
});
333360
});

packages/core/src/tokensource/cookies-base.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,15 @@ type CookieSettings = {
6363
expiresIn: number | "session";
6464
};
6565

66-
export type BaseCookieSourceOptions = {
66+
export type BaseCookieSourceOptions<TRequest> = {
6767
secure: boolean;
6868
sameSite: "strict" | "lax" | "none" | boolean;
69-
refreshTokenPath: string;
69+
refreshTokenPath: string | ((request: TRequest) => string | undefined);
7070
cookieNames?: Partial<CookieNames>;
7171
guestToken?: CookieSettings;
7272
userToken?: CookieSettings;
7373
refreshToken?: CookieSettings;
74+
cookiePathFn?: (request: TRequest) => string | undefined;
7475
};
7576

7677
export abstract class BaseCookieTokenSource<TRequest, TResponse>
@@ -79,7 +80,7 @@ export abstract class BaseCookieTokenSource<TRequest, TResponse>
7980
protected cookieNames: CookieNames;
8081
protected abstract adapter: CookieAdapter<TRequest, TResponse>;
8182

82-
constructor(protected options: BaseCookieSourceOptions) {
83+
constructor(protected options: BaseCookieSourceOptions<TRequest>) {
8384
this.cookieNames = {
8485
...DEFAULT_COOKIE_NAMES,
8586
...(options.cookieNames ?? {}),
@@ -96,7 +97,7 @@ export abstract class BaseCookieTokenSource<TRequest, TResponse>
9697

9798
deleteRefreshToken(request: TRequest, response: TResponse): void {
9899
this.adapter.clearCookie(request, response, this.cookieNames.refreshToken, {
99-
path: this.options.refreshTokenPath,
100+
path: this._getRefreshTokenPath(request),
100101
domain: this.adapter.getPrivateDomain(request),
101102
});
102103

@@ -128,6 +129,7 @@ export abstract class BaseCookieTokenSource<TRequest, TResponse>
128129
if (this.adapter.getCookie(request, name)) {
129130
this.adapter.clearCookie(request, response, name, {
130131
domain: this.adapter.getPublicDomain(request),
132+
path: this.options.cookiePathFn?.(request) ?? "/",
131133
});
132134
}
133135
}
@@ -140,6 +142,7 @@ export abstract class BaseCookieTokenSource<TRequest, TResponse>
140142
if (this.adapter.getCookie(request, name)) {
141143
this.adapter.clearCookie(request, response, name, {
142144
domain: this.adapter.getPublicDomain(request),
145+
path: this.options.cookiePathFn?.(request) ?? "/",
143146
});
144147
}
145148
}
@@ -180,7 +183,7 @@ export abstract class BaseCookieTokenSource<TRequest, TResponse>
180183
opts.expiresIn === "session"
181184
? undefined
182185
: new Date(Date.now() + opts.expiresIn * 1000),
183-
path: "/",
186+
path: this.options.cookiePathFn?.(request) ?? "/",
184187
};
185188

186189
if (isAuthenticated) {
@@ -189,7 +192,7 @@ export abstract class BaseCookieTokenSource<TRequest, TResponse>
189192
response,
190193
this.cookieNames.userData,
191194
token,
192-
cookieOptions,
195+
cookieOptions
193196
);
194197
this.deleteAccessTokenByName(
195198
request,
@@ -244,7 +247,7 @@ export abstract class BaseCookieTokenSource<TRequest, TResponse>
244247
opts.expiresIn === "session"
245248
? undefined
246249
: new Date(Date.now() + opts.expiresIn * 1000),
247-
path: "/",
250+
path: this.options.cookiePathFn?.(request) ?? "/",
248251
};
249252

250253
if (isAuthenticated) {
@@ -299,7 +302,7 @@ export abstract class BaseCookieTokenSource<TRequest, TResponse>
299302
{
300303
...cookieOptions,
301304
httpOnly: true,
302-
path: this.options.refreshTokenPath,
305+
path: this._getRefreshTokenPath(request),
303306
},
304307
);
305308

@@ -331,4 +334,9 @@ export abstract class BaseCookieTokenSource<TRequest, TResponse>
331334
);
332335
}
333336
}
337+
338+
private _getRefreshTokenPath(req: TRequest): string | undefined {
339+
const path = this.options.refreshTokenPath;
340+
return typeof path === "function" ? path(req) : path;
341+
}
334342
}

packages/express-adapter/src/cookies.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ import {
55
} from "@labdigital/federated-token/tokensource";
66
import type { CookieOptions, Request, Response } from "express";
77

8-
type ExpressCookieSourceOptions = BaseCookieSourceOptions & {
8+
type ExpressCookieSourceOptions = BaseCookieSourceOptions<Request> & {
9+
refreshTokenPath: string | ((request: Request) => string | undefined);
910
publicDomainFn?: (request: Request) => string | undefined;
1011
privateDomainFn?: (request: Request) => string | undefined;
12+
cookiePathFn?: (request: Request) => string | undefined;
1113
};
1214

1315
class ExpressCookieAdapter implements CookieAdapter<Request, Response> {

packages/fastify-adapter/src/cookies.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { CookieSerializeOptions } from "@fastify/cookie";
22
import type { FastifyReply, FastifyRequest } from "fastify";
3-
import { describe, expect, it } from "vitest";
3+
import { describe, expect, it, } from "vitest";
44
import { CookieTokenSource } from "./cookies";
55

66
type CookieValue = {

packages/fastify-adapter/src/cookies.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ import {
66
} from "@labdigital/federated-token/tokensource";
77
import type { FastifyReply, FastifyRequest } from "fastify";
88

9-
type FastifyCookieSourceOptions = BaseCookieSourceOptions & {
9+
type FastifyCookieSourceOptions = BaseCookieSourceOptions<FastifyRequest> & {
10+
refreshTokenPath: string | ((request: Request) => string | undefined);
1011
publicDomainFn?: (request: FastifyRequest) => string | undefined;
1112
privateDomainFn?: (request: FastifyRequest) => string | undefined;
13+
cookiePathFn?: (request: Request) => string | undefined;
1214
};
1315

1416
class FastifyCookieAdapter

0 commit comments

Comments
 (0)