@rudderjs/passport 1.0.0 → 1.1.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.
Files changed (102) hide show
  1. package/dist/Passport.d.ts +93 -0
  2. package/dist/Passport.d.ts.map +1 -1
  3. package/dist/Passport.js +147 -0
  4. package/dist/Passport.js.map +1 -1
  5. package/dist/client-secret.d.ts +12 -0
  6. package/dist/client-secret.d.ts.map +1 -0
  7. package/dist/client-secret.js +63 -0
  8. package/dist/client-secret.js.map +1 -0
  9. package/dist/commands/client.d.ts +21 -0
  10. package/dist/commands/client.d.ts.map +1 -1
  11. package/dist/commands/client.js +27 -2
  12. package/dist/commands/client.js.map +1 -1
  13. package/dist/commands/keys.d.ts +28 -4
  14. package/dist/commands/keys.d.ts.map +1 -1
  15. package/dist/commands/keys.js +34 -4
  16. package/dist/commands/keys.js.map +1 -1
  17. package/dist/commands/purge.d.ts +6 -1
  18. package/dist/commands/purge.d.ts.map +1 -1
  19. package/dist/commands/purge.js +15 -31
  20. package/dist/commands/purge.js.map +1 -1
  21. package/dist/device-code-secret.d.ts +28 -0
  22. package/dist/device-code-secret.d.ts.map +1 -0
  23. package/dist/device-code-secret.js +31 -0
  24. package/dist/device-code-secret.js.map +1 -0
  25. package/dist/grants/authorization-code.d.ts +23 -0
  26. package/dist/grants/authorization-code.d.ts.map +1 -1
  27. package/dist/grants/authorization-code.js +126 -15
  28. package/dist/grants/authorization-code.js.map +1 -1
  29. package/dist/grants/client-credentials.d.ts.map +1 -1
  30. package/dist/grants/client-credentials.js +13 -5
  31. package/dist/grants/client-credentials.js.map +1 -1
  32. package/dist/grants/device-code.d.ts +10 -1
  33. package/dist/grants/device-code.d.ts.map +1 -1
  34. package/dist/grants/device-code.js +41 -10
  35. package/dist/grants/device-code.js.map +1 -1
  36. package/dist/grants/index.d.ts +1 -1
  37. package/dist/grants/index.d.ts.map +1 -1
  38. package/dist/grants/index.js +1 -1
  39. package/dist/grants/index.js.map +1 -1
  40. package/dist/grants/issue-tokens.d.ts +9 -0
  41. package/dist/grants/issue-tokens.d.ts.map +1 -1
  42. package/dist/grants/issue-tokens.js +39 -5
  43. package/dist/grants/issue-tokens.js.map +1 -1
  44. package/dist/grants/refresh-token.d.ts.map +1 -1
  45. package/dist/grants/refresh-token.js +64 -9
  46. package/dist/grants/refresh-token.js.map +1 -1
  47. package/dist/grants/safe-compare.d.ts +19 -0
  48. package/dist/grants/safe-compare.d.ts.map +1 -0
  49. package/dist/grants/safe-compare.js +28 -0
  50. package/dist/grants/safe-compare.js.map +1 -0
  51. package/dist/index.d.ts +27 -6
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js +122 -67
  54. package/dist/index.js.map +1 -1
  55. package/dist/middleware/bearer.d.ts.map +1 -1
  56. package/dist/middleware/bearer.js +36 -6
  57. package/dist/middleware/bearer.js.map +1 -1
  58. package/dist/middleware/scope.d.ts +12 -2
  59. package/dist/middleware/scope.d.ts.map +1 -1
  60. package/dist/middleware/scope.js +46 -2
  61. package/dist/middleware/scope.js.map +1 -1
  62. package/dist/models/AccessToken.d.ts +32 -0
  63. package/dist/models/AccessToken.d.ts.map +1 -1
  64. package/dist/models/AccessToken.js +63 -3
  65. package/dist/models/AccessToken.js.map +1 -1
  66. package/dist/models/AuthCode.d.ts +16 -0
  67. package/dist/models/AuthCode.d.ts.map +1 -1
  68. package/dist/models/AuthCode.js +17 -1
  69. package/dist/models/AuthCode.js.map +1 -1
  70. package/dist/models/DeviceCode.d.ts +12 -2
  71. package/dist/models/DeviceCode.d.ts.map +1 -1
  72. package/dist/models/DeviceCode.js +7 -1
  73. package/dist/models/DeviceCode.js.map +1 -1
  74. package/dist/models/OAuthClient.d.ts +4 -0
  75. package/dist/models/OAuthClient.d.ts.map +1 -1
  76. package/dist/models/OAuthClient.js +13 -1
  77. package/dist/models/OAuthClient.js.map +1 -1
  78. package/dist/models/RefreshToken.d.ts +11 -0
  79. package/dist/models/RefreshToken.d.ts.map +1 -1
  80. package/dist/models/RefreshToken.js +12 -2
  81. package/dist/models/RefreshToken.js.map +1 -1
  82. package/dist/models/helpers.d.ts +6 -0
  83. package/dist/models/helpers.d.ts.map +1 -1
  84. package/dist/models/helpers.js +15 -2
  85. package/dist/models/helpers.js.map +1 -1
  86. package/dist/opaque-token.d.ts +32 -0
  87. package/dist/opaque-token.d.ts.map +1 -0
  88. package/dist/opaque-token.js +38 -0
  89. package/dist/opaque-token.js.map +1 -0
  90. package/dist/personal-access-tokens.d.ts.map +1 -1
  91. package/dist/personal-access-tokens.js +48 -10
  92. package/dist/personal-access-tokens.js.map +1 -1
  93. package/dist/routes.d.ts +149 -0
  94. package/dist/routes.d.ts.map +1 -1
  95. package/dist/routes.js +279 -41
  96. package/dist/routes.js.map +1 -1
  97. package/dist/token.d.ts +80 -4
  98. package/dist/token.d.ts.map +1 -1
  99. package/dist/token.js +97 -13
  100. package/dist/token.js.map +1 -1
  101. package/package.json +5 -5
  102. package/schema/passport.prisma +29 -9
