@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,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bring-your-own bot-challenge verification for auth endpoints — the CAPTCHA
|
|
3
|
+
* analogue of the {@link RateLimiterLike} seam. {@link CaptchaProvider} is the
|
|
4
|
+
* contract a client-issued challenge token is verified against server-side;
|
|
5
|
+
* Cloudflare Turnstile, hCaptcha, and reCAPTCHA all satisfy it, so nothing here
|
|
6
|
+
* depends on a vendor SDK. {@link verifyCaptcha} composes a provider with the
|
|
7
|
+
* fail-open/closed discipline a login flow needs: a missing or rejected token
|
|
8
|
+
* is a refusal, but a provider *outage* degrades to your existing rate-limit and
|
|
9
|
+
* lockout floor rather than locking everyone out.
|
|
10
|
+
*
|
|
11
|
+
* Tenant-agnostic: scope by passing whatever `action` and `ip` your provider
|
|
12
|
+
* scores on. **No provider means no challenge** — {@link verifyCaptcha} passes
|
|
13
|
+
* every request, so wiring the seam without configuring a provider changes
|
|
14
|
+
* nothing about existing behavior.
|
|
15
|
+
*
|
|
16
|
+
* @module
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Verify a client-issued CAPTCHA token with the fail-open/closed discipline an
|
|
20
|
+
* auth endpoint needs, and return a {@link CaptchaOutcome}.
|
|
21
|
+
*
|
|
22
|
+
* - **No provider** → unchallenged `"pass"` (`challenged: false`).
|
|
23
|
+
* - **Missing/empty token** → `"fail"` (the challenge went unanswered).
|
|
24
|
+
* - **Provider `{ success }`** → `"pass"`/`"fail"` accordingly.
|
|
25
|
+
* - **Provider throws (outage)** → `failOpen` decides `"pass"` or `"fail"`;
|
|
26
|
+
* either way the outcome is `degraded: true`, because no token was really
|
|
27
|
+
* evaluated.
|
|
28
|
+
*
|
|
29
|
+
* Never throws — a broken provider becomes a decision, not an exception the
|
|
30
|
+
* route must catch. Compose the result with your rate limiter and lockout; this
|
|
31
|
+
* does not replace them.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* const outcome = await verifyCaptcha({ provider, token, context: { ip, action: "signin" } });
|
|
36
|
+
* if (outcome.decision === "fail") return uniformRejection();
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export async function verifyCaptcha(options) {
|
|
40
|
+
const { provider, token, context } = options;
|
|
41
|
+
const failOpen = options.failOpen ?? true;
|
|
42
|
+
if (!provider) {
|
|
43
|
+
return { decision: "pass", challenged: false, degraded: false };
|
|
44
|
+
}
|
|
45
|
+
if (!token)
|
|
46
|
+
return { decision: "fail", challenged: true, degraded: false };
|
|
47
|
+
try {
|
|
48
|
+
const result = await provider.verify(token, context);
|
|
49
|
+
return {
|
|
50
|
+
decision: result.success ? "pass" : "fail",
|
|
51
|
+
challenged: true,
|
|
52
|
+
score: result.score,
|
|
53
|
+
degraded: false,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
console.error("[@udibo/oauth2] captcha provider verify failed (%s):", failOpen ? "fail-open, allowing" : "fail-closed, refusing", error instanceof Error ? error.message : error);
|
|
58
|
+
return {
|
|
59
|
+
decision: failOpen ? "pass" : "fail",
|
|
60
|
+
challenged: true,
|
|
61
|
+
degraded: true,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delivery seam for identity self-service messages (verification, reset, and
|
|
3
|
+
* passwordless sign-in links, plus one-time sign-in codes).
|
|
4
|
+
*
|
|
5
|
+
* The library owns the **token lifecycle** ({@link TokenFlowService}); your app
|
|
6
|
+
* owns **transport** — SMTP, SES, Resend, Twilio, etc. So delivery is a set of
|
|
7
|
+
* app-supplied async hooks, and the package ships URL builders so apps stop
|
|
8
|
+
* hand-assembling `${protocol}://${host}/verify-email?token=…` strings. This is
|
|
9
|
+
* the Better Auth model (`sendVerificationEmail` / `sendResetPassword`).
|
|
10
|
+
*
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
13
|
+
/** What a delivery hook receives for one message. */
|
|
14
|
+
export interface DeliveryMessage {
|
|
15
|
+
/** Recipient — an email address or phone number. */
|
|
16
|
+
to: string;
|
|
17
|
+
/**
|
|
18
|
+
* The ready-to-use action link (already carrying the token), when the caller
|
|
19
|
+
* built one via {@link buildVerificationUrl} / {@link buildResetUrl}. Absent
|
|
20
|
+
* if you'd rather build the message body from `token` yourself.
|
|
21
|
+
*/
|
|
22
|
+
url?: string;
|
|
23
|
+
/** The raw token, for apps that compose their own URL or message. */
|
|
24
|
+
token: string;
|
|
25
|
+
/** The subject the token acts on (typically a user id). */
|
|
26
|
+
subject: string;
|
|
27
|
+
/** Token expiry, epoch ms. */
|
|
28
|
+
expiresAt: number;
|
|
29
|
+
}
|
|
30
|
+
/** A single delivery transport callback. */
|
|
31
|
+
export type DeliveryHook = (message: DeliveryMessage) => Promise<void> | void;
|
|
32
|
+
/**
|
|
33
|
+
* What a code-delivery hook receives for one message. Unlike a
|
|
34
|
+
* {@link DeliveryMessage} there is no URL — a one-time code is typed in, not
|
|
35
|
+
* clicked — and the raw `code` is the whole payload.
|
|
36
|
+
*/
|
|
37
|
+
export interface CodeDeliveryMessage {
|
|
38
|
+
/** Recipient — an email address or phone number. */
|
|
39
|
+
to: string;
|
|
40
|
+
/** The one-time code to include in the message body, e.g. `"482913"`. */
|
|
41
|
+
code: string;
|
|
42
|
+
/** The subject the code signs in (typically a user id). */
|
|
43
|
+
subject: string;
|
|
44
|
+
/** Code expiry, epoch ms. */
|
|
45
|
+
expiresAt: number;
|
|
46
|
+
}
|
|
47
|
+
/** A delivery transport callback for one-time codes. */
|
|
48
|
+
export type CodeDeliveryHook = (message: CodeDeliveryMessage) => Promise<void> | void;
|
|
49
|
+
/**
|
|
50
|
+
* App-supplied delivery transports. Omit a hook to disable that message type.
|
|
51
|
+
* The package calls the relevant hook after minting the token; you send it.
|
|
52
|
+
*
|
|
53
|
+
* Trigger point `delivery` (see `docs/trigger-points.md`). The package calls a
|
|
54
|
+
* hook after minting the credential; a **throw** (e.g. a mailer outage) is
|
|
55
|
+
* trapped for every hook here: `IdentityService` invalidates the just-minted
|
|
56
|
+
* token or code — best effort, so a failed send does not leave a live,
|
|
57
|
+
* un-emailed credential in your store — and the requesting flow still resolves
|
|
58
|
+
* with its uniform result, so a transport outage cannot become an
|
|
59
|
+
* account-enumeration oracle. Handle transport failures inside the hook (log,
|
|
60
|
+
* retry, queue) if you need to observe or recover from them; the flow will not
|
|
61
|
+
* surface the throw to your caller.
|
|
62
|
+
*
|
|
63
|
+
* Every trapped failure emits the `delivery.failed` identity event, carrying the
|
|
64
|
+
* hook and an `invalidated` flag. `invalidated: false` means the cleanup failed
|
|
65
|
+
* too and a credential the recipient never received is live until it expires —
|
|
66
|
+
* the one case that needs an alert, and only your `onEvent` hook can see it.
|
|
67
|
+
*/
|
|
68
|
+
export interface DeliveryHooks {
|
|
69
|
+
/** Deliver an email-verification link. */
|
|
70
|
+
sendEmailVerification?: DeliveryHook;
|
|
71
|
+
/** Deliver a password-reset link. */
|
|
72
|
+
sendPasswordReset?: DeliveryHook;
|
|
73
|
+
/** Deliver an account-unlock link after a failed-attempt lockout. */
|
|
74
|
+
sendAccountUnlock?: DeliveryHook;
|
|
75
|
+
/** Deliver a passwordless sign-in (magic) link. */
|
|
76
|
+
sendSignInLink?: DeliveryHook;
|
|
77
|
+
/** Deliver a one-time sign-in code (no URL; the user types the code in). */
|
|
78
|
+
sendSignInCode?: CodeDeliveryHook;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Build an absolute action URL that embeds a token as a query parameter.
|
|
82
|
+
*
|
|
83
|
+
* @param base An origin (e.g. `https://app.example`) or origin + path prefix.
|
|
84
|
+
* @param path The route the token is consumed at (e.g. `/reset-password`).
|
|
85
|
+
* @param token The raw token to embed.
|
|
86
|
+
* @param param The query parameter name. Defaults to `"token"`.
|
|
87
|
+
*/
|
|
88
|
+
export declare function buildTokenUrl(base: string, path: string, token: string, param?: string): string;
|
|
89
|
+
/** Build an email-verification URL (default path `/verify-email`). */
|
|
90
|
+
export declare function buildVerificationUrl(base: string, token: string, path?: string): string;
|
|
91
|
+
/** Build a password-reset URL (default path `/reset-password`). */
|
|
92
|
+
export declare function buildResetUrl(base: string, token: string, path?: string): string;
|
|
93
|
+
/** Build a passwordless sign-in (magic-link) URL (default path `/signin-link`). */
|
|
94
|
+
export declare function buildSignInUrl(base: string, token: string, path?: string): string;
|
|
95
|
+
/** Build an account-unlock URL (default path `/unlock-account`). */
|
|
96
|
+
export declare function buildUnlockUrl(base: string, token: string, path?: string): string;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delivery seam for identity self-service messages (verification, reset, and
|
|
3
|
+
* passwordless sign-in links, plus one-time sign-in codes).
|
|
4
|
+
*
|
|
5
|
+
* The library owns the **token lifecycle** ({@link TokenFlowService}); your app
|
|
6
|
+
* owns **transport** — SMTP, SES, Resend, Twilio, etc. So delivery is a set of
|
|
7
|
+
* app-supplied async hooks, and the package ships URL builders so apps stop
|
|
8
|
+
* hand-assembling `${protocol}://${host}/verify-email?token=…` strings. This is
|
|
9
|
+
* the Better Auth model (`sendVerificationEmail` / `sendResetPassword`).
|
|
10
|
+
*
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Build an absolute action URL that embeds a token as a query parameter.
|
|
15
|
+
*
|
|
16
|
+
* @param base An origin (e.g. `https://app.example`) or origin + path prefix.
|
|
17
|
+
* @param path The route the token is consumed at (e.g. `/reset-password`).
|
|
18
|
+
* @param token The raw token to embed.
|
|
19
|
+
* @param param The query parameter name. Defaults to `"token"`.
|
|
20
|
+
*/
|
|
21
|
+
export function buildTokenUrl(base, path, token, param = "token") {
|
|
22
|
+
const url = new URL(path, base);
|
|
23
|
+
url.searchParams.set(param, token);
|
|
24
|
+
return url.toString();
|
|
25
|
+
}
|
|
26
|
+
/** Build an email-verification URL (default path `/verify-email`). */
|
|
27
|
+
export function buildVerificationUrl(base, token, path = "/verify-email") {
|
|
28
|
+
return buildTokenUrl(base, path, token);
|
|
29
|
+
}
|
|
30
|
+
/** Build a password-reset URL (default path `/reset-password`). */
|
|
31
|
+
export function buildResetUrl(base, token, path = "/reset-password") {
|
|
32
|
+
return buildTokenUrl(base, path, token);
|
|
33
|
+
}
|
|
34
|
+
/** Build a passwordless sign-in (magic-link) URL (default path `/signin-link`). */
|
|
35
|
+
export function buildSignInUrl(base, token, path = "/signin-link") {
|
|
36
|
+
return buildTokenUrl(base, path, token);
|
|
37
|
+
}
|
|
38
|
+
/** Build an account-unlock URL (default path `/unlock-account`). */
|
|
39
|
+
export function buildUnlockUrl(base, token, path = "/unlock-account") {
|
|
40
|
+
return buildTokenUrl(base, path, token);
|
|
41
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A stable error contract for identity flows, so a front-end can switch on a
|
|
3
|
+
* code instead of parsing messages, and the Hono route factory can map errors
|
|
4
|
+
* to consistent responses.
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Stable, machine-readable identity error codes. Most are raised by your store
|
|
10
|
+
* or the service; `forbidden_origin` is raised by the Hono route factory's
|
|
11
|
+
* same-origin guard before a handler runs.
|
|
12
|
+
*/
|
|
13
|
+
export type IdentityErrorCode = "invalid_credentials" | "invalid_token" | "rate_limited" | "weak_password" | "identifier_taken" | "email_not_verified" | "mfa_not_enrolled" | "mfa_already_enrolled" | "captcha_failed" | "forbidden_origin";
|
|
14
|
+
/** Options for {@link IdentityError}. */
|
|
15
|
+
export interface IdentityErrorOptions extends ErrorOptions {
|
|
16
|
+
/** For `rate_limited`: milliseconds until the caller may retry. */
|
|
17
|
+
retryAfterMs?: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* A typed error for identity flows. Throw it from your `IdentityUserStore`
|
|
21
|
+
* (e.g. `identifier_taken` on a duplicate) or your own validation; the Hono
|
|
22
|
+
* route factory maps it to `{ error: code }` with the right status.
|
|
23
|
+
*/
|
|
24
|
+
export declare class IdentityError extends Error {
|
|
25
|
+
/** Always `"IdentityError"`, so instances are recognizable across realms. */
|
|
26
|
+
readonly name = "IdentityError";
|
|
27
|
+
/** The stable, machine-readable code a client can switch on. */
|
|
28
|
+
readonly code: IdentityErrorCode;
|
|
29
|
+
/** For `rate_limited`: ms until retry, if known. */
|
|
30
|
+
readonly retryAfterMs?: number;
|
|
31
|
+
/** Builds an identity error; `message` defaults to the `code` when omitted. */
|
|
32
|
+
constructor(code: IdentityErrorCode, message?: string, options?: IdentityErrorOptions);
|
|
33
|
+
}
|
|
34
|
+
/** Type guard for {@link IdentityError}. */
|
|
35
|
+
export declare function isIdentityError(value: unknown): value is IdentityError;
|
|
36
|
+
/** The HTTP statuses an identity error maps to (a `ContentfulStatusCode` union). */
|
|
37
|
+
export type IdentityErrorStatus = 400 | 401 | 403 | 409 | 422 | 429;
|
|
38
|
+
/** The HTTP status conventionally paired with an identity error code. */
|
|
39
|
+
export declare function identityErrorStatus(code: IdentityErrorCode): IdentityErrorStatus;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A stable error contract for identity flows, so a front-end can switch on a
|
|
3
|
+
* code instead of parsing messages, and the Hono route factory can map errors
|
|
4
|
+
* to consistent responses.
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* A typed error for identity flows. Throw it from your `IdentityUserStore`
|
|
10
|
+
* (e.g. `identifier_taken` on a duplicate) or your own validation; the Hono
|
|
11
|
+
* route factory maps it to `{ error: code }` with the right status.
|
|
12
|
+
*/
|
|
13
|
+
export class IdentityError extends Error {
|
|
14
|
+
/** Always `"IdentityError"`, so instances are recognizable across realms. */
|
|
15
|
+
name = "IdentityError";
|
|
16
|
+
/** The stable, machine-readable code a client can switch on. */
|
|
17
|
+
code;
|
|
18
|
+
/** For `rate_limited`: ms until retry, if known. */
|
|
19
|
+
retryAfterMs;
|
|
20
|
+
/** Builds an identity error; `message` defaults to the `code` when omitted. */
|
|
21
|
+
constructor(code, message, options) {
|
|
22
|
+
super(message ?? code, options);
|
|
23
|
+
this.code = code;
|
|
24
|
+
this.retryAfterMs = options?.retryAfterMs;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/** Type guard for {@link IdentityError}. */
|
|
28
|
+
export function isIdentityError(value) {
|
|
29
|
+
return value instanceof IdentityError;
|
|
30
|
+
}
|
|
31
|
+
const STATUS = {
|
|
32
|
+
invalid_credentials: 401,
|
|
33
|
+
invalid_token: 400,
|
|
34
|
+
rate_limited: 429,
|
|
35
|
+
weak_password: 422,
|
|
36
|
+
identifier_taken: 409,
|
|
37
|
+
email_not_verified: 403,
|
|
38
|
+
mfa_not_enrolled: 409,
|
|
39
|
+
mfa_already_enrolled: 409,
|
|
40
|
+
captcha_failed: 403,
|
|
41
|
+
forbidden_origin: 403,
|
|
42
|
+
};
|
|
43
|
+
/** The HTTP status conventionally paired with an identity error code. */
|
|
44
|
+
export function identityErrorStatus(code) {
|
|
45
|
+
return STATUS[code] ?? 400;
|
|
46
|
+
}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identity flow events — the observability seam for audit capture.
|
|
3
|
+
*
|
|
4
|
+
* {@link IdentityService} emits an {@link IdentityEvent} after each significant
|
|
5
|
+
* auth outcome (sign-in success/failure, sign-up, credential upgrade-on-login,
|
|
6
|
+
* reset and verification lifecycle, passwordless sign-in links and codes),
|
|
7
|
+
* `MfaService` emits the `mfa.*` events, and a password-policy validator emits
|
|
8
|
+
* `password_policy.check_unavailable` when it cannot complete its check. Wire
|
|
9
|
+
* an {@link IdentityEventHook} via the service's `onEvent`
|
|
10
|
+
* option to persist them in your own audit store — storage stays app-owned,
|
|
11
|
+
* like every other seam in this layer.
|
|
12
|
+
*
|
|
13
|
+
* Some outcomes are *only* visible here, because the flow that produced them
|
|
14
|
+
* resolves uniformly by design: `delivery.failed` is the sole in-band signal
|
|
15
|
+
* that a message never went out (and, when `invalidated` is `false`, that a
|
|
16
|
+
* live credential the user never received is still outstanding). Treat this
|
|
17
|
+
* hook as a monitored surface, not just an audit log.
|
|
18
|
+
*
|
|
19
|
+
* Events describe internal outcomes (including whether an identifier resolved
|
|
20
|
+
* to an account), so they are for server-side capture only — never surface
|
|
21
|
+
* their contents to the end user, or they become an enumeration oracle.
|
|
22
|
+
*
|
|
23
|
+
* @module
|
|
24
|
+
*/
|
|
25
|
+
import type { DeliveryHooks } from "./delivery.js";
|
|
26
|
+
/** Why a sign-in attempt failed. Internal detail — never shown to the user. */
|
|
27
|
+
export type SignInFailureReason = "unknown_identifier" | "no_password" | "wrong_password";
|
|
28
|
+
/**
|
|
29
|
+
* Why a sign-in code verification failed. `unknown_email` is internal-only
|
|
30
|
+
* detail — the caller-facing result reports it as `invalid`.
|
|
31
|
+
*/
|
|
32
|
+
export type SignInCodeFailureReason = "invalid" | "expired" | "locked" | "unknown_email";
|
|
33
|
+
/**
|
|
34
|
+
* An auth outcome emitted by {@link IdentityService}, `MfaService`, or a
|
|
35
|
+
* password-policy validator. Discriminated on `type`; `userId` is present when
|
|
36
|
+
* the flow resolved an account.
|
|
37
|
+
*/
|
|
38
|
+
export type IdentityEvent = {
|
|
39
|
+
type: "sign_in.succeeded";
|
|
40
|
+
userId: string;
|
|
41
|
+
identifier: string;
|
|
42
|
+
} | {
|
|
43
|
+
type: "sign_in.failed";
|
|
44
|
+
identifier: string;
|
|
45
|
+
reason: SignInFailureReason;
|
|
46
|
+
userId?: string;
|
|
47
|
+
} | {
|
|
48
|
+
type: "sign_in.rate_limited";
|
|
49
|
+
identifier: string;
|
|
50
|
+
retryAfterMs: number;
|
|
51
|
+
enforced: boolean;
|
|
52
|
+
} | {
|
|
53
|
+
type: "sign_in.locked";
|
|
54
|
+
userId: string;
|
|
55
|
+
identifier: string;
|
|
56
|
+
enforced: boolean;
|
|
57
|
+
} | {
|
|
58
|
+
type: "lockout";
|
|
59
|
+
userId: string;
|
|
60
|
+
identifier: string;
|
|
61
|
+
failures: number;
|
|
62
|
+
lockedUntil?: number;
|
|
63
|
+
enforced: boolean;
|
|
64
|
+
} | {
|
|
65
|
+
type: "account_unlock.requested";
|
|
66
|
+
userId: string;
|
|
67
|
+
email: string;
|
|
68
|
+
} | {
|
|
69
|
+
type: "account_unlock.rate_limited";
|
|
70
|
+
userId: string;
|
|
71
|
+
email: string;
|
|
72
|
+
retryAfterMs: number;
|
|
73
|
+
enforced: boolean;
|
|
74
|
+
} | {
|
|
75
|
+
type: "account_unlock.completed";
|
|
76
|
+
userId: string;
|
|
77
|
+
} | {
|
|
78
|
+
type: "account_unlock.failed";
|
|
79
|
+
reason: "expired" | "invalid";
|
|
80
|
+
} | {
|
|
81
|
+
type: "sign_up";
|
|
82
|
+
userId: string;
|
|
83
|
+
} | {
|
|
84
|
+
type: "password.upgraded";
|
|
85
|
+
userId: string;
|
|
86
|
+
verifierId: string;
|
|
87
|
+
} | {
|
|
88
|
+
type: "password_policy.check_unavailable";
|
|
89
|
+
/**
|
|
90
|
+
* Which {@link PasswordPolicy} validator could not finish, as a stable
|
|
91
|
+
* snake_case name. {@link breachedPasswordValidator} reports
|
|
92
|
+
* `"breached_password"`.
|
|
93
|
+
*/
|
|
94
|
+
validator: string;
|
|
95
|
+
/**
|
|
96
|
+
* `true` when the password was accepted anyway (the validator's `failOpen`
|
|
97
|
+
* default), `false` when it was rejected instead. A run of `true` is a
|
|
98
|
+
* control that stopped running — the question this event exists to answer.
|
|
99
|
+
*/
|
|
100
|
+
failedOpen: boolean;
|
|
101
|
+
/**
|
|
102
|
+
* The underlying failure's message — a timeout, a DNS or egress-policy
|
|
103
|
+
* refusal, a non-2xx status from the upstream service.
|
|
104
|
+
*/
|
|
105
|
+
error: string;
|
|
106
|
+
} | {
|
|
107
|
+
type: "password_reset.requested";
|
|
108
|
+
email: string;
|
|
109
|
+
userId?: string;
|
|
110
|
+
} | {
|
|
111
|
+
type: "password_reset.rate_limited";
|
|
112
|
+
email: string;
|
|
113
|
+
retryAfterMs: number;
|
|
114
|
+
enforced: boolean;
|
|
115
|
+
} | {
|
|
116
|
+
type: "password_reset.completed";
|
|
117
|
+
userId: string;
|
|
118
|
+
} | {
|
|
119
|
+
type: "password_reset.failed";
|
|
120
|
+
/**
|
|
121
|
+
* `"invalid_token"` — the reset link was unknown, expired, or already used.
|
|
122
|
+
* `"session_revocation_failed"` — the new password was set but the
|
|
123
|
+
* configured {@link RevocableSessionService} threw before the user's other
|
|
124
|
+
* sessions could be revoked, so the reset is reported failed (and rethrown)
|
|
125
|
+
* rather than trusted; retry it so the still-live sessions are cleared.
|
|
126
|
+
*/
|
|
127
|
+
reason: "invalid_token" | "session_revocation_failed";
|
|
128
|
+
} | {
|
|
129
|
+
type: "email_verification.requested";
|
|
130
|
+
userId: string;
|
|
131
|
+
email: string;
|
|
132
|
+
} | {
|
|
133
|
+
type: "email_verification.rate_limited";
|
|
134
|
+
userId: string;
|
|
135
|
+
email: string;
|
|
136
|
+
retryAfterMs: number;
|
|
137
|
+
enforced: boolean;
|
|
138
|
+
} | {
|
|
139
|
+
type: "email_verification.completed";
|
|
140
|
+
userId: string;
|
|
141
|
+
email?: string;
|
|
142
|
+
} | {
|
|
143
|
+
type: "email_verification.failed";
|
|
144
|
+
reason: "expired" | "invalid";
|
|
145
|
+
} | {
|
|
146
|
+
type: "signin_link.requested";
|
|
147
|
+
email: string;
|
|
148
|
+
userId?: string;
|
|
149
|
+
} | {
|
|
150
|
+
type: "signin_link.consumed";
|
|
151
|
+
userId: string;
|
|
152
|
+
} | {
|
|
153
|
+
type: "signin_link.failed";
|
|
154
|
+
reason: "expired" | "invalid";
|
|
155
|
+
} | {
|
|
156
|
+
type: "signin_link.rate_limited";
|
|
157
|
+
email: string;
|
|
158
|
+
retryAfterMs?: number;
|
|
159
|
+
enforced: boolean;
|
|
160
|
+
} | {
|
|
161
|
+
type: "signin_code.requested";
|
|
162
|
+
email: string;
|
|
163
|
+
userId?: string;
|
|
164
|
+
} | {
|
|
165
|
+
type: "signin_code.verified";
|
|
166
|
+
userId: string;
|
|
167
|
+
} | {
|
|
168
|
+
type: "signin_code.failed";
|
|
169
|
+
email: string;
|
|
170
|
+
reason: SignInCodeFailureReason;
|
|
171
|
+
} | {
|
|
172
|
+
type: "signin_code.rate_limited";
|
|
173
|
+
email: string;
|
|
174
|
+
retryAfterMs?: number;
|
|
175
|
+
enforced: boolean;
|
|
176
|
+
} | {
|
|
177
|
+
type: "delivery.failed";
|
|
178
|
+
/** Which {@link DeliveryHooks} hook threw. */
|
|
179
|
+
hook: keyof DeliveryHooks;
|
|
180
|
+
/**
|
|
181
|
+
* `true` when the just-minted token or code was invalidated after the
|
|
182
|
+
* failed send — the intended outcome, nothing usable left behind. `false`
|
|
183
|
+
* when that cleanup *also* failed: a live credential the recipient never
|
|
184
|
+
* received is still outstanding until it expires. Alert on `false`.
|
|
185
|
+
*/
|
|
186
|
+
invalidated: boolean;
|
|
187
|
+
/** The delivery hook's failure message. */
|
|
188
|
+
error: string;
|
|
189
|
+
} | {
|
|
190
|
+
type: "mfa.enrollment.confirmed";
|
|
191
|
+
userId: string;
|
|
192
|
+
} | {
|
|
193
|
+
type: "mfa.verify.succeeded";
|
|
194
|
+
userId: string;
|
|
195
|
+
method: "totp" | "recovery";
|
|
196
|
+
remainingRecoveryCodes?: number;
|
|
197
|
+
} | {
|
|
198
|
+
type: "mfa.verify.failed";
|
|
199
|
+
userId: string;
|
|
200
|
+
/**
|
|
201
|
+
* `"replayed"` marks a *valid* TOTP code rejected because its time step
|
|
202
|
+
* was already spent — the classic signal of code interception or a
|
|
203
|
+
* concurrent session, worth distinguishing from a typo in audit review.
|
|
204
|
+
*/
|
|
205
|
+
reason: "invalid" | "replayed";
|
|
206
|
+
} | {
|
|
207
|
+
type: "mfa.verify.rate_limited";
|
|
208
|
+
userId: string;
|
|
209
|
+
retryAfterMs: number;
|
|
210
|
+
enforced: boolean;
|
|
211
|
+
} | {
|
|
212
|
+
type: "mfa.recovery_codes.regenerated";
|
|
213
|
+
userId: string;
|
|
214
|
+
} | {
|
|
215
|
+
type: "mfa.disabled";
|
|
216
|
+
userId: string;
|
|
217
|
+
};
|
|
218
|
+
/**
|
|
219
|
+
* Receives each {@link IdentityEvent} after the flow's outcome is decided. The
|
|
220
|
+
* hook is awaited, but it must not affect the flow: a rejection is caught and
|
|
221
|
+
* logged, never rethrown — report failures through your own channel if you
|
|
222
|
+
* need delivery guarantees.
|
|
223
|
+
*
|
|
224
|
+
* Trigger point `event` (see `docs/trigger-points.md`) — the one
|
|
225
|
+
* **fire-and-forget** seam in the identity layer.
|
|
226
|
+
*/
|
|
227
|
+
export type IdentityEventHook = (event: IdentityEvent) => void | Promise<void>;
|
|
228
|
+
/** The `*.rate_limited` members of {@link IdentityEvent}. */
|
|
229
|
+
export type RateLimitedEvent = Extract<IdentityEvent, {
|
|
230
|
+
type: `${string}.rate_limited`;
|
|
231
|
+
}>;
|
|
232
|
+
/**
|
|
233
|
+
* A {@link RateLimitedEvent} minus the fields the throttle sequence fills in
|
|
234
|
+
* (`retryAfterMs`, `enforced`) — what a flow passes to `enforceRateLimit`.
|
|
235
|
+
*/
|
|
236
|
+
export type RateLimitedEventInit<E = RateLimitedEvent> = E extends RateLimitedEvent ? Omit<E, "retryAfterMs" | "enforced"> : never;
|
|
237
|
+
/**
|
|
238
|
+
* Deliver an event to an optional {@link IdentityEventHook}, upholding the
|
|
239
|
+
* hook contract: a rejection is logged (prefixed with `label`), never
|
|
240
|
+
* rethrown. Every identity-layer service emits through this.
|
|
241
|
+
*/
|
|
242
|
+
export declare function dispatchIdentityEvent(hook: IdentityEventHook | undefined, label: string, event: IdentityEvent): Promise<void>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identity flow events — the observability seam for audit capture.
|
|
3
|
+
*
|
|
4
|
+
* {@link IdentityService} emits an {@link IdentityEvent} after each significant
|
|
5
|
+
* auth outcome (sign-in success/failure, sign-up, credential upgrade-on-login,
|
|
6
|
+
* reset and verification lifecycle, passwordless sign-in links and codes),
|
|
7
|
+
* `MfaService` emits the `mfa.*` events, and a password-policy validator emits
|
|
8
|
+
* `password_policy.check_unavailable` when it cannot complete its check. Wire
|
|
9
|
+
* an {@link IdentityEventHook} via the service's `onEvent`
|
|
10
|
+
* option to persist them in your own audit store — storage stays app-owned,
|
|
11
|
+
* like every other seam in this layer.
|
|
12
|
+
*
|
|
13
|
+
* Some outcomes are *only* visible here, because the flow that produced them
|
|
14
|
+
* resolves uniformly by design: `delivery.failed` is the sole in-band signal
|
|
15
|
+
* that a message never went out (and, when `invalidated` is `false`, that a
|
|
16
|
+
* live credential the user never received is still outstanding). Treat this
|
|
17
|
+
* hook as a monitored surface, not just an audit log.
|
|
18
|
+
*
|
|
19
|
+
* Events describe internal outcomes (including whether an identifier resolved
|
|
20
|
+
* to an account), so they are for server-side capture only — never surface
|
|
21
|
+
* their contents to the end user, or they become an enumeration oracle.
|
|
22
|
+
*
|
|
23
|
+
* @module
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* Deliver an event to an optional {@link IdentityEventHook}, upholding the
|
|
27
|
+
* hook contract: a rejection is logged (prefixed with `label`), never
|
|
28
|
+
* rethrown. Every identity-layer service emits through this.
|
|
29
|
+
*/
|
|
30
|
+
export async function dispatchIdentityEvent(hook, label, event) {
|
|
31
|
+
if (!hook)
|
|
32
|
+
return;
|
|
33
|
+
try {
|
|
34
|
+
await hook(event);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
console.error(`[@udibo/oauth2] ${label} onEvent hook failed:`, error);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal helpers shared by the external-provider connectors.
|
|
3
|
+
*
|
|
4
|
+
* Not part of the public API — reach for a connector from
|
|
5
|
+
* `@udibo/oauth2/identity/external` instead, which re-exports the one type an
|
|
6
|
+
* adopter configures ({@link AzpPolicy}). These live here so the same failure
|
|
7
|
+
* reads the same way whichever connector hit it: one error renderer, one
|
|
8
|
+
* error-body reader, one provider-text sanitizer, and one id_token claim gate
|
|
9
|
+
* rather than a copy per provider. The token exchange itself is
|
|
10
|
+
* `_token-exchange.ts`.
|
|
11
|
+
*
|
|
12
|
+
* @module
|
|
13
|
+
*/
|
|
14
|
+
import { PROVIDER_TEXT_MAX_LENGTH, sanitizeProviderText } from "../../utils/text.js";
|
|
15
|
+
export { PROVIDER_TEXT_MAX_LENGTH, sanitizeProviderText };
|
|
16
|
+
/**
|
|
17
|
+
* Clock-skew allowance, in seconds, when checking an id_token's `exp` claim.
|
|
18
|
+
* The default for {@link assertIdTokenClaims}.
|
|
19
|
+
*/
|
|
20
|
+
export declare const ID_TOKEN_EXPIRY_LEEWAY_SECONDS = 120;
|
|
21
|
+
/**
|
|
22
|
+
* Renders a thrown value for an error message, unwrapping an OAuth2 error into
|
|
23
|
+
* its `error` / `error_description` extensions so a provider-reported failure
|
|
24
|
+
* is quoted rather than buried in a generic message. Every branch is
|
|
25
|
+
* {@link sanitizeProviderText}-bounded: the thrown value routinely carries
|
|
26
|
+
* provider bytes — a JSON parse error quotes the body it choked on, and an
|
|
27
|
+
* OAuth2 error carries a description the provider wrote.
|
|
28
|
+
*/
|
|
29
|
+
export declare function describeError(error: unknown): string;
|
|
30
|
+
/**
|
|
31
|
+
* Reads a failed response's body into a bounded, single-line `": <text>"`
|
|
32
|
+
* suffix to append to an error message, or `""` when the body is empty or
|
|
33
|
+
* unreadable. Consumes the body, so the caller must not read it again.
|
|
34
|
+
*/
|
|
35
|
+
export declare function readErrorBody(res: Response): Promise<string>;
|
|
36
|
+
/**
|
|
37
|
+
* Drops one trailing `"/"` so issuer URLs compare and concatenate consistently
|
|
38
|
+
* (`"https://x/"` and `"https://x"` identify the same issuer).
|
|
39
|
+
*/
|
|
40
|
+
export declare function stripTrailingSlash(value: string): string;
|
|
41
|
+
/**
|
|
42
|
+
* How {@link assertIdTokenClaims} treats an id_token's `azp` (authorized
|
|
43
|
+
* party) claim.
|
|
44
|
+
*
|
|
45
|
+
* - `"conditional"` — OpenID Connect Core §3.1.3.7: when the token carries
|
|
46
|
+
* multiple audiences or an `azp` claim, `azp` must be this client id.
|
|
47
|
+
* - `"ignore"` — skip the check. For a provider that legitimately reports a
|
|
48
|
+
* *different* authorized party than the client doing the exchange: Google's
|
|
49
|
+
* cross-client identity, where another client of the same project requests the
|
|
50
|
+
* token (`azp` is that client id) for this client's audience (`aud`), is the
|
|
51
|
+
* canonical case. Weakens §3.1.3.7 rules 4 and 5, so `aud` remains the only
|
|
52
|
+
* check binding the token to this registration — reach for it only when a real
|
|
53
|
+
* provider forces it. Configure it on
|
|
54
|
+
* {@link OidcProviderOptions.azp}/{@link GoogleProviderOptions.azp}.
|
|
55
|
+
*/
|
|
56
|
+
export type AzpPolicy = "conditional" | "ignore";
|
|
57
|
+
/** Input for {@link assertIdTokenClaims}. */
|
|
58
|
+
export interface IdTokenClaimsInput {
|
|
59
|
+
/** Provider id carried on the thrown {@link ExternalAuthError}. */
|
|
60
|
+
provider: string;
|
|
61
|
+
/** The decoded id_token claims. */
|
|
62
|
+
claims: Record<string, unknown>;
|
|
63
|
+
/** The issuer the `iss` claim must equal exactly. */
|
|
64
|
+
issuer: string;
|
|
65
|
+
/** The client id the `aud` claim must contain. */
|
|
66
|
+
clientId: string;
|
|
67
|
+
/** The nonce sent on the authorize request; omit when none was sent. */
|
|
68
|
+
expectedNonce?: string;
|
|
69
|
+
/** How to check `azp`. Defaults to `"conditional"`. */
|
|
70
|
+
azp?: AzpPolicy;
|
|
71
|
+
/**
|
|
72
|
+
* Clock-skew allowance for `exp`, in seconds. Defaults to
|
|
73
|
+
* {@link ID_TOKEN_EXPIRY_LEEWAY_SECONDS}.
|
|
74
|
+
*/
|
|
75
|
+
leewaySeconds?: number;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Enforces the id_token claim rules of OpenID Connect Core §3.1.3.7 that do not
|
|
79
|
+
* depend on the signature: `iss`, `aud`, `azp`, `exp`, and `nonce`, in that
|
|
80
|
+
* order. Callers verify the signature (or establish the token's provenance)
|
|
81
|
+
* themselves before calling this.
|
|
82
|
+
*
|
|
83
|
+
* Throws {@link ExternalAuthError} with code `nonce_mismatch` when the `nonce`
|
|
84
|
+
* claim does not match, and `provider_error` for every other rule.
|
|
85
|
+
*/
|
|
86
|
+
export declare function assertIdTokenClaims(input: IdTokenClaimsInput): void;
|