@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,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal buffer conversion shared by the modules that call Web Crypto.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Copy to a standalone `ArrayBuffer` to satisfy `BufferSource` on runtimes that
|
|
8
|
+
* narrow `Uint8Array<ArrayBufferLike>` away from `ArrayBuffer`.
|
|
9
|
+
*/
|
|
10
|
+
export declare function toArrayBuffer(bytes: Uint8Array): ArrayBuffer;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal buffer conversion shared by the modules that call Web Crypto.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Copy to a standalone `ArrayBuffer` to satisfy `BufferSource` on runtimes that
|
|
8
|
+
* narrow `Uint8Array<ArrayBufferLike>` away from `ArrayBuffer`.
|
|
9
|
+
*/
|
|
10
|
+
export function toArrayBuffer(bytes) {
|
|
11
|
+
return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
12
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal constant-time matching shared by the MFA and BFF code paths.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Constant-time scan for `target` among `candidates`: every candidate is
|
|
8
|
+
* compared (no early exit), so timing reveals neither whether nor where a
|
|
9
|
+
* match occurred. Returns the index of the *last* matching candidate, or
|
|
10
|
+
* `undefined` when none match — order candidates least- to most-preferred so
|
|
11
|
+
* a duplicate resolves to the preferred entry.
|
|
12
|
+
*/
|
|
13
|
+
export declare function timingSafeMatchIndex(candidates: string[], target: string): number | undefined;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal constant-time matching shared by the MFA and BFF code paths.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
import { timingSafeEqualString } from "./crypto.js";
|
|
7
|
+
/**
|
|
8
|
+
* Constant-time scan for `target` among `candidates`: every candidate is
|
|
9
|
+
* compared (no early exit), so timing reveals neither whether nor where a
|
|
10
|
+
* match occurred. Returns the index of the *last* matching candidate, or
|
|
11
|
+
* `undefined` when none match — order candidates least- to most-preferred so
|
|
12
|
+
* a duplicate resolves to the preferred entry.
|
|
13
|
+
*/
|
|
14
|
+
export function timingSafeMatchIndex(candidates, target) {
|
|
15
|
+
let index;
|
|
16
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
17
|
+
if (timingSafeEqualString(candidates[i], target)) {
|
|
18
|
+
index = i;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return index;
|
|
22
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { AuthenticationContext } from "../models/authentication.js";
|
|
2
|
+
/** Copies verified event claims without sharing mutable method arrays. */
|
|
3
|
+
export declare function snapshotAuthenticationContext(context: AuthenticationContext | undefined): AuthenticationContext | undefined;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Copies verified event claims without sharing mutable method arrays. */
|
|
2
|
+
export function snapshotAuthenticationContext(context) {
|
|
3
|
+
if (context === undefined)
|
|
4
|
+
return undefined;
|
|
5
|
+
return Object.freeze({
|
|
6
|
+
...(context.auth_time === undefined
|
|
7
|
+
? {}
|
|
8
|
+
: { auth_time: context.auth_time }),
|
|
9
|
+
...(context.acr === undefined ? {} : { acr: context.acr }),
|
|
10
|
+
...(context.amr === undefined
|
|
11
|
+
? {}
|
|
12
|
+
: { amr: Object.freeze([...context.amr]) }),
|
|
13
|
+
});
|
|
14
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP Basic Authentication utilities.
|
|
3
|
+
* @module
|
|
4
|
+
*/
|
|
5
|
+
/** Parsed basic authentication credentials. */
|
|
6
|
+
export interface BasicAuth {
|
|
7
|
+
/** Username portion (the segment before the first colon). */
|
|
8
|
+
name: string;
|
|
9
|
+
/** Password portion (everything after the first colon). */
|
|
10
|
+
pass: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Parses basic authentication credentials from an Authorization header.
|
|
14
|
+
*
|
|
15
|
+
* Both segments are decoded from `application/x-www-form-urlencoded` form, as
|
|
16
|
+
* RFC 6749 section 2.3.1 requires of client credentials, so a client id or
|
|
17
|
+
* secret containing `+`, a space, `%`, `:`, or non-ASCII survives the round
|
|
18
|
+
* trip. A segment that is not valid percent-encoding is taken verbatim, so a
|
|
19
|
+
* client that sends such characters un-encoded still authenticates.
|
|
20
|
+
*
|
|
21
|
+
* @param authorization The Authorization header value
|
|
22
|
+
* @returns The parsed credentials
|
|
23
|
+
* @throws {InvalidClientError} If the header is missing or malformed
|
|
24
|
+
* @see https://datatracker.ietf.org/doc/html/rfc6749#section-2.3.1
|
|
25
|
+
*/
|
|
26
|
+
export declare function parseBasicAuth(authorization: string | null): BasicAuth;
|
|
27
|
+
/**
|
|
28
|
+
* Encodes client credentials for HTTP Basic Authentication.
|
|
29
|
+
*
|
|
30
|
+
* Both values are `application/x-www-form-urlencoded` before base64, as RFC
|
|
31
|
+
* 6749 section 2.3.1 requires: that keeps `+`, spaces, `%`, and `:` unambiguous
|
|
32
|
+
* on the wire and makes a non-ASCII secret encodable at all. Credentials made
|
|
33
|
+
* only of `A-Z`, `a-z`, `0-9`, `-`, `_`, `.` and `*` encode byte-identically to
|
|
34
|
+
* their raw form; every other character, `~` included, is percent-encoded.
|
|
35
|
+
*
|
|
36
|
+
* @param clientId The client ID
|
|
37
|
+
* @param clientSecret The client secret
|
|
38
|
+
* @returns The encoded Authorization header value
|
|
39
|
+
* @see https://datatracker.ietf.org/doc/html/rfc6749#section-2.3.1
|
|
40
|
+
*/
|
|
41
|
+
export declare function encodeBasicAuth(clientId: string, clientSecret: string): string;
|
|
42
|
+
/**
|
|
43
|
+
* Tries to parse basic authentication credentials from an Authorization header.
|
|
44
|
+
* Returns undefined if the header is missing or not Basic auth. Decodes both
|
|
45
|
+
* segments the same way {@link parseBasicAuth} does.
|
|
46
|
+
*
|
|
47
|
+
* @param authorization The Authorization header value
|
|
48
|
+
* @returns The parsed credentials or undefined
|
|
49
|
+
*/
|
|
50
|
+
export declare function tryParseBasicAuth(authorization: string | null): BasicAuth | undefined;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP Basic Authentication utilities.
|
|
3
|
+
* @module
|
|
4
|
+
*/
|
|
5
|
+
import { InvalidClientError } from "../errors.js";
|
|
6
|
+
const CREDENTIALS = /^ *(?:[Bb][Aa][Ss][Ii][Cc]) +([\w-.~+/]+=*) *$/;
|
|
7
|
+
const NAME_PASS = /^([^:]+):(.*)$/;
|
|
8
|
+
const NOT_FORM_SAFE = /[!'()~]/g;
|
|
9
|
+
const ENCODED_SPACE = /%20/g;
|
|
10
|
+
const PLUS = /\+/g;
|
|
11
|
+
function formUrlEncode(value) {
|
|
12
|
+
return encodeURIComponent(value)
|
|
13
|
+
.replace(NOT_FORM_SAFE, (character) => `%${character.charCodeAt(0).toString(16).toUpperCase()}`)
|
|
14
|
+
.replace(ENCODED_SPACE, "+");
|
|
15
|
+
}
|
|
16
|
+
function formUrlDecode(value) {
|
|
17
|
+
try {
|
|
18
|
+
return decodeURIComponent(value.replace(PLUS, " "));
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Parses basic authentication credentials from an Authorization header.
|
|
26
|
+
*
|
|
27
|
+
* Both segments are decoded from `application/x-www-form-urlencoded` form, as
|
|
28
|
+
* RFC 6749 section 2.3.1 requires of client credentials, so a client id or
|
|
29
|
+
* secret containing `+`, a space, `%`, `:`, or non-ASCII survives the round
|
|
30
|
+
* trip. A segment that is not valid percent-encoding is taken verbatim, so a
|
|
31
|
+
* client that sends such characters un-encoded still authenticates.
|
|
32
|
+
*
|
|
33
|
+
* @param authorization The Authorization header value
|
|
34
|
+
* @returns The parsed credentials
|
|
35
|
+
* @throws {InvalidClientError} If the header is missing or malformed
|
|
36
|
+
* @see https://datatracker.ietf.org/doc/html/rfc6749#section-2.3.1
|
|
37
|
+
*/
|
|
38
|
+
export function parseBasicAuth(authorization) {
|
|
39
|
+
if (!authorization) {
|
|
40
|
+
throw new InvalidClientError("authorization header required");
|
|
41
|
+
}
|
|
42
|
+
let match = CREDENTIALS.exec(authorization);
|
|
43
|
+
if (!match) {
|
|
44
|
+
throw new InvalidClientError("unsupported authorization header");
|
|
45
|
+
}
|
|
46
|
+
let value;
|
|
47
|
+
try {
|
|
48
|
+
value = atob(match[1]);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
throw new InvalidClientError("authorization header is not correctly encoded");
|
|
52
|
+
}
|
|
53
|
+
match = NAME_PASS.exec(value);
|
|
54
|
+
if (!match) {
|
|
55
|
+
throw new InvalidClientError("authorization header is malformed");
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
name: formUrlDecode(match[1]),
|
|
59
|
+
pass: formUrlDecode(match[2]),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Encodes client credentials for HTTP Basic Authentication.
|
|
64
|
+
*
|
|
65
|
+
* Both values are `application/x-www-form-urlencoded` before base64, as RFC
|
|
66
|
+
* 6749 section 2.3.1 requires: that keeps `+`, spaces, `%`, and `:` unambiguous
|
|
67
|
+
* on the wire and makes a non-ASCII secret encodable at all. Credentials made
|
|
68
|
+
* only of `A-Z`, `a-z`, `0-9`, `-`, `_`, `.` and `*` encode byte-identically to
|
|
69
|
+
* their raw form; every other character, `~` included, is percent-encoded.
|
|
70
|
+
*
|
|
71
|
+
* @param clientId The client ID
|
|
72
|
+
* @param clientSecret The client secret
|
|
73
|
+
* @returns The encoded Authorization header value
|
|
74
|
+
* @see https://datatracker.ietf.org/doc/html/rfc6749#section-2.3.1
|
|
75
|
+
*/
|
|
76
|
+
export function encodeBasicAuth(clientId, clientSecret) {
|
|
77
|
+
return `Basic ${btoa(`${formUrlEncode(clientId)}:${formUrlEncode(clientSecret)}`)}`;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Tries to parse basic authentication credentials from an Authorization header.
|
|
81
|
+
* Returns undefined if the header is missing or not Basic auth. Decodes both
|
|
82
|
+
* segments the same way {@link parseBasicAuth} does.
|
|
83
|
+
*
|
|
84
|
+
* @param authorization The Authorization header value
|
|
85
|
+
* @returns The parsed credentials or undefined
|
|
86
|
+
*/
|
|
87
|
+
export function tryParseBasicAuth(authorization) {
|
|
88
|
+
if (!authorization)
|
|
89
|
+
return undefined;
|
|
90
|
+
const match = CREDENTIALS.exec(authorization);
|
|
91
|
+
if (!match)
|
|
92
|
+
return undefined;
|
|
93
|
+
try {
|
|
94
|
+
const value = atob(match[1]);
|
|
95
|
+
const namePass = NAME_PASS.exec(value);
|
|
96
|
+
if (!namePass)
|
|
97
|
+
return undefined;
|
|
98
|
+
return {
|
|
99
|
+
name: formUrlDecode(namePass[1]),
|
|
100
|
+
pass: formUrlDecode(namePass[2]),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authenticated-encryption (AES-256-GCM) and key-derivation helpers for sealing
|
|
3
|
+
* session and token data at rest or in a cookie.
|
|
4
|
+
*
|
|
5
|
+
* Two key-derivation paths, for two different threat models:
|
|
6
|
+
*
|
|
7
|
+
* - {@link deriveAesKey} hashes a single **server-held** secret (SHA-256) into
|
|
8
|
+
* an AES-256-GCM key. Use this for a stateless cookie sealed under one
|
|
9
|
+
* configured secret (see `EncryptedCookieSessionStore`), where the secret is
|
|
10
|
+
* never persisted.
|
|
11
|
+
*
|
|
12
|
+
* - {@link deriveSealKey} uses **HKDF** with a domain-separating `info` label so
|
|
13
|
+
* a high-entropy *per-session* secret yields an encryption key that is
|
|
14
|
+
* independent of any other value derived from the same secret. This is what
|
|
15
|
+
* makes "the cookie value is both the lookup credential **and** the decryption
|
|
16
|
+
* key" safe: a database can store `sha256(secret)` as a lookup hash while the
|
|
17
|
+
* sealing key is `HKDF(secret, info)`, and knowing the stored hash never
|
|
18
|
+
* reveals the key. Always pass a distinct `info` per purpose.
|
|
19
|
+
*
|
|
20
|
+
* {@link seal}/{@link unseal} use AES-256-GCM (an AEAD), so tampered ciphertext
|
|
21
|
+
* fails to unseal (returns `null`) rather than decrypting to garbage.
|
|
22
|
+
*
|
|
23
|
+
* @module
|
|
24
|
+
*/
|
|
25
|
+
import "../_dnt.polyfills.js";
|
|
26
|
+
import { sha256Hash } from "../server/utils/hash.js";
|
|
27
|
+
import { toArrayBuffer } from "./_buffer.js";
|
|
28
|
+
export { sha256Hash };
|
|
29
|
+
export { toArrayBuffer };
|
|
30
|
+
/** Constant-time string equality (length difference short-circuits to false). */
|
|
31
|
+
export declare function timingSafeEqualString(a: string, b: string): boolean;
|
|
32
|
+
/** URL-safe base64 encode (no padding). */
|
|
33
|
+
export declare function base64urlEncode(bytes: Uint8Array): string;
|
|
34
|
+
/**
|
|
35
|
+
* URL-safe base64 decode. Padding is optional, so JWS/JWT segments — which
|
|
36
|
+
* carry none — decode as-is. Only the URL-safe alphabet is accepted.
|
|
37
|
+
*
|
|
38
|
+
* @throws {TypeError} on a character outside the URL-safe alphabet — including
|
|
39
|
+
* the standard alphabet's `+` and `/`, and whitespace.
|
|
40
|
+
* @throws {RangeError} on a truncated value (a length that leaves a remainder
|
|
41
|
+
* of 1 when divided by 4, which no encoding can produce).
|
|
42
|
+
*/
|
|
43
|
+
export declare function base64urlDecode(value: string): Uint8Array;
|
|
44
|
+
/**
|
|
45
|
+
* Mint a cryptographically random, URL-safe token: `base64url` of `bytes`
|
|
46
|
+
* random bytes, unpadded, so it drops into a URL, a link in an email, or a
|
|
47
|
+
* cookie value with no further escaping.
|
|
48
|
+
*
|
|
49
|
+
* This is the primitive behind session secrets, verification / password-reset
|
|
50
|
+
* links, and download tokens. The token is a bearer credential: hand it out
|
|
51
|
+
* once and persist only a hash of it ({@link sha256Hash}, from this module).
|
|
52
|
+
* The default 32 bytes (256 bits, 43 characters) is what every such credential
|
|
53
|
+
* should use; shrink it only for values that don't authorize anything.
|
|
54
|
+
*
|
|
55
|
+
* @param bytes How many random bytes to draw. Defaults to 32.
|
|
56
|
+
* @returns The unpadded base64url encoding of those bytes.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```ts
|
|
60
|
+
* randomToken(); // 43 chars, e.g. "kQ7…" — a reset-link token
|
|
61
|
+
* randomToken(16); // 22 chars — a non-credential correlation id
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
export declare function randomToken(bytes?: number): string;
|
|
65
|
+
/**
|
|
66
|
+
* Derive an AES-256-GCM key from a single server-held secret via SHA-256.
|
|
67
|
+
*
|
|
68
|
+
* Suitable when the secret is a long-lived configuration value that is never
|
|
69
|
+
* stored alongside the ciphertext. Do **not** use this when a hash of the same
|
|
70
|
+
* secret is persisted (e.g. as a lookup key) — use {@link deriveSealKey} so the
|
|
71
|
+
* sealing key is independent of that stored hash.
|
|
72
|
+
*
|
|
73
|
+
* @throws if the secret has fewer than 16 bytes of entropy (32+ recommended).
|
|
74
|
+
*/
|
|
75
|
+
export declare function deriveAesKey(secret: string | Uint8Array): Promise<CryptoKey>;
|
|
76
|
+
/**
|
|
77
|
+
* Derive an AES-256-GCM sealing key from a high-entropy secret via HKDF-SHA-256
|
|
78
|
+
* with a domain-separating `info` label.
|
|
79
|
+
*
|
|
80
|
+
* Keys derived with different `info` values (or different from `sha256(secret)`)
|
|
81
|
+
* are independent, so the same secret can safely back both a stored lookup hash
|
|
82
|
+
* and a never-stored sealing key. Pass a stable, unique `info` per purpose, e.g.
|
|
83
|
+
* `"udibo:bff-session-tokens:v1"`.
|
|
84
|
+
*/
|
|
85
|
+
export declare function deriveSealKey(secret: string | Uint8Array, info: string): Promise<CryptoKey>;
|
|
86
|
+
/** Seal bytes with AES-256-GCM; returns `base64url(iv ‖ ciphertext+tag)`. */
|
|
87
|
+
export declare function seal(key: CryptoKey, plaintext: Uint8Array): Promise<string>;
|
|
88
|
+
/**
|
|
89
|
+
* Unseal an AES-256-GCM value produced by {@link seal}. Returns `null` on a
|
|
90
|
+
* malformed input, a wrong key, or tampering (the AEAD tag fails); it never
|
|
91
|
+
* throws.
|
|
92
|
+
*/
|
|
93
|
+
export declare function unseal(key: CryptoKey, sealed: string): Promise<Uint8Array | null>;
|
|
94
|
+
/** Seal a JSON-serializable value. */
|
|
95
|
+
export declare function sealJson(key: CryptoKey, value: unknown): Promise<string>;
|
|
96
|
+
/** Unseal a value sealed by {@link sealJson}, or `null` if it can't be unsealed. */
|
|
97
|
+
export declare function unsealJson<T>(key: CryptoKey, sealed: string): Promise<T | null>;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authenticated-encryption (AES-256-GCM) and key-derivation helpers for sealing
|
|
3
|
+
* session and token data at rest or in a cookie.
|
|
4
|
+
*
|
|
5
|
+
* Two key-derivation paths, for two different threat models:
|
|
6
|
+
*
|
|
7
|
+
* - {@link deriveAesKey} hashes a single **server-held** secret (SHA-256) into
|
|
8
|
+
* an AES-256-GCM key. Use this for a stateless cookie sealed under one
|
|
9
|
+
* configured secret (see `EncryptedCookieSessionStore`), where the secret is
|
|
10
|
+
* never persisted.
|
|
11
|
+
*
|
|
12
|
+
* - {@link deriveSealKey} uses **HKDF** with a domain-separating `info` label so
|
|
13
|
+
* a high-entropy *per-session* secret yields an encryption key that is
|
|
14
|
+
* independent of any other value derived from the same secret. This is what
|
|
15
|
+
* makes "the cookie value is both the lookup credential **and** the decryption
|
|
16
|
+
* key" safe: a database can store `sha256(secret)` as a lookup hash while the
|
|
17
|
+
* sealing key is `HKDF(secret, info)`, and knowing the stored hash never
|
|
18
|
+
* reveals the key. Always pass a distinct `info` per purpose.
|
|
19
|
+
*
|
|
20
|
+
* {@link seal}/{@link unseal} use AES-256-GCM (an AEAD), so tampered ciphertext
|
|
21
|
+
* fails to unseal (returns `null`) rather than decrypting to garbage.
|
|
22
|
+
*
|
|
23
|
+
* @module
|
|
24
|
+
*/
|
|
25
|
+
import "../_dnt.polyfills.js";
|
|
26
|
+
import { timingSafeEqual } from "../deps/jsr.io/@std/crypto/1.1.0/timing_safe_equal.js";
|
|
27
|
+
import { decodeBase64Url, encodeBase64Url } from "../deps/jsr.io/@std/encoding/1.0.11/base64url.js";
|
|
28
|
+
import { sha256Hash } from "../server/utils/hash.js";
|
|
29
|
+
import { toArrayBuffer } from "./_buffer.js";
|
|
30
|
+
export { sha256Hash };
|
|
31
|
+
export { toArrayBuffer };
|
|
32
|
+
function toBytes(secret) {
|
|
33
|
+
return typeof secret === "string" ? new TextEncoder().encode(secret) : secret;
|
|
34
|
+
}
|
|
35
|
+
/** Constant-time string equality (length difference short-circuits to false). */
|
|
36
|
+
export function timingSafeEqualString(a, b) {
|
|
37
|
+
const encoder = new TextEncoder();
|
|
38
|
+
const aBytes = encoder.encode(a);
|
|
39
|
+
const bBytes = encoder.encode(b);
|
|
40
|
+
if (aBytes.byteLength !== bBytes.byteLength)
|
|
41
|
+
return false;
|
|
42
|
+
return timingSafeEqual(aBytes, bBytes);
|
|
43
|
+
}
|
|
44
|
+
/** URL-safe base64 encode (no padding). */
|
|
45
|
+
export function base64urlEncode(bytes) {
|
|
46
|
+
return encodeBase64Url(bytes);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* URL-safe base64 decode. Padding is optional, so JWS/JWT segments — which
|
|
50
|
+
* carry none — decode as-is. Only the URL-safe alphabet is accepted.
|
|
51
|
+
*
|
|
52
|
+
* @throws {TypeError} on a character outside the URL-safe alphabet — including
|
|
53
|
+
* the standard alphabet's `+` and `/`, and whitespace.
|
|
54
|
+
* @throws {RangeError} on a truncated value (a length that leaves a remainder
|
|
55
|
+
* of 1 when divided by 4, which no encoding can produce).
|
|
56
|
+
*/
|
|
57
|
+
export function base64urlDecode(value) {
|
|
58
|
+
return decodeBase64Url(value);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Mint a cryptographically random, URL-safe token: `base64url` of `bytes`
|
|
62
|
+
* random bytes, unpadded, so it drops into a URL, a link in an email, or a
|
|
63
|
+
* cookie value with no further escaping.
|
|
64
|
+
*
|
|
65
|
+
* This is the primitive behind session secrets, verification / password-reset
|
|
66
|
+
* links, and download tokens. The token is a bearer credential: hand it out
|
|
67
|
+
* once and persist only a hash of it ({@link sha256Hash}, from this module).
|
|
68
|
+
* The default 32 bytes (256 bits, 43 characters) is what every such credential
|
|
69
|
+
* should use; shrink it only for values that don't authorize anything.
|
|
70
|
+
*
|
|
71
|
+
* @param bytes How many random bytes to draw. Defaults to 32.
|
|
72
|
+
* @returns The unpadded base64url encoding of those bytes.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```ts
|
|
76
|
+
* randomToken(); // 43 chars, e.g. "kQ7…" — a reset-link token
|
|
77
|
+
* randomToken(16); // 22 chars — a non-credential correlation id
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
export function randomToken(bytes = 32) {
|
|
81
|
+
return base64urlEncode(crypto.getRandomValues(new Uint8Array(bytes)));
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Derive an AES-256-GCM key from a single server-held secret via SHA-256.
|
|
85
|
+
*
|
|
86
|
+
* Suitable when the secret is a long-lived configuration value that is never
|
|
87
|
+
* stored alongside the ciphertext. Do **not** use this when a hash of the same
|
|
88
|
+
* secret is persisted (e.g. as a lookup key) — use {@link deriveSealKey} so the
|
|
89
|
+
* sealing key is independent of that stored hash.
|
|
90
|
+
*
|
|
91
|
+
* @throws if the secret has fewer than 16 bytes of entropy (32+ recommended).
|
|
92
|
+
*/
|
|
93
|
+
export async function deriveAesKey(secret) {
|
|
94
|
+
const raw = toBytes(secret);
|
|
95
|
+
if (raw.byteLength < 16) {
|
|
96
|
+
throw new Error("secret must be at least 16 bytes; 32+ bytes of entropy is recommended.");
|
|
97
|
+
}
|
|
98
|
+
const digest = await crypto.subtle.digest("SHA-256", toArrayBuffer(raw));
|
|
99
|
+
return await crypto.subtle.importKey("raw", digest, { name: "AES-GCM" }, false, ["encrypt", "decrypt"]);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Derive an AES-256-GCM sealing key from a high-entropy secret via HKDF-SHA-256
|
|
103
|
+
* with a domain-separating `info` label.
|
|
104
|
+
*
|
|
105
|
+
* Keys derived with different `info` values (or different from `sha256(secret)`)
|
|
106
|
+
* are independent, so the same secret can safely back both a stored lookup hash
|
|
107
|
+
* and a never-stored sealing key. Pass a stable, unique `info` per purpose, e.g.
|
|
108
|
+
* `"udibo:bff-session-tokens:v1"`.
|
|
109
|
+
*/
|
|
110
|
+
export async function deriveSealKey(secret, info) {
|
|
111
|
+
const base = await crypto.subtle.importKey("raw", toArrayBuffer(toBytes(secret)), "HKDF", false, ["deriveKey"]);
|
|
112
|
+
return await crypto.subtle.deriveKey({
|
|
113
|
+
name: "HKDF",
|
|
114
|
+
hash: "SHA-256",
|
|
115
|
+
salt: new Uint8Array(0),
|
|
116
|
+
info: new TextEncoder().encode(info),
|
|
117
|
+
}, base, { name: "AES-GCM", length: 256 }, false, ["encrypt", "decrypt"]);
|
|
118
|
+
}
|
|
119
|
+
/** Seal bytes with AES-256-GCM; returns `base64url(iv ‖ ciphertext+tag)`. */
|
|
120
|
+
export async function seal(key, plaintext) {
|
|
121
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
122
|
+
const ciphertext = new Uint8Array(await crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, toArrayBuffer(plaintext)));
|
|
123
|
+
const packed = new Uint8Array(iv.length + ciphertext.length);
|
|
124
|
+
packed.set(iv, 0);
|
|
125
|
+
packed.set(ciphertext, iv.length);
|
|
126
|
+
return base64urlEncode(packed);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Unseal an AES-256-GCM value produced by {@link seal}. Returns `null` on a
|
|
130
|
+
* malformed input, a wrong key, or tampering (the AEAD tag fails); it never
|
|
131
|
+
* throws.
|
|
132
|
+
*/
|
|
133
|
+
export async function unseal(key, sealed) {
|
|
134
|
+
try {
|
|
135
|
+
const bytes = base64urlDecode(sealed);
|
|
136
|
+
if (bytes.length < 13)
|
|
137
|
+
return null;
|
|
138
|
+
const iv = bytes.slice(0, 12);
|
|
139
|
+
const ciphertext = bytes.slice(12);
|
|
140
|
+
const plaintext = await crypto.subtle.decrypt({ name: "AES-GCM", iv }, key, toArrayBuffer(ciphertext));
|
|
141
|
+
return new Uint8Array(plaintext);
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/** Seal a JSON-serializable value. */
|
|
148
|
+
export async function sealJson(key, value) {
|
|
149
|
+
return await seal(key, new TextEncoder().encode(JSON.stringify(value)));
|
|
150
|
+
}
|
|
151
|
+
/** Unseal a value sealed by {@link sealJson}, or `null` if it can't be unsealed. */
|
|
152
|
+
export async function unsealJson(key, sealed) {
|
|
153
|
+
const bytes = await unseal(key, sealed);
|
|
154
|
+
if (!bytes)
|
|
155
|
+
return null;
|
|
156
|
+
try {
|
|
157
|
+
return JSON.parse(new TextDecoder().decode(bytes));
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PKCE (Proof Key for Code Exchange) utilities for OAuth2.
|
|
3
|
+
*
|
|
4
|
+
* Implements RFC 7636 for preventing authorization code interception attacks.
|
|
5
|
+
*
|
|
6
|
+
* @see https://datatracker.ietf.org/doc/html/rfc7636
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* A challenge method used for PKCE.
|
|
11
|
+
* Transforms a verifier into a challenge.
|
|
12
|
+
*/
|
|
13
|
+
export type ChallengeMethod = (verifier: string) => Promise<string>;
|
|
14
|
+
/** The allowed PKCE code challenge methods. */
|
|
15
|
+
export interface ChallengeMethods {
|
|
16
|
+
/** Maps a `code_challenge_method` name (e.g. `"S256"`) to its transform. */
|
|
17
|
+
[key: string]: ChallengeMethod;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Minimum length for code verifier per RFC 7636 Section 4.1.
|
|
21
|
+
* @see https://datatracker.ietf.org/doc/html/rfc7636#section-4.1
|
|
22
|
+
*/
|
|
23
|
+
export declare const CODE_VERIFIER_MIN_LENGTH = 43;
|
|
24
|
+
/**
|
|
25
|
+
* Maximum length for code verifier per RFC 7636 Section 4.1.
|
|
26
|
+
* @see https://datatracker.ietf.org/doc/html/rfc7636#section-4.1
|
|
27
|
+
*/
|
|
28
|
+
export declare const CODE_VERIFIER_MAX_LENGTH = 128;
|
|
29
|
+
/**
|
|
30
|
+
* Pattern for valid code verifier characters per RFC 7636 Section 4.1.
|
|
31
|
+
* Allowed characters: [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~"
|
|
32
|
+
* @see https://datatracker.ietf.org/doc/html/rfc7636#section-4.1
|
|
33
|
+
*/
|
|
34
|
+
export declare const CODE_VERIFIER_PATTERN: RegExp;
|
|
35
|
+
/**
|
|
36
|
+
* The default allowed PKCE code challenge methods.
|
|
37
|
+
*
|
|
38
|
+
* Per RFC 7636 and OAuth 2.1, clients SHOULD use the S256 method.
|
|
39
|
+
* The "plain" method is NOT included by default as it provides
|
|
40
|
+
* weaker security guarantees.
|
|
41
|
+
*
|
|
42
|
+
* @see https://datatracker.ietf.org/doc/html/rfc7636#section-4.2
|
|
43
|
+
* @see https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics#section-2.1.1
|
|
44
|
+
*/
|
|
45
|
+
export declare const challengeMethods: ChallengeMethods;
|
|
46
|
+
/**
|
|
47
|
+
* Validates a code verifier per RFC 7636 Section 4.1.
|
|
48
|
+
*
|
|
49
|
+
* The code verifier must:
|
|
50
|
+
* - Be between 43 and 128 characters long
|
|
51
|
+
* - Contain only unreserved characters: [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~"
|
|
52
|
+
*
|
|
53
|
+
* @param verifier The code verifier to validate
|
|
54
|
+
* @returns true if the verifier is valid
|
|
55
|
+
* @see https://datatracker.ietf.org/doc/html/rfc7636#section-4.1
|
|
56
|
+
*/
|
|
57
|
+
export declare function validateCodeVerifier(verifier: string): boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Generates a random code verifier with a minimum of 256 bits of entropy.
|
|
60
|
+
*
|
|
61
|
+
* This is done by generating a random 32-octet sequence then base64url encoding it
|
|
62
|
+
* to produce a 43 character URL-safe string.
|
|
63
|
+
*
|
|
64
|
+
* @returns A cryptographically random code verifier
|
|
65
|
+
* @see https://datatracker.ietf.org/doc/html/rfc7636#section-4.1
|
|
66
|
+
* @see https://datatracker.ietf.org/doc/html/rfc7636#section-7.1
|
|
67
|
+
*/
|
|
68
|
+
export declare function generateCodeVerifier(): string;
|
|
69
|
+
/**
|
|
70
|
+
* Generates a code challenge from a code verifier using the S256 method.
|
|
71
|
+
*
|
|
72
|
+
* The S256 challenge is: BASE64URL(SHA256(ASCII(code_verifier)))
|
|
73
|
+
*
|
|
74
|
+
* @param verifier The code verifier to transform
|
|
75
|
+
* @returns The code challenge
|
|
76
|
+
* @see https://datatracker.ietf.org/doc/html/rfc7636#section-4.2
|
|
77
|
+
*/
|
|
78
|
+
export declare function generateCodeChallenge(verifier: string): Promise<string>;
|
|
79
|
+
/**
|
|
80
|
+
* Validates a `code_challenge` per RFC 7636 Section 4.2, for the challenge
|
|
81
|
+
* methods whose output shape that section fixes.
|
|
82
|
+
*
|
|
83
|
+
* `S256` and `plain` are the only methods the PKCE registry defines, and both
|
|
84
|
+
* produce a challenge drawn from the same 43-128 unreserved characters as a
|
|
85
|
+
* code verifier, so an authorization endpoint can reject a malformed challenge
|
|
86
|
+
* before it is ever stored. Any other `method` — one a server registered in its
|
|
87
|
+
* own {@link ChallengeMethods} map — may legitimately produce any shape, so
|
|
88
|
+
* this returns `true` for it rather than imposing a format its author never
|
|
89
|
+
* agreed to. Check the name is allowed with {@linkcode getChallengeMethod}
|
|
90
|
+
* first; this answers only for the format.
|
|
91
|
+
*
|
|
92
|
+
* @param challenge The client-supplied `code_challenge`
|
|
93
|
+
* @param method The client-supplied `code_challenge_method`; defaults to `S256`
|
|
94
|
+
* @returns true when the challenge is acceptable for that method
|
|
95
|
+
* @example
|
|
96
|
+
* ```ts
|
|
97
|
+
* validateCodeChallenge("a".repeat(43)); // → true
|
|
98
|
+
* validateCodeChallenge("short"); // → false
|
|
99
|
+
* validateCodeChallenge("short", "custom-method"); // → true
|
|
100
|
+
* ```
|
|
101
|
+
* @see https://datatracker.ietf.org/doc/html/rfc7636#section-4.2
|
|
102
|
+
*/
|
|
103
|
+
export declare function validateCodeChallenge(challenge: string, method?: string | null): boolean;
|
|
104
|
+
/**
|
|
105
|
+
* Resolves a client-supplied `code_challenge_method` name against the methods
|
|
106
|
+
* a server allows.
|
|
107
|
+
*
|
|
108
|
+
* Reach for this instead of indexing a {@link ChallengeMethods} map directly.
|
|
109
|
+
* The name comes off the wire, and a map written as a plain object literal
|
|
110
|
+
* also inherits `toString`, `valueOf`, `constructor` and friends from
|
|
111
|
+
* `Object.prototype` — a bare lookup hands those back as if the server had
|
|
112
|
+
* allowed them, which downgrades PKCE to a value any verifier satisfies. Only
|
|
113
|
+
* own, callable properties of `methods` resolve here; every other name is
|
|
114
|
+
* `undefined`, including names inherited from a prototype.
|
|
115
|
+
*
|
|
116
|
+
* @param methods The challenge methods the server allows
|
|
117
|
+
* @param method The client-supplied method name; defaults to `S256`
|
|
118
|
+
* @returns The transform for that method, or `undefined` when it is not allowed
|
|
119
|
+
* @example
|
|
120
|
+
* ```ts
|
|
121
|
+
* getChallengeMethod(challengeMethods, "S256"); // → the S256 transform
|
|
122
|
+
* getChallengeMethod(challengeMethods); // → the S256 transform
|
|
123
|
+
* getChallengeMethod(challengeMethods, "toString"); // → undefined
|
|
124
|
+
* getChallengeMethod(challengeMethods, "__proto__"); // → undefined
|
|
125
|
+
* ```
|
|
126
|
+
* @see https://datatracker.ietf.org/doc/html/rfc7636#section-4.3
|
|
127
|
+
*/
|
|
128
|
+
export declare function getChallengeMethod(methods: ChallengeMethods, method?: string | null): ChallengeMethod | undefined;
|
|
129
|
+
/**
|
|
130
|
+
* Verifies that a code verifier matches a code challenge.
|
|
131
|
+
*
|
|
132
|
+
* Fails closed against the default {@link challengeMethods} map only: a
|
|
133
|
+
* `method` that map does not hold — `"plain"`, a name inherited from
|
|
134
|
+
* `Object.prototype` such as `"toString"`, or one registered on a grant via
|
|
135
|
+
* its own `challengeMethods` — resolves `false` rather than throwing, so it is
|
|
136
|
+
* indistinguishable from a mismatched verifier.
|
|
137
|
+
*
|
|
138
|
+
* @param verifier The code verifier provided by the client
|
|
139
|
+
* @param challenge The code challenge stored from the authorization request
|
|
140
|
+
* @param method The challenge method used (defaults to S256)
|
|
141
|
+
* @returns true if the verifier matches the challenge
|
|
142
|
+
* @see https://datatracker.ietf.org/doc/html/rfc7636#section-4.6
|
|
143
|
+
*/
|
|
144
|
+
export declare function verifyCodeChallenge(verifier: string, challenge: string, method?: string): Promise<boolean>;
|
|
145
|
+
/**
|
|
146
|
+
* Generates a random state parameter for CSRF protection.
|
|
147
|
+
*
|
|
148
|
+
* @returns A random UUID to use as the state parameter
|
|
149
|
+
* @see https://datatracker.ietf.org/doc/html/rfc6749#section-10.12
|
|
150
|
+
*/
|
|
151
|
+
export declare function generateState(): string;
|