@fluojs/passport 1.0.4 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,4 @@
1
+ import { setCookie } from '@fluojs/http';
1
2
  import { normalizeCookieAuthOptions } from './cookie-auth.js';
2
3
 
3
4
  /**
@@ -5,7 +6,13 @@ import { normalizeCookieAuthOptions } from './cookie-auth.js';
5
6
  */
6
7
 
7
8
  /**
8
- * Describes the set cookie options contract.
9
+ * Describes the set cookie options contract accepted by {@link CookieManagerConfig}.
10
+ *
11
+ * @remarks
12
+ * `accessTokenTtlSeconds` and `refreshTokenTtlSeconds` become the default `Max-Age`
13
+ * for the matching token cookie when the positional TTL argument of
14
+ * `CookieManager.setAccessTokenCookie(...)` / `setRefreshTokenCookie(...)` is omitted.
15
+ * An explicit positional TTL always wins over these defaults.
9
16
  */
10
17
 
11
18
  /**
@@ -23,34 +30,6 @@ export const DEFAULT_COOKIE_OPTIONS = {
23
30
  domain: undefined,
24
31
  maxAge: undefined
25
32
  };
26
- function buildCookieHeader(name, value, options) {
27
- const parts = [`${name}=${value}`];
28
- if (options.maxAge !== undefined && options.maxAge >= 0) {
29
- parts.push(`Max-Age=${options.maxAge}`);
30
- }
31
- if (options.path) {
32
- parts.push(`Path=${options.path}`);
33
- }
34
- if (options.domain) {
35
- parts.push(`Domain=${options.domain}`);
36
- }
37
- if (options.secure) {
38
- parts.push('Secure');
39
- }
40
- if (options.httpOnly) {
41
- parts.push('HttpOnly');
42
- }
43
- if (options.sameSite) {
44
- parts.push(`SameSite=${options.sameSite.charAt(0).toUpperCase() + options.sameSite.slice(1)}`);
45
- }
46
- return parts.join('; ');
47
- }
48
- function buildClearCookieHeader(name, options) {
49
- return buildCookieHeader(name, '', {
50
- ...options,
51
- maxAge: 0
52
- });
53
- }
54
33
  function getHeaderCaseInsensitive(headers, name) {
55
34
  for (const [headerName, value] of Object.entries(headers)) {
56
35
  if (headerName.toLowerCase() === name.toLowerCase() && (typeof value === 'string' || Array.isArray(value))) {
@@ -75,6 +54,8 @@ function toHeaderValues(value) {
75
54
  export class CookieManager {
76
55
  options;
77
56
  cookieOptions;
57
+ accessTokenTtlSeconds;
58
+ refreshTokenTtlSeconds;
78
59
  constructor(config) {
79
60
  this.options = normalizeCookieAuthOptions(config);
80
61
  this.cookieOptions = {
@@ -85,28 +66,36 @@ export class CookieManager {
85
66
  domain: config?.cookieOptions?.domain ?? DEFAULT_COOKIE_OPTIONS.domain,
86
67
  maxAge: config?.cookieOptions?.maxAge ?? DEFAULT_COOKIE_OPTIONS.maxAge
87
68
  };
69
+ this.accessTokenTtlSeconds = config?.cookieOptions?.accessTokenTtlSeconds;
70
+ this.refreshTokenTtlSeconds = config?.cookieOptions?.refreshTokenTtlSeconds;
88
71
  }
89
72
  setAccessTokenCookie(response, token, ttlSeconds) {
90
- const cookie = buildCookieHeader(this.options.accessTokenCookieName, token, {
91
- ...this.cookieOptions,
92
- maxAge: ttlSeconds ?? this.cookieOptions.maxAge
73
+ this.writeCookie(response, {
74
+ maxAgeSeconds: ttlSeconds ?? this.accessTokenTtlSeconds ?? this.cookieOptions.maxAge,
75
+ name: this.options.accessTokenCookieName,
76
+ value: token
93
77
  });
94
- this.appendSetCookie(response, cookie);
95
78
  }
96
79
  setRefreshTokenCookie(response, token, ttlSeconds) {
97
- const cookie = buildCookieHeader(this.options.refreshTokenCookieName, token, {
98
- ...this.cookieOptions,
99
- maxAge: ttlSeconds ?? this.cookieOptions.maxAge
80
+ this.writeCookie(response, {
81
+ maxAgeSeconds: ttlSeconds ?? this.refreshTokenTtlSeconds ?? this.cookieOptions.maxAge,
82
+ name: this.options.refreshTokenCookieName,
83
+ value: token
100
84
  });
101
- this.appendSetCookie(response, cookie);
102
85
  }
103
86
  clearAccessTokenCookie(response) {
104
- const cookie = buildClearCookieHeader(this.options.accessTokenCookieName, this.cookieOptions);
105
- this.appendSetCookie(response, cookie);
87
+ this.writeCookie(response, {
88
+ maxAgeSeconds: 0,
89
+ name: this.options.accessTokenCookieName,
90
+ value: ''
91
+ });
106
92
  }
107
93
  clearRefreshTokenCookie(response) {
108
- const cookie = buildClearCookieHeader(this.options.refreshTokenCookieName, this.cookieOptions);
109
- this.appendSetCookie(response, cookie);
94
+ this.writeCookie(response, {
95
+ maxAgeSeconds: 0,
96
+ name: this.options.refreshTokenCookieName,
97
+ value: ''
98
+ });
110
99
  }
111
100
  clearAllCookies(response) {
112
101
  this.clearAccessTokenCookie(response);
@@ -118,11 +107,21 @@ export class CookieManager {
118
107
  this.setRefreshTokenCookie(response, refreshToken, refreshTokenTtlSeconds);
119
108
  }
120
109
  }
121
- appendSetCookie(response, cookie) {
110
+ writeCookie(response, cookie) {
122
111
  const existingHeader = getHeaderCaseInsensitive(response.headers, 'Set-Cookie');
123
- const cookies = [...toHeaderValues(existingHeader?.value), cookie];
124
- response.setHeader('Set-Cookie', cookie);
112
+ const existingValues = toHeaderValues(existingHeader?.value);
113
+ setCookie(response, cookie.name, cookie.value, {
114
+ domain: this.cookieOptions.domain,
115
+ httpOnly: this.cookieOptions.httpOnly,
116
+ maxAgeSeconds: cookie.maxAgeSeconds,
117
+ path: this.cookieOptions.path,
118
+ sameSite: this.cookieOptions.sameSite,
119
+ secure: this.cookieOptions.secure
120
+ });
125
121
  const updatedHeader = getHeaderCaseInsensitive(response.headers, 'Set-Cookie');
122
+ const writtenValues = toHeaderValues(response.headers['Set-Cookie'] ?? updatedHeader?.value);
123
+ const appendedExistingValues = writtenValues.length > existingValues.length && existingValues.every((value, index) => writtenValues[index] === value);
124
+ const cookies = appendedExistingValues ? writtenValues : [...existingValues, ...writtenValues];
126
125
  if (updatedHeader?.key && updatedHeader.key !== 'Set-Cookie') {
127
126
  delete response.headers[updatedHeader.key];
128
127
  }
@@ -1 +1 @@
1
- {"version":3,"file":"guard.d.ts","sourceRoot":"","sources":["../src/guard.ts"],"names":[],"mappings":"AAAA,OAAO,EAA6C,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAa5F,OAAO,KAAK,EACV,iBAAiB,EAKjB,oBAAoB,EACpB,qBAAqB,EACtB,MAAM,YAAY,CAAC;AAsFpB;;;;;;;;;GASG;AACH,qBACa,SAAU,YAAW,iBAAiB;IAE/C,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,OAAO;gBADP,UAAU,GAAE,oBAAyB,EACrC,OAAO,GAAE,qBAA0B;IAGtD;;;;;;;;;;;;;;;;;;;OAmBG;IACG,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;CAkExD"}
1
+ {"version":3,"file":"guard.d.ts","sourceRoot":"","sources":["../src/guard.ts"],"names":[],"mappings":"AAAA,OAAO,EAA6C,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAa5F,OAAO,KAAK,EACV,iBAAiB,EAKjB,oBAAoB,EACpB,qBAAqB,EACtB,MAAM,YAAY,CAAC;AAsFpB;;;;;;;;;GASG;AACH,qBACa,SAAU,YAAW,iBAAiB;IAE/C,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,OAAO;gBADP,UAAU,GAAE,oBAAyB,EACrC,OAAO,GAAE,qBAA0B;IAGtD;;;;;;;;;;;;;;;;;;;OAmBG;IACG,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;CAmExD"}
package/dist/guard.js CHANGED
@@ -129,13 +129,13 @@ class AuthGuard {
129
129
  });
130
130
  try {
131
131
  const result = await strategy.authenticate(context);
132
- const principal = resolvePrincipal(result);
133
- if (isAuthHandledResult(result) && !principal) {
132
+ if (isAuthHandledResult(result)) {
134
133
  if (!context.requestContext.response.committed) {
135
- throw new AuthenticationFailedError('Auth strategy returned handled:true without a principal but did not commit a response.');
134
+ throw new AuthenticationFailedError('Auth strategy returned handled:true but did not commit a response.');
136
135
  }
137
136
  return true;
138
137
  }
138
+ const principal = resolvePrincipal(result);
139
139
  if (!principal) {
140
140
  if (isAuthOptionalResult(result) && requirement?.optional && !requirement.scopes?.length) {
141
141
  return true;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './account/account-linking.js';
2
+ export * from './bearer/bearer-jwt.js';
2
3
  export * from './cookie/cookie-auth.js';
3
4
  export * from './cookie/cookie-auth-module.js';
4
5
  export * from './cookie/cookie-manager.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,8BAA8B,CAAC;AAC7C,cAAc,yBAAyB,CAAC;AACxC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,wCAAwC,CAAC;AACvD,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,2BAA2B,CAAC;AAC1C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,8BAA8B,CAAC;AAC7C,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,wCAAwC,CAAC;AACvD,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,2BAA2B,CAAC;AAC1C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './account/account-linking.js';
2
+ export * from './bearer/bearer-jwt.js';
2
3
  export * from './cookie/cookie-auth.js';
3
4
  export * from './cookie/cookie-auth-module.js';
4
5
  export * from './cookie/cookie-manager.js';
@@ -1 +1 @@
1
- {"version":3,"file":"jwt-refresh-token-adapter.d.ts","sourceRoot":"","sources":["../../src/refresh/jwt-refresh-token-adapter.ts"],"names":[],"mappings":"AACA,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAGlB,KAAK,iBAAiB,EACvB,MAAM,aAAa,CAAC;AAErB,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE9D;;GAEG;AACH,eAAO,MAAM,4BAA4B,eAA2D,CAAC;AAErG;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,iBAAiB,GAAG,QAAQ,CAAC;CACrC;AA2ED;;GAEG;AACH,qBACa,sBAAuB,YAAW,mBAAmB;IAChE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAyB;gBAG/C,MAAM,EAAE,gBAAgB,EACxB,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,EAAE,yBAAyB;IAuB9B,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAInD,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC;IAIhG,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlD,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAG1D"}
1
+ {"version":3,"file":"jwt-refresh-token-adapter.d.ts","sourceRoot":"","sources":["../../src/refresh/jwt-refresh-token-adapter.ts"],"names":[],"mappings":"AACA,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAIlB,KAAK,iBAAiB,EACvB,MAAM,aAAa,CAAC;AAErB,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE9D;;GAEG;AACH,eAAO,MAAM,4BAA4B,eAA2D,CAAC;AAErG;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,iBAAiB,GAAG,QAAQ,CAAC;CACrC;AAoFD;;GAEG;AACH,qBACa,sBAAuB,YAAW,mBAAmB;IAChE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAyB;gBAG/C,MAAM,EAAE,gBAAgB,EACxB,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,EAAE,yBAAyB;IAuB9B,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAInD,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC;IAIhG,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlD,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAG1D"}
@@ -23,25 +23,46 @@ function resolveSecret(options) {
23
23
  }
24
24
  function createInMemoryStore() {
25
25
  const records = new Map();
26
+ const pruneExpired = now => {
27
+ for (const [id, record] of records.entries()) {
28
+ if (record.expiresAt.getTime() <= now) {
29
+ records.delete(id);
30
+ }
31
+ }
32
+ };
26
33
  return {
27
34
  async save(token) {
28
35
  records.set(token.id, token);
36
+ pruneExpired(Date.now());
29
37
  },
30
38
  async find(tokenId) {
31
- return records.get(tokenId);
39
+ const record = records.get(tokenId);
40
+ pruneExpired(Date.now());
41
+ return record;
32
42
  },
33
43
  async revoke(tokenId) {
44
+ pruneExpired(Date.now());
34
45
  records.delete(tokenId);
35
46
  },
36
47
  async revokeBySubject(subject) {
48
+ pruneExpired(Date.now());
37
49
  for (const [id, record] of records.entries()) {
38
50
  if (record.subject === subject) {
39
51
  records.delete(id);
40
52
  }
41
53
  }
42
54
  },
55
+ async revokeByFamily(family) {
56
+ pruneExpired(Date.now());
57
+ for (const [id, record] of records.entries()) {
58
+ if (record.family === family) {
59
+ records.delete(id);
60
+ }
61
+ }
62
+ },
43
63
  async consume(input) {
44
64
  const record = records.get(input.tokenId);
65
+ pruneExpired(input.now.getTime());
45
66
  if (!record) {
46
67
  return 'invalid';
47
68
  }
@@ -1,8 +1,8 @@
1
- import type { GuardContext } from '@fluojs/http';
2
1
  import { type Token } from '@fluojs/core';
2
+ import type { GuardContext } from '@fluojs/http';
3
3
  import { DefaultJwtVerifier } from '@fluojs/jwt';
4
4
  import { type ModuleType } from '@fluojs/runtime';
5
- import type { AuthStrategy, AuthStrategyRegistration, AuthStrategyResult } from '../types.js';
5
+ import type { AuthStrategy, AuthStrategyRegistration } from '../types.js';
6
6
  /**
7
7
  * Defines the operations required to issue, rotate, and revoke refresh tokens.
8
8
  */
