@atproto/oauth-provider 0.10.2 → 0.11.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 (85) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/constants.d.ts +2 -0
  3. package/dist/constants.d.ts.map +1 -1
  4. package/dist/constants.js +3 -1
  5. package/dist/constants.js.map +1 -1
  6. package/dist/customization/branding.d.ts +5 -19
  7. package/dist/customization/branding.d.ts.map +1 -1
  8. package/dist/customization/customization.d.ts +7 -25
  9. package/dist/customization/customization.d.ts.map +1 -1
  10. package/dist/customization/links.d.ts +3 -13
  11. package/dist/customization/links.d.ts.map +1 -1
  12. package/dist/customization/links.js +1 -1
  13. package/dist/customization/links.js.map +1 -1
  14. package/dist/lexicon/lexicon-data.d.ts +9 -0
  15. package/dist/lexicon/lexicon-data.d.ts.map +1 -0
  16. package/dist/lexicon/lexicon-data.js +3 -0
  17. package/dist/lexicon/lexicon-data.js.map +1 -0
  18. package/dist/lexicon/lexicon-getter.d.ts +15 -0
  19. package/dist/lexicon/lexicon-getter.d.ts.map +1 -0
  20. package/dist/lexicon/lexicon-getter.js +55 -0
  21. package/dist/lexicon/lexicon-getter.js.map +1 -0
  22. package/dist/lexicon/lexicon-manager.d.ts +60 -0
  23. package/dist/lexicon/lexicon-manager.d.ts.map +1 -0
  24. package/dist/lexicon/lexicon-manager.js +105 -0
  25. package/dist/lexicon/lexicon-manager.js.map +1 -0
  26. package/dist/lexicon/lexicon-store.d.ts +13 -0
  27. package/dist/lexicon/lexicon-store.d.ts.map +1 -0
  28. package/dist/lexicon/lexicon-store.js +24 -0
  29. package/dist/lexicon/lexicon-store.js.map +1 -0
  30. package/dist/lib/html/hydration-data.d.ts.map +1 -1
  31. package/dist/lib/html/hydration-data.js +1 -2
  32. package/dist/lib/html/hydration-data.js.map +1 -1
  33. package/dist/lib/nsid.d.ts +4 -0
  34. package/dist/lib/nsid.d.ts.map +1 -0
  35. package/dist/lib/nsid.js +15 -0
  36. package/dist/lib/nsid.js.map +1 -0
  37. package/dist/lib/util/locale.d.ts +1 -15
  38. package/dist/lib/util/locale.d.ts.map +1 -1
  39. package/dist/lib/util/locale.js +2 -7
  40. package/dist/lib/util/locale.js.map +1 -1
  41. package/dist/oauth-provider.d.ts +14 -9
  42. package/dist/oauth-provider.d.ts.map +1 -1
  43. package/dist/oauth-provider.js +15 -6
  44. package/dist/oauth-provider.js.map +1 -1
  45. package/dist/oauth-store.d.ts +1 -0
  46. package/dist/oauth-store.d.ts.map +1 -1
  47. package/dist/oauth-store.js +1 -0
  48. package/dist/oauth-store.js.map +1 -1
  49. package/dist/request/request-manager.d.ts +3 -1
  50. package/dist/request/request-manager.d.ts.map +1 -1
  51. package/dist/request/request-manager.js +19 -4
  52. package/dist/request/request-manager.js.map +1 -1
  53. package/dist/result/authorization-result-authorize-page.d.ts +5 -3
  54. package/dist/result/authorization-result-authorize-page.d.ts.map +1 -1
  55. package/dist/router/assets/send-authorization-page.d.ts.map +1 -1
  56. package/dist/router/assets/send-authorization-page.js +1 -0
  57. package/dist/router/assets/send-authorization-page.js.map +1 -1
  58. package/dist/token/token-data.d.ts +7 -0
  59. package/dist/token/token-data.d.ts.map +1 -1
  60. package/dist/token/token-manager.d.ts +5 -6
  61. package/dist/token/token-manager.d.ts.map +1 -1
  62. package/dist/token/token-manager.js +37 -13
  63. package/dist/token/token-manager.js.map +1 -1
  64. package/dist/token/token-store.d.ts +16 -2
  65. package/dist/token/token-store.d.ts.map +1 -1
  66. package/dist/token/token-store.js.map +1 -1
  67. package/package.json +12 -10
  68. package/src/constants.ts +3 -0
  69. package/src/customization/links.ts +2 -2
  70. package/src/lexicon/lexicon-data.ts +9 -0
  71. package/src/lexicon/lexicon-getter.ts +62 -0
  72. package/src/lexicon/lexicon-manager.ts +116 -0
  73. package/src/lexicon/lexicon-store.ts +36 -0
  74. package/src/lib/html/hydration-data.ts +1 -2
  75. package/src/lib/nsid.ts +10 -0
  76. package/src/lib/util/locale.ts +3 -9
  77. package/src/oauth-provider.ts +30 -9
  78. package/src/oauth-store.ts +1 -0
  79. package/src/request/request-manager.ts +26 -7
  80. package/src/result/authorization-result-authorize-page.ts +5 -3
  81. package/src/router/assets/send-authorization-page.ts +1 -0
  82. package/src/token/token-data.ts +8 -0
  83. package/src/token/token-manager.ts +68 -34
  84. package/src/token/token-store.ts +17 -5
  85. package/tsconfig.build.tsbuildinfo +1 -1
