@udibo/oauth2 0.4.1 → 0.5.0

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 (159) hide show
  1. package/README.md +3 -3
  2. package/SECURITY.md +0 -4
  3. package/esm/adapters/hono/_common.d.ts +10 -13
  4. package/esm/adapters/hono/_common.js +11 -20
  5. package/esm/adapters/hono/authorization-server.d.ts +16 -10
  6. package/esm/adapters/hono/authorization-server.js +13 -8
  7. package/esm/adapters/hono/bff/auth-request-store.d.ts +4 -0
  8. package/esm/adapters/hono/bff/auth-request-store.js +4 -0
  9. package/esm/adapters/hono/bff/bff.d.ts +29 -18
  10. package/esm/adapters/hono/bff/bff.js +15 -11
  11. package/esm/adapters/hono/bff/proxy.js +7 -55
  12. package/esm/adapters/hono/bff/session-store.d.ts +9 -9
  13. package/esm/adapters/hono/bff/session-store.js +3 -2
  14. package/esm/adapters/hono/bff/testing.d.ts +14 -3
  15. package/esm/adapters/hono/bff/testing.js +14 -3
  16. package/esm/adapters/hono/identity.d.ts +5 -3
  17. package/esm/adapters/hono/identity.js +1 -17
  18. package/esm/adapters/hono/resource-server.d.ts +3 -2
  19. package/esm/adapters/hono/resource-server.js +3 -2
  20. package/esm/client/_http.d.ts +4 -7
  21. package/esm/client/_http.js +4 -37
  22. package/esm/client/base.d.ts +3 -6
  23. package/esm/client/bff-client.d.ts +7 -6
  24. package/esm/client/bff-client.js +7 -6
  25. package/esm/client/browser-storage.d.ts +33 -34
  26. package/esm/client/browser-storage.js +47 -32
  27. package/esm/client/check.d.ts +10 -3
  28. package/esm/client/check.js +28 -8
  29. package/esm/client/direct-client.d.ts +49 -29
  30. package/esm/client/direct-client.js +59 -59
  31. package/esm/client/discovery-cache.d.ts +12 -5
  32. package/esm/client/discovery-cache.js +11 -4
  33. package/esm/client/events.d.ts +11 -3
  34. package/esm/client/events.js +1 -1
  35. package/esm/client/mod.d.ts +6 -4
  36. package/esm/client/mod.js +5 -3
  37. package/esm/client/storage.d.ts +53 -23
  38. package/esm/client/storage.js +43 -15
  39. package/esm/errors.d.ts +15 -12
  40. package/esm/errors.js +12 -8
  41. package/esm/identity/delivery.d.ts +7 -7
  42. package/esm/identity/delivery.js +1 -2
  43. package/esm/identity/errors.d.ts +10 -4
  44. package/esm/identity/errors.js +4 -1
  45. package/esm/identity/events.d.ts +21 -5
  46. package/esm/identity/events.js +4 -2
  47. package/esm/identity/external/_shared.d.ts +5 -7
  48. package/esm/identity/external/_shared.js +5 -7
  49. package/esm/identity/external/_token-exchange.d.ts +3 -6
  50. package/esm/identity/external/_token-exchange.js +2 -5
  51. package/esm/identity/external/apple-client-secret.d.ts +13 -8
  52. package/esm/identity/external/apple-client-secret.js +9 -6
  53. package/esm/identity/external/apple.d.ts +17 -10
  54. package/esm/identity/external/apple.js +12 -9
  55. package/esm/identity/external/discord.d.ts +5 -1
  56. package/esm/identity/external/errors.d.ts +6 -2
  57. package/esm/identity/external/errors.js +6 -2
  58. package/esm/identity/external/flow.d.ts +9 -6
  59. package/esm/identity/external/flow.js +6 -5
  60. package/esm/identity/external/github.d.ts +7 -3
  61. package/esm/identity/external/github.js +2 -2
  62. package/esm/identity/external/google.d.ts +5 -1
  63. package/esm/identity/external/oauth2.d.ts +13 -2
  64. package/esm/identity/external/oauth2.js +3 -3
  65. package/esm/identity/external/oidc.d.ts +21 -10
  66. package/esm/identity/external/oidc.js +11 -6
  67. package/esm/identity/external/provider.d.ts +4 -2
  68. package/esm/identity/identifier.d.ts +6 -5
  69. package/esm/identity/identifier.js +6 -5
  70. package/esm/identity/lockout.d.ts +4 -3
  71. package/esm/identity/lockout.js +4 -3
  72. package/esm/identity/mfa/mod.d.ts +2 -1
  73. package/esm/identity/mfa/mod.js +2 -1
  74. package/esm/identity/mfa/recovery-codes.d.ts +2 -1
  75. package/esm/identity/mfa/service.d.ts +29 -11
  76. package/esm/identity/mfa/service.js +35 -11
  77. package/esm/identity/mfa/totp.d.ts +4 -2
  78. package/esm/identity/mfa/totp.js +4 -2
  79. package/esm/identity/migration.d.ts +8 -7
  80. package/esm/identity/migration.js +9 -12
  81. package/esm/identity/mod.d.ts +2 -2
  82. package/esm/identity/mod.js +2 -2
  83. package/esm/identity/otp.d.ts +23 -16
  84. package/esm/identity/otp.js +13 -12
  85. package/esm/identity/password-policy.d.ts +3 -3
  86. package/esm/identity/password-policy.js +3 -3
  87. package/esm/identity/password.d.ts +23 -20
  88. package/esm/identity/password.js +15 -16
  89. package/esm/identity/rate-limit.d.ts +1 -7
  90. package/esm/identity/rate-limit.js +1 -2
  91. package/esm/identity/service.d.ts +76 -44
  92. package/esm/identity/service.js +86 -60
  93. package/esm/identity/session.d.ts +5 -4
  94. package/esm/identity/session.js +5 -4
  95. package/esm/identity/token-flow.d.ts +8 -8
  96. package/esm/identity/token-flow.js +5 -6
  97. package/esm/models/client.d.ts +7 -4
  98. package/esm/models/token.d.ts +2 -3
  99. package/esm/react/callback.d.ts +3 -3
  100. package/esm/react/callback.js +3 -3
  101. package/esm/react/components/class-names.d.ts +3 -6
  102. package/esm/react/components/class-names.js +3 -6
  103. package/esm/react/components/internal.d.ts +7 -17
  104. package/esm/react/components/internal.js +7 -17
  105. package/esm/react/components/mod.d.ts +7 -5
  106. package/esm/react/components/mod.js +7 -5
  107. package/esm/react/components/types.d.ts +8 -6
  108. package/esm/react/context.d.ts +3 -3
  109. package/esm/react/provider.d.ts +3 -2
  110. package/esm/react/provider.js +0 -13
  111. package/esm/react/require-auth.d.ts +0 -3
  112. package/esm/react/require-auth.js +0 -3
  113. package/esm/react/testing.d.ts +3 -2
  114. package/esm/react/testing.js +3 -3
  115. package/esm/react/use-oauth2.d.ts +1 -1
  116. package/esm/server/authorization-server.d.ts +81 -54
  117. package/esm/server/authorization-server.js +76 -100
  118. package/esm/server/grants/authorization-code.d.ts +7 -3
  119. package/esm/server/grants/authorization-code.js +6 -2
  120. package/esm/server/grants/device-authorization.d.ts +1 -1
  121. package/esm/server/grants/grant.d.ts +15 -8
  122. package/esm/server/grants/grant.js +26 -18
  123. package/esm/server/grants/password.d.ts +1 -1
  124. package/esm/server/grants/refresh-token.d.ts +6 -3
  125. package/esm/server/grants/refresh-token.js +4 -3
  126. package/esm/server/introspection-token-reader.d.ts +16 -7
  127. package/esm/server/introspection-token-reader.js +9 -3
  128. package/esm/server/jwks-token-reader.d.ts +3 -3
  129. package/esm/server/jwks-token-reader.js +3 -3
  130. package/esm/server/redirect-uri.d.ts +6 -9
  131. package/esm/server/redirect-uri.js +7 -34
  132. package/esm/server/resource-server.d.ts +5 -3
  133. package/esm/server/resource-server.js +5 -3
  134. package/esm/server/services/client.d.ts +5 -1
  135. package/esm/server/services/device-authorization.d.ts +6 -6
  136. package/esm/server/services/token.d.ts +15 -8
  137. package/esm/server/services/token.js +1 -1
  138. package/esm/server/signing-keys.d.ts +24 -9
  139. package/esm/server/signing-keys.js +20 -9
  140. package/esm/testing/contract/mod.d.ts +3 -3
  141. package/esm/testing/contract/mod.js +3 -3
  142. package/esm/testing/mod.d.ts +4 -2
  143. package/esm/testing/mod.js +4 -2
  144. package/esm/testing/services.d.ts +1 -1
  145. package/esm/testing/services.js +1 -1
  146. package/esm/testing/tenant.d.ts +13 -4
  147. package/esm/utils/_buffer.d.ts +3 -2
  148. package/esm/utils/_buffer.js +3 -2
  149. package/esm/utils/_timing-safe.d.ts +8 -6
  150. package/esm/utils/_timing-safe.js +8 -6
  151. package/esm/utils/basic-auth.d.ts +5 -12
  152. package/esm/utils/basic-auth.js +5 -12
  153. package/esm/utils/crypto.d.ts +2 -1
  154. package/esm/utils/crypto.js +2 -1
  155. package/esm/utils/text.d.ts +2 -9
  156. package/esm/utils/text.js +2 -9
  157. package/esm/utils/url.d.ts +2 -2
  158. package/esm/utils/url.js +0 -2
  159. package/package.json +1 -1
