@atproto/oauth-provider 0.2.17 → 0.3.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 (91) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/client/client-manager.d.ts.map +1 -1
  3. package/dist/client/client-manager.js +6 -8
  4. package/dist/client/client-manager.js.map +1 -1
  5. package/dist/device/device-data.d.ts +7 -8
  6. package/dist/device/device-data.d.ts.map +1 -1
  7. package/dist/device/device-data.js +3 -2
  8. package/dist/device/device-data.js.map +1 -1
  9. package/dist/device/device-manager.d.ts +104 -19
  10. package/dist/device/device-manager.d.ts.map +1 -1
  11. package/dist/device/device-manager.js +43 -30
  12. package/dist/device/device-manager.js.map +1 -1
  13. package/dist/lib/http/accept.d.ts +2 -1
  14. package/dist/lib/http/accept.d.ts.map +1 -1
  15. package/dist/lib/http/accept.js.map +1 -1
  16. package/dist/lib/http/method.d.ts +1 -1
  17. package/dist/lib/http/method.d.ts.map +1 -1
  18. package/dist/lib/http/request.d.ts +10 -1
  19. package/dist/lib/http/request.d.ts.map +1 -1
  20. package/dist/lib/http/request.js +38 -0
  21. package/dist/lib/http/request.js.map +1 -1
  22. package/dist/lib/http/response.d.ts +3 -2
  23. package/dist/lib/http/response.d.ts.map +1 -1
  24. package/dist/lib/http/response.js.map +1 -1
  25. package/dist/lib/http/route.d.ts +2 -1
  26. package/dist/lib/http/route.d.ts.map +1 -1
  27. package/dist/lib/http/route.js.map +1 -1
  28. package/dist/lib/http/router.d.ts +2 -1
  29. package/dist/lib/http/router.d.ts.map +1 -1
  30. package/dist/lib/http/router.js.map +1 -1
  31. package/dist/lib/http/stream.d.ts +1 -1
  32. package/dist/lib/http/stream.d.ts.map +1 -1
  33. package/dist/lib/http/types.d.ts +0 -1
  34. package/dist/lib/http/types.d.ts.map +1 -1
  35. package/dist/lib/util/function.d.ts +8 -0
  36. package/dist/lib/util/function.d.ts.map +1 -1
  37. package/dist/lib/util/function.js +1 -1
  38. package/dist/lib/util/function.js.map +1 -1
  39. package/dist/lib/util/time.d.ts +7 -1
  40. package/dist/lib/util/time.d.ts.map +1 -1
  41. package/dist/lib/util/time.js +23 -12
  42. package/dist/lib/util/time.js.map +1 -1
  43. package/dist/oauth-hooks.d.ts +56 -4
  44. package/dist/oauth-hooks.d.ts.map +1 -1
  45. package/dist/oauth-hooks.js +8 -0
  46. package/dist/oauth-hooks.js.map +1 -1
  47. package/dist/oauth-provider.d.ts +10 -7
  48. package/dist/oauth-provider.d.ts.map +1 -1
  49. package/dist/oauth-provider.js +24 -23
  50. package/dist/oauth-provider.js.map +1 -1
  51. package/dist/output/output-manager.d.ts +1 -1
  52. package/dist/output/output-manager.d.ts.map +1 -1
  53. package/dist/output/send-authorize-redirect.d.ts +1 -1
  54. package/dist/output/send-authorize-redirect.d.ts.map +1 -1
  55. package/dist/output/send-web-page.d.ts +1 -1
  56. package/dist/output/send-web-page.d.ts.map +1 -1
  57. package/dist/request/request-manager.d.ts +2 -1
  58. package/dist/request/request-manager.d.ts.map +1 -1
  59. package/dist/request/request-manager.js +9 -1
  60. package/dist/request/request-manager.js.map +1 -1
  61. package/dist/token/token-manager.d.ts +3 -2
  62. package/dist/token/token-manager.d.ts.map +1 -1
  63. package/dist/token/token-manager.js +39 -24
  64. package/dist/token/token-manager.js.map +1 -1
  65. package/package.json +2 -4
  66. package/src/client/client-manager.ts +9 -11
  67. package/src/device/device-data.ts +3 -2
  68. package/src/device/device-manager.ts +93 -75
  69. package/src/lib/http/accept.ts +2 -6
  70. package/src/lib/http/method.ts +1 -1
  71. package/src/lib/http/request.ts +64 -1
  72. package/src/lib/http/response.ts +3 -2
  73. package/src/lib/http/route.ts +2 -1
  74. package/src/lib/http/router.ts +2 -1
  75. package/src/lib/http/stream.ts +1 -1
  76. package/src/lib/http/types.ts +0 -1
  77. package/src/lib/util/function.ts +19 -2
  78. package/src/lib/util/time.ts +35 -18
  79. package/src/oauth-hooks.ts +73 -13
  80. package/src/oauth-provider.ts +69 -28
  81. package/src/output/output-manager.ts +1 -1
  82. package/src/output/send-authorize-redirect.ts +1 -1
  83. package/src/output/send-web-page.ts +1 -1
  84. package/src/request/request-manager.ts +13 -2
  85. package/src/token/token-manager.ts +52 -23
  86. package/tsconfig.backend.tsbuildinfo +1 -1
  87. package/dist/device/device-details.d.ts +0 -16
  88. package/dist/device/device-details.d.ts.map +0 -1
  89. package/dist/device/device-details.js +0 -34
  90. package/dist/device/device-details.js.map +0 -1
  91. package/src/device/device-details.ts +0 -42
