@atproto/oauth-provider 0.13.1 → 0.13.3

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 (183) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/dist/access-token/access-token-mode.js.map +1 -1
  3. package/dist/account/account-manager.d.ts.map +1 -1
  4. package/dist/account/account-manager.js +20 -2
  5. package/dist/account/account-manager.js.map +1 -1
  6. package/dist/account/account-store.d.ts +2 -2
  7. package/dist/account/account-store.d.ts.map +1 -1
  8. package/dist/account/account-store.js.map +1 -1
  9. package/dist/account/sign-in-data.js.map +1 -1
  10. package/dist/account/sign-up-input.js.map +1 -1
  11. package/dist/client/client-auth.js.map +1 -1
  12. package/dist/client/client-data.js.map +1 -1
  13. package/dist/client/client-id.js.map +1 -1
  14. package/dist/client/client-info.js.map +1 -1
  15. package/dist/client/client-manager.d.ts.map +1 -1
  16. package/dist/client/client-manager.js +31 -53
  17. package/dist/client/client-manager.js.map +1 -1
  18. package/dist/client/client-store.js.map +1 -1
  19. package/dist/client/client-utils.d.ts.map +1 -1
  20. package/dist/client/client-utils.js +1 -2
  21. package/dist/client/client-utils.js.map +1 -1
  22. package/dist/client/client.js.map +1 -1
  23. package/dist/constants.js.map +1 -1
  24. package/dist/customization/branding.js.map +1 -1
  25. package/dist/customization/build-customization-css.js.map +1 -1
  26. package/dist/customization/build-customization-data.js.map +1 -1
  27. package/dist/customization/colors.js.map +1 -1
  28. package/dist/customization/customization.js.map +1 -1
  29. package/dist/customization/links.js.map +1 -1
  30. package/dist/device/device-data.js.map +1 -1
  31. package/dist/device/device-id.js.map +1 -1
  32. package/dist/device/device-manager.d.ts +8 -8
  33. package/dist/device/device-manager.js.map +1 -1
  34. package/dist/device/device-store.js.map +1 -1
  35. package/dist/device/session-id.js.map +1 -1
  36. package/dist/dpop/dpop-manager.js.map +1 -1
  37. package/dist/dpop/dpop-nonce.js.map +1 -1
  38. package/dist/dpop/dpop-proof.js.map +1 -1
  39. package/dist/errors/access-denied-error.js.map +1 -1
  40. package/dist/errors/account-selection-required-error.js.map +1 -1
  41. package/dist/errors/authorization-error.js.map +1 -1
  42. package/dist/errors/consent-required-error.js.map +1 -1
  43. package/dist/errors/error-parser.js.map +1 -1
  44. package/dist/errors/handle-unavailable-error.js.map +1 -1
  45. package/dist/errors/invalid-authorization-details-error.js.map +1 -1
  46. package/dist/errors/invalid-client-error.js.map +1 -1
  47. package/dist/errors/invalid-client-id-error.js.map +1 -1
  48. package/dist/errors/invalid-client-metadata-error.js.map +1 -1
  49. package/dist/errors/invalid-dpop-key-binding-error.js.map +1 -1
  50. package/dist/errors/invalid-dpop-proof-error.js.map +1 -1
  51. package/dist/errors/invalid-grant-error.js.map +1 -1
  52. package/dist/errors/invalid-invite-code-error.js.map +1 -1
  53. package/dist/errors/invalid-redirect-uri-error.js.map +1 -1
  54. package/dist/errors/invalid-request-error.js.map +1 -1
  55. package/dist/errors/invalid-scope-error.js.map +1 -1
  56. package/dist/errors/invalid-token-error.js.map +1 -1
  57. package/dist/errors/login-required-error.js.map +1 -1
  58. package/dist/errors/oauth-error.js.map +1 -1
  59. package/dist/errors/second-authentication-factor-required-error.js.map +1 -1
  60. package/dist/errors/unauthorized-client-error.js.map +1 -1
  61. package/dist/errors/use-dpop-nonce-error.js.map +1 -1
  62. package/dist/errors/www-authenticate-error.js.map +1 -1
  63. package/dist/index.js.map +1 -1
  64. package/dist/lexicon/lexicon-data.js.map +1 -1
  65. package/dist/lexicon/lexicon-getter.js.map +1 -1
  66. package/dist/lexicon/lexicon-manager.js.map +1 -1
  67. package/dist/lexicon/lexicon-store.js.map +1 -1
  68. package/dist/lib/csp/index.js.map +1 -1
  69. package/dist/lib/hcaptcha.js.map +1 -1
  70. package/dist/lib/html/build-document.js.map +1 -1
  71. package/dist/lib/html/escapers.js.map +1 -1
  72. package/dist/lib/html/html.js.map +1 -1
  73. package/dist/lib/html/hydration-data.js.map +1 -1
  74. package/dist/lib/html/index.js.map +1 -1
  75. package/dist/lib/html/tags.js.map +1 -1
  76. package/dist/lib/html/util.js.map +1 -1
  77. package/dist/lib/http/accept.js.map +1 -1
  78. package/dist/lib/http/context.js.map +1 -1
  79. package/dist/lib/http/headers.js.map +1 -1
  80. package/dist/lib/http/index.js.map +1 -1
  81. package/dist/lib/http/method.js.map +1 -1
  82. package/dist/lib/http/middleware.js.map +1 -1
  83. package/dist/lib/http/parser.js.map +1 -1
  84. package/dist/lib/http/path.js.map +1 -1
  85. package/dist/lib/http/request.js.map +1 -1
  86. package/dist/lib/http/response.js.map +1 -1
  87. package/dist/lib/http/route.js.map +1 -1
  88. package/dist/lib/http/router.js.map +1 -1
  89. package/dist/lib/http/security-headers.js.map +1 -1
  90. package/dist/lib/http/stream.js.map +1 -1
  91. package/dist/lib/http/types.js.map +1 -1
  92. package/dist/lib/http/url.js.map +1 -1
  93. package/dist/lib/nsid.js.map +1 -1
  94. package/dist/lib/redis.js.map +1 -1
  95. package/dist/lib/send-web-page.js.map +1 -1
  96. package/dist/lib/util/authorization-header.js.map +1 -1
  97. package/dist/lib/util/cast.js.map +1 -1
  98. package/dist/lib/util/color.js.map +1 -1
  99. package/dist/lib/util/crypto.js.map +1 -1
  100. package/dist/lib/util/date.js.map +1 -1
  101. package/dist/lib/util/error.js.map +1 -1
  102. package/dist/lib/util/function.js.map +1 -1
  103. package/dist/lib/util/locale.js.map +1 -1
  104. package/dist/lib/util/redirect-uri.js.map +1 -1
  105. package/dist/lib/util/time.js.map +1 -1
  106. package/dist/lib/util/type.js.map +1 -1
  107. package/dist/lib/util/ui8.js.map +1 -1
  108. package/dist/lib/util/well-known.js.map +1 -1
  109. package/dist/lib/util/zod-error.js.map +1 -1
  110. package/dist/metadata/build-metadata.js +4 -3
  111. package/dist/metadata/build-metadata.js.map +1 -1
  112. package/dist/oauth-client.js.map +1 -1
  113. package/dist/oauth-dpop.js.map +1 -1
  114. package/dist/oauth-errors.js.map +1 -1
  115. package/dist/oauth-hooks.d.ts +20 -0
  116. package/dist/oauth-hooks.d.ts.map +1 -1
  117. package/dist/oauth-hooks.js.map +1 -1
  118. package/dist/oauth-middleware.js.map +1 -1
  119. package/dist/oauth-provider.d.ts +128 -98
  120. package/dist/oauth-provider.d.ts.map +1 -1
  121. package/dist/oauth-provider.js.map +1 -1
  122. package/dist/oauth-store.js.map +1 -1
  123. package/dist/oauth-verifier.js.map +1 -1
  124. package/dist/oidc/sub.js.map +1 -1
  125. package/dist/replay/replay-manager.js.map +1 -1
  126. package/dist/replay/replay-store-memory.js.map +1 -1
  127. package/dist/replay/replay-store-redis.js.map +1 -1
  128. package/dist/replay/replay-store.js.map +1 -1
  129. package/dist/request/code.js.map +1 -1
  130. package/dist/request/request-data.js.map +1 -1
  131. package/dist/request/request-id.js.map +1 -1
  132. package/dist/request/request-manager.d.ts +18 -18
  133. package/dist/request/request-manager.d.ts.map +1 -1
  134. package/dist/request/request-manager.js.map +1 -1
  135. package/dist/request/request-store.js.map +1 -1
  136. package/dist/request/request-uri.js.map +1 -1
  137. package/dist/result/authorization-redirect-parameters.js.map +1 -1
  138. package/dist/result/authorization-result-authorize-page.js.map +1 -1
  139. package/dist/result/authorization-result-redirect.js.map +1 -1
  140. package/dist/router/assets/assets-manifest.js.map +1 -1
  141. package/dist/router/assets/assets.js.map +1 -1
  142. package/dist/router/assets/csrf.js.map +1 -1
  143. package/dist/router/assets/send-account-page.js.map +1 -1
  144. package/dist/router/assets/send-authorization-page.js.map +1 -1
  145. package/dist/router/assets/send-error-page.js.map +1 -1
  146. package/dist/router/create-account-page-middleware.js.map +1 -1
  147. package/dist/router/create-api-middleware.js.map +1 -1
  148. package/dist/router/create-authorization-page-middleware.js.map +1 -1
  149. package/dist/router/create-oauth-middleware.js.map +1 -1
  150. package/dist/router/error-handler.js.map +1 -1
  151. package/dist/router/middleware-options.js.map +1 -1
  152. package/dist/router/send-redirect.js.map +1 -1
  153. package/dist/signer/access-token-payload.d.ts +4113 -1362
  154. package/dist/signer/access-token-payload.d.ts.map +1 -1
  155. package/dist/signer/access-token-payload.js.map +1 -1
  156. package/dist/signer/api-token-payload.d.ts +3974 -1223
  157. package/dist/signer/api-token-payload.d.ts.map +1 -1
  158. package/dist/signer/api-token-payload.js.map +1 -1
  159. package/dist/signer/signer.d.ts +46 -26
  160. package/dist/signer/signer.d.ts.map +1 -1
  161. package/dist/signer/signer.js.map +1 -1
  162. package/dist/token/refresh-token.js.map +1 -1
  163. package/dist/token/token-claims.js.map +1 -1
  164. package/dist/token/token-data.js.map +1 -1
  165. package/dist/token/token-id.js.map +1 -1
  166. package/dist/token/token-manager.js.map +1 -1
  167. package/dist/token/token-store.js.map +1 -1
  168. package/dist/types/authorization-response-error.js.map +1 -1
  169. package/dist/types/color-hue.js.map +1 -1
  170. package/dist/types/email-otp.js.map +1 -1
  171. package/dist/types/email.js.map +1 -1
  172. package/dist/types/handle.js.map +1 -1
  173. package/dist/types/invite-code.js.map +1 -1
  174. package/dist/types/par-response-error.js.map +1 -1
  175. package/dist/types/password.js.map +1 -1
  176. package/dist/types/rgb-color.js.map +1 -1
  177. package/package.json +11 -11
  178. package/src/account/account-manager.ts +24 -2
  179. package/src/account/account-store.ts +7 -2
  180. package/src/client/client-manager.ts +41 -71
  181. package/src/client/client-utils.ts +1 -1
  182. package/src/metadata/build-metadata.ts +4 -4
  183. package/src/oauth-hooks.ts +22 -0