@@ -27,17 +27,53 @@ export interface RefreshTokenInput {
27
27
  }
28
28
  /**
29
29
  * Captures the token pair returned after a successful refresh-token exchange.
30
+ *
31
+ * @remarks
32
+ * This is the application-facing exchange payload shape refresh endpoints return to
33
+ * clients. It is not the shape `RefreshTokenStrategy` places on `ctx.principal`;
34
+ * that principal result is typed by {@link RefreshTokenPrincipal}.
30
35
  */
31
36
  export interface RefreshTokenAuthResult {
32
37
  accessToken: string;
33
38
  refreshToken: string;
34
39
  subject: string;
35
40
  }
41
+ /**
42
+ * The principal `RefreshTokenStrategy` resolves onto `ctx.principal` after a
43
+ * successful exchange, with the rotated token pair nested under `claims`.
44
+ */
45
+ export interface RefreshTokenPrincipal {
46
+ /**
47
+ * Principal claims carrying the rotated pair under `accessToken` and `refreshToken`.
48
+ */
49
+ claims: Record<string, unknown> & {
50
+ accessToken: string;
51
+ refreshToken: string;
52
+ };
53
+ /**
54
+ * Verified subject the rotated access token was issued for.
55
+ */
56
+ subject: string;
57
+ }
36
58
  /**
37
59
  * Identifies the built-in refresh-token authentication strategy.
38
60
  */
