@atproto/oauth-provider 0.8.1 → 0.9.1

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 (77) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/dist/client/client-auth.d.ts +48 -3
  3. package/dist/client/client-auth.d.ts.map +1 -1
  4. package/dist/client/client-auth.js +0 -31
  5. package/dist/client/client-auth.js.map +1 -1
  6. package/dist/client/client-manager.d.ts.map +1 -1
  7. package/dist/client/client-manager.js +19 -34
  8. package/dist/client/client-manager.js.map +1 -1
  9. package/dist/client/client.d.ts +14 -17
  10. package/dist/client/client.d.ts.map +1 -1
  11. package/dist/client/client.js +115 -73
  12. package/dist/client/client.js.map +1 -1
  13. package/dist/constants.d.ts +9 -8
  14. package/dist/constants.d.ts.map +1 -1
  15. package/dist/constants.js +10 -9
  16. package/dist/constants.js.map +1 -1
  17. package/dist/metadata/build-metadata.js +1 -1
  18. package/dist/metadata/build-metadata.js.map +1 -1
  19. package/dist/oauth-provider.d.ts +20 -16
  20. package/dist/oauth-provider.d.ts.map +1 -1
  21. package/dist/oauth-provider.js +268 -122
  22. package/dist/oauth-provider.js.map +1 -1
  23. package/dist/replay/replay-manager.d.ts +1 -1
  24. package/dist/replay/replay-manager.d.ts.map +1 -1
  25. package/dist/replay/replay-manager.js +5 -2
  26. package/dist/replay/replay-manager.js.map +1 -1
  27. package/dist/request/request-data.d.ts +3 -2
  28. package/dist/request/request-data.d.ts.map +1 -1
  29. package/dist/request/request-data.js.map +1 -1
  30. package/dist/request/request-info.d.ts +1 -1
  31. package/dist/request/request-info.d.ts.map +1 -1
  32. package/dist/request/request-manager.d.ts +73 -9
  33. package/dist/request/request-manager.d.ts.map +1 -1
  34. package/dist/request/request-manager.js +20 -61
  35. package/dist/request/request-manager.js.map +1 -1
  36. package/dist/request/request-store.d.ts +6 -2
  37. package/dist/request/request-store.d.ts.map +1 -1
  38. package/dist/request/request-store.js +6 -6
  39. package/dist/request/request-store.js.map +1 -1
  40. package/dist/router/create-api-middleware.js +1 -1
  41. package/dist/router/create-api-middleware.js.map +1 -1
  42. package/dist/router/create-oauth-middleware.d.ts.map +1 -1
  43. package/dist/router/create-oauth-middleware.js +2 -1
  44. package/dist/router/create-oauth-middleware.js.map +1 -1
  45. package/dist/token/token-data.d.ts +2 -2
  46. package/dist/token/token-data.d.ts.map +1 -1
  47. package/dist/token/token-manager.d.ts +10 -10
  48. package/dist/token/token-manager.d.ts.map +1 -1
  49. package/dist/token/token-manager.js +64 -201
  50. package/dist/token/token-manager.js.map +1 -1
  51. package/package.json +7 -7
  52. package/src/client/client-auth.ts +52 -33
  53. package/src/client/client-manager.ts +26 -46
  54. package/src/client/client.ts +153 -89
  55. package/src/constants.ts +10 -8
  56. package/src/metadata/build-metadata.ts +2 -2
  57. package/src/oauth-provider.ts +391 -191
  58. package/src/replay/replay-manager.ts +10 -6
  59. package/src/request/request-data.ts +12 -2
  60. package/src/request/request-info.ts +1 -1
  61. package/src/request/request-manager.ts +25 -85
  62. package/src/request/request-store.ts +11 -8
  63. package/src/router/create-api-middleware.ts +1 -1
  64. package/src/router/create-oauth-middleware.ts +7 -1
  65. package/src/token/token-data.ts +2 -2
  66. package/src/token/token-manager.ts +112 -312
  67. package/tsconfig.build.tsbuildinfo +1 -1
  68. package/dist/request/request-store-memory.d.ts +0 -16
  69. package/dist/request/request-store-memory.d.ts.map +0 -1
  70. package/dist/request/request-store-memory.js +0 -31
  71. package/dist/request/request-store-memory.js.map +0 -1
  72. package/dist/request/request-store-redis.d.ts +0 -24
  73. package/dist/request/request-store-redis.d.ts.map +0 -1
  74. package/dist/request/request-store-redis.js +0 -58
  75. package/dist/request/request-store-redis.js.map +0 -1
  76. package/src/request/request-store-memory.ts +0 -39
  77. package/src/request/request-store-redis.ts +0 -71
@@ -1,30 +1,16 @@
1
- import { createHash } from 'node:crypto'
2
1
  import { SignedJwt, isSignedJwt } from '@atproto/jwk'
3
2
  import type { Account } from '@atproto/oauth-provider-api'
4
3
  import {
5
- CLIENT_ASSERTION_TYPE_JWT_BEARER,
6
4
  OAuthAccessToken,
7
- OAuthAuthorizationCodeGrantTokenRequest,
8
5
  OAuthAuthorizationRequestParameters,
9
- OAuthClientCredentialsGrantTokenRequest,
10
- OAuthPasswordGrantTokenRequest,
11
- OAuthRefreshTokenGrantTokenRequest,
12
6
  OAuthTokenResponse,
13
7
  OAuthTokenType,
14
8
  } from '@atproto/oauth-types'