@@ -1,7 +1,17 @@
1
1
  import { Passport } from '../Passport.js';
2
2
  import { clientHelpers, deviceCodeHelpers } from '../models/helpers.js';
3
+ import { hashDeviceSecret } from '../device-code-secret.js';
3
4
  import { issueTokens } from './issue-tokens.js';
4
- import { OAuthError } from './authorization-code.js';
5
+ import { OAuthError, validateScopes } from './authorization-code.js';
6
+ /**
7
+ * Initial polling interval for new device-code rows (RFC 8628 §3.5).
8
+ * Server escalates by 5s on each `slow_down` response, capped at
9
+ * `Passport.deviceMaxIntervalSeconds()` (default 60). RFC 8628 §3.5 doesn't
10
+ * specify a cap; we add one to keep degenerate clients from pushing the
11
+ * interval to absurd values, with the cap exposed to the operator so a
12
+ * niche flow (machine-only daemons, integration tests) can override it.
13
+ */
14
+ const INITIAL_INTERVAL_SECONDS = 5;
5
15
  /**
6
16
  * Step 1: Device requests authorization codes.
7
17
  * Returns device_code + user_code for the user to enter.
@@ -16,18 +26,27 @@ export async function requestDeviceCode(params) {
16
26
  if (!clientHelpers.hasGrantType(client, 'urn:ietf:params:oauth:grant-type:device_code')) {
17
27
  throw new OAuthError('unauthorized_client', 'Client is not authorized for device authorization grant.');
18
28
  }
29
+ const scopes = params.scope ? params.scope.split(' ').filter(Boolean) : [];
30
+ validateScopes(client, scopes);
19
31
  const { randomBytes } = await import('node:crypto');
20
32
  const deviceCode = randomBytes(32).toString('hex');
21
33
  const userCode = await generateUserCode();
22
- const scopes = params.scope ? params.scope.split(' ').filter(Boolean) : [];
23
34
  const expiresAt = new Date(Date.now() + 15 * 60 * 1000); // 15 minutes
35
+ // Hash both codes at rest (M4 / RFC 8628 §6.1) — a DB read leak should
36
+ // not yield usable codes. The plaintext is returned to the client below
37
+ // and never persisted.
38
+ const [deviceCodeHash, userCodeHash] = await Promise.all([
39
+ hashDeviceSecret(deviceCode),
40
+ hashDeviceSecret(userCode),
41
+ ]);
24
42
  await DeviceCodeCls.create({
25
43
  clientId: params.clientId,
26
- deviceCode,
27
- userCode,
44
+ deviceCodeHash,
45
+ userCodeHash,
28
46
  scopes: JSON.stringify(scopes),
29
47
  userId: null,
30
48
  approved: null,
49
+ interval: INITIAL_INTERVAL_SECONDS,
31
50
  expiresAt,
32
51
  lastPolledAt: null,
33
52
  });
@@ -37,7 +56,7 @@ export async function requestDeviceCode(params) {
37
56
  verification_uri: params.verificationUri,
38
57
  verification_uri_complete: `${params.verificationUri}?user_code=${userCode}`,
39
58
  expires_in: 15 * 60, // 15 minutes in seconds
40
- interval: 5, // poll every 5 seconds
59
+ interval: INITIAL_INTERVAL_SECONDS,
41
60
  };
42
61
  }
43
62
  // ─── User Approval ────────────────────────────────────────
@@ -46,7 +65,11 @@ export async function requestDeviceCode(params) {
46
65
  */