39
61
  export declare const REFRESH_TOKEN_STRATEGY_NAME = "refresh-token";
40
62
  type RefreshTokenModuleType = ModuleType;
63
+ /**
64
+ * Configures application modules that supply refresh-token service dependencies.
65
+ */
66
+ export interface RefreshTokenModuleImportOptions {
67
+ /**
68
+ * Modules that export dependencies injected into the refresh-token service.
69
+ *
70
+ * @remarks
71
+ * `RefreshTokenModule` keeps ownership of a class service token. Import modules
72
+ * that own and export its constructor dependencies so they are visible without
73
+ * duplicating the service provider in the importing application module.
74
+ */
75
+ imports?: ModuleType[];
76
+ }
41
77
  /**
42
78
  * Authenticates refresh-token requests and exchanges them for a fresh token pair.
43
79
  */
@@ -45,7 +81,7 @@ export declare class RefreshTokenStrategy implements AuthStrategy {
45
81
  private readonly refreshTokenService;
46
82
  private readonly verifier;
47
83
  constructor(refreshTokenService: RefreshTokenService, verifier: DefaultJwtVerifier);
48
- authenticate(context: GuardContext): Promise<AuthStrategyResult>;
84
+ authenticate(context: GuardContext): Promise<RefreshTokenPrincipal>;
49
85
  private rotateRefreshToken;
50
86
  private extractRefreshToken;
51
87
  private normalizeRefreshToken;
@@ -65,7 +101,20 @@ export declare class RefreshTokenModule {
65
101
  * Registers the shared refresh-token service alias together with `RefreshTokenStrategy`.
66
102
  *
67
103
  * @param service DI token for the concrete refresh-token service implementation.
104
+ * Class tokens are registered inside this module.
105
+ * String and symbol tokens must be visible to this module through an imported
106
+ * module export, a global module export, or bootstrap runtime providers.
107
+ * @param options Optional module imports that export class-service constructor dependencies.
68
108
  * @returns A module definition that exports `RefreshTokenStrategy` and `REFRESH_TOKEN_SERVICE`.
109
+ * @remarks
110
+ * To register a class service with constructor dependencies, place those
111
+ * dependencies in an application-owned module, export them, and pass that module
112
+ * through `options.imports`. This preserves strict
113
+ * `duplicateProviderPolicy: 'throw'` behavior because the service class remains
114
+ * registered exactly once by `RefreshTokenModule`. Do not re-register the service
115
+ * class in the importing application module. String and symbol service tokens
116
+ * can be exported by a module in `options.imports`; globally exported and
117
+ * bootstrap runtime provider tokens are also visible.
69
118
  *
70
119
  * @example
71
120
  * ```ts
@@ -85,12 +134,11 @@ export declare class RefreshTokenModule {
85
134
  * [{ name: REFRESH_TOKEN_STRATEGY_NAME, token: RefreshTokenStrategy }],
86
135
  * ),
87
136
  * ],
88
- * providers: [MyRefreshTokenService],
89
137
  * })
90
138
  * export class AuthModule {}
91
139
  * ```
92
140
  */
93
- static forRoot(service: Token<RefreshTokenService>): RefreshTokenModuleType;
141
+ static forRoot(service: Token<RefreshTokenService>, options?: RefreshTokenModuleImportOptions): RefreshTokenModuleType;
94
142
  }
95
143
  export {};
96
144
  //# sourceMappingURL=refresh-token.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"refresh-token.d.ts","sourceRoot":"","sources":["../../src/refresh/refresh-token.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAkB,MAAM,cAAc,CAAC;AACjE,OAAO,EAA0B,KAAK,KAAK,EAAE,MAAM,cAAc,CAAC;AAElE,OAAO,EAAE,kBAAkB,EAA8C,MAAM,aAAa,CAAC;AAC7F,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAOhE,OAAO,KAAK,EAAE,YAAY,EAAE,wBAAwB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAE9F;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACpD,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjG,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACrD;AAED;;GAEG;AACH,eAAO,MAAM,qBAAqB,eAAoD,CAAC;AAEvF;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,eAAO,MAAM,2BAA2B,kBAAkB,CAAC;AAI3D,KAAK,sBAAsB,GAAG,UAAU,CAAC;AAEzC;;GAEG;AACH,qBACa,oBAAqB,YAAW,YAAY;IAErD,OAAO,CAAC,QAAQ,CAAC,mBAAmB;IACpC,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBADR,mBAAmB,EAAE,mBAAmB,EACxC,QAAQ,EAAE,kBAAkB;IAGzC,YAAY,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,kBAAkB,CAAC;YAwBxD,kBAAkB;IAmBhC,OAAO,CAAC,mBAAmB;IAe3B,OAAO,CAAC,qBAAqB;YAYf,sBAAsB;CAQrC;AAaD;;;;GAIG;AACH,wBAAgB,sCAAsC,IAAI,wBAAwB,CAKjF;AAED;;GAEG;AACH,qBAAa,kBAAkB;IAC7B;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,mBAAmB,CAAC,GAAG,sBAAsB;CAQ5E"}
1
+ {"version":3,"file":"refresh-token.d.ts","sourceRoot":"","sources":["../../src/refresh/refresh-token.ts"],"names":[],"mappings":"AAAA,OAAO,EAA0B,KAAK,KAAK,EAAE,MAAM,cAAc,CAAC;AAElE,OAAO,KAAK,EAAE,YAAY,EAAkB,MAAM,cAAc,CAAC;AACjE,OAAO,EAAE,kBAAkB,EAA8C,MAAM,aAAa,CAAC;AAC7F,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAOhE,OAAO,KAAK,EAAE,YAAY,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAE1E;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACpD,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjG,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACrD;AAED;;GAEG;AACH,eAAO,MAAM,qBAAqB,eAAoD,CAAC;AAEvF;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IACpC;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC;IAChF;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,eAAO,MAAM,2BAA2B,kBAAkB,CAAC;AAI3D,KAAK,sBAAsB,GAAG,UAAU,CAAC;AAEzC;;GAEG;AACH,MAAM,WAAW,+BAA+B;IAC9C;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,UAAU,EAAE,CAAC;CACxB;AAED;;GAEG;AACH,qBACa,oBAAqB,YAAW,YAAY;IAErD,OAAO,CAAC,QAAQ,CAAC,mBAAmB;IACpC,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBADR,mBAAmB,EAAE,mBAAmB,EACxC,QAAQ,EAAE,kBAAkB;IAGzC,YAAY,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,qBAAqB,CAAC;YAwB3D,kBAAkB;IAmBhC,OAAO,CAAC,mBAAmB;IAe3B,OAAO,CAAC,qBAAqB;YAYf,sBAAsB;CAQrC;AAoBD;;;;GAIG;AACH,wBAAgB,sCAAsC,IAAI,wBAAwB,CAKjF;AAED;;GAEG;AACH,qBAAa,kBAAkB;IAC7B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAwCG;IACH,MAAM,CAAC,OAAO,CACZ,OAAO,EAAE,KAAK,CAAC,mBAAmB,CAAC,EACnC,OAAO,GAAE,+BAAoC,GAC5C,sBAAsB;CAY1B"}
@@ -24,6 +24,16 @@ export const REFRESH_TOKEN_SERVICE = Symbol.for('fluo.passport.refresh-token-ser
24
24
 
25
25
  /**
26
26
  * Captures the token pair returned after a successful refresh-token exchange.
27
+ *
28
+ * @remarks
29
+ * This is the application-facing exchange payload shape refresh endpoints return to
30
+ * clients. It is not the shape `RefreshTokenStrategy` places on `ctx.principal`;
31
+ * that principal result is typed by {@link RefreshTokenPrincipal}.
32
+ */
33
+
34
+ /**
35
+ * The principal `RefreshTokenStrategy` resolves onto `ctx.principal` after a
36
+ * successful exchange, with the rotated token pair nested under `claims`.
27
37
  */
28
38
 
29
39
  /**
@@ -31,6 +41,10 @@ export const REFRESH_TOKEN_SERVICE = Symbol.for('fluo.passport.refresh-token-ser
31
41
  */
32
42
  export const REFRESH_TOKEN_STRATEGY_NAME = 'refresh-token';
33
43
  const MALFORMED_REFRESH_TOKEN = Symbol('MALFORMED_REFRESH_TOKEN');
44
+
45
+ /**
46
+ * Configures application modules that supply refresh-token service dependencies.
47
+ */
34
48
  let _RefreshTokenStrategy;
35
49
  /**
36
50
  * Authenticates refresh-token requests and exchanges them for a fresh token pair.
@@ -115,8 +129,11 @@ class RefreshTokenStrategy {
115
129
  }
116
130
  }
117
131
  export { _RefreshTokenStrategy as RefreshTokenStrategy };
132
+ function isClassToken(token) {
133
+ return typeof token === 'function';
134
+ }
118
135
  function createRefreshTokenAliasProviders(service) {
119
- return [{
136
+ return [...(isClassToken(service) ? [service] : []), {
120
137
  provide: REFRESH_TOKEN_SERVICE,
121
138
  useExisting: service
122
139
  }];
@@ -142,7 +159,20 @@ export class RefreshTokenModule {
142
159
  * Registers the shared refresh-token service alias together with `RefreshTokenStrategy`.
143
160
  *
144
161
  * @param service DI token for the concrete refresh-token service implementation.
162
+ * Class tokens are registered inside this module.
163
+ * String and symbol tokens must be visible to this module through an imported
164
+ * module export, a global module export, or bootstrap runtime providers.
165
+ * @param options Optional module imports that export class-service constructor dependencies.
145
166
  * @returns A module definition that exports `RefreshTokenStrategy` and `REFRESH_TOKEN_SERVICE`.
167
+ * @remarks
168
+ * To register a class service with constructor dependencies, place those
169
+ * dependencies in an application-owned module, export them, and pass that module
170
+ * through `options.imports`. This preserves strict
171
+ * `duplicateProviderPolicy: 'throw'` behavior because the service class remains
172
+ * registered exactly once by `RefreshTokenModule`. Do not re-register the service
173
+ * class in the importing application module. String and symbol service tokens
174
+ * can be exported by a module in `options.imports`; globally exported and
175
+ * bootstrap runtime provider tokens are also visible.
146
176
  *
147
177
  * @example
148
178
  * ```ts
@@ -162,15 +192,15 @@ export class RefreshTokenModule {
162
192
  * [{ name: REFRESH_TOKEN_STRATEGY_NAME, token: RefreshTokenStrategy }],
163
193
  * ),
164
194
  * ],
165
- * providers: [MyRefreshTokenService],
166
195
  * })
167
196
  * export class AuthModule {}
168
197
  * ```
169
198
  */
170
- static forRoot(service) {
199
+ static forRoot(service, options = {}) {
171
200
  class RefreshTokenRuntimeModule extends RefreshTokenModule {}
172
201
  return defineModule(RefreshTokenRuntimeModule, {
173
202
  exports: [_RefreshTokenStrategy, REFRESH_TOKEN_SERVICE],
203
+ imports: options.imports ? [...options.imports] : undefined,
174
204
  providers: [_RefreshTokenStrategy, ...createRefreshTokenAliasProviders(service)]
175
205
  });
176
206
  }
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "guard",
10
10
  "strategy"
11
11
  ],
12
- "version": "1.0.4",
12
+ "version": "2.0.0",
13
13
  "private": false,
14
14
  "license": "MIT",
15
15
  "repository": {
@@ -18,7 +18,7 @@
18
18
  "directory": "packages/passport"
19
19
  },
20
20
  "engines": {
21
- "node": ">=20.0.0"
21
+ "node": ">=24.0.0 <27"
22
22
  },
23
23
  "publishConfig": {
24
24
  "access": "public"
@@ -36,14 +36,14 @@
36
36
  "dist"
37
37
  ],
38
38
  "dependencies": {
39
- "@fluojs/di": "^1.0.3",
40
- "@fluojs/http": "^1.1.0",
41
- "@fluojs/jwt": "^1.0.1",
42
- "@fluojs/runtime": "^1.1.2",
43
- "@fluojs/core": "^1.0.3"
39
+ "@fluojs/core": "^2.0.0",
40
+ "@fluojs/di": "^3.0.0",
41
+ "@fluojs/http": "^3.0.0",
42
+ "@fluojs/jwt": "^2.0.0",
43
+ "@fluojs/runtime": "^3.0.0"
44
44
  },
45
45
  "devDependencies": {
46
- "vitest": "^3.2.4"
46
+ "vitest": "^4.1.11"
47
47
  },
48
48
  "scripts": {
49
49
  "prebuild": "node ../../tooling/scripts/clean-dist.mjs",