@@ -258,7 +258,18 @@ export class AccountManager {
258
258
  })
259
259
 
260
260
  return constantTime(TIMING_ATTACK_MITIGATION_DELAY, async () => {
261
- await this.store.resetPasswordRequest(input)
261
+ const account = await this.store.resetPasswordRequest(input)
262
+
263
+ if (!account) {
264
+ return // Silently ignore to prevent user enumeration
265
+ }
266
+
267
+ await this.hooks.onResetPasswordRequested?.call(null, {
268
+ input,
269
+ deviceId,
270
+ deviceMetadata,
271
+ account,
272
+ })
262
273
  })
263
274
  }
264
275
 
@@ -274,7 +285,18 @@ export class AccountManager {
274
285
  })
275
286
 
276
287
  return constantTime(TIMING_ATTACK_MITIGATION_DELAY, async () => {
277
- await this.store.resetPasswordConfirm(input)
288
+ const account = await this.store.resetPasswordConfirm(input)
289
+
290
+ if (!account) {
291
+ throw new InvalidRequestError('Invalid token')
292
+ }
293
+
294
+ await this.hooks.onResetPasswordConfirmed?.call(null, {
295
+ input,
296
+ deviceId,
297
+ deviceMetadata,
298
+ account,
299
+ })
278
300
  })
