@rudderjs/passport 0.1.4 → 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
@@ -2,26 +2,56 @@ import { Passport } from '../Passport.js';
2
2
  /**
3
3
  * Generate RSA keypair for JWT signing.
4
4
  * Writes to storage/oauth-private.key and storage/oauth-public.key.
5
+ *
6
+ * With `--force`, existing keys are renamed to `*.bak.<ISO-timestamp>` before
7
+ * being replaced AND the prior public key is also copied to
8
+ * `oauth-previous-public.key`. The verifier walks both keys during the
9
+ * grace window (until the prior tokens expire naturally), so a rotation no
10
+ * longer forces an immediate global sign-out. The audit backups live
11
+ * alongside for full recovery; the previous-public file is the operational
12
+ * one that the verifier consults.
5
13
  */
6
14
  export async function generateKeys(opts = {}) {
7
15
  const { generateKeyPairSync } = await import('node:crypto');
8
- const { writeFile, mkdir } = await import('node:fs/promises');
16
+ const { writeFile, mkdir, rename, copyFile } = await import('node:fs/promises');
9
17
  const { existsSync } = await import('node:fs');
10
18
  const { join } = await import('node:path');
11
19
  const keyDir = join(process.cwd(), Passport.keyPath());
12
20
  const privatePath = join(keyDir, 'oauth-private.key');
13
21
  const publicPath = join(keyDir, 'oauth-public.key');
14
- if (!opts.force && existsSync(privatePath)) {
22
+ const previousPublicPath = join(keyDir, 'oauth-previous-public.key');
23
+ const privateExists = existsSync(privatePath);
24
+ const publicExists = existsSync(publicPath);
25
+ if (!opts.force && privateExists) {
15
26
  throw new Error(`Keys already exist at ${privatePath}. Use --force to overwrite.`);
16
27
  }
28
+ await mkdir(keyDir, { recursive: true });
29
+ let backup = null;
30
+ let previousPublicWritten = null;
31
+ if (opts.force && (privateExists || publicExists)) {
32
+ const stamp = new Date().toISOString().replace(/[:.]/g, '-');
33
+ const privateBackup = `${privatePath}.bak.${stamp}`;
34
+ const publicBackup = `${publicPath}.bak.${stamp}`;
35
+ // Copy the public key to the rolling "previous" slot BEFORE renaming —
36
+ // the verifier loads from `oauth-previous-public.key` so JWTs signed by
37
+ // the about-to-rotate key keep verifying during their natural lifetime.
38
+ if (publicExists) {
39
+ await copyFile(publicPath, previousPublicPath);
40
+ previousPublicWritten = previousPublicPath;
41
+ }
42
+ if (privateExists)
43
+ await rename(privatePath, privateBackup);
44
+ if (publicExists)
45
+ await rename(publicPath, publicBackup);
46
+ backup = { privatePath: privateBackup, publicPath: publicBackup };
47
+ }
17
48
  const { privateKey, publicKey } = generateKeyPairSync('rsa', {
18
49
  modulusLength: 4096,
19
50
  publicKeyEncoding: { type: 'spki', format: 'pem' },
20
51
  privateKeyEncoding: { type: 'pkcs8', format: 'pem' },
21
52
  });
22
- await mkdir(keyDir, { recursive: true });
23
53
  await writeFile(privatePath, privateKey, { mode: 0o600 });
24
54
  await writeFile(publicPath, publicKey, { mode: 0o644 });
25
- return { privatePath, publicPath };
55
+ return { privatePath, publicPath, backup, previousPublicPath: previousPublicWritten };
26
56
  }
27
57
  //# sourceMappingURL=keys.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"keys.js","sourceRoot":"","sources":["../../src/commands/keys.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAEzC;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,OAA4B,EAAE;IAC/D,MAAM,EAAE,mBAAmB,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAA;IAC3D,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAA;IAC7D,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,CAAA;IAC9C,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,CAAA;IAE1C,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAA;IACtD,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAA;IACrD,MAAM,UAAU,GAAI,IAAI,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAA;IAEpD,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;QAC3C,MAAM,IAAI,KAAK,CAAC,yBAAyB,WAAW,6BAA6B,CAAC,CAAA;IACpF,CAAC;IAED,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,GAAG,mBAAmB,CAAC,KAAK,EAAE;QAC3D,aAAa,EAAE,IAAI;QACnB,iBAAiB,EAAG,EAAE,IAAI,EAAE,MAAM,EAAG,MAAM,EAAE,KAAK,EAAE;QACpD,kBAAkB,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE;KACrD,CAAC,CAAA;IAEF,MAAM,KAAK,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IACxC,MAAM,SAAS,CAAC,WAAW,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;IACzD,MAAM,SAAS,CAAC,UAAU,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;IAEvD,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,CAAA;AACpC,CAAC"}
1
+ {"version":3,"file":"keys.js","sourceRoot":"","sources":["../../src/commands/keys.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,11 @@
1
1
  /**
2
2
  * Remove expired and revoked tokens from the database.
3
- * Returns counts of purged records.
3
+ *
4
+ * Each model is purged with a single bulk `deleteAll()` against the
5
+ * QueryBuilder — one round-trip per model regardless of row count. No
6
+ * hydration, no per-row delete calls, no observers (counter-style data plane).
7
+ *
8
+ * Returns the number of rows deleted per model.
4
9
  */
5
10
  export declare function purgeTokens(): Promise<{
6
11
  accessTokens: number;
@@ -1 +1 @@
1
- {"version":3,"file":"purge.d.ts","sourceRoot":"","sources":["../../src/commands/purge.ts"],"names":[],"mappings":"AAMA;;;GAGG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC;IAC3C,YAAY,EAAG,MAAM,CAAA;IACrB,aAAa,EAAE,MAAM,CAAA;IACrB,SAAS,EAAM,MAAM,CAAA;IACrB,WAAW,EAAI,MAAM,CAAA;CACtB,CAAC,CAgDD"}
1
+ {"version":3,"file":"purge.d.ts","sourceRoot":"","sources":["../../src/commands/purge.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC;IAC3C,YAAY,EAAG,MAAM,CAAA;IACrB,aAAa,EAAE,MAAM,CAAA;IACrB,SAAS,EAAM,MAAM,CAAA;IACrB,WAAW,EAAI,MAAM,CAAA;CACtB,CAAC,CA2BD"}
@@ -1,7 +1,12 @@
1
1
  import { Passport } from '../Passport.js';
2
2
  /**
3
3
  * Remove expired and revoked tokens from the database.
4
- * Returns counts of purged records.
4
+ *
5
+ * Each model is purged with a single bulk `deleteAll()` against the
6
+ * QueryBuilder — one round-trip per model regardless of row count. No
7
+ * hydration, no per-row delete calls, no observers (counter-style data plane).
8
+ *
9
+ * Returns the number of rows deleted per model.
5
10
  */
6
11
  export async function purgeTokens() {
7
12
  const now = new Date();
@@ -9,41 +14,20 @@ export async function purgeTokens() {
9
14
  const RefreshTokenCls = await Passport.refreshTokenModel();
10
15
  const AuthCodeCls = await Passport.authCodeModel();
11
16
  const DeviceCodeCls = await Passport.deviceCodeModel();
12
- // Purge expired/revoked access tokens
13
- const expiredAccess = await AccessTokenCls.query()
17
+ const accessTokens = await AccessTokenCls.query()
14
18
  .where('expiresAt', '<', now)
15
19
  .orWhere('revoked', true)
16
- .get();
17
- for (const t of expiredAccess) {
18
- await AccessTokenCls.delete(t.id);
19
- }
20
- // Purge expired/revoked refresh tokens
21
- const expiredRefresh = await RefreshTokenCls.query()
20
+ .deleteAll();
21
+ const refreshTokens = await RefreshTokenCls.query()
22
22
  .where('expiresAt', '<', now)
23
23
  .orWhere('revoked', true)
24
- .get();
25
- for (const t of expiredRefresh) {
26
- await RefreshTokenCls.delete(t.id);
27
- }
28
- // Purge expired auth codes
29
- const expiredCodes = await AuthCodeCls.query()
24
+ .deleteAll();
25
+ const authCodes = await AuthCodeCls.query()
30
26
  .where('expiresAt', '<', now)
31
- .get();
32
- for (const c of expiredCodes) {
33
- await AuthCodeCls.delete(c.id);
34
- }
35
- // Purge expired device codes
36
- const expiredDevices = await DeviceCodeCls.query()
27
+ .deleteAll();
28
+ const deviceCodes = await DeviceCodeCls.query()
37
29
  .where('expiresAt', '<', now)
38
- .get();
39
- for (const d of expiredDevices) {
40
- await DeviceCodeCls.delete(d.id);
41
- }
42
- return {
43
- accessTokens: expiredAccess.length,
44
- refreshTokens: expiredRefresh.length,
45
- authCodes: expiredCodes.length,
46
- deviceCodes: expiredDevices.length,
47
- };
30
+ .deleteAll();
31
+ return { accessTokens, refreshTokens, authCodes, deviceCodes };
48
32
  }
49
33
  //# sourceMappingURL=purge.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"purge.js","sourceRoot":"","sources":["../../src/commands/purge.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAMzC;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW;IAM/B,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAA;IAEtB,MAAM,cAAc,GAAI,MAAM,QAAQ,CAAC,UAAU,EAAE,CAAA;IACnD,MAAM,eAAe,GAAG,MAAM,QAAQ,CAAC,iBAAiB,EAAE,CAAA;IAC1D,MAAM,WAAW,GAAO,MAAM,QAAQ,CAAC,aAAa,EAAE,CAAA;IACtD,MAAM,aAAa,GAAK,MAAM,QAAQ,CAAC,eAAe,EAAE,CAAA;IAExD,sCAAsC;IACtC,MAAM,aAAa,GAAG,MAAM,cAAc,CAAC,KAAK,EAAE;SAC/C,KAAK,CAAC,WAAW,EAAE,GAAG,EAAE,GAAG,CAAC;SAC5B,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC;SACxB,GAAG,EAAmB,CAAA;IACzB,KAAK,MAAM,CAAC,IAAI,aAAa,EAAE,CAAC;QAC9B,MAAM,cAAc,CAAC,MAAM,CAAE,CAAS,CAAC,EAAY,CAAC,CAAA;IACtD,CAAC;IAED,uCAAuC;IACvC,MAAM,cAAc,GAAG,MAAM,eAAe,CAAC,KAAK,EAAE;SACjD,KAAK,CAAC,WAAW,EAAE,GAAG,EAAE,GAAG,CAAC;SAC5B,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC;SACxB,GAAG,EAAoB,CAAA;IAC1B,KAAK,MAAM,CAAC,IAAI,cAAc,EAAE,CAAC;QAC/B,MAAM,eAAe,CAAC,MAAM,CAAE,CAAS,CAAC,EAAY,CAAC,CAAA;IACvD,CAAC;IAED,2BAA2B;IAC3B,MAAM,YAAY,GAAG,MAAM,WAAW,CAAC,KAAK,EAAE;SAC3C,KAAK,CAAC,WAAW,EAAE,GAAG,EAAE,GAAG,CAAC;SAC5B,GAAG,EAAgB,CAAA;IACtB,KAAK,MAAM,CAAC,IAAI,YAAY,EAAE,CAAC;QAC7B,MAAM,WAAW,CAAC,MAAM,CAAE,CAAS,CAAC,EAAY,CAAC,CAAA;IACnD,CAAC;IAED,6BAA6B;IAC7B,MAAM,cAAc,GAAG,MAAM,aAAa,CAAC,KAAK,EAAE;SAC/C,KAAK,CAAC,WAAW,EAAE,GAAG,EAAE,GAAG,CAAC;SAC5B,GAAG,EAAkB,CAAA;IACxB,KAAK,MAAM,CAAC,IAAI,cAAc,EAAE,CAAC;QAC/B,MAAM,aAAa,CAAC,MAAM,CAAE,CAAS,CAAC,EAAY,CAAC,CAAA;IACrD,CAAC;IAED,OAAO;QACL,YAAY,EAAG,aAAa,CAAC,MAAM;QACnC,aAAa,EAAE,cAAc,CAAC,MAAM;QACpC,SAAS,EAAM,YAAY,CAAC,MAAM;QAClC,WAAW,EAAI,cAAc,CAAC,MAAM;KACrC,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"purge.js","sourceRoot":"","sources":["../../src/commands/purge.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAEzC;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW;IAM/B,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAA;IAEtB,MAAM,cAAc,GAAI,MAAM,QAAQ,CAAC,UAAU,EAAE,CAAA;IACnD,MAAM,eAAe,GAAG,MAAM,QAAQ,CAAC,iBAAiB,EAAE,CAAA;IAC1D,MAAM,WAAW,GAAO,MAAM,QAAQ,CAAC,aAAa,EAAE,CAAA;IACtD,MAAM,aAAa,GAAK,MAAM,QAAQ,CAAC,eAAe,EAAE,CAAA;IAExD,MAAM,YAAY,GAAG,MAAM,cAAc,CAAC,KAAK,EAAE;SAC9C,KAAK,CAAC,WAAW,EAAE,GAAG,EAAE,GAAG,CAAC;SAC5B,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC;SACxB,SAAS,EAAE,CAAA;IAEd,MAAM,aAAa,GAAG,MAAM,eAAe,CAAC,KAAK,EAAE;SAChD,KAAK,CAAC,WAAW,EAAE,GAAG,EAAE,GAAG,CAAC;SAC5B,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC;SACxB,SAAS,EAAE,CAAA;IAEd,MAAM,SAAS,GAAG,MAAM,WAAW,CAAC,KAAK,EAAE;SACxC,KAAK,CAAC,WAAW,EAAE,GAAG,EAAE,GAAG,CAAC;SAC5B,SAAS,EAAE,CAAA;IAEd,MAAM,WAAW,GAAG,MAAM,aAAa,CAAC,KAAK,EAAE;SAC5C,KAAK,CAAC,WAAW,EAAE,GAAG,EAAE,GAAG,CAAC;SAC5B,SAAS,EAAE,CAAA;IAEd,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,WAAW,EAAE,CAAA;AAChE,CAAC"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * SHA-256 hashing for device-flow secrets (`device_code` + `user_code`).
3
+ *
4
+ * Different threat model from client secrets — we don't pepper here:
5
+ *
6
+ * - `deviceCode` is `randomBytes(32).toString('hex')` (256 bits CSPRNG).
7
+ * `userCode` is 8 chars from a 32-symbol alphabet (~1.1×10^12 keyspace).
8
+ * Both are already unguessable per request.
9
+ * - The threat being mitigated is **DB read leak**: an attacker with
10
+ * `SELECT *` access on `oauth_device_codes` should not get usable codes
11
+ * that they can replay against `/oauth/token` or `/oauth/device/approve`.
12
+ * SHA-256 of the plaintext is sufficient — the attacker can't reverse it,
13
+ * and brute-force by guessing the input is no easier than guessing the
14
+ * original code without a DB leak at all.
15
+ * - Pepper would help against an offline attacker who learned a column hash
16
+ * AND could test guesses against an online endpoint. Device codes are
17
+ * TTL-limited (15 min) and the per-IP rate limit (#279 + the api-group
18
+ * default) prevents online brute force, so the pepper buys nothing.
19
+ *
20
+ * This is intentionally simpler than `client-secret.ts` (which DOES pepper
21
+ * via APP_KEY) — long-lived client secrets across multiple confidential
22
+ * clients have a different risk profile from short-lived per-flow codes.
23
+ *
24
+ * Lazy-loads `node:crypto` so the package stays importable from non-Node
25
+ * runtimes that never reach this code path.
26
+ */
27
+ export declare function hashDeviceSecret(plaintext: string): Promise<string>;
28
+ //# sourceMappingURL=device-code-secret.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device-code-secret.d.ts","sourceRoot":"","sources":["../src/device-code-secret.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAGzE"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * SHA-256 hashing for device-flow secrets (`device_code` + `user_code`).
3
+ *
4
+ * Different threat model from client secrets — we don't pepper here:
5
+ *
6
+ * - `deviceCode` is `randomBytes(32).toString('hex')` (256 bits CSPRNG).
7
+ * `userCode` is 8 chars from a 32-symbol alphabet (~1.1×10^12 keyspace).
8
+ * Both are already unguessable per request.
9
+ * - The threat being mitigated is **DB read leak**: an attacker with
10
+ * `SELECT *` access on `oauth_device_codes` should not get usable codes
11
+ * that they can replay against `/oauth/token` or `/oauth/device/approve`.
12
+ * SHA-256 of the plaintext is sufficient — the attacker can't reverse it,
13
+ * and brute-force by guessing the input is no easier than guessing the
14
+ * original code without a DB leak at all.
15
+ * - Pepper would help against an offline attacker who learned a column hash
16
+ * AND could test guesses against an online endpoint. Device codes are
17
+ * TTL-limited (15 min) and the per-IP rate limit (#279 + the api-group
18
+ * default) prevents online brute force, so the pepper buys nothing.
19
+ *
20
+ * This is intentionally simpler than `client-secret.ts` (which DOES pepper
21
+ * via APP_KEY) — long-lived client secrets across multiple confidential
22
+ * clients have a different risk profile from short-lived per-flow codes.
23
+ *
24
+ * Lazy-loads `node:crypto` so the package stays importable from non-Node
25
+ * runtimes that never reach this code path.
26
+ */
27
+ export async function hashDeviceSecret(plaintext) {
28
+ const { createHash } = await import('node:crypto');
29
+ return createHash('sha256').update(plaintext).digest('hex');
30
+ }
31
+ //# sourceMappingURL=device-code-secret.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device-code-secret.js","sourceRoot":"","sources":["../src/device-code-secret.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,SAAiB;IACtD,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAA;IAClD,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AAC7D,CAAC"}
@@ -46,6 +46,29 @@ export interface TokenExchangeRequest {
46
46
  * Exchange an authorization code for access + refresh tokens.
47
47
  */
48
48
  export declare function exchangeAuthCode(params: TokenExchangeRequest): Promise<IssuedTokens>;
49
+ /**
50
+ * Validate requested scopes against two gates and throw `invalid_scope` per
51
+ * RFC 6749 §3.3 if any requested scope fails either:
52
+ *
53
+ * 1. **Global registry** — declared via `Passport.tokensCan({...})`.
54
+ * Rejects scopes the operator hasn't acknowledged exist.
55
+ * 2. **Per-client allow-list** — `client.scopes`. Rejects scopes outside
56
+ * the operator-configured subset for this specific client.
57
+ *
58
+ * Each gate is **only enforced when populated**:
59
+ * - Empty global registry → no global gate (treated as "scopes not yet
60
+ * declared"). Matches Laravel Passport's "no scopes defined → permissive"
61
+ * default; existing apps that haven't called `tokensCan()` won't break.
62
+ * - Empty `client.scopes` → no per-client gate ("client may request any
63
+ * globally-known scope"). The vast majority of clients leave this empty.
64
+ *
65
+ * Used by the auth-code, device-code, and client-credentials grants. Refresh
66
+ * token already has its own narrowing logic (can only narrow vs. the original
67
+ * issuance, never widen) and skips this helper.
68
+ *
69
+ * The `*` wildcard is always allowed — same convention as `Passport.validScopes()`.
70
+ */
71
+ export declare function validateScopes(client: OAuthClient, requested: string[]): void;
49
72
  export declare class OAuthError extends Error {
50
73
  readonly error: string;
51
74
  readonly errorDescription: string;
@@ -1 +1 @@
1
- {"version":3,"file":"authorization-code.d.ts","sourceRoot":"","sources":["../../src/grants/authorization-code.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAG3D,OAAO,EAAe,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAIlE,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAO,MAAM,CAAA;IACrB,WAAW,EAAI,MAAM,CAAA;IACrB,YAAY,EAAG,MAAM,CAAA;IACrB,KAAK,EAAU,MAAM,CAAA;IACrB,KAAK,CAAC,EAAS,MAAM,CAAA;IACrB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,mBAAmB,CAAC,EAAE,MAAM,CAAA;CAC7B;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAS,WAAW,CAAA;IAC1B,WAAW,EAAI,MAAM,CAAA;IACrB,MAAM,EAAS,MAAM,EAAE,CAAA;IACvB,KAAK,CAAC,EAAS,MAAM,CAAA;IACrB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,mBAAmB,CAAC,EAAE,MAAM,CAAA;CAC7B;AAED;;;GAGG;AACH,wBAAsB,4BAA4B,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CA0C9G;AAID;;;GAGG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE;IACxC,MAAM,EAAK,MAAM,CAAA;IACjB,QAAQ,EAAG,MAAM,CAAA;IACjB,MAAM,EAAK,MAAM,EAAE,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,mBAAmB,CAAC,EAAE,MAAM,CAAA;CAC7B,GAAG,OAAO,CAAC,MAAM,CAAC,CAelB;AAID,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAK,MAAM,CAAA;IACpB,IAAI,EAAU,MAAM,CAAA;IACpB,QAAQ,EAAM,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,WAAW,EAAG,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,YAAY,CAAC,CA0E1F;AAID,qBAAa,UAAW,SAAQ,KAAK;aAEjB,KAAK,EAAE,MAAM;aACb,gBAAgB,EAAE,MAAM;aACxB,UAAU,EAAE,MAAM;gBAFlB,KAAK,EAAE,MAAM,EACb,gBAAgB,EAAE,MAAM,EACxB,UAAU,GAAE,MAAY;IAM1C,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;CAMjC"}
1
+ {"version":3,"file":"authorization-code.d.ts","sourceRoot":"","sources":["../../src/grants/authorization-code.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAM3D,OAAO,EAAe,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAIlE,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAO,MAAM,CAAA;IACrB,WAAW,EAAI,MAAM,CAAA;IACrB,YAAY,EAAG,MAAM,CAAA;IACrB,KAAK,EAAU,MAAM,CAAA;IACrB,KAAK,CAAC,EAAS,MAAM,CAAA;IACrB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,mBAAmB,CAAC,EAAE,MAAM,CAAA;CAC7B;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAS,WAAW,CAAA;IAC1B,WAAW,EAAI,MAAM,CAAA;IACrB,MAAM,EAAS,MAAM,EAAE,CAAA;IACvB,KAAK,CAAC,EAAS,MAAM,CAAA;IACrB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,mBAAmB,CAAC,EAAE,MAAM,CAAA;CAC7B;AAED;;;GAGG;AACH,wBAAsB,4BAA4B,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAoD9G;AAID;;;GAGG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE;IACxC,MAAM,EAAK,MAAM,CAAA;IACjB,QAAQ,EAAG,MAAM,CAAA;IACjB,MAAM,EAAK,MAAM,EAAE,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,mBAAmB,CAAC,EAAE,MAAM,CAAA;CAC7B,GAAG,OAAO,CAAC,MAAM,CAAC,CAwBlB;AAID,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAK,MAAM,CAAA;IACpB,IAAI,EAAU,MAAM,CAAA;IACpB,QAAQ,EAAM,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,WAAW,EAAG,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,YAAY,CAAC,CA0H1F;AAID;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,IAAI,CA0B7E;AAID,qBAAa,UAAW,SAAQ,KAAK;aAEjB,KAAK,EAAE,MAAM;aACb,gBAAgB,EAAE,MAAM;aACxB,UAAU,EAAE,MAAM;gBAFlB,KAAK,EAAE,MAAM,EACb,gBAAgB,EAAE,MAAM,EACxB,UAAU,GAAE,MAAY;IAM1C,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;CAMjC"}
@@ -1,5 +1,8 @@
1
1
  import { Passport } from '../Passport.js';
2
2
  import { clientHelpers, authCodeHelpers } from '../models/helpers.js';
3
+ import { safeCompare } from './safe-compare.js';
4
+ import { verifyClientSecret } from '../client-secret.js';
5
+ import { hashOpaqueToken, newOpaqueToken } from '../opaque-token.js';
3
6
  import { issueTokens } from './issue-tokens.js';
4
7
  /**
5
8
  * Validate an authorization request (GET /oauth/authorize).
@@ -22,15 +25,25 @@ export async function validateAuthorizationRequest(params) {
22
25
  }
23
26
  // PKCE validation
24
27
  if (params.codeChallenge) {
25
- if (params.codeChallengeMethod && params.codeChallengeMethod !== 'S256' && params.codeChallengeMethod !== 'plain') {
28
+ const method = params.codeChallengeMethod ?? 'S256';
29
+ if (method !== 'S256' && method !== 'plain') {
26
30
  throw new OAuthError('invalid_request', 'Unsupported code_challenge_method. Use S256 or plain.');
27
31
  }
32
+ // Public clients must use S256. RFC 7636 §4.4.1 + OAuth 2.0 BCP recommend
33
+ // S256 over `plain` because `plain` makes verifier == challenge — a stolen
34
+ // authorization code is already enough to mint tokens, defeating PKCE's
35
+ // entire purpose. Confidential clients keep the `plain` option for
36
+ // backward-compat with non-RFC-7636-compliant integrations.
37
+ if (method === 'plain' && clientHelpers.isPublic(client)) {
38
+ throw new OAuthError('invalid_request', 'Public clients must use code_challenge_method=S256.');
39
+ }
28
40
  }
29
41
  else if (clientHelpers.isPublic(client)) {
30
42
  // Public clients MUST use PKCE
31
43
  throw new OAuthError('invalid_request', 'Public clients must use PKCE (code_challenge required).');
32
44
  }
33
45
  const scopes = params.scope ? params.scope.split(' ').filter(Boolean) : [];
46
+ validateScopes(client, scopes);
34
47
  const result = {
35
48
  client,
36
49
  redirectUri: params.redirectUri,
@@ -52,17 +65,25 @@ export async function validateAuthorizationRequest(params) {
52
65
  */
53
66
  export async function issueAuthCode(opts) {
54
67
  const expiresAt = new Date(Date.now() + 10 * 60 * 1000); // 10 minutes
68
+ // M5/P6: the plaintext returned to the redirect URI is freshly generated
69
+ // CSPRNG hex; only its SHA-256 is persisted. The previous shape returned
70
+ // the row's cuid `id` directly, so a DB read leak handed every in-flight
71
+ // auth code to anyone with `SELECT * ON oauth_auth_codes` privilege.
72
+ const codePlaintext = await newOpaqueToken();
73
+ const codeHash = await hashOpaqueToken(codePlaintext);
55
74
  const AuthCodeCls = await Passport.authCodeModel();
56
- const code = await AuthCodeCls.create({
75
+ await AuthCodeCls.create({
57
76
  userId: opts.userId,
58
77
  clientId: opts.clientId,
78
+ tokenHash: codeHash,
59
79
  scopes: JSON.stringify(opts.scopes),
60
80
  revoked: false,
61
81
  expiresAt,
82
+ redirectUri: opts.redirectUri,
62
83
  codeChallenge: opts.codeChallenge ?? null,
63
84
  codeChallengeMethod: opts.codeChallengeMethod ?? null,
64
85
  });
65
- return code.id;
86
+ return codePlaintext;
66
87
  }
67
88
  /**
68
89
  * Exchange an authorization code for access + refresh tokens.
@@ -73,24 +94,36 @@ export async function exchangeAuthCode(params) {
73
94
  }
74
95
  const ClientCls = await Passport.clientModel();
75
96
  const AuthCodeCls = await Passport.authCodeModel();
76
- // Validate client
97
+ // Validate client. RFC 6749 §5.2 — client authentication failures at
98
+ // the token endpoint MUST return HTTP 401 with a `WWW-Authenticate`
99
+ // header (the latter is set in routes.ts on 401 responses). The
100
+ // refresh-token and client-credentials grants already return 401 here
101
+ // — auth-code was the inconsistent outlier.
77
102
  const client = await ClientCls.where('id', params.clientId).first();
78
103
  if (!client || client.revoked) {
79
- throw new OAuthError('invalid_client', 'Client not found.');
104
+ throw new OAuthError('invalid_client', 'Client not found.', 401);
80
105
  }
81
106
  // Confidential clients must provide a valid secret
82
107
  if (client.confidential) {
83
108
  if (!params.clientSecret) {
84
- throw new OAuthError('invalid_client', 'Client secret required.');
109
+ throw new OAuthError('invalid_client', 'Client secret required.', 401);
85
110
  }
86
- const { createHash } = await import('node:crypto');
87
- const hashed = createHash('sha256').update(params.clientSecret).digest('hex');
88
- if (hashed !== client.secret) {
89
- throw new OAuthError('invalid_client', 'Invalid client secret.');
111
+ // Schema allows `client.secret` to be null; explicit guard so a future
112
+ // refactor can't mask `secret = null` as authenticating. See
113
+ // `client-credentials.ts` for the longer-form rationale.
114
+ if (client.secret == null) {
115
+ throw new OAuthError('invalid_client', 'Confidential client has no secret on file.', 401);
116
+ }
117
+ if (!(await verifyClientSecret(params.clientSecret, client.secret))) {
118
+ throw new OAuthError('invalid_client', 'Invalid client secret.', 401);
90
119
  }
91
120
  }
92
- // Validate auth code
93
- const authCode = await AuthCodeCls.where('id', params.code).first();
121
+ // Validate auth code by hashed plaintext (M5/P6) — the row's `id` is no
122
+ // longer the bearer secret. Pre-migration codes won't match because their
123
+ // hashed form was never persisted; affected exchanges fall through to the
124
+ // 10-minute TTL drain window and the user simply re-clicks "Authorize".
125
+ const codeHash = await hashOpaqueToken(params.code);
126
+ const authCode = await AuthCodeCls.where('tokenHash', codeHash).first();
94
127
  if (!authCode) {
95
128
  throw new OAuthError('invalid_grant', 'Authorization code not found.');
96
129
  }
@@ -103,6 +136,20 @@ export async function exchangeAuthCode(params) {
103
136
  if (authCode.clientId !== params.clientId) {
104
137
  throw new OAuthError('invalid_grant', 'Authorization code was not issued to this client.');
105
138
  }
139
+ // RFC 6749 §4.1.3 — if a redirect_uri was bound at issuance, the exchange
140
+ // MUST present an identical value. Without this binding, an auth code
141
+ // obtained through one approved redirect can be exchanged via any other
142
+ // redirect registered to the same client, breaking the OAuth threat model.
143
+ // `redirectUri` is null only for codes issued before this column existed
144
+ // (≤10-minute legacy compat window after the migration lands).
145
+ if (authCode.redirectUri !== null && authCode.redirectUri !== undefined) {
146
+ if (!params.redirectUri) {
147
+ throw new OAuthError('invalid_grant', 'redirect_uri is required for this authorization code.');
148
+ }
149
+ if (authCode.redirectUri !== params.redirectUri) {
150
+ throw new OAuthError('invalid_grant', 'redirect_uri does not match the value used at authorization time.');
151
+ }
152
+ }
106
153
  // PKCE verification
107
154
  if (authCode.codeChallenge) {
108
155
  if (!params.codeVerifier) {
@@ -119,12 +166,33 @@ export async function exchangeAuthCode(params) {
119
166
  // plain
120
167
  expected = params.codeVerifier;
121
168
  }
122
- if (expected !== authCode.codeChallenge) {
169
+ // Constant-time compare; both sides are equal-length encodings (S256 →
170
+ // base64url SHA-256, plain → identity). On mismatch the helper short-
171
+ // circuits the length check first, but the equal-length common path
172
+ // runs the full timingSafeEqual.
173
+ if (!(await safeCompare(expected, authCode.codeChallenge))) {
123
174
  throw new OAuthError('invalid_grant', 'PKCE code_verifier does not match.');
124
175
  }
125
176
  }
126
- // Revoke the auth code (single-use)
127
- await AuthCodeCls.update(authCode.id, { revoked: true });
177
+ // Atomically consume the auth code (M3). RFC 6749 §4.1.2 requires
178
+ // single-use codes. Without a conditional update, two concurrent
179
+ // exchanges of the same code can BOTH read `revoked=false`, BOTH pass
180
+ // PKCE / redirect_uri / client checks, and BOTH issue tokens. The
181
+ // unconditional update used previously was idempotent at the SQL level,
182
+ // so the second writer didn't see any error. We use a conditional
183
+ // `where('revoked', false).updateAll(...)` instead — the underlying
184
+ // `UPDATE ... WHERE revoked = false` is atomic in every SQL backend, so
185
+ // exactly one caller observes `count === 1`; the rest see `count === 0`
186
+ // and surface `invalid_grant`. (Tokens already minted from a prior
187
+ // successful exchange of the same code are NOT retroactively revoked
188
+ // here — that's a separate hardening, RFC §4.1.2's SHOULD clause.)
189
+ const consumed = await AuthCodeCls
190
+ .where('id', authCode.id)
191
+ .where('revoked', false)
192
+ .updateAll({ revoked: true });
193
+ if (consumed === 0) {
194
+ throw new OAuthError('invalid_grant', 'Authorization code has already been used.');
195
+ }
128
196
  // Issue tokens
129
197
  return issueTokens({
130
198
  userId: authCode.userId,
@@ -133,6 +201,49 @@ export async function exchangeAuthCode(params) {
133
201
  includeRefresh: true,
134
202
  });
135
203
  }
204
+ // ─── Scope validation ─────────────────────────────────────
205
+ /**
206
+ * Validate requested scopes against two gates and throw `invalid_scope` per
207
+ * RFC 6749 §3.3 if any requested scope fails either:
208
+ *
209
+ * 1. **Global registry** — declared via `Passport.tokensCan({...})`.
210
+ * Rejects scopes the operator hasn't acknowledged exist.
211
+ * 2. **Per-client allow-list** — `client.scopes`. Rejects scopes outside
212
+ * the operator-configured subset for this specific client.
213
+ *
214
+ * Each gate is **only enforced when populated**:
215
+ * - Empty global registry → no global gate (treated as "scopes not yet
216
+ * declared"). Matches Laravel Passport's "no scopes defined → permissive"
217
+ * default; existing apps that haven't called `tokensCan()` won't break.
218
+ * - Empty `client.scopes` → no per-client gate ("client may request any
219
+ * globally-known scope"). The vast majority of clients leave this empty.
220
+ *
221
+ * Used by the auth-code, device-code, and client-credentials grants. Refresh
222
+ * token already has its own narrowing logic (can only narrow vs. the original
223
+ * issuance, never widen) and skips this helper.
224
+ *
225
+ * The `*` wildcard is always allowed — same convention as `Passport.validScopes()`.
226
+ */
227
+ export function validateScopes(client, requested) {
228
+ if (requested.length === 0)
229
+ return;
230
+ const registered = Passport.scopes();
231
+ if (registered.length > 0) {
232
+ const validIds = new Set(registered.map(s => s.id));
233
+ const unknown = requested.filter(s => s !== '*' && !validIds.has(s));
234
+ if (unknown.length > 0) {
235
+ throw new OAuthError('invalid_scope', `The requested scope is invalid, unknown, or malformed: ${unknown.join(' ')}.`);
236
+ }
237
+ }
238
+ const clientScopes = clientHelpers.getScopes(client);
239
+ if (clientScopes.length > 0) {
240
+ const allow = new Set(clientScopes);
241
+ const denied = requested.filter(s => s !== '*' && !allow.has(s));
242
+ if (denied.length > 0) {
243
+ throw new OAuthError('invalid_scope', `The requested scope is not authorized for this client: ${denied.join(' ')}.`);
244
+ }
245
+ }
246
+ }
136
247
  // ─── OAuth Error ──────────────────────────────────────────
137
248
  export class OAuthError extends Error {
138
249
  error;
@@ -1 +1 @@
1
- {"version":3,"file":"authorization-code.js","sourceRoot":"","sources":["../../src/grants/authorization-code.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"authorization-code.js","sourceRoot":"","sources":["../../src/grants/authorization-code.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"client-credentials.d.ts","sourceRoot":"","sources":["../../src/grants/client-credentials.ts"],"names":[],"mappings":"AAGA,OAAO,EAAe,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAGlE,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAK,MAAM,CAAA;IACpB,QAAQ,EAAM,MAAM,CAAA;IACpB,YAAY,EAAE,MAAM,CAAA;IACpB,KAAK,CAAC,EAAQ,MAAM,CAAA;CACrB;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAAC,MAAM,EAAE,wBAAwB,GAAG,OAAO,CAAC,YAAY,CAAC,CAkCpG"}
1
+ {"version":3,"file":"client-credentials.d.ts","sourceRoot":"","sources":["../../src/grants/client-credentials.ts"],"names":[],"mappings":"AAIA,OAAO,EAAe,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAGlE,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAK,MAAM,CAAA;IACpB,QAAQ,EAAM,MAAM,CAAA;IACpB,YAAY,EAAE,MAAM,CAAA;IACpB,KAAK,CAAC,EAAQ,MAAM,CAAA;CACrB;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAAC,MAAM,EAAE,wBAAwB,GAAG,OAAO,CAAC,YAAY,CAAC,CA0CpG"}
@@ -1,7 +1,8 @@
1
1
  import { Passport } from '../Passport.js';
2
2
  import { clientHelpers } from '../models/helpers.js';
3
+ import { verifyClientSecret } from '../client-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';
5
6
  /**
6
7
  * Client credentials grant — machine-to-machine, no user context.
7
8
  * Issues an access token (no refresh token).
@@ -21,13 +22,20 @@ export async function clientCredentialsGrant(params) {
21
22
  if (!client.confidential) {
22
23
  throw new OAuthError('invalid_client', 'Client credentials grant requires a confidential client.');
23
24
  }
24
- // Verify secret
25
- const { createHash } = await import('node:crypto');
26
- const hashed = createHash('sha256').update(params.clientSecret).digest('hex');
27
- if (hashed !== client.secret) {
25
+ // Schema allows `client.secret` to be null (public clients), but reaching
26
+ // this branch with a confidential client should always have a hashed secret
27
+ // on file. Catching the null case explicitly prevents a future refactor
28
+ // from masking `secret = null` as authenticating against
29
+ // `verifyClientSecret(_, null)` (which fails closed today, but the guard
30
+ // makes the contract obvious to readers and hardens against drift).
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))) {
28
35
  throw new OAuthError('invalid_client', 'Invalid client secret.', 401);
29
36
  }
30
37
  const scopes = params.scope ? params.scope.split(' ').filter(Boolean) : [];
38
+ validateScopes(client, scopes);
31
39
  return issueTokens({
32
40
  userId: null, // no user context
33
41
  clientId: params.clientId,
@@ -1 +1 @@
1
- {"version":3,"file":"client-credentials.js","sourceRoot":"","sources":["../../src/grants/client-credentials.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAEzC,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AACpD,OAAO,EAAE,WAAW,EAAqB,MAAM,mBAAmB,CAAA;AAClE,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAA;AASpD;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,MAAgC;IAC3E,IAAI,MAAM,CAAC,SAAS,KAAK,oBAAoB,EAAE,CAAC;QAC9C,MAAM,IAAI,UAAU,CAAC,wBAAwB,EAAE,yCAAyC,CAAC,CAAA;IAC3F,CAAC;IAED,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAA;IAC9C,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAwB,CAAA;IACzF,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QAC9B,MAAM,IAAI,UAAU,CAAC,gBAAgB,EAAE,mBAAmB,EAAE,GAAG,CAAC,CAAA;IAClE,CAAC;IAED,IAAI,CAAC,aAAa,CAAC,YAAY,CAAC,MAAa,EAAE,oBAAoB,CAAC,EAAE,CAAC;QACrE,MAAM,IAAI,UAAU,CAAC,qBAAqB,EAAE,wDAAwD,CAAC,CAAA;IACvG,CAAC;IAED,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC;QACzB,MAAM,IAAI,UAAU,CAAC,gBAAgB,EAAE,0DAA0D,CAAC,CAAA;IACpG,CAAC;IAED,gBAAgB;IAChB,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAA;IAClD,MAAM,MAAM,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IAC7E,IAAI,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,CAAC;QAC7B,MAAM,IAAI,UAAU,CAAC,gBAAgB,EAAE,wBAAwB,EAAE,GAAG,CAAC,CAAA;IACvE,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;IAE1E,OAAO,WAAW,CAAC;QACjB,MAAM,EAAU,IAAI,EAAE,kBAAkB;QACxC,QAAQ,EAAQ,MAAM,CAAC,QAAQ;QAC/B,MAAM;QACN,cAAc,EAAE,KAAK,EAAE,8CAA8C;KACtE,CAAC,CAAA;AACJ,CAAC"}
1
+ {"version":3,"file":"client-credentials.js","sourceRoot":"","sources":["../../src/grants/client-credentials.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAEzC,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AACxD,OAAO,EAAE,WAAW,EAAqB,MAAM,mBAAmB,CAAA;AAClE,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAA;AASpE;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,MAAgC;IAC3E,IAAI,MAAM,CAAC,SAAS,KAAK,oBAAoB,EAAE,CAAC;QAC9C,MAAM,IAAI,UAAU,CAAC,wBAAwB,EAAE,yCAAyC,CAAC,CAAA;IAC3F,CAAC;IAED,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAA;IAC9C,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAwB,CAAA;IACzF,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QAC9B,MAAM,IAAI,UAAU,CAAC,gBAAgB,EAAE,mBAAmB,EAAE,GAAG,CAAC,CAAA;IAClE,CAAC;IAED,IAAI,CAAC,aAAa,CAAC,YAAY,CAAC,MAAa,EAAE,oBAAoB,CAAC,EAAE,CAAC;QACrE,MAAM,IAAI,UAAU,CAAC,qBAAqB,EAAE,wDAAwD,CAAC,CAAA;IACvG,CAAC;IAED,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC;QACzB,MAAM,IAAI,UAAU,CAAC,gBAAgB,EAAE,0DAA0D,CAAC,CAAA;IACpG,CAAC;IAED,0EAA0E;IAC1E,4EAA4E;IAC5E,wEAAwE;IACxE,yDAAyD;IACzD,yEAAyE;IACzE,oEAAoE;IACpE,IAAI,MAAM,CAAC,MAAM,IAAI,IAAI,EAAE,CAAC;QAC1B,MAAM,IAAI,UAAU,CAAC,gBAAgB,EAAE,4CAA4C,EAAE,GAAG,CAAC,CAAA;IAC3F,CAAC;IAED,IAAI,CAAC,CAAC,MAAM,kBAAkB,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC;QACpE,MAAM,IAAI,UAAU,CAAC,gBAAgB,EAAE,wBAAwB,EAAE,GAAG,CAAC,CAAA;IACvE,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;IAC1E,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAE9B,OAAO,WAAW,CAAC;QACjB,MAAM,EAAU,IAAI,EAAE,kBAAkB;QACxC,QAAQ,EAAQ,MAAM,CAAC,QAAQ;QAC/B,MAAM;QACN,cAAc,EAAE,KAAK,EAAE,8CAA8C;KACtE,CAAC,CAAA;AACJ,CAAC"}
@@ -25,8 +25,17 @@ export type DevicePollResult = {
25
25
  tokens: IssuedTokens;
26
26
  } | {
27
27
  status: 'authorization_pending';
28
- } | {
28
+ }
29
+ /**
30
+ * RFC 8628 §3.5: when the device polls faster than the current interval,
31
+ * the server returns `slow_down` AND increments the required interval by
32
+ * 5 seconds (capped at 60). The new interval is included in the result so
33
+ * the route handler can forward it on the wire — well-behaved clients
34
+ * use it directly instead of guessing at the new value.
35
+ */
36
+ | {
29
37
  status: 'slow_down';
38
+ interval: number;
30
39
  } | {
31
40
  status: 'access_denied';
32
41
  } | {
@@ -1 +1 @@
1
- {"version":3,"file":"device-code.d.ts","sourceRoot":"","sources":["../../src/grants/device-code.ts"],"names":[],"mappings":"AAIA,OAAO,EAAe,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAKlE,MAAM,WAAW,2BAA2B;IAC1C,WAAW,EAAiB,MAAM,CAAA;IAClC,SAAS,EAAmB,MAAM,CAAA;IAClC,gBAAgB,EAAY,MAAM,CAAA;IAClC,yBAAyB,CAAC,EAAE,MAAM,CAAA;IAClC,UAAU,EAAkB,MAAM,CAAA;IAClC,QAAQ,EAAoB,MAAM,CAAA;CACnC;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,EAAE;IAC9C,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAI,MAAM,CAAA;IAChB,eAAe,EAAE,MAAM,CAAA;CACxB,GAAG,OAAO,CAAC,2BAA2B,CAAC,CAsCvC;AAID;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAiB1G;AAID,MAAM,MAAM,gBAAgB,GACxB;IAAE,MAAM,EAAE,YAAY,CAAC;IAAC,MAAM,EAAE,YAAY,CAAA;CAAE,GAC9C;IAAE,MAAM,EAAE,uBAAuB,CAAA;CAAE,GACnC;IAAE,MAAM,EAAE,WAAW,CAAA;CAAE,GACvB;IAAE,MAAM,EAAE,eAAe,CAAA;CAAE,GAC3B;IAAE,MAAM,EAAE,eAAe,CAAA;CAAE,CAAA;AAE/B;;GAEG;AACH,wBAAsB,cAAc,CAAC,MAAM,EAAE;IAC3C,SAAS,EAAG,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAI,MAAM,CAAA;CACnB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAkD5B"}
1
+ {"version":3,"file":"device-code.d.ts","sourceRoot":"","sources":["../../src/grants/device-code.ts"],"names":[],"mappings":"AAKA,OAAO,EAAe,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAelE,MAAM,WAAW,2BAA2B;IAC1C,WAAW,EAAiB,MAAM,CAAA;IAClC,SAAS,EAAmB,MAAM,CAAA;IAClC,gBAAgB,EAAY,MAAM,CAAA;IAClC,yBAAyB,CAAC,EAAE,MAAM,CAAA;IAClC,UAAU,EAAkB,MAAM,CAAA;IAClC,QAAQ,EAAoB,MAAM,CAAA;CACnC;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,EAAE;IAC9C,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAI,MAAM,CAAA;IAChB,eAAe,EAAE,MAAM,CAAA;CACxB,GAAG,OAAO,CAAC,2BAA2B,CAAC,CAiDvC;AAID;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAqB1G;AAID,MAAM,MAAM,gBAAgB,GACxB;IAAE,MAAM,EAAE,YAAY,CAAC;IAAC,MAAM,EAAE,YAAY,CAAA;CAAE,GAC9C;IAAE,MAAM,EAAE,uBAAuB,CAAA;CAAE;AACrC;;;;;;GAMG;GACD;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GACzC;IAAE,MAAM,EAAE,eAAe,CAAA;CAAE,GAC3B;IAAE,MAAM,EAAE,eAAe,CAAA;CAAE,CAAA;AAE/B;;GAEG;AACH,wBAAsB,cAAc,CAAC,MAAM,EAAE;IAC3C,SAAS,EAAG,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAI,MAAM,CAAA;CACnB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA0D5B"}