@@ -1,6 +1,7 @@
1
1
  import { createHash } from 'node:crypto'
2
2
  import type { Redis, RedisOptions } from 'ioredis'
3
3
  import { Jwks, Keyset } from '@atproto/jwk'
4
+ import { LexiconResolver } from '@atproto/lexicon-resolver'
4
5
  import type { Account } from '@atproto/oauth-provider-api'
5
6
  import {
6
7
  CLIENT_ASSERTION_TYPE_JWT_BEARER,
@@ -71,11 +72,13 @@ import { InvalidDpopProofError } from './errors/invalid-dpop-proof-error.js'
71
72
  import { InvalidGrantError } from './errors/invalid-grant-error.js'
72
73
  import { InvalidRequestError } from './errors/invalid-request-error.js'
73
74
  import { LoginRequiredError } from './errors/login-required-error.js'
75
+ import { LexiconManager } from './lexicon/lexicon-manager.js'
76
+ import { LexiconStore, asLexiconStore } from './lexicon/lexicon-store.js'
74
77
  import { HcaptchaConfig } from './lib/hcaptcha.js'
75
78
  import { RequestMetadata } from './lib/http/request.js'
76
79
  import { dateToRelativeSeconds } from './lib/util/date.js'
77
80
  import { formatError } from './lib/util/error.js'
78
- import { LocalizedString, MultiLangString } from './lib/util/locale.js'
81
+ import { MultiLangString } from './lib/util/locale.js'
79
82
  import { CustomMetadata, buildMetadata } from './metadata/build-metadata.js'
80
83
  import { OAuthHooks } from './oauth-hooks.js'
81
84
  import {
@@ -117,7 +120,7 @@ export type {
117
120
  CustomizationInput,
118
121
  ErrorHandler,
119
122
  HcaptchaConfig,
120
- LocalizedString,
123
+ LexiconResolver,
121
124
  MultiLangString,
122
125
  OAuthAuthorizationServerMetadata,
123
126
  }
@@ -129,11 +132,6 @@ type OAuthProviderConfig = {
129
132
  */
130
133
  authenticationMaxAge?: number
131
134
 
132
- /**
133
- * Maximum age an ephemeral session (one where "remember me" was not
134
- * checked) can be before requiring re-authentication.
135
- */
136
-
137
135
  /**
138
136
  * Maximum age access & id tokens can be before requiring a refresh.
139
137
  */
@@ -169,6 +167,11 @@ type OAuthProviderConfig = {
169
167
  */
170
168
  safeFetch?: typeof globalThis.fetch
171
169
 
170
+ /**
171
+ * A custom ATProto lexicon resolver
172
+ */
173
+ lexiconResolver?: LexiconResolver
174
+
172
175
  /**
173
176
  * A redis instance to use for replay protection. If not provided, replay
174
177
  * protection will use memory storage.
@@ -186,6 +189,7 @@ type OAuthProviderConfig = {
186
189
  AccountStore &
187
190
  ClientStore &
188
191
  DeviceStore &
192
+ LexiconStore &
189
193
  ReplayStore &
190
194
  RequestStore &
191
195
  TokenStore
@@ -194,6 +198,7 @@ type OAuthProviderConfig = {
194
198
  accountStore?: AccountStore
195
199
  clientStore?: ClientStore
196
200
  deviceStore?: DeviceStore
201
+ lexiconStore?: LexiconStore
197
202
  replayStore?: ReplayStore
198
203
  requestStore?: RequestStore
199
204
  tokenStore?: TokenStore
@@ -243,6 +248,7 @@ export class OAuthProvider extends OAuthVerifier {
243
248
  public readonly accountManager: AccountManager
244
249
  public readonly deviceManager: DeviceManager
245
250
  public readonly clientManager: ClientManager
251
+ public readonly lexiconManager: LexiconManager
246
252
  public readonly requestManager: RequestManager
247
253
  public readonly tokenManager: TokenManager
248
254
 
@@ -254,16 +260,18 @@ export class OAuthProvider extends OAuthVerifier {
254
260
 
255
261
  metadata,
256
262
 
263
+ lexiconResolver,
257
264
  safeFetch = safeFetchWrap(),
258
265
  store, // compound store implementation
259
266
 
260
- // Requires stores
267
+ // Required stores
261
268
  accountStore = asAccountStore(store),
262
269
  deviceStore = asDeviceStore(store),
270
+ lexiconStore = asLexiconStore(store),
263
271
  tokenStore = asTokenStore(store),
264
272
  requestStore = asRequestStore(store),
265
273
 
266
- // These are optional
274
+ // Optional stores
267
275
  clientStore = ifClientStore(store),
268
276
  replayStore = ifReplayStore(store),
269
277
 
@@ -322,14 +330,17 @@ export class OAuthProvider extends OAuthVerifier {
322
330
  clientJwksCache,
323
331
  clientMetadataCache,
324
332
  )
333
+ this.lexiconManager = new LexiconManager(lexiconStore, lexiconResolver)
325
334
  this.requestManager = new RequestManager(
326
335
  requestStore,
336
+ this.lexiconManager,
327
337
  this.signer,
328
338
  this.metadata,
329
339
  this.hooks,
330
340
  )
331
341
  this.tokenManager = new TokenManager(
332
342
  tokenStore,
343
+ this.lexiconManager,
333
344
  this.signer,
334
345
  this.hooks,
335
346
  this.accessTokenMode,
@@ -667,6 +678,16 @@ export class OAuthProvider extends OAuthVerifier {
667
678
  loginRequired: session.loginRequired,
668
679
  consentRequired: session.consentRequired,
669
680
  })),
681
+ permissionSets: await this.lexiconManager
682
+ .getPermissionSetsFromScope(parameters.scope)
683
+ .catch((cause) => {
684
+ throw new AuthorizationError(
685
+ parameters,
686
+ 'Unable to retrieve permission sets',
687
+ 'invalid_scope',
688
+ cause,
689
+ )
690
+ }),
670
691
  }
671
692
  } catch (err) {
672
693
  try {
@@ -6,6 +6,7 @@
6
6
  export * from './account/account-store.js'
7
7
  export * from './client/client-store.js'
8
8
  export * from './device/device-store.js'
9
+ export * from './lexicon/lexicon-store.js'
9
10
  export * from './replay/replay-store.js'
10
11
  export * from './request/request-store.js'
11
12
  export * from './token/token-store.js'
@@ -1,6 +1,7 @@
1
1
  import { isAtprotoDid } from '@atproto/did'
2
+ import { LexiconResolutionError } from '@atproto/lexicon-resolver'
2
3
  import type { Account } from '@atproto/oauth-provider-api'
3
- import { isValidAtprotoOauthScope } from '@atproto/oauth-scopes'
4
+ import { isAtprotoOauthScope } from '@atproto/oauth-scopes'
4
5
  import {
5
6
  OAuthAuthorizationRequestParameters,
6
7
  OAuthAuthorizationServerMetadata,
@@ -23,6 +24,7 @@ import { InvalidAuthorizationDetailsError } from '../errors/invalid-authorizatio
23
24
  import { InvalidGrantError } from '../errors/invalid-grant-error.js'
24
25
  import { InvalidRequestError } from '../errors/invalid-request-error.js'
25
26
  import { InvalidScopeError } from '../errors/invalid-scope-error.js'
27
+ import { LexiconManager } from '../lexicon/lexicon-manager.js'
26
28
  import { RequestMetadata } from '../lib/http/request.js'
27
29
  import { callAsync } from '../lib/util/function.js'
28
30
  import { OAuthHooks } from '../oauth-hooks.js'
@@ -43,6 +45,7 @@ import {
43
45
  export class RequestManager {
44
46
  constructor(
45
47
  protected readonly store: RequestStore,
48
+ protected readonly lexiconManager: LexiconManager,
46
49
  protected readonly signer: Signer,
47
50
  protected readonly metadata: OAuthAuthorizationServerMetadata,
48
51
  protected readonly hooks: OAuthHooks,
@@ -171,17 +174,16 @@ export class RequestManager {
171
174
  // > (Section 3.2.3) to inform the client of the actual scope granted.
172
175
 
173
176
  // Let's make sure the scopes are unique (to reduce the token & storage
174
- // size) & are indeed supported.
177
+ // size).
178
+ const scopes = new Set(parameters.scope?.split(' '))
175
179
 
176
180
  // @NOTE An app requesting a not yet supported list of scopes will need to
177
181
  // re-authenticate the user once the scopes are supported. This is due to
178
182
  // the fact that the AS does not know how to properly display those scopes
179
183
  // to the user, so it cannot properly ask for consent.
180
- const scopes = new Set(
181
- parameters.scope?.split(' ')?.filter(isValidAtprotoOauthScope),
182
- )
183
-
184
- parameters = { ...parameters, scope: [...scopes].join(' ') || undefined }
184
+ const scope =
185
+ Array.from(scopes).filter(isAtprotoOauthScope).join(' ') || undefined
186
+ parameters = { ...parameters, scope }
185
187
 
186
188
  if (parameters.code_challenge) {
187
189
  switch (parameters.code_challenge_method) {
@@ -288,6 +290,23 @@ export class RequestManager {
288
290
  parameters = { ...parameters, login_hint: hint }
289
291
  }
290
292
 
293
+ // Make sure that every nsid in the scope resolves to a valid permission set
294
+ // lexicon
295
+ if (parameters.scope) {
296
+ await this.lexiconManager
297
+ .getPermissionSetsFromScope(parameters.scope)
298
+ .catch((cause) => {
299
+ throw new AuthorizationError(
300
+ parameters,
301
+ cause instanceof LexiconResolutionError
302
+ ? cause.message
303
+ : 'Unable to retrieve included permission sets',
304
+ 'invalid_scope',
305
+ cause,
306
+ )
307
+ })
308
+ }
309
+
291
310
  return parameters
292
311
  }
293
312
 
@@ -1,12 +1,14 @@
1
+ import type { LexPermissionSet } from '@atproto/lexicon'
1
2
  import type { Session } from '@atproto/oauth-provider-api'
2
- import { OAuthAuthorizationRequestParameters } from '@atproto/oauth-types'
3
- import { Client } from '../client/client.js'
4
- import { RequestUri } from '../request/request-uri.js'
3
+ import type { OAuthAuthorizationRequestParameters } from '@atproto/oauth-types'
4
+ import type { Client } from '../client/client.js'
5
+ import type { RequestUri } from '../request/request-uri.js'
5
6
 
6
7
  export type AuthorizationResultAuthorizePage = {
7
8
  issuer: string
8
9
  client: Client
9
10
  parameters: OAuthAuthorizationRequestParameters
11
+ permissionSets: Map<string, LexPermissionSet>
10
12
 
11
13
  requestUri: RequestUri
12
14
  sessions: readonly Session[]
@@ -46,6 +46,7 @@ export function sendAuthorizePageFactory(customization: Customization) {
46
46
  scope: data.parameters.scope,
47
47
  uiLocales: data.parameters.ui_locales,
48
48
  loginHint: data.parameters.login_hint,
49
+ permissionSets: Object.fromEntries(data.permissionSets),
49
50
  },
50
51
  __sessions: data.sessions,
51
52
  })
@@ -29,4 +29,12 @@ export type TokenData = {
29
29
  parameters: OAuthAuthorizationRequestParameters
30
30
  details?: null // Legacy field, not used
31
31
  code: Code | null
32
+
33
+ /**
34
+ * This will contain the parameter scope, translated into permissions
35
+ *
36
+ * @note null because this didn't use to exist. New tokens should always
37
+ * include a scope.
38
+ */
39
+ scope: string | null
32
40
  }
@@ -1,4 +1,5 @@
1
1
  import { SignedJwt, isSignedJwt } from '@atproto/jwk'
2
+ import { LexiconResolutionError } from '@atproto/lexicon-resolver'
2
3
  import type { Account } from '@atproto/oauth-provider-api'
3
4
  import {
4
5
  OAuthAccessToken,
@@ -14,6 +15,7 @@ import { DeviceId } from '../device/device-id.js'
14
15
  import { InvalidGrantError } from '../errors/invalid-grant-error.js'
15
16
  import { InvalidRequestError } from '../errors/invalid-request-error.js'
16
17
  import { InvalidTokenError } from '../errors/invalid-token-error.js'
18
+ import { LexiconManager } from '../lexicon/lexicon-manager.js'
17
19
  import { RequestMetadata } from '../lib/http/request.js'
18
20
  import { dateToEpoch, dateToRelativeSeconds } from '../lib/util/date.js'
19
21
  import { callAsync } from '../lib/util/function.js'
@@ -28,9 +30,8 @@ import {
28
30
  generateRefreshToken,
29
31
  isRefreshToken,
30
32
  } from './refresh-token.js'
31
- import { TokenData } from './token-data.js'
32
33
  import { TokenId, generateTokenId, isTokenId } from './token-id.js'
33
- import { TokenInfo, TokenStore } from './token-store.js'
34
+ import { CreateTokenData, TokenInfo, TokenStore } from './token-store.js'
34
35
  import {
35
36
  VerifyTokenClaimsOptions,
36
37
  VerifyTokenClaimsResult,
@@ -43,6 +44,7 @@ export type { OAuthHooks, TokenStore, VerifyTokenClaimsResult }
43
44
  export class TokenManager {
44
45
  constructor(
45
46
  protected readonly store: TokenStore,
47
+ protected readonly lexiconManager: LexiconManager,
46
48
  protected readonly signer: Signer,
47
49
  protected readonly hooks: OAuthHooks,
48
50
  protected readonly accessTokenMode: AccessTokenMode,
@@ -58,21 +60,20 @@ export class TokenManager {
58
60
  account: Account,
59
61
  client: Client,
60
62
  parameters: OAuthAuthorizationRequestParameters,
61
- options: {
62
- now: Date
63
- expiresAt: Date
64
- },
63
+ createdAt: Date,
64
+ expiresAt: Date,
65
+ scope: string,
65
66
  ): Promise<OAuthAccessToken> {
66
67
  return this.signer.createAccessToken({
67
68
  jti: tokenId,
68
69
  sub: account.sub,
69
- exp: dateToEpoch(options.expiresAt),
70
- iat: dateToEpoch(options.now),
70
+ exp: dateToEpoch(expiresAt),
71
+ iat: dateToEpoch(createdAt),
71
72
  cnf: parameters.dpop_jkt ? { jkt: parameters.dpop_jkt } : undefined,
72
73
 
73
74
  ...(this.accessTokenMode === AccessTokenMode.stateless && {
74
75
  aud: account.aud,
75
- scope: parameters.scope,
76
+ scope,
76
77
  // https://datatracker.ietf.org/doc/html/rfc8693#section-4.3
77
78
  client_id: client.id,
78
79
  }),
@@ -98,36 +99,50 @@ export class TokenManager {
98
99
  const now = new Date()
99
100
  const expiresAt = this.createTokenExpiry(now)
100
101
 
101
- const tokenData: TokenData = {
102
- createdAt: now,
103
- updatedAt: now,
104
- expiresAt,
105
- clientId: client.id,
106
- clientAuth,
107
- deviceId,
108
- sub: account.sub,
109
- parameters,
110
- details: null,
111
- code,
112
- }
102
+ const scope = await this.lexiconManager
103
+ .buildTokenScope(parameters.scope!)
104
+ .catch((cause) => {
105
+ throw new InvalidRequestError(
106
+ cause instanceof LexiconResolutionError
107
+ ? cause.message
108
+ : 'Unable to retrieve included permission sets',
109
+ cause,
110
+ )
111
+ })
113
112
 
114
113
  const accessToken = await this.buildAccessToken(
115
114
  tokenId,
116
115
  account,
117
116
  client,
118
117
  parameters,
119
- { now, expiresAt },
118
+ now,
119
+ expiresAt,
120
+ scope,
120
121
  )
121
122
 
122
- const response = await this.buildTokenResponse(
123
- client,
123
+ const response = this.buildTokenResponse(
124
+ inferTokenType(parameters),
124
125
  accessToken,
125
126
  refreshToken,
126
127
  expiresAt,
127
- parameters,
128
128
  account.sub,
129
+ scope,
129
130
  )
130
131
 
132
+ const tokenData: CreateTokenData = {
133
+ createdAt: now,
134
+ updatedAt: now,
135
+ expiresAt,
136
+ clientId: client.id,
137
+ clientAuth,
138
+ deviceId,
139
+ sub: account.sub,
140
+ parameters,
141
+ details: null,
142
+ scope,
143
+ code,
144
+ }
145
+
131
146
  await this.store.createToken(tokenId, tokenData, refreshToken)
132
147
 
133
148
  try {
@@ -161,18 +176,18 @@ export class TokenManager {
161
176
  }
162
177
 
163
178
  protected buildTokenResponse(
164
- client: Client,
179
+ tokenType: OAuthTokenType,
165
180
  accessToken: OAuthAccessToken,
166
181
  refreshToken: string | undefined,
167
182
  expiresAt: Date,
168
- parameters: OAuthAuthorizationRequestParameters,
169
183
  sub: Sub,
184
+ scope: string,
170
185
  ): OAuthTokenResponse {
171
186
  return {
172
187
  access_token: accessToken,
173
- token_type: parameters.dpop_jkt ? 'DPoP' : 'Bearer',
188
+ token_type: tokenType,
174
189
  refresh_token: refreshToken,
175
- scope: parameters.scope,
190
+ scope,
176
191
 
177
192
  // @NOTE using a getter so that the value gets computed when the JSON
178
193
  // response is generated, allowing to value to be as accurate as possible.
@@ -204,6 +219,11 @@ export class TokenManager {
204
219
  const now = new Date()
205
220
  const expiresAt = this.createTokenExpiry(now)
206
221
 
222
+ // @NOTE since the permission sets are stored in a persistent store,
223
+ // it's fine to propagate a 500 (server_error) here as the values should
224
+ // be retrievable from the store.
225
+ const scope = await this.lexiconManager.buildTokenScope(parameters.scope!)
226
+
207
227
  await this.store.rotateToken(tokenInfo.id, nextTokenId, nextRefreshToken, {
208
228
  updatedAt: now,
209
229
  expiresAt,
@@ -214,6 +234,7 @@ export class TokenManager {
214
234
  // - Allow clients to become "confidential" if they were previously
215
235
  // "public"
216
236
  clientAuth,
237
+ scope,
217
238
  })
218
239
 
219
240
  const accessToken = await this.buildAccessToken(
@@ -221,16 +242,18 @@ export class TokenManager {
221
242
  account,
222
243
  client,
223
244
  parameters,
224
- { now, expiresAt },
245
+ now,
246
+ expiresAt,
247
+ scope,
225
248
  )
226
249
 
227
- const response = await this.buildTokenResponse(
228
- client,
250
+ const response = this.buildTokenResponse(
251
+ inferTokenType(parameters),
229
252
  accessToken,
230
253
  nextRefreshToken,
231
254
  expiresAt,
232
- parameters,
233
255
  account.sub,
256
+ scope,
234
257
  )
235
258
 
236
259
  await callAsync(this.hooks.onTokenRefreshed, {
@@ -361,7 +384,9 @@ export class TokenManager {
361
384
  // These are not stored in the JWT access token in "light" access token
362
385
  // mode. See `buildAccessToken`.
363
386
  aud: account.aud,
364
- scope: parameters.scope,
387
+ // Note we fallback to parameters.scope for sessions created before
388
+ // TokenData.scope was introduced.
389
+ scope: data.scope ?? parameters.scope,
365
390
  client_id: data.clientId,
366
391
  }
367
392
 
@@ -386,3 +411,12 @@ export class TokenManager {
386
411
  function isCurrentTokenExpired(tokenInfo: TokenInfo): boolean {
387
412
  return tokenInfo.data.expiresAt.getTime() < Date.now()
388
413
  }
414
+
415
+ function inferTokenType(
416
+ parameters: OAuthAuthorizationRequestParameters,
417
+ ): OAuthTokenType {
418
+ if (parameters.dpop_jkt) {
419
+ return 'DPoP'
420
+ }
421
+ return 'Bearer'
422
+ }
@@ -19,15 +19,27 @@ export type TokenInfo = {
19
19
  currentRefreshToken: null | RefreshToken
20
20
  }
21
21
 
22
- export type NewTokenData = Pick<
23
- TokenData,
24
- 'clientAuth' | 'expiresAt' | 'updatedAt'
25
- >
22
+ export type NewTokenData = {
23
+ clientAuth: TokenData['clientAuth']
24
+ expiresAt: TokenData['expiresAt']
25
+ updatedAt: TokenData['updatedAt']
26
+ scope: NonNullable<TokenData['scope']>
27
+ }
28
+
29
+ export type CreateTokenData = TokenData & {
30
+ scope: NonNullable<TokenData['scope']>
31
+ }
26
32
 
33
+ /**
34
+ * @param data historically, {@link TokenData.scope} was not present in
35
+ * {@link TokenData}, causing it to be "nullable" when returned from
36
+ * {@link TokenStore.readToken}. We use {@link CreateTokenData} here to allow
37
+ * the store implementation to expect its presence.
38
+ */
27
39
  export interface TokenStore {
28
40
  createToken(
29
41
  tokenId: TokenId,
30
- data: TokenData,
42
+ data: CreateTokenData,
31
43
  refreshToken?: RefreshToken,
32
44
  ): Awaitable<void>
33
45
 
@@ -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/authorization-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-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/error.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-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/authorization-response-error.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/par-response-error.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/authorization-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-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/lexicon/lexicon-data.ts","./src/lexicon/lexicon-getter.ts","./src/lexicon/lexicon-manager.ts","./src/lexicon/lexicon-store.ts","./src/lib/hcaptcha.ts","./src/lib/nsid.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/error.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-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/authorization-response-error.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/par-response-error.ts","./src/types/password.ts","./src/types/rgb-color.ts"],"version":"5.8.3"}