@@ -33,7 +33,9 @@ export interface ClientInterface {
33
33
  * the request when none authorizes it.
34
34
  *
35
35
  * An ordinary entry is a **literal**, compared by exact string match (RFC
36
- * 6749 §3.1.2.3), and an exact match always wins. An entry may instead be a
36
+ * 6749 §3.1.2.3), and an exact match always wins. A literal on a loopback
37
+ * IP host (`127.0.0.1` or `[::1]`) also matches the same URI on any port,
38
+ * for native apps (RFC 8252 §7.3). An entry may instead be a
37
39
  * **pattern** — one `*` in the leftmost host label, `https` only, over a
38
40
  * single registrable domain (`https://myapp-*.myorg.deno.net/cb`) — so one
39
41
  * registration covers the per-deploy hostnames a preview environment
@@ -48,9 +50,10 @@ export interface ClientInterface {
48
50
  * **literal** entry, so order the array with that default first. When omitted
49
51
  * or empty, no redirect URI is authorized at all.
50
52
  *
51
- * A legacy literal entry containing a `*` (registrable before patterns
52
- * existed) now matches **nothing** — the value reads as a pattern and fails
53
- * the rules. That fails closed, but silently: re-register such a client.
53
+ * Any entry containing a `*` is read as a pattern, so a literal URI that
54
+ * happens to contain one matches **nothing** unless it also satisfies the
55
+ * pattern rules. That fails closed, but silently: register such a URI in a
56
+ * form without `*`.
54
57
  */
55
58
  redirectUris?: string[];
56
59
  /**
@@ -62,9 +62,8 @@ export interface RefreshToken<Client extends ClientInterface, User, Scope extend
62
62
  * service that persists refresh tokens **must** persist and restore it —
63
63
  * dropping it on the round trip disables the cap.
64
64
  *
65
- * A record without an anchor (one stored before the cap existed) rotates
66
- * once uncapped; that rotation is issued with a fresh anchor, so the family
67
- * is capped from then on.
65
+ * A record without an anchor rotates once uncapped; that rotation is issued
66
+ * with a fresh anchor, so the family is capped from then on.
68
67
  */
69
68
  familyCreatedAt?: Date;
70
69
  }
