@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,579 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `IdentityService` — composable functions for an app that owns its login.
|
|
3
|
+
*
|
|
4
|
+
* Orchestrates the identity primitives ({@link PasswordIdentityService},
|
|
5
|
+
* {@link TokenFlowService}, delivery hooks, {@link RevocableSessionService})
|
|
6
|
+
* over an **app-owned** {@link IdentityUserStore} into the flows a single app
|
|
7
|
+
* needs: sign-up, sign-in, password reset, email verification, and passwordless
|
|
8
|
+
* sign-in (magic links + one-time codes). These are plain
|
|
9
|
+
* methods you call from your own routes/loaders/actions — there is no opinionated
|
|
10
|
+
* mounted framework here. A thin, optional Hono route factory lives in
|
|
11
|
+
* `@udibo/oauth2/hono/identity` for apps that want the endpoints mounted.
|
|
12
|
+
*
|
|
13
|
+
* Storage-agnostic by design: every flow addresses users by opaque id and the
|
|
14
|
+
* {@link IdentityUserStore} is yours — back it with whatever database, ORM, and
|
|
15
|
+
* schema your app already uses. The library composes the flows; it never owns
|
|
16
|
+
* your tables. It stays below the turnkey-identity-service line.
|
|
17
|
+
*
|
|
18
|
+
* @module
|
|
19
|
+
*/
|
|
20
|
+
import { type DeliveryHooks } from "./delivery.js";
|
|
21
|
+
import type { AccountLockoutLike } from "./lockout.js";
|
|
22
|
+
import { type PasswordCredential, type PasswordHasherLike } from "./password.js";
|
|
23
|
+
import { type TokenFlowService } from "./token-flow.js";
|
|
24
|
+
import type { RevocableSessionService } from "./session.js";
|
|
25
|
+
import { type PasswordPolicy } from "./password-policy.js";
|
|
26
|
+
import { type RateLimiterLike } from "./rate-limit.js";
|
|
27
|
+
import { type OtpStore } from "./otp.js";
|
|
28
|
+
import { type IdentityEventHook } from "./events.js";
|
|
29
|
+
import { type LegacyPasswordVerifier } from "./migration.js";
|
|
30
|
+
/** The minimum the identity flows require of your user record. */
|
|
31
|
+
export interface IdentityUser {
|
|
32
|
+
/** Opaque user id the flows address users by; never interpreted by the library. */
|
|
33
|
+
id: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* App-owned user storage the identity flows orchestrate. You implement it over
|
|
37
|
+
* your own database/ORM; the flows only ever address users by opaque id, so the
|
|
38
|
+
* backing store stays entirely yours.
|
|
39
|
+
*/
|
|
40
|
+
export interface IdentityUserStore<User extends IdentityUser> {
|
|
41
|
+
/**
|
|
42
|
+
* Create a user from a sign-up `profile` (your app's fields — validate them)
|
|
43
|
+
* plus the hashed password credential.
|
|
44
|
+
*/
|
|
45
|
+
create(profile: Record<string, unknown>, credential: PasswordCredential): Promise<User>;
|
|
46
|
+
/** Find a user by a sign-in identifier (email/username/phone). */
|
|
47
|
+
findByIdentifier(identifier: string): Promise<User | undefined>;
|
|
48
|
+
/** Find a user by email (password-reset / re-verification requests). */
|
|
49
|
+
findByEmail(email: string): Promise<User | undefined>;
|
|
50
|
+
/**
|
|
51
|
+
* The user's stored password credential, or `undefined`.
|
|
52
|
+
*
|
|
53
|
+
* Must reflect every write committed before a
|
|
54
|
+
* {@link IdentityUserStore.replaceCredential} call returned `false` — read
|
|
55
|
+
* the primary, never a cache, a lagging replica, or a snapshot shared with
|
|
56
|
+
* the compare-and-set. The service re-reads through this to decide a sign-in
|
|
57
|
+
* that lost the compare-and-set, so a stale read lets a password retired by
|
|
58
|
+
* the winning write still sign in.
|
|
59
|
+
*/
|
|
60
|
+
getCredential(userId: string): Promise<PasswordCredential | undefined>;
|
|
61
|
+
/** Replace the user's password credential (reset / change). */
|
|
62
|
+
setCredential(userId: string, credential: PasswordCredential): Promise<void>;
|
|
63
|
+
/**
|
|
64
|
+
* Atomically replace the credential only if it still equals `expected`.
|
|
65
|
+
* `undefined` expects no native credential, for an imported-password upgrade.
|
|
66
|
+
* Return `false` without writing if anything changed. Compare all stored
|
|
67
|
+
* credential fields in the same database operation as the write, never read
|
|
68
|
+
* then write. On `false` the service re-reads the credential and re-verifies
|
|
69
|
+
* the password against it, so a concurrent sign-in that rehashed first still
|
|
70
|
+
* lets this one in while a reset or change to a different password rejects
|
|
71
|
+
* it. Without this capability, sign-in succeeds but automatic rehash and
|
|
72
|
+
* imported credential upgrades are skipped; explicit password resets still
|
|
73
|
+
* work.
|
|
74
|
+
*/
|
|
75
|
+
replaceCredential?(userId: string, expected: PasswordCredential | undefined, credential: PasswordCredential): Promise<boolean>;
|
|
76
|
+
/**
|
|
77
|
+
* Mark the user's email verified. Omit if you don't verify email.
|
|
78
|
+
*
|
|
79
|
+
* `email` is the address the verification token was **issued for**, when the
|
|
80
|
+
* token carried one. Your implementation must only flip the flag when the
|
|
81
|
+
* user's current email still equals it (`WHERE id = $userId AND email =
|
|
82
|
+
* $email`) — otherwise a link minted for one address verifies whatever
|
|
83
|
+
* address the account holds when the link is clicked, letting an attacker
|
|
84
|
+
* mark a victim's address verified by changing the account email after
|
|
85
|
+
* requesting the link.
|
|
86
|
+
*
|
|
87
|
+
* Must be idempotent: it runs before the single-use token is consumed, so a
|
|
88
|
+
* retried link calls it again.
|
|
89
|
+
*/
|
|
90
|
+
markEmailVerified?(userId: string, email?: string): Promise<void>;
|
|
91
|
+
/**
|
|
92
|
+
* The user's imported foreign password hash (bcrypt/argon2/PBKDF2/… PHC
|
|
93
|
+
* string), or `null` once migrated or never imported. Implement it — alongside
|
|
94
|
+
* a `legacyCredential` column and {@link IdentityUserStore.clearLegacyCredential}
|
|
95
|
+
* — to enable upgrade-on-login: {@link IdentityService.signIn} verifies this
|
|
96
|
+
* hash with the configured `legacyVerifiers` only while the user has no
|
|
97
|
+
* native credential (the native credential, once set, is authoritative —
|
|
98
|
+
* a wrong password never falls back to the imported hash). Only consulted
|
|
99
|
+
* when `legacyVerifiers` is set.
|
|
100
|
+
*/
|
|
101
|
+
getLegacyCredential?(userId: string): Promise<string | null>;
|
|
102
|
+
/**
|
|
103
|
+
* Drop the user's imported foreign hash once it is superseded — after a
|
|
104
|
+
* successful upgrade-on-login and after {@link IdentityService.resetPassword}.
|
|
105
|
+
* **Implement this whenever you implement {@link getLegacyCredential}:** a
|
|
106
|
+
* lingering foreign hash keeps the old imported password alive as a
|
|
107
|
+
* fallback — ignored while a native credential exists, but it would come
|
|
108
|
+
* back to life if the native credential is ever removed. Called at most
|
|
109
|
+
* once per user per supersession.
|
|
110
|
+
*/
|
|
111
|
+
clearLegacyCredential?(userId: string): Promise<void>;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Per-flow limiter overrides for {@link IdentityServiceOptions.rateLimiters}.
|
|
115
|
+
* One key per throttled flow, each falling back to
|
|
116
|
+
* {@link IdentityServiceOptions.rateLimiter} when unset. `signInCode` covers
|
|
117
|
+
* both requesting and verifying a code — they share the `otp:signin:` key.
|
|
118
|
+
*/
|
|
119
|
+
export interface IdentityRateLimiters {
|
|
120
|
+
/** `signin:<identifier>` — {@link IdentityService.signIn}. */
|
|
121
|
+
signIn?: RateLimiterLike;
|
|
122
|
+
/** `pwreset:<email>` — {@link IdentityService.requestPasswordReset}. */
|
|
123
|
+
passwordReset?: RateLimiterLike;
|
|
124
|
+
/** `verifyemail:<userId>` — {@link IdentityService.requestEmailVerification}. */
|
|
125
|
+
emailVerification?: RateLimiterLike;
|
|
126
|
+
/** `unlock:<userId>` — {@link IdentityService.requestAccountUnlock}. */
|
|
127
|
+
accountUnlock?: RateLimiterLike;
|
|
128
|
+
/** `pwless:<email>` — {@link IdentityService.requestSignInLink}. */
|
|
129
|
+
signInLink?: RateLimiterLike;
|
|
130
|
+
/** `otp:signin:<email>` — request and verify of a sign-in code. */
|
|
131
|
+
signInCode?: RateLimiterLike;
|
|
132
|
+
}
|
|
133
|
+
/** Options for {@link IdentityService}. */
|
|
134
|
+
export interface IdentityServiceOptions<User extends IdentityUser> {
|
|
135
|
+
/** App-owned user storage. */
|
|
136
|
+
users: IdentityUserStore<User>;
|
|
137
|
+
/** Token flow for verify/reset links. Required for those flows. */
|
|
138
|
+
tokens?: TokenFlowService;
|
|
139
|
+
/**
|
|
140
|
+
* Password hasher. Defaults to a new {@link PasswordIdentityService}
|
|
141
|
+
* (PBKDF2-SHA-256). Any {@link PasswordHasherLike} works, so argon2 or scrypt
|
|
142
|
+
* plug in with your own dependency. When the hasher implements
|
|
143
|
+
* `needsRehash`, {@link IdentityService.signIn} transparently re-hashes and
|
|
144
|
+
* stores a credential whose work factor has fallen behind, when the user
|
|
145
|
+
* store implements `replaceCredential`.
|
|
146
|
+
*/
|
|
147
|
+
passwords?: PasswordHasherLike;
|
|
148
|
+
/**
|
|
149
|
+
* Foreign password-hash verifiers for **upgrade-on-login**, tried in order.
|
|
150
|
+
* When set, {@link IdentityService.signIn} — for a user with no native
|
|
151
|
+
* credential yet — reads the imported hash via
|
|
152
|
+
* {@link IdentityUserStore.getLegacyCredential} and verifies it with the first
|
|
153
|
+
* verifier whose `canVerify` accepts it. On a match it rehashes the password
|
|
154
|
+
* into the native format ({@link IdentityUserStore.replaceCredential}), clears the
|
|
155
|
+
* imported hash ({@link IdentityUserStore.clearLegacyCredential}), emits a
|
|
156
|
+
* `password.upgraded` event, and signs the user in. The package ships
|
|
157
|
+
* {@link pbkdf2Verifier} built-in; bcrypt/argon2/scrypt are bring-your-own via
|
|
158
|
+
* the {@link LegacyPasswordVerifier} seam (no dep-free implementation exists).
|
|
159
|
+
*/
|
|
160
|
+
legacyVerifiers?: LegacyPasswordVerifier[];
|
|
161
|
+
/**
|
|
162
|
+
* Minimum wall-clock duration of a **failed** {@link IdentityService.signIn},
|
|
163
|
+
* in ms. Defaults to `250`. Every rejection — unknown identifier, locked
|
|
164
|
+
* account, no password set, wrong native password, wrong imported password —
|
|
165
|
+
* is held to the same deadline measured from the call's first statement, so
|
|
166
|
+
* the response time reveals nothing about which branch ran.
|
|
167
|
+
*
|
|
168
|
+
* Set it above the cost of your slowest configured
|
|
169
|
+
* {@link IdentityServiceOptions.legacyVerifiers} plus one password hash —
|
|
170
|
+
* that is the expensive branch (a foreign KDF the native branch never runs),
|
|
171
|
+
* and without a floor above it an unauthenticated caller can separate the
|
|
172
|
+
* accounts still carrying an imported hash from the migrated ones by response
|
|
173
|
+
* time alone. A branch that outruns the floor returns as soon as its own work
|
|
174
|
+
* finishes: whatever it spends *above* the floor stays visible, so raising a
|
|
175
|
+
* work factor means re-checking this number.
|
|
176
|
+
*
|
|
177
|
+
* `0` disables the floor entirely, which restores that signal in full — the
|
|
178
|
+
* one value that turns it off. Anything else out of range (negative, `NaN`,
|
|
179
|
+
* `Infinity`) falls back to the default rather than to no floor.
|
|
180
|
+
* Successful sign-ins are never delayed.
|
|
181
|
+
*/
|
|
182
|
+
failedSignInFloorMs?: number;
|
|
183
|
+
/** Session revocation, called on a successful password reset. Optional. */
|
|
184
|
+
sessions?: RevocableSessionService;
|
|
185
|
+
/** Delivery transports for verify/reset links. Optional. */
|
|
186
|
+
delivery?: DeliveryHooks;
|
|
187
|
+
/**
|
|
188
|
+
* Base URL (origin, or origin + prefix) used to build the action links
|
|
189
|
+
* passed to delivery hooks. When omitted, hooks receive only the raw `token`.
|
|
190
|
+
*/
|
|
191
|
+
baseUrl?: string;
|
|
192
|
+
/**
|
|
193
|
+
* Token lifetimes in ms. Defaults: password reset 1h, email verification 24h,
|
|
194
|
+
* account unlock 1h, sign-in link 15m. A per-call `ttlMs` — currently only
|
|
195
|
+
* {@link IdentityService.requestSignInLink} takes one — overrides the
|
|
196
|
+
* configured default for that request. One-time sign-in **codes** are not
|
|
197
|
+
* tokens; their lifetime is {@link IdentityServiceOptions.otp}'s `ttlMs`.
|
|
198
|
+
*/
|
|
199
|
+
ttl?: {
|
|
200
|
+
passwordReset?: number;
|
|
201
|
+
emailVerification?: number;
|
|
202
|
+
accountUnlock?: number;
|
|
203
|
+
signInLink?: number;
|
|
204
|
+
};
|
|
205
|
+
/**
|
|
206
|
+
* One-time sign-in codes ("email me a code"). When set,
|
|
207
|
+
* {@link IdentityService.requestSignInCode} and
|
|
208
|
+
* {@link IdentityService.verifySignInCode} run an {@link EmailOtpService}
|
|
209
|
+
* over the app-owned store. Storage and code shape only — the code flow's
|
|
210
|
+
* single throttle is `rateLimiters.signInCode` (falling back to
|
|
211
|
+
* {@link rateLimiter}) under the key `otp:signin:<email>`, so a request for
|
|
212
|
+
* a known email is throttled exactly like one for an unknown email.
|
|
213
|
+
*/
|
|
214
|
+
otp?: {
|
|
215
|
+
/** App-owned code storage. */
|
|
216
|
+
store: OtpStore;
|
|
217
|
+
/** Code length in digits. Defaults to `6`. */
|
|
218
|
+
digits?: number;
|
|
219
|
+
/** Code lifetime in ms. Defaults to 10 minutes. */
|
|
220
|
+
ttlMs?: number;
|
|
221
|
+
/** Wrong-attempt budget before the code locks. Defaults to `5`. */
|
|
222
|
+
maxAttempts?: number;
|
|
223
|
+
};
|
|
224
|
+
/**
|
|
225
|
+
* Password policy enforced by {@link IdentityService.signUp} and
|
|
226
|
+
* {@link IdentityService.resetPassword} before hashing — always, not only
|
|
227
|
+
* when you configure one. Omitting it leaves the
|
|
228
|
+
* {@link PasswordPolicy} defaults in force: 8–256 characters, no composition
|
|
229
|
+
* rules, no extra validators. The maximum is a DoS guard on the hash input
|
|
230
|
+
* (the hasher itself has none), so every password the app can **store** is
|
|
231
|
+
* bounded in every configuration. {@link IdentityService.signIn} is outside
|
|
232
|
+
* the policy by design — it hashes whatever the request carries, because the
|
|
233
|
+
* equal-work path that hides an unknown identifier has to run on the
|
|
234
|
+
* submitted password as-is. A violation — including a non-string password,
|
|
235
|
+
* which the hasher would otherwise `String`-coerce — throws
|
|
236
|
+
* {@link IdentityError} `weak_password`.
|
|
237
|
+
*/
|
|
238
|
+
passwordPolicy?: PasswordPolicy;
|
|
239
|
+
/**
|
|
240
|
+
* Auth-flow rate limiter. When set, {@link IdentityService.signIn} throttles
|
|
241
|
+
* by identifier — an unknown and a known identifier throttle identically, so
|
|
242
|
+
* the limit can't be used to enumerate accounts — throws
|
|
243
|
+
* {@link IdentityError} `rate_limited` (with `retryAfterMs`) when exceeded,
|
|
244
|
+
* and resets the counter on a successful sign-in. Every other flow throttles
|
|
245
|
+
* on the same limiter with its own key: the email-sending requests
|
|
246
|
+
* ({@link IdentityService.requestPasswordReset} per case-folded email,
|
|
247
|
+
* {@link IdentityService.requestEmailVerification} and
|
|
248
|
+
* {@link IdentityService.requestAccountUnlock} per user) and the passwordless
|
|
249
|
+
* flows (`pwless:<email>` for sign-in links, `otp:signin:<email>` for sign-in
|
|
250
|
+
* codes — request and verify share the key; a successful verify resets it),
|
|
251
|
+
* so none of them can drive unbounded sends. IP-based limiting stays a
|
|
252
|
+
* route-layer concern: the service has no request context, which is the
|
|
253
|
+
* correct seam.
|
|
254
|
+
*
|
|
255
|
+
* This is the **default** for every flow; {@link rateLimiters} overrides it
|
|
256
|
+
* per flow when one threshold cannot serve them all.
|
|
257
|
+
*/
|
|
258
|
+
rateLimiter?: RateLimiterLike;
|
|
259
|
+
/**
|
|
260
|
+
* Per-flow limiter overrides. Set one when a single threshold cannot serve
|
|
261
|
+
* every flow — a limit tuned as a sign-in floor is usually far too permissive
|
|
262
|
+
* for the four flows that make the server **send an email to a
|
|
263
|
+
* caller-supplied address** (`passwordReset`, `emailVerification`,
|
|
264
|
+
* `accountUnlock`, `signInLink`, plus `signInCode`), which typically want a
|
|
265
|
+
* handful of requests per hour rather than per 15 minutes.
|
|
266
|
+
*
|
|
267
|
+
* Each entry is a whole {@link RateLimiterLike} — build one
|
|
268
|
+
* {@link RateLimiter} per flow (they can share a {@link RateLimitStore}; the
|
|
269
|
+
* key prefixes already keep their counters disjoint) or bring your own.
|
|
270
|
+
* Entries left unset fall back to {@link rateLimiter}, so adding this option
|
|
271
|
+
* never changes an existing caller's behavior.
|
|
272
|
+
*
|
|
273
|
+
* @example Throttle the email-sending flows harder than sign-in
|
|
274
|
+
* ```ts
|
|
275
|
+
* const emailFlow = new RateLimiter({
|
|
276
|
+
* store: sharedStore,
|
|
277
|
+
* limit: 3,
|
|
278
|
+
* windowMs: 60 * 60_000,
|
|
279
|
+
* });
|
|
280
|
+
* const identity = new IdentityService<AppUser>({
|
|
281
|
+
* users: userStore,
|
|
282
|
+
* rateLimiter: new RateLimiter({ store: sharedStore }),
|
|
283
|
+
* rateLimiters: {
|
|
284
|
+
* passwordReset: emailFlow,
|
|
285
|
+
* emailVerification: emailFlow,
|
|
286
|
+
* accountUnlock: emailFlow,
|
|
287
|
+
* signInLink: emailFlow,
|
|
288
|
+
* signInCode: emailFlow,
|
|
289
|
+
* },
|
|
290
|
+
* });
|
|
291
|
+
* ```
|
|
292
|
+
*/
|
|
293
|
+
rateLimiters?: IdentityRateLimiters;
|
|
294
|
+
/**
|
|
295
|
+
* Failed-attempt account lockout. When set, {@link IdentityService.signIn}
|
|
296
|
+
* records consecutive wrong-password failures per account and — once the
|
|
297
|
+
* threshold locks the account — rejects further attempts with the same
|
|
298
|
+
* uniform, timing-equalized `null` an unknown identifier gets (no lockout
|
|
299
|
+
* oracle). A successful sign-in resets the count;
|
|
300
|
+
* {@link IdentityService.unlockAccount} clears a lock via an emailed
|
|
301
|
+
* single-use token.
|
|
302
|
+
*/
|
|
303
|
+
lockout?: AccountLockoutLike;
|
|
304
|
+
/**
|
|
305
|
+
* How the attack protections respond when tripped. `"enforce"` (the safe
|
|
306
|
+
* default) blocks: rate limiting throws and a locked account can't sign in.
|
|
307
|
+
* `"log-only"` never blocks — events still fire (with `enforced: false`) so
|
|
308
|
+
* adopters can observe thresholds before turning enforcement on.
|
|
309
|
+
*/
|
|
310
|
+
protectionMode?: "enforce" | "log-only";
|
|
311
|
+
/**
|
|
312
|
+
* Called with an {@link IdentityEvent} after each flow outcome — the seam for
|
|
313
|
+
* audit capture. Awaited, but isolated: a rejection is logged and swallowed
|
|
314
|
+
* so observability can never break the auth flow. Optional; no-op when
|
|
315
|
+
* absent.
|
|
316
|
+
*/
|
|
317
|
+
onEvent?: IdentityEventHook;
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Outcome of {@link IdentityService.verifyEmail}. `success` carries the verified
|
|
321
|
+
* user; `expired` and `invalid` are distinguished so the UI can offer to resend
|
|
322
|
+
* an expired link rather than showing one generic error.
|
|
323
|
+
*/
|
|
324
|
+
export type VerifyEmailResult = {
|
|
325
|
+
status: "success";
|
|
326
|
+
userId: string;
|
|
327
|
+
email?: string;
|
|
328
|
+
} | {
|
|
329
|
+
status: "expired";
|
|
330
|
+
} | {
|
|
331
|
+
status: "invalid";
|
|
332
|
+
};
|
|
333
|
+
/**
|
|
334
|
+
* Outcome of {@link IdentityService.consumeSignInLink}. `expired` and
|
|
335
|
+
* `invalid` are distinguished so the UI can offer a fresh link for an expired
|
|
336
|
+
* one. No session is created — do that in your app on `success`.
|
|
337
|
+
*/
|
|
338
|
+
export type ConsumeSignInLinkResult = {
|
|
339
|
+
status: "success";
|
|
340
|
+
userId: string;
|
|
341
|
+
} | {
|
|
342
|
+
status: "expired";
|
|
343
|
+
} | {
|
|
344
|
+
status: "invalid";
|
|
345
|
+
};
|
|
346
|
+
/**
|
|
347
|
+
* Outcome of {@link IdentityService.verifySignInCode}. Deliberately only
|
|
348
|
+
* `success` vs `invalid`: a known email whose code expired or hit its attempt
|
|
349
|
+
* cap collapses to `invalid`, identical to an unknown email, so the caller-
|
|
350
|
+
* facing result is not an account-enumeration oracle. The precise reason
|
|
351
|
+
* (`expired`/`locked`/`unknown_email`) is available server-side via the
|
|
352
|
+
* `signin_code.failed` event.
|
|
353
|
+
*/
|
|
354
|
+
export type VerifySignInCodeResult = {
|
|
355
|
+
status: "success";
|
|
356
|
+
userId: string;
|
|
357
|
+
} | {
|
|
358
|
+
status: "invalid";
|
|
359
|
+
};
|
|
360
|
+
/**
|
|
361
|
+
* Outcome of {@link IdentityService.unlockAccount}. `expired` and `invalid`
|
|
362
|
+
* are distinguished so the UI can offer a fresh link for an expired one.
|
|
363
|
+
*/
|
|
364
|
+
export type UnlockAccountResult = {
|
|
365
|
+
status: "success";
|
|
366
|
+
userId: string;
|
|
367
|
+
} | {
|
|
368
|
+
status: "expired";
|
|
369
|
+
} | {
|
|
370
|
+
status: "invalid";
|
|
371
|
+
};
|
|
372
|
+
/**
|
|
373
|
+
* Composable own-auth flows. Construct once with your dependencies and call the
|
|
374
|
+
* methods from your routes; the credential form, copy, and session creation stay
|
|
375
|
+
* in your app.
|
|
376
|
+
*/
|
|
377
|
+
export declare class IdentityService<User extends IdentityUser> {
|
|
378
|
+
#private;
|
|
379
|
+
/** Wires the user store and optional collaborators; applies the TTL and hasher defaults. */
|
|
380
|
+
constructor(options: IdentityServiceOptions<User>);
|
|
381
|
+
/**
|
|
382
|
+
* Create a user with a hashed password. `profile` is your app's own fields.
|
|
383
|
+
*
|
|
384
|
+
* @throws {IdentityError} `weak_password` when `password` fails the password
|
|
385
|
+
* policy — including the 8–256 character defaults that apply when
|
|
386
|
+
* {@link IdentityServiceOptions.passwordPolicy} is omitted, and a non-string
|
|
387
|
+
* password (the hash would `String`-coerce it).
|
|
388
|
+
*/
|
|
389
|
+
signUp(input: {
|
|
390
|
+
password: string;
|
|
391
|
+
profile: Record<string, unknown>;
|
|
392
|
+
}): Promise<User>;
|
|
393
|
+
/**
|
|
394
|
+
* Verify credentials; returns the user, or `null` for an unknown identifier or
|
|
395
|
+
* wrong password (don't reveal which). Establishing a session is your app's
|
|
396
|
+
* job — do it on a non-null result.
|
|
397
|
+
*
|
|
398
|
+
* The throttle window is keyed on the identifier as submitted (trimmed, not
|
|
399
|
+
* case-folded), so pass the same normalized form you resolve accounts by —
|
|
400
|
+
* otherwise casing variants of one account each get their own window, and
|
|
401
|
+
* the identifiers you later hand {@link IdentityService.resetSignInThrottle}
|
|
402
|
+
* won't match the ones that filled it.
|
|
403
|
+
*
|
|
404
|
+
* Every rejection is held to
|
|
405
|
+
* {@link IdentityServiceOptions.failedSignInFloorMs} (250 ms by default),
|
|
406
|
+
* measured from entry, so an unknown identifier, a locked account, a wrong
|
|
407
|
+
* native password, and a wrong password against an imported hash all take the
|
|
408
|
+
* same time — the branch that runs a foreign KDF is not visible from
|
|
409
|
+
* outside. A success returns as soon as it is done.
|
|
410
|
+
*
|
|
411
|
+
* @throws {IdentityError} `rate_limited` (with `retryAfterMs`) when a
|
|
412
|
+
* `rateLimiter` is configured, the identifier's limit is hit, and
|
|
413
|
+
* `protectionMode` is `"enforce"`. Rate-limit rejections throw before the
|
|
414
|
+
* account lookup and are not held to the floor.
|
|
415
|
+
*/
|
|
416
|
+
signIn(input: {
|
|
417
|
+
identifier: string;
|
|
418
|
+
password: string;
|
|
419
|
+
}): Promise<User | null>;
|
|
420
|
+
/**
|
|
421
|
+
* Clear the sign-in rate-limit window for one identifier. Call it after a
|
|
422
|
+
* flow that proves account ownership (account unlock, password reset) with
|
|
423
|
+
* each identifier the user signs in with — otherwise the failed attempts
|
|
424
|
+
* that caused the lockout still fill the window and the user's next sign-in
|
|
425
|
+
* is rejected as rate-limited despite being unlocked. No-op without a
|
|
426
|
+
* `rateLimiter`.
|
|
427
|
+
*/
|
|
428
|
+
resetSignInThrottle(identifier: string): Promise<void>;
|
|
429
|
+
/**
|
|
430
|
+
* Enumeration-safe: always resolves or throws identically whether or not the
|
|
431
|
+
* email maps to a user. If it does, mints a single-use reset token and calls
|
|
432
|
+
* `delivery.sendPasswordReset`. With a `rateLimiter`, requests throttle per
|
|
433
|
+
* target email, case-folded so casing variants share one window — the check
|
|
434
|
+
* runs before the lookup, so a 429 reveals nothing —
|
|
435
|
+
* and throw {@link IdentityError} `rate_limited` when exceeded; catch it and
|
|
436
|
+
* return your uniform success response. Give it a tighter threshold than
|
|
437
|
+
* sign-in via `rateLimiters.passwordReset`.
|
|
438
|
+
*/
|
|
439
|
+
requestPasswordReset(email: string): Promise<void>;
|
|
440
|
+
/**
|
|
441
|
+
* Consume a reset token, set the new password, and (if a `sessions` service
|
|
442
|
+
* was configured) revoke the user's other sessions. Clears any failed-attempt
|
|
443
|
+
* lockout — the emailed token proves the same account ownership the unlock
|
|
444
|
+
* flow does. Returns `{ userId }`, or `null` for an invalid/expired/used
|
|
445
|
+
* token.
|
|
446
|
+
*
|
|
447
|
+
* A reset is the user's "lock everyone else out" action, so the outstanding
|
|
448
|
+
* passwordless credentials for the same account are voided too: sign-in links
|
|
449
|
+
* (needs a token store with `deleteBySubject` — without it they stay
|
|
450
|
+
* redeemable until they expire) and, when an `otp` service is configured, the
|
|
451
|
+
* pending sign-in code. Neither can fail the reset: a store that throws is
|
|
452
|
+
* logged and the reset still completes, since the password has already
|
|
453
|
+
* changed by then.
|
|
454
|
+
*
|
|
455
|
+
* Sessions are revoked **after** the password is changed. If that revocation
|
|
456
|
+
* throws, the reset is reported failed — a `password_reset.failed`
|
|
457
|
+
* (`session_revocation_failed`) event fires, no `password_reset.completed`
|
|
458
|
+
* fires, and the error rethrows — rather than silently returning success with
|
|
459
|
+
* the user's old sessions still live. Retry the reset to clear them.
|
|
460
|
+
*
|
|
461
|
+
* @throws {IdentityError} `weak_password` when `password` fails the password
|
|
462
|
+
* policy — including the 8–256 character defaults that apply when
|
|
463
|
+
* {@link IdentityServiceOptions.passwordPolicy} is omitted. The token is left
|
|
464
|
+
* unconsumed, so the user's link still works.
|
|
465
|
+
*/
|
|
466
|
+
resetPassword(input: {
|
|
467
|
+
token: string;
|
|
468
|
+
password: string;
|
|
469
|
+
}): Promise<{
|
|
470
|
+
userId: string;
|
|
471
|
+
} | null>;
|
|
472
|
+
/**
|
|
473
|
+
* Mint + deliver an email-verification link for a user. Typically called
|
|
474
|
+
* right after sign-up, or from an authenticated "resend" action — not exposed
|
|
475
|
+
* as an unauthenticated route (you'd be emailing arbitrary user ids). With a
|
|
476
|
+
* `rateLimiter` (or a tighter `rateLimiters.emailVerification`), requests
|
|
477
|
+
* throttle per user and throw {@link IdentityError} `rate_limited` when
|
|
478
|
+
* exceeded — surface it as a friendly cooldown.
|
|
479
|
+
*/
|
|
480
|
+
requestEmailVerification(input: {
|
|
481
|
+
userId: string;
|
|
482
|
+
email: string;
|
|
483
|
+
}): Promise<void>;
|
|
484
|
+
/**
|
|
485
|
+
* Consume a verification token and mark the user's email verified.
|
|
486
|
+
*
|
|
487
|
+
* Returns a discriminated {@link VerifyEmailResult} so the UI can tell an
|
|
488
|
+
* **expired** link (offer to resend) from a generically **invalid** one — the
|
|
489
|
+
* `inspect()` distinction, surfaced through the flow. A `valid` token is
|
|
490
|
+
* consumed; a lost race (consumed between inspect and consume) reports
|
|
491
|
+
* `invalid`.
|
|
492
|
+
*
|
|
493
|
+
* The email is marked verified **before** the token is consumed, so a throw
|
|
494
|
+
* from `markEmailVerified` leaves the single-use link unspent and the user
|
|
495
|
+
* can retry with the same link (`markEmailVerified` must be idempotent).
|
|
496
|
+
*
|
|
497
|
+
* The address the token was issued for is passed to
|
|
498
|
+
* {@link IdentityUserStore.markEmailVerified} alongside the user id, so a
|
|
499
|
+
* store that predicates on both never verifies an address the link was not
|
|
500
|
+
* minted for.
|
|
501
|
+
*/
|
|
502
|
+
verifyEmail(token: string): Promise<VerifyEmailResult>;
|
|
503
|
+
/**
|
|
504
|
+
* Mint + deliver a single-use account-unlock link after a lockout. Call it
|
|
505
|
+
* with the locked account's email (typically from your `lockout` event
|
|
506
|
+
* handler — the service only knows opaque user ids). Requires `tokens` and
|
|
507
|
+
* a `delivery.sendAccountUnlock` hook to actually send. With a `rateLimiter`
|
|
508
|
+
* (or a tighter `rateLimiters.accountUnlock`), requests throttle per user and
|
|
509
|
+
* throw {@link IdentityError} `rate_limited` when exceeded.
|
|
510
|
+
*/
|
|
511
|
+
requestAccountUnlock(input: {
|
|
512
|
+
userId: string;
|
|
513
|
+
email: string;
|
|
514
|
+
}): Promise<void>;
|
|
515
|
+
/**
|
|
516
|
+
* Consume an unlock token and clear the account's lockout state (failures
|
|
517
|
+
* and lock). The self-service unlock path: the emailed link lands here.
|
|
518
|
+
*/
|
|
519
|
+
unlockAccount(token: string): Promise<UnlockAccountResult>;
|
|
520
|
+
/**
|
|
521
|
+
* Passwordless request: always resolves with the same `void` result. If the
|
|
522
|
+
* email maps to a user, mints a single-use sign-in token (invalidating any
|
|
523
|
+
* outstanding one) and calls `delivery.sendSignInLink`; an unknown email does
|
|
524
|
+
* neither, so the **return value** never reveals whether an account exists.
|
|
525
|
+
* The synchronous path is not fully timing-uniform, though — only the
|
|
526
|
+
* known-email branch awaits token minting and delivery — so send email from a
|
|
527
|
+
* background queue and apply IP-level throttling at the route to close the
|
|
528
|
+
* residual latency/side-channel oracle. Throttled per email (case-folded,
|
|
529
|
+
* key `pwless:<email>`) when a `rateLimiter` — or a tighter
|
|
530
|
+
* `rateLimiters.signInLink` — is configured; throws {@link IdentityError}
|
|
531
|
+
* `rate_limited` in enforce mode. Default link lifetime is 15 minutes.
|
|
532
|
+
*/
|
|
533
|
+
requestSignInLink(email: string, options?: {
|
|
534
|
+
ttlMs?: number;
|
|
535
|
+
}): Promise<void>;
|
|
536
|
+
/**
|
|
537
|
+
* Consume a sign-in link token (single-use). `success` carries the `userId`
|
|
538
|
+
* to sign in; establishing the session — and running your MFA gate first —
|
|
539
|
+
* stays your app's job. So does re-resolving the user: a user deleted or
|
|
540
|
+
* disabled since the link was minted must be treated as `invalid` by your
|
|
541
|
+
* user store, not signed in. `expired` and `invalid` are distinguished so
|
|
542
|
+
* the UI can offer a fresh link.
|
|
543
|
+
*/
|
|
544
|
+
consumeSignInLink(token: string): Promise<ConsumeSignInLinkResult>;
|
|
545
|
+
/**
|
|
546
|
+
* Passwordless request: always resolves with the same `void` result. If the
|
|
547
|
+
* email maps to a user, mints a one-time numeric code (invalidating any
|
|
548
|
+
* outstanding one) and calls `delivery.sendSignInCode`; an unknown email does
|
|
549
|
+
* neither, so the **return value** never reveals whether an account exists.
|
|
550
|
+
* As with {@link IdentityService.requestSignInLink}, only the known-email
|
|
551
|
+
* branch awaits code minting and delivery, so send from a background queue
|
|
552
|
+
* and throttle by IP at the route to close the residual timing oracle.
|
|
553
|
+
* Requires the `otp` option. Throttled per email (case-folded, key
|
|
554
|
+
* `otp:signin:<email>`, shared with {@link IdentityService.verifySignInCode})
|
|
555
|
+
* when a `rateLimiter` — or a tighter `rateLimiters.signInCode` — is
|
|
556
|
+
* configured; throws {@link IdentityError} `rate_limited` in enforce mode.
|
|
557
|
+
* That is the flow's only throttle, and it runs before the lookup, so a
|
|
558
|
+
* known email can never be rate-limited on a request an unknown one survives.
|
|
559
|
+
* A throwing `sendSignInCode` is trapped like the link hooks (see
|
|
560
|
+
* {@link DeliveryHooks}): that exact code is invalidated — never a newer one a
|
|
561
|
+
* retry may have delivered meanwhile — a `delivery.failed` event is emitted,
|
|
562
|
+
* and the call still resolves `void`, so a mailer outage is not an enumeration
|
|
563
|
+
* oracle either.
|
|
564
|
+
*/
|
|
565
|
+
requestSignInCode(email: string): Promise<void>;
|
|
566
|
+
/**
|
|
567
|
+
* Verify a one-time sign-in code. An unknown email performs the same hash
|
|
568
|
+
* work as a known one and reports plain `invalid` — the result is not an
|
|
569
|
+
* enumeration oracle. Wrong tries spend the code's attempt budget (`locked`
|
|
570
|
+
* once it runs out); success consumes the code (single-use), resets the
|
|
571
|
+
* flow's throttle window, and carries the `userId` to sign in — session
|
|
572
|
+
* creation and your MFA gate stay your app's job, as does treating a
|
|
573
|
+
* since-deleted/disabled user as a failed sign-in.
|
|
574
|
+
*/
|
|
575
|
+
verifySignInCode(input: {
|
|
576
|
+
email: string;
|
|
577
|
+
code: string;
|
|
578
|
+
}): Promise<VerifySignInCodeResult>;
|
|
579
|
+
}
|