15
9
  import { AccessTokenMode } from '../access-token/access-token-mode.js'
16
10
  import { ClientAuth } from '../client/client-auth.js'
17
11
  import { Client } from '../client/client.js'
18
- import {
19
- AUTHENTICATED_REFRESH_INACTIVITY_TIMEOUT,
20
- AUTHENTICATED_REFRESH_LIFETIME,
21
- TOKEN_MAX_AGE,
22
- UNAUTHENTICATED_REFRESH_INACTIVITY_TIMEOUT,
23
- UNAUTHENTICATED_REFRESH_LIFETIME,
24
- } from '../constants.js'
12
+ import { TOKEN_MAX_AGE } from '../constants.js'
25
13
  import { DeviceId } from '../device/device-id.js'
26
- import { InvalidDpopKeyBindingError } from '../errors/invalid-dpop-key-binding-error.js'
27
- import { InvalidDpopProofError } from '../errors/invalid-dpop-proof-error.js'
28
14
  import { InvalidGrantError } from '../errors/invalid-grant-error.js'
29
15
  import { InvalidRequestError } from '../errors/invalid-request-error.js'
30
16
  import { InvalidTokenError } from '../errors/invalid-token-error.js'
@@ -41,7 +27,6 @@ import {
41
27
  RefreshToken,
42
28
  generateRefreshToken,
43
29
  isRefreshToken,
44
- refreshTokenSchema,
45
30
  } from './refresh-token.js'
46
31
  import { TokenData } from './token-data.js'
47
32
  import { TokenId, generateTokenId, isTokenId } from './token-id.js'
@@ -94,125 +79,16 @@ export class TokenManager {
94
79
  })
95
80
  }
96
81
 
