@udibo/oauth2 0.14.0 → 0.15.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/README.md +45 -24
- package/{esm → dist}/adapters/hono/_common.d.ts +5 -5
- package/{esm → dist}/adapters/hono/_common.js +1 -1
- package/{esm → dist}/adapters/hono/authorization-server.d.ts +6 -7
- package/{esm → dist}/adapters/hono/authorization-server.js +0 -1
- package/{esm → dist}/adapters/hono/bff/auth-request-store.d.ts +2 -2
- package/{esm → dist}/adapters/hono/bff/auth-request-store.js +2 -2
- package/{esm → dist}/adapters/hono/bff/bff.d.ts +22 -12
- package/{esm → dist}/adapters/hono/bff/bff.js +35 -29
- package/{esm → dist}/adapters/hono/bff/cookie-prefix.js +1 -2
- package/{esm → dist}/adapters/hono/bff/mod.d.ts +5 -6
- package/{esm → dist}/adapters/hono/bff/mod.js +1 -2
- package/{esm → dist}/adapters/hono/bff/proxy.js +2 -1
- package/{esm → dist}/adapters/hono/bff/session-store.d.ts +2 -2
- package/{esm → dist}/adapters/hono/bff/session-store.js +7 -6
- package/{esm → dist}/adapters/hono/bff/testing.d.ts +7 -8
- package/{esm → dist}/adapters/hono/bff/testing.js +19 -22
- package/{esm → dist}/adapters/hono/identity.d.ts +1 -2
- package/{esm → dist}/adapters/hono/identity.js +10 -10
- package/{esm → dist}/adapters/hono/log.d.ts +0 -1
- package/{esm → dist}/adapters/hono/log.js +0 -6
- package/{esm → dist}/adapters/hono/resource-server.d.ts +5 -6
- package/{esm → dist}/adapters/hono/resource-server.js +0 -1
- package/dist/cli/bin.d.ts +2 -0
- package/dist/cli/bin.js +3 -0
- package/dist/cli/idp/config.d.ts +110 -0
- package/dist/cli/idp/config.js +269 -0
- package/dist/cli/idp/mod.d.ts +20 -0
- package/dist/cli/idp/mod.js +175 -0
- package/dist/cli/idp/pages.d.ts +56 -0
- package/dist/cli/idp/pages.js +190 -0
- package/dist/cli/idp/server.d.ts +93 -0
- package/dist/cli/idp/server.js +559 -0
- package/dist/cli/keygen.d.ts +13 -0
- package/dist/cli/keygen.js +30 -0
- package/dist/cli/mod.d.ts +34 -0
- package/dist/cli/mod.js +94 -0
- package/{esm → dist}/client/_http.d.ts +1 -1
- package/{esm → dist}/client/base.d.ts +1 -1
- package/{esm → dist}/client/bff-client.d.ts +1 -1
- package/{esm → dist}/client/bff-client.js +7 -6
- package/{esm → dist}/client/browser-storage.d.ts +1 -1
- package/{esm → dist}/client/check.js +1 -1
- package/{esm → dist}/client/direct-client.d.ts +6 -6
- package/{esm → dist}/client/direct-client.js +11 -13
- package/{esm → dist}/client/discovery-cache.d.ts +1 -1
- package/{esm → dist}/client/discovery-cache.js +2 -2
- package/{esm → dist}/client/events.d.ts +1 -1
- package/{esm → dist}/client/mod.d.ts +11 -12
- package/{esm → dist}/client/mod.js +1 -1
- package/{esm → dist}/errors.d.ts +1 -1
- package/{esm → dist}/errors.js +1 -1
- package/{esm → dist}/identity/events.d.ts +1 -1
- package/{esm → dist}/identity/external/_shared.d.ts +1 -1
- package/{esm → dist}/identity/external/_shared.js +1 -2
- package/{esm → dist}/identity/external/apple-client-secret.d.ts +1 -1
- package/{esm → dist}/identity/external/apple-client-secret.js +6 -7
- package/{esm → dist}/identity/external/apple.d.ts +5 -5
- package/{esm → dist}/identity/external/apple.js +10 -11
- package/{esm → dist}/identity/external/discord.d.ts +3 -3
- package/{esm → dist}/identity/external/discord.js +2 -2
- package/{esm → dist}/identity/external/flow.d.ts +1 -1
- package/{esm → dist}/identity/external/flow.js +2 -2
- package/{esm → dist}/identity/external/github.d.ts +3 -3
- package/{esm → dist}/identity/external/github.js +12 -17
- package/{esm → dist}/identity/external/google.d.ts +5 -5
- package/{esm → dist}/identity/external/google.js +2 -2
- package/{esm → dist}/identity/external/mod.d.ts +12 -13
- package/{esm → dist}/identity/external/mod.js +0 -1
- package/{esm → dist}/identity/external/oauth2.d.ts +1 -1
- package/{esm → dist}/identity/external/oauth2.js +8 -8
- package/{esm → dist}/identity/external/oidc.d.ts +4 -4
- package/{esm → dist}/identity/external/oidc.js +9 -8
- package/{esm → dist}/identity/hibp.d.ts +1 -1
- package/{esm → dist}/identity/hibp.js +1 -1
- package/{esm → dist}/identity/lockout.js +1 -2
- package/{esm → dist}/identity/mfa/mod.d.ts +4 -5
- package/{esm → dist}/identity/mfa/mod.js +1 -2
- package/{esm → dist}/identity/mfa/service.d.ts +3 -3
- package/{esm → dist}/identity/mfa/service.js +4 -3
- package/{esm → dist}/identity/mfa/totp.d.ts +1 -1
- package/{esm → dist}/identity/mfa/totp.js +2 -2
- package/{esm → dist}/identity/migration.d.ts +2 -3
- package/{esm → dist}/identity/migration.js +3 -4
- package/{esm → dist}/identity/mod.d.ts +14 -15
- package/{esm → dist}/identity/mod.js +0 -1
- package/{esm → dist}/identity/otp.d.ts +1 -1
- package/{esm → dist}/identity/otp.js +4 -2
- package/{esm → dist}/identity/password.js +8 -5
- package/{esm → dist}/identity/rate-limit.d.ts +1 -1
- package/{esm → dist}/identity/service.d.ts +10 -10
- package/{esm → dist}/identity/service.js +17 -18
- package/{esm → dist}/identity/session.js +2 -2
- package/{esm → dist}/identity/token-flow.js +3 -2
- package/{esm → dist}/models/authorization-code.d.ts +3 -3
- package/{esm → dist}/models/authorization.d.ts +1 -1
- package/{esm → dist}/models/authorization.js +1 -2
- package/{esm → dist}/models/device-authorization.d.ts +2 -2
- package/{esm → dist}/models/token.d.ts +3 -3
- package/{esm → dist}/react/callback.d.ts +1 -1
- package/{esm → dist}/react/callback.js +0 -1
- package/{esm → dist}/react/components/class-names.d.ts +1 -1
- package/{esm → dist}/react/components/class-names.js +3 -2
- package/{esm → dist}/react/components/internal.d.ts +2 -2
- package/{esm → dist}/react/components/internal.js +5 -11
- package/{esm → dist}/react/components/mfa-challenge-form.d.ts +2 -2
- package/{esm → dist}/react/components/mfa-challenge-form.js +4 -6
- package/{esm → dist}/react/components/mfa-enrollment-form.d.ts +2 -2
- package/{esm → dist}/react/components/mfa-enrollment-form.js +2 -6
- package/{esm → dist}/react/components/mod.d.ts +20 -21
- package/{esm → dist}/react/components/mod.js +1 -2
- package/{esm → dist}/react/components/request-password-reset-form.d.ts +2 -2
- package/{esm → dist}/react/components/request-password-reset-form.js +1 -3
- package/{esm → dist}/react/components/reset-password-form.d.ts +2 -2
- package/{esm → dist}/react/components/reset-password-form.js +2 -2
- package/{esm → dist}/react/components/sign-in-form.d.ts +2 -2
- package/{esm → dist}/react/components/sign-in-form.js +1 -5
- package/{esm → dist}/react/components/sign-up-form.d.ts +2 -2
- package/{esm → dist}/react/components/sign-up-form.js +4 -10
- package/{esm → dist}/react/components/types.d.ts +2 -2
- package/{esm → dist}/react/components/use-auth-form.d.ts +3 -3
- package/{esm → dist}/react/components/use-auth-form.js +4 -7
- package/{esm → dist}/react/components/user-menu.d.ts +2 -2
- package/{esm → dist}/react/components/user-menu.js +9 -14
- package/{esm → dist}/react/context.d.ts +1 -1
- package/{esm → dist}/react/mod.d.ts +6 -7
- package/{esm → dist}/react/mod.js +0 -1
- package/{esm → dist}/react/provider.d.ts +2 -2
- package/{esm → dist}/react/require-auth.js +1 -3
- package/{esm → dist}/react/testing.d.ts +2 -3
- package/{esm → dist}/react/testing.js +1 -2
- package/{esm → dist}/react/use-authorization.d.ts +1 -1
- package/{esm → dist}/react/use-oauth2.d.ts +8 -1
- package/{esm → dist}/react/use-oauth2.js +0 -1
- package/{esm → dist}/server/_clock-skew.js +2 -1
- package/{esm → dist}/server/authorization-server.d.ts +40 -41
- package/{esm → dist}/server/authorization-server.js +32 -42
- package/{esm → dist}/server/client-authentication.d.ts +2 -2
- package/{esm → dist}/server/grants/authorization-code.d.ts +9 -9
- package/{esm → dist}/server/grants/authorization-code.js +5 -6
- package/{esm → dist}/server/grants/client-credentials.d.ts +4 -4
- package/{esm → dist}/server/grants/device-authorization.d.ts +6 -6
- package/{esm → dist}/server/grants/device-authorization.js +4 -4
- package/{esm → dist}/server/grants/grant.d.ts +7 -7
- package/{esm → dist}/server/grants/grant.js +7 -7
- package/{esm → dist}/server/grants/password.d.ts +5 -5
- package/{esm → dist}/server/grants/refresh-token.d.ts +4 -4
- package/{esm → dist}/server/grants/refresh-token.js +5 -6
- package/{esm → dist}/server/introspection-token-reader.d.ts +5 -5
- package/{esm → dist}/server/introspection-token-reader.js +4 -4
- package/{esm → dist}/server/jwks-token-reader.d.ts +5 -5
- package/{esm → dist}/server/jwks-token-reader.js +4 -4
- package/{esm → dist}/server/mod.d.ts +12 -13
- package/{esm → dist}/server/mod.js +0 -1
- package/{esm → dist}/server/public-suffix/mod.d.ts +2 -3
- package/{esm → dist}/server/public-suffix/mod.js +1 -2
- package/{esm → dist}/server/public-suffix/rules.d.ts +1 -1
- package/{esm → dist}/server/public-suffix/rules.js +1 -1
- package/{esm → dist}/server/redirect-uri.js +5 -4
- package/{esm → dist}/server/resource-server.d.ts +11 -12
- package/{esm → dist}/server/resource-server.js +12 -14
- package/{esm → dist}/server/services/authorization-code.d.ts +5 -5
- package/{esm → dist}/server/services/client.d.ts +1 -1
- package/{esm → dist}/server/services/device-authorization.d.ts +3 -3
- package/{esm → dist}/server/services/token.d.ts +6 -6
- package/{esm → dist}/server/services/token.js +3 -1
- package/{esm → dist}/server/services/user.d.ts +1 -1
- package/{esm → dist}/server/signing-keys.d.ts +1 -1
- package/{esm → dist}/server/signing-keys.js +8 -2
- package/{esm → dist}/server/utils/hash.js +1 -1
- package/{esm → dist}/testing/contract/auth-request-storage.d.ts +1 -1
- package/{esm → dist}/testing/contract/auth-request-storage.js +24 -29
- package/{esm → dist}/testing/contract/authorization-code.d.ts +6 -6
- package/{esm → dist}/testing/contract/authorization-code.js +16 -17
- package/{esm → dist}/testing/contract/client.d.ts +4 -4
- package/{esm → dist}/testing/contract/client.js +14 -15
- package/{esm → dist}/testing/contract/device-authorization.d.ts +6 -6
- package/{esm → dist}/testing/contract/device-authorization.js +37 -39
- package/{esm → dist}/testing/contract/identity-session.d.ts +6 -1
- package/{esm → dist}/testing/contract/identity-session.js +44 -36
- package/{esm → dist}/testing/contract/identity-user-store.d.ts +8 -2
- package/{esm → dist}/testing/contract/identity-user-store.js +48 -46
- package/{esm → dist}/testing/contract/lockout-store.d.ts +1 -1
- package/{esm → dist}/testing/contract/lockout-store.js +23 -24
- package/{esm → dist}/testing/contract/mfa-store.d.ts +1 -1
- package/{esm → dist}/testing/contract/mfa-store.js +53 -54
- package/{esm → dist}/testing/contract/mod.d.ts +15 -16
- package/{esm → dist}/testing/contract/mod.js +0 -1
- package/{esm → dist}/testing/contract/otp-store.d.ts +1 -1
- package/{esm → dist}/testing/contract/otp-store.js +33 -34
- package/{esm → dist}/testing/contract/rate-limit-store.d.ts +1 -1
- package/{esm → dist}/testing/contract/rate-limit-store.js +15 -16
- package/{esm → dist}/testing/contract/tenant.js +361 -330
- package/{esm → dist}/testing/contract/token-flow-store.d.ts +1 -1
- package/{esm → dist}/testing/contract/token-flow-store.js +27 -32
- package/{esm → dist}/testing/contract/token-reader.d.ts +3 -3
- package/{esm → dist}/testing/contract/token-reader.js +6 -7
- package/{esm → dist}/testing/contract/token.d.ts +7 -7
- package/{esm → dist}/testing/contract/token.js +48 -53
- package/{esm → dist}/testing/contract/user.d.ts +2 -2
- package/{esm → dist}/testing/contract/user.js +8 -9
- package/{esm → dist}/testing/mod.d.ts +5 -6
- package/{esm → dist}/testing/mod.js +2 -3
- package/{esm → dist}/testing/server.d.ts +7 -6
- package/{esm → dist}/testing/server.js +2 -1
- package/{esm → dist}/testing/services.d.ts +11 -11
- package/{esm → dist}/testing/services.js +8 -8
- package/{esm → dist}/testing/tenant.d.ts +11 -7
- package/{esm → dist}/testing/tenant.js +109 -94
- package/dist/utils/_constant-time.d.ts +12 -0
- package/dist/utils/_constant-time.js +19 -0
- package/{esm → dist}/utils/_default-fetch.js +4 -1
- package/dist/utils/_delay.d.ts +13 -0
- package/dist/utils/_delay.js +28 -0
- package/dist/utils/_encoding.d.ts +39 -0
- package/dist/utils/_encoding.js +126 -0
- package/{esm → dist}/utils/authentication-context.d.ts +1 -1
- package/{esm → dist}/utils/crypto.d.ts +2 -3
- package/{esm → dist}/utils/crypto.js +3 -4
- package/{esm → dist}/utils/pkce.js +1 -1
- package/{esm → dist}/utils/text.js +1 -1
- package/{esm → dist}/utils/url.d.ts +0 -1
- package/{esm → dist}/utils/url.js +0 -1
- package/package.json +156 -33
- package/esm/_dnt.polyfills.d.ts +0 -31
- package/esm/_dnt.polyfills.js +0 -26
- package/esm/deps/jsr.io/@std/assert/1.0.19/almost_equals.d.ts +0 -29
- package/esm/deps/jsr.io/@std/assert/1.0.19/almost_equals.js +0 -47
- package/esm/deps/jsr.io/@std/assert/1.0.19/array_includes.d.ts +0 -28
- package/esm/deps/jsr.io/@std/assert/1.0.19/array_includes.js +0 -65
- package/esm/deps/jsr.io/@std/assert/1.0.19/assert.d.ts +0 -16
- package/esm/deps/jsr.io/@std/assert/1.0.19/assert.js +0 -23
- package/esm/deps/jsr.io/@std/assert/1.0.19/assertion_error.d.ts +0 -25
- package/esm/deps/jsr.io/@std/assert/1.0.19/assertion_error.js +0 -30
- package/esm/deps/jsr.io/@std/assert/1.0.19/equal.d.ts +0 -24
- package/esm/deps/jsr.io/@std/assert/1.0.19/equal.js +0 -193
- package/esm/deps/jsr.io/@std/assert/1.0.19/equals.d.ts +0 -36
- package/esm/deps/jsr.io/@std/assert/1.0.19/equals.js +0 -61
- package/esm/deps/jsr.io/@std/assert/1.0.19/exists.d.ts +0 -17
- package/esm/deps/jsr.io/@std/assert/1.0.19/exists.js +0 -27
- package/esm/deps/jsr.io/@std/assert/1.0.19/fail.d.ts +0 -14
- package/esm/deps/jsr.io/@std/assert/1.0.19/fail.js +0 -20
- package/esm/deps/jsr.io/@std/assert/1.0.19/false.d.ts +0 -18
- package/esm/deps/jsr.io/@std/assert/1.0.19/false.js +0 -23
- package/esm/deps/jsr.io/@std/assert/1.0.19/greater.d.ts +0 -19
- package/esm/deps/jsr.io/@std/assert/1.0.19/greater.js +0 -29
- package/esm/deps/jsr.io/@std/assert/1.0.19/greater_or_equal.d.ts +0 -19
- package/esm/deps/jsr.io/@std/assert/1.0.19/greater_or_equal.js +0 -29
- package/esm/deps/jsr.io/@std/assert/1.0.19/instance_of.d.ts +0 -22
- package/esm/deps/jsr.io/@std/assert/1.0.19/instance_of.js +0 -52
- package/esm/deps/jsr.io/@std/assert/1.0.19/is_error.d.ts +0 -24
- package/esm/deps/jsr.io/@std/assert/1.0.19/is_error.js +0 -53
- package/esm/deps/jsr.io/@std/assert/1.0.19/less.d.ts +0 -18
- package/esm/deps/jsr.io/@std/assert/1.0.19/less.js +0 -28
- package/esm/deps/jsr.io/@std/assert/1.0.19/less_or_equal.d.ts +0 -19
- package/esm/deps/jsr.io/@std/assert/1.0.19/less_or_equal.js +0 -29
- package/esm/deps/jsr.io/@std/assert/1.0.19/match.d.ts +0 -17
- package/esm/deps/jsr.io/@std/assert/1.0.19/match.js +0 -26
- package/esm/deps/jsr.io/@std/assert/1.0.19/mod.d.ts +0 -43
- package/esm/deps/jsr.io/@std/assert/1.0.19/mod.js +0 -45
- package/esm/deps/jsr.io/@std/assert/1.0.19/not_equals.d.ts +0 -26
- package/esm/deps/jsr.io/@std/assert/1.0.19/not_equals.js +0 -39
- package/esm/deps/jsr.io/@std/assert/1.0.19/not_instance_of.d.ts +0 -19
- package/esm/deps/jsr.io/@std/assert/1.0.19/not_instance_of.js +0 -29
- package/esm/deps/jsr.io/@std/assert/1.0.19/not_match.d.ts +0 -17
- package/esm/deps/jsr.io/@std/assert/1.0.19/not_match.js +0 -26
- package/esm/deps/jsr.io/@std/assert/1.0.19/not_strict_equals.d.ts +0 -22
- package/esm/deps/jsr.io/@std/assert/1.0.19/not_strict_equals.js +0 -32
- package/esm/deps/jsr.io/@std/assert/1.0.19/object_match.d.ts +0 -28
- package/esm/deps/jsr.io/@std/assert/1.0.19/object_match.js +0 -177
- package/esm/deps/jsr.io/@std/assert/1.0.19/rejects.d.ts +0 -41
- package/esm/deps/jsr.io/@std/assert/1.0.19/rejects.js +0 -53
- package/esm/deps/jsr.io/@std/assert/1.0.19/strict_equals.d.ts +0 -23
- package/esm/deps/jsr.io/@std/assert/1.0.19/strict_equals.js +0 -58
- package/esm/deps/jsr.io/@std/assert/1.0.19/string_includes.d.ts +0 -17
- package/esm/deps/jsr.io/@std/assert/1.0.19/string_includes.js +0 -26
- package/esm/deps/jsr.io/@std/assert/1.0.19/throws.d.ts +0 -44
- package/esm/deps/jsr.io/@std/assert/1.0.19/throws.js +0 -44
- package/esm/deps/jsr.io/@std/assert/1.0.19/unimplemented.d.ts +0 -14
- package/esm/deps/jsr.io/@std/assert/1.0.19/unimplemented.js +0 -20
- package/esm/deps/jsr.io/@std/assert/1.0.19/unreachable.d.ts +0 -14
- package/esm/deps/jsr.io/@std/assert/1.0.19/unreachable.js +0 -20
- package/esm/deps/jsr.io/@std/async/1.5.0/delay.d.ts +0 -44
- package/esm/deps/jsr.io/@std/async/1.5.0/delay.js +0 -84
- package/esm/deps/jsr.io/@std/crypto/1.1.0/timing_safe_equal.d.ts +0 -39
- package/esm/deps/jsr.io/@std/crypto/1.1.0/timing_safe_equal.js +0 -61
- package/esm/deps/jsr.io/@std/encoding/1.0.11/_common16.d.ts +0 -22
- package/esm/deps/jsr.io/@std/encoding/1.0.11/_common16.js +0 -51
- package/esm/deps/jsr.io/@std/encoding/1.0.11/_common32.d.ts +0 -34
- package/esm/deps/jsr.io/@std/encoding/1.0.11/_common32.js +0 -192
- package/esm/deps/jsr.io/@std/encoding/1.0.11/_common64.d.ts +0 -34
- package/esm/deps/jsr.io/@std/encoding/1.0.11/_common64.js +0 -113
- package/esm/deps/jsr.io/@std/encoding/1.0.11/_common_detach.d.ts +0 -3
- package/esm/deps/jsr.io/@std/encoding/1.0.11/_common_detach.js +0 -13
- package/esm/deps/jsr.io/@std/encoding/1.0.11/_types.d.ts +0 -8
- package/esm/deps/jsr.io/@std/encoding/1.0.11/_types.js +0 -2
- package/esm/deps/jsr.io/@std/encoding/1.0.11/_validate_binary_like.d.ts +0 -1
- package/esm/deps/jsr.io/@std/encoding/1.0.11/_validate_binary_like.js +0 -26
- package/esm/deps/jsr.io/@std/encoding/1.0.11/ascii85.d.ts +0 -60
- package/esm/deps/jsr.io/@std/encoding/1.0.11/ascii85.js +0 -152
- package/esm/deps/jsr.io/@std/encoding/1.0.11/base32.d.ts +0 -39
- package/esm/deps/jsr.io/@std/encoding/1.0.11/base32.js +0 -87
- package/esm/deps/jsr.io/@std/encoding/1.0.11/base58.d.ts +0 -39
- package/esm/deps/jsr.io/@std/encoding/1.0.11/base58.js +0 -131
- package/esm/deps/jsr.io/@std/encoding/1.0.11/base64.d.ts +0 -39
- package/esm/deps/jsr.io/@std/encoding/1.0.11/base64.js +0 -82
- package/esm/deps/jsr.io/@std/encoding/1.0.11/base64url.d.ts +0 -39
- package/esm/deps/jsr.io/@std/encoding/1.0.11/base64url.js +0 -72
- package/esm/deps/jsr.io/@std/encoding/1.0.11/hex.d.ts +0 -38
- package/esm/deps/jsr.io/@std/encoding/1.0.11/hex.js +0 -87
- package/esm/deps/jsr.io/@std/encoding/1.0.11/mod.d.ts +0 -97
- package/esm/deps/jsr.io/@std/encoding/1.0.11/mod.js +0 -99
- package/esm/deps/jsr.io/@std/encoding/1.0.11/varint.d.ts +0 -119
- package/esm/deps/jsr.io/@std/encoding/1.0.11/varint.js +0 -209
- package/esm/deps/jsr.io/@std/http/1.1.3/status.d.ts +0 -343
- package/esm/deps/jsr.io/@std/http/1.1.3/status.js +0 -343
- package/esm/deps/jsr.io/@std/internal/1.0.14/assertion_state.d.ts +0 -159
- package/esm/deps/jsr.io/@std/internal/1.0.14/assertion_state.js +0 -223
- package/esm/deps/jsr.io/@std/internal/1.0.14/build_message.d.ts +0 -79
- package/esm/deps/jsr.io/@std/internal/1.0.14/build_message.js +0 -116
- package/esm/deps/jsr.io/@std/internal/1.0.14/diff.d.ts +0 -139
- package/esm/deps/jsr.io/@std/internal/1.0.14/diff.js +0 -277
- package/esm/deps/jsr.io/@std/internal/1.0.14/diff_str.d.ts +0 -98
- package/esm/deps/jsr.io/@std/internal/1.0.14/diff_str.js +0 -181
- package/esm/deps/jsr.io/@std/internal/1.0.14/format.d.ts +0 -29
- package/esm/deps/jsr.io/@std/internal/1.0.14/format.js +0 -68
- package/esm/deps/jsr.io/@std/internal/1.0.14/styles.d.ts +0 -158
- package/esm/deps/jsr.io/@std/internal/1.0.14/styles.js +0 -206
- package/esm/deps/jsr.io/@std/internal/1.0.14/types.d.ts +0 -24
- package/esm/deps/jsr.io/@std/internal/1.0.14/types.js +0 -3
- package/esm/deps/jsr.io/@std/testing/1.0.20/_test_suite.d.ts +0 -91
- package/esm/deps/jsr.io/@std/testing/1.0.20/_test_suite.js +0 -347
- package/esm/deps/jsr.io/@std/testing/1.0.20/bdd.d.ts +0 -382
- package/esm/deps/jsr.io/@std/testing/1.0.20/bdd.js +0 -1110
- package/esm/deps/jsr.io/@udibo/http-error/0.11.1/mod.d.ts +0 -363
- package/esm/deps/jsr.io/@udibo/http-error/0.11.1/mod.js +0 -616
- package/esm/package.json +0 -3
- /package/{esm → dist}/adapters/hono/bff/cookie-prefix.d.ts +0 -0
- /package/{esm → dist}/adapters/hono/bff/proxy.d.ts +0 -0
- /package/{esm → dist}/client/_http.js +0 -0
- /package/{esm → dist}/client/base.js +0 -0
- /package/{esm → dist}/client/browser-storage.js +0 -0
- /package/{esm → dist}/client/check.d.ts +0 -0
- /package/{esm → dist}/client/events.js +0 -0
- /package/{esm → dist}/client/storage.d.ts +0 -0
- /package/{esm → dist}/client/storage.js +0 -0
- /package/{esm → dist}/identity/captcha.d.ts +0 -0
- /package/{esm → dist}/identity/captcha.js +0 -0
- /package/{esm → dist}/identity/delivery.d.ts +0 -0
- /package/{esm → dist}/identity/delivery.js +0 -0
- /package/{esm → dist}/identity/errors.d.ts +0 -0
- /package/{esm → dist}/identity/errors.js +0 -0
- /package/{esm → dist}/identity/events.js +0 -0
- /package/{esm → dist}/identity/external/_token-exchange.d.ts +0 -0
- /package/{esm → dist}/identity/external/_token-exchange.js +0 -0
- /package/{esm → dist}/identity/external/errors.d.ts +0 -0
- /package/{esm → dist}/identity/external/errors.js +0 -0
- /package/{esm → dist}/identity/external/provider.d.ts +0 -0
- /package/{esm → dist}/identity/external/provider.js +0 -0
- /package/{esm → dist}/identity/identifier.d.ts +0 -0
- /package/{esm → dist}/identity/identifier.js +0 -0
- /package/{esm → dist}/identity/lockout.d.ts +0 -0
- /package/{esm → dist}/identity/mfa/recovery-codes.d.ts +0 -0
- /package/{esm → dist}/identity/mfa/recovery-codes.js +0 -0
- /package/{esm → dist}/identity/password-policy.d.ts +0 -0
- /package/{esm → dist}/identity/password-policy.js +0 -0
- /package/{esm → dist}/identity/password.d.ts +0 -0
- /package/{esm → dist}/identity/rate-limit.js +0 -0
- /package/{esm → dist}/identity/session.d.ts +0 -0
- /package/{esm → dist}/identity/token-flow.d.ts +0 -0
- /package/{esm → dist}/models/authentication.d.ts +0 -0
- /package/{esm → dist}/models/authentication.js +0 -0
- /package/{esm → dist}/models/authorization-code.js +0 -0
- /package/{esm → dist}/models/client.d.ts +0 -0
- /package/{esm → dist}/models/client.js +0 -0
- /package/{esm → dist}/models/device-authorization.js +0 -0
- /package/{esm → dist}/models/responses.d.ts +0 -0
- /package/{esm → dist}/models/responses.js +0 -0
- /package/{esm → dist}/models/scope.d.ts +0 -0
- /package/{esm → dist}/models/scope.js +0 -0
- /package/{esm → dist}/models/token.js +0 -0
- /package/{esm → dist}/react/components/types.js +0 -0
- /package/{esm → dist}/react/context.js +0 -0
- /package/{esm → dist}/react/provider.js +0 -0
- /package/{esm → dist}/react/require-auth.d.ts +0 -0
- /package/{esm → dist}/react/use-authorization.js +0 -0
- /package/{esm → dist}/server/_clock-skew.d.ts +0 -0
- /package/{esm → dist}/server/client-authentication.js +0 -0
- /package/{esm → dist}/server/grants/client-credentials.js +0 -0
- /package/{esm → dist}/server/grants/password.js +0 -0
- /package/{esm → dist}/server/redirect-uri.d.ts +0 -0
- /package/{esm → dist}/server/services/authorization-code.js +0 -0
- /package/{esm → dist}/server/services/client.js +0 -0
- /package/{esm → dist}/server/services/device-authorization.js +0 -0
- /package/{esm → dist}/server/services/user.js +0 -0
- /package/{esm → dist}/server/utils/hash.d.ts +0 -0
- /package/{esm → dist}/testing/contract/_race.d.ts +0 -0
- /package/{esm → dist}/testing/contract/_race.js +0 -0
- /package/{esm → dist}/testing/contract/tenant.d.ts +0 -0
- /package/{esm → dist}/utils/_body.d.ts +0 -0
- /package/{esm → dist}/utils/_body.js +0 -0
- /package/{esm → dist}/utils/_buffer.d.ts +0 -0
- /package/{esm → dist}/utils/_buffer.js +0 -0
- /package/{esm → dist}/utils/_default-fetch.d.ts +0 -0
- /package/{esm → dist}/utils/_timing-safe.d.ts +0 -0
- /package/{esm → dist}/utils/_timing-safe.js +0 -0
- /package/{esm → dist}/utils/authentication-context.js +0 -0
- /package/{esm → dist}/utils/basic-auth.d.ts +0 -0
- /package/{esm → dist}/utils/basic-auth.js +0 -0
- /package/{esm → dist}/utils/pkce.d.ts +0 -0
- /package/{esm → dist}/utils/text.d.ts +0 -0
|
@@ -23,8 +23,7 @@
|
|
|
23
23
|
*
|
|
24
24
|
* @module
|
|
25
25
|
*/
|
|
26
|
-
import {
|
|
27
|
-
import { beforeEach, describe, it } from "../../deps/jsr.io/@std/testing/1.0.20/bdd.js";
|
|
26
|
+
import { beforeEach, describe, expect, it } from "vitest";
|
|
28
27
|
import { CONCURRENT_CALLERS, race } from "./_race.js";
|
|
29
28
|
const USER = "user-1";
|
|
30
29
|
const OTHER_USER = "user-2";
|
|
@@ -51,63 +50,63 @@ export function runMfaStoreContractTests(options) {
|
|
|
51
50
|
});
|
|
52
51
|
describe("getTotp / setPendingTotp", () => {
|
|
53
52
|
it("returns undefined for a user with no record", async () => {
|
|
54
|
-
|
|
53
|
+
expect(await store.getTotp(USER)).toBe(undefined);
|
|
55
54
|
});
|
|
56
55
|
it("stores a pending secret without activating it", async () => {
|
|
57
56
|
await store.setPendingTotp(USER, SECRET_A);
|
|
58
57
|
const record = await store.getTotp(USER);
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
expect(record?.pendingSecretBase32).toStrictEqual(SECRET_A);
|
|
59
|
+
expect(record?.secretBase32).toBe(undefined);
|
|
61
60
|
});
|
|
62
61
|
it("replaces a previous pending secret", async () => {
|
|
63
62
|
await store.setPendingTotp(USER, SECRET_A);
|
|
64
63
|
await store.setPendingTotp(USER, SECRET_B);
|
|
65
64
|
const record = await store.getTotp(USER);
|
|
66
|
-
|
|
65
|
+
expect(record?.pendingSecretBase32).toStrictEqual(SECRET_B);
|
|
67
66
|
});
|
|
68
67
|
it("keeps an active credential when a new enrollment starts", async () => {
|
|
69
68
|
await store.setPendingTotp(USER, SECRET_A);
|
|
70
69
|
await store.activateTotp(USER, SECRET_A, 100);
|
|
71
70
|
await store.setPendingTotp(USER, SECRET_B);
|
|
72
71
|
const record = await store.getTotp(USER);
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
72
|
+
expect(record?.secretBase32).toStrictEqual(SECRET_A);
|
|
73
|
+
expect(record?.lastStep).toStrictEqual(100);
|
|
74
|
+
expect(record?.pendingSecretBase32).toStrictEqual(SECRET_B);
|
|
76
75
|
});
|
|
77
76
|
it("keeps each user's record separate", async () => {
|
|
78
77
|
await store.setPendingTotp(USER, SECRET_A);
|
|
79
|
-
|
|
78
|
+
expect(await store.getTotp(OTHER_USER)).toBe(undefined);
|
|
80
79
|
});
|
|
81
80
|
});
|
|
82
81
|
describe("activateTotp", () => {
|
|
83
82
|
it("promotes the matching pending secret and clears it", async () => {
|
|
84
83
|
await store.setPendingTotp(USER, SECRET_A);
|
|
85
|
-
|
|
84
|
+
expect(await store.activateTotp(USER, SECRET_A, 42)).toBe(true);
|
|
86
85
|
const record = await store.getTotp(USER);
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
86
|
+
expect(record?.secretBase32).toStrictEqual(SECRET_A);
|
|
87
|
+
expect(record?.lastStep).toStrictEqual(42);
|
|
88
|
+
expect(record?.pendingSecretBase32).toBe(undefined);
|
|
90
89
|
});
|
|
91
90
|
it("refuses when the user has no pending secret", async () => {
|
|
92
|
-
|
|
93
|
-
|
|
91
|
+
expect(await store.activateTotp(USER, SECRET_A, 42)).toBe(false);
|
|
92
|
+
expect(await store.getTotp(USER)).toBe(undefined);
|
|
94
93
|
});
|
|
95
94
|
it("refuses a secret a newer enrollment replaced, changing nothing", async () => {
|
|
96
95
|
await store.setPendingTotp(USER, SECRET_A);
|
|
97
96
|
await store.setPendingTotp(USER, SECRET_B);
|
|
98
|
-
|
|
97
|
+
expect(await store.activateTotp(USER, SECRET_A, 42)).toBe(false);
|
|
99
98
|
const record = await store.getTotp(USER);
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
expect(record?.secretBase32).toBe(undefined);
|
|
100
|
+
expect(record?.pendingSecretBase32).toStrictEqual(SECRET_B);
|
|
102
101
|
});
|
|
103
102
|
it(`activates for exactly one of ${CONCURRENT_CALLERS} concurrent confirmations`, async () => {
|
|
104
103
|
await store.setPendingTotp(USER, SECRET_A);
|
|
105
104
|
const results = await race((caller) => store.activateTotp(USER, SECRET_A, 10 + caller));
|
|
106
|
-
|
|
107
|
-
"confirmation finds it already spent");
|
|
105
|
+
expect(countTrue(results), "the pending secret must be claimable once — a second concurrent " +
|
|
106
|
+
"confirmation finds it already spent").toBe(1);
|
|
108
107
|
const record = await store.getTotp(USER);
|
|
109
|
-
|
|
110
|
-
|
|
108
|
+
expect(record?.secretBase32).toStrictEqual(SECRET_A);
|
|
109
|
+
expect(record?.pendingSecretBase32).toBe(undefined);
|
|
111
110
|
});
|
|
112
111
|
it("activates for exactly one of two concurrent enrollments", async () => {
|
|
113
112
|
await store.setPendingTotp(USER, SECRET_B);
|
|
@@ -115,9 +114,9 @@ export function runMfaStoreContractTests(options) {
|
|
|
115
114
|
store.activateTotp(USER, SECRET_A, 10),
|
|
116
115
|
store.activateTotp(USER, SECRET_B, 20),
|
|
117
116
|
]);
|
|
118
|
-
|
|
117
|
+
expect(countTrue(results)).toBe(1);
|
|
119
118
|
const record = await store.getTotp(USER);
|
|
120
|
-
|
|
119
|
+
expect(record?.secretBase32, "only the secret that was actually pending may become active").toStrictEqual(SECRET_B);
|
|
121
120
|
});
|
|
122
121
|
});
|
|
123
122
|
describe("clearTotp", () => {
|
|
@@ -126,11 +125,11 @@ export function runMfaStoreContractTests(options) {
|
|
|
126
125
|
await store.activateTotp(USER, SECRET_A, 5);
|
|
127
126
|
await store.setPendingTotp(USER, SECRET_B);
|
|
128
127
|
await store.clearTotp(USER);
|
|
129
|
-
|
|
128
|
+
expect(await store.getTotp(USER)).toBe(undefined);
|
|
130
129
|
});
|
|
131
130
|
it("is a no-op for a user with no record", async () => {
|
|
132
131
|
await store.clearTotp(USER);
|
|
133
|
-
|
|
132
|
+
expect(await store.getTotp(USER)).toBe(undefined);
|
|
134
133
|
});
|
|
135
134
|
});
|
|
136
135
|
describe("advanceLastStep", () => {
|
|
@@ -139,71 +138,71 @@ export function runMfaStoreContractTests(options) {
|
|
|
139
138
|
await store.activateTotp(USER, SECRET_A, 100);
|
|
140
139
|
});
|
|
141
140
|
it("advances to a higher step", async () => {
|
|
142
|
-
|
|
143
|
-
|
|
141
|
+
expect(await store.advanceLastStep(USER, 101)).toBe(true);
|
|
142
|
+
expect((await store.getTotp(USER))?.lastStep).toStrictEqual(101);
|
|
144
143
|
});
|
|
145
144
|
it("refuses the step already spent", async () => {
|
|
146
|
-
|
|
147
|
-
|
|
145
|
+
expect(await store.advanceLastStep(USER, 100)).toBe(false);
|
|
146
|
+
expect((await store.getTotp(USER))?.lastStep).toStrictEqual(100);
|
|
148
147
|
});
|
|
149
148
|
it("refuses an earlier step, leaving the guard where it was", async () => {
|
|
150
|
-
|
|
151
|
-
|
|
149
|
+
expect(await store.advanceLastStep(USER, 99)).toBe(false);
|
|
150
|
+
expect((await store.getTotp(USER))?.lastStep).toStrictEqual(100);
|
|
152
151
|
});
|
|
153
152
|
it("refuses for a user with no record", async () => {
|
|
154
|
-
|
|
153
|
+
expect(await store.advanceLastStep(OTHER_USER, 1)).toBe(false);
|
|
155
154
|
});
|
|
156
155
|
it(`admits exactly one of ${CONCURRENT_CALLERS} concurrent claims on the same step`, async () => {
|
|
157
156
|
const results = await race(() => store.advanceLastStep(USER, 101));
|
|
158
|
-
|
|
159
|
-
|
|
157
|
+
expect(countTrue(results), "a time step is spendable once — the loser must read as a replay").toBe(1);
|
|
158
|
+
expect((await store.getTotp(USER))?.lastStep).toStrictEqual(101);
|
|
160
159
|
});
|
|
161
160
|
it("never regresses under concurrent claims on different steps", async () => {
|
|
162
161
|
await Promise.all([
|
|
163
162
|
store.advanceLastStep(USER, 105),
|
|
164
163
|
store.advanceLastStep(USER, 101),
|
|
165
164
|
]);
|
|
166
|
-
|
|
165
|
+
expect((await store.getTotp(USER))?.lastStep, "the highest accepted step must survive a concurrent lower one").toStrictEqual(105);
|
|
167
166
|
});
|
|
168
167
|
});
|
|
169
168
|
describe("recovery hashes", () => {
|
|
170
169
|
it("returns an empty list for a user with none", async () => {
|
|
171
|
-
|
|
170
|
+
expect(await store.getRecoveryHashes(USER)).toStrictEqual([]);
|
|
172
171
|
});
|
|
173
172
|
it("replaces the stored set exactly", async () => {
|
|
174
173
|
await store.setRecoveryHashes(USER, ["h1", "h2", "h3"]);
|
|
175
|
-
|
|
174
|
+
expect([...(await store.getRecoveryHashes(USER))].sort()).toStrictEqual(["h1", "h2", "h3"]);
|
|
176
175
|
await store.setRecoveryHashes(USER, ["h4"]);
|
|
177
|
-
|
|
176
|
+
expect(await store.getRecoveryHashes(USER)).toStrictEqual(["h4"]);
|
|
178
177
|
});
|
|
179
178
|
it("clears the set when given an empty list", async () => {
|
|
180
179
|
await store.setRecoveryHashes(USER, ["h1"]);
|
|
181
180
|
await store.setRecoveryHashes(USER, []);
|
|
182
|
-
|
|
181
|
+
expect(await store.getRecoveryHashes(USER)).toStrictEqual([]);
|
|
183
182
|
});
|
|
184
183
|
it("keeps each user's codes separate", async () => {
|
|
185
184
|
await store.setRecoveryHashes(USER, ["h1"]);
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
185
|
+
expect(await store.getRecoveryHashes(OTHER_USER)).toStrictEqual([]);
|
|
186
|
+
expect(await store.consumeRecoveryHash(OTHER_USER, "h1")).toBe(false);
|
|
187
|
+
expect(await store.getRecoveryHashes(USER)).toStrictEqual(["h1"]);
|
|
189
188
|
});
|
|
190
189
|
it("burns a consumed hash and refuses it afterwards", async () => {
|
|
191
190
|
await store.setRecoveryHashes(USER, ["h1", "h2"]);
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
191
|
+
expect(await store.consumeRecoveryHash(USER, "h1")).toBe(true);
|
|
192
|
+
expect(await store.consumeRecoveryHash(USER, "h1")).toBe(false);
|
|
193
|
+
expect(await store.getRecoveryHashes(USER)).toStrictEqual(["h2"]);
|
|
195
194
|
});
|
|
196
195
|
it("refuses a hash that was never stored", async () => {
|
|
197
196
|
await store.setRecoveryHashes(USER, ["h1"]);
|
|
198
|
-
|
|
199
|
-
|
|
197
|
+
expect(await store.consumeRecoveryHash(USER, "nope")).toBe(false);
|
|
198
|
+
expect(await store.getRecoveryHashes(USER)).toStrictEqual(["h1"]);
|
|
200
199
|
});
|
|
201
200
|
it(`burns a code for exactly one of ${CONCURRENT_CALLERS} concurrent redemptions`, async () => {
|
|
202
201
|
await store.setRecoveryHashes(USER, ["h1", "h2"]);
|
|
203
202
|
const results = await race(() => store.consumeRecoveryHash(USER, "h1"));
|
|
204
|
-
|
|
205
|
-
"not each be told they redeemed it");
|
|
206
|
-
|
|
203
|
+
expect(countTrue(results), "a recovery code is single-use — requests racing one code must " +
|
|
204
|
+
"not each be told they redeemed it").toBe(1);
|
|
205
|
+
expect(await store.getRecoveryHashes(USER)).toStrictEqual(["h2"]);
|
|
207
206
|
});
|
|
208
207
|
it("burns both codes when two different ones are redeemed concurrently", async () => {
|
|
209
208
|
await store.setRecoveryHashes(USER, ["h1", "h2", "h3"]);
|
|
@@ -211,8 +210,8 @@ export function runMfaStoreContractTests(options) {
|
|
|
211
210
|
store.consumeRecoveryHash(USER, "h1"),
|
|
212
211
|
store.consumeRecoveryHash(USER, "h2"),
|
|
213
212
|
]);
|
|
214
|
-
|
|
215
|
-
|
|
213
|
+
expect(countTrue(results)).toBe(2);
|
|
214
|
+
expect(await store.getRecoveryHashes(USER), "neither deletion may be lost to the other's stale read").toStrictEqual(["h3"]);
|
|
216
215
|
});
|
|
217
216
|
});
|
|
218
217
|
});
|
|
@@ -30,19 +30,18 @@
|
|
|
30
30
|
*
|
|
31
31
|
* @module
|
|
32
32
|
*/
|
|
33
|
-
|
|
34
|
-
export {
|
|
35
|
-
export { type
|
|
36
|
-
export {
|
|
37
|
-
export {
|
|
38
|
-
export { type
|
|
39
|
-
export { type
|
|
40
|
-
export { type
|
|
41
|
-
export {
|
|
42
|
-
export { type
|
|
43
|
-
export { type
|
|
44
|
-
export { type
|
|
45
|
-
export { type
|
|
46
|
-
export {
|
|
47
|
-
export { type
|
|
48
|
-
export { type IdentitySessionContractState, type ListableSessionServiceContractFixture, type ListableSessionServiceContractOptions, type RevocableSessionServiceContractFixture, type RevocableSessionServiceContractOptions, runListableSessionServiceContractTests, runRevocableSessionServiceContractTests, } from "./identity-session.js";
|
|
33
|
+
export { runUserServiceContractTests, type UserServiceContractOptions, } from "./user.ts";
|
|
34
|
+
export { type ClientServiceContractOptions, runClientServiceContractTests, } from "./client.ts";
|
|
35
|
+
export { runTokenServiceContractTests, type TokenServiceContractOptions, } from "./token.ts";
|
|
36
|
+
export { runTokenReaderContractTests, type TokenReaderContractFixture, type TokenReaderContractOptions, } from "./token-reader.ts";
|
|
37
|
+
export { type AuthorizationCodeServiceContractOptions, runAuthorizationCodeServiceContractTests, } from "./authorization-code.ts";
|
|
38
|
+
export { type DeviceAuthorizationServiceContractOptions, runDeviceAuthorizationServiceContractTests, } from "./device-authorization.ts";
|
|
39
|
+
export { type MfaStoreContractOptions, runMfaStoreContractTests, } from "./mfa-store.ts";
|
|
40
|
+
export { runTokenFlowStoreContractTests, type TokenFlowStoreContractOptions, } from "./token-flow-store.ts";
|
|
41
|
+
export { type OtpStoreContractOptions, runOtpStoreContractTests, } from "./otp-store.ts";
|
|
42
|
+
export { type RateLimitStoreContractOptions, runRateLimitStoreContractTests, } from "./rate-limit-store.ts";
|
|
43
|
+
export { type LockoutStoreContractOptions, runLockoutStoreContractTests, } from "./lockout-store.ts";
|
|
44
|
+
export { type AuthRequestStorageContractOptions, runAuthRequestStorageContractTests, } from "./auth-request-storage.ts";
|
|
45
|
+
export { runTenantContractTests, type TenantContractClient, type TenantContractFixture, type TenantContractMachineClientOptions, type TenantContractMachinePermission, type TenantContractOptions, type TenantContractProfile, type TenantContractSignInOptions, type TenantContractTokens, } from "./tenant.ts";
|
|
46
|
+
export { type IdentityUserContractProfile, type IdentityUserStoreContractFixture, type IdentityUserStoreContractOptions, runIdentityUserStoreContractTests, } from "./identity-user-store.ts";
|
|
47
|
+
export { type IdentitySessionContractState, type ListableSessionServiceContractFixture, type ListableSessionServiceContractOptions, type RevocableSessionServiceContractFixture, type RevocableSessionServiceContractOptions, runListableSessionServiceContractTests, runRevocableSessionServiceContractTests, } from "./identity-session.ts";
|
|
@@ -22,8 +22,7 @@
|
|
|
22
22
|
*
|
|
23
23
|
* @module
|
|
24
24
|
*/
|
|
25
|
-
import { assert,
|
|
26
|
-
import { beforeEach, describe, it } from "../../deps/jsr.io/@std/testing/1.0.20/bdd.js";
|
|
25
|
+
import { assert, beforeEach, describe, expect, it } from "vitest";
|
|
27
26
|
import { CONCURRENT_CALLERS, race } from "./_race.js";
|
|
28
27
|
const EMAIL = "user@example.com";
|
|
29
28
|
const OTHER_EMAIL = "other@example.com";
|
|
@@ -60,20 +59,20 @@ export function runOtpStoreContractTests(options) {
|
|
|
60
59
|
});
|
|
61
60
|
describe("create / findActive", () => {
|
|
62
61
|
it("returns null when the pair has no code", async () => {
|
|
63
|
-
|
|
62
|
+
expect(await store.findActive(EMAIL, PURPOSE)).toBe(null);
|
|
64
63
|
});
|
|
65
64
|
it("round-trips every field of a created record", async () => {
|
|
66
65
|
const created = record({ codeHash: "hash-1", maxAttempts: 3 });
|
|
67
66
|
await store.create(created);
|
|
68
67
|
const found = await store.findActive(EMAIL, PURPOSE);
|
|
69
68
|
assert(found !== null, "a created record must be findable");
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
69
|
+
expect(found.id).toBe(created.id);
|
|
70
|
+
expect(found.email).toBe(created.email);
|
|
71
|
+
expect(found.purpose).toBe(created.purpose);
|
|
72
|
+
expect(found.codeHash).toBe(created.codeHash);
|
|
73
|
+
expect(found.expiresAt).toBe(created.expiresAt);
|
|
74
|
+
expect(found.attempts).toBe(0);
|
|
75
|
+
expect(found.maxAttempts).toBe(3);
|
|
77
76
|
});
|
|
78
77
|
it("returns the most recently created active record", async () => {
|
|
79
78
|
const now = Date.now();
|
|
@@ -81,17 +80,17 @@ export function runOtpStoreContractTests(options) {
|
|
|
81
80
|
const newer = record({ createdAt: now, codeHash: "new" });
|
|
82
81
|
await store.create(older);
|
|
83
82
|
await store.create(newer);
|
|
84
|
-
|
|
83
|
+
expect((await store.findActive(EMAIL, PURPOSE))?.id, "a re-send supersedes the code before it").toBe(newer.id);
|
|
85
84
|
});
|
|
86
85
|
it("returns an expired record rather than hiding it", async () => {
|
|
87
86
|
await store.create(record({ expiresAt: Date.now() - 1000 }));
|
|
88
|
-
assert(await store.findActive(EMAIL, PURPOSE) !== null, "expiry is the service's call — it reports `expired` distinctly " +
|
|
87
|
+
assert((await store.findActive(EMAIL, PURPOSE)) !== null, "expiry is the service's call — it reports `expired` distinctly " +
|
|
89
88
|
"from `invalid`, which it cannot do if the store swallows the row");
|
|
90
89
|
});
|
|
91
90
|
it("scopes lookups by email and by purpose", async () => {
|
|
92
91
|
await store.create(record());
|
|
93
|
-
|
|
94
|
-
|
|
92
|
+
expect(await store.findActive(OTHER_EMAIL, PURPOSE)).toBe(null);
|
|
93
|
+
expect(await store.findActive(EMAIL, OTHER_PURPOSE)).toBe(null);
|
|
95
94
|
});
|
|
96
95
|
});
|
|
97
96
|
describe("consume", () => {
|
|
@@ -99,15 +98,15 @@ export function runOtpStoreContractTests(options) {
|
|
|
99
98
|
const created = record();
|
|
100
99
|
await store.create(created);
|
|
101
100
|
const results = await race(() => store.consume(created.id));
|
|
102
|
-
|
|
103
|
-
"be told they claimed it");
|
|
104
|
-
|
|
101
|
+
expect(results.filter((claimed) => claimed).length, "a code is single-use — consumers racing one code must not each " +
|
|
102
|
+
"be told they claimed it").toBe(1);
|
|
103
|
+
expect(await store.consume("missing-code")).toBe(false);
|
|
105
104
|
});
|
|
106
105
|
it("takes the record out of the active set", async () => {
|
|
107
106
|
const created = record();
|
|
108
107
|
await store.create(created);
|
|
109
108
|
await store.consume(created.id);
|
|
110
|
-
|
|
109
|
+
expect(await store.findActive(EMAIL, PURPOSE)).toBe(null);
|
|
111
110
|
});
|
|
112
111
|
it("leaves an older active code findable", async () => {
|
|
113
112
|
const now = Date.now();
|
|
@@ -116,13 +115,13 @@ export function runOtpStoreContractTests(options) {
|
|
|
116
115
|
await store.create(older);
|
|
117
116
|
await store.create(newer);
|
|
118
117
|
await store.consume(newer.id);
|
|
119
|
-
|
|
118
|
+
expect((await store.findActive(EMAIL, PURPOSE))?.id).toBe(older.id);
|
|
120
119
|
});
|
|
121
120
|
it("is a no-op for an unknown id", async () => {
|
|
122
121
|
const created = record();
|
|
123
122
|
await store.create(created);
|
|
124
123
|
await store.consume("no-such-id");
|
|
125
|
-
|
|
124
|
+
expect((await store.findActive(EMAIL, PURPOSE))?.id).toBe(created.id);
|
|
126
125
|
});
|
|
127
126
|
});
|
|
128
127
|
describe("invalidateById", () => {
|
|
@@ -133,14 +132,14 @@ export function runOtpStoreContractTests(options) {
|
|
|
133
132
|
await store.create(previous);
|
|
134
133
|
await store.create(undelivered);
|
|
135
134
|
await store.invalidateById(undelivered.id);
|
|
136
|
-
|
|
137
|
-
"request's code with it");
|
|
135
|
+
expect((await store.findActive(EMAIL, PURPOSE))?.id, "discarding an undeliverable code must not take a concurrent " +
|
|
136
|
+
"request's code with it").toBe(previous.id);
|
|
138
137
|
});
|
|
139
138
|
it("is a no-op for an unknown id", async () => {
|
|
140
139
|
const created = record();
|
|
141
140
|
await store.create(created);
|
|
142
141
|
await store.invalidateById("no-such-id");
|
|
143
|
-
|
|
142
|
+
expect((await store.findActive(EMAIL, PURPOSE))?.id).toBe(created.id);
|
|
144
143
|
});
|
|
145
144
|
});
|
|
146
145
|
describe("invalidate", () => {
|
|
@@ -148,31 +147,31 @@ export function runOtpStoreContractTests(options) {
|
|
|
148
147
|
await store.create(record({ createdAt: Date.now() - 1000 }));
|
|
149
148
|
await store.create(record());
|
|
150
149
|
await store.invalidate(EMAIL, PURPOSE);
|
|
151
|
-
|
|
150
|
+
expect(await store.findActive(EMAIL, PURPOSE)).toBe(null);
|
|
152
151
|
});
|
|
153
152
|
it("leaves other emails and other purposes alone", async () => {
|
|
154
153
|
await store.create(record());
|
|
155
154
|
await store.create(record({ email: OTHER_EMAIL }));
|
|
156
155
|
await store.create(record({ purpose: OTHER_PURPOSE }));
|
|
157
156
|
await store.invalidate(EMAIL, PURPOSE);
|
|
158
|
-
assert(await store.findActive(OTHER_EMAIL, PURPOSE) !== null);
|
|
159
|
-
assert(await store.findActive(EMAIL, OTHER_PURPOSE) !== null);
|
|
157
|
+
assert((await store.findActive(OTHER_EMAIL, PURPOSE)) !== null);
|
|
158
|
+
assert((await store.findActive(EMAIL, OTHER_PURPOSE)) !== null);
|
|
160
159
|
});
|
|
161
160
|
it("is a no-op when the pair has no active code", async () => {
|
|
162
161
|
await store.invalidate(EMAIL, PURPOSE);
|
|
163
|
-
|
|
162
|
+
expect(await store.findActive(EMAIL, PURPOSE)).toBe(null);
|
|
164
163
|
});
|
|
165
164
|
});
|
|
166
165
|
describe("recordAttempt", () => {
|
|
167
166
|
it("returns the count after the increment", async () => {
|
|
168
167
|
const created = record();
|
|
169
168
|
await store.create(created);
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
169
|
+
expect(await store.recordAttempt(created.id)).toBe(1);
|
|
170
|
+
expect(await store.recordAttempt(created.id)).toBe(2);
|
|
171
|
+
expect(await store.recordAttempt(created.id)).toBe(3);
|
|
173
172
|
});
|
|
174
173
|
it("returns 0 for an unknown id", async () => {
|
|
175
|
-
|
|
174
|
+
expect(await store.recordAttempt("no-such-id")).toBe(0);
|
|
176
175
|
});
|
|
177
176
|
it("counts each record separately", async () => {
|
|
178
177
|
const mine = record();
|
|
@@ -181,15 +180,15 @@ export function runOtpStoreContractTests(options) {
|
|
|
181
180
|
await store.create(theirs);
|
|
182
181
|
await store.recordAttempt(mine.id);
|
|
183
182
|
await store.recordAttempt(mine.id);
|
|
184
|
-
|
|
183
|
+
expect(await store.recordAttempt(theirs.id)).toBe(1);
|
|
185
184
|
});
|
|
186
185
|
it("gives every concurrent guess its own count", async () => {
|
|
187
186
|
const created = record({ maxAttempts: 5 });
|
|
188
187
|
await store.create(created);
|
|
189
188
|
const counts = await Promise.all(Array.from({ length: 5 }, () => store.recordAttempt(created.id)));
|
|
190
|
-
|
|
189
|
+
expect([...counts].sort((a, b) => a - b), "the guess budget is spent by the returned count — concurrent " +
|
|
191
190
|
"guesses sharing one count spend the budget once and let the " +
|
|
192
|
-
"rest through");
|
|
191
|
+
"rest through").toStrictEqual([1, 2, 3, 4, 5]);
|
|
193
192
|
});
|
|
194
193
|
});
|
|
195
194
|
});
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
*
|
|
23
23
|
* @module
|
|
24
24
|
*/
|
|
25
|
-
import type { RateLimitStore } from "../../identity/rate-limit.
|
|
25
|
+
import type { RateLimitStore } from "../../identity/rate-limit.ts";
|
|
26
26
|
/** Options for {@link runRateLimitStoreContractTests}. */
|
|
27
27
|
export interface RateLimitStoreContractOptions {
|
|
28
28
|
/**
|
|
@@ -22,8 +22,7 @@
|
|
|
22
22
|
*
|
|
23
23
|
* @module
|
|
24
24
|
*/
|
|
25
|
-
import { assert,
|
|
26
|
-
import { beforeEach, describe, it } from "../../deps/jsr.io/@std/testing/1.0.20/bdd.js";
|
|
25
|
+
import { assert, beforeEach, describe, expect, it } from "vitest";
|
|
27
26
|
const KEY = "signin:1.2.3.4";
|
|
28
27
|
const OTHER_KEY = "signin:5.6.7.8";
|
|
29
28
|
const WINDOW_MS = 60_000;
|
|
@@ -46,37 +45,37 @@ export function runRateLimitStoreContractTests(options) {
|
|
|
46
45
|
describe("increment", () => {
|
|
47
46
|
it("opens a window at count 1 that ends one window from now", async () => {
|
|
48
47
|
const result = await store.increment(KEY, WINDOW_MS, now);
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
expect(result.count).toStrictEqual(1);
|
|
49
|
+
expect(Math.abs(result.resetAt - (now + WINDOW_MS))).toBeLessThanOrEqual(tolerance);
|
|
51
50
|
});
|
|
52
51
|
it("counts up within the window without moving its end", async () => {
|
|
53
52
|
const first = await store.increment(KEY, WINDOW_MS, now);
|
|
54
53
|
const second = await store.increment(KEY, WINDOW_MS, now + 10);
|
|
55
54
|
const third = await store.increment(KEY, WINDOW_MS, now + 20);
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
"the reset out");
|
|
55
|
+
expect([second.count, third.count]).toStrictEqual([2, 3]);
|
|
56
|
+
expect([second.resetAt, third.resetAt], "a fixed window ends when it ends — hits inside it must not push " +
|
|
57
|
+
"the reset out").toStrictEqual([first.resetAt, first.resetAt]);
|
|
59
58
|
});
|
|
60
59
|
it("opens a fresh window once the previous one has lapsed", async () => {
|
|
61
60
|
const first = await store.increment(KEY, WINDOW_MS, now);
|
|
62
61
|
const after = first.resetAt + 1;
|
|
63
62
|
const result = await store.increment(KEY, WINDOW_MS, after);
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
expect(result.count).toStrictEqual(1);
|
|
64
|
+
expect(Math.abs(result.resetAt - (after + WINDOW_MS))).toBeLessThanOrEqual(tolerance);
|
|
66
65
|
});
|
|
67
66
|
it("counts each key on its own", async () => {
|
|
68
67
|
await store.increment(KEY, WINDOW_MS, now);
|
|
69
68
|
await store.increment(KEY, WINDOW_MS, now);
|
|
70
69
|
const other = await store.increment(OTHER_KEY, WINDOW_MS, now);
|
|
71
|
-
|
|
70
|
+
expect(other.count).toStrictEqual(1);
|
|
72
71
|
});
|
|
73
72
|
it("gives every concurrent hit its own count", async () => {
|
|
74
73
|
const results = await Promise.all(Array.from({ length: 5 }, () => store.increment(KEY, WINDOW_MS, now)));
|
|
75
|
-
|
|
74
|
+
expect(results.map((result) => result.count).sort((a, b) => a - b), "the limiter decides on the returned count — concurrent hits " +
|
|
76
75
|
"sharing a count are exactly the burst that slips past the " +
|
|
77
|
-
"threshold");
|
|
76
|
+
"threshold").toStrictEqual([1, 2, 3, 4, 5]);
|
|
78
77
|
const next = await store.increment(KEY, WINDOW_MS, now);
|
|
79
|
-
|
|
78
|
+
expect(next.count, "no hit may be lost to a stale read").toStrictEqual(6);
|
|
80
79
|
});
|
|
81
80
|
});
|
|
82
81
|
describe("reset", () => {
|
|
@@ -85,15 +84,15 @@ export function runRateLimitStoreContractTests(options) {
|
|
|
85
84
|
await store.increment(KEY, WINDOW_MS, now);
|
|
86
85
|
await store.reset(KEY);
|
|
87
86
|
const result = await store.increment(KEY, WINDOW_MS, now);
|
|
88
|
-
|
|
89
|
-
|
|
87
|
+
expect(result.count).toStrictEqual(1);
|
|
88
|
+
expect(Math.abs(result.resetAt - (now + WINDOW_MS))).toBeLessThanOrEqual(tolerance);
|
|
90
89
|
});
|
|
91
90
|
it("clears only the key it is given", async () => {
|
|
92
91
|
await store.increment(KEY, WINDOW_MS, now);
|
|
93
92
|
await store.increment(OTHER_KEY, WINDOW_MS, now);
|
|
94
93
|
await store.reset(KEY);
|
|
95
94
|
const other = await store.increment(OTHER_KEY, WINDOW_MS, now);
|
|
96
|
-
|
|
95
|
+
expect(other.count).toStrictEqual(2);
|
|
97
96
|
});
|
|
98
97
|
it("is a no-op for a key with no counter", async () => {
|
|
99
98
|
await store.reset("never-seen");
|