@@ -11,21 +11,29 @@ import { ClientId } from './client/client-id.js'
11
11
  import { ClientInfo } from './client/client-info.js'
12
12
  import { Client } from './client/client.js'
13
13
  import { InvalidAuthorizationDetailsError } from './errors/invalid-authorization-details-error.js'
14
+ import { RequestMetadata } from './lib/http/request.js'
14
15
  import { Awaitable } from './lib/util/type.js'
16
+ import { AccessDeniedError, OAuthError } from './oauth-errors.js'
17
+ import { DeviceId } from './oauth-store.js'
15
18
 
16
19
  // Make sure all types needed to implement the OAuthHooks are exported
17
- export type {
18
- Account,
20
+ export {
21
+ AccessDeniedError,
22
+ type Account,
23
+ type Awaitable,
19
24
  Client,
20
- ClientAuth,
21
- ClientId,
22
- ClientInfo,
25
+ type ClientAuth,
26
+ type ClientId,
27
+ type ClientInfo,
28
+ type DeviceId,
23
29
  InvalidAuthorizationDetailsError,
24
- Jwks,
25
- OAuthAuthorizationDetails,
26
- OAuthAuthorizationRequestParameters,
27
- OAuthClientMetadata,
28
- OAuthTokenResponse,
30
+ type Jwks,
31
+ type OAuthAuthorizationDetails,
32
+ type OAuthAuthorizationRequestParameters,
33
+ type OAuthClientMetadata,
34
+ OAuthError,
35
+ type OAuthTokenResponse,
36
+ type RequestMetadata,
29
37
  }
30
38
 
31
39
  export type OAuthHooks = {
@@ -36,10 +44,10 @@ export type OAuthHooks = {
36
44
  * @throws {InvalidClientMetadataError} if the metadata is invalid
37
45
  * @see {@link InvalidClientMetadataError}
38
46
  */
39
- onClientInfo?: (
47
+ getClientInfo?: (
40
48
  clientId: ClientId,
41
49
  data: { metadata: OAuthClientMetadata; jwks?: Jwks },
42
- ) => Awaitable<void | undefined | Partial<ClientInfo>>
50
+ ) => Awaitable<undefined | Partial<ClientInfo>>
43
51
 
44
52
  /**
45
53
  * Allows enriching the authorization details with additional information
@@ -47,9 +55,61 @@ export type OAuthHooks = {
47
55
  *
48
56
  * @see {@link https://datatracker.ietf.org/doc/html/rfc9396 | RFC 9396}
49
57
  */
50
- onAuthorizationDetails?: (data: {
58
+ getAuthorizationDetails?: (data: {
51
59
  client: Client
60
+ clientAuth: ClientAuth
61
+ clientMetadata: RequestMetadata
52
62
  parameters: OAuthAuthorizationRequestParameters
53
63
  account: Account
54
64
  }) => Awaitable<undefined | OAuthAuthorizationDetails>
65
+
66
+ /**
67
+ * This hook is called when a client is authorized.
68
+ *
69
+ * @throws {AccessDeniedError} to deny the authorization request and redirect
70
+ * the user to the client with an OAuth error (other errors will result in an
71
+ * internal server error being displayed to the user)
72
+ *
73
+ * @note We use `deviceMetadata` instead of `clientMetadata` to make it clear
74
+ * that this metadata is from the user device, which might be different from
75
+ * the client metadata (because the OAuth client could live in a backend).
76
+ */
77
+ onAuthorized?: (data: {
78
+ client: Client
79
+ account: Account
80
+ parameters: OAuthAuthorizationRequestParameters
81
+ deviceId: DeviceId
82
+ deviceMetadata: RequestMetadata
83
+ }) => Awaitable<void>
84
+
85
+ /**
86
+ * This hook is called when an authorized client exchanges an authorization
87
+ * code for an access token.
88
+ *
89
+ * @throws {OAuthError} to cancel the token creation and revoke the session
90
+ */
91
+ onTokenCreated?: (data: {
92
+ client: Client
93
+ clientAuth: ClientAuth
94
+ clientMetadata: RequestMetadata
95
+ account: Account
96
+ parameters: OAuthAuthorizationRequestParameters
97
+ /** null when "password grant" used (in which case {@link onAuthorized} won't have been called) */
98
+ deviceId: null | DeviceId
99
+ }) => Awaitable<void>
100
+
101
+ /**
102
+ * This hook is called when an authorized client refreshes an access token.
103
+ *
104
+ * @throws {OAuthError} to cancel the token refresh and revoke the session
105
+ */
106
+ onTokenRefreshed?: (data: {
107
+ client: Client
108
+ clientAuth: ClientAuth
109
+ clientMetadata: RequestMetadata
110
+ account: Account
111
+ parameters: OAuthAuthorizationRequestParameters
112
+ /** null when "password grant" used (in which case {@link onAuthorized} won't have been called) */
113
+ deviceId: null | DeviceId
114
+ }) => Awaitable<void>
55
115
  }
@@ -1,3 +1,4 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http'
1
2
  import { mediaType } from '@hapi/accept'
2
3
  import createHttpError from 'http-errors'
3
4
  import type { Redis, RedisOptions } from 'ioredis'
@@ -54,7 +55,7 @@ import { ClientStore, ifClientStore } from './client/client-store.js'
54
55
  import { Client } from './client/client.js'
55
56
  import { AUTHENTICATION_MAX_AGE, TOKEN_MAX_AGE } from './constants.js'
56
57
  import { DeviceId } from './device/device-id.js'
57
- import { DeviceManager } from './device/device-manager.js'
58
+ import { DeviceManager, DeviceManagerOptions } from './device/device-manager.js'
58
59
  import { DeviceStore, asDeviceStore } from './device/device-store.js'
59
60
  import { AccessDeniedError } from './errors/access-denied-error.js'
60
61
  import { AccountSelectionRequiredError } from './errors/account-selection-required-error.js'
@@ -69,10 +70,8 @@ import { UnauthorizedClientError } from './errors/unauthorized-client-error.js'
69
70
  import { WWWAuthenticateError } from './errors/www-authenticate-error.js'
70
71
  import {
71
72
  Handler,
72
- IncomingMessage,
73
73
  Middleware,
74
74
  Router,
75
- ServerResponse,
76
75
  combineMiddlewares,
77
76
  parseHttpRequest,
78
77
  setupCsrfToken,
@@ -85,6 +84,7 @@ import {
85
84
  validateSameOrigin,
86
85
  writeJson,
87
86
  } from './lib/http/index.js'
87
+ import { RequestMetadata } from './lib/http/request.js'
88
88
  import { dateToEpoch, dateToRelativeSeconds } from './lib/util/date.js'
89
89
  import { Override } from './lib/util/type.js'
90
90
  import { CustomMetadata, buildMetadata } from './metadata/build-metadata.js'
@@ -134,7 +134,7 @@ export {
134
134
  export type RouterOptions<
135
135
  Req extends IncomingMessage = IncomingMessage,
136
136
  Res extends ServerResponse = ServerResponse,
137
- > = {
137
+ > = DeviceManagerOptions & {
138
138
  onError?: (req: Req, res: Res, err: unknown, message: string) => void
139
139
  }
140
140
 
@@ -529,9 +529,10 @@ export class OAuthProvider extends OAuthVerifier {
529
529
  * @see {@link https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-11#section-4.1.1}
530
530
  */
531
531
  protected async authorize(
532
- deviceId: DeviceId,
533
- credentials: OAuthClientCredentialsNone,
532
+ clientCredentials: OAuthClientCredentialsNone,
534
533
  query: OAuthAuthorizationRequestQuery,
534
+ deviceId: DeviceId,
535
+ deviceMetadata: RequestMetadata,
535
536
  ): Promise<AuthorizationResultRedirect | AuthorizationResultAuthorize> {
536
537
  const { issuer } = this
537
538
 
@@ -546,7 +547,7 @@ export class OAuthProvider extends OAuthVerifier {
546
547
  : null
547
548
 
548
549
  const client = await this.clientManager
549
- .getClient(credentials.client_id)
550
+ .getClient(clientCredentials.client_id)
550
551
  .catch(accessDeniedCatcher)
551
552
 
552
553
  const { clientAuth, parameters, uri } =
@@ -580,10 +581,11 @@ export class OAuthProvider extends OAuthVerifier {
580
581
  }
581
582
 
582
583
  const code = await this.requestManager.setAuthorized(
583
- client,
584
584
  uri,
585
- deviceId,
585
+ client,
586
586
  ssoSession.account,
587
+ deviceId,
588
+ deviceMetadata,
587
589
  )
588
590
 
589
591
  return { issuer, client, parameters, redirect: { code } }
@@ -596,10 +598,11 @@ export class OAuthProvider extends OAuthVerifier {
596
598
  const ssoSession = ssoSessions[0]!
597
599
  if (!ssoSession.loginRequired && !ssoSession.consentRequired) {
598
600
  const code = await this.requestManager.setAuthorized(
599
- client,
600
601
  uri,
601
- deviceId,
602
+ client,
602
603
  ssoSession.account,
604
+ deviceId,
605
+ deviceMetadata,
603
606
  )
604
607
 
605
608
  return { issuer, client, parameters, redirect: { code } }
@@ -720,10 +723,11 @@ export class OAuthProvider extends OAuthVerifier {
720
723
  }
721
724
 
722
725
  protected async acceptRequest(
723
- deviceId: DeviceId,
724
726
  uri: RequestUri,
725
727
  clientId: ClientId,
726
728
  sub: string,
729
+ deviceId: DeviceId,
730
+ deviceMetadata: RequestMetadata,
727
731
  ): Promise<AuthorizationResultRedirect> {
728
732
  const { issuer } = this
729
733
  const client = await this.clientManager.getClient(clientId)
@@ -746,10 +750,11 @@ export class OAuthProvider extends OAuthVerifier {
746
750
  }
747
751
 
748
752
  const code = await this.requestManager.setAuthorized(
749
- client,
750
753
  uri,
751
- deviceId,
754
+ client,
752
755
  account,
756
+ deviceId,
757
+ deviceMetadata,
753
758
  )
754
759
 
755
760
  await this.accountManager.addAuthorizedClient(
@@ -791,11 +796,13 @@ export class OAuthProvider extends OAuthVerifier {
791
796
  }
792
797
 
793
798
  protected async token(
794
- credentials: OAuthClientCredentials,
799
+ clientCredentials: OAuthClientCredentials,
800
+ clientMetadata: RequestMetadata,
795
801
  request: OAuthTokenRequest,
796
802
  dpopJkt: null | string,
797
803
  ): Promise<OAuthTokenResponse> {
798
- const [client, clientAuth] = await this.authenticateClient(credentials)
804
+ const [client, clientAuth] =
805
+ await this.authenticateClient(clientCredentials)
799
806
 
800
807
  if (!this.metadata.grant_types_supported?.includes(request.grant_type)) {
801
808
  throw new InvalidGrantError(
@@ -810,11 +817,23 @@ export class OAuthProvider extends OAuthVerifier {
810
817
  }
811
818
 
812
819
  if (request.grant_type === 'authorization_code') {
813
- return this.codeGrant(client, clientAuth, request, dpopJkt)
820
+ return this.codeGrant(
821
+ client,
822
+ clientAuth,
823
+ clientMetadata,
824
+ request,
825
+ dpopJkt,
826
+ )
814
827
  }
815
828
 
816
829
  if (request.grant_type === 'refresh_token') {
817
- return this.refreshTokenGrant(client, clientAuth, request, dpopJkt)
830
+ return this.refreshTokenGrant(
831
+ client,
832
+ clientAuth,
833
+ clientMetadata,
834
+ request,
835
+ dpopJkt,
836
+ )
818
837
  }
819
838
 
820
839
  throw new InvalidGrantError(
@@ -825,6 +844,7 @@ export class OAuthProvider extends OAuthVerifier {
825
844
  protected async codeGrant(
826
845
  client: Client,
827
846
  clientAuth: ClientAuth,
847
+ clientMetadata: RequestMetadata,
828
848
  input: OAuthAuthorizationCodeGrantTokenRequest,
829
849
  dpopJkt: null | string,
830
850
  ): Promise<OAuthTokenResponse> {
@@ -868,6 +888,7 @@ export class OAuthProvider extends OAuthVerifier {
868
888
  return await this.tokenManager.create(
869
889
  client,
870
890
  clientAuth,
891
+ clientMetadata,
871
892
  account,
872
893
  { id: deviceId, info },
873
894
  parameters,
@@ -890,10 +911,17 @@ export class OAuthProvider extends OAuthVerifier {
890
911
  async refreshTokenGrant(
891
912
  client: Client,
892
913
  clientAuth: ClientAuth,
914
+ clientMetadata: RequestMetadata,
893
915
  input: OAuthRefreshTokenGrantTokenRequest,
894
916
  dpopJkt: null | string,
895
917
  ): Promise<OAuthTokenResponse> {
896
- return this.tokenManager.refresh(client, clientAuth, input, dpopJkt)
918
+ return this.tokenManager.refresh(
919
+ client,
920
+ clientAuth,
921
+ clientMetadata,
922
+ input,
923
+ dpopJkt,
924
+ )
897
925
  }
898
926
 
899
927
  /**
@@ -998,7 +1026,7 @@ export class OAuthProvider extends OAuthVerifier {
998
1026
  Req extends IncomingMessage = IncomingMessage,
999
1027
  Res extends ServerResponse = ServerResponse,
1000
1028
  >(options?: RouterOptions<Req, Res>) {
1001
- const deviceManager = new DeviceManager(this.deviceStore)
1029
+ const deviceManager = new DeviceManager(this.deviceStore, options)
1002
1030
  const outputManager = new OutputManager(this.customization)
1003
1031
 
1004
1032
  // eslint-disable-next-line @typescript-eslint/no-this-alias
@@ -1224,7 +1252,9 @@ export class OAuthProvider extends OAuthVerifier {
1224
1252
  jsonHandler(async function (req, _res) {
1225
1253
  const payload = await parseHttpRequest(req, ['json', 'urlencoded'])
1226
1254
 
1227
- const credentials = await oauthClientCredentialsSchema
1255
+ const clientMetadata = await deviceManager.getRequestMetadata(req)
1256
+
1257
+ const clientCredentials = await oauthClientCredentialsSchema
1228
1258
  .parseAsync(payload, { path: ['body'] })
1229
1259
  .catch(throwInvalidClient)
1230
1260
 
@@ -1238,7 +1268,12 @@ export class OAuthProvider extends OAuthVerifier {
1238
1268
  this.url,
1239
1269
  )
1240
1270
 
1241
- return server.token(credentials, tokenRequest, dpopJkt)
1271
+ return server.token(
1272
+ clientCredentials,
1273
+ clientMetadata,
1274
+ tokenRequest,
1275
+ dpopJkt,
1276
+ )
1242
1277
  }),
1243
1278
  )
1244
1279
 
@@ -1313,11 +1348,11 @@ export class OAuthProvider extends OAuthVerifier {
1313
1348
 
1314
1349
  const query = Object.fromEntries(this.url.searchParams)
1315
1350
 
1316
- const credentials = await oauthClientCredentialsSchema
1351
+ const clientCredentials = await oauthClientCredentialsSchema
1317
1352
  .parseAsync(query, { path: ['body'] })
1318
1353
  .catch(throwInvalidRequest)
1319
1354
 
1320
- if ('client_secret' in credentials) {
1355
+ if ('client_secret' in clientCredentials) {
1321
1356
  throw new InvalidRequestError('Client secret must not be provided')
1322
1357
  }
1323
1358
 
@@ -1325,10 +1360,15 @@ export class OAuthProvider extends OAuthVerifier {
1325
1360
  .parseAsync(query, { path: ['query'] })
1326
1361
  .catch(throwInvalidRequest)
1327
1362
 
1328
- const { deviceId } = await deviceManager.load(req, res)
1363
+ const { deviceId, deviceMetadata } = await deviceManager.load(req, res)
1329
1364
 
1330
1365
  const data = await server
1331
- .authorize(deviceId, credentials, authorizationRequest)
1366
+ .authorize(
1367
+ clientCredentials,
1368
+ authorizationRequest,
1369
+ deviceId,
1370
+ deviceMetadata,
1371
+ )
1332
1372
  .catch((err) => accessDeniedToRedirectCatcher(req, res, err))
1333
1373
 
1334
1374
  switch (true) {
@@ -1431,14 +1471,15 @@ export class OAuthProvider extends OAuthVerifier {
1431
1471
  true,
1432
1472
  )
1433
1473
 
1434
- const { deviceId } = await deviceManager.load(req, res)
1474
+ const { deviceId, deviceMetadata } = await deviceManager.load(req, res)
1435
1475
 
1436
1476
  const data = await server
1437
1477
  .acceptRequest(
1438
- deviceId,
1439
1478
  input.request_uri,
1440
1479
  input.client_id,
1441
1480
  input.account_sub,
1481
+ deviceId,
1482
+ deviceMetadata,
1442
1483
  )
1443
1484
  .catch((err) => accessDeniedToRedirectCatcher(req, res, err))
1444
1485
 
@@ -1,4 +1,4 @@
1
- import { ServerResponse } from 'node:http'
1
+ import type { ServerResponse } from 'node:http'
2
2
  import { Asset } from '../assets/asset.js'
3
3
  import { getAsset } from '../assets/index.js'
4
4
  import { Html, cssCode, html } from '../lib/html/index.js'
@@ -1,4 +1,4 @@
1
- import { ServerResponse } from 'node:http'
1
+ import type { ServerResponse } from 'node:http'
2
2
  import {
3
3
  OAuthAuthorizationRequestParameters,
4
4
  OAuthTokenType,
@@ -1,5 +1,5 @@
1
1
  import { createHash } from 'node:crypto'
2
- import { ServerResponse } from 'node:http'
2
+ import type { ServerResponse } from 'node:http'
3
3
  import {
4
4
  AssetRef,
5
5
  BuildDocumentOptions,
@@ -20,6 +20,8 @@ import { InvalidGrantError } from '../errors/invalid-grant-error.js'
20
20
  import { InvalidParametersError } from '../errors/invalid-parameters-error.js'
21
21
  import { InvalidRequestError } from '../errors/invalid-request-error.js'
22
22
  import { InvalidScopeError } from '../errors/invalid-scope-error.js'
23
+ import { RequestMetadata } from '../lib/http/request.js'
24
+ import { callAsync } from '../lib/util/function.js'
23
25
  import { OAuthHooks } from '../oauth-hooks.js'
24
26
  import { Signer } from '../signer/signer.js'
25
27
  import { Code, generateCode } from './code.js'
@@ -369,10 +371,11 @@ export class RequestManager {
369
371
  }
370
372
 
371
373
  async setAuthorized(
372
- client: Client,
373
374
  uri: RequestUri,
374
- deviceId: DeviceId,
375
+ client: Client,
375
376
  account: Account,
377
+ deviceId: DeviceId,
378
+ deviceMetadata: RequestMetadata,
376
379
  ): Promise<Code> {
377
380
  const id = decodeRequestUri(uri)
378
381
 
@@ -413,6 +416,14 @@ export class RequestManager {
413
416
  expiresAt: new Date(Date.now() + AUTHORIZATION_INACTIVITY_TIMEOUT),
414
417
  })
415
418
 
419
+ await callAsync(this.hooks.onAuthorized, {
420
+ client,
421
+ account,
422
+ parameters: data.parameters,
423
+ deviceId,
424
+ deviceMetadata,
425
+ })
426
+
416
427
  return code
417
428
  } catch (err) {
418
429
  await this.store.deleteRequest(id)
@@ -29,6 +29,7 @@ import { InvalidDpopProofError } from '../errors/invalid-dpop-proof-error.js'
29
29
  import { InvalidGrantError } from '../errors/invalid-grant-error.js'
30
30
  import { InvalidRequestError } from '../errors/invalid-request-error.js'
31
31
  import { InvalidTokenError } from '../errors/invalid-token-error.js'
32
+ import { RequestMetadata } from '../lib/http/request.js'
32
33
  import { dateToEpoch, dateToRelativeSeconds } from '../lib/util/date.js'
33
34
  import { callAsync } from '../lib/util/function.js'
34
35
  import { OAuthHooks } from '../oauth-hooks.js'
@@ -82,6 +83,7 @@ export class TokenManager {
82
83
  async create(
83
84
  client: Client,
84
85
  clientAuth: ClientAuth,
86
+ clientMetadata: RequestMetadata,
85
87
  account: Account,
86
88
  device: null | { id: DeviceId; info: DeviceAccountInfo },
87
89
  parameters: OAuthAuthorizationRequestParameters,
@@ -207,13 +209,16 @@ export class TokenManager {
207
209
  const now = new Date()
208
210
  const expiresAt = this.createTokenExpiry(now)
209
211
 
210
- const authorizationDetails = this.hooks.onAuthorizationDetails
211
- ? await callAsync(this.hooks.onAuthorizationDetails, {
212
- client,
213
- parameters,
214
- account,
215
- })
216
- : undefined
212
+ const authorizationDetails = await callAsync(
213
+ this.hooks.getAuthorizationDetails,
214
+ {
215
+ client,
216
+ clientAuth,
217
+ clientMetadata,
218
+ parameters,
219
+ account,
220
+ },
221
+ )
217
222
 
218
223
  const tokenData: TokenData = {
219
224
  createdAt: now,
@@ -246,7 +251,7 @@ export class TokenManager {
246
251
  authorization_details: authorizationDetails,
247
252
  })
248
253
 
249
- return this.buildTokenResponse(
254
+ const response = await this.buildTokenResponse(
250
255
  client,
251
256
  accessToken,
252
257
  refreshToken,
@@ -255,6 +260,17 @@ export class TokenManager {
255
260
  account,
256
261
  authorizationDetails,
257
262
  )
263
+
264
+ await callAsync(this.hooks.onTokenCreated, {
265
+ client,
266
+ clientAuth,
267
+ clientMetadata,
268
+ account,
269
+ parameters,
270
+ deviceId: device ? device.id : null,
271
+ })
272
+
273
+ return response
258
274
  } catch (err) {
259
275
  // Just in case the token could not be issued, we delete it from the store
260
276
  await this.store.deleteToken(tokenId)
@@ -316,6 +332,7 @@ export class TokenManager {
316
332
  async refresh(
317
333
  client: Client,
318
334
  clientAuth: ClientAuth,
335
+ clientMetadata: RequestMetadata,
319
336
  input: OAuthRefreshTokenGrantTokenRequest,
320
337
  dpopJkt: null | string,
321
338
  ): Promise<OAuthTokenResponse> {
@@ -377,13 +394,16 @@ export class TokenManager {
377
394
  throw new InvalidGrantError(`Refresh token expired`)
378
395
  }
379
396
 
380
- const authorization_details = this.hooks.onAuthorizationDetails
381
- ? await callAsync(this.hooks.onAuthorizationDetails, {
382
- client,
383
- parameters,
384
- account,
385
- })
386
- : undefined
397
+ const authorizationDetails = await callAsync(
398
+ this.hooks.getAuthorizationDetails,
399
+ {
400
+ client,
401
+ clientAuth,
402
+ clientMetadata,
403
+ parameters,
404
+ account,
405
+ },
406
+ )
387
407
 
388
408
  const nextTokenId = await generateTokenId()
389
409
  const nextRefreshToken = await generateRefreshToken()
@@ -426,24 +446,33 @@ export class TokenManager {
426
446
  iat: now,
427
447
  jti: nextTokenId,
428
448
  cnf: parameters.dpop_jkt ? { jkt: parameters.dpop_jkt } : undefined,
429
- authorization_details,
449
+ authorization_details: authorizationDetails,
430
450
  })
431
451
 
432
- return this.buildTokenResponse(
452
+ const response = await this.buildTokenResponse(
433
453
  client,
434
454
  accessToken,
435
455
  nextRefreshToken,
436
456
  expiresAt,
437
457
  parameters,
438
458
  account,
439
- authorization_details,
459
+ authorizationDetails,
440
460
  )
461
+
462
+ await callAsync(this.hooks.onTokenRefreshed, {
463
+ client,
464
+ clientAuth,
465
+ clientMetadata,
466
+ account,
467
+ parameters,
468
+ deviceId: tokenInfo.data.deviceId,
469
+ })
470
+
471
+ return response
441
472
  } catch (err) {
442
- if (err instanceof InvalidRequestError) {
443
- // Consider the refresh token might be compromised if sanity checks
444
- // failed.
445
- await this.store.deleteToken(tokenInfo.id)
446
- }
473
+ // Just in case the token could not be refreshed, we delete it from the store
474
+ await this.store.deleteToken(tokenInfo.id)
475
+
447
476
  throw err
448
477
  }
449
478
  }
@@ -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-provider.ts","./src/oauth-store.ts","./src/oauth-verifier.ts","./src/access-token/access-token-type.ts","./src/account/account-manager.ts","./src/account/account-store.ts","./src/account/account.ts","./src/assets/asset.ts","./src/assets/assets-middleware.ts","./src/assets/index.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/device/device-data.ts","./src/device/device-details.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/errors/access-denied-error.ts","./src/errors/account-selection-required-error.ts","./src/errors/consent-required-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-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/redis.ts","./src/lib/html/build-document.ts","./src/lib/html/escapers.ts","./src/lib/html/html.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/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/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/crypto.ts","./src/lib/util/date.ts","./src/lib/util/function.ts","./src/lib/util/hostname.ts","./src/lib/util/redirect-uri.ts","./src/lib/util/time.ts","./src/lib/util/type.ts","./src/lib/util/well-known.ts","./src/metadata/build-metadata.ts","./src/oidc/sub.ts","./src/output/build-authorize-data.ts","./src/output/build-error-payload.ts","./src/output/customization.ts","./src/output/output-manager.ts","./src/output/send-authorize-redirect.ts","./src/output/send-web-page.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/signer/signed-token-payload.ts","./src/signer/signer.ts","./src/token/refresh-token.ts","./src/token/token-claims.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"],"version":"5.6.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-provider.ts","./src/oauth-store.ts","./src/oauth-verifier.ts","./src/access-token/access-token-type.ts","./src/account/account-manager.ts","./src/account/account-store.ts","./src/account/account.ts","./src/assets/asset.ts","./src/assets/assets-middleware.ts","./src/assets/index.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/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/errors/access-denied-error.ts","./src/errors/account-selection-required-error.ts","./src/errors/consent-required-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-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/redis.ts","./src/lib/html/build-document.ts","./src/lib/html/escapers.ts","./src/lib/html/html.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/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/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/crypto.ts","./src/lib/util/date.ts","./src/lib/util/function.ts","./src/lib/util/hostname.ts","./src/lib/util/redirect-uri.ts","./src/lib/util/time.ts","./src/lib/util/type.ts","./src/lib/util/well-known.ts","./src/metadata/build-metadata.ts","./src/oidc/sub.ts","./src/output/build-authorize-data.ts","./src/output/build-error-payload.ts","./src/output/customization.ts","./src/output/output-manager.ts","./src/output/send-authorize-redirect.ts","./src/output/send-web-page.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/signer/signed-token-payload.ts","./src/signer/signer.ts","./src/token/refresh-token.ts","./src/token/token-claims.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"],"version":"5.6.3"}
@@ -1,16 +0,0 @@
1
- import { IncomingMessage } from 'node:http';
2
- import { z } from 'zod';
3
- export declare const deviceDetailsSchema: z.ZodObject<{
4
- userAgent: z.ZodNullable<z.ZodString>;
5
- ipAddress: z.ZodString;
6
- }, "strip", z.ZodTypeAny, {
7
- userAgent: string | null;
8
- ipAddress: string;
9
- }, {
10
- userAgent: string | null;
11
- ipAddress: string;
12
- }>;
13
- export type DeviceDetails = z.infer<typeof deviceDetailsSchema>;
14
- export declare function extractDeviceDetails(req: IncomingMessage, trustProxy: boolean): DeviceDetails;
15
- export declare function extractIpAddress(req: IncomingMessage, trustProxy: boolean): string | undefined;
16
- //# sourceMappingURL=device-details.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"device-details.d.ts","sourceRoot":"","sources":["../../src/device/device-details.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAC3C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,eAAO,MAAM,mBAAmB;;;;;;;;;EAG9B,CAAA;AACF,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAA;AAE/D,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,eAAe,EACpB,UAAU,EAAE,OAAO,GAClB,aAAa,CASf;AAED,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,eAAe,EACpB,UAAU,EAAE,OAAO,GAClB,MAAM,GAAG,SAAS,CAepB"}
@@ -1,34 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.deviceDetailsSchema = void 0;
4
- exports.extractDeviceDetails = extractDeviceDetails;
5
- exports.extractIpAddress = extractIpAddress;
6
- const zod_1 = require("zod");
7
- exports.deviceDetailsSchema = zod_1.z.object({
8
- userAgent: zod_1.z.string().nullable(),
9
- ipAddress: zod_1.z.string(),
10
- });
11
- function extractDeviceDetails(req, trustProxy) {
12
- const userAgent = req.headers['user-agent'] || null;
13
- const ipAddress = extractIpAddress(req, trustProxy) || null;
14
- if (!ipAddress) {
15
- throw new Error('Could not determine IP address');
16
- }
17
- return { userAgent, ipAddress };
18
- }
19
- function extractIpAddress(req, trustProxy) {
20
- // Express app compatibility
21
- if ('ip' in req && typeof req.ip === 'string') {
22
- return req.ip;
23
- }
24
- if (trustProxy) {
25
- const forwardedFor = req.headers['x-forwarded-for'];
26
- if (typeof forwardedFor === 'string') {
27
- const firstForward = forwardedFor.split(',')[0].trim();
28
- if (firstForward)
29
- return firstForward;
30
- }
31
- }
32
- return req.socket.remoteAddress;
33
- }
34
- //# sourceMappingURL=device-details.js.map