@atproto/oauth-provider 0.19.6 → 0.19.8

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 (165) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/errors/error-parser.js +5 -5
  3. package/dist/errors/error-parser.js.map +1 -1
  4. package/package.json +22 -18
  5. package/src/access-token/access-token-mode.ts +0 -4
  6. package/src/account/account-manager.ts +0 -591
  7. package/src/account/account-store.ts +0 -305
  8. package/src/account/sign-in-data.ts +0 -15
  9. package/src/account/sign-up-input.ts +0 -20
  10. package/src/client/client-auth.ts +0 -64
  11. package/src/client/client-data.ts +0 -9
  12. package/src/client/client-id.ts +0 -4
  13. package/src/client/client-info.ts +0 -13
  14. package/src/client/client-manager.ts +0 -772
  15. package/src/client/client-store.ts +0 -35
  16. package/src/client/client-utils.ts +0 -37
  17. package/src/client/client.ts +0 -394
  18. package/src/constants.ts +0 -80
  19. package/src/customization/branding.ts +0 -12
  20. package/src/customization/build-customization-css.ts +0 -55
  21. package/src/customization/build-customization-data.ts +0 -24
  22. package/src/customization/colors.ts +0 -48
  23. package/src/customization/customization.ts +0 -29
  24. package/src/customization/links.ts +0 -10
  25. package/src/device/device-data.ts +0 -11
  26. package/src/device/device-id.ts +0 -27
  27. package/src/device/device-manager.ts +0 -292
  28. package/src/device/device-store.ts +0 -31
  29. package/src/device/session-id.ts +0 -21
  30. package/src/dpop/dpop-manager.ts +0 -214
  31. package/src/dpop/dpop-nonce.ts +0 -121
  32. package/src/dpop/dpop-proof.ts +0 -6
  33. package/src/errors/access-denied-error.ts +0 -12
  34. package/src/errors/account-selection-required-error.ts +0 -12
  35. package/src/errors/authorization-error.ts +0 -45
  36. package/src/errors/consent-required-error.ts +0 -12
  37. package/src/errors/error-parser.ts +0 -147
  38. package/src/errors/handle-unavailable-error.ts +0 -23
  39. package/src/errors/invalid-authorization-details-error.ts +0 -27
  40. package/src/errors/invalid-client-error.ts +0 -20
  41. package/src/errors/invalid-client-id-error.ts +0 -31
  42. package/src/errors/invalid-client-metadata-error.ts +0 -68
  43. package/src/errors/invalid-credentials-error.ts +0 -29
  44. package/src/errors/invalid-dpop-key-binding-error.ts +0 -21
  45. package/src/errors/invalid-dpop-proof-error.ts +0 -13
  46. package/src/errors/invalid-grant-error.ts +0 -21
  47. package/src/errors/invalid-invite-code-error.ts +0 -10
  48. package/src/errors/invalid-redirect-uri-error.ts +0 -17
  49. package/src/errors/invalid-request-error.ts +0 -32
  50. package/src/errors/invalid-scope-error.ts +0 -15
  51. package/src/errors/invalid-token-error.ts +0 -60
  52. package/src/errors/login-required-error.ts +0 -12
  53. package/src/errors/oauth-error.ts +0 -28
  54. package/src/errors/second-authentication-factor-required-error.ts +0 -25
  55. package/src/errors/unauthorized-client-error.ts +0 -20
  56. package/src/errors/use-dpop-nonce-error.ts +0 -32
  57. package/src/errors/www-authenticate-error.ts +0 -64
  58. package/src/index.ts +0 -16
  59. package/src/lexicon/lexicon-data.ts +0 -11
  60. package/src/lexicon/lexicon-getter.ts +0 -55
  61. package/src/lexicon/lexicon-manager.ts +0 -116
  62. package/src/lexicon/lexicon-store.ts +0 -35
  63. package/src/lib/csp/index.ts +0 -101
  64. package/src/lib/hcaptcha.ts +0 -228
  65. package/src/lib/html/README.md +0 -9
  66. package/src/lib/html/build-document.ts +0 -151
  67. package/src/lib/html/escapers.ts +0 -66
  68. package/src/lib/html/html.ts +0 -56
  69. package/src/lib/html/hydration-data.ts +0 -19
  70. package/src/lib/html/index.ts +0 -5
  71. package/src/lib/html/tags.ts +0 -67
  72. package/src/lib/html/util.ts +0 -17
  73. package/src/lib/http/README.md +0 -11
  74. package/src/lib/http/accept.ts +0 -90
  75. package/src/lib/http/context.ts +0 -42
  76. package/src/lib/http/headers.ts +0 -15
  77. package/src/lib/http/index.ts +0 -10
  78. package/src/lib/http/method.ts +0 -18
  79. package/src/lib/http/middleware.ts +0 -169
  80. package/src/lib/http/parser.ts +0 -101
  81. package/src/lib/http/path.ts +0 -82
  82. package/src/lib/http/request.ts +0 -256
  83. package/src/lib/http/response.ts +0 -122
  84. package/src/lib/http/route.ts +0 -60
  85. package/src/lib/http/router.ts +0 -117
  86. package/src/lib/http/security-headers.ts +0 -100
  87. package/src/lib/http/stream.ts +0 -57
  88. package/src/lib/http/types.ts +0 -16
  89. package/src/lib/http/url.ts +0 -23
  90. package/src/lib/nsid.ts +0 -10
  91. package/src/lib/redis.ts +0 -25
  92. package/src/lib/util/authorization-header.ts +0 -28
  93. package/src/lib/util/cast.ts +0 -18
  94. package/src/lib/util/color.ts +0 -168
  95. package/src/lib/util/crypto.ts +0 -32
  96. package/src/lib/util/date.ts +0 -7
  97. package/src/lib/util/error.ts +0 -7
  98. package/src/lib/util/function.ts +0 -39
  99. package/src/lib/util/locale.ts +0 -12
  100. package/src/lib/util/object.ts +0 -23
  101. package/src/lib/util/redirect-uri.ts +0 -46
  102. package/src/lib/util/time.ts +0 -49
  103. package/src/lib/util/type.ts +0 -182
  104. package/src/lib/util/ui8.ts +0 -14
  105. package/src/lib/util/well-known.ts +0 -8
  106. package/src/lib/util/zod-error.ts +0 -26
  107. package/src/lib/write-form-redirect.ts +0 -58
  108. package/src/lib/write-html.ts +0 -70
  109. package/src/metadata/build-metadata.ts +0 -141
  110. package/src/oauth-client.ts +0 -3
  111. package/src/oauth-dpop.ts +0 -2
  112. package/src/oauth-errors.ts +0 -26
  113. package/src/oauth-hooks.ts +0 -519
  114. package/src/oauth-middleware.ts +0 -53
  115. package/src/oauth-provider.ts +0 -1110
  116. package/src/oauth-store.ts +0 -12
  117. package/src/oauth-verifier.ts +0 -260
  118. package/src/replay/replay-manager.ts +0 -51
  119. package/src/replay/replay-store-memory.ts +0 -36
  120. package/src/replay/replay-store-redis.ts +0 -30
  121. package/src/replay/replay-store.ts +0 -44
  122. package/src/request/code.ts +0 -23
  123. package/src/request/request-data.ts +0 -36
  124. package/src/request/request-id.ts +0 -22
  125. package/src/request/request-manager.ts +0 -521
  126. package/src/request/request-store.ts +0 -60
  127. package/src/request/request-uri.ts +0 -42
  128. package/src/result/authorization-redirect-parameters.ts +0 -24
  129. package/src/result/authorization-result-authorize-page.ts +0 -16
  130. package/src/result/authorization-result-redirect.ts +0 -8
  131. package/src/router/assets/assets-manifest.ts +0 -117
  132. package/src/router/assets/assets.ts +0 -124
  133. package/src/router/assets/csrf.ts +0 -79
  134. package/src/router/assets/send-account-page.ts +0 -23
  135. package/src/router/assets/send-authorization-page.ts +0 -42
  136. package/src/router/assets/send-cookie-error-page.ts +0 -21
  137. package/src/router/assets/send-error-page.ts +0 -25
  138. package/src/router/assets/send-redirect.ts +0 -140
  139. package/src/router/create-account-page-middleware.ts +0 -88
  140. package/src/router/create-api-middleware.ts +0 -1051
  141. package/src/router/create-authorization-page-middleware.ts +0 -281
  142. package/src/router/create-oauth-middleware.ts +0 -257
  143. package/src/router/error-handler.ts +0 -6
  144. package/src/router/middleware-options.ts +0 -9
  145. package/src/signer/access-token-payload.ts +0 -25
  146. package/src/signer/api-token-payload.ts +0 -18
  147. package/src/signer/signer.ts +0 -121
  148. package/src/token/refresh-token.ts +0 -30
  149. package/src/token/token-claims.ts +0 -22
  150. package/src/token/token-data.ts +0 -40
  151. package/src/token/token-id.ts +0 -25
  152. package/src/token/token-manager.ts +0 -427
  153. package/src/token/token-store.ts +0 -88
  154. package/src/types/authorization-response-error.ts +0 -27
  155. package/src/types/color-hue.ts +0 -3
  156. package/src/types/email-otp.ts +0 -3
  157. package/src/types/email.ts +0 -26
  158. package/src/types/handle.ts +0 -23
  159. package/src/types/invite-code.ts +0 -4
  160. package/src/types/par-response-error.ts +0 -25
  161. package/src/types/password.ts +0 -4
  162. package/src/types/rgb-color.ts +0 -21
  163. package/tsconfig.build.json +0 -8
  164. package/tsconfig.build.tsbuildinfo +0 -1
  165. package/tsconfig.json +0 -4