@@ -7,9 +7,9 @@
7
7
  * component renders its `fallback` with a clear error rather than pretend
8
8
  * otherwise.
9
9
  *
10
- * Idempotent against React 18 strict-mode double-mounts because the
11
- * client itself dedupes by `code` — the second invocation returns the
12
- * first call's promise.
10
+ * Idempotent against React strict-mode double-mounts because the client
11
+ * dedupes by `code`: a second call while the first exchange is still in
12
+ * flight returns the first call's promise.
13
13
  *
14
14
  * @module
15
15
  */
@@ -9,9 +9,9 @@ import { jsx as _jsx } from "react/jsx-runtime";
9
9
  * component renders its `fallback` with a clear error rather than pretend
10
10
  * otherwise.
11
11
  *
12
- * Idempotent against React 18 strict-mode double-mounts because the
13
- * client itself dedupes by `code` — the second invocation returns the
14
- * first call's promise.
12
+ * Idempotent against React strict-mode double-mounts because the client
13
+ * dedupes by `code`: a second call while the first exchange is still in
14
+ * flight returns the first call's promise.
15
15
  *
16
16
  * @module
17
17
  */
@@ -18,8 +18,7 @@ import type { AuthFormClassNames } from "./types.js";
18
18
  * {@link AuthFormClassNamesProvider}'s map with `overrides` applied slot by