47
66
  export async function approveDeviceCode(userCode, userId, approved) {
48
67
  const DeviceCodeCls = await Passport.deviceCodeModel();
49
- const device = await DeviceCodeCls.where('userCode', userCode).first();
68
+ // M4 look up by hash, not the plaintext the user typed. Same lookup
69
+ // surface an attacker would attempt against; the hash makes the column
70
+ // useless to an attacker who got a DB read.
71
+ const userCodeHash = await hashDeviceSecret(userCode);
72
+ const device = await DeviceCodeCls.where('userCodeHash', userCodeHash).first();
50
73
  if (!device) {
51
74
  throw new OAuthError('invalid_request', 'Device code not found.');
52
75
  }
@@ -69,7 +92,9 @@ export async function pollDeviceCode(params) {
69
92
  throw new OAuthError('unsupported_grant_type', 'Expected grant_type=urn:ietf:params:oauth:grant-type:device_code.');
70
93
  }
71
94
  const DeviceCodeCls = await Passport.deviceCodeModel();
72
- const device = await DeviceCodeCls.where('deviceCode', params.deviceCode).first();
95
+ // M4 hash the supplied plaintext and look up by hash.
96
+ const deviceCodeHash = await hashDeviceSecret(params.deviceCode);
97
+ const device = await DeviceCodeCls.where('deviceCodeHash', deviceCodeHash).first();
73
98
  if (!device) {
74
99
  throw new OAuthError('invalid_grant', 'Device code not found.');
75
100
  }
@@ -79,11 +104,17 @@ export async function pollDeviceCode(params) {
79
104
  if (deviceCodeHelpers.isExpired(device)) {
80
105
  return { status: 'expired_token' };
81
106
  }
82
- // Rate limiting: enforce 5-second interval
107
+ // Rate limiting (RFC 8628 §3.5). Enforce against the per-row `interval`
108
+ // (defaults to 5s, escalates by 5s per slow_down, capped at 60s). Persist
109
+ // the new interval so subsequent polls see the escalated value.
83
110
  if (device.lastPolledAt) {
84
111
  const elapsed = Date.now() - new Date(device.lastPolledAt).getTime();
85
- if (elapsed < 5000) {
86
- return { status: 'slow_down' };
112
+ if (elapsed < device.interval * 1000) {
113
+ const nextInterval = Math.min(device.interval + 5, Passport.deviceMaxIntervalSeconds());
114
+ if (nextInterval !== device.interval) {
115
+ await DeviceCodeCls.update(device.id, { interval: nextInterval });
116
+ }
117
+ return { status: 'slow_down', interval: nextInterval };
87
118
  }
88
119
  }
89
120
  // Update last polled time
@@ -1 +1 @@
1
- {"version":3,"file":"device-code.js","sourceRoot":"","sources":["../../src/grants/device-code.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"device-code.js","sourceRoot":"","sources":["../../src/grants/device-code.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  export { issueTokens } from './issue-tokens.js';
2
2
  export type { IssuedTokens } from './issue-tokens.js';
3
- export { validateAuthorizationRequest, issueAuthCode, exchangeAuthCode, OAuthError, } from './authorization-code.js';
3
+ export { validateAuthorizationRequest, issueAuthCode, exchangeAuthCode, validateScopes, OAuthError, } from './authorization-code.js';
4
4
  export type { AuthorizationRequest, ValidatedAuthRequest, TokenExchangeRequest, } from './authorization-code.js';
5
5
  export { clientCredentialsGrant } from './client-credentials.js';
6
6
  export type { ClientCredentialsRequest } from './client-credentials.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/grants/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAErD,OAAO,EACL,4BAA4B,EAC5B,aAAa,EACb,gBAAgB,EAChB,UAAU,GACX,MAAM,yBAAyB,CAAA;AAChC,YAAY,EACV,oBAAoB,EACpB,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,yBAAyB,CAAA;AAEhC,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAA;AAChE,YAAY,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAA;AAEvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AACtD,YAAY,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AAE7D,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,cAAc,GACf,MAAM,kBAAkB,CAAA;AACzB,YAAY,EACV,2BAA2B,EAC3B,gBAAgB,GACjB,MAAM,kBAAkB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/grants/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAErD,OAAO,EACL,4BAA4B,EAC5B,aAAa,EACb,gBAAgB,EAChB,cAAc,EACd,UAAU,GACX,MAAM,yBAAyB,CAAA;AAChC,YAAY,EACV,oBAAoB,EACpB,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,yBAAyB,CAAA;AAEhC,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAA;AAChE,YAAY,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAA;AAEvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AACtD,YAAY,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AAE7D,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,cAAc,GACf,MAAM,kBAAkB,CAAA;AACzB,YAAY,EACV,2BAA2B,EAC3B,gBAAgB,GACjB,MAAM,kBAAkB,CAAA"}
@@ -1,5 +1,5 @@
1
1
  export { issueTokens } from './issue-tokens.js';
2
- export { validateAuthorizationRequest, issueAuthCode, exchangeAuthCode, OAuthError, } from './authorization-code.js';
2
+ export { validateAuthorizationRequest, issueAuthCode, exchangeAuthCode, validateScopes, OAuthError, } from './authorization-code.js';
3
3
  export { clientCredentialsGrant } from './client-credentials.js';
4
4
  export { refreshTokenGrant } from './refresh-token.js';
5
5
  export { requestDeviceCode, approveDeviceCode, pollDeviceCode, } from './device-code.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/grants/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAG/C,OAAO,EACL,4BAA4B,EAC5B,aAAa,EACb,gBAAgB,EAChB,UAAU,GACX,MAAM,yBAAyB,CAAA;AAOhC,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAA;AAGhE,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAGtD,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,cAAc,GACf,MAAM,kBAAkB,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/grants/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAG/C,OAAO,EACL,4BAA4B,EAC5B,aAAa,EACb,gBAAgB,EAChB,cAAc,EACd,UAAU,GACX,MAAM,yBAAyB,CAAA;AAOhC,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAA;AAGhE,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAGtD,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,cAAc,GACf,MAAM,kBAAkB,CAAA"}
@@ -6,6 +6,13 @@ export interface IssuedTokens {
6
6
  }
7
7
  /**
8
8
  * Issue an access token (+ optional refresh token) and persist to DB.
9
+ *
10
+ * `familyId` ties a refresh token to its rotation chain. On the first
11
+ * refresh-emitting grant for a session (auth-code, device-code, password)
12
+ * the caller leaves it undefined and a fresh family identifier is
13
+ * generated. On subsequent rotations the refresh-token grant passes the
14
+ * existing familyId through, so reuse-detection in `refreshTokenGrant`
15
+ * can revoke the entire chain in a single query (RFC 6819 §5.2.2.3).
9
16
  */
10
17
  export declare function issueTokens(opts: {
11
18
  userId: string | null;
@@ -14,5 +21,7 @@ export declare function issueTokens(opts: {
14
21
  includeRefresh?: boolean;
15
22
  /** Override access token lifetime in ms */
16
23
  lifetime?: number;
24
+ /** Existing rotation-family id to copy onto the new refresh token. */
25
+ familyId?: string | null;
17
26
  }): Promise<IssuedTokens>;
18
27
  //# sourceMappingURL=issue-tokens.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"issue-tokens.d.ts","sourceRoot":"","sources":["../../src/grants/issue-tokens.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAG,MAAM,CAAA;IACrB,UAAU,EAAK,QAAQ,CAAA;IACvB,UAAU,EAAK,MAAM,CAAA;IACrB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED;;GAEG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE;IACtC,MAAM,EAAQ,MAAM,GAAG,IAAI,CAAA;IAC3B,QAAQ,EAAM,MAAM,CAAA;IACpB,MAAM,EAAQ,MAAM,EAAE,CAAA;IACtB,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,2CAA2C;IAC3C,QAAQ,CAAC,EAAK,MAAM,CAAA;CACrB,GAAG,OAAO,CAAC,YAAY,CAAC,CA8CxB"}
1
+ {"version":3,"file":"issue-tokens.d.ts","sourceRoot":"","sources":["../../src/grants/issue-tokens.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAG,MAAM,CAAA;IACrB,UAAU,EAAK,QAAQ,CAAA;IACvB,UAAU,EAAK,MAAM,CAAA;IACrB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED;;;;;;;;;GASG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE;IACtC,MAAM,EAAQ,MAAM,GAAG,IAAI,CAAA;IAC3B,QAAQ,EAAM,MAAM,CAAA;IACpB,MAAM,EAAQ,MAAM,EAAE,CAAA;IACtB,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,2CAA2C;IAC3C,QAAQ,CAAC,EAAK,MAAM,CAAA;IACpB,sEAAsE;IACtE,QAAQ,CAAC,EAAK,MAAM,GAAG,IAAI,CAAA;CAC5B,GAAG,OAAO,CAAC,YAAY,CAAC,CAgExB"}
@@ -1,11 +1,26 @@
1
1
  import { Passport } from '../Passport.js';
2
2
  import { createToken } from '../token.js';
3
+ import { hashOpaqueToken, newOpaqueToken } from '../opaque-token.js';
3
4
  /**
4
5
  * Issue an access token (+ optional refresh token) and persist to DB.
6
+ *
7
+ * `familyId` ties a refresh token to its rotation chain. On the first
8
+ * refresh-emitting grant for a session (auth-code, device-code, password)
9
+ * the caller leaves it undefined and a fresh family identifier is
10
+ * generated. On subsequent rotations the refresh-token grant passes the
11
+ * existing familyId through, so reuse-detection in `refreshTokenGrant`
12
+ * can revoke the entire chain in a single query (RFC 6819 §5.2.2.3).
5
13
  */
6
14
  export async function issueTokens(opts) {
7
15
  const lifetime = opts.lifetime ?? Passport.tokenLifetime();
8
- const expiresAt = new Date(Date.now() + lifetime);
16
+ // Single wall-clock snapshot for the entire issuance — `iat` (in JWT),
17
+ // `exp` (= expiresAt), `expires_in` (lifetime/1000), and the refresh
18
+ // token's `expiresAt` all derive from this instant so a downstream
19
+ // verifier never sees `iat + expires_in !== exp` (sub-second drift between
20
+ // independent `Date.now()` reads is otherwise possible across the
21
+ // intervening async DB writes + key load).
22
+ const now = Date.now();
23
+ const expiresAt = new Date(now + lifetime);
9
24
  const AccessTokenCls = await Passport.tokenModel();
10
25
  const RefreshTokenCls = await Passport.refreshTokenModel();
11
26
  // Create DB record
@@ -24,22 +39,41 @@ export async function issueTokens(opts) {
24
39
  clientId: opts.clientId,
25
40
  scopes: opts.scopes,
26
41
  expiresAt,
42
+ iatMs: now,
27
43
  });
28
44
  const result = {
29
45
  access_token: jwt,
30
46
  token_type: 'Bearer',
31
47
  expires_in: Math.floor(lifetime / 1000),
32
48
  };
33
- // Issue refresh token
49
+ // Issue refresh token. M5/P6: the plaintext returned to the client is a
50
+ // fresh CSPRNG opaque string; only its SHA-256 is persisted (`tokenHash`).
51
+ // The previous shape — `result.refresh_token = refreshRecord.id` — handed
52
+ // every active refresh token to anyone with `SELECT * ON oauth_refresh_tokens`
53
+ // privilege on the database.
34
54
  if (opts.includeRefresh !== false) {
35
- const refreshExpiresAt = new Date(Date.now() + Passport.refreshTokenLifetime());
36
- const refreshRecord = await RefreshTokenCls.create({
55
+ const refreshExpiresAt = new Date(now + Passport.refreshTokenLifetime());
56
+ const familyId = opts.familyId ?? await newFamilyId();
57
+ const refreshPlaintext = await newOpaqueToken();
58
+ const refreshHash = await hashOpaqueToken(refreshPlaintext);
59
+ await RefreshTokenCls.create({
37
60
  accessTokenId: tokenId,
61
+ tokenHash: refreshHash,
62
+ familyId,
38
63
  revoked: false,
39
64
  expiresAt: refreshExpiresAt,
40
65
  });
41
- result.refresh_token = refreshRecord.id;
66
+ result.refresh_token = refreshPlaintext;
42
67
  }
43
68
  return result;
44
69
  }
70
+ /**
71
+ * Generate an opaque rotation-family id. Lazy-loads `node:crypto` so the
72
+ * package stays importable from non-Node runtimes that don't issue tokens
73
+ * themselves (the caller will have already loaded crypto when it got here).
74
+ */
75
+ async function newFamilyId() {
76
+ const { randomUUID } = await import('node:crypto');
77
+ return randomUUID();
78
+ }
45
79
  //# sourceMappingURL=issue-tokens.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"issue-tokens.js","sourceRoot":"","sources":["../../src/grants/issue-tokens.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAGzC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AASzC;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAOjC;IACC,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,QAAQ,CAAC,aAAa,EAAE,CAAA;IAC1D,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,CAAC,CAAA;IAEjD,MAAM,cAAc,GAAI,MAAM,QAAQ,CAAC,UAAU,EAAE,CAAA;IACnD,MAAM,eAAe,GAAG,MAAM,QAAQ,CAAC,iBAAiB,EAAE,CAAA;IAE1D,mBAAmB;IACnB,MAAM,WAAW,GAAG,MAAM,cAAc,CAAC,MAAM,CAAC;QAC9C,MAAM,EAAK,IAAI,CAAC,MAAM;QACtB,QAAQ,EAAG,IAAI,CAAC,QAAQ;QACxB,MAAM,EAAK,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC;QACtC,OAAO,EAAI,KAAK;QAChB,SAAS;KACiB,CAAgB,CAAA;IAE5C,MAAM,OAAO,GAAI,WAAmB,CAAC,EAAY,CAAA;IAEjD,WAAW;IACX,MAAM,GAAG,GAAG,MAAM,WAAW,CAAC;QAC5B,OAAO;QACP,MAAM,EAAI,IAAI,CAAC,MAAM;QACrB,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,MAAM,EAAI,IAAI,CAAC,MAAM;QACrB,SAAS;KACV,CAAC,CAAA;IAEF,MAAM,MAAM,GAAiB;QAC3B,YAAY,EAAE,GAAG;QACjB,UAAU,EAAI,QAAQ;QACtB,UAAU,EAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,IAAI,CAAC;KAC1C,CAAA;IAED,sBAAsB;IACtB,IAAI,IAAI,CAAC,cAAc,KAAK,KAAK,EAAE,CAAC;QAClC,MAAM,gBAAgB,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,CAAC,oBAAoB,EAAE,CAAC,CAAA;QAC/E,MAAM,aAAa,GAAG,MAAM,eAAe,CAAC,MAAM,CAAC;YACjD,aAAa,EAAE,OAAO;YACtB,OAAO,EAAQ,KAAK;YACpB,SAAS,EAAM,gBAAgB;SACL,CAAiB,CAAA;QAE7C,MAAM,CAAC,aAAa,GAAI,aAAqB,CAAC,EAAY,CAAA;IAC5D,CAAC;IAED,OAAO,MAAM,CAAA;AACf,CAAC"}
1
+ {"version":3,"file":"issue-tokens.js","sourceRoot":"","sources":["../../src/grants/issue-tokens.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAEzC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AASpE;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IASjC;IACC,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,QAAQ,CAAC,aAAa,EAAE,CAAA;IAC1D,uEAAuE;IACvE,qEAAqE;IACrE,mEAAmE;IACnE,2EAA2E;IAC3E,kEAAkE;IAClE,2CAA2C;IAC3C,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;IACtB,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,GAAG,GAAG,QAAQ,CAAC,CAAA;IAE1C,MAAM,cAAc,GAAI,MAAM,QAAQ,CAAC,UAAU,EAAE,CAAA;IACnD,MAAM,eAAe,GAAG,MAAM,QAAQ,CAAC,iBAAiB,EAAE,CAAA;IAE1D,mBAAmB;IACnB,MAAM,WAAW,GAAG,MAAM,cAAc,CAAC,MAAM,CAAC;QAC9C,MAAM,EAAK,IAAI,CAAC,MAAM;QACtB,QAAQ,EAAG,IAAI,CAAC,QAAQ;QACxB,MAAM,EAAK,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC;QACtC,OAAO,EAAI,KAAK;QAChB,SAAS;KACiB,CAAgB,CAAA;IAE5C,MAAM,OAAO,GAAG,WAAW,CAAC,EAAE,CAAA;IAE9B,WAAW;IACX,MAAM,GAAG,GAAG,MAAM,WAAW,CAAC;QAC5B,OAAO;QACP,MAAM,EAAI,IAAI,CAAC,MAAM;QACrB,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,MAAM,EAAI,IAAI,CAAC,MAAM;QACrB,SAAS;QACT,KAAK,EAAK,GAAG;KACd,CAAC,CAAA;IAEF,MAAM,MAAM,GAAiB;QAC3B,YAAY,EAAE,GAAG;QACjB,UAAU,EAAI,QAAQ;QACtB,UAAU,EAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,IAAI,CAAC;KAC1C,CAAA;IAED,wEAAwE;IACxE,2EAA2E;IAC3E,0EAA0E;IAC1E,+EAA+E;IAC/E,6BAA6B;IAC7B,IAAI,IAAI,CAAC,cAAc,KAAK,KAAK,EAAE,CAAC;QAClC,MAAM,gBAAgB,GAAG,IAAI,IAAI,CAAC,GAAG,GAAG,QAAQ,CAAC,oBAAoB,EAAE,CAAC,CAAA;QACxE,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,MAAM,WAAW,EAAE,CAAA;QACrD,MAAM,gBAAgB,GAAG,MAAM,cAAc,EAAE,CAAA;QAC/C,MAAM,WAAW,GAAQ,MAAM,eAAe,CAAC,gBAAgB,CAAC,CAAA;QAEhE,MAAM,eAAe,CAAC,MAAM,CAAC;YAC3B,aAAa,EAAE,OAAO;YACtB,SAAS,EAAM,WAAW;YAC1B,QAAQ;YACR,OAAO,EAAQ,KAAK;YACpB,SAAS,EAAM,gBAAgB;SACL,CAAC,CAAA;QAE7B,MAAM,CAAC,aAAa,GAAG,gBAAgB,CAAA;IACzC,CAAC;IAED,OAAO,MAAM,CAAA;AACf,CAAC;AAED;;;;GAIG;AACH,KAAK,UAAU,WAAW;IACxB,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAA;IAClD,OAAO,UAAU,EAAE,CAAA;AACrB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"refresh-token.d.ts","sourceRoot":"","sources":["../../src/grants/refresh-token.ts"],"names":[],"mappings":"AAKA,OAAO,EAAe,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAGlE,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAK,MAAM,CAAA;IACpB,YAAY,EAAE,MAAM,CAAA;IACpB,QAAQ,EAAM,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,KAAK,CAAC,EAAQ,MAAM,CAAA;CACrB;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,YAAY,CAAC,CAuE1F"}
1
+ {"version":3,"file":"refresh-token.d.ts","sourceRoot":"","sources":["../../src/grants/refresh-token.ts"],"names":[],"mappings":"AAOA,OAAO,EAAe,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAGlE,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAK,MAAM,CAAA;IACpB,YAAY,EAAE,MAAM,CAAA;IACpB,QAAQ,EAAM,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,KAAK,CAAC,EAAQ,MAAM,CAAA;CACrB;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,YAAY,CAAC,CA6F1F"}
@@ -1,5 +1,7 @@
1
1
  import { Passport } from '../Passport.js';
2
2
  import { accessTokenHelpers, refreshTokenHelpers } from '../models/helpers.js';
3
+ import { verifyClientSecret } from '../client-secret.js';
4
+ import { hashOpaqueToken } from '../opaque-token.js';
3
5
  import { issueTokens } from './issue-tokens.js';
4
6
  import { OAuthError } from './authorization-code.js';
5
7
  /**
@@ -23,18 +25,35 @@ export async function refreshTokenGrant(params) {
23
25
  if (!params.clientSecret) {
24
26
  throw new OAuthError('invalid_client', 'Client secret required.', 401);
25
27
  }
26
- const { createHash } = await import('node:crypto');
27
- const hashed = createHash('sha256').update(params.clientSecret).digest('hex');
28
- if (hashed !== client.secret) {
28
+ // Schema allows `client.secret` to be null; explicit guard so a future
29
+ // refactor can't mask `secret = null` as authenticating. See
30
+ // `client-credentials.ts` for the longer-form rationale.
31
+ if (client.secret == null) {
32
+ throw new OAuthError('invalid_client', 'Confidential client has no secret on file.', 401);
33
+ }
34
+ if (!(await verifyClientSecret(params.clientSecret, client.secret))) {
29
35
  throw new OAuthError('invalid_client', 'Invalid client secret.', 401);
30
36
  }
31
37
  }
32
- // Find refresh token
33
- const refreshToken = await RefreshTokenCls.where('id', params.refreshToken).first();
38
+ // Find refresh token by hashed plaintext (M5/P6) — the row's `id` is no
39
+ // longer the bearer secret, so a DB read leak doesn't yield usable tokens.
40
+ // Pre-migration tokens (which used `id` as the plaintext) won't match
41
+ // because their hashed form was never persisted; affected sessions force-
42
+ // relogin on next refresh, same blast radius as RSA keypair rotation.
43
+ const refreshTokenHash = await hashOpaqueToken(params.refreshToken);
44
+ const refreshToken = await RefreshTokenCls.where('tokenHash', refreshTokenHash).first();
34
45
  if (!refreshToken) {
35
46
  throw new OAuthError('invalid_grant', 'Refresh token not found.');
36
47
  }
37
48
  if (refreshToken.revoked) {
49
+ // Reuse detected. RFC 6819 §5.2.2.3 / OAuth 2.0 BCP §4.14: revoke the
50
+ // entire family so a stolen+rotated refresh token can't keep living
51
+ // alongside the legitimate user's session. Legacy rows minted before
52
+ // the familyId column existed have null and are exempt during the
53
+ // migration window — same approach as the redirect_uri rollout.
54
+ if (refreshToken.familyId) {
55
+ await revokeFamily(RefreshTokenCls, AccessTokenCls, refreshToken.familyId);
56
+ }
38
57
  throw new OAuthError('invalid_grant', 'Refresh token has been revoked.');
39
58
  }
40
59
  if (refreshTokenHelpers.isExpired(refreshToken)) {
@@ -59,15 +78,51 @@ export async function refreshTokenGrant(params) {
59
78
  }
60
79
  scopes = requested;
61
80
  }
62
- // Revoke old tokens
63
- await RefreshTokenCls.update(refreshToken.id, { revoked: true });
64
- await AccessTokenCls.update(accessToken.id, { revoked: true });
65
- // Issue new pair
81
+ // Revoke old tokens. Goes through QueryBuilder.updateAll() — bypasses the
82
+ // mass-assignment filter (`revoked` is no longer in fillable on either
83
+ // model) and avoids paying for a second hydration round-trip per
84
+ // refresh.
85
+ await RefreshTokenCls.where('id', refreshToken.id).updateAll({ revoked: true });
86
+ await AccessTokenCls.where('id', accessToken.id).updateAll({ revoked: true });
87
+ // Issue new pair — propagate the existing familyId so the rotation chain
88
+ // is preserved. Legacy rows with null get a fresh family on next rotation.
66
89
  return issueTokens({
67
90
  userId: accessToken.userId,
68
91
  clientId: params.clientId,
69
92
  scopes,
70
93
  includeRefresh: true,
94
+ familyId: refreshToken.familyId ?? null,
71
95
  });
72
96
  }
97
+ /**
98
+ * Revoke every access + refresh token in a rotation family. Called on
99
+ * detected reuse of an already-revoked refresh token. Best-effort: ORM
100
+ * errors are not propagated to the caller because the outer flow is
101
+ * already going to throw `invalid_grant`. Failures here would only mask
102
+ * the security signal that prompted the family lookup.
103
+ */
104
+ async function revokeFamily(RefreshTokenCls, AccessTokenCls, familyId) {
105
+ try {
106
+ // Two bulk QueryBuilder.updateAll() calls — one per table — replace
107
+ // the prior read-then-N+1-update loop. Each is idempotent: refresh
108
+ // tokens already revoked are filtered by the inner where, and access
109
+ // tokens scoped by family-membership via their refresh tokens'
110
+ // accessTokenId list. Bypasses the mass-assignment filter (`revoked`
111
+ // is no longer in fillable on either model).
112
+ const family = await RefreshTokenCls.where('familyId', familyId).get();
113
+ if (family.length === 0)
114
+ return;
115
+ await RefreshTokenCls.where('familyId', familyId)
116
+ .where('revoked', false)
117
+ .updateAll({ revoked: true });
118
+ const accessTokenIds = family.map(rt => rt.accessTokenId);
119
+ // `Model.where(col, val)` is 2-arg only; operator overloads live on the
120
+ // QueryBuilder returned by `query()`. Use that to express IN(...).
121
+ await AccessTokenCls.query().where('id', 'IN', accessTokenIds)
122
+ .updateAll({ revoked: true });
123
+ }
124
+ catch {
125
+ // Swallow — the outer handler always throws invalid_grant on reuse.
126
+ }
127
+ }
73
128
  //# sourceMappingURL=refresh-token.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"refresh-token.js","sourceRoot":"","sources":["../../src/grants/refresh-token.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"refresh-token.js","sourceRoot":"","sources":["../../src/grants/refresh-token.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Constant-time string comparison.
3
+ *
4
+ * Used on every site that compares a hashed credential or a verifier:
5
+ * client secret hashes (hex), PKCE verifier ↔ challenge (base64url for
6
+ * S256, raw for plain). `===` / `!==` short-circuits on first mismatch,
7
+ * leaking timing on pathological inputs; `timingSafeEqual` runs in O(n)
8
+ * regardless of where the first mismatch falls.
9
+ *
10
+ * Returns `false` for any null/undefined input or length mismatch — both
11
+ * surface as authentication failures upstream, no need to throw.
12
+ *
13
+ * `node:crypto` is lazy-loaded so this module is safe to import from
14
+ * package entrypoints (matches the rest of @rudderjs/passport's lazy
15
+ * crypto pattern; Vite externalizes node:* and a top-level import crashes
16
+ * the browser).
17
+ */
18
+ export declare function safeCompare(a: string | null | undefined, b: string | null | undefined): Promise<boolean>;
19
+ //# sourceMappingURL=safe-compare.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safe-compare.d.ts","sourceRoot":"","sources":["../../src/grants/safe-compare.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,CAO9G"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Constant-time string comparison.
3
+ *
4
+ * Used on every site that compares a hashed credential or a verifier:
5
+ * client secret hashes (hex), PKCE verifier ↔ challenge (base64url for
6
+ * S256, raw for plain). `===` / `!==` short-circuits on first mismatch,
7
+ * leaking timing on pathological inputs; `timingSafeEqual` runs in O(n)
8
+ * regardless of where the first mismatch falls.
9
+ *
10
+ * Returns `false` for any null/undefined input or length mismatch — both
11
+ * surface as authentication failures upstream, no need to throw.
12
+ *
13
+ * `node:crypto` is lazy-loaded so this module is safe to import from
14
+ * package entrypoints (matches the rest of @rudderjs/passport's lazy
15
+ * crypto pattern; Vite externalizes node:* and a top-level import crashes
16
+ * the browser).
17
+ */
18
+ export async function safeCompare(a, b) {
19
+ if (a == null || b == null)
20
+ return false;
21
+ if (a.length !== b.length)
22
+ return false;
23
+ const { timingSafeEqual } = await import('node:crypto');
24
+ // UTF-8 bytes — works for any ASCII encoding (hex, base64url) and length
25
+ // checks above guarantee Buffer.byteLength matches for these inputs.
26
+ return timingSafeEqual(Buffer.from(a, 'utf8'), Buffer.from(b, 'utf8'));
27
+ }
28
+ //# sourceMappingURL=safe-compare.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safe-compare.js","sourceRoot":"","sources":["../../src/grants/safe-compare.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,CAA4B,EAAE,CAA4B;IAC1F,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,IAAI;QAAE,OAAO,KAAK,CAAA;IACxC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM;QAAG,OAAO,KAAK,CAAA;IACxC,MAAM,EAAE,eAAe,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAA;IACvD,yEAAyE;IACzE,qEAAqE;IACrE,OAAO,eAAe,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAA;AACxE,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,24 +1,27 @@
1
1
  import { ServiceProvider } from '@rudderjs/core';
2
2
  export { Passport } from './Passport.js';
3
3
  export type { PassportScope, AuthorizationViewContext, AuthorizationViewFn } from './Passport.js';
4
- export { createToken, verifyToken, decodeToken } from './token.js';
5
- export type { JwtHeader, JwtPayload } from './token.js';
4
+ export { createToken, verifyToken, unsafeDecodeToken, decodeToken } from './token.js';
5
+ export type { JwtHeader, JwtPayload, VerifyTokenOptions } from './token.js';
6
6
  export { OAuthClient } from './models/OAuthClient.js';
7
7
  export { AccessToken } from './models/AccessToken.js';
8
8
  export { RefreshToken } from './models/RefreshToken.js';
9
9
  export { AuthCode } from './models/AuthCode.js';
10
10
  export { DeviceCode } from './models/DeviceCode.js';
11
11
  export { BearerMiddleware, RequireBearer } from './middleware/bearer.js';
12
- export { scope } from './middleware/scope.js';
12
+ export { scope, scopeAny } from './middleware/scope.js';
13
13
  export { generateKeys } from './commands/keys.js';
14
- export { createClient } from './commands/client.js';
14
+ export { createClient, resolveClientGrantTypes } from './commands/client.js';
15
15
  export type { CreateClientOpts } from './commands/client.js';
16
16
  export { purgeTokens } from './commands/purge.js';
17
- export { issueTokens, validateAuthorizationRequest, issueAuthCode, exchangeAuthCode, OAuthError, clientCredentialsGrant, refreshTokenGrant, requestDeviceCode, approveDeviceCode, pollDeviceCode, } from './grants/index.js';
17
+ export { hashClientSecret, verifyClientSecret } from './client-secret.js';
18
+ export { hashDeviceSecret } from './device-code-secret.js';
19
+ export { hashOpaqueToken, newOpaqueToken } from './opaque-token.js';
20
+ export { issueTokens, validateAuthorizationRequest, issueAuthCode, exchangeAuthCode, validateScopes, OAuthError, clientCredentialsGrant, refreshTokenGrant, requestDeviceCode, approveDeviceCode, pollDeviceCode, } from './grants/index.js';
18
21
  export type { IssuedTokens, AuthorizationRequest, ValidatedAuthRequest, TokenExchangeRequest, ClientCredentialsRequest, RefreshTokenRequest, DeviceAuthorizationResponse, DevicePollResult, } from './grants/index.js';
19
22
  export { HasApiTokens, resetPersonalAccessClient } from './personal-access-tokens.js';
20
23
  export type { NewPersonalAccessToken } from './personal-access-tokens.js';
21
- export { registerPassportRoutes } from './routes.js';
24
+ export { registerPassportRoutes, registerPassportWebRoutes, registerPassportApiRoutes } from './routes.js';
22
25
  export type { PassportRouteOptions, PassportRouteGroup } from './routes.js';
23
26
  export interface PassportConfig {
24
27
  /** Directory where RSA keys are stored (default: 'storage') */
@@ -34,6 +37,24 @@ export interface PassportConfig {
34
37
  personalAccessTokensExpireIn?: number;
35
38
  /** OAuth scopes: { scopeId: 'description' } */
36
39
  scopes?: Record<string, string>;
40
+ /**
41
+ * JWT issuer URL. When set, `createToken()` stamps it as the `iss` claim
42
+ * on every new access token, and `BearerMiddleware`/`RequireBearer` ask
43
+ * `verifyToken()` to reject tokens whose `iss` claim doesn't match.
44
+ * Tokens minted before this is configured carry no `iss` and pass
45
+ * verification (legacy migration window). Recommended once a deployment
46
+ * has more than one possible signer (multi-tenant, staging+prod sharing
47
+ * keys). RFC 8725 §3.10.
48
+ */
49
+ issuer?: string;
50
+ /**
51
+ * Maximum value (in seconds) the per-row `oauth_device_codes.interval`
52
+ * is allowed to grow to via repeated `slow_down` escalations (RFC 8628
53
+ * §3.5). Default 60. Floor is 5 (the initial interval); values below
54
+ * the floor are clamped. Raise for machine-only / no-human-in-the-loop
55
+ * device flows where misbehaving clients warrant aggressive back-off.
56
+ */
57
+ deviceMaxInterval?: number;
37
58
  }
38
59
  export declare class PassportProvider extends ServiceProvider {
39
60
  register(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAU,MAAM,gBAAgB,CAAA;AAIxD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACxC,YAAY,EAAE,aAAa,EAAE,wBAAwB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAA;AAEjG,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAClE,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAEvD,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AAEnD,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;AACxE,OAAO,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAA;AAE7C,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AACnD,YAAY,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAC5D,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAGjD,OAAO,EACL,WAAW,EACX,4BAA4B,EAC5B,aAAa,EACb,gBAAgB,EAChB,UAAU,EACV,sBAAsB,EACtB,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EACjB,cAAc,GACf,MAAM,mBAAmB,CAAA;AAC1B,YAAY,EACV,YAAY,EACZ,oBAAoB,EACpB,oBAAoB,EACpB,oBAAoB,EACpB,wBAAwB,EACxB,mBAAmB,EACnB,2BAA2B,EAC3B,gBAAgB,GACjB,MAAM,mBAAmB,CAAA;AAG1B,OAAO,EAAE,YAAY,EAAE,yBAAyB,EAAE,MAAM,6BAA6B,CAAA;AACrF,YAAY,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAA;AAGzE,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AACpD,YAAY,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAI3E,MAAM,WAAW,cAAc;IAC7B,+DAA+D;IAC/D,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,yCAAyC;IACzC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,qDAAqD;IACrD,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,sDAAsD;IACtD,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,gEAAgE;IAChE,4BAA4B,CAAC,EAAE,MAAM,CAAA;IACrC,+CAA+C;IAC/C,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAChC;AAID,qBAAa,gBAAiB,SAAQ,eAAe;IACnD,QAAQ,IAAI,IAAI;IAEV,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAsG5B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}