279
301
  }
280
302
 
@@ -171,8 +171,13 @@ export interface AccountStore {
171
171
  filter: { sub: Sub } | { deviceId: DeviceId },
172
172
  ): Awaitable<DeviceAccount[]>
173
173
 
174
- resetPasswordRequest(data: ResetPasswordRequestInput): Awaitable<void>
175
- resetPasswordConfirm(data: ResetPasswordConfirmInput): Awaitable<void>
174
+ resetPasswordRequest(
175
+ data: ResetPasswordRequestInput,
176
+ ): Awaitable<null | Account>
177
+
178
+ resetPasswordConfirm(
179
+ data: ResetPasswordConfirmInput,
180
+ ): Awaitable<null | Account>
176
181
 
177
182
  /**
178
183
  * @throws {HandleUnavailableError} - To indicate that the handle is already taken
@@ -5,7 +5,7 @@ import {
5
5
  OAuthClientIdLoopback,
6
6
  OAuthClientMetadata,
7
7
  OAuthClientMetadataInput,
8
- isLoopbackHost,
8
+ isLocalHostname,
9
9
  isOAuthClientIdDiscoverable,
10
10
  isOAuthClientIdLoopback,
11
11
  oauthClientMetadataSchema,
@@ -17,7 +17,6 @@ import {
17
17
  fetchJsonZodProcessor,
18
18
  fetchOkProcessor,
19
19
  } from '@atproto-labs/fetch'
20
- import { isLocalHostname } from '@atproto-labs/fetch-node'
21
20
  import { pipe } from '@atproto-labs/pipe'
22
21
  import {
23
22
  CachedGetter,
@@ -36,7 +35,7 @@ import { Client } from './client.js'
36
35
 
37
36
  const fetchMetadataHandler = pipe(
38
37
  fetchOkProcessor(),
39
- // https://drafts.aaronpk.com/draft-parecki-oauth-client-id-metadata-document/draft-parecki-oauth-client-id-metadata-document.html#section-4.1
38
+ // https://www.ietf.org/archive/id/draft-ietf-oauth-client-id-metadata-document-00.html#section-4.1
40
39
  fetchJsonProcessor('application/json', true),
41
40
  fetchJsonZodProcessor(oauthClientMetadataSchema),
42
41
  )
@@ -176,10 +175,24 @@ export class ClientManager {
176
175
  throw new InvalidClientMetadataError('Loopback clients are not allowed')
177
176
  }
178
177
 
179
- const metadata = oauthClientMetadataSchema.parse(
180
- await loopbackMetadata(clientId),
178
+ const metadataRaw = await callAsync(loopbackMetadata, clientId).catch(
179
+ (err) => {
180
+ throw InvalidClientMetadataError.from(
181
+ err,
182
+ `Invalid loopback client id "${clientId}"`,
183
+ )
184
+ },
181
185
  )
182
186
 
187
+ const metadata = await oauthClientMetadataSchema
188
+ .parseAsync(metadataRaw)
189
+ .catch((err) => {
190
+ throw InvalidClientMetadataError.from(
191
+ err,
192
+ `Invalid loopback client metadata for "${clientId}"`,
193
+ )
194
+ })
195
+
183
196
  return this.validateClientMetadata(clientId, metadata)
184
197
  }
185
198
 
@@ -218,6 +231,10 @@ export class ClientManager {
218
231
  clientId: ClientId,
219
232
  metadata: OAuthClientMetadata,
220
233
  ): OAuthClientMetadata {
234
+ // @TODO This method should only check for rules that are specific to this
235
+ // implementation or the ATPROTO specification. All generic validation rules
236
+ // should be moved to the @atproto/oauth-types package.
237
+
221
238
  if (metadata.jwks && metadata.jwks_uri) {
222
239
  throw new InvalidClientMetadataError(
223
240
  'jwks_uri and jwks are mutually exclusive',
@@ -590,56 +607,12 @@ export class ClientManager {
590
607
  }
591
608
 
592
609
  case isPrivateUseUriScheme(url): {
593
- // https://datatracker.ietf.org/doc/html/rfc8252#section-7.1
594
- //
595
- // > When choosing a URI scheme to associate with the app, apps MUST
596
- // > use a URI scheme based on a domain name under their control,
597
- // > expressed in reverse order, as recommended by Section 3.8 of
598
- // > [RFC7595] for private-use URI schemes.
599
-
600
610
  if (metadata.application_type !== 'native') {
601
611
  throw new InvalidRedirectUriError(
602
612
  `Private-Use URI Scheme redirect URI are only allowed for native apps`,
603
613
  )
604
614
  }
605
615
 
606
- // https://datatracker.ietf.org/doc/html/rfc8252#section-8.4
607
- //
608
- // > In addition to the collision-resistant properties, requiring a
609
- // > URI scheme based on a domain name that is under the control of
610
- // > the app can help to prove ownership in the event of a dispute
611
- // > where two apps claim the same private-use URI scheme (where one
612
- // > app is acting maliciously).
613
- //
614
- // We can't check for ownership here (as there is no concept of
615
- // proven ownership in the generic client validation), but we can
616
- // check that the domain is a valid domain name.
617
-
618
- const urlDomain = reverseDomain(url.protocol.slice(0, -1))
619
-
620
- if (isLocalHostname(urlDomain)) {
621
- throw new InvalidRedirectUriError(
622
- `Private-use URI Scheme redirect URI must not be a local hostname`,
623
- )
624
- }
625
-
626
- // https://datatracker.ietf.org/doc/html/rfc8252#section-7.1
627
- //
628
- // > Following the requirements of Section 3.2 of [RFC3986], as there
629
- // > is no naming authority for private-use URI scheme redirects, only
630
- // > a single slash ("/") appears after the scheme component.
631
- if (
632
- url.href.startsWith(`${url.protocol}//`) ||
633
- url.username ||
634
- url.password ||
635
- url.hostname ||
636
- url.port
637
- ) {
638
- throw new InvalidRedirectUriError(
639
- `Private-Use URI Scheme must be in the form ${url.protocol}/<path>`,
640
- )
641
- }
642
-
643
616
  break
644
617
  }
645
618
 
@@ -686,22 +659,6 @@ export class ClientManager {
686
659
  )
687
660
  }
688
661
 
689
- for (const redirectUri of metadata.redirect_uris) {
690
- const url = parseRedirectUri(redirectUri)
691
-
692
- if (url.protocol !== 'http:') {
693
- throw new InvalidRedirectUriError(
694
- `Loopback clients must use HTTP redirect URIs`,
695
- )
696
- }
697
-
698
- if (!isLoopbackHost(url.hostname)) {
699
- throw new InvalidRedirectUriError(
700
- `Loopback clients must use loopback redirect URIs`,
701
- )
702
- }
703
- }
704
-
705
662
  return metadata
706
663
  }
707
664
 
@@ -710,7 +667,7 @@ export class ClientManager {
710
667
  metadata: OAuthClientMetadata,
711
668
  ): OAuthClientMetadata {
712
669
  if (!metadata.client_id) {
713
- // https://drafts.aaronpk.com/draft-parecki-oauth-client-id-metadata-document/draft-parecki-oauth-client-id-metadata-document.html
670
+ // https://www.ietf.org/archive/id/draft-ietf-oauth-client-id-metadata-document-00.html
714
671
  throw new InvalidClientMetadataError(
715
672
  `client_id is required for discoverable clients`,
716
673
  )
@@ -719,7 +676,7 @@ export class ClientManager {
719
676
  const clientIdUrl = parseDiscoverableClientId(clientId)
720
677
 
721
678
  if (metadata.client_uri) {
722
- // https://drafts.aaronpk.com/draft-parecki-oauth-client-id-metadata-document/draft-parecki-oauth-client-id-metadata-document.html
679
+ // https://www.ietf.org/archive/id/draft-ietf-oauth-client-id-metadata-document-00.html
723
680
  //
724
681
  // The client_uri must be a parent of the client_id URL. This might be
725
682
  // relaxed in the future.
@@ -748,9 +705,19 @@ export class ClientManager {
748
705
  }
749
706
 
750
707
  for (const redirectUri of metadata.redirect_uris) {
708
+ // @NOTE at this point, all redirect URIs have already been validated by
709
+ // oauthRedirectUriSchema
710
+
751
711
  const url = parseRedirectUri(redirectUri)
752
712
 
753
713
  if (isPrivateUseUriScheme(url)) {
714
+ // https://datatracker.ietf.org/doc/html/rfc8252#section-7.1
715
+ //
716
+ // > When choosing a URI scheme to associate with the app, apps MUST use
717
+ // > a URI scheme based on a domain name under their control, expressed
718
+ // > in reverse order, as recommended by Section 3.8 of [RFC7595] for
719
+ // > private-use URI schemes.
720
+
754
721
  // https://datatracker.ietf.org/doc/html/rfc8252#section-8.4
755
722
  //
756
723
  // > In addition to the collision-resistant properties, requiring a
@@ -759,11 +726,14 @@ export class ClientManager {
759
726
  // > where two apps claim the same private-use URI scheme (where one
760
727
  // > app is acting maliciously).
761
728
 
762
- // https://drafts.aaronpk.com/draft-parecki-oauth-client-id-metadata-document/draft-parecki-oauth-client-id-metadata-document.html
729
+ // https://atproto.com/specs/oauth
763
730
  //
764
- // Fully qualified domain name (FQDN) of the client_id, in reverse
765
- // order. This could be relaxed to allow same apex domain names, or
766
- // parent domains, but for now we require an exact match.
731
+ // > Any custom scheme must match the client_id hostname in
732
+ // > reverse-domain order. The URI scheme must be followed by a single
733
+ // > colon (:) then a single forward slash (/) and then a URI path
734
+ // > component. For example, an app with client_id
735
+ // > https://app.example.com/client-metadata.json could have a
736
+ // > redirect_uri of com.example.app:/callback.
767
737
  const protocol = `${reverseDomain(clientIdUrl.hostname)}:`
768
738
  if (url.protocol !== protocol) {
769
739
  throw new InvalidRedirectUriError(
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  OAuthClientIdDiscoverable,
3
+ isLocalHostname,
3
4
  parseOAuthDiscoverableClientId,
4
5
  } from '@atproto/oauth-types'
5
- import { isLocalHostname } from '@atproto-labs/fetch-node'
6
6
  import { InvalidClientIdError } from '../errors/invalid-client-id-error.js'
7
7
  import { InvalidRedirectUriError } from '../errors/invalid-redirect-uri-error.js'
8
8
 
@@ -106,8 +106,8 @@ export function buildMetadata(
106
106
 
107
107
  revocation_endpoint: new URL('/oauth/revoke', issuer).href,
108
108
 
109
- introspection_endpoint: new URL('/oauth/introspect', issuer).href,
110
-
109
+ // @TODO Should we implement these endpoints?
110
+ // introspection_endpoint: new URL('/oauth/introspect', issuer).href,
111
111
  // end_session_endpoint: new URL('/oauth/logout', issuer).href,
112
112
 
113
113
  // https://datatracker.ietf.org/doc/html/rfc9126#section-5
@@ -122,10 +122,10 @@ export function buildMetadata(
122
122
  authorization_details_types_supported:
123
123
  customMetadata?.authorization_details_types_supported,
124
124
 
125
- // https://datatracker.ietf.org/doc/html/draft-ietf-oauth-resource-metadata-05#section-4
125
+ // https://www.rfc-editor.org/rfc/rfc9728.html#section-4
126
126
  protected_resources: customMetadata?.protected_resources,
127
127
 
128
- // https://drafts.aaronpk.com/draft-parecki-oauth-client-id-metadata-document/draft-parecki-oauth-client-id-metadata-document.html
128
+ // https://www.ietf.org/archive/id/draft-ietf-oauth-client-id-metadata-document-00.html
129
129
  client_id_metadata_document_supported: true,
130
130
  })
131
131
  }
@@ -115,6 +115,17 @@ export type OAuthHooks = {
115
115
  deviceMetadata: RequestMetadata
116
116
  }) => Awaitable<void>
117
117
 
118
+ /**
119
+ * This hook is called when a user requests a password reset, before the
120
+ * reset password request is triggered on the account store.
121
+ */
122
+ onResetPasswordRequested?: (data: {
123
+ input: ResetPasswordRequestInput
124
+ deviceId: DeviceId
125
+ deviceMetadata: RequestMetadata
126
+ account: Account
127
+ }) => Awaitable<void>
128
+
118
129
  /**
119
130
  * This hook is called when a user confirms a password reset, before the
120
131
  * password is actually reset on the account store.
@@ -125,6 +136,17 @@ export type OAuthHooks = {
125
136
  deviceMetadata: RequestMetadata
126
137
  }) => Awaitable<void>
127
138
 
139
+ /**
140
+ * This hook is called after a user confirms a password reset, and the
141
+ * password was successfully reset on the account store.
142
+ */
143
+ onResetPasswordConfirmed?: (data: {
144
+ input: ResetPasswordConfirmInput
145
+ deviceId: DeviceId
146
+ deviceMetadata: RequestMetadata
147
+ account: Account
148
+ }) => Awaitable<void>
149
+
128
150
  /**
129
151
  * This hook is called when a user successfully signs up.
130
152
  *