97
- async create(
82
+ async createToken(
98
83
  client: Client,
99
84
  clientAuth: ClientAuth,
100
85
  clientMetadata: RequestMetadata,
101
86
  account: Account,
102
87
  deviceId: null | DeviceId,
103
88
  parameters: OAuthAuthorizationRequestParameters,
104
- input:
105
- | OAuthAuthorizationCodeGrantTokenRequest
106
- | OAuthClientCredentialsGrantTokenRequest
107
- | OAuthPasswordGrantTokenRequest,
108
- dpopProof: null | DpopProof,
89
+ code: Code,
109
90
  ): Promise<OAuthTokenResponse> {
110
- // @NOTE the atproto specific DPoP requirement is enforced though the
111
- // "dpop_bound_access_tokens" metadata, which is enforced by the
112
- // ClientManager class.
113
- if (client.metadata.dpop_bound_access_tokens && !dpopProof) {
114
- throw new InvalidDpopProofError('DPoP proof required')
115
- }
116
-
117
- if (!parameters.dpop_jkt) {
118
- // Allow clients to bind their access tokens to a DPoP key during
119
- // token request if they didn't provide a "dpop_jkt" during the
120
- // authorization request.
121
- if (dpopProof) parameters = { ...parameters, dpop_jkt: dpopProof.jkt }
122
- } else if (!dpopProof) {
123
- throw new InvalidDpopProofError('DPoP proof required')
124
- } else if (parameters.dpop_jkt !== dpopProof.jkt) {
125
- throw new InvalidDpopKeyBindingError()
126
- }
127
-
128
- if (clientAuth.method === CLIENT_ASSERTION_TYPE_JWT_BEARER) {
129
- // Clients **must not** use their private key to sign DPoP proofs.
130
- if (parameters.dpop_jkt && clientAuth.jkt === parameters.dpop_jkt) {
131
- throw new InvalidRequestError(
132
- 'The DPoP proof must be signed with a different key than the client assertion',
133
- )
134
- }
135
- }
136
-
137
- if (!client.metadata.grant_types.includes(input.grant_type)) {
138
- throw new InvalidGrantError(
139
- `This client is not allowed to use the "${input.grant_type}" grant type`,
140
- )
141
- }
142
-
143
- let code: Code | null = null
144
-
145
- switch (input.grant_type) {
146
- case 'authorization_code': {
147
- if (!isCode(input.code)) {
148
- throw new InvalidGrantError('Invalid code')
149
- }
150
-
151
- // @NOTE not using `this.findByCode` because we want to delete the token
152
- // if it still exists (rather than throwing if the code is invalid).
153
- const tokenInfo = await this.store.findTokenByCode(input.code)
154
- if (tokenInfo) {
155
- await this.deleteToken(tokenInfo.id)
156
- throw new InvalidGrantError(`Code replayed`)
157
- }
158
-
159
- code = input.code
160
-
161
- if (parameters.redirect_uri !== input.redirect_uri) {
162
- throw new InvalidGrantError(
163
- 'The redirect_uri parameter must match the one used in the authorization request',
164
- )
165
- }
166
-
167
- if (parameters.code_challenge) {
168
- if (!input.code_verifier) {
169
- throw new InvalidGrantError('code_verifier is required')
170
- }
171
- if (input.code_verifier.length < 43) {
172
- throw new InvalidGrantError('code_verifier too short')
173
- }
174
- switch (parameters.code_challenge_method ?? 'plain') {
175
- case 'plain': {
176
- if (parameters.code_challenge !== input.code_verifier) {
177
- throw new InvalidGrantError('Invalid code_verifier')
178
- }
179
- break
180
- }
181
- case 'S256': {
182
- const inputChallenge = Buffer.from(
183
- parameters.code_challenge,
184
- 'base64',
185
- )
186
- const computedChallenge = createHash('sha256')
187
- .update(input.code_verifier)
188
- .digest()
189
- if (inputChallenge.compare(computedChallenge) !== 0) {
190
- throw new InvalidGrantError('Invalid code_verifier')
191
- }
192
- break
193
- }
194
- default: {
195
- // Should never happen (because request validation should catch this)
196
- throw new Error(`Unsupported code_challenge_method`)
197
- }
198
- }
199
- } else if (input.code_verifier !== undefined) {
200
- throw new InvalidRequestError(
201
- "code_challenge parameter wasn't provided",
202
- )
203
- }
204
-
205
- break
206
- }
207
-
208
- default: {
209
- // Other grants (e.g "password", "client_credentials") could be added
210
- // here in the future...
211
- throw new InvalidRequestError(
212
- `Unsupported grant type "${input.grant_type}"`,
213
- )
214
- }
215
- }
91
+ await this.validateTokenParams(client, clientAuth, parameters)
216
92
 
217
93
  const tokenId = await generateTokenId()
218
94
  const refreshToken = client.metadata.grant_types.includes('refresh_token')
@@ -235,26 +111,26 @@ export class TokenManager {
235
111
  code,
236
112
  }
237
113
 
238
- await this.store.createToken(tokenId, tokenData, refreshToken)
114
+ const accessToken = await this.buildAccessToken(
115
+ tokenId,
116
+ account,
117
+ client,
118
+ parameters,
119
+ { now, expiresAt },
120
+ )
239
121
 
240
- try {
241
- const accessToken = await this.buildAccessToken(
242
- tokenId,
243
- account,
244
- client,
245
- parameters,
246
- { now, expiresAt },
247
- )
122
+ const response = await this.buildTokenResponse(
123
+ client,
124
+ accessToken,
125
+ refreshToken,
126
+ expiresAt,
127
+ parameters,
128
+ account.sub,
129
+ )
248
130
 
249
- const response = await this.buildTokenResponse(
250
- client,
251
- accessToken,
252
- refreshToken,
253
- expiresAt,
254
- parameters,
255
- account.sub,
256
- )
131
+ await this.store.createToken(tokenId, tokenData, refreshToken)
257
132
 
133
+ try {
258
134
  await callAsync(this.hooks.onTokenCreated, {
259
135
  client,
260
136
  clientAuth,
@@ -265,13 +141,25 @@ export class TokenManager {
265
141
 
266
142
  return response
267
143
  } catch (err) {
268
- // Just in case the token could not be issued, we delete it from the store
144
+ // If the hook fails, we delete the token to avoid leaving a dangling
145
+ // token in the store.
269
146
  await this.deleteToken(tokenId)
270
-
271
147
  throw err
272
148
  }
273
149
  }
274
150
 
151
+ protected async validateTokenParams(
152
+ client: Client,
153
+ clientAuth: ClientAuth,
154
+ parameters: OAuthAuthorizationRequestParameters,
155
+ ): Promise<void> {
156
+ if (client.metadata.dpop_bound_access_tokens && !parameters.dpop_jkt) {
157
+ throw new InvalidGrantError(
158
+ `DPoP JKT is required for DPoP bound access tokens`,
159
+ )
160
+ }
161
+ }
162
+
275
163
  protected buildTokenResponse(
276
164
  client: Client,
277
165
  accessToken: OAuthAccessToken,
@@ -299,166 +187,68 @@ export class TokenManager {
299
187
  }
300
188
  }
301
189
 
302
- public async validateAccess(
303
- client: Client,
304
- clientAuth: ClientAuth,
305
- tokenInfo: TokenInfo,
306
- ) {
307
- if (tokenInfo.data.clientId !== client.id) {
308
- throw new InvalidGrantError(`Token was not issued to this client`)
309
- }
310
-
311
- if (tokenInfo.data.clientAuth.method !== clientAuth.method) {
312
- throw new InvalidGrantError(`Client authentication method mismatch`)
313
- }
314
-
315
- if (!(await client.validateClientAuth(tokenInfo.data.clientAuth))) {
316
- throw new InvalidGrantError(`Client authentication mismatch`)
317
- }
318
- }
319
-
320
- public async validateRefresh(
321
- client: Client,
322
- clientAuth: ClientAuth,
323
- { data }: TokenInfo,
324
- ): Promise<void> {
325
- // @TODO This value should be computable even if we don't have the "client"
326
- // (because fetching client info could be flaky). Instead, all the info
327
- // needed should be stored in the token info.
328
- const allowLongerLifespan =
329
- client.info.isFirstParty || data.clientAuth.method !== 'none'
330
-
331
- const lifetime = allowLongerLifespan
332
- ? AUTHENTICATED_REFRESH_LIFETIME
333
- : UNAUTHENTICATED_REFRESH_LIFETIME
334
-
335
- if (data.createdAt.getTime() + lifetime < Date.now()) {
336
- throw new InvalidGrantError(`Refresh token expired`)
337
- }
338
-
339
- const inactivityTimeout = allowLongerLifespan
340
- ? AUTHENTICATED_REFRESH_INACTIVITY_TIMEOUT
341
- : UNAUTHENTICATED_REFRESH_INACTIVITY_TIMEOUT
342
-
343
- if (data.updatedAt.getTime() + inactivityTimeout < Date.now()) {
344
- throw new InvalidGrantError(`Refresh token exceeded inactivity timeout`)
345
- }
346
- }
347
-
348
- async refresh(
190
+ async rotateToken(
349
191
  client: Client,
350
192
  clientAuth: ClientAuth,
351
193
  clientMetadata: RequestMetadata,
352
- input: OAuthRefreshTokenGrantTokenRequest,
353
- dpopProof: null | DpopProof,
194
+ tokenInfo: TokenInfo,
354
195
  ): Promise<OAuthTokenResponse> {
355
- const refreshTokenParsed = refreshTokenSchema.safeParse(input.refresh_token)
356
- if (!refreshTokenParsed.success) {
357
- throw new InvalidRequestError('Invalid refresh token')
358
- }
359
- const refreshToken = refreshTokenParsed.data
360
-
361
- const tokenInfo = await this.findByRefreshToken(refreshToken).catch(
362
- (err) => {
363
- throw InvalidGrantError.from(
364
- err,
365
- err instanceof InvalidRequestError
366
- ? err.error_description
367
- : 'Invalid refresh token',
368
- )
369
- },
370
- )
371
-
372
196
  const { account, data } = tokenInfo
373
197
  const { parameters } = data
374
198
 
375
- try {
376
- await this.validateAccess(client, clientAuth, tokenInfo)
377
- await this.validateRefresh(client, clientAuth, tokenInfo)
378
-
379
- if (!client.metadata.grant_types.includes(input.grant_type)) {
380
- // In case the client metadata was updated after the token was issued
381
- throw new InvalidGrantError(
382
- `This client is not allowed to use the "${input.grant_type}" grant type`,
383
- )
384
- }
385
-
386
- if (parameters.dpop_jkt) {
387
- if (!dpopProof) {
388
- throw new InvalidDpopProofError('DPoP proof required')
389
- } else if (parameters.dpop_jkt !== dpopProof.jkt) {
390
- throw new InvalidDpopKeyBindingError()
391
- }
392
- }
393
-
394
- const nextTokenId = await generateTokenId()
395
- const nextRefreshToken = await generateRefreshToken()
396
-
397
- const now = new Date()
398
- const expiresAt = this.createTokenExpiry(now)
399
-
400
- await this.store.rotateToken(
401
- tokenInfo.id,
402
- nextTokenId,
403
- nextRefreshToken,
404
- {
405
- updatedAt: now,
406
- expiresAt,
407
- // When clients rotate their public keys, we store the key that was
408
- // used by the client to authenticate itself while requesting new
409
- // tokens. The validateAccess() method will ensure that the client
410
- // still advertises the key that was used to issue the previous
411
- // refresh token. If a client stops advertising a key, all tokens
412
- // bound to that key will no longer be be refreshable. This allows
413
- // clients to proactively invalidate tokens when a key is compromised.
414
- // Note that the original DPoP key cannot be rotated. This protects
415
- // users in case the ownership of the client id changes. In the latter
416
- // case, a malicious actor could still advertises the public keys of
417
- // the previous owner, but the new owner would not be able to present
418
- // a valid DPoP proof.
419
- clientAuth,
420
- },
421
- )
199
+ await this.validateTokenParams(client, clientAuth, parameters)
422
200
 
423
- const accessToken = await this.buildAccessToken(
424
- nextTokenId,
425
- account,
426
- client,
427
- parameters,
428
- { now, expiresAt },
429
- )
201
+ const nextTokenId = await generateTokenId()
202
+ const nextRefreshToken = await generateRefreshToken()
430
203
 
431
- const response = await this.buildTokenResponse(
432
- client,
433
- accessToken,
434
- nextRefreshToken,
435
- expiresAt,
436
- parameters,
437
- account.sub,
438
- )
204
+ const now = new Date()
205
+ const expiresAt = this.createTokenExpiry(now)
439
206
 
440
- await callAsync(this.hooks.onTokenRefreshed, {
441
- client,
442
- clientAuth,
443
- clientMetadata,
444
- account,
445
- parameters,
446
- })
207
+ await this.store.rotateToken(tokenInfo.id, nextTokenId, nextRefreshToken, {
208
+ updatedAt: now,
209
+ expiresAt,
210
+ // @NOTE Normally, the clientAuth not change over time. There are two
211
+ // exceptions:
212
+ // - Upgrade from a legacy representation of client authentication to
213
+ // a modern one.
214
+ // - Allow clients to become "confidential" if they were previously
215
+ // "public"
216
+ clientAuth,
217
+ })
447
218
 
448
- return response
449
- } catch (err) {
450
- // Just in case the token could not be refreshed, we delete it from the store
451
- await this.deleteToken(tokenInfo.id)
219
+ const accessToken = await this.buildAccessToken(
220
+ nextTokenId,
221
+ account,
222
+ client,
223
+ parameters,
224
+ { now, expiresAt },
225
+ )
452
226
 
453
- throw err
454
- }
227
+ const response = await this.buildTokenResponse(
228
+ client,
229
+ accessToken,
230
+ nextRefreshToken,
231
+ expiresAt,
232
+ parameters,
233
+ account.sub,
234
+ )
235
+
236
+ await callAsync(this.hooks.onTokenRefreshed, {
237
+ client,
238
+ clientAuth,
239
+ clientMetadata,
240
+ account,
241
+ parameters,
242
+ })
243
+
244
+ return response
455
245
  }
456
246
 
457
247
  /**
458
248
  * @note The token validity is not guaranteed. The caller must ensure that the
459
249
  * token is valid before using the returned token info.
460
250
  */
461
- public async findToken(token: string): Promise<TokenInfo> {
251
+ public async findToken(token: string): Promise<null | TokenInfo> {
462
252
  if (isTokenId(token)) {
463
253
  return this.getTokenInfo(token)
464
254
  } else if (isCode(token)) {
@@ -466,18 +256,19 @@ export class TokenManager {
466
256
  } else if (isRefreshToken(token)) {
467
257
  return this.findByRefreshToken(token)
468
258
  } else if (isSignedJwt(token)) {
469
- return this.findBySignedJwt(token)
259
+ return this.findByAccessToken(token)
470
260
  } else {
471
261
  throw new InvalidRequestError(`Invalid token`)
472
262
  }
473
263
  }
474
264
 
475
- public async findBySignedJwt(token: SignedJwt): Promise<TokenInfo> {
265
+ public async findByAccessToken(token: SignedJwt): Promise<null | TokenInfo> {
476
266
  const { payload } = await this.signer.verifyAccessToken(token, {
477
267
  clockTolerance: Infinity,
478
268
  })
479
269
 
480
270
  const tokenInfo = await this.getTokenInfo(payload.jti)
271
+ if (!tokenInfo) return null
481
272
 
482
273
  // Fool-proof: Invalid store implementation ?
483
274
  if (payload.sub !== tokenInfo.account.sub) {
@@ -490,44 +281,49 @@ export class TokenManager {
490
281
  return tokenInfo
491
282
  }
492
283
 
493
- public async findByRefreshToken(token: RefreshToken): Promise<TokenInfo> {
494
- const tokenInfo = await this.store.findTokenByRefreshToken(token)
284
+ protected async findByRefreshToken(
285
+ token: RefreshToken,
286
+ ): Promise<null | TokenInfo> {
287
+ return this.store.findTokenByRefreshToken(token)
288
+ }
289
+
290
+ public async consumeRefreshToken(token: RefreshToken): Promise<TokenInfo> {
291
+ // @NOTE concurrent refreshes of the same refresh token could theoretically
292
+ // lead to two new tokens (access & refresh) being created. This is deemed
293
+ // acceptable for now (as the mechanism can only be used once since only one
294
+ // of the two refresh token created will be valid, and any future refresh
295
+ // attempts from outdated tokens will cause the entire session to be
296
+ // invalidated). Ideally, the store should be able to handle this case by
297
+ // atomically consuming the refresh token and returning the token info.
298
+
299
+ // @TODO Add another store method that atomically consumes the refresh token
300
+ // with a lock.
301
+ const tokenInfo = await this.findByRefreshToken(token).catch((err) => {
302
+ throw InvalidTokenError.from(err, `Invalid refresh token`)
303
+ })
495
304
 
496
305
  if (!tokenInfo) {
497
- throw new InvalidRequestError(`Invalid refresh token`)
306
+ throw new InvalidGrantError(`Invalid refresh token`)
498
307
  }
499
308
 
500
309
  if (tokenInfo.currentRefreshToken !== token) {
501
310
  await this.deleteToken(tokenInfo.id)
502
-
503
- throw new InvalidRequestError(`Refresh token replayed`)
311
+ throw new InvalidGrantError(`Refresh token replayed`)
504
312
  }
505
313
 
506
314
  return tokenInfo
507
315
  }
508
316
 
509
- public async findByCode(code: Code): Promise<TokenInfo> {
510
- const tokenInfo = await this.store.findTokenByCode(code)
511
-
512
- if (!tokenInfo) {
513
- throw new InvalidRequestError(`Invalid code`)
514
- }
515
-
516
- return tokenInfo
317
+ public async findByCode(code: Code): Promise<null | TokenInfo> {
318
+ return this.store.findTokenByCode(code)
517
319
  }
518
320
 
519
321
  public async deleteToken(tokenId: TokenId): Promise<void> {
520
322
  return this.store.deleteToken(tokenId)
521
323
  }
522
324
 
523
- async getTokenInfo(tokenId: TokenId): Promise<TokenInfo> {
524
- const tokenInfo = await this.store.readToken(tokenId)
525
-
526
- if (!tokenInfo) {
527
- throw new InvalidRequestError(`Invalid token`)
528
- }
529
-
530
- return tokenInfo
325
+ async getTokenInfo(tokenId: TokenId): Promise<null | TokenInfo> {
326
+ return this.store.readToken(tokenId)
531
327
  }
532
328
 
533
329
  async verifyToken(
@@ -541,6 +337,10 @@ export class TokenManager {
541
337
  throw InvalidTokenError.from(err, tokenType)
542
338
  })
543
339
 
340
+ if (!tokenInfo) {
341
+ throw new InvalidTokenError(tokenType, `Invalid token`)
342
+ }
343
+
544
344
  if (isCurrentTokenExpired(tokenInfo)) {
545
345
  await this.deleteToken(tokenId)
546
346
  throw new InvalidTokenError(tokenType, `Token expired`)
@@ -1 +1 @@
1
- {"root":["./src/constants.ts","./src/index.ts","./src/oauth-client.ts","./src/oauth-dpop.ts","./src/oauth-errors.ts","./src/oauth-hooks.ts","./src/oauth-middleware.ts","./src/oauth-provider.ts","./src/oauth-store.ts","./src/oauth-verifier.ts","./src/access-token/access-token-mode.ts","./src/account/account-manager.ts","./src/account/account-store.ts","./src/account/sign-in-data.ts","./src/account/sign-up-input.ts","./src/client/client-auth.ts","./src/client/client-data.ts","./src/client/client-id.ts","./src/client/client-info.ts","./src/client/client-manager.ts","./src/client/client-store.ts","./src/client/client-utils.ts","./src/client/client.ts","./src/customization/branding.ts","./src/customization/build-customization-css.ts","./src/customization/build-customization-data.ts","./src/customization/colors.ts","./src/customization/customization.ts","./src/customization/links.ts","./src/device/device-data.ts","./src/device/device-id.ts","./src/device/device-manager.ts","./src/device/device-store.ts","./src/device/session-id.ts","./src/dpop/dpop-manager.ts","./src/dpop/dpop-nonce.ts","./src/dpop/dpop-proof.ts","./src/errors/access-denied-error.ts","./src/errors/account-selection-required-error.ts","./src/errors/consent-required-error.ts","./src/errors/error-parser.ts","./src/errors/handle-unavailable-error.ts","./src/errors/invalid-authorization-details-error.ts","./src/errors/invalid-client-error.ts","./src/errors/invalid-client-id-error.ts","./src/errors/invalid-client-metadata-error.ts","./src/errors/invalid-dpop-key-binding-error.ts","./src/errors/invalid-dpop-proof-error.ts","./src/errors/invalid-grant-error.ts","./src/errors/invalid-invite-code-error.ts","./src/errors/invalid-parameters-error.ts","./src/errors/invalid-redirect-uri-error.ts","./src/errors/invalid-request-error.ts","./src/errors/invalid-scope-error.ts","./src/errors/invalid-token-error.ts","./src/errors/login-required-error.ts","./src/errors/oauth-error.ts","./src/errors/second-authentication-factor-required-error.ts","./src/errors/unauthorized-client-error.ts","./src/errors/use-dpop-nonce-error.ts","./src/errors/www-authenticate-error.ts","./src/lib/hcaptcha.ts","./src/lib/redis.ts","./src/lib/send-web-page.ts","./src/lib/csp/index.ts","./src/lib/html/build-document.ts","./src/lib/html/escapers.ts","./src/lib/html/html.ts","./src/lib/html/hydration-data.ts","./src/lib/html/index.ts","./src/lib/html/tags.ts","./src/lib/html/util.ts","./src/lib/http/accept.ts","./src/lib/http/context.ts","./src/lib/http/headers.ts","./src/lib/http/index.ts","./src/lib/http/method.ts","./src/lib/http/middleware.ts","./src/lib/http/parser.ts","./src/lib/http/path.ts","./src/lib/http/request.ts","./src/lib/http/response.ts","./src/lib/http/route.ts","./src/lib/http/router.ts","./src/lib/http/security-headers.ts","./src/lib/http/stream.ts","./src/lib/http/types.ts","./src/lib/http/url.ts","./src/lib/util/authorization-header.ts","./src/lib/util/cast.ts","./src/lib/util/color.ts","./src/lib/util/crypto.ts","./src/lib/util/date.ts","./src/lib/util/function.ts","./src/lib/util/locale.ts","./src/lib/util/redirect-uri.ts","./src/lib/util/time.ts","./src/lib/util/type.ts","./src/lib/util/ui8.ts","./src/lib/util/well-known.ts","./src/lib/util/zod-error.ts","./src/metadata/build-metadata.ts","./src/oidc/sub.ts","./src/replay/replay-manager.ts","./src/replay/replay-store-memory.ts","./src/replay/replay-store-redis.ts","./src/replay/replay-store.ts","./src/request/code.ts","./src/request/request-data.ts","./src/request/request-id.ts","./src/request/request-info.ts","./src/request/request-manager.ts","./src/request/request-store-memory.ts","./src/request/request-store-redis.ts","./src/request/request-store.ts","./src/request/request-uri.ts","./src/result/authorization-redirect-parameters.ts","./src/result/authorization-result-authorize-page.ts","./src/result/authorization-result-redirect.ts","./src/router/create-account-page-middleware.ts","./src/router/create-api-middleware.ts","./src/router/create-authorization-page-middleware.ts","./src/router/create-oauth-middleware.ts","./src/router/error-handler.ts","./src/router/middleware-options.ts","./src/router/send-redirect.ts","./src/router/assets/assets-manifest.ts","./src/router/assets/assets.ts","./src/router/assets/csrf.ts","./src/router/assets/send-account-page.ts","./src/router/assets/send-authorization-page.ts","./src/router/assets/send-error-page.ts","./src/signer/api-token-payload.ts","./src/signer/signed-token-payload.ts","./src/signer/signer.ts","./src/token/refresh-token.ts","./src/token/token-data.ts","./src/token/token-id.ts","./src/token/token-manager.ts","./src/token/token-store.ts","./src/token/verify-token-claims.ts","./src/types/color-hue.ts","./src/types/email-otp.ts","./src/types/email.ts","./src/types/handle.ts","./src/types/invite-code.ts","./src/types/password.ts","./src/types/rgb-color.ts"],"version":"5.8.3"}
1
+ {"root":["./src/constants.ts","./src/index.ts","./src/oauth-client.ts","./src/oauth-dpop.ts","./src/oauth-errors.ts","./src/oauth-hooks.ts","./src/oauth-middleware.ts","./src/oauth-provider.ts","./src/oauth-store.ts","./src/oauth-verifier.ts","./src/access-token/access-token-mode.ts","./src/account/account-manager.ts","./src/account/account-store.ts","./src/account/sign-in-data.ts","./src/account/sign-up-input.ts","./src/client/client-auth.ts","./src/client/client-data.ts","./src/client/client-id.ts","./src/client/client-info.ts","./src/client/client-manager.ts","./src/client/client-store.ts","./src/client/client-utils.ts","./src/client/client.ts","./src/customization/branding.ts","./src/customization/build-customization-css.ts","./src/customization/build-customization-data.ts","./src/customization/colors.ts","./src/customization/customization.ts","./src/customization/links.ts","./src/device/device-data.ts","./src/device/device-id.ts","./src/device/device-manager.ts","./src/device/device-store.ts","./src/device/session-id.ts","./src/dpop/dpop-manager.ts","./src/dpop/dpop-nonce.ts","./src/dpop/dpop-proof.ts","./src/errors/access-denied-error.ts","./src/errors/account-selection-required-error.ts","./src/errors/consent-required-error.ts","./src/errors/error-parser.ts","./src/errors/handle-unavailable-error.ts","./src/errors/invalid-authorization-details-error.ts","./src/errors/invalid-client-error.ts","./src/errors/invalid-client-id-error.ts","./src/errors/invalid-client-metadata-error.ts","./src/errors/invalid-dpop-key-binding-error.ts","./src/errors/invalid-dpop-proof-error.ts","./src/errors/invalid-grant-error.ts","./src/errors/invalid-invite-code-error.ts","./src/errors/invalid-parameters-error.ts","./src/errors/invalid-redirect-uri-error.ts","./src/errors/invalid-request-error.ts","./src/errors/invalid-scope-error.ts","./src/errors/invalid-token-error.ts","./src/errors/login-required-error.ts","./src/errors/oauth-error.ts","./src/errors/second-authentication-factor-required-error.ts","./src/errors/unauthorized-client-error.ts","./src/errors/use-dpop-nonce-error.ts","./src/errors/www-authenticate-error.ts","./src/lib/hcaptcha.ts","./src/lib/redis.ts","./src/lib/send-web-page.ts","./src/lib/csp/index.ts","./src/lib/html/build-document.ts","./src/lib/html/escapers.ts","./src/lib/html/html.ts","./src/lib/html/hydration-data.ts","./src/lib/html/index.ts","./src/lib/html/tags.ts","./src/lib/html/util.ts","./src/lib/http/accept.ts","./src/lib/http/context.ts","./src/lib/http/headers.ts","./src/lib/http/index.ts","./src/lib/http/method.ts","./src/lib/http/middleware.ts","./src/lib/http/parser.ts","./src/lib/http/path.ts","./src/lib/http/request.ts","./src/lib/http/response.ts","./src/lib/http/route.ts","./src/lib/http/router.ts","./src/lib/http/security-headers.ts","./src/lib/http/stream.ts","./src/lib/http/types.ts","./src/lib/http/url.ts","./src/lib/util/authorization-header.ts","./src/lib/util/cast.ts","./src/lib/util/color.ts","./src/lib/util/crypto.ts","./src/lib/util/date.ts","./src/lib/util/function.ts","./src/lib/util/locale.ts","./src/lib/util/redirect-uri.ts","./src/lib/util/time.ts","./src/lib/util/type.ts","./src/lib/util/ui8.ts","./src/lib/util/well-known.ts","./src/lib/util/zod-error.ts","./src/metadata/build-metadata.ts","./src/oidc/sub.ts","./src/replay/replay-manager.ts","./src/replay/replay-store-memory.ts","./src/replay/replay-store-redis.ts","./src/replay/replay-store.ts","./src/request/code.ts","./src/request/request-data.ts","./src/request/request-id.ts","./src/request/request-info.ts","./src/request/request-manager.ts","./src/request/request-store.ts","./src/request/request-uri.ts","./src/result/authorization-redirect-parameters.ts","./src/result/authorization-result-authorize-page.ts","./src/result/authorization-result-redirect.ts","./src/router/create-account-page-middleware.ts","./src/router/create-api-middleware.ts","./src/router/create-authorization-page-middleware.ts","./src/router/create-oauth-middleware.ts","./src/router/error-handler.ts","./src/router/middleware-options.ts","./src/router/send-redirect.ts","./src/router/assets/assets-manifest.ts","./src/router/assets/assets.ts","./src/router/assets/csrf.ts","./src/router/assets/send-account-page.ts","./src/router/assets/send-authorization-page.ts","./src/router/assets/send-error-page.ts","./src/signer/api-token-payload.ts","./src/signer/signed-token-payload.ts","./src/signer/signer.ts","./src/token/refresh-token.ts","./src/token/token-data.ts","./src/token/token-id.ts","./src/token/token-manager.ts","./src/token/token-store.ts","./src/token/verify-token-claims.ts","./src/types/color-hue.ts","./src/types/email-otp.ts","./src/types/email.ts","./src/types/handle.ts","./src/types/invite-code.ts","./src/types/password.ts","./src/types/rgb-color.ts"],"version":"5.8.3"}
@@ -1,16 +0,0 @@
1
- import { Code } from './code.js';
2
- import { RequestData } from './request-data.js';
3
- import { RequestId } from './request-id.js';
4
- import { RequestStore } from './request-store.js';
5
- export declare class RequestStoreMemory implements RequestStore {
6
- #private;
7
- readRequest(id: RequestId): Promise<RequestData | null>;
8
- createRequest(id: RequestId, data: RequestData): Promise<void>;
9
- updateRequest(id: RequestId, data: Partial<RequestData>): Promise<void>;
10
- deleteRequest(id: RequestId): Promise<void>;
11
- findRequestByCode(code: Code): Promise<{
12
- id: RequestId;
13
- data: RequestData;
14
- } | null>;
15
- }
16
- //# sourceMappingURL=request-store-memory.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"request-store-memory.d.ts","sourceRoot":"","sources":["../../src/request/request-store-memory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEjD,qBAAa,kBAAmB,YAAW,YAAY;;IAG/C,WAAW,CAAC,EAAE,EAAE,SAAS,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAIvD,aAAa,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAI9D,aAAa,CACjB,EAAE,EAAE,SAAS,EACb,IAAI,EAAE,OAAO,CAAC,WAAW,CAAC,GACzB,OAAO,CAAC,IAAI,CAAC;IAOV,aAAa,CAAC,EAAE,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAI3C,iBAAiB,CACrB,IAAI,EAAE,IAAI,GACT,OAAO,CAAC;QAAE,EAAE,EAAE,SAAS,CAAC;QAAC,IAAI,EAAE,WAAW,CAAA;KAAE,GAAG,IAAI,CAAC;CAMxD"}
@@ -1,31 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.RequestStoreMemory = void 0;
4
- class RequestStoreMemory {
5
- #requests = new Map();
6
- async readRequest(id) {
7
- return this.#requests.get(id) ?? null;
8
- }
9
- async createRequest(id, data) {
10
- this.#requests.set(id, data);
11
- }
12
- async updateRequest(id, data) {
13
- const current = this.#requests.get(id);
14
- if (!current)
15
- throw new Error('Request not found');
16
- const newData = { ...current, ...data };
17
- this.#requests.set(id, newData);
18
- }
19
- async deleteRequest(id) {
20
- this.#requests.delete(id);
21
- }
22
- async findRequestByCode(code) {
23
- for (const [id, data] of this.#requests) {
24
- if (data.code === code)
25
- return { id, data };
26
- }
27
- return null;
28
- }
29
- }
30
- exports.RequestStoreMemory = RequestStoreMemory;
31
- //# sourceMappingURL=request-store-memory.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"request-store-memory.js","sourceRoot":"","sources":["../../src/request/request-store-memory.ts"],"names":[],"mappings":";;;AAKA,MAAa,kBAAkB;IAC7B,SAAS,GAAG,IAAI,GAAG,EAA0B,CAAA;IAE7C,KAAK,CAAC,WAAW,CAAC,EAAa;QAC7B,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,IAAI,CAAA;IACvC,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,EAAa,EAAE,IAAiB;QAClD,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,CAAA;IAC9B,CAAC;IAED,KAAK,CAAC,aAAa,CACjB,EAAa,EACb,IAA0B;QAE1B,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QACtC,IAAI,CAAC,OAAO;YAAE,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAA;QAClD,MAAM,OAAO,GAAG,EAAE,GAAG,OAAO,EAAE,GAAG,IAAI,EAAE,CAAA;QACvC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,EAAE,OAAO,CAAC,CAAA;IACjC,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,EAAa;QAC/B,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;IAC3B,CAAC;IAED,KAAK,CAAC,iBAAiB,CACrB,IAAU;QAEV,KAAK,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACxC,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI;gBAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAA;QAC7C,CAAC;QACD,OAAO,IAAI,CAAA;IACb,CAAC;CACF;AAjCD,gDAiCC"}
@@ -1,24 +0,0 @@
1
- import type { Redis } from 'ioredis';
2
- import { CreateRedisOptions } from '../lib/redis.js';
3
- import { Code } from './code.js';
4
- import { RequestData } from './request-data.js';
5
- import { RequestId } from './request-id.js';
6
- import { RequestStore } from './request-store.js';
7
- export type { CreateRedisOptions, Redis };
8
- export type ReplayStoreRedisOptions = {
9
- redis: CreateRedisOptions;
10
- };
11
- export declare class RequestStoreRedis implements RequestStore {
12
- private readonly redis;
13
- constructor(options: ReplayStoreRedisOptions);
14
- readRequest(id: RequestId): Promise<RequestData | null>;
15
- createRequest(id: RequestId, data: RequestData): Promise<void>;
16
- updateRequest(id: RequestId, data: Partial<RequestData>): Promise<void>;
17
- deleteRequest(id: RequestId): Promise<void>;
18
- private findRequestIdByCode;
19
- findRequestByCode(code: Code): Promise<{
20
- id: RequestId;
21
- data: RequestData;
22
- } | null>;
23
- }
24
- //# sourceMappingURL=request-store-redis.d.ts.map