@udibo/oauth2 0.1.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.
- package/LICENSE +20 -0
- package/README.md +174 -0
- package/SECURITY.md +77 -0
- package/esm/_dnt.polyfills.d.ts +17 -0
- package/esm/_dnt.polyfills.js +15 -0
- package/esm/adapters/hono/_common.d.ts +66 -0
- package/esm/adapters/hono/_common.js +108 -0
- package/esm/adapters/hono/authorization-server.d.ts +211 -0
- package/esm/adapters/hono/authorization-server.js +214 -0
- package/esm/adapters/hono/bff/auth-request-store.d.ts +164 -0
- package/esm/adapters/hono/bff/auth-request-store.js +154 -0
- package/esm/adapters/hono/bff/bff.d.ts +852 -0
- package/esm/adapters/hono/bff/bff.js +1194 -0
- package/esm/adapters/hono/bff/cookie-prefix.d.ts +72 -0
- package/esm/adapters/hono/bff/cookie-prefix.js +85 -0
- package/esm/adapters/hono/bff/mod.d.ts +19 -0
- package/esm/adapters/hono/bff/mod.js +19 -0
- package/esm/adapters/hono/bff/proxy.d.ts +108 -0
- package/esm/adapters/hono/bff/proxy.js +314 -0
- package/esm/adapters/hono/bff/session-store.d.ts +250 -0
- package/esm/adapters/hono/bff/session-store.js +216 -0
- package/esm/adapters/hono/bff/testing.d.ts +165 -0
- package/esm/adapters/hono/bff/testing.js +254 -0
- package/esm/adapters/hono/identity.d.ts +117 -0
- package/esm/adapters/hono/identity.js +215 -0
- package/esm/adapters/hono/log.d.ts +49 -0
- package/esm/adapters/hono/log.js +66 -0
- package/esm/adapters/hono/resource-server.d.ts +101 -0
- package/esm/adapters/hono/resource-server.js +108 -0
- package/esm/client/_http.d.ts +91 -0
- package/esm/client/_http.js +303 -0
- package/esm/client/base.d.ts +184 -0
- package/esm/client/base.js +93 -0
- package/esm/client/bff-client.d.ts +235 -0
- package/esm/client/bff-client.js +280 -0
- package/esm/client/browser-storage.d.ts +152 -0
- package/esm/client/browser-storage.js +246 -0
- package/esm/client/check.d.ts +114 -0
- package/esm/client/check.js +97 -0
- package/esm/client/direct-client.d.ts +707 -0
- package/esm/client/direct-client.js +1237 -0
- package/esm/client/discovery-cache.d.ts +138 -0
- package/esm/client/discovery-cache.js +124 -0
- package/esm/client/events.d.ts +54 -0
- package/esm/client/events.js +37 -0
- package/esm/client/mod.d.ts +35 -0
- package/esm/client/mod.js +33 -0
- package/esm/client/storage.d.ts +127 -0
- package/esm/client/storage.js +81 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/almost_equals.d.ts +29 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/almost_equals.js +47 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/array_includes.d.ts +28 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/array_includes.js +65 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/assert.d.ts +16 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/assert.js +23 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/assertion_error.d.ts +25 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/assertion_error.js +30 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/equal.d.ts +24 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/equal.js +193 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/equals.d.ts +36 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/equals.js +61 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/exists.d.ts +17 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/exists.js +27 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/fail.d.ts +14 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/fail.js +20 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/false.d.ts +18 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/false.js +23 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/greater.d.ts +19 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/greater.js +29 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/greater_or_equal.d.ts +19 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/greater_or_equal.js +29 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/instance_of.d.ts +22 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/instance_of.js +52 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/is_error.d.ts +24 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/is_error.js +53 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/less.d.ts +18 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/less.js +28 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/less_or_equal.d.ts +19 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/less_or_equal.js +29 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/match.d.ts +17 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/match.js +26 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/mod.d.ts +43 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/mod.js +45 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/not_equals.d.ts +26 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/not_equals.js +39 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/not_instance_of.d.ts +19 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/not_instance_of.js +29 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/not_match.d.ts +17 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/not_match.js +26 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/not_strict_equals.d.ts +22 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/not_strict_equals.js +32 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/object_match.d.ts +28 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/object_match.js +177 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/rejects.d.ts +41 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/rejects.js +53 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/strict_equals.d.ts +23 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/strict_equals.js +58 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/string_includes.d.ts +17 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/string_includes.js +26 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/throws.d.ts +44 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/throws.js +44 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/unimplemented.d.ts +14 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/unimplemented.js +20 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/unreachable.d.ts +14 -0
- package/esm/deps/jsr.io/@std/assert/1.0.19/unreachable.js +20 -0
- package/esm/deps/jsr.io/@std/async/1.5.0/delay.d.ts +44 -0
- package/esm/deps/jsr.io/@std/async/1.5.0/delay.js +84 -0
- package/esm/deps/jsr.io/@std/crypto/1.1.0/timing_safe_equal.d.ts +39 -0
- package/esm/deps/jsr.io/@std/crypto/1.1.0/timing_safe_equal.js +61 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/_common16.d.ts +22 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/_common16.js +51 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/_common32.d.ts +34 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/_common32.js +192 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/_common64.d.ts +34 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/_common64.js +113 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/_common_detach.d.ts +3 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/_common_detach.js +13 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/_types.d.ts +8 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/_types.js +2 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/_validate_binary_like.d.ts +1 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/_validate_binary_like.js +26 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/ascii85.d.ts +60 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/ascii85.js +152 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/base32.d.ts +39 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/base32.js +87 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/base58.d.ts +39 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/base58.js +131 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/base64.d.ts +39 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/base64.js +82 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/base64url.d.ts +39 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/base64url.js +72 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/hex.d.ts +38 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/hex.js +87 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/mod.d.ts +97 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/mod.js +99 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/varint.d.ts +119 -0
- package/esm/deps/jsr.io/@std/encoding/1.0.11/varint.js +209 -0
- package/esm/deps/jsr.io/@std/http/1.1.3/status.d.ts +343 -0
- package/esm/deps/jsr.io/@std/http/1.1.3/status.js +343 -0
- package/esm/deps/jsr.io/@std/internal/1.0.14/assertion_state.d.ts +159 -0
- package/esm/deps/jsr.io/@std/internal/1.0.14/assertion_state.js +223 -0
- package/esm/deps/jsr.io/@std/internal/1.0.14/build_message.d.ts +79 -0
- package/esm/deps/jsr.io/@std/internal/1.0.14/build_message.js +116 -0
- package/esm/deps/jsr.io/@std/internal/1.0.14/diff.d.ts +139 -0
- package/esm/deps/jsr.io/@std/internal/1.0.14/diff.js +277 -0
- package/esm/deps/jsr.io/@std/internal/1.0.14/diff_str.d.ts +98 -0
- package/esm/deps/jsr.io/@std/internal/1.0.14/diff_str.js +181 -0
- package/esm/deps/jsr.io/@std/internal/1.0.14/format.d.ts +29 -0
- package/esm/deps/jsr.io/@std/internal/1.0.14/format.js +68 -0
- package/esm/deps/jsr.io/@std/internal/1.0.14/styles.d.ts +158 -0
- package/esm/deps/jsr.io/@std/internal/1.0.14/styles.js +206 -0
- package/esm/deps/jsr.io/@std/internal/1.0.14/types.d.ts +24 -0
- package/esm/deps/jsr.io/@std/internal/1.0.14/types.js +3 -0
- package/esm/deps/jsr.io/@std/testing/1.0.20/_test_suite.d.ts +91 -0
- package/esm/deps/jsr.io/@std/testing/1.0.20/_test_suite.js +347 -0
- package/esm/deps/jsr.io/@std/testing/1.0.20/bdd.d.ts +382 -0
- package/esm/deps/jsr.io/@std/testing/1.0.20/bdd.js +1110 -0
- package/esm/deps/jsr.io/@udibo/http-error/0.11.1/mod.d.ts +363 -0
- package/esm/deps/jsr.io/@udibo/http-error/0.11.1/mod.js +616 -0
- package/esm/errors.d.ts +203 -0
- package/esm/errors.js +287 -0
- package/esm/identity/captcha.d.ts +147 -0
- package/esm/identity/captcha.js +64 -0
- package/esm/identity/delivery.d.ts +96 -0
- package/esm/identity/delivery.js +41 -0
- package/esm/identity/errors.d.ts +39 -0
- package/esm/identity/errors.js +46 -0
- package/esm/identity/events.d.ts +242 -0
- package/esm/identity/events.js +39 -0
- package/esm/identity/external/_shared.d.ts +86 -0
- package/esm/identity/external/_shared.js +107 -0
- package/esm/identity/external/_token-exchange.d.ts +61 -0
- package/esm/identity/external/_token-exchange.js +179 -0
- package/esm/identity/external/apple-client-secret.d.ts +82 -0
- package/esm/identity/external/apple-client-secret.js +133 -0
- package/esm/identity/external/apple.d.ts +64 -0
- package/esm/identity/external/apple.js +248 -0
- package/esm/identity/external/discord.d.ts +38 -0
- package/esm/identity/external/discord.js +62 -0
- package/esm/identity/external/errors.d.ts +48 -0
- package/esm/identity/external/errors.js +36 -0
- package/esm/identity/external/flow.d.ts +107 -0
- package/esm/identity/external/flow.js +158 -0
- package/esm/identity/external/github.d.ts +42 -0
- package/esm/identity/external/github.js +164 -0
- package/esm/identity/external/google.d.ts +52 -0
- package/esm/identity/external/google.js +38 -0
- package/esm/identity/external/mod.d.ts +63 -0
- package/esm/identity/external/mod.js +61 -0
- package/esm/identity/external/oauth2.d.ts +110 -0
- package/esm/identity/external/oauth2.js +158 -0
- package/esm/identity/external/oidc.d.ts +81 -0
- package/esm/identity/external/oidc.js +250 -0
- package/esm/identity/external/provider.d.ts +132 -0
- package/esm/identity/external/provider.js +12 -0
- package/esm/identity/hibp.d.ts +67 -0
- package/esm/identity/hibp.js +87 -0
- package/esm/identity/identifier.d.ts +44 -0
- package/esm/identity/identifier.js +47 -0
- package/esm/identity/lockout.d.ts +155 -0
- package/esm/identity/lockout.js +117 -0
- package/esm/identity/mfa/mod.d.ts +42 -0
- package/esm/identity/mfa/mod.js +42 -0
- package/esm/identity/mfa/recovery-codes.d.ts +96 -0
- package/esm/identity/mfa/recovery-codes.js +80 -0
- package/esm/identity/mfa/service.d.ts +338 -0
- package/esm/identity/mfa/service.js +351 -0
- package/esm/identity/mfa/totp.d.ts +186 -0
- package/esm/identity/mfa/totp.js +194 -0
- package/esm/identity/migration.d.ts +155 -0
- package/esm/identity/migration.js +243 -0
- package/esm/identity/mod.d.ts +65 -0
- package/esm/identity/mod.js +64 -0
- package/esm/identity/otp.d.ts +255 -0
- package/esm/identity/otp.js +245 -0
- package/esm/identity/password-policy.d.ts +45 -0
- package/esm/identity/password-policy.js +60 -0
- package/esm/identity/password.d.ts +171 -0
- package/esm/identity/password.js +160 -0
- package/esm/identity/rate-limit.d.ts +192 -0
- package/esm/identity/rate-limit.js +168 -0
- package/esm/identity/service.d.ts +579 -0
- package/esm/identity/service.js +835 -0
- package/esm/identity/session.d.ts +122 -0
- package/esm/identity/session.js +48 -0
- package/esm/identity/token-flow.d.ts +214 -0
- package/esm/identity/token-flow.js +173 -0
- package/esm/models/authentication.d.ts +24 -0
- package/esm/models/authentication.js +1 -0
- package/esm/models/authorization-code.d.ts +63 -0
- package/esm/models/authorization-code.js +1 -0
- package/esm/models/authorization.d.ts +165 -0
- package/esm/models/authorization.js +160 -0
- package/esm/models/client.d.ts +78 -0
- package/esm/models/client.js +1 -0
- package/esm/models/device-authorization.d.ts +30 -0
- package/esm/models/device-authorization.js +1 -0
- package/esm/models/responses.d.ts +93 -0
- package/esm/models/responses.js +1 -0
- package/esm/models/scope.d.ts +81 -0
- package/esm/models/scope.js +125 -0
- package/esm/models/token.d.ts +135 -0
- package/esm/models/token.js +1 -0
- package/esm/package.json +3 -0
- package/esm/react/callback.d.ts +52 -0
- package/esm/react/callback.js +71 -0
- package/esm/react/components/class-names.d.ts +91 -0
- package/esm/react/components/class-names.js +118 -0
- package/esm/react/components/internal.d.ts +62 -0
- package/esm/react/components/internal.js +102 -0
- package/esm/react/components/mfa-challenge-form.d.ts +66 -0
- package/esm/react/components/mfa-challenge-form.js +46 -0
- package/esm/react/components/mfa-enrollment-form.d.ts +83 -0
- package/esm/react/components/mfa-enrollment-form.js +41 -0
- package/esm/react/components/mod.d.ts +103 -0
- package/esm/react/components/mod.js +93 -0
- package/esm/react/components/request-password-reset-form.d.ts +61 -0
- package/esm/react/components/request-password-reset-form.js +41 -0
- package/esm/react/components/reset-password-form.d.ts +66 -0
- package/esm/react/components/reset-password-form.js +56 -0
- package/esm/react/components/sign-in-form.d.ts +74 -0
- package/esm/react/components/sign-in-form.js +43 -0
- package/esm/react/components/sign-up-form.d.ts +85 -0
- package/esm/react/components/sign-up-form.js +57 -0
- package/esm/react/components/types.d.ts +208 -0
- package/esm/react/components/types.js +14 -0
- package/esm/react/components/use-auth-form.d.ts +118 -0
- package/esm/react/components/use-auth-form.js +172 -0
- package/esm/react/components/user-menu.d.ts +79 -0
- package/esm/react/components/user-menu.js +73 -0
- package/esm/react/context.d.ts +51 -0
- package/esm/react/context.js +16 -0
- package/esm/react/mod.d.ts +26 -0
- package/esm/react/mod.js +26 -0
- package/esm/react/provider.d.ts +40 -0
- package/esm/react/provider.js +189 -0
- package/esm/react/require-auth.d.ts +31 -0
- package/esm/react/require-auth.js +37 -0
- package/esm/react/testing.d.ts +322 -0
- package/esm/react/testing.js +461 -0
- package/esm/react/use-authorization.d.ts +31 -0
- package/esm/react/use-authorization.js +36 -0
- package/esm/react/use-oauth2.d.ts +112 -0
- package/esm/react/use-oauth2.js +127 -0
- package/esm/server/authorization-server.d.ts +736 -0
- package/esm/server/authorization-server.js +1304 -0
- package/esm/server/client-authentication.d.ts +28 -0
- package/esm/server/client-authentication.js +51 -0
- package/esm/server/grants/authorization-code.d.ts +179 -0
- package/esm/server/grants/authorization-code.js +232 -0
- package/esm/server/grants/client-credentials.d.ts +52 -0
- package/esm/server/grants/client-credentials.js +53 -0
- package/esm/server/grants/device-authorization.d.ts +103 -0
- package/esm/server/grants/device-authorization.js +156 -0
- package/esm/server/grants/grant.d.ts +165 -0
- package/esm/server/grants/grant.js +173 -0
- package/esm/server/grants/password.d.ts +80 -0
- package/esm/server/grants/password.js +92 -0
- package/esm/server/grants/refresh-token.d.ts +80 -0
- package/esm/server/grants/refresh-token.js +136 -0
- package/esm/server/introspection-token-reader.d.ts +153 -0
- package/esm/server/introspection-token-reader.js +164 -0
- package/esm/server/jwks-token-reader.d.ts +263 -0
- package/esm/server/jwks-token-reader.js +508 -0
- package/esm/server/mod.d.ts +23 -0
- package/esm/server/mod.js +15 -0
- package/esm/server/public-suffix/mod.d.ts +62 -0
- package/esm/server/public-suffix/mod.js +103 -0
- package/esm/server/public-suffix/rules.d.ts +23 -0
- package/esm/server/public-suffix/rules.js +23 -0
- package/esm/server/redirect-uri.d.ts +187 -0
- package/esm/server/redirect-uri.js +331 -0
- package/esm/server/resource-server.d.ts +286 -0
- package/esm/server/resource-server.js +365 -0
- package/esm/server/services/authorization-code.d.ts +84 -0
- package/esm/server/services/authorization-code.js +35 -0
- package/esm/server/services/client.d.ts +35 -0
- package/esm/server/services/client.js +1 -0
- package/esm/server/services/device-authorization.d.ts +134 -0
- package/esm/server/services/device-authorization.js +7 -0
- package/esm/server/services/token.d.ts +242 -0
- package/esm/server/services/token.js +78 -0
- package/esm/server/services/user.d.ts +29 -0
- package/esm/server/services/user.js +7 -0
- package/esm/server/signing-keys.d.ts +201 -0
- package/esm/server/signing-keys.js +225 -0
- package/esm/server/utils/hash.d.ts +6 -0
- package/esm/server/utils/hash.js +11 -0
- package/esm/testing/contract/authorization-code.d.ts +37 -0
- package/esm/testing/contract/authorization-code.js +93 -0
- package/esm/testing/contract/client.d.ts +85 -0
- package/esm/testing/contract/client.js +126 -0
- package/esm/testing/contract/device-authorization.d.ts +37 -0
- package/esm/testing/contract/device-authorization.js +193 -0
- package/esm/testing/contract/lockout-store.d.ts +47 -0
- package/esm/testing/contract/lockout-store.js +122 -0
- package/esm/testing/contract/mfa-store.d.ts +50 -0
- package/esm/testing/contract/mfa-store.js +226 -0
- package/esm/testing/contract/mod.d.ts +39 -0
- package/esm/testing/contract/mod.js +39 -0
- package/esm/testing/contract/otp-store.d.ts +48 -0
- package/esm/testing/contract/otp-store.js +197 -0
- package/esm/testing/contract/rate-limit-store.d.ts +52 -0
- package/esm/testing/contract/rate-limit-store.js +105 -0
- package/esm/testing/contract/token-flow-store.d.ts +54 -0
- package/esm/testing/contract/token-flow-store.js +179 -0
- package/esm/testing/contract/token-reader.d.ts +77 -0
- package/esm/testing/contract/token-reader.js +78 -0
- package/esm/testing/contract/token.d.ts +61 -0
- package/esm/testing/contract/token.js +287 -0
- package/esm/testing/contract/user.d.ts +63 -0
- package/esm/testing/contract/user.js +75 -0
- package/esm/testing/mod.d.ts +34 -0
- package/esm/testing/mod.js +34 -0
- package/esm/testing/server.d.ts +197 -0
- package/esm/testing/server.js +193 -0
- package/esm/testing/services.d.ts +373 -0
- package/esm/testing/services.js +703 -0
- package/esm/utils/_buffer.d.ts +10 -0
- package/esm/utils/_buffer.js +12 -0
- package/esm/utils/_timing-safe.d.ts +13 -0
- package/esm/utils/_timing-safe.js +22 -0
- package/esm/utils/authentication-context.d.ts +3 -0
- package/esm/utils/authentication-context.js +14 -0
- package/esm/utils/basic-auth.d.ts +50 -0
- package/esm/utils/basic-auth.js +106 -0
- package/esm/utils/crypto.d.ts +97 -0
- package/esm/utils/crypto.js +162 -0
- package/esm/utils/pkce.d.ts +151 -0
- package/esm/utils/pkce.js +179 -0
- package/esm/utils/text.d.ts +24 -0
- package/esm/utils/text.js +28 -0
- package/esm/utils/url.d.ts +66 -0
- package/esm/utils/url.js +90 -0
- package/package.json +92 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef } from "react";
|
|
3
|
+
import { AuthFormClassNamesScope, useAuthFormClassNames, } from "./class-names.js";
|
|
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.
|
|
16
|
+
*/
|
|
17
|
+
export function AuthFormShell(props) {
|
|
18
|
+
const { name, form, className, classNames, render, rootProps, aboveForm, succeeded, children, action, method, formRef, } = props;
|
|
19
|
+
return (_jsx(AuthFormClassNamesScope, { classNames: classNames, children: render ? render(form) : (_jsxs("div", { "data-oauth2-form": name, ...rootProps, className: combineClassNames(className, classNames.root), children: [aboveForm, succeeded ?? (_jsxs("form", { onSubmit: form.handleSubmit, action: action, method: method ?? (action ? "post" : undefined), ref: formRef, noValidate: true, "data-oauth2-form-element": "", className: classNames.form, children: [_jsx(ErrorSummary, { error: form.formError, focusTrigger: form.submitCount }), children] }))] })) }));
|
|
20
|
+
}
|
|
21
|
+
/** The actions row and its submit button, disabled while the form is in flight. */
|
|
22
|
+
export function SubmitButton(props) {
|
|
23
|
+
const { isSubmitting, label, submittingLabel } = props;
|
|
24
|
+
const classNames = useAuthFormClassNames();
|
|
25
|
+
return (_jsx("div", { "data-oauth2-actions": "", className: classNames.actions, children: _jsx("button", { type: "submit", disabled: isSubmitting, "data-oauth2-submit": "", className: classNames.submit, children: isSubmitting ? submittingLabel : label }) }));
|
|
26
|
+
}
|
|
27
|
+
export function TextField(props) {
|
|
28
|
+
const { fieldProps, label, type = "text", autoComplete, required, error, inputRef, children, } = props;
|
|
29
|
+
const classNames = useAuthFormClassNames();
|
|
30
|
+
return (_jsxs("div", { "data-oauth2-field": fieldProps.name, className: classNames.field, children: [_jsx("label", { htmlFor: fieldProps.id, "data-oauth2-label": "", className: classNames.label, children: label }), _jsx("input", { ...fieldProps, type: type, autoComplete: autoComplete, required: required, "data-oauth2-input": "", className: classNames.input, ref: inputRef }), children, error
|
|
31
|
+
? (_jsx("p", { id: `${fieldProps.id}-error`, role: "alert", "data-oauth2-error": "", className: classNames.error, children: error }))
|
|
32
|
+
: null] }));
|
|
33
|
+
}
|
|
34
|
+
export function ErrorSummary(props) {
|
|
35
|
+
const { error, focusTrigger } = props;
|
|
36
|
+
const classNames = useAuthFormClassNames();
|
|
37
|
+
const ref = useRef(null);
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (error)
|
|
40
|
+
ref.current?.focus();
|
|
41
|
+
}, [error, focusTrigger]);
|
|
42
|
+
if (!error)
|
|
43
|
+
return null;
|
|
44
|
+
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 }) }));
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Moves keyboard focus to the first field that came back with an error after a
|
|
48
|
+
* failed submit, so a screen-reader / keyboard user lands on the control to fix
|
|
49
|
+
* (its label + error are read via `aria-describedby`) instead of being stranded
|
|
50
|
+
* on the disabled submit button. No-ops on the initial render and when the form
|
|
51
|
+
* carries a form-level error ({@link ErrorSummary} owns that focus). Call it
|
|
52
|
+
* once, unconditionally, from a form component built on {@link useAuthForm}.
|
|
53
|
+
*/
|
|
54
|
+
export function useFocusFirstError(form) {
|
|
55
|
+
const handled = useRef(0);
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
if (form.status !== "error")
|
|
58
|
+
return;
|
|
59
|
+
if (handled.current === form.submitCount)
|
|
60
|
+
return;
|
|
61
|
+
handled.current = form.submitCount;
|
|
62
|
+
if (form.formError)
|
|
63
|
+
return;
|
|
64
|
+
const names = Object.keys(form.values);
|
|
65
|
+
const firstErrored = names.find((name) => Boolean(form.errors[name]));
|
|
66
|
+
if (!firstErrored)
|
|
67
|
+
return;
|
|
68
|
+
const { id } = form.getFieldProps(firstErrored);
|
|
69
|
+
if (typeof document === "undefined")
|
|
70
|
+
return;
|
|
71
|
+
document.getElementById(id)?.focus();
|
|
72
|
+
}, [form.status, form.submitCount, form.formError]);
|
|
73
|
+
}
|
|
74
|
+
export function SocialButtons(props) {
|
|
75
|
+
const { socialProviders, onSocialSelect, socialHref } = props;
|
|
76
|
+
const classNames = useAuthFormClassNames();
|
|
77
|
+
if (!socialProviders || socialProviders.length === 0)
|
|
78
|
+
return null;
|
|
79
|
+
const label = (provider) => (_jsxs(_Fragment, { children: [provider.iconSlot
|
|
80
|
+
? _jsx("span", { "data-oauth2-social-icon": "", children: provider.iconSlot })
|
|
81
|
+
: null, _jsxs("span", { "data-oauth2-social-label": "", children: ["Continue with ", provider.name] })] }));
|
|
82
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { "data-oauth2-social": "", className: classNames.socialSection, children: socialProviders.map((provider) => socialHref
|
|
83
|
+
? (_jsx("a", { href: socialHref(provider), "data-oauth2-social-button": provider.id, className: classNames.socialButton, children: label(provider) }, provider.id))
|
|
84
|
+
: (_jsx("button", { type: "button", onClick: () => onSocialSelect?.(provider), "data-oauth2-social-button": provider.id, className: classNames.socialButton, children: label(provider) }, provider.id))) }), _jsx("div", { "data-oauth2-social-divider": "", "aria-hidden": "true", className: classNames.socialDivider })] }));
|
|
85
|
+
}
|
|
86
|
+
export function combineClassNames(className, slot) {
|
|
87
|
+
return [className, slot].filter(Boolean).join(" ") || undefined;
|
|
88
|
+
}
|
|
89
|
+
export function displayName(user) {
|
|
90
|
+
const candidates = [
|
|
91
|
+
user.name,
|
|
92
|
+
user.preferred_username,
|
|
93
|
+
user.nickname,
|
|
94
|
+
user.email,
|
|
95
|
+
user.sub,
|
|
96
|
+
];
|
|
97
|
+
for (const candidate of candidates) {
|
|
98
|
+
if (typeof candidate === "string" && candidate.length > 0)
|
|
99
|
+
return candidate;
|
|
100
|
+
}
|
|
101
|
+
return "Account";
|
|
102
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* {@link MfaChallengeForm} — a default, accessible, unstyled second-factor
|
|
3
|
+
* challenge form built on {@link useAuthForm}. Collects a one-time code and,
|
|
4
|
+
* when allowed, toggles to accepting a recovery code instead. The active
|
|
5
|
+
* method is forwarded to `onSubmit` so your endpoint knows how to verify it.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
import type { ReactNode } from "react";
|
|
10
|
+
import type { AuthFormState } from "./use-auth-form.js";
|
|
11
|
+
import type { BaseAuthFormProps } from "./types.js";
|
|
12
|
+
/** Which credential the user is submitting. */
|
|
13
|
+
export type MfaChallengeMethod = "totp" | "recovery";
|
|
14
|
+
/** Values collected by {@link MfaChallengeForm}. */
|
|
15
|
+
export type MfaChallengeValues = {
|
|
16
|
+
/** The one-time code or recovery code the user entered. */
|
|
17
|
+
code: string;
|
|
18
|
+
/** Which credential `code` represents. */
|
|
19
|
+
method: MfaChallengeMethod;
|
|
20
|
+
};
|
|
21
|
+
/** Visible text overrides for {@link MfaChallengeForm}. */
|
|
22
|
+
export interface MfaChallengeLabels {
|
|
23
|
+
/** Label for the TOTP code field. Default: `"Authentication code"`. */
|
|
24
|
+
code?: string;
|
|
25
|
+
/** Label for the recovery-code field. Default: `"Recovery code"`. */
|
|
26
|
+
recoveryCode?: string;
|
|
27
|
+
/** Submit button text. Default: `"Verify"`. */
|
|
28
|
+
submit?: string;
|
|
29
|
+
/** Submit button text while submitting. Default: `"Verifying…"`. */
|
|
30
|
+
submitting?: string;
|
|
31
|
+
/** Toggle text to switch to recovery mode. Default: `"Use a recovery code"`. */
|
|
32
|
+
useRecovery?: string;
|
|
33
|
+
/** Toggle text to switch back to code mode. Default: `"Use your authenticator app"`. */
|
|
34
|
+
useAuthenticator?: string;
|
|
35
|
+
}
|
|
36
|
+
/** Props for {@link MfaChallengeForm}. */
|
|
37
|
+
export interface MfaChallengeFormProps extends BaseAuthFormProps<MfaChallengeValues> {
|
|
38
|
+
/** Offer the "use a recovery code" toggle. Default: `true`. */
|
|
39
|
+
allowRecoveryCode?: boolean;
|
|
40
|
+
/** Text overrides. */
|
|
41
|
+
labels?: MfaChallengeLabels;
|
|
42
|
+
/**
|
|
43
|
+
* Render-prop escape hatch. When provided, you own the markup and receive
|
|
44
|
+
* the live {@link AuthFormState}. Toggle `method` with `setValue`.
|
|
45
|
+
*/
|
|
46
|
+
children?: (state: AuthFormState<MfaChallengeValues>) => ReactNode;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* A ready-to-use MFA challenge form. Wire `onSubmit` to your verification
|
|
50
|
+
* endpoint; inspect `values.method` to decide how to validate `values.code`.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```tsx
|
|
54
|
+
* <MfaChallengeForm
|
|
55
|
+
* onSubmit={async (values) => {
|
|
56
|
+
* const res = await fetch("/auth/mfa/verify", {
|
|
57
|
+
* method: "POST",
|
|
58
|
+
* headers: { "content-type": "application/json" },
|
|
59
|
+
* body: JSON.stringify(values),
|
|
60
|
+
* });
|
|
61
|
+
* if (!res.ok) return { error: "That code was not valid." };
|
|
62
|
+
* }}
|
|
63
|
+
* />
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
export declare function MfaChallengeForm(props: MfaChallengeFormProps): ReactNode;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useAuthForm } from "./use-auth-form.js";
|
|
3
|
+
import { AuthFormShell, SubmitButton, TextField, useFocusFirstError, } from "./internal.js";
|
|
4
|
+
import { useAuthFormClassNames } from "./class-names.js";
|
|
5
|
+
/**
|
|
6
|
+
* A ready-to-use MFA challenge form. Wire `onSubmit` to your verification
|
|
7
|
+
* endpoint; inspect `values.method` to decide how to validate `values.code`.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```tsx
|
|
11
|
+
* <MfaChallengeForm
|
|
12
|
+
* onSubmit={async (values) => {
|
|
13
|
+
* const res = await fetch("/auth/mfa/verify", {
|
|
14
|
+
* method: "POST",
|
|
15
|
+
* headers: { "content-type": "application/json" },
|
|
16
|
+
* body: JSON.stringify(values),
|
|
17
|
+
* });
|
|
18
|
+
* if (!res.ok) return { error: "That code was not valid." };
|
|
19
|
+
* }}
|
|
20
|
+
* />
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export function MfaChallengeForm(props) {
|
|
24
|
+
const { onSubmit, className, allowRecoveryCode = true, labels, children, } = props;
|
|
25
|
+
const form = useAuthForm({
|
|
26
|
+
initialValues: { code: "", method: "totp" },
|
|
27
|
+
onSubmit,
|
|
28
|
+
onSuccess: props.onSuccess,
|
|
29
|
+
});
|
|
30
|
+
useFocusFirstError(form);
|
|
31
|
+
const classNames = useAuthFormClassNames(props.classNames);
|
|
32
|
+
const isRecovery = form.values.method === "recovery";
|
|
33
|
+
const toggleMethod = () => {
|
|
34
|
+
form.setValues({
|
|
35
|
+
method: isRecovery ? "totp" : "recovery",
|
|
36
|
+
code: "",
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
return (_jsxs(AuthFormShell, { name: "mfa-challenge", form: form, className: className, classNames: classNames, render: children, action: props.action, method: props.method, formRef: props.formRef, rootProps: { "data-oauth2-method": form.values.method }, children: [_jsx(TextField, { fieldProps: form.getFieldProps("code"), label: isRecovery
|
|
40
|
+
? labels?.recoveryCode ?? "Recovery code"
|
|
41
|
+
: labels?.code ?? "Authentication code", autoComplete: isRecovery ? "off" : "one-time-code", required: true, error: form.errors.code }), _jsx(SubmitButton, { isSubmitting: form.isSubmitting, label: labels?.submit ?? "Verify", submittingLabel: labels?.submitting ?? "Verifying…" }), allowRecoveryCode
|
|
42
|
+
? (_jsx("button", { type: "button", onClick: toggleMethod, "data-oauth2-secondary": "", "data-oauth2-toggle-recovery": "", className: classNames.secondary, children: isRecovery
|
|
43
|
+
? labels?.useAuthenticator ?? "Use your authenticator app"
|
|
44
|
+
: labels?.useRecovery ?? "Use a recovery code" }))
|
|
45
|
+
: null] }));
|
|
46
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* {@link MfaEnrollmentForm} — a default, accessible, unstyled TOTP enrollment
|
|
3
|
+
* panel built on {@link useAuthForm}. Displays the shared secret / `otpauth://`
|
|
4
|
+
* URI (with a slot for your own QR renderer) and recovery codes, then collects
|
|
5
|
+
* a confirmation code to prove the authenticator is set up.
|
|
6
|
+
*
|
|
7
|
+
* The package ships no QR-code or crypto dependency: pass a rendered QR node
|
|
8
|
+
* via `qrSlot`, and provide the secret / URI / recovery codes as data.
|
|
9
|
+
*
|
|
10
|
+
* @module
|
|
11
|
+
*/
|
|
12
|
+
import type { ReactNode } from "react";
|
|
13
|
+
import type { AuthFormState } from "./use-auth-form.js";
|
|
14
|
+
import type { BaseAuthFormProps } from "./types.js";
|
|
15
|
+
/** The enrollment secret material to display, produced server-side. */
|
|
16
|
+
export interface MfaEnrollmentData {
|
|
17
|
+
/** Base32 shared secret for manual entry into an authenticator app. */
|
|
18
|
+
secret: string;
|
|
19
|
+
/** The full `otpauth://totp/...` provisioning URI (encode as a QR code). */
|
|
20
|
+
otpauthUri: string;
|
|
21
|
+
/** One-time recovery codes to show once and have the user save. */
|
|
22
|
+
recoveryCodes?: string[];
|
|
23
|
+
}
|
|
24
|
+
/** Values collected by {@link MfaEnrollmentForm}'s confirmation step. */
|
|
25
|
+
export type MfaEnrollmentValues = {
|
|
26
|
+
/** The code from the authenticator app confirming enrollment. */
|
|
27
|
+
code: string;
|
|
28
|
+
};
|
|
29
|
+
/** Visible text overrides for {@link MfaEnrollmentForm}. */
|
|
30
|
+
export interface MfaEnrollmentLabels {
|
|
31
|
+
/** Heading above the secret. Default: `"Scan this QR code"`. */
|
|
32
|
+
secretHeading?: string;
|
|
33
|
+
/** Prefix before the manual secret. Default: `"Or enter this code:"`. */
|
|
34
|
+
secretHint?: string;
|
|
35
|
+
/** Heading above recovery codes. Default: `"Save your recovery codes"`. */
|
|
36
|
+
recoveryHeading?: string;
|
|
37
|
+
/** Label for the confirmation code field. Default: `"Authentication code"`. */
|
|
38
|
+
code?: string;
|
|
39
|
+
/** Submit button text. Default: `"Confirm"`. */
|
|
40
|
+
submit?: string;
|
|
41
|
+
/** Submit button text while submitting. Default: `"Confirming…"`. */
|
|
42
|
+
submitting?: string;
|
|
43
|
+
}
|
|
44
|
+
/** Props for {@link MfaEnrollmentForm}. */
|
|
45
|
+
export interface MfaEnrollmentFormProps extends BaseAuthFormProps<MfaEnrollmentValues> {
|
|
46
|
+
/** The secret, URI, and recovery codes to display. */
|
|
47
|
+
data: MfaEnrollmentData;
|
|
48
|
+
/**
|
|
49
|
+
* Your rendered QR code for `data.otpauthUri` (the package ships no QR
|
|
50
|
+
* dependency). A node, or a function of the URI.
|
|
51
|
+
*/
|
|
52
|
+
qrSlot?: ReactNode | ((otpauthUri: string) => ReactNode);
|
|
53
|
+
/** Text overrides. */
|
|
54
|
+
labels?: MfaEnrollmentLabels;
|
|
55
|
+
/**
|
|
56
|
+
* Render-prop escape hatch. When provided, you own all of the markup —
|
|
57
|
+
* including the secret / QR / recovery-code display — and receive the live
|
|
58
|
+
* {@link AuthFormState}; the component still owns state and submit.
|
|
59
|
+
*/
|
|
60
|
+
children?: (state: AuthFormState<MfaEnrollmentValues>) => ReactNode;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* A ready-to-use TOTP enrollment panel: shows the QR/secret and recovery
|
|
64
|
+
* codes, then confirms via a code. Wire `onSubmit` to your verification
|
|
65
|
+
* endpoint and pass a rendered QR via `qrSlot`.
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```tsx
|
|
69
|
+
* <MfaEnrollmentForm
|
|
70
|
+
* data={{ secret, otpauthUri, recoveryCodes }}
|
|
71
|
+
* qrSlot={(uri) => <QRCode value={uri} />}
|
|
72
|
+
* onSubmit={async ({ code }) => {
|
|
73
|
+
* const res = await fetch("/auth/mfa/enroll/confirm", {
|
|
74
|
+
* method: "POST",
|
|
75
|
+
* headers: { "content-type": "application/json" },
|
|
76
|
+
* body: JSON.stringify({ code }),
|
|
77
|
+
* });
|
|
78
|
+
* if (!res.ok) return { error: "That code was not valid." };
|
|
79
|
+
* }}
|
|
80
|
+
* />
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
export declare function MfaEnrollmentForm(props: MfaEnrollmentFormProps): ReactNode;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useAuthForm } from "./use-auth-form.js";
|
|
3
|
+
import { AuthFormShell, SubmitButton, TextField, useFocusFirstError, } from "./internal.js";
|
|
4
|
+
import { useAuthFormClassNames } from "./class-names.js";
|
|
5
|
+
/**
|
|
6
|
+
* A ready-to-use TOTP enrollment panel: shows the QR/secret and recovery
|
|
7
|
+
* codes, then confirms via a code. Wire `onSubmit` to your verification
|
|
8
|
+
* endpoint and pass a rendered QR via `qrSlot`.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```tsx
|
|
12
|
+
* <MfaEnrollmentForm
|
|
13
|
+
* data={{ secret, otpauthUri, recoveryCodes }}
|
|
14
|
+
* qrSlot={(uri) => <QRCode value={uri} />}
|
|
15
|
+
* onSubmit={async ({ code }) => {
|
|
16
|
+
* const res = await fetch("/auth/mfa/enroll/confirm", {
|
|
17
|
+
* method: "POST",
|
|
18
|
+
* headers: { "content-type": "application/json" },
|
|
19
|
+
* body: JSON.stringify({ code }),
|
|
20
|
+
* });
|
|
21
|
+
* if (!res.ok) return { error: "That code was not valid." };
|
|
22
|
+
* }}
|
|
23
|
+
* />
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export function MfaEnrollmentForm(props) {
|
|
27
|
+
const { data, onSubmit, qrSlot, className, labels, children } = props;
|
|
28
|
+
const form = useAuthForm({
|
|
29
|
+
initialValues: { code: "" },
|
|
30
|
+
onSubmit,
|
|
31
|
+
onSuccess: props.onSuccess,
|
|
32
|
+
});
|
|
33
|
+
useFocusFirstError(form);
|
|
34
|
+
const classNames = useAuthFormClassNames(props.classNames);
|
|
35
|
+
const qr = typeof qrSlot === "function" ? qrSlot(data.otpauthUri) : qrSlot;
|
|
36
|
+
return (_jsxs(AuthFormShell, { name: "mfa-enrollment", form: form, className: className, classNames: classNames, render: children, action: props.action, method: props.method, formRef: props.formRef, aboveForm: _jsxs(_Fragment, { children: [_jsxs("div", { "data-oauth2-secret": "", className: classNames.secret, children: [_jsx("p", { role: "heading", "aria-level": 2, "data-oauth2-secret-heading": "", children: labels?.secretHeading ?? "Scan this QR code" }), qr
|
|
37
|
+
? _jsx("div", { "data-oauth2-qr": "", className: classNames.qr, children: qr })
|
|
38
|
+
: null, _jsxs("p", { "data-oauth2-secret-hint": "", children: [labels?.secretHint ?? "Or enter this code:", " ", _jsx("code", { "data-oauth2-secret-value": "", children: data.secret })] })] }), data.recoveryCodes && data.recoveryCodes.length > 0
|
|
39
|
+
? (_jsxs("div", { "data-oauth2-recovery": "", children: [_jsx("p", { role: "heading", "aria-level": 2, "data-oauth2-recovery-heading": "", children: labels?.recoveryHeading ?? "Save your recovery codes" }), _jsx("ul", { "data-oauth2-recovery-codes": "", className: classNames.recoveryCodes, children: data.recoveryCodes.map((recoveryCode) => (_jsx("li", { "data-oauth2-recovery-code": "", className: classNames.recoveryCode, children: _jsx("code", { children: recoveryCode }) }, recoveryCode))) })] }))
|
|
40
|
+
: null] }), children: [_jsx(TextField, { fieldProps: form.getFieldProps("code"), label: labels?.code ?? "Authentication code", autoComplete: "one-time-code", required: true, error: form.errors.code }), _jsx(SubmitButton, { isSubmitting: form.isSubmitting, label: labels?.submit ?? "Confirm", submittingLabel: labels?.submitting ?? "Confirming…" })] }));
|
|
41
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prebuilt React auth components for `@udibo/oauth2` — the composable,
|
|
3
|
+
* headless-first, themeable form set an app assembles into its own auth
|
|
4
|
+
* surface. Works in both plain React Router v7 and Juniper (SSR React).
|
|
5
|
+
*
|
|
6
|
+
* These components are **composed by your app**: you own the routes, storage,
|
|
7
|
+
* session, and the submit wiring. Nothing here mounts routes, talks to a
|
|
8
|
+
* server on its own, or assumes a design system — every component ships
|
|
9
|
+
* unstyled with per-slot `className` hooks and stable `data-oauth2-*`
|
|
10
|
+
* attributes, and takes its action via an injected `onSubmit` prop so the same
|
|
11
|
+
* component drives a client-side `fetch` or a router action.
|
|
12
|
+
*
|
|
13
|
+
* ## Two integration modes
|
|
14
|
+
*
|
|
15
|
+
* - **Default components** — {@link SignInForm}, {@link SignUpForm},
|
|
16
|
+
* {@link RequestPasswordResetForm}, {@link ResetPasswordForm},
|
|
17
|
+
* {@link MfaEnrollmentForm}, {@link MfaChallengeForm}, {@link UserMenu} —
|
|
18
|
+
* render accessible markup with class hooks.
|
|
19
|
+
* - **Headless** — {@link useAuthForm} owns values, errors, and the submit
|
|
20
|
+
* lifecycle with no markup. Every default component is built on it; "eject"
|
|
21
|
+
* by using the hook directly, or by passing a function as a component's
|
|
22
|
+
* `children` (render prop) to keep the component's wiring but own the markup.
|
|
23
|
+
*
|
|
24
|
+
* Social buttons appear automatically on the sign-in / sign-up components when
|
|
25
|
+
* you pass a `socialProviders` array.
|
|
26
|
+
*
|
|
27
|
+
* ## Server-rendered pages
|
|
28
|
+
*
|
|
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
|
|
33
|
+
* native post is suppressed. `formRef` hands you the same `<form>` element, for
|
|
34
|
+
* example to trigger a native `submit()` yourself.
|
|
35
|
+
*
|
|
36
|
+
* ## Theming
|
|
37
|
+
*
|
|
38
|
+
* Every form takes a per-slot {@link AuthFormClassNames} map and publishes it
|
|
39
|
+
* to the elements it renders. Wrap a subtree in
|
|
40
|
+
* {@link AuthFormClassNamesProvider} to theme every form under it at once — a
|
|
41
|
+
* form's own `classNames` overrides the inherited map slot by slot — and read
|
|
42
|
+
* the map in effect from your own markup with {@link useAuthFormClassNames}.
|
|
43
|
+
*
|
|
44
|
+
* The claims type {@link UserMenu} hands to `renderUser` / `children` is
|
|
45
|
+
* `UserInfoClaims`; import it from `@udibo/oauth2/client`, which owns it.
|
|
46
|
+
*
|
|
47
|
+
* ## Accessibility
|
|
48
|
+
*
|
|
49
|
+
* These components target **WCAG 2.2 level AA** and are accessible by default,
|
|
50
|
+
* so dropping them in does not quietly ship a barrier:
|
|
51
|
+
*
|
|
52
|
+
* - Every field is programmatically labelled (`<label htmlFor>` ↔ input `id`);
|
|
53
|
+
* errors set `aria-invalid` + `aria-describedby` and render `role="alert"`.
|
|
54
|
+
* - A failed submit moves focus to the first errored field (or the form-level
|
|
55
|
+
* error summary), so a keyboard / screen-reader user lands on what to fix.
|
|
56
|
+
* - One-time-code fields use `autocomplete="one-time-code"` and **never block
|
|
57
|
+
* paste** (WCAG 2.2 §3.3.8 Accessible Authentication).
|
|
58
|
+
* - 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`.
|
|
61
|
+
*
|
|
62
|
+
* @example Plain React Router v7
|
|
63
|
+
* ```tsx
|
|
64
|
+
* import { SignInForm } from "@udibo/oauth2/react/components";
|
|
65
|
+
*
|
|
66
|
+
* function SignIn() {
|
|
67
|
+
* return (
|
|
68
|
+
* <SignInForm
|
|
69
|
+
* onSubmit={async (values) => {
|
|
70
|
+
* const res = await fetch("/auth/sign-in", {
|
|
71
|
+
* method: "POST",
|
|
72
|
+
* headers: { "content-type": "application/json" },
|
|
73
|
+
* body: JSON.stringify(values),
|
|
74
|
+
* });
|
|
75
|
+
* if (!res.ok) return await res.json();
|
|
76
|
+
* }}
|
|
77
|
+
* />
|
|
78
|
+
* );
|
|
79
|
+
* }
|
|
80
|
+
* ```
|
|
81
|
+
*
|
|
82
|
+
* @module
|
|
83
|
+
*/
|
|
84
|
+
import "../../_dnt.polyfills.js";
|
|
85
|
+
export { useAuthForm } from "./use-auth-form.js";
|
|
86
|
+
export type { AuthFieldProps, AuthFormState, AuthFormStatus, UseAuthFormOptions, } from "./use-auth-form.js";
|
|
87
|
+
export type { AuthFormClassNames, AuthFormErrors, AuthFormResult, AuthFormValues, AuthSubmitHandler, BaseAuthFormProps, BaseClassNames, SocialProvider, SocialProvidersProps, } from "./types.js";
|
|
88
|
+
export { AuthFormClassNamesProvider, useAuthFormClassNames, } from "./class-names.js";
|
|
89
|
+
export type { AuthFormClassNamesProviderProps } from "./class-names.js";
|
|
90
|
+
export { SignInForm } from "./sign-in-form.js";
|
|
91
|
+
export type { SignInFormProps, SignInLabels, SignInValues, } from "./sign-in-form.js";
|
|
92
|
+
export { SignUpForm } from "./sign-up-form.js";
|
|
93
|
+
export type { SignUpFormProps, SignUpLabels, SignUpValues, } from "./sign-up-form.js";
|
|
94
|
+
export { RequestPasswordResetForm } from "./request-password-reset-form.js";
|
|
95
|
+
export type { RequestPasswordResetFormProps, RequestPasswordResetLabels, RequestPasswordResetValues, } from "./request-password-reset-form.js";
|
|
96
|
+
export { ResetPasswordForm } from "./reset-password-form.js";
|
|
97
|
+
export type { ResetPasswordFormProps, ResetPasswordLabels, ResetPasswordValues, } from "./reset-password-form.js";
|
|
98
|
+
export { MfaEnrollmentForm } from "./mfa-enrollment-form.js";
|
|
99
|
+
export type { MfaEnrollmentData, MfaEnrollmentFormProps, MfaEnrollmentLabels, MfaEnrollmentValues, } from "./mfa-enrollment-form.js";
|
|
100
|
+
export { MfaChallengeForm } from "./mfa-challenge-form.js";
|
|
101
|
+
export type { MfaChallengeFormProps, MfaChallengeLabels, MfaChallengeMethod, MfaChallengeValues, } from "./mfa-challenge-form.js";
|
|
102
|
+
export { UserMenu } from "./user-menu.js";
|
|
103
|
+
export type { UserMenuClassNames, UserMenuLabels, UserMenuProps, } from "./user-menu.js";
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prebuilt React auth components for `@udibo/oauth2` — the composable,
|
|
3
|
+
* headless-first, themeable form set an app assembles into its own auth
|
|
4
|
+
* surface. Works in both plain React Router v7 and Juniper (SSR React).
|
|
5
|
+
*
|
|
6
|
+
* These components are **composed by your app**: you own the routes, storage,
|
|
7
|
+
* session, and the submit wiring. Nothing here mounts routes, talks to a
|
|
8
|
+
* server on its own, or assumes a design system — every component ships
|
|
9
|
+
* unstyled with per-slot `className` hooks and stable `data-oauth2-*`
|
|
10
|
+
* attributes, and takes its action via an injected `onSubmit` prop so the same
|
|
11
|
+
* component drives a client-side `fetch` or a router action.
|
|
12
|
+
*
|
|
13
|
+
* ## Two integration modes
|
|
14
|
+
*
|
|
15
|
+
* - **Default components** — {@link SignInForm}, {@link SignUpForm},
|
|
16
|
+
* {@link RequestPasswordResetForm}, {@link ResetPasswordForm},
|
|
17
|
+
* {@link MfaEnrollmentForm}, {@link MfaChallengeForm}, {@link UserMenu} —
|
|
18
|
+
* render accessible markup with class hooks.
|
|
19
|
+
* - **Headless** — {@link useAuthForm} owns values, errors, and the submit
|
|
20
|
+
* lifecycle with no markup. Every default component is built on it; "eject"
|
|
21
|
+
* by using the hook directly, or by passing a function as a component's
|
|
22
|
+
* `children` (render prop) to keep the component's wiring but own the markup.
|
|
23
|
+
*
|
|
24
|
+
* Social buttons appear automatically on the sign-in / sign-up components when
|
|
25
|
+
* you pass a `socialProviders` array.
|
|
26
|
+
*
|
|
27
|
+
* ## Server-rendered pages
|
|
28
|
+
*
|
|
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
|
|
33
|
+
* native post is suppressed. `formRef` hands you the same `<form>` element, for
|
|
34
|
+
* example to trigger a native `submit()` yourself.
|
|
35
|
+
*
|
|
36
|
+
* ## Theming
|
|
37
|
+
*
|
|
38
|
+
* Every form takes a per-slot {@link AuthFormClassNames} map and publishes it
|
|
39
|
+
* to the elements it renders. Wrap a subtree in
|
|
40
|
+
* {@link AuthFormClassNamesProvider} to theme every form under it at once — a
|
|
41
|
+
* form's own `classNames` overrides the inherited map slot by slot — and read
|
|
42
|
+
* the map in effect from your own markup with {@link useAuthFormClassNames}.
|
|
43
|
+
*
|
|
44
|
+
* The claims type {@link UserMenu} hands to `renderUser` / `children` is
|
|
45
|
+
* `UserInfoClaims`; import it from `@udibo/oauth2/client`, which owns it.
|
|
46
|
+
*
|
|
47
|
+
* ## Accessibility
|
|
48
|
+
*
|
|
49
|
+
* These components target **WCAG 2.2 level AA** and are accessible by default,
|
|
50
|
+
* so dropping them in does not quietly ship a barrier:
|
|
51
|
+
*
|
|
52
|
+
* - Every field is programmatically labelled (`<label htmlFor>` ↔ input `id`);
|
|
53
|
+
* errors set `aria-invalid` + `aria-describedby` and render `role="alert"`.
|
|
54
|
+
* - A failed submit moves focus to the first errored field (or the form-level
|
|
55
|
+
* error summary), so a keyboard / screen-reader user lands on what to fix.
|
|
56
|
+
* - One-time-code fields use `autocomplete="one-time-code"` and **never block
|
|
57
|
+
* paste** (WCAG 2.2 §3.3.8 Accessible Authentication).
|
|
58
|
+
* - 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`.
|
|
61
|
+
*
|
|
62
|
+
* @example Plain React Router v7
|
|
63
|
+
* ```tsx
|
|
64
|
+
* import { SignInForm } from "@udibo/oauth2/react/components";
|
|
65
|
+
*
|
|
66
|
+
* function SignIn() {
|
|
67
|
+
* return (
|
|
68
|
+
* <SignInForm
|
|
69
|
+
* onSubmit={async (values) => {
|
|
70
|
+
* const res = await fetch("/auth/sign-in", {
|
|
71
|
+
* method: "POST",
|
|
72
|
+
* headers: { "content-type": "application/json" },
|
|
73
|
+
* body: JSON.stringify(values),
|
|
74
|
+
* });
|
|
75
|
+
* if (!res.ok) return await res.json();
|
|
76
|
+
* }}
|
|
77
|
+
* />
|
|
78
|
+
* );
|
|
79
|
+
* }
|
|
80
|
+
* ```
|
|
81
|
+
*
|
|
82
|
+
* @module
|
|
83
|
+
*/
|
|
84
|
+
import "../../_dnt.polyfills.js";
|
|
85
|
+
export { useAuthForm } from "./use-auth-form.js";
|
|
86
|
+
export { AuthFormClassNamesProvider, useAuthFormClassNames, } from "./class-names.js";
|
|
87
|
+
export { SignInForm } from "./sign-in-form.js";
|
|
88
|
+
export { SignUpForm } from "./sign-up-form.js";
|
|
89
|
+
export { RequestPasswordResetForm } from "./request-password-reset-form.js";
|
|
90
|
+
export { ResetPasswordForm } from "./reset-password-form.js";
|
|
91
|
+
export { MfaEnrollmentForm } from "./mfa-enrollment-form.js";
|
|
92
|
+
export { MfaChallengeForm } from "./mfa-challenge-form.js";
|
|
93
|
+
export { UserMenu } from "./user-menu.js";
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* {@link RequestPasswordResetForm} — a default, accessible, unstyled
|
|
3
|
+
* "forgot password" form built on {@link useAuthForm}. Collects an email and,
|
|
4
|
+
* on success, swaps the form for a confirmation message (a neutral one that
|
|
5
|
+
* doesn't reveal whether the address exists).
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
import type { ReactNode } from "react";
|
|
10
|
+
import type { AuthFormState } from "./use-auth-form.js";
|
|
11
|
+
import type { BaseAuthFormProps } from "./types.js";
|
|
12
|
+
/** Values collected by {@link RequestPasswordResetForm}. */
|
|
13
|
+
export type RequestPasswordResetValues = {
|
|
14
|
+
/** The email to send the reset link to. */
|
|
15
|
+
email: string;
|
|
16
|
+
};
|
|
17
|
+
/** Visible text overrides for {@link RequestPasswordResetForm}. */
|
|
18
|
+
export interface RequestPasswordResetLabels {
|
|
19
|
+
/** Label for the email field. Default: `"Email"`. */
|
|
20
|
+
email?: string;
|
|
21
|
+
/** Submit button text. Default: `"Send reset link"`. */
|
|
22
|
+
submit?: string;
|
|
23
|
+
/** Submit button text while submitting. Default: `"Sending…"`. */
|
|
24
|
+
submitting?: string;
|
|
25
|
+
/** Confirmation shown after success. Default: a neutral "check your email". */
|
|
26
|
+
success?: string;
|
|
27
|
+
}
|
|
28
|
+
/** Props for {@link RequestPasswordResetForm}. */
|
|
29
|
+
export interface RequestPasswordResetFormProps extends BaseAuthFormProps<RequestPasswordResetValues> {
|
|
30
|
+
/** Text overrides. */
|
|
31
|
+
labels?: RequestPasswordResetLabels;
|
|
32
|
+
/**
|
|
33
|
+
* Render-prop escape hatch. When provided, you own the markup and receive
|
|
34
|
+
* the live {@link AuthFormState} (including `succeeded`).
|
|
35
|
+
*/
|
|
36
|
+
children?: (state: AuthFormState<RequestPasswordResetValues>) => ReactNode;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* A ready-to-use "forgot password" request form. Wire `onSubmit` to your
|
|
40
|
+
* reset-request endpoint. Return no error to trigger the neutral success state.
|
|
41
|
+
*
|
|
42
|
+
* **Enumeration safety is the endpoint's job:** the success copy is neutral
|
|
43
|
+
* ("if an account exists…"), but this form renders whatever error `onSubmit`
|
|
44
|
+
* returns/throws. Point it at an enumeration-safe endpoint (like
|
|
45
|
+
* `IdentityService.requestPasswordReset`, which always resolves) — do **not**
|
|
46
|
+
* surface a "no such account" error, or the neutral copy is undone.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```tsx
|
|
50
|
+
* <RequestPasswordResetForm
|
|
51
|
+
* onSubmit={async ({ email }) => {
|
|
52
|
+
* await fetch("/auth/forgot-password", {
|
|
53
|
+
* method: "POST",
|
|
54
|
+
* headers: { "content-type": "application/json" },
|
|
55
|
+
* body: JSON.stringify({ email }),
|
|
56
|
+
* });
|
|
57
|
+
* }}
|
|
58
|
+
* />
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
export declare function RequestPasswordResetForm(props: RequestPasswordResetFormProps): ReactNode;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useAuthForm } from "./use-auth-form.js";
|
|
3
|
+
import { AuthFormShell, SubmitButton, TextField, useFocusFirstError, } from "./internal.js";
|
|
4
|
+
import { useAuthFormClassNames } from "./class-names.js";
|
|
5
|
+
const DEFAULT_SUCCESS = "If an account exists for that email, a reset link is on its way.";
|
|
6
|
+
/**
|
|
7
|
+
* A ready-to-use "forgot password" request form. Wire `onSubmit` to your
|
|
8
|
+
* reset-request endpoint. Return no error to trigger the neutral success state.
|
|
9
|
+
*
|
|
10
|
+
* **Enumeration safety is the endpoint's job:** the success copy is neutral
|
|
11
|
+
* ("if an account exists…"), but this form renders whatever error `onSubmit`
|
|
12
|
+
* returns/throws. Point it at an enumeration-safe endpoint (like
|
|
13
|
+
* `IdentityService.requestPasswordReset`, which always resolves) — do **not**
|
|
14
|
+
* surface a "no such account" error, or the neutral copy is undone.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```tsx
|
|
18
|
+
* <RequestPasswordResetForm
|
|
19
|
+
* onSubmit={async ({ email }) => {
|
|
20
|
+
* await fetch("/auth/forgot-password", {
|
|
21
|
+
* method: "POST",
|
|
22
|
+
* headers: { "content-type": "application/json" },
|
|
23
|
+
* body: JSON.stringify({ email }),
|
|
24
|
+
* });
|
|
25
|
+
* }}
|
|
26
|
+
* />
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export function RequestPasswordResetForm(props) {
|
|
30
|
+
const { onSubmit, className, labels, children } = props;
|
|
31
|
+
const form = useAuthForm({
|
|
32
|
+
initialValues: { email: "" },
|
|
33
|
+
onSubmit,
|
|
34
|
+
onSuccess: props.onSuccess,
|
|
35
|
+
});
|
|
36
|
+
useFocusFirstError(form);
|
|
37
|
+
const classNames = useAuthFormClassNames(props.classNames);
|
|
38
|
+
return (_jsxs(AuthFormShell, { name: "request-password-reset", form: form, className: className, classNames: classNames, render: children, action: props.action, method: props.method, formRef: props.formRef, succeeded: form.succeeded
|
|
39
|
+
? (_jsx("div", { "data-oauth2-success": "", role: "status", className: classNames.success, children: labels?.success ?? DEFAULT_SUCCESS }))
|
|
40
|
+
: null, children: [_jsx(TextField, { fieldProps: form.getFieldProps("email"), label: labels?.email ?? "Email", type: "email", autoComplete: "email", required: true, error: form.errors.email }), _jsx(SubmitButton, { isSubmitting: form.isSubmitting, label: labels?.submit ?? "Send reset link", submittingLabel: labels?.submitting ?? "Sending…" })] }));
|
|
41
|
+
}
|