@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,125 @@
|
|
|
1
|
+
import { InvalidScopeError } from "../errors.js";
|
|
2
|
+
/** NQCHAR as defined in RFC 6749. */
|
|
3
|
+
export const NQCHAR = /[\x21\x23-\x5b\x5d-\x7e]/;
|
|
4
|
+
/** Regex for validating a scope string. */
|
|
5
|
+
export const SCOPE = new RegExp(`^(?:(?:${NQCHAR.source}+)(?: ${NQCHAR.source}+)*)?$`);
|
|
6
|
+
/** Regex for extracting scope tokens. */
|
|
7
|
+
export const SCOPE_TOKEN = new RegExp(`${NQCHAR.source}+`, "g");
|
|
8
|
+
/**
|
|
9
|
+
* Base class for OAuth2 scope implementations.
|
|
10
|
+
*
|
|
11
|
+
* Provides the contract that the authorization server, grants, and adapters
|
|
12
|
+
* rely on. Users who want a custom scope type (e.g. one that enforces a
|
|
13
|
+
* fixed vocabulary, or one that encodes hierarchical scopes) can extend
|
|
14
|
+
* this class; the framework only requires a constructor that accepts an
|
|
15
|
+
* optional scope string and the instance methods declared here.
|
|
16
|
+
*
|
|
17
|
+
* The default implementation is {@link BasicScope}; it stores a set of
|
|
18
|
+
* space-delimited NQCHAR tokens per RFC 6749 Section 3.3.
|
|
19
|
+
*/
|
|
20
|
+
export class AbstractScope {
|
|
21
|
+
}
|
|
22
|
+
/** A basic implementation of OAuth2 scope. */
|
|
23
|
+
export class BasicScope extends AbstractScope {
|
|
24
|
+
stringCache;
|
|
25
|
+
tokens;
|
|
26
|
+
/**
|
|
27
|
+
* Parses a space-delimited scope string into its tokens.
|
|
28
|
+
*
|
|
29
|
+
* @throws {InvalidScopeError} If `scope` contains characters outside the
|
|
30
|
+
* RFC 6749 NQCHAR set.
|
|
31
|
+
*/
|
|
32
|
+
constructor(scope) {
|
|
33
|
+
super();
|
|
34
|
+
if (scope && !SCOPE.test(scope)) {
|
|
35
|
+
throw new InvalidScopeError("invalid scope");
|
|
36
|
+
}
|
|
37
|
+
this.tokens = scope ? new Set(scope.match(SCOPE_TOKEN)) : new Set();
|
|
38
|
+
}
|
|
39
|
+
/** Creates a new scope from a scope. */
|
|
40
|
+
static from(scope) {
|
|
41
|
+
if (typeof scope === "string")
|
|
42
|
+
return new BasicScope(scope);
|
|
43
|
+
const result = new BasicScope();
|
|
44
|
+
result.add(scope);
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
/** Creates a new scope with all scope tokens from both scopes. */
|
|
48
|
+
static union(a, b) {
|
|
49
|
+
return BasicScope.from(a).add(b);
|
|
50
|
+
}
|
|
51
|
+
/** Creates a new scope with all scope tokens that are present in both scopes. */
|
|
52
|
+
static intersection(a, b) {
|
|
53
|
+
const result = new BasicScope();
|
|
54
|
+
const scopeA = typeof a === "string" ? new BasicScope(a) : a;
|
|
55
|
+
const scopeB = typeof b === "string" ? new BasicScope(b) : b;
|
|
56
|
+
for (const token of scopeA) {
|
|
57
|
+
if (scopeB.tokens.has(token))
|
|
58
|
+
result.tokens.add(token);
|
|
59
|
+
}
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
/** Deletes all scope tokens from this scope. */
|
|
63
|
+
clear() {
|
|
64
|
+
this.tokens = new Set();
|
|
65
|
+
delete this.stringCache;
|
|
66
|
+
return this;
|
|
67
|
+
}
|
|
68
|
+
/** Adds all scope tokens in the passed in scope to this scope. */
|
|
69
|
+
add(scope) {
|
|
70
|
+
const other = typeof scope === "string" ? new BasicScope(scope) : scope;
|
|
71
|
+
for (const token of other) {
|
|
72
|
+
this.tokens.add(token);
|
|
73
|
+
}
|
|
74
|
+
delete this.stringCache;
|
|
75
|
+
return this;
|
|
76
|
+
}
|
|
77
|
+
/** Removes all scope tokens in the passed in scope from this scope. */
|
|
78
|
+
remove(scope) {
|
|
79
|
+
const other = typeof scope === "string" ? new BasicScope(scope) : scope;
|
|
80
|
+
for (const token of other) {
|
|
81
|
+
this.tokens.delete(token);
|
|
82
|
+
}
|
|
83
|
+
delete this.stringCache;
|
|
84
|
+
return this;
|
|
85
|
+
}
|
|
86
|
+
/** Checks that this scope has all scope tokens in the passed in scope. */
|
|
87
|
+
has(scope) {
|
|
88
|
+
const other = typeof scope === "string" ? new BasicScope(scope) : scope;
|
|
89
|
+
for (const token of other) {
|
|
90
|
+
if (!this.tokens.has(token))
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
/** Checks that this scope is equal to the passed in scope. */
|
|
96
|
+
equals(scope) {
|
|
97
|
+
const other = typeof scope === "string" ? new BasicScope(scope) : scope;
|
|
98
|
+
if (this.tokens.size !== other.size)
|
|
99
|
+
return false;
|
|
100
|
+
for (const token of other) {
|
|
101
|
+
if (!this.tokens.has(token))
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
/** Returns the number of scope tokens. */
|
|
107
|
+
get size() {
|
|
108
|
+
return this.tokens.size;
|
|
109
|
+
}
|
|
110
|
+
/** Converts the scope to a string representation. */
|
|
111
|
+
toString() {
|
|
112
|
+
if (typeof this.stringCache !== "string") {
|
|
113
|
+
this.stringCache = [...this].join(" ");
|
|
114
|
+
}
|
|
115
|
+
return this.stringCache;
|
|
116
|
+
}
|
|
117
|
+
/** Converts the scope to a JSON representation. */
|
|
118
|
+
toJSON() {
|
|
119
|
+
return this.toString();
|
|
120
|
+
}
|
|
121
|
+
/** Iterates the scope's tokens in insertion order. */
|
|
122
|
+
*[Symbol.iterator]() {
|
|
123
|
+
yield* this.tokens.values();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import type { AbstractScope, BasicScope } from "./scope.js";
|
|
2
|
+
import type { ClientInterface } from "./client.js";
|
|
3
|
+
import type { AuthenticationContext } from "./authentication.js";
|
|
4
|
+
/**
|
|
5
|
+
* Token model representing an access token.
|
|
6
|
+
*
|
|
7
|
+
* Returned by the token services and the resource server, and part of the
|
|
8
|
+
* public `TokenServiceInterface` / `TokenReaderInterface` surface that
|
|
9
|
+
* consumers implement. The generic parameters allow customizing the client,
|
|
10
|
+
* user, and scope types.
|
|
11
|
+
*/
|
|
12
|
+
export interface Token<Client extends ClientInterface, User, Scope extends AbstractScope = BasicScope> {
|
|
13
|
+
/** The access token string. */
|
|
14
|
+
accessToken: string;
|
|
15
|
+
/** When the access token expires. */
|
|
16
|
+
accessTokenExpiresAt?: Date;
|
|
17
|
+
/** The client this token was issued to. */
|
|
18
|
+
client: Client;
|
|
19
|
+
/** The user this token was issued for (if applicable). */
|
|
20
|
+
user?: User;
|
|
21
|
+
/** The scope granted to this token. */
|
|
22
|
+
scope?: Scope | null;
|
|
23
|
+
/**
|
|
24
|
+
* The verified claims this token carried, when the reader that produced it
|
|
25
|
+
* had them — a validated JWT's payload, or an introspection response. The
|
|
26
|
+
* resource server builds its `Authorization` from these; a DB-backed token
|
|
27
|
+
* service may leave them unset, which yields an authorization with scope
|
|
28
|
+
* only.
|
|
29
|
+
*/
|
|
30
|
+
claims?: Record<string, unknown>;
|
|
31
|
+
/** The authorization code used to generate this token, for replay detection per RFC 6819. */
|
|
32
|
+
code?: string;
|
|
33
|
+
/**
|
|
34
|
+
* OIDC nonce carried transiently from the authorization code so the token
|
|
35
|
+
* endpoint can echo it in the id_token. Not persisted by token stores.
|
|
36
|
+
*/
|
|
37
|
+
nonce?: string;
|
|
38
|
+
/**
|
|
39
|
+
* Immutable event inherited from the code or refresh ancestor. Token stores
|
|
40
|
+
* must persist and restore it; never reconstruct it from the current session.
|
|
41
|
+
* Machine credentials omit it.
|
|
42
|
+
*/
|
|
43
|
+
authenticationContext?: AuthenticationContext;
|
|
44
|
+
}
|
|
45
|
+
/** Token with refresh token capability. */
|
|
46
|
+
export interface RefreshToken<Client extends ClientInterface, User, Scope extends AbstractScope = BasicScope> extends Token<Client, User, Scope> {
|
|
47
|
+
/** The refresh token string. */
|
|
48
|
+
refreshToken: string;
|
|
49
|
+
/** When the refresh token expires. */
|
|
50
|
+
refreshTokenExpiresAt?: Date;
|
|
51
|
+
/**
|
|
52
|
+
* The rotation family this refresh token belongs to: assigned at first
|
|
53
|
+
* issuance and inherited by every rotation, so replay of a rotated-out
|
|
54
|
+
* member can revoke the whole family (reuse detection).
|
|
55
|
+
*/
|
|
56
|
+
familyId?: string;
|
|
57
|
+
/**
|
|
58
|
+
* When the rotation family was first issued: set alongside
|
|
59
|
+
* {@linkcode RefreshToken.familyId} at first issuance and carried unchanged
|
|
60
|
+
* through every rotation. It anchors the absolute cap
|
|
61
|
+
* ({@linkcode TokenServiceInterface.refreshTokenFamilyExpiresAt}), so a token
|
|
62
|
+
* service that persists refresh tokens **must** persist and restore it —
|
|
63
|
+
* dropping it on the round trip disables the cap.
|
|
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.
|
|
68
|
+
*/
|
|
69
|
+
familyCreatedAt?: Date;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Standard OAuth2 token response.
|
|
73
|
+
*
|
|
74
|
+
* Returned by the token endpoint as defined in RFC 6749 Section 5.1.
|
|
75
|
+
*
|
|
76
|
+
* @see https://datatracker.ietf.org/doc/html/rfc6749#section-5.1
|
|
77
|
+
*/
|
|
78
|
+
export interface TokenResponse {
|
|
79
|
+
/** Always "Bearer" for OAuth2 Bearer tokens. */
|
|
80
|
+
token_type: "Bearer";
|
|
81
|
+
/** The access token issued by the authorization server. */
|
|
82
|
+
access_token: string;
|
|
83
|
+
/** Token lifetime in seconds. */
|
|
84
|
+
expires_in?: number;
|
|
85
|
+
/** Refresh token for obtaining new access tokens. */
|
|
86
|
+
refresh_token?: string;
|
|
87
|
+
/** Space-delimited list of granted scopes. */
|
|
88
|
+
scope?: string;
|
|
89
|
+
/** OIDC ID Token, present when the `openid` scope was granted to a user. */
|
|
90
|
+
id_token?: string;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Token introspection response as defined in RFC 7662.
|
|
94
|
+
*
|
|
95
|
+
* Used by resource servers to query the authorization server
|
|
96
|
+
* about the state of an access token. Authorization servers MAY
|
|
97
|
+
* include additional extension fields; the index signature exposes
|
|
98
|
+
* them to mapping functions like
|
|
99
|
+
* {@linkcode IntrospectionTokenReaderOptions.getClient}.
|
|
100
|
+
*
|
|
101
|
+
* @see https://datatracker.ietf.org/doc/html/rfc7662#section-2.2
|
|
102
|
+
*/
|
|
103
|
+
export interface IntrospectionResponse {
|
|
104
|
+
/** Whether the token is currently active. */
|
|
105
|
+
active: boolean;
|
|
106
|
+
/** Space-delimited list of scopes associated with the token. */
|
|
107
|
+
scope?: string;
|
|
108
|
+
/** Client identifier for the OAuth2 client that requested the token. */
|
|
109
|
+
client_id?: string;
|
|
110
|
+
/** Human-readable identifier for the resource owner. */
|
|
111
|
+
username?: string;
|
|
112
|
+
/** Type of the token (e.g., "Bearer"). */
|
|
113
|
+
token_type?: string;
|
|
114
|
+
/** Token expiration time (Unix timestamp). */
|
|
115
|
+
exp?: number;
|
|
116
|
+
/** Token issue time (Unix timestamp). */
|
|
117
|
+
iat?: number;
|
|
118
|
+
/** Token not-before time (Unix timestamp). */
|
|
119
|
+
nbf?: number;
|
|
120
|
+
/**
|
|
121
|
+
* Subject identifier — the resource owner. Absent for a machine token
|
|
122
|
+
* issued without a user (client credentials): its `client_id` identifies
|
|
123
|
+
* the caller, and the missing `sub` marks that no resource owner is behind
|
|
124
|
+
* the token.
|
|
125
|
+
*/
|
|
126
|
+
sub?: string;
|
|
127
|
+
/** Audience(s) the token is intended for. */
|
|
128
|
+
aud?: string | string[];
|
|
129
|
+
/** Issuer of the token. */
|
|
130
|
+
iss?: string;
|
|
131
|
+
/** Unique token identifier. */
|
|
132
|
+
jti?: string;
|
|
133
|
+
/** Extension fields. RFC 7662 allows servers to add their own claims. */
|
|
134
|
+
[extension: string]: unknown;
|
|
135
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/esm/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<OAuth2Callback>` — mount on the SPA's callback route when the app runs a
|
|
3
|
+
* `DirectClient`. Calls `client.handleAuthorizationCallback` with the current
|
|
4
|
+
* URL, then navigates to the captured `returnTo`.
|
|
5
|
+
*
|
|
6
|
+
* Not for a `BffClient` app — the BFF owns `/auth/callback` there, and this
|
|
7
|
+
* component renders its `fallback` with a clear error rather than pretend
|
|
8
|
+
* otherwise.
|
|
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.
|
|
13
|
+
*
|
|
14
|
+
* @module
|
|
15
|
+
*/
|
|
16
|
+
import { type ReactNode } from "react";
|
|
17
|
+
import { type OAuth2Error } from "../client/mod.js";
|
|
18
|
+
/** Props for {@link OAuth2Callback}. */
|
|
19
|
+
export interface OAuth2CallbackProps {
|
|
20
|
+
/**
|
|
21
|
+
* Optional navigation hook. Receives the captured `returnTo` (or
|
|
22
|
+
* `"/"` if the original request didn't carry one). Use this to
|
|
23
|
+
* delegate to your router (`react-router`, `tanstack-router`,
|
|
24
|
+
* `wouter`, …). Defaults to
|
|
25
|
+
* `history.replaceState(null, "", returnTo)` so the auth params drop
|
|
26
|
+
* out of the URL bar without a full page navigation.
|
|
27
|
+
*/
|
|
28
|
+
onNavigate?: (returnTo: string) => void;
|
|
29
|
+
/**
|
|
30
|
+
* Renders during the in-flight code exchange. Defaults to `null`.
|
|
31
|
+
*/
|
|
32
|
+
pending?: ReactNode;
|
|
33
|
+
/**
|
|
34
|
+
* Renders if the callback fails. Defaults to a plain `<pre>` with the
|
|
35
|
+
* message.
|
|
36
|
+
*
|
|
37
|
+
* The error is always a real {@link OAuth2Error}, so `extensions` is safe to
|
|
38
|
+
* read: a failure the authorization server did not describe — a bookmarked
|
|
39
|
+
* callback route with no `code`, an undecodable `id_token` — arrives wrapped
|
|
40
|
+
* as `server_error` with the original error as its `cause`.
|
|
41
|
+
*/
|
|
42
|
+
fallback?: (error: OAuth2Error) => ReactNode;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Mount on the callback route. Exchanges the code, then navigates to
|
|
46
|
+
* `returnTo`.
|
|
47
|
+
*
|
|
48
|
+
* @param props Optional navigation hook plus pending / error renderers.
|
|
49
|
+
* @returns The pending node while the exchange is in flight, the fallback on
|
|
50
|
+
* failure, and nothing once it has navigated.
|
|
51
|
+
*/
|
|
52
|
+
export declare function OAuth2Callback(props: OAuth2CallbackProps): ReactNode;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
// deno-lint-ignore-file no-window -- browser-only; `window` used only after isBrowser() guards
|
|
3
|
+
/**
|
|
4
|
+
* `<OAuth2Callback>` — mount on the SPA's callback route when the app runs a
|
|
5
|
+
* `DirectClient`. Calls `client.handleAuthorizationCallback` with the current
|
|
6
|
+
* URL, then navigates to the captured `returnTo`.
|
|
7
|
+
*
|
|
8
|
+
* Not for a `BffClient` app — the BFF owns `/auth/callback` there, and this
|
|
9
|
+
* component renders its `fallback` with a clear error rather than pretend
|
|
10
|
+
* otherwise.
|
|
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.
|
|
15
|
+
*
|
|
16
|
+
* @module
|
|
17
|
+
*/
|
|
18
|
+
import { useEffect, useRef, useState } from "react";
|
|
19
|
+
import { DirectClient } from "../client/mod.js";
|
|
20
|
+
import { ServerError, toOAuth2Error } from "../errors.js";
|
|
21
|
+
import { useOAuth2 } from "./use-oauth2.js";
|
|
22
|
+
const isBrowser = () => typeof window !== "undefined";
|
|
23
|
+
const defaultNavigate = (returnTo) => {
|
|
24
|
+
if (!isBrowser())
|
|
25
|
+
return;
|
|
26
|
+
window.history.replaceState(null, "", returnTo);
|
|
27
|
+
};
|
|
28
|
+
const defaultFallback = (error) => (_jsx("pre", { style: { color: "red" }, children: error.message }));
|
|
29
|
+
/**
|
|
30
|
+
* Mount on the callback route. Exchanges the code, then navigates to
|
|
31
|
+
* `returnTo`.
|
|
32
|
+
*
|
|
33
|
+
* @param props Optional navigation hook plus pending / error renderers.
|
|
34
|
+
* @returns The pending node while the exchange is in flight, the fallback on
|
|
35
|
+
* failure, and nothing once it has navigated.
|
|
36
|
+
*/
|
|
37
|
+
export function OAuth2Callback(props) {
|
|
38
|
+
const { onNavigate = defaultNavigate, pending = null, fallback = defaultFallback, } = props;
|
|
39
|
+
const { client } = useOAuth2();
|
|
40
|
+
const [error, setError] = useState(null);
|
|
41
|
+
// Ref so the exchange effect depends only on `client`; a non-memoized
|
|
42
|
+
// `onNavigate` must not re-run it after `code` is stripped from the URL.
|
|
43
|
+
const onNavigateRef = useRef(onNavigate);
|
|
44
|
+
onNavigateRef.current = onNavigate;
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (!isBrowser())
|
|
47
|
+
return;
|
|
48
|
+
if (!(client instanceof DirectClient)) {
|
|
49
|
+
setError(new ServerError("<OAuth2Callback> needs the provider's client to be a " +
|
|
50
|
+
`DirectClient, but it is a ${client.constructor.name}. Behind a ` +
|
|
51
|
+
"BffClient the BFF owns the callback route."));
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
let cancelled = false;
|
|
55
|
+
client.handleAuthorizationCallback(window.location.href).then((result) => {
|
|
56
|
+
if (cancelled)
|
|
57
|
+
return;
|
|
58
|
+
onNavigateRef.current(result.returnTo ?? "/");
|
|
59
|
+
}, (err) => {
|
|
60
|
+
if (cancelled)
|
|
61
|
+
return;
|
|
62
|
+
setError(toOAuth2Error(err));
|
|
63
|
+
});
|
|
64
|
+
return () => {
|
|
65
|
+
cancelled = true;
|
|
66
|
+
};
|
|
67
|
+
}, [client]);
|
|
68
|
+
if (error)
|
|
69
|
+
return fallback(error);
|
|
70
|
+
return pending;
|
|
71
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The styling seam shared by the prebuilt auth forms
|
|
3
|
+
* (`@udibo/oauth2/react/components`): a React context carrying the per-slot
|
|
4
|
+
* class map ({@link AuthFormClassNames}), so the elements inside a form read
|
|
5
|
+
* their classes from the tree rather than from a `classNames` prop threaded
|
|
6
|
+
* through every wrapper.
|
|
7
|
+
*
|
|
8
|
+
* Wrap a subtree in {@link AuthFormClassNamesProvider} to theme every form
|
|
9
|
+
* under it at once, and read the map in effect from your own components with
|
|
10
|
+
* {@link useAuthFormClassNames}.
|
|
11
|
+
*
|
|
12
|
+
* @module
|
|
13
|
+
*/
|
|
14
|
+
import { type ReactNode } from "react";
|
|
15
|
+
import type { AuthFormClassNames } from "./types.js";
|
|
16
|
+
/**
|
|
17
|
+
* Resolves the per-slot class map for this position in the tree: the nearest
|
|
18
|
+
* {@link AuthFormClassNamesProvider}'s map with `overrides` applied slot by
|
|
19
|
+
* slot. An absent or `undefined` slot in `overrides` keeps the inherited value,
|
|
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.
|
|
23
|
+
*
|
|
24
|
+
* The result is cached on the slot values, so passing a fresh `overrides`
|
|
25
|
+
* literal every render returns the identical map.
|
|
26
|
+
*
|
|
27
|
+
* Call it with no argument from a component rendered **inside** a form to read
|
|
28
|
+
* the theme in effect; call it with a component's own `classNames` prop to
|
|
29
|
+
* resolve the map that component should both render with and publish.
|
|
30
|
+
*
|
|
31
|
+
* @param overrides Per-slot classes that win over the inherited map.
|
|
32
|
+
* @returns The resolved map, keyed by {@link AuthFormClassNames} slot.
|
|
33
|
+
*
|
|
34
|
+
* @example Theme a custom field to match the form around it
|
|
35
|
+
* ```tsx
|
|
36
|
+
* function TenantField(props: { fieldProps: AuthFieldProps }) {
|
|
37
|
+
* const classNames = useAuthFormClassNames();
|
|
38
|
+
* return (
|
|
39
|
+
* <div className={classNames.field}>
|
|
40
|
+
* <label className={classNames.label} htmlFor={props.fieldProps.id}>
|
|
41
|
+
* Tenant
|
|
42
|
+
* </label>
|
|
43
|
+
* <input {...props.fieldProps} className={classNames.input} />
|
|
44
|
+
* </div>
|
|
45
|
+
* );
|
|
46
|
+
* }
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export declare function useAuthFormClassNames(overrides?: AuthFormClassNames): Readonly<AuthFormClassNames>;
|
|
50
|
+
/**
|
|
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}.
|
|
55
|
+
*/
|
|
56
|
+
export declare function AuthFormClassNamesScope(props: {
|
|
57
|
+
classNames: Readonly<AuthFormClassNames>;
|
|
58
|
+
children?: ReactNode;
|
|
59
|
+
}): ReactNode;
|
|
60
|
+
/** Props for {@link AuthFormClassNamesProvider}. */
|
|
61
|
+
export interface AuthFormClassNamesProviderProps {
|
|
62
|
+
/** Per-slot classes to publish to `children`. See {@link AuthFormClassNames}. */
|
|
63
|
+
classNames: AuthFormClassNames;
|
|
64
|
+
/** The subtree the map applies to. */
|
|
65
|
+
children?: ReactNode;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Publishes a per-slot class map to every prebuilt auth form in `children`, so
|
|
69
|
+
* an app themes its whole auth surface once instead of repeating the same
|
|
70
|
+
* `classNames` on each form. Providers nest: an inner provider — or a form's
|
|
71
|
+
* own `classNames` prop — overrides the inherited map slot by slot, leaving the
|
|
72
|
+
* slots it does not name intact.
|
|
73
|
+
*
|
|
74
|
+
* It themes the auth **forms**; `UserMenu` is deliberately outside its reach
|
|
75
|
+
* and takes its classes from its own `className` / `classNames` props.
|
|
76
|
+
*
|
|
77
|
+
* An inline `classNames` literal is fine here — the published map is cached on
|
|
78
|
+
* the slot values rather than on the object's identity, so re-rendering with an
|
|
79
|
+
* equal literal hands the forms below the identical map.
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* ```tsx
|
|
83
|
+
* <AuthFormClassNamesProvider
|
|
84
|
+
* classNames={{ input: "input", submit: "btn btn-primary" }}
|
|
85
|
+
* >
|
|
86
|
+
* <SignInForm onSubmit={signIn} />
|
|
87
|
+
* <SignUpForm onSubmit={signUp} />
|
|
88
|
+
* </AuthFormClassNamesProvider>
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
export declare function AuthFormClassNamesProvider(props: AuthFormClassNamesProviderProps): ReactNode;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* The styling seam shared by the prebuilt auth forms
|
|
4
|
+
* (`@udibo/oauth2/react/components`): a React context carrying the per-slot
|
|
5
|
+
* class map ({@link AuthFormClassNames}), so the elements inside a form read
|
|
6
|
+
* their classes from the tree rather than from a `classNames` prop threaded
|
|
7
|
+
* through every wrapper.
|
|
8
|
+
*
|
|
9
|
+
* Wrap a subtree in {@link AuthFormClassNamesProvider} to theme every form
|
|
10
|
+
* under it at once, and read the map in effect from your own components with
|
|
11
|
+
* {@link useAuthFormClassNames}.
|
|
12
|
+
*
|
|
13
|
+
* @module
|
|
14
|
+
*/
|
|
15
|
+
import { createContext, useContext, useRef } from "react";
|
|
16
|
+
const EMPTY_CLASS_NAMES = Object.freeze({});
|
|
17
|
+
const AuthFormClassNamesContext = createContext(EMPTY_CLASS_NAMES);
|
|
18
|
+
function merge(inherited, overrides) {
|
|
19
|
+
const resolved = { ...inherited };
|
|
20
|
+
for (const [slot, value] of Object.entries(overrides)) {
|
|
21
|
+
if (value !== undefined) {
|
|
22
|
+
resolved[slot] = value;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return Object.freeze(resolved);
|
|
26
|
+
}
|
|
27
|
+
function slotKey(classNames) {
|
|
28
|
+
const slots = Object.entries(classNames)
|
|
29
|
+
.filter(([, value]) => value !== undefined)
|
|
30
|
+
.sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
31
|
+
return JSON.stringify(slots);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Resolves the per-slot class map for this position in the tree: the nearest
|
|
35
|
+
* {@link AuthFormClassNamesProvider}'s map with `overrides` applied slot by
|
|
36
|
+
* slot. An absent or `undefined` slot in `overrides` keeps the inherited value,
|
|
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.
|
|
40
|
+
*
|
|
41
|
+
* The result is cached on the slot values, so passing a fresh `overrides`
|
|
42
|
+
* literal every render returns the identical map.
|
|
43
|
+
*
|
|
44
|
+
* Call it with no argument from a component rendered **inside** a form to read
|
|
45
|
+
* the theme in effect; call it with a component's own `classNames` prop to
|
|
46
|
+
* resolve the map that component should both render with and publish.
|
|
47
|
+
*
|
|
48
|
+
* @param overrides Per-slot classes that win over the inherited map.
|
|
49
|
+
* @returns The resolved map, keyed by {@link AuthFormClassNames} slot.
|
|
50
|
+
*
|
|
51
|
+
* @example Theme a custom field to match the form around it
|
|
52
|
+
* ```tsx
|
|
53
|
+
* function TenantField(props: { fieldProps: AuthFieldProps }) {
|
|
54
|
+
* const classNames = useAuthFormClassNames();
|
|
55
|
+
* return (
|
|
56
|
+
* <div className={classNames.field}>
|
|
57
|
+
* <label className={classNames.label} htmlFor={props.fieldProps.id}>
|
|
58
|
+
* Tenant
|
|
59
|
+
* </label>
|
|
60
|
+
* <input {...props.fieldProps} className={classNames.input} />
|
|
61
|
+
* </div>
|
|
62
|
+
* );
|
|
63
|
+
* }
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
export function useAuthFormClassNames(overrides) {
|
|
67
|
+
const inherited = useContext(AuthFormClassNamesContext);
|
|
68
|
+
const key = overrides ? slotKey(overrides) : "";
|
|
69
|
+
const held = useRef(null);
|
|
70
|
+
if (held.current === null || held.current.inherited !== inherited ||
|
|
71
|
+
held.current.key !== key) {
|
|
72
|
+
held.current = {
|
|
73
|
+
inherited,
|
|
74
|
+
key,
|
|
75
|
+
resolved: overrides ? merge(inherited, overrides) : inherited,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return held.current.resolved;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
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}.
|
|
85
|
+
*/
|
|
86
|
+
export function AuthFormClassNamesScope(props) {
|
|
87
|
+
const { classNames, children } = props;
|
|
88
|
+
return (_jsx(AuthFormClassNamesContext.Provider, { value: classNames, children: children }));
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Publishes a per-slot class map to every prebuilt auth form in `children`, so
|
|
92
|
+
* an app themes its whole auth surface once instead of repeating the same
|
|
93
|
+
* `classNames` on each form. Providers nest: an inner provider — or a form's
|
|
94
|
+
* own `classNames` prop — overrides the inherited map slot by slot, leaving the
|
|
95
|
+
* slots it does not name intact.
|
|
96
|
+
*
|
|
97
|
+
* It themes the auth **forms**; `UserMenu` is deliberately outside its reach
|
|
98
|
+
* and takes its classes from its own `className` / `classNames` props.
|
|
99
|
+
*
|
|
100
|
+
* An inline `classNames` literal is fine here — the published map is cached on
|
|
101
|
+
* the slot values rather than on the object's identity, so re-rendering with an
|
|
102
|
+
* equal literal hands the forms below the identical map.
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```tsx
|
|
106
|
+
* <AuthFormClassNamesProvider
|
|
107
|
+
* classNames={{ input: "input", submit: "btn btn-primary" }}
|
|
108
|
+
* >
|
|
109
|
+
* <SignInForm onSubmit={signIn} />
|
|
110
|
+
* <SignUpForm onSubmit={signUp} />
|
|
111
|
+
* </AuthFormClassNamesProvider>
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
export function AuthFormClassNamesProvider(props) {
|
|
115
|
+
const { classNames, children } = props;
|
|
116
|
+
const resolved = useAuthFormClassNames(classNames);
|
|
117
|
+
return (_jsx(AuthFormClassNamesScope, { classNames: resolved, children: children }));
|
|
118
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { type ReactNode, type Ref } from "react";
|
|
2
|
+
import type { AuthFieldProps, AuthFormState } from "./use-auth-form.js";
|
|
3
|
+
import type { AuthFormClassNames, AuthFormValues, SocialProvidersProps } from "./types.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 declare function AuthFormShell<V extends AuthFormValues>(props: {
|
|
18
|
+
name: string;
|
|
19
|
+
form: AuthFormState<V>;
|
|
20
|
+
className: string | undefined;
|
|
21
|
+
classNames: Readonly<AuthFormClassNames>;
|
|
22
|
+
render: ((state: AuthFormState<V>) => ReactNode) | undefined;
|
|
23
|
+
rootProps?: Record<`data-${string}`, string>;
|
|
24
|
+
aboveForm?: ReactNode;
|
|
25
|
+
succeeded?: ReactNode;
|
|
26
|
+
children?: ReactNode;
|
|
27
|
+
action?: string;
|
|
28
|
+
method?: "get" | "post";
|
|
29
|
+
formRef?: Ref<HTMLFormElement>;
|
|
30
|
+
}): ReactNode;
|
|
31
|
+
/** The actions row and its submit button, disabled while the form is in flight. */
|
|
32
|
+
export declare function SubmitButton(props: {
|
|
33
|
+
isSubmitting: boolean;
|
|
34
|
+
label: string;
|
|
35
|
+
submittingLabel: string;
|
|
36
|
+
}): ReactNode;
|
|
37
|
+
export declare function TextField(props: {
|
|
38
|
+
fieldProps: AuthFieldProps;
|
|
39
|
+
label: string;
|
|
40
|
+
type?: string;
|
|
41
|
+
autoComplete?: string;
|
|
42
|
+
required?: boolean;
|
|
43
|
+
error?: string;
|
|
44
|
+
inputRef?: Ref<HTMLInputElement>;
|
|
45
|
+
children?: ReactNode;
|
|
46
|
+
}): ReactNode;
|
|
47
|
+
export declare function ErrorSummary(props: {
|
|
48
|
+
error: string | null;
|
|
49
|
+
focusTrigger?: number;
|
|
50
|
+
}): ReactNode;
|
|
51
|
+
/**
|
|
52
|
+
* Moves keyboard focus to the first field that came back with an error after a
|
|
53
|
+
* failed submit, so a screen-reader / keyboard user lands on the control to fix
|
|
54
|
+
* (its label + error are read via `aria-describedby`) instead of being stranded
|
|
55
|
+
* on the disabled submit button. No-ops on the initial render and when the form
|
|
56
|
+
* carries a form-level error ({@link ErrorSummary} owns that focus). Call it
|
|
57
|
+
* once, unconditionally, from a form component built on {@link useAuthForm}.
|
|
58
|
+
*/
|
|
59
|
+
export declare function useFocusFirstError<V extends AuthFormValues>(form: AuthFormState<V>): void;
|
|
60
|
+
export declare function SocialButtons(props: SocialProvidersProps): ReactNode;
|
|
61
|
+
export declare function combineClassNames(className: string | undefined, slot: string | undefined): string | undefined;
|
|
62
|
+
export declare function displayName(user: Record<string, unknown>): string;
|