@@ -1,1110 +0,0 @@
1
- import { createHash } from 'node:crypto'
2
- import type { Redis, RedisOptions } from 'ioredis'
3
- import { Jwks, Keyset } from '@atproto/jwk'
4
- import { LexResolver } from '@atproto/lex-resolver'
5
- import type { Account } from '@atproto/oauth-provider-api'
6
- import {
7
- CLIENT_ASSERTION_TYPE_JWT_BEARER,
8
- OAuthAccessToken,
9
- OAuthAuthorizationCodeGrantTokenRequest,
10
- OAuthAuthorizationRequestJar,
11
- OAuthAuthorizationRequestPar,
12
- OAuthAuthorizationRequestParameters,
13
- OAuthAuthorizationRequestQuery,
14
- OAuthAuthorizationServerMetadata,
15
- OAuthClientCredentials,
16
- OAuthClientMetadata,
17
- OAuthParResponse,
18
- OAuthRefreshTokenGrantTokenRequest,
19
- OAuthTokenIdentification,
20
- OAuthTokenRequest,
21
- OAuthTokenResponse,
22
- OAuthTokenType,
23
- atprotoLoopbackClientMetadata,
24
- oauthAuthorizationRequestParametersSchema,
25
- } from '@atproto/oauth-types'
26
- import { safeFetchWrap } from '@atproto-labs/fetch-node'
27
- import { SimpleStore } from '@atproto-labs/simple-store'
28
- import { SimpleStoreMemory } from '@atproto-labs/simple-store-memory'
29
- import { AccessTokenMode } from './access-token/access-token-mode.js'
30
- import { AccountManager } from './account/account-manager.js'
31
- import {
32
- AccountStore,
33
- AuthorizedClientData,
34
- DeviceAccount,
35
- asAccountStore,
36
- } from './account/account-store.js'
37
- import { ClientAuth, ClientAuthLegacy } from './client/client-auth.js'
38
- import { ClientId } from './client/client-id.js'
39
- import {
40
- ClientManager,
41
- LoopbackMetadataGetter,
42
- } from './client/client-manager.js'
43
- import { ClientStore, ifClientStore } from './client/client-store.js'
44
- import { Client } from './client/client.js'
45
- import {
46
- AUTHENTICATION_MAX_AGE,
47
- CONFIDENTIAL_CLIENT_REFRESH_LIFETIME,
48
- CONFIDENTIAL_CLIENT_SESSION_LIFETIME,
49
- PUBLIC_CLIENT_REFRESH_LIFETIME,
50
- PUBLIC_CLIENT_SESSION_LIFETIME,
51
- TOKEN_MAX_AGE,
52
- } from './constants.js'
53
- import { Branding, BrandingInput } from './customization/branding.js'
54
- import {
55
- Customization,
56
- CustomizationInput,
57
- customizationSchema,
58
- } from './customization/customization.js'
59
- import { DeviceId } from './device/device-id.js'
60
- import {
61
- DeviceInfo,
62
- DeviceManager,
63
- DeviceManagerOptions,
64
- } from './device/device-manager.js'
65
- import { DeviceStore, asDeviceStore } from './device/device-store.js'
66
- import { AccountSelectionRequiredError } from './errors/account-selection-required-error.js'
67
- import { AuthorizationError } from './errors/authorization-error.js'
68
- import { ConsentRequiredError } from './errors/consent-required-error.js'
69
- import { InvalidDpopKeyBindingError } from './errors/invalid-dpop-key-binding-error.js'
70
- import { InvalidDpopProofError } from './errors/invalid-dpop-proof-error.js'
71
- import { InvalidGrantError } from './errors/invalid-grant-error.js'
72
- import { InvalidRequestError } from './errors/invalid-request-error.js'
73
- import { LoginRequiredError } from './errors/login-required-error.js'
74
- import { LexiconManager } from './lexicon/lexicon-manager.js'
75
- import { LexiconStore, asLexiconStore } from './lexicon/lexicon-store.js'
76
- import { HcaptchaConfig } from './lib/hcaptcha.js'
77
- import { RequestMetadata } from './lib/http/request.js'
78
- import { dateToRelativeSeconds } from './lib/util/date.js'
79
- import { formatError } from './lib/util/error.js'
80
- import { MultiLangString } from './lib/util/locale.js'
81
- import { CustomMetadata, buildMetadata } from './metadata/build-metadata.js'
82
- import { OAuthHooks } from './oauth-hooks.js'
83
- import {
84
- DpopProof,
85
- OAuthVerifier,
86
- OAuthVerifierOptions,
87
- VerifyTokenPayloadOptions,
88
- } from './oauth-verifier.js'
89
- import { ReplayStore, ifReplayStore } from './replay/replay-store.js'
90
- import { codeSchema } from './request/code.js'
91
- import { RequestManager } from './request/request-manager.js'
92
- import { RequestStore, asRequestStore } from './request/request-store.js'
93
- import { parseRequestUri } from './request/request-uri.js'
94
- import { AuthorizationRedirectParameters } from './result/authorization-redirect-parameters.js'
95
- import { AuthorizationResultAuthorizePage } from './result/authorization-result-authorize-page.js'
96
- import { AuthorizationResultRedirect } from './result/authorization-result-redirect.js'
97
- import { ErrorHandler } from './router/error-handler.js'
98
- import { AccessTokenPayload } from './signer/access-token-payload.js'
99
- import { TokenData } from './token/token-data.js'
100
- import { TokenManager } from './token/token-manager.js'
101
- import {
102
- TokenStore,
103
- asTokenStore,
104
- refreshTokenSchema,
105
- } from './token/token-store.js'
106
- import { isPARResponseError } from './types/par-response-error.js'
107
-
108
- export { AccessTokenMode, Keyset, LexResolver }
109
- export type {
110
- AccessTokenPayload,
111
- AuthorizationRedirectParameters,
112
- AuthorizationResultAuthorizePage as AuthorizationResultAuthorize,
113
- AuthorizationResultRedirect,
114
- Branding,
115
- BrandingInput,
116
- CustomMetadata,
117
- Customization,
118
- CustomizationInput,
119
- ErrorHandler,
120
- HcaptchaConfig,
121
- MultiLangString,
122
- OAuthAuthorizationServerMetadata,
123
- VerifyTokenPayloadOptions,
124
- }
125
-
126
- type OAuthProviderConfig = {
127
- /**
128
- * Maximum age a device/account session can be before requiring
129
- * re-authentication.
130
- */
131
- authenticationMaxAge?: number
132
-
133
- /**
134
- * Maximum age access & id tokens can be before requiring a refresh.
135
- */
136
- tokenMaxAge?: number
137
-
138
- /**
139
- * If set to {@link AccessTokenMode.stateless}, the generated access tokens
140
- * will contain all the necessary information to validate the token without
141
- * needing to query the database. This is useful for cases where the Resource
142
- * Server is on a different host/server than the Authorization Server.
143
- *
144
- * When set to {@link AccessTokenMode.light}, the access tokens will contain
145
- * only the necessary information to validate the token, but the token id
146
- * will need to be queried from the database to retrieve the full token
147
- * information (scope, audience, etc.)
148
- *
149
- * @see {@link AccessTokenMode}
150
- * @default {AccessTokenMode.stateless}
151
- */
152
- accessTokenMode?: AccessTokenMode
153
-
154
- /**
155
- * Additional metadata to be included in the discovery document.
156
- */
157
- metadata?: CustomMetadata
158
-
159
- /**
160
- * A Lexicon resolver instance to use for fetching lexicon schemas.
161
- */
162
- lexResolver?: LexResolver
163
-
164
- /**
165
- * A custom fetch function that can be used to fetch the client metadata from
166
- * the internet. By default, the fetch function is a safeFetchWrap() function
167
- * that protects against SSRF attacks, large responses & known bad domains. If
168
- * you want to disable all protections, you can provide `globalThis.fetch` as
169
- * fetch function.
170
- */
171
- safeFetch?: typeof globalThis.fetch
172
-
173
- /**
174
- * A redis instance to use for replay protection. If not provided, replay
175
- * protection will use memory storage.
176
- */
177
- redis?: Redis | RedisOptions | string
178
-
179
- /**
180
- * This will be used as the default store for all the stores. If a store is
181
- * not provided, this store will be used instead. If the `store` does not
182
- * implement a specific store, a runtime error will be thrown. Make sure that
183
- * this store implements all the interfaces not provided in the other
184
- * `<name>Store` options.
185
- */
186
- store?: Partial<
187
- AccountStore &
188
- ClientStore &
189
- DeviceStore &
190
- LexiconStore &
191
- ReplayStore &
192
- RequestStore &
193
- TokenStore
194
- >
195
-
196
- accountStore?: AccountStore
197
- clientStore?: ClientStore
198
- deviceStore?: DeviceStore
199
- lexiconStore?: LexiconStore
200
- replayStore?: ReplayStore
201
- requestStore?: RequestStore
202
- tokenStore?: TokenStore
203
-
204
- /**
205
- * In order to speed up the client fetching process, you can provide a cache
206
- * to store HTTP responses.
207
- *
208
- * @note the cached entries should automatically expire after a certain time (typically 10 minutes)
209
- */
210
- clientJwksCache?: SimpleStore<string, Jwks>
211
-
212
- /**
213
- * In order to speed up the client fetching process, you can provide a cache
214
- * to store HTTP responses.
215
- *
216
- * @note the cached entries should automatically expire after a certain time (typically 10 minutes)
217
- */
218
- clientMetadataCache?: SimpleStore<string, OAuthClientMetadata>
219
-
220
- /**
221
- * In order to enable loopback clients, you can provide a function that
222
- * returns the client metadata for a given loopback URL. This is useful for
223
- * development and testing purposes. This function is not called for internet
224
- * clients.
225
- *
226
- * @default is as specified by ATPROTO
227
- */
228
- loopbackMetadata?: null | false | LoopbackMetadataGetter
229
- }
230
-
231
- export type OAuthProviderOptions = OAuthProviderConfig &
232
- OAuthVerifierOptions &
233
- OAuthHooks &
234
- DeviceManagerOptions &
235
- CustomizationInput
236
-
237
- export class OAuthProvider extends OAuthVerifier {
238
- protected readonly accessTokenMode: AccessTokenMode
239
- protected readonly hooks: OAuthHooks
240
-
241
- public readonly metadata: OAuthAuthorizationServerMetadata
242
- public readonly customization: Customization
243
-
244
- public readonly authenticationMaxAge: number
245
-
246
- public readonly accountManager: AccountManager
247
- public readonly deviceManager: DeviceManager
248
- public readonly clientManager: ClientManager
249
- public readonly lexiconManager: LexiconManager
250
- public readonly requestManager: RequestManager
251
- public readonly tokenManager: TokenManager
252
-
253
- public constructor({
254
- // OAuthProviderConfig
255
- authenticationMaxAge = AUTHENTICATION_MAX_AGE,
256
- tokenMaxAge = TOKEN_MAX_AGE,
257
- accessTokenMode = AccessTokenMode.stateless,
258
-
259
- metadata,
260
-
261
- safeFetch = safeFetchWrap(),
262
- store, // compound store implementation
263
- lexResolver = new LexResolver({ fetch: safeFetch }),
264
-
265
- // Required stores
266
- accountStore = asAccountStore(store),
267
- deviceStore = asDeviceStore(store),
268
- lexiconStore = asLexiconStore(store),
269
- tokenStore = asTokenStore(store),
270
- requestStore = asRequestStore(store),
271
-
272
- // Optional stores
273
- clientStore = ifClientStore(store),
274
- replayStore = ifReplayStore(store),
275
-
276
- clientJwksCache = new SimpleStoreMemory({
277
- maxSize: 50_000_000,
278
- ttl: 600e3,
279
- }),
280
- clientMetadataCache = new SimpleStoreMemory({
281
- maxSize: 50_000_000,
282
- ttl: 600e3,
283
- }),
284
-
285
- loopbackMetadata = atprotoLoopbackClientMetadata,
286
-
287
- // OAuthHooks &
288
- // OAuthVerifierOptions &
289
- // DeviceManagerOptions &
290
- // Customization
291
- ...rest
292
- }: OAuthProviderOptions) {
293
- super({ replayStore, ...rest })
294
-
295
- // @NOTE: hooks don't really need a type parser, as all zod can actually
296
- // check at runtime is the fact that the values are functions. The only way
297
- // we would benefit from zod here would be to wrap the functions with a
298
- // validator for the provided function's return types, which we don't
299
- // really need if types are respected.
300
- this.hooks = rest
301
-
302
- this.accessTokenMode = accessTokenMode
303
- this.authenticationMaxAge = authenticationMaxAge
304
- this.metadata = buildMetadata(this.issuer, this.keyset, metadata)
305
- this.customization = customizationSchema.parse(rest)
306
-
307
- this.deviceManager = new DeviceManager(deviceStore, {
308
- ...rest,
309
- cookie: {
310
- ...rest.cookie,
311
- // "secure" defaults to "true" in DeviceManager. For the oauth routes to
312
- // work from localhost on Safari, we need to explicitly set secure to
313
- // false for localhost usage. This is not really an issue with Chrome
314
- // and Firefox, but Safari enforces it strictly.
315
- secure: !this.issuer.startsWith('http:'),
316
- },
317
- })
318
- this.accountManager = new AccountManager(
319
- this.issuer,
320
- accountStore,
321
- this.hooks,
322
- this.customization,
323
- )
324
- this.clientManager = new ClientManager(
325
- this.metadata,
326
- this.keyset,
327
- this.hooks,
328
- clientStore || null,
329
- loopbackMetadata || null,
330
- safeFetch,
331
- clientJwksCache,
332
- clientMetadataCache,
333
- )
334
- this.lexiconManager = new LexiconManager(lexiconStore, lexResolver)
335
- this.requestManager = new RequestManager(
336
- requestStore,
337
- this.lexiconManager,
338
- this.signer,
339
- this.metadata,
340
- this.hooks,
341
- )
342
- this.tokenManager = new TokenManager(
343
- tokenStore,
344
- this.lexiconManager,
345
- this.signer,
346
- this.hooks,
347
- this.accessTokenMode,
348
- tokenMaxAge,
349
- )
350
- }
351
-
352
- get jwks() {
353
- return this.keyset.publicJwks
354
- }
355
-
356
- /**
357
- * @returns true if the user's consent is required for the requested scopes
358
- */
359
- public checkConsentRequired(
360
- parameters: OAuthAuthorizationRequestParameters,
361
- clientData?: AuthorizedClientData,
362
- ) {
363
- // Client was never authorized before
364
- if (!clientData) return true
365
-
366
- // Client explicitly asked for consent
367
- if (parameters.prompt === 'consent') return true
368
-
369
- // No scope requested, and client is known by user, no consent required
370
- const requestedScopes = parameters.scope?.split(' ')
371
- if (requestedScopes == null) return false
372
-
373
- // Ensure that all requested scopes were previously authorized by the user
374
- const { authorizedScopes } = clientData
375
- return !requestedScopes.every((scope) => authorizedScopes.includes(scope))
376
- }
377
-
378
- public checkLoginRequired(deviceAccount: DeviceAccount) {
379
- const authAge = Date.now() - deviceAccount.updatedAt.getTime()
380
- return authAge > this.authenticationMaxAge
381
- }
382
-
383
- protected async authenticateClient(
384
- clientCredentials: OAuthClientCredentials,
385
- dpopProof: null | DpopProof,
386
- options?: {
387
- allowMissingDpopProof?: boolean
388
- },
389
- ): Promise<{
390
- client: Client
391
- clientAuth: ClientAuth
392
- }> {
393
- const client = await this.clientManager.getClient(
394
- clientCredentials.client_id,
395
- )
396
-
397
- if (
398
- client.metadata.dpop_bound_access_tokens &&
399
- !dpopProof &&
400
- !options?.allowMissingDpopProof
401
- ) {
402
- throw new InvalidDpopProofError('DPoP proof required')
403
- }
404
-
405
- if (dpopProof && !client.metadata.dpop_bound_access_tokens) {
406
- throw new InvalidDpopProofError('DPoP proof not allowed for this client')
407
- }
408
-
409
- const clientAuth = await client.authenticate(clientCredentials, {
410
- authorizationServerIdentifier: this.issuer,
411
- })
412
-
413
- if (clientAuth.method === 'private_key_jwt') {
414
- // Clients MUST NOT use their client assertion key to sign DPoP proofs
415
- if (dpopProof && clientAuth.jkt === dpopProof.jkt) {
416
- throw new InvalidRequestError(
417
- 'The DPoP proof must be signed with a different key than the client assertion',
418
- )
419
- }
420
-
421
- // https://www.rfc-editor.org/rfc/rfc7523.html#section-3
422
- // > 7. [...] The authorization server MAY ensure that JWTs are not
423
- // > replayed by maintaining the set of used "jti" values for the
424
- // > length of time for which the JWT would be considered valid based
425
- // > on the applicable "exp" instant.
426
-
427
- const unique = await this.replayManager.uniqueAuth(
428
- clientAuth.jti,
429
- client.id,
430
- clientAuth.exp,
431
- )
432
- if (!unique) {
433
- throw new InvalidGrantError(`${clientAuth.method} jti reused`)
434
- }
435
- }
436
-
437
- return { client, clientAuth }
438
- }
439
-
440
- async decodeJAR(
441
- client: Client,
442
- input: OAuthAuthorizationRequestJar,
443
- ): Promise<OAuthAuthorizationRequestParameters> {
444
- const { payload } = await client.decodeRequestObject(
445
- input.request,
446
- this.issuer,
447
- )
448
-
449
- const { jti } = payload
450
- if (!jti) {
451
- throw new InvalidRequestError(
452
- 'Request object payload must contain a "jti" claim',
453
- )
454
- }
455
- if (!(await this.replayManager.uniqueJar(jti, client.id))) {
456
- throw new InvalidRequestError('Request object was replayed')
457
- }
458
-
459
- const parameters = await oauthAuthorizationRequestParametersSchema
460
- .parseAsync(payload)
461
- .catch((err) => {
462
- const msg = formatError(err, 'Invalid parameters in JAR')
463
- throw new InvalidRequestError(msg, err)
464
- })
465
-
466
- return parameters
467
- }
468
-
469
- /**
470
- * @see {@link https://datatracker.ietf.org/doc/html/rfc9126}
471
- */
472
- public async pushedAuthorizationRequest(
473
- credentials: OAuthClientCredentials,
474
- authorizationRequest: OAuthAuthorizationRequestPar,
475
- dpopProof: null | DpopProof,
476
- ): Promise<OAuthParResponse> {
477
- try {
478
- const { client, clientAuth } = await this.authenticateClient(
479
- credentials,
480
- dpopProof,
481
- // Allow missing DPoP header for PAR requests as rfc9449 allows it
482
- // (though the dpop_jkt parameter must be present in that case, see
483
- // check bellow).
484
- { allowMissingDpopProof: true },
485
- )
486
-
487
- const parameters =
488
- 'request' in authorizationRequest // Handle JAR
489
- ? await this.decodeJAR(client, authorizationRequest)
490
- : authorizationRequest
491
-
492
- if (!parameters.dpop_jkt) {
493
- if (client.metadata.dpop_bound_access_tokens) {
494
- if (dpopProof) parameters.dpop_jkt = dpopProof.jkt
495
- else {
496
- // @NOTE When both PAR and DPoP are used, either the DPoP header, or
497
- // the dpop_jkt parameter must be present. We do not enforce this
498
- // for legacy reasons.
499
- // https://datatracker.ietf.org/doc/html/rfc9449#section-10.1
500
- }
501
- }
502
- } else {
503
- if (!client.metadata.dpop_bound_access_tokens) {
504
- throw new InvalidRequestError(
505
- 'DPoP bound access tokens are not enabled for this client',
506
- )
507
- }
508
-
509
- // Proof is optional if the dpop_jkt is provided, but if it is provided,
510
- // it must match the DPoP proof JKT.
511
- if (dpopProof && dpopProof.jkt !== parameters.dpop_jkt) {
512
- throw new InvalidDpopKeyBindingError()
513
- }
514
- }
515
-
516
- const { requestUri, expiresAt } =
517
- await this.requestManager.createAuthorizationRequest(
518
- client,
519
- clientAuth,
520
- parameters,
521
- null,
522
- )
523
-
524
- return {
525
- request_uri: requestUri,
526
- expires_in: dateToRelativeSeconds(expiresAt),
527
- }
528
- } catch (err) {
529
- // https://datatracker.ietf.org/doc/html/rfc9126#section-2.3-1
530
- // > Since initial processing of the pushed authorization request does not
531
- // > involve resource owner interaction, error codes related to user
532
- // > interaction, such as "access_denied", are never returned.
533
- if (err instanceof AuthorizationError && !isPARResponseError(err.error)) {
534
- throw new InvalidRequestError(err.error_description, err)
535
- }
536
- throw err
537
- }
538
- }
539
-
540
- private async processAuthorizationRequest(
541
- client: Client,
542
- deviceId: DeviceId,
543
- query: OAuthAuthorizationRequestQuery,
544
- ) {
545
- // PAR
546
- if ('request_uri' in query) {
547
- const requestUri = parseRequestUri(query.request_uri, {
548
- path: ['query', 'request_uri'],
549
- })
550
- return this.requestManager.get(requestUri, deviceId, client.id)
551
- }
552
-
553
- // JAR
554
- if ('request' in query) {
555
- // @NOTE Since JAR are signed with the client's private key, a JAR *could*
556
- // technically be used to authenticate the client when requests are
557
- // created without PAR (i.e. created on the fly by the authorize
558
- // endpoint). This implementation actually used to support this
559
- // (un-spec'd) behavior. That support was removed:
560
- // - Because it was not actually used
561
- // - Because it was not part of any standard
562
- // - Because it makes extending the client authentication mechanism more
563
- // complex since any extension would not only need to affect the
564
- // "private_key_jwt" auth method but also the JAR "request" object.
565
- const parameters = await this.decodeJAR(client, query)
566
-
567
- return this.requestManager.createAuthorizationRequest(
568
- client,
569
- null,
570
- parameters,
571
- deviceId,
572
- )
573
- }
574
-
575
- // "Regular" authorization request (created on the fly by directing the user
576
- // to the authorization endpoint with all the parameters in the url).
577
- return this.requestManager.createAuthorizationRequest(
578
- client,
579
- null,
580
- query,
581
- deviceId,
582
- )
583
- }
584
-
585
- /**
586
- * @see {@link https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-11#section-4.1.1}
587
- */
588
- public async authorize(
589
- query: OAuthAuthorizationRequestQuery,
590
- { deviceId, deviceMetadata }: DeviceInfo,
591
- ): Promise<AuthorizationResultRedirect | AuthorizationResultAuthorizePage> {
592
- const { issuer } = this
593
-
594
- // If there is a chance to redirect the user to the client, let's do
595
- // it by wrapping the error in an AuthorizationError.
596
- const throwAuthorizationError =
597
- 'redirect_uri' in query
598
- ? (err: unknown): never => {
599
- // https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-11#section-4.1.2.1
600
- throw AuthorizationError.from(query, err)
601
- }
602
- : null
603
-
604
- const client = await this.clientManager
605
- .getClient(query.client_id)
606
- .catch(throwAuthorizationError)
607
-
608
- const { parameters, requestUri } = await this.processAuthorizationRequest(
609
- client,
610
- deviceId,
611
- query,
612
- ).catch(throwAuthorizationError)
613
-
614
- try {
615
- const sessions = (
616
- await this.accountManager.listDeviceAccounts(deviceId)
617
- ).map((deviceAccount) => ({
618
- account: deviceAccount.account,
619
-
620
- // @TODO Return the session expiration date instead of a boolean to
621
- // avoid having to rely on a leeway when "accepting" the request.
622
- loginRequired:
623
- parameters.prompt === 'login' ||
624
- this.checkLoginRequired(deviceAccount),
625
- consentRequired: this.checkConsentRequired(
626
- parameters,
627
- deviceAccount.authorizedClients.get(client.id),
628
- ),
629
- }))
630
-
631
- // https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
632
-
633
- // prompt=select_account
634
- //
635
- // > The Authorization Server SHOULD prompt the End-User to select a user
636
- // > account. This enables an End-User who has multiple accounts at the
637
- // > Authorization Server to select amongst the multiple accounts that
638
- // > they might have current sessions for. If it cannot obtain an account
639
- // > selection choice made by the End-User, it MUST return an error,
640
- // > typically account_selection_required.
641
- if (parameters.prompt === 'select_account' && !sessions.length) {
642
- throw new AccountSelectionRequiredError(parameters)
643
- }
644
-
645
- const ssoSessions = sessions
646
- .filter(hasActiveAccount)
647
- .filter(matchesHint, parameters)
648
-
649
- // prompt=none
650
- //
651
- // > The Authorization Server MUST NOT display any authentication or
652
- // > consent user interface pages. An error is returned if an End-User is
653
- // > not already authenticated or the Client does not have pre-configured
654
- // > consent for the requested Claims or does not fulfill other conditions
655
- // > for processing the request. The error code will typically be
656
- // > login_required, interaction_required, or another code defined in
657
- // > Section 3.1.2.6. This can be used as a method to check for existing
658
- // > authentication and/or consent.
659
- if (parameters.prompt === 'none') {
660
- if (ssoSessions.length > 1) {
661
- throw new AccountSelectionRequiredError(parameters)
662
- }
663
- if (ssoSessions.length < 1) {
664
- throw new LoginRequiredError(parameters)
665
- }
666
-
667
- const ssoSession = ssoSessions[0]!
668
- if (ssoSession.loginRequired) {
669
- throw new LoginRequiredError(parameters)
670
- }
671
- if (ssoSession.consentRequired) {
672
- throw new ConsentRequiredError(parameters)
673
- }
674
-
675
- const code = await this.requestManager.setAuthorized(
676
- requestUri,
677
- client,
678
- ssoSession.account,
679
- deviceId,
680
- deviceMetadata,
681
- )
682
-
683
- return { issuer, parameters, redirect: { code } }
684
- }
685
-
686
- // Automatic SSO when a hint was provided that matches a single session
687
- if (parameters.prompt == null && parameters.login_hint != null) {
688
- if (ssoSessions.length === 1) {
689
- const ssoSession = ssoSessions[0]!
690
- if (!ssoSession.loginRequired && !ssoSession.consentRequired) {
691
- const code = await this.requestManager.setAuthorized(
692
- requestUri,
693
- client,
694
- ssoSession.account,
695
- deviceId,
696
- deviceMetadata,
697
- )
698
-
699
- return { issuer, parameters, redirect: { code } }
700
- }
701
- }
702
- }
703
-
704
- return {
705
- issuer,
706
- client,
707
- parameters,
708
- requestUri,
709
- sessions: sessions
710
- // Strip un-necessary information (like consentRequired)
711
- .map(({ account, loginRequired }) => ({ account, loginRequired })),
712
- selectedDid:
713
- parameters.prompt == null ||
714
- parameters.prompt === 'login' ||
715
- parameters.prompt === 'consent'
716
- ? sessions.find(matchesHint, parameters)?.account.did
717
- : undefined,
718
- permissionSets: await this.lexiconManager
719
- .getPermissionSetsFromScope(parameters.scope)
720
- .catch((cause) => {
721
- throw new AuthorizationError(
722
- parameters,
723
- 'Unable to retrieve permission sets',
724
- 'invalid_scope',
725
- cause,
726
- )
727
- }),
728
- }
729
- } catch (err) {
730
- try {
731
- await this.requestManager.delete(requestUri)
732
- } catch {
733
- // There are two error here. Better keep the outer one.
734
- //
735
- // @TODO Maybe move this entire code to the /authorize endpoint
736
- // (allowing to log this error)
737
- }
738
-
739
- throw AuthorizationError.from(parameters, err)
740
- }
741
- }
742
-
743
- public async token(
744
- clientCredentials: OAuthClientCredentials,
745
- clientMetadata: RequestMetadata,
746
- request: OAuthTokenRequest,
747
- dpopProof: null | DpopProof,
748
- ): Promise<OAuthTokenResponse> {
749
- const { client, clientAuth } = await this.authenticateClient(
750
- clientCredentials,
751
- dpopProof,
752
- )
753
-
754
- if (!this.metadata.grant_types_supported?.includes(request.grant_type)) {
755
- throw new InvalidGrantError(
756
- `Grant type "${request.grant_type}" is not supported by the server`,
757
- )
758
- }
759
-
760
- if (!client.metadata.grant_types.includes(request.grant_type)) {
761
- throw new InvalidGrantError(
762
- `"${request.grant_type}" grant type is not allowed for this client`,
763
- )
764
- }
765
-
766
- if (request.grant_type === 'authorization_code') {
767
- return this.authorizationCodeGrant(
768
- client,
769
- clientAuth,
770
- clientMetadata,
771
- request,
772
- dpopProof,
773
- )
774
- }
775
-
776
- if (request.grant_type === 'refresh_token') {
777
- return this.refreshTokenGrant(
778
- client,
779
- clientAuth,
780
- clientMetadata,
781
- request,
782
- dpopProof,
783
- )
784
- }
785
-
786
- throw new InvalidGrantError(
787
- `Grant type "${request.grant_type}" not supported`,
788
- )
789
- }
790
-
791
- protected async compareClientAuth(
792
- client: Client,
793
- clientAuth: ClientAuth,
794
- dpopProof: null | DpopProof,
795
- initial: {
796
- parameters: OAuthAuthorizationRequestParameters
797
- clientId: ClientId
798
- clientAuth: null | ClientAuth | ClientAuthLegacy
799
- },
800
- ): Promise<void> {
801
- // Fool proofing, ensure that the client is authenticating using the right method
802
- if (clientAuth.method !== client.metadata.token_endpoint_auth_method) {
803
- throw new InvalidGrantError(
804
- `Client authentication method mismatch (expected ${client.metadata.token_endpoint_auth_method}, got ${clientAuth.method})`,
805
- )
806
- }
807
-
808
- if (initial.clientId !== client.id) {
809
- throw new InvalidGrantError(`Token was not issued to this client`)
810
- }
811
-
812
- const { parameters } = initial
813
- if (parameters.dpop_jkt) {
814
- if (!dpopProof) {
815
- throw new InvalidGrantError(`DPoP proof is required for this request`)
816
- } else if (parameters.dpop_jkt !== dpopProof.jkt) {
817
- throw new InvalidGrantError(
818
- `DPoP proof does not match the expected JKT`,
819
- )
820
- }
821
- }
822
-
823
- if (!initial.clientAuth) {
824
- // If the client did not use PAR, it was not authenticated when the request
825
- // was initially created (see authorize() method in OAuthProvider). Since
826
- // PAR is not mandatory, and since the token exchange currently taking place
827
- // *is* authenticated (`clientAuth`), we allow "upgrading" the
828
- // authentication method (the token created will be bound to the current
829
- // clientAuth).
830
- return
831
- }
832
-
833
- switch (initial.clientAuth.method) {
834
- case CLIENT_ASSERTION_TYPE_JWT_BEARER: // LEGACY
835
- case 'private_key_jwt':
836
- if (clientAuth.method !== 'private_key_jwt') {
837
- throw new InvalidGrantError(
838
- `Client authentication method mismatch (expected ${initial.clientAuth.method})`,
839
- )
840
- }
841
- if (
842
- clientAuth.kid !== initial.clientAuth.kid ||
843
- clientAuth.alg !== initial.clientAuth.alg ||
844
- clientAuth.jkt !== initial.clientAuth.jkt
845
- ) {
846
- throw new InvalidGrantError(
847
- `The session was initiated with a different key than the client assertion currently used`,
848
- )
849
- }
850
- break
851
- case 'none':
852
- // @NOTE We allow the client to "upgrade" to a confidential client if
853
- // the session was initially created without client authentication.
854
- break
855
- default:
856
- throw new InvalidGrantError(
857
- // @ts-expect-error (future proof, backwards compatibility)
858
- `Invalid method "${initial.clientAuth.method}"`,
859
- )
860
- }
861
- }
862
-
863
- protected async authorizationCodeGrant(
864
- client: Client,
865
- clientAuth: ClientAuth,
866
- clientMetadata: RequestMetadata,
867
- input: OAuthAuthorizationCodeGrantTokenRequest,
868
- dpopProof: null | DpopProof,
869
- ): Promise<OAuthTokenResponse> {
870
- const code = await codeSchema
871
- .parseAsync(input.code, { path: ['code'] })
872
- .catch((err) => {
873
- const msg = formatError(err, 'Invalid code')
874
- throw new InvalidGrantError(msg, err)
875
- })
876
-
877
- const data = await this.requestManager
878
- .consumeCode(code)
879
- .catch(async (err) => {
880
- // Code not found in request manager: check for replays
881
- const tokenInfo = await this.tokenManager.findByCode(code)
882
- if (tokenInfo) {
883
- // try/finally to ensure that both code path get executed (sequentially)
884
- try {
885
- // "code" was replayed, delete existing session
886
- await this.tokenManager.deleteToken(tokenInfo.id)
887
- } finally {
888
- // As an additional security measure, we also sign the device out,
889
- // so that the device cannot be used to access the account anymore
890
- // without a new authentication.
891
- const { deviceId, did } = tokenInfo.data
892
- if (deviceId) {
893
- await this.accountManager.removeDeviceAccount(deviceId, did)
894
- }
895
- }
896
- }
897
-
898
- throw InvalidGrantError.from(err, `Invalid code`)
899
- })
900
-
901
- // @NOTE at this point, the request data was removed from the store and only
902
- // exists in memory here (in the "data" variable). Because of this, any
903
- // error thrown after this point will permanently cause the request data to
904
- // be lost.
905
-
906
- await this.compareClientAuth(client, clientAuth, dpopProof, data)
907
-
908
- // If the DPoP proof was not provided earlier (PAR / authorize), let's add
909
- // it now.
910
- const parameters =
911
- dpopProof &&
912
- client.metadata.dpop_bound_access_tokens &&
913
- !data.parameters.dpop_jkt
914
- ? { ...data.parameters, dpop_jkt: dpopProof.jkt }
915
- : data.parameters
916
-
917
- await this.validateCodeGrant(parameters, input)
918
-
919
- const { account } = await this.accountManager.getAccount(data.did)
920
-
921
- return this.tokenManager.createToken(
922
- client,
923
- clientAuth,
924
- clientMetadata,
925
- account,
926
- data.deviceId,
927
- parameters,
928
- code,
929
- )
930
- }
931
-
932
- protected async validateCodeGrant(
933
- parameters: OAuthAuthorizationRequestParameters,
934
- input: OAuthAuthorizationCodeGrantTokenRequest,
935
- ): Promise<void> {
936
- if (parameters.redirect_uri !== input.redirect_uri) {
937
- throw new InvalidGrantError(
938
- 'The redirect_uri parameter must match the one used in the authorization request',
939
- )
940
- }
941
-
942
- if (parameters.code_challenge) {
943
- if (!input.code_verifier) {
944
- throw new InvalidGrantError('code_verifier is required')
945
- }
946
- if (input.code_verifier.length < 43) {
947
- throw new InvalidGrantError('code_verifier too short')
948
- }
949
- switch (parameters.code_challenge_method) {
950
- case undefined: // default is "plain"
951
- case 'plain':
952
- if (parameters.code_challenge !== input.code_verifier) {
953
- throw new InvalidGrantError('Invalid code_verifier')
954
- }
955
- break
956
-
957
- case 'S256': {
958
- const inputChallenge = Buffer.from(
959
- parameters.code_challenge,
960
- 'base64',
961
- )
962
- const computedChallenge = createHash('sha256')
963
- .update(input.code_verifier)
964
- .digest()
965
- if (inputChallenge.compare(computedChallenge) !== 0) {
966
- throw new InvalidGrantError('Invalid code_verifier')
967
- }
968
- break
969
- }
970
-
971
- default:
972
- // Should never happen (because request validation should catch this)
973
- throw new Error(`Unsupported code_challenge_method`)
974
- }
975
- const unique = await this.replayManager.uniqueCodeChallenge(
976
- parameters.code_challenge,
977
- )
978
- if (!unique) {
979
- throw new InvalidGrantError('Code challenge already used')
980
- }
981
- } else if (input.code_verifier !== undefined) {
982
- throw new InvalidRequestError("code_challenge parameter wasn't provided")
983
- }
984
- }
985
-
986
- protected async refreshTokenGrant(
987
- client: Client,
988
- clientAuth: ClientAuth,
989
- clientMetadata: RequestMetadata,
990
- input: OAuthRefreshTokenGrantTokenRequest,
991
- dpopProof: null | DpopProof,
992
- ): Promise<OAuthTokenResponse> {
993
- const refreshToken = await refreshTokenSchema
994
- .parseAsync(input.refresh_token, { path: ['refresh_token'] })
995
- .catch((err) => {
996
- const msg = formatError(err, 'Invalid refresh token')
997
- throw new InvalidGrantError(msg, err)
998
- })
999
-
1000
- const tokenInfo = await this.tokenManager.consumeRefreshToken(refreshToken)
1001
-
1002
- try {
1003
- const { data } = tokenInfo
1004
- await this.compareClientAuth(client, clientAuth, dpopProof, data)
1005
- await this.validateRefreshGrant(client, clientAuth, data)
1006
-
1007
- return await this.tokenManager.rotateToken(
1008
- client,
1009
- clientAuth,
1010
- clientMetadata,
1011
- tokenInfo,
1012
- )
1013
- } catch (err) {
1014
- await this.tokenManager.deleteToken(tokenInfo.id)
1015
-
1016
- throw err
1017
- }
1018
- }
1019
-
1020
- protected async validateRefreshGrant(
1021
- client: Client,
1022
- clientAuth: ClientAuth,
1023
- data: TokenData,
1024
- ): Promise<void> {
1025
- const [sessionLifetime, refreshLifetime] =
1026
- clientAuth.method !== 'none' || client.info.isFirstParty
1027
- ? [
1028
- CONFIDENTIAL_CLIENT_SESSION_LIFETIME,
1029
- CONFIDENTIAL_CLIENT_REFRESH_LIFETIME,
1030
- ]
1031
- : [PUBLIC_CLIENT_SESSION_LIFETIME, PUBLIC_CLIENT_REFRESH_LIFETIME]
1032
-
1033
- const sessionAge = Date.now() - data.createdAt.getTime()
1034
- if (sessionAge > sessionLifetime) {
1035
- throw new InvalidGrantError(`Session expired`)
1036
- }
1037
-
1038
- const refreshAge = Date.now() - data.updatedAt.getTime()
1039
- if (refreshAge > refreshLifetime) {
1040
- throw new InvalidGrantError(`Refresh token expired`)
1041
- }
1042
- }
1043
-
1044
- /**
1045
- * @see {@link https://datatracker.ietf.org/doc/html/rfc7009#section-2.1 rfc7009}
1046
- */
1047
- public async revoke(
1048
- clientCredentials: OAuthClientCredentials,
1049
- { token }: OAuthTokenIdentification,
1050
- dpopProof: null | DpopProof,
1051
- ) {
1052
- // > The authorization server first validates the client credentials (in
1053
- // > case of a confidential client)
1054
- const { client, clientAuth } = await this.authenticateClient(
1055
- clientCredentials,
1056
- dpopProof,
1057
- )
1058
-
1059
- const tokenInfo = await this.tokenManager.findToken(token)
1060
- if (tokenInfo) {
1061
- // > [...] and then verifies whether the token was issued to the client
1062
- // > making the revocation request.
1063
- const { data } = tokenInfo
1064
- await this.compareClientAuth(client, clientAuth, dpopProof, data)
1065
-
1066
- // > In the next step, the authorization server invalidates the token. The
1067
- // > invalidation takes place immediately, and the token cannot be used
1068
- // > again after the revocation.
1069
- await this.tokenManager.deleteToken(tokenInfo.id)
1070
- }
1071
- }
1072
-
1073
- protected override async decodeToken(
1074
- tokenType: OAuthTokenType,
1075
- token: OAuthAccessToken,
1076
- dpopProof: null | DpopProof,
1077
- ): Promise<AccessTokenPayload> {
1078
- const tokenPayload = await super.decodeToken(tokenType, token, dpopProof)
1079
-
1080
- if (this.accessTokenMode !== AccessTokenMode.stateless) {
1081
- // @NOTE in non stateless mode, some claims can be omitted (most notably
1082
- // "scope"). We load the token claims here (allowing to ensure that the
1083
- // token is still valid, and to retrieve a (potentially updated) set of
1084
- // claims).
1085
-
1086
- const tokenClaims = await this.tokenManager.loadTokenClaims(
1087
- tokenType,
1088
- tokenPayload,
1089
- )
1090
-
1091
- Object.assign(tokenPayload, tokenClaims)
1092
- }
1093
-
1094
- return tokenPayload
1095
- }
1096
- }
1097
-
1098
- function matchesHint(
1099
- this: OAuthAuthorizationRequestParameters,
1100
- { account }: { account: Account },
1101
- ): boolean {
1102
- const hint = this.login_hint
1103
- if (!hint) return false
1104
-
1105
- return account.did === hint || account.handle === hint
1106
- }
1107
-
1108
- function hasActiveAccount({ account }: { account: Account }): boolean {
1109
- return !account.deactivated
1110
- }