19
19
  * slot. An absent or `undefined` slot in `overrides` keeps the inherited value,
20
20
  * and with no provider above the map is empty (every slot `undefined`), so the
21
- * hook is safe to call anywhere. This is the only place the precedence rule is
22
- * implemented.
21
+ * hook is safe to call anywhere.
23
22
  *
24
23
  * The result is cached on the slot values, so passing a fresh `overrides`
25
24
  * literal every render returns the identical map.
@@ -48,10 +47,8 @@ import type { AuthFormClassNames } from "./types.js";
48
47
  */
49
48
  export declare function useAuthFormClassNames(overrides?: AuthFormClassNames): Readonly<AuthFormClassNames>;
50
49
  /**
51
- * Publishes an already-resolved map verbatim, without merging. A form and
52
- * `AuthFormShell` resolve once through {@link useAuthFormClassNames} and
53
- * publish through this, so the precedence rule keeps exactly one
54
- * implementation. App code wants {@link AuthFormClassNamesProvider}.
50
+ * Publishes an already-resolved map (from {@link useAuthFormClassNames})
51
+ * verbatim, without merging. App code wants {@link AuthFormClassNamesProvider}.
55
52
  */
56
53
  export declare function AuthFormClassNamesScope(props: {
57
54
  classNames: Readonly<AuthFormClassNames>;
@@ -35,8 +35,7 @@ function slotKey(classNames) {
35
35
  * {@link AuthFormClassNamesProvider}'s map with `overrides` applied slot by
36
36
  * slot. An absent or `undefined` slot in `overrides` keeps the inherited value,
37
37
  * and with no provider above the map is empty (every slot `undefined`), so the
38
- * hook is safe to call anywhere. This is the only place the precedence rule is
39
- * implemented.
38
+ * hook is safe to call anywhere.
40
39
  *
41
40
  * The result is cached on the slot values, so passing a fresh `overrides`
42
41
  * literal every render returns the identical map.
@@ -78,10 +77,8 @@ export function useAuthFormClassNames(overrides) {
78
77
  return held.current.resolved;
79
78
  }
80
79
  /**
81
- * Publishes an already-resolved map verbatim, without merging. A form and
82
- * `AuthFormShell` resolve once through {@link useAuthFormClassNames} and
83
- * publish through this, so the precedence rule keeps exactly one
84
- * implementation. App code wants {@link AuthFormClassNamesProvider}.
80
+ * Publishes an already-resolved map (from {@link useAuthFormClassNames})
81
+ * verbatim, without merging. App code wants {@link AuthFormClassNamesProvider}.
85
82
  */
86
83
  export function AuthFormClassNamesScope(props) {
87
84
  const { classNames, children } = props;
@@ -2,17 +2,10 @@ import { type ReactNode, type Ref } from "react";
2
2
  import type { AuthFieldProps, AuthFormState } from "./use-auth-form.js";
3
3
  import type { AuthFormClassNames, AuthFormValues, SocialProvidersProps } from "./types.js";
4
4
  /**
5
- * The single exit every default form component returns through — it owns both
6
- * outcomes, so a form has one `return` and cannot leave a subtree unscoped.
7
- *
8
- * With no `render`, it lays out the markup the forms share: the root wrapper
9
- * carrying `data-oauth2-form={name}`, then the `<form>` with its error summary
10
- * and the caller's fields. Use `aboveForm` for content that belongs inside the
11
- * root but outside the form (social buttons, an enrollment secret), and
12
- * `succeeded` for a terminal state that replaces the form entirely. With a
13
- * `render` — the form's render-prop `children` — that markup is skipped for the
14
- * caller's own. Either way the already-resolved `classNames` is published to
15
- * what renders, so the elements inside take their classes from the tree.
5
+ * What every default form component returns: the shared root, `<form>` and
6
+ * error summary, or the caller's `render` output instead, with the resolved
7
+ * `classNames` published to either. `aboveForm` sits inside the root but
8
+ * outside the form; `succeeded` replaces the form.
16
9
  */
17
10
  export declare function AuthFormShell<V extends AuthFormValues>(props: {
18
11
  name: string;
@@ -48,12 +41,9 @@ export declare function ErrorSummary(props: {
48
41
  focusTrigger?: number;
49
42
  }): ReactNode;
50
43
  /**
51
- * Moves keyboard focus to the first field that came back with an error after a
52
- * failed submit, so a screen-reader / keyboard user lands on the control to fix
53
- * (its label + error are read via `aria-describedby`) instead of being left on
54
- * the submit button. No-ops on the initial render and when the form
55
- * carries a form-level error ({@link ErrorSummary} owns that focus). Call it
56
- * once, unconditionally, from a form component built on {@link useAuthForm}.
44
+ * After a failed submit, focuses the first field with an error; does nothing
45
+ * when there is a form-level error, which {@link ErrorSummary} focuses. Call
46
+ * once, unconditionally, from a form built on {@link useAuthForm}.
57
47
  */
58
48
  export declare function useFocusFirstError<V extends AuthFormValues>(form: AuthFormState<V>): void;
59
49
  export declare function SocialButtons(props: SocialProvidersProps): ReactNode;
@@ -2,17 +2,10 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import { useEffect, useRef, } from "react";
3
3
  import { AuthFormClassNamesScope, useAuthFormClassNames, } from "./class-names.js";
4
4
  /**
5
- * The single exit every default form component returns through — it owns both
6
- * outcomes, so a form has one `return` and cannot leave a subtree unscoped.
7
- *
8
- * With no `render`, it lays out the markup the forms share: the root wrapper
9
- * carrying `data-oauth2-form={name}`, then the `<form>` with its error summary
10
- * and the caller's fields. Use `aboveForm` for content that belongs inside the
11
- * root but outside the form (social buttons, an enrollment secret), and
12
- * `succeeded` for a terminal state that replaces the form entirely. With a
13
- * `render` — the form's render-prop `children` — that markup is skipped for the
14
- * caller's own. Either way the already-resolved `classNames` is published to
15
- * what renders, so the elements inside take their classes from the tree.
5
+ * What every default form component returns: the shared root, `<form>` and
6
+ * error summary, or the caller's `render` output instead, with the resolved
7
+ * `classNames` published to either. `aboveForm` sits inside the root but
8
+ * outside the form; `succeeded` replaces the form.
16
9
  */
17
10
  export function AuthFormShell(props) {
18
11
  const { name, form, className, classNames, render, rootProps, aboveForm, succeeded, children, action, method, formRef, } = props;
@@ -46,12 +39,9 @@ export function ErrorSummary(props) {
46
39
  return (_jsx("div", { ref: ref, tabIndex: -1, "data-oauth2-error-summary": "", className: classNames.errorSummary, children: _jsx("span", { role: "alert", "data-oauth2-error-summary-message": "", children: error }) }));
47
40
  }
48
41
  /**
49
- * Moves keyboard focus to the first field that came back with an error after a
50
- * failed submit, so a screen-reader / keyboard user lands on the control to fix
51
- * (its label + error are read via `aria-describedby`) instead of being left on
52
- * the submit button. No-ops on the initial render and when the form
53
- * carries a form-level error ({@link ErrorSummary} owns that focus). Call it
54
- * once, unconditionally, from a form component built on {@link useAuthForm}.
42
+ * After a failed submit, focuses the first field with an error; does nothing
43
+ * when there is a form-level error, which {@link ErrorSummary} focuses. Call
44
+ * once, unconditionally, from a form built on {@link useAuthForm}.
55
45
  */
56
46
  export function useFocusFirstError(form) {
57
47
  const handled = useRef(0);
@@ -27,9 +27,12 @@
27
27
  * ## Server-rendered pages
28
28
  *
29
29
  * Pass `action` + `method` and the rendered `<form>` carries them, so a
30
- * server-rendered page still posts when JavaScript never arrives — every field
31
- * carries its `name`, so the native post sends the same keys `onSubmit`
32
- * receives. Once React is running, `onSubmit` handles the submit and the
30
+ * server-rendered page still posts when JavaScript never arrives — every
31
+ * rendered field carries its `name`, so the native post sends those fields
32
+ * under the keys `onSubmit` receives. Values a form holds without rendering a
33
+ * field for them are absent from a native post: `ResetPasswordForm`'s
34
+ * `token`, `MfaChallengeForm`'s `method`, and any `SignUpForm` field left
35
+ * hidden. Once React is running, `onSubmit` handles the submit and the
33
36
  * native post is suppressed. `formRef` hands you the same `<form>` element, for
34
37
  * example to trigger a native `submit()` yourself.
35
38
  *
@@ -56,8 +59,7 @@
56
59
  * - One-time-code fields use `autocomplete="one-time-code"` and **never block
57
60
  * paste** (WCAG 2.2 §3.3.8 Accessible Authentication).
58
61
  * - Section titles carry heading semantics; the components ship unstyled, so
59
- * **contrast and visible focus are yours to satisfy** with your own theme —
60
- * the ratios and focus rules Udibo uses are in its `DESIGN.md`.
62
+ * **contrast and visible focus are yours to satisfy** with your own theme.
61
63
  *
62
64
  * @example Plain React Router v7
63
65
  * ```tsx
@@ -27,9 +27,12 @@
27
27
  * ## Server-rendered pages
28
28
  *
29
29
  * Pass `action` + `method` and the rendered `<form>` carries them, so a
30
- * server-rendered page still posts when JavaScript never arrives — every field
31
- * carries its `name`, so the native post sends the same keys `onSubmit`
32
- * receives. Once React is running, `onSubmit` handles the submit and the
30
+ * server-rendered page still posts when JavaScript never arrives — every
31
+ * rendered field carries its `name`, so the native post sends those fields
32
+ * under the keys `onSubmit` receives. Values a form holds without rendering a
33
+ * field for them are absent from a native post: `ResetPasswordForm`'s
34
+ * `token`, `MfaChallengeForm`'s `method`, and any `SignUpForm` field left
35
+ * hidden. Once React is running, `onSubmit` handles the submit and the
33
36
  * native post is suppressed. `formRef` hands you the same `<form>` element, for
34
37
  * example to trigger a native `submit()` yourself.
35
38
  *
@@ -56,8 +59,7 @@
56
59
  * - One-time-code fields use `autocomplete="one-time-code"` and **never block
57
60
  * paste** (WCAG 2.2 §3.3.8 Accessible Authentication).
58
61
  * - Section titles carry heading semantics; the components ship unstyled, so
59
- * **contrast and visible focus are yours to satisfy** with your own theme —
60
- * the ratios and focus rules Udibo uses are in its `DESIGN.md`.
62
+ * **contrast and visible focus are yours to satisfy** with your own theme.
61
63
  *
62
64
  * @example Plain React Router v7
63
65
  * ```tsx
@@ -107,7 +107,7 @@ export interface AuthFormClassNames extends BaseClassNames {
107
107
  /** Each inline field error (`data-oauth2-error`). */
108
108
  error?: string;
109
109
  /**
110
- * Inline content rendered under a field — today the sign-in form's "Forgot
110
+ * Inline content rendered under a field — the sign-in form's "Forgot
111
111
  * password?" link (`data-oauth2-forgot-password`).
112
112
  */
113
113
  hint?: string;
@@ -186,17 +186,19 @@ export interface BaseAuthFormProps<V extends AuthFormValues> {
186
186
  /**
187
187
  * `action` for the rendered `<form>`. Set it (with {@link method}) on a
188
188
  * server-rendered page so the form still posts when JavaScript never
189
- * arrives; every field carries its `name`, so a native post sends the same
190
- * keys `onSubmit` receives. Once React is running, `onSubmit` handles the
191
- * submit and the native post is suppressed.
189
+ * arrives; every rendered field carries its `name`, so a native post sends
190
+ * those fields under the keys `onSubmit` receives. A value the form holds
191
+ * without a rendered field (such as `ResetPasswordForm`'s `token`) is not
192
+ * posted, so the server's handler must obtain it another way. Once React is
193
+ * running, `onSubmit` handles the submit and the native post is suppressed.
192
194
  *
193
195
  * Ignored in render-prop mode — you own the `<form>` there.
194
196
  */
195
197
  action?: string;
196
198
  /**
197
199
  * `method` for the rendered `<form>`. Defaults to `"post"` whenever
198
- * {@link action} is set, because the fields these forms render — passwords,
199
- * one-time codes, reset tokens — must never reach a URL, where a `get` form
200
+ * {@link action} is set, because the fields these forms render — passwords
201
+ * and one-time codes — must never reach a URL, where a `get` form
200
202
  * would put them: browser history, the `Referer` sent to every third-party
201
203
  * asset on the destination page, and the server's access log.
202
204
  */
@@ -14,9 +14,9 @@ export interface OAuth2State {
14
14
  /** Whether a user is currently authenticated. */
15
15
  isAuthenticated: boolean;
16
16
  /**
17
- * `true` during the initial session probe and during a token refresh
18
- * with no currently valid access token. Settles to `false` once
19
- * authentication state is known.
17
+ * `true` while the provider's initial session probe is in flight; `false`
18
+ * once it settles, and from the start when the provider is given
19
+ * `initialState`. A later token refresh does not set it.
20
20
  */
21
21
  isLoading: boolean;
22
22
  /**
@@ -19,8 +19,9 @@ export interface OAuth2ProviderProps {
19
19
  * Optional pre-resolved auth state (typically from a server-side
20
20
  * session probe). Populating this skips the on-mount session probe
21
21
  * and renders the authenticated tree on the first paint — no
22
- * hydration flash. Pass a stable reference (don't construct it inline
23
- * in render) so the on-mount effect doesn't re-run.
22
+ * hydration flash. When a later render passes a different value, the
23
+ * provider re-seeds its state only if `isAuthenticated` or `user.sub`
24
+ * changed, so an object built inline in render is fine.
24
25
  */
25
26
  initialState?: Partial<OAuth2State>;
26
27
  /** The app subtree that reads auth state via `useOAuth2()`. */
@@ -19,24 +19,12 @@ const DEFAULT_STATE = {
19
19
  };
20
20
  const RENEW_SKEW_SECONDS = 30;
21
21
  const MIN_RENEW_MS = 5_000;
22
- /**
23
- * Fraction of the renew delay given up to jitter, applied one-sided so a renew
24
- * never lands later than the un-jittered schedule.
25
- */
26
22
  const RENEW_JITTER = 0.1;
27
23
  const isBrowser = () => typeof window !== "undefined";
28
- /**
29
- * Delay before renewing a session that expires in `expiresIn` seconds.
30
- *
31
- * Floors at {@link MIN_RENEW_MS}, so `0` — which a `DirectClient` reports for
32
- * an already-expired token a refresh token can still revive — schedules a
33
- * near-immediate renew rather than nothing at all.
34
- */
35
24
  function renewDelayMs(expiresIn) {
36
25
  const base = Math.max(MIN_RENEW_MS, (expiresIn - RENEW_SKEW_SECONDS) * 1000);
37
26
  return base * (1 - RENEW_JITTER * Math.random());
38
27
  }
39
- /** Seconds until `tokens` expires, or `null` when the server said nothing. */
40
28
  function expiresInFrom(tokens) {
41
29
  if (!tokens.accessTokenExpiresAt)
42
30
  return null;
@@ -65,7 +53,6 @@ export function OAuth2Provider(props) {
65
53
  let cancelled = false;
66
54
  const handle = (event) => {
67
55
  setState((prev) => reduce(prev, event));
68
- // The `authenticated` event carries tokens only, so refetch user claims.
69
56
  if (event.type === "authenticated") {
70
57
  client.getUser().then((user) => {
71
58
  if (!cancelled)
@@ -5,9 +5,6 @@
5
5
  * `returnTo` set to the current path, and renders a configurable
6
6
  * `fallback` while the redirect is in flight.
7
7
  *
8
- * Avoids the `withAuthenticationRequired` re-mount footgun (the HOC
9
- * isn't created in render; this component just gates its children).
10
- *
11
8
  * @module
12
9
  */
13
10
  import { type ReactNode } from "react";
@@ -6,9 +6,6 @@
6
6
  * `returnTo` set to the current path, and renders a configurable
7
7
  * `fallback` while the redirect is in flight.
8
8
  *
9
- * Avoids the `withAuthenticationRequired` re-mount footgun (the HOC
10
- * isn't created in render; this component just gates its children).
11
- *
12
9
  * @module
13
10
  */
14
11
  import { useEffect } from "react";
@@ -132,8 +132,9 @@ export declare class MockOAuth2Client extends OAuth2ClientBase implements MockOA
132
132
  setSessionExpiresIn(seconds: number | null): void;
133
133
  }
134
134
  /**
135
- * A real `BffClient` whose four session operations answer from mock state
136
- * instead of the BFF. `useBffClient()` accepts it.
135
+ * A real `BffClient` whose session operations, `login`, `logout` and `fetch`
136
+ * answer from mock state or pass through instead of reaching the BFF.
137
+ * `useBffClient()` accepts it.
137
138
  */
138
139
  export declare class MockBffClient extends BffClient implements MockOAuth2Controls {
139
140
  #private;
@@ -47,7 +47,6 @@ import "../_dnt.polyfills.js";
47
47
  import { useMemo } from "react";
48
48
  import { BffClient, DirectClient, OAuth2ClientBase, } from "../client/mod.js";
49
49
  import { OAuth2Context } from "./context.js";
50
- /** The mock behavior each client class delegates to. */
51
50
  class MockCore {
52
51
  initialState;
53
52
  renewCount = 0;
@@ -197,8 +196,9 @@ export class MockOAuth2Client extends OAuth2ClientBase {
197
196
  }
198
197
  }
199
198
  /**
200
- * A real `BffClient` whose four session operations answer from mock state
201
- * instead of the BFF. `useBffClient()` accepts it.
199
+ * A real `BffClient` whose session operations, `login`, `logout` and `fetch`
200
+ * answer from mock state or pass through instead of reaching the BFF.
201
+ * `useBffClient()` accepts it.
202
202
  */
203
203
  export class MockBffClient extends BffClient {
204
204
  #core;
@@ -3,7 +3,7 @@ import { type BaseLoginOptions, BffClient, DirectClient, type LogoutOptions, typ
3
3
  export interface UseOAuth2Result {
4
4
  /** True iff a user is authenticated. */
5
5
  isAuthenticated: boolean;
6
- /** True during the initial probe / a refresh in progress. */
6
+ /** True while the provider's initial session probe is in flight. */
7
7
  isLoading: boolean;
8
8
  /** Current user claims, or `null`. */
9
9
  user: UserInfoClaims | null;