@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,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OIDC signing keys and compact JWS signing — Web Crypto only, no
|
|
3
|
+
* dependencies. Backs id_token issuance, the JWKS endpoint, and the optional
|
|
4
|
+
* signed (JWT) access-token generator.
|
|
5
|
+
*
|
|
6
|
+
* Keys are ES256 (the OIDC-recommended default with broad library support).
|
|
7
|
+
* Generate once with {@link generateSigningKey}, persist via
|
|
8
|
+
* {@link exportSigningKeyJwk}, and load on boot with {@link importSigningKeyJwk}
|
|
9
|
+
* so every instance of a multi-instance deploy signs with the same key (an
|
|
10
|
+
* ephemeral per-instance key would fail verification across instances).
|
|
11
|
+
*
|
|
12
|
+
* @module
|
|
13
|
+
*/
|
|
14
|
+
import { base64urlDecode, base64urlEncode } from "../utils/crypto.js";
|
|
15
|
+
import { snapshotAuthenticationContext } from "../utils/authentication-context.js";
|
|
16
|
+
/** A {@link SigningKeyProvider} over one fixed key. */
|
|
17
|
+
export class StaticSigningKeyProvider {
|
|
18
|
+
#key;
|
|
19
|
+
/** Fixes the single key this provider signs with and publishes. */
|
|
20
|
+
constructor(key) {
|
|
21
|
+
this.#key = key;
|
|
22
|
+
}
|
|
23
|
+
/** The fixed key passed at construction. */
|
|
24
|
+
getSigningKey() {
|
|
25
|
+
return Promise.resolve(this.#key);
|
|
26
|
+
}
|
|
27
|
+
/** A JWKS containing only the fixed key's public JWK. */
|
|
28
|
+
getPublicJwks() {
|
|
29
|
+
return Promise.resolve({ keys: [this.#key.publicJwk] });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* A {@link SigningKeyProvider} for rotating the signing key through a JWKS
|
|
34
|
+
* grace window: sign with {@link RotatingSigningKeyProviderOptions.current}
|
|
35
|
+
* only, while {@link RotatingSigningKeyProviderOptions.previous} keys stay
|
|
36
|
+
* published so outstanding tokens keep validating until they expire and every
|
|
37
|
+
* consumer has refetched JWKS.
|
|
38
|
+
*
|
|
39
|
+
* Rotation is: promote the new key to `current`, move the old one into
|
|
40
|
+
* `previous`, and hold that grace window for at least the longest token
|
|
41
|
+
* lifetime before dropping the old key from `previous`. Because
|
|
42
|
+
* {@link signJwt} stamps the current key's `kid` into every JWS header and each
|
|
43
|
+
* published JWK carries its own `kid`, verifiers select the right key by `kid`
|
|
44
|
+
* across the window. Keys are published current-first and de-duplicated by
|
|
45
|
+
* `kid`.
|
|
46
|
+
*
|
|
47
|
+
* A single-key configuration (`previous` empty or omitted) publishes exactly
|
|
48
|
+
* the one key and is behaviourally identical to
|
|
49
|
+
* {@link StaticSigningKeyProvider} — adopting this provider does not change a
|
|
50
|
+
* deployment that has not started a rotation.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```ts
|
|
54
|
+
* const provider = new RotatingSigningKeyProvider({
|
|
55
|
+
* current: await importSigningKeyJwk(JSON.parse(currentJwk)),
|
|
56
|
+
* previous: [await importSigningKeyJwk(JSON.parse(oldJwk))],
|
|
57
|
+
* });
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export class RotatingSigningKeyProvider {
|
|
61
|
+
#current;
|
|
62
|
+
#publicJwks;
|
|
63
|
+
/** Fixes the current signing key and the published set (current + previous). */
|
|
64
|
+
constructor(options) {
|
|
65
|
+
this.#current = options.current;
|
|
66
|
+
const seen = new Set();
|
|
67
|
+
const keys = [];
|
|
68
|
+
for (const key of [options.current, ...(options.previous ?? [])]) {
|
|
69
|
+
if (seen.has(key.kid))
|
|
70
|
+
continue;
|
|
71
|
+
seen.add(key.kid);
|
|
72
|
+
keys.push(key.publicJwk);
|
|
73
|
+
}
|
|
74
|
+
this.#publicJwks = { keys };
|
|
75
|
+
}
|
|
76
|
+
/** The current key — the only key new tokens are ever signed with. */
|
|
77
|
+
getSigningKey() {
|
|
78
|
+
return Promise.resolve(this.#current);
|
|
79
|
+
}
|
|
80
|
+
/** Public JWKs for the current key and every still-published previous key. */
|
|
81
|
+
getPublicJwks() {
|
|
82
|
+
return Promise.resolve({ keys: [...this.#publicJwks.keys] });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const ES256_PARAMS = { name: "ECDSA", namedCurve: "P-256" };
|
|
86
|
+
const ES256_SIGN_PARAMS = { name: "ECDSA", hash: "SHA-256" };
|
|
87
|
+
const encoder = new TextEncoder();
|
|
88
|
+
const decoder = new TextDecoder();
|
|
89
|
+
function encodeSegment(value) {
|
|
90
|
+
return base64urlEncode(encoder.encode(JSON.stringify(value)));
|
|
91
|
+
}
|
|
92
|
+
/** Maps a user to a JWT `sub` claim: the user's `id` property, else the user itself. */
|
|
93
|
+
export function defaultSubjectOf(user) {
|
|
94
|
+
return String(user?.id ?? user);
|
|
95
|
+
}
|
|
96
|
+
function publicJwkOf(jwk, kid) {
|
|
97
|
+
return {
|
|
98
|
+
kty: jwk.kty,
|
|
99
|
+
crv: jwk.crv,
|
|
100
|
+
x: jwk.x,
|
|
101
|
+
y: jwk.y,
|
|
102
|
+
kid,
|
|
103
|
+
alg: "ES256",
|
|
104
|
+
use: "sig",
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/** Generate a fresh ES256 signing key with a random `kid`. */
|
|
108
|
+
export async function generateSigningKey() {
|
|
109
|
+
const pair = await crypto.subtle.generateKey(ES256_PARAMS, true, [
|
|
110
|
+
"sign",
|
|
111
|
+
"verify",
|
|
112
|
+
]);
|
|
113
|
+
const kid = crypto.randomUUID();
|
|
114
|
+
const jwk = await crypto.subtle.exportKey("jwk", pair.publicKey);
|
|
115
|
+
return {
|
|
116
|
+
kid,
|
|
117
|
+
alg: "ES256",
|
|
118
|
+
privateKey: pair.privateKey,
|
|
119
|
+
publicJwk: publicJwkOf(jwk, kid),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Export a signing key (including private material) as a JWK for persistence
|
|
124
|
+
* — e.g. into a secret-manager entry the app loads on boot. Guard it like any
|
|
125
|
+
* credential.
|
|
126
|
+
*/
|
|
127
|
+
export async function exportSigningKeyJwk(key) {
|
|
128
|
+
const jwk = await crypto.subtle.exportKey("jwk", key.privateKey);
|
|
129
|
+
return { ...jwk, kid: key.kid, alg: key.alg };
|
|
130
|
+
}
|
|
131
|
+
/** Import a signing key previously exported with {@link exportSigningKeyJwk}. */
|
|
132
|
+
export async function importSigningKeyJwk(jwk) {
|
|
133
|
+
const privateKey = await crypto.subtle.importKey("jwk", jwk, ES256_PARAMS, true, ["sign"]);
|
|
134
|
+
const kid = jwk.kid ?? crypto.randomUUID();
|
|
135
|
+
return {
|
|
136
|
+
kid,
|
|
137
|
+
alg: "ES256",
|
|
138
|
+
privateKey,
|
|
139
|
+
publicJwk: publicJwkOf(jwk, kid),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Sign a compact JWS (`header.payload.signature`) with the given key —
|
|
144
|
+
* the primitive behind id_tokens and JWT access tokens.
|
|
145
|
+
*/
|
|
146
|
+
export async function signJwt(key, payload, header = {}) {
|
|
147
|
+
const signingInput = `${encodeSegment({ alg: key.alg, typ: "JWT", kid: key.kid, ...header })}.${encodeSegment(payload)}`;
|
|
148
|
+
const signature = await crypto.subtle.sign(ES256_SIGN_PARAMS, key.privateKey, encoder.encode(signingInput));
|
|
149
|
+
return `${signingInput}.${base64urlEncode(new Uint8Array(signature))}`;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Verify a compact JWS against a public JWK and return its payload, or
|
|
153
|
+
* `undefined` when the signature (or shape) is invalid or the payload's `exp`
|
|
154
|
+
* is in the past. Exported for tests and lightweight in-process verification;
|
|
155
|
+
* production resource servers should verify against the JWKS endpoint with
|
|
156
|
+
* their JOSE library of choice.
|
|
157
|
+
*/
|
|
158
|
+
export async function verifyJwt(jwt, publicJwk, options = {}) {
|
|
159
|
+
const parts = jwt.split(".");
|
|
160
|
+
if (parts.length !== 3)
|
|
161
|
+
return undefined;
|
|
162
|
+
const [headerPart, payloadPart, signaturePart] = parts;
|
|
163
|
+
try {
|
|
164
|
+
const publicKey = await crypto.subtle.importKey("jwk", publicJwk, ES256_PARAMS, false, ["verify"]);
|
|
165
|
+
const valid = await crypto.subtle.verify(ES256_SIGN_PARAMS, publicKey, new Uint8Array(base64urlDecode(signaturePart)), encoder.encode(`${headerPart}.${payloadPart}`));
|
|
166
|
+
if (!valid)
|
|
167
|
+
return undefined;
|
|
168
|
+
const payload = JSON.parse(decoder.decode(base64urlDecode(payloadPart)));
|
|
169
|
+
if (!options.ignoreExpiration &&
|
|
170
|
+
typeof payload.exp === "number" && payload.exp * 1000 <= Date.now()) {
|
|
171
|
+
return undefined;
|
|
172
|
+
}
|
|
173
|
+
return payload;
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
return undefined;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Build a `generateAccessToken` implementation that issues **signed JWT
|
|
181
|
+
* access tokens** (RFC 9068 `at+jwt`) instead of opaque strings — the
|
|
182
|
+
* "verifiable signed-token option": resource servers can validate offline
|
|
183
|
+
* against the JWKS endpoint. Wire it into your token service:
|
|
184
|
+
*
|
|
185
|
+
* ```ts
|
|
186
|
+
* class JwtTokenService extends MyTokenService {
|
|
187
|
+
* override generateAccessToken = createJwtAccessTokenGenerator({
|
|
188
|
+
* signingKeys, issuer: "https://auth.example.com",
|
|
189
|
+
* });
|
|
190
|
+
* }
|
|
191
|
+
* ```
|
|
192
|
+
*
|
|
193
|
+
* The token store still persists the (hashed) JWT string, so revocation and
|
|
194
|
+
* introspection keep working exactly as with opaque tokens.
|
|
195
|
+
*/
|
|
196
|
+
export function createJwtAccessTokenGenerator(options) {
|
|
197
|
+
const lifetime = options.lifetimeSeconds ?? 3600;
|
|
198
|
+
const subjectOf = options.subjectOf ?? defaultSubjectOf;
|
|
199
|
+
return async (client, user, scope, authenticationContext) => {
|
|
200
|
+
const event = user
|
|
201
|
+
? snapshotAuthenticationContext(authenticationContext)
|
|
202
|
+
: undefined;
|
|
203
|
+
const key = await options.signingKeys.getSigningKey();
|
|
204
|
+
const now = Math.floor(Date.now() / 1000);
|
|
205
|
+
const extraClaims = user && options.userClaims
|
|
206
|
+
? await options.userClaims(user, scope, client, event)
|
|
207
|
+
: undefined;
|
|
208
|
+
const payload = {
|
|
209
|
+
...extraClaims,
|
|
210
|
+
iss: options.issuer,
|
|
211
|
+
sub: user ? subjectOf(user) : client.id,
|
|
212
|
+
client_id: client.id,
|
|
213
|
+
aud: options.audience ?? client.id,
|
|
214
|
+
iat: now,
|
|
215
|
+
exp: now + lifetime,
|
|
216
|
+
jti: crypto.randomUUID(),
|
|
217
|
+
};
|
|
218
|
+
const scopeText = scope?.toString();
|
|
219
|
+
if (scopeText)
|
|
220
|
+
payload.scope = scopeText;
|
|
221
|
+
else
|
|
222
|
+
delete payload.scope;
|
|
223
|
+
return await signJwt(key, payload, { typ: "at+jwt" });
|
|
224
|
+
};
|
|
225
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hashing utilities for secure token and secret storage.
|
|
3
|
+
* @module
|
|
4
|
+
*/
|
|
5
|
+
import { encodeHex } from "../../deps/jsr.io/@std/encoding/1.0.11/hex.js";
|
|
6
|
+
/** Hashes a high-entropy string (UUID, token, secret) using SHA-256. */
|
|
7
|
+
export async function sha256Hash(value) {
|
|
8
|
+
const data = new TextEncoder().encode(value);
|
|
9
|
+
const hash = await crypto.subtle.digest("SHA-256", data);
|
|
10
|
+
return encodeHex(hash);
|
|
11
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contract test suite for {@link AuthorizationCodeServiceInterface}
|
|
3
|
+
* implementations.
|
|
4
|
+
*
|
|
5
|
+
* @module
|
|
6
|
+
*/
|
|
7
|
+
import type { ClientInterface } from "../../models/client.js";
|
|
8
|
+
import type { AbstractScope, BasicScope } from "../../models/scope.js";
|
|
9
|
+
import type { AuthorizationCodeServiceInterface } from "../../server/services/authorization-code.js";
|
|
10
|
+
import type { ClientServiceInterface } from "../../server/services/client.js";
|
|
11
|
+
import type { UserServiceInterface } from "../../server/services/user.js";
|
|
12
|
+
import type { MemoryUserShape } from "../services.js";
|
|
13
|
+
/** Options for {@link runAuthorizationCodeServiceContractTests}. */
|
|
14
|
+
export interface AuthorizationCodeServiceContractOptions<C extends ClientInterface, U extends MemoryUserShape, S extends AbstractScope = BasicScope> {
|
|
15
|
+
/** Returns fresh services for each test. */
|
|
16
|
+
makeServices(): Promise<{
|
|
17
|
+
userService: UserServiceInterface<U>;
|
|
18
|
+
clientService: ClientServiceInterface<C, U>;
|
|
19
|
+
authorizationCodeService: AuthorizationCodeServiceInterface<C, U, S>;
|
|
20
|
+
}> | {
|
|
21
|
+
userService: UserServiceInterface<U>;
|
|
22
|
+
clientService: ClientServiceInterface<C, U>;
|
|
23
|
+
authorizationCodeService: AuthorizationCodeServiceInterface<C, U, S>;
|
|
24
|
+
};
|
|
25
|
+
/** Persists a user with the given password into the user service under test. */
|
|
26
|
+
addUser(service: UserServiceInterface<U>, user: U, password: string): Promise<void>;
|
|
27
|
+
/** Persists a client (optionally with a secret and owning user) into the client service under test. */
|
|
28
|
+
addClient(service: ClientServiceInterface<C, U>, client: C, secret?: string, ownerUserId?: string): Promise<void>;
|
|
29
|
+
/** Builds a distinct user fixture for the given sequence number. Defaults to a minimal `{ id, username }` shape. */
|
|
30
|
+
makeUser?(seq: number): U;
|
|
31
|
+
/** Builds a distinct client fixture for the given sequence number. Defaults to a client granting `authorization_code`. */
|
|
32
|
+
makeClient?(seq: number): C;
|
|
33
|
+
/** Overrides the name passed to the outer `describe` block. */
|
|
34
|
+
describeName?: string;
|
|
35
|
+
}
|
|
36
|
+
/** Runs the {@link AuthorizationCodeServiceInterface} contract suite. */
|
|
37
|
+
export declare function runAuthorizationCodeServiceContractTests<C extends ClientInterface, U extends MemoryUserShape, S extends AbstractScope = BasicScope>(options: AuthorizationCodeServiceContractOptions<C, U, S>): void;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contract test suite for {@link AuthorizationCodeServiceInterface}
|
|
3
|
+
* implementations.
|
|
4
|
+
*
|
|
5
|
+
* @module
|
|
6
|
+
*/
|
|
7
|
+
import { assertEquals, assertStrictEquals } from "../../deps/jsr.io/@std/assert/1.0.19/mod.js";
|
|
8
|
+
import { beforeEach, describe, it } from "../../deps/jsr.io/@std/testing/1.0.20/bdd.js";
|
|
9
|
+
const defaultMakeUser = (seq) => ({ id: `u${seq}`, username: `user${seq}` });
|
|
10
|
+
const defaultMakeClient = (seq) => ({ id: `client-${seq}`, grants: ["authorization_code"] });
|
|
11
|
+
/** Runs the {@link AuthorizationCodeServiceInterface} contract suite. */
|
|
12
|
+
export function runAuthorizationCodeServiceContractTests(options) {
|
|
13
|
+
const makeUser = options.makeUser ?? (defaultMakeUser);
|
|
14
|
+
const makeClient = options.makeClient ?? (defaultMakeClient);
|
|
15
|
+
describe(options.describeName ?? "AuthorizationCodeServiceInterface contract", () => {
|
|
16
|
+
let userService;
|
|
17
|
+
let clientService;
|
|
18
|
+
let codeService;
|
|
19
|
+
let user;
|
|
20
|
+
let client;
|
|
21
|
+
beforeEach(async () => {
|
|
22
|
+
const services = await options.makeServices();
|
|
23
|
+
userService = services.userService;
|
|
24
|
+
clientService = services.clientService;
|
|
25
|
+
codeService = services.authorizationCodeService;
|
|
26
|
+
user = makeUser(1);
|
|
27
|
+
client = makeClient(1);
|
|
28
|
+
await options.addUser(userService, user, "pw");
|
|
29
|
+
await options.addClient(clientService, client);
|
|
30
|
+
});
|
|
31
|
+
it("generateCode returns unique strings", async () => {
|
|
32
|
+
const a = await codeService.generateCode(client, user);
|
|
33
|
+
const b = await codeService.generateCode(client, user);
|
|
34
|
+
assertEquals(a !== b, true);
|
|
35
|
+
});
|
|
36
|
+
it("expiresAt returns a future date", async () => {
|
|
37
|
+
const expires = await codeService.expiresAt(client, user);
|
|
38
|
+
assertEquals(expires.getTime() > Date.now(), true);
|
|
39
|
+
});
|
|
40
|
+
it("save + get round-trips an authorization code", async () => {
|
|
41
|
+
const code = await codeService.generateCode(client, user);
|
|
42
|
+
const expiresAt = await codeService.expiresAt(client, user);
|
|
43
|
+
const ac = {
|
|
44
|
+
code,
|
|
45
|
+
expiresAt,
|
|
46
|
+
client,
|
|
47
|
+
user,
|
|
48
|
+
redirectUri: "http://app/cb",
|
|
49
|
+
challenge: "abc",
|
|
50
|
+
challengeMethod: "S256",
|
|
51
|
+
nonce: "nonce-1",
|
|
52
|
+
};
|
|
53
|
+
await codeService.save(ac);
|
|
54
|
+
const fetched = await codeService.get(code);
|
|
55
|
+
assertEquals(fetched?.code, code);
|
|
56
|
+
assertEquals(fetched?.client.id, client.id);
|
|
57
|
+
assertEquals(fetched?.redirectUri, "http://app/cb");
|
|
58
|
+
assertEquals(fetched?.challenge, "abc");
|
|
59
|
+
assertEquals(fetched?.nonce, "nonce-1");
|
|
60
|
+
});
|
|
61
|
+
it("get returns undefined for an unknown code", async () => {
|
|
62
|
+
assertStrictEquals(await codeService.get("missing"), undefined);
|
|
63
|
+
});
|
|
64
|
+
it("revoke deletes the code by string", async () => {
|
|
65
|
+
const code = await codeService.generateCode(client, user);
|
|
66
|
+
await codeService.save({
|
|
67
|
+
code,
|
|
68
|
+
expiresAt: await codeService.expiresAt(client, user),
|
|
69
|
+
client,
|
|
70
|
+
user,
|
|
71
|
+
});
|
|
72
|
+
const ok = await codeService.revoke(code);
|
|
73
|
+
assertStrictEquals(ok, true);
|
|
74
|
+
assertStrictEquals(await codeService.get(code), undefined);
|
|
75
|
+
});
|
|
76
|
+
it("revoke deletes the code by object", async () => {
|
|
77
|
+
const code = await codeService.generateCode(client, user);
|
|
78
|
+
const ac = {
|
|
79
|
+
code,
|
|
80
|
+
expiresAt: await codeService.expiresAt(client, user),
|
|
81
|
+
client,
|
|
82
|
+
user,
|
|
83
|
+
};
|
|
84
|
+
await codeService.save(ac);
|
|
85
|
+
const ok = await codeService.revoke(ac);
|
|
86
|
+
assertStrictEquals(ok, true);
|
|
87
|
+
assertStrictEquals(await codeService.get(code), undefined);
|
|
88
|
+
});
|
|
89
|
+
it("revoke returns false for an unknown code", async () => {
|
|
90
|
+
assertStrictEquals(await codeService.revoke("missing"), false);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contract test suite for {@link ClientServiceInterface} implementations.
|
|
3
|
+
*
|
|
4
|
+
* `getUser` is contract-conformant either way: resolving a user and resolving
|
|
5
|
+
* nothing are both required to pass. A client that resolves nothing is a
|
|
6
|
+
* machine client whose token carries no resource owner (RFC 6749 §4.4), not a
|
|
7
|
+
* misconfiguration — the client credentials grant issues it a user-less token.
|
|
8
|
+
* Resolve a user only when that user is a principal of its own, such as a
|
|
9
|
+
* per-application service account; resolving a human owner hands the machine
|
|
10
|
+
* that person's identity (RFC 9700 §4.15).
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* import { runClientServiceContractTests } from "@udibo/oauth2/testing/contract";
|
|
15
|
+
* import type { ClientInterface } from "@udibo/oauth2/server";
|
|
16
|
+
* import type {
|
|
17
|
+
* ClientServiceInterface,
|
|
18
|
+
* UserServiceInterface,
|
|
19
|
+
* } from "@udibo/oauth2/server/authorization";
|
|
20
|
+
*
|
|
21
|
+
* interface AppUser {
|
|
22
|
+
* id: string;
|
|
23
|
+
* username: string;
|
|
24
|
+
* }
|
|
25
|
+
* interface DrizzleUserService extends UserServiceInterface<AppUser> {
|
|
26
|
+
* register(user: AppUser, password: string): Promise<void>;
|
|
27
|
+
* }
|
|
28
|
+
* interface DrizzleClientService
|
|
29
|
+
* extends ClientServiceInterface<ClientInterface, AppUser> {
|
|
30
|
+
* register(
|
|
31
|
+
* client: ClientInterface,
|
|
32
|
+
* secret?: string,
|
|
33
|
+
* ownerUserId?: string,
|
|
34
|
+
* ): Promise<void>;
|
|
35
|
+
* }
|
|
36
|
+
* declare function freshServices(): Promise<{
|
|
37
|
+
* userService: DrizzleUserService;
|
|
38
|
+
* clientService: DrizzleClientService;
|
|
39
|
+
* }>;
|
|
40
|
+
*
|
|
41
|
+
* runClientServiceContractTests<ClientInterface, AppUser>({
|
|
42
|
+
* makeServices: freshServices,
|
|
43
|
+
* addUser: (svc, user, pw) => (svc as DrizzleUserService).register(user, pw),
|
|
44
|
+
* addClient: (svc, client, secret, ownerUserId) =>
|
|
45
|
+
* (svc as DrizzleClientService).register(client, secret, ownerUserId),
|
|
46
|
+
* });
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* @module
|
|
50
|
+
*/
|
|
51
|
+
import type { ClientInterface } from "../../models/client.js";
|
|
52
|
+
import type { ClientServiceInterface } from "../../server/services/client.js";
|
|
53
|
+
import type { UserServiceInterface } from "../../server/services/user.js";
|
|
54
|
+
import type { MemoryUserShape } from "../services.js";
|
|
55
|
+
/** Options for {@link runClientServiceContractTests}. */
|
|
56
|
+
export interface ClientServiceContractOptions<C extends ClientInterface, U extends MemoryUserShape> {
|
|
57
|
+
/**
|
|
58
|
+
* Returns fresh services for each test. The `userService` is needed
|
|
59
|
+
* because `clientService.getUser(client)` resolves through it.
|
|
60
|
+
*/
|
|
61
|
+
makeServices(): Promise<{
|
|
62
|
+
userService: UserServiceInterface<U>;
|
|
63
|
+
clientService: ClientServiceInterface<C, U>;
|
|
64
|
+
}> | {
|
|
65
|
+
userService: UserServiceInterface<U>;
|
|
66
|
+
clientService: ClientServiceInterface<C, U>;
|
|
67
|
+
};
|
|
68
|
+
/** Adds a user via the consumer's preferred mechanism. */
|
|
69
|
+
addUser(service: UserServiceInterface<U>, user: U, password: string): Promise<void>;
|
|
70
|
+
/**
|
|
71
|
+
* Adds a client. Pass `secret` to register a confidential client; omit
|
|
72
|
+
* for public. `ownerUserId` is the user returned by `getUser`; omit it to
|
|
73
|
+
* register a client that resolves no user, which the suite requires to be
|
|
74
|
+
* supported.
|
|
75
|
+
*/
|
|
76
|
+
addClient(service: ClientServiceInterface<C, U>, client: C, secret?: string, ownerUserId?: string): Promise<void>;
|
|
77
|
+
/** Builds a distinct user fixture for the given sequence number. Defaults to a minimal `{ id, username }` shape. */
|
|
78
|
+
makeUser?(seq: number): U;
|
|
79
|
+
/** Builds a distinct client fixture for the given sequence number. Defaults to a client granting `client_credentials`. */
|
|
80
|
+
makeClient?(seq: number): C;
|
|
81
|
+
/** Overrides the name passed to the outer `describe` block. */
|
|
82
|
+
describeName?: string;
|
|
83
|
+
}
|
|
84
|
+
/** Runs the {@link ClientServiceInterface} contract suite. */
|
|
85
|
+
export declare function runClientServiceContractTests<C extends ClientInterface, U extends MemoryUserShape>(options: ClientServiceContractOptions<C, U>): void;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contract test suite for {@link ClientServiceInterface} implementations.
|
|
3
|
+
*
|
|
4
|
+
* `getUser` is contract-conformant either way: resolving a user and resolving
|
|
5
|
+
* nothing are both required to pass. A client that resolves nothing is a
|
|
6
|
+
* machine client whose token carries no resource owner (RFC 6749 §4.4), not a
|
|
7
|
+
* misconfiguration — the client credentials grant issues it a user-less token.
|
|
8
|
+
* Resolve a user only when that user is a principal of its own, such as a
|
|
9
|
+
* per-application service account; resolving a human owner hands the machine
|
|
10
|
+
* that person's identity (RFC 9700 §4.15).
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* import { runClientServiceContractTests } from "@udibo/oauth2/testing/contract";
|
|
15
|
+
* import type { ClientInterface } from "@udibo/oauth2/server";
|
|
16
|
+
* import type {
|
|
17
|
+
* ClientServiceInterface,
|
|
18
|
+
* UserServiceInterface,
|
|
19
|
+
* } from "@udibo/oauth2/server/authorization";
|
|
20
|
+
*
|
|
21
|
+
* interface AppUser {
|
|
22
|
+
* id: string;
|
|
23
|
+
* username: string;
|
|
24
|
+
* }
|
|
25
|
+
* interface DrizzleUserService extends UserServiceInterface<AppUser> {
|
|
26
|
+
* register(user: AppUser, password: string): Promise<void>;
|
|
27
|
+
* }
|
|
28
|
+
* interface DrizzleClientService
|
|
29
|
+
* extends ClientServiceInterface<ClientInterface, AppUser> {
|
|
30
|
+
* register(
|
|
31
|
+
* client: ClientInterface,
|
|
32
|
+
* secret?: string,
|
|
33
|
+
* ownerUserId?: string,
|
|
34
|
+
* ): Promise<void>;
|
|
35
|
+
* }
|
|
36
|
+
* declare function freshServices(): Promise<{
|
|
37
|
+
* userService: DrizzleUserService;
|
|
38
|
+
* clientService: DrizzleClientService;
|
|
39
|
+
* }>;
|
|
40
|
+
*
|
|
41
|
+
* runClientServiceContractTests<ClientInterface, AppUser>({
|
|
42
|
+
* makeServices: freshServices,
|
|
43
|
+
* addUser: (svc, user, pw) => (svc as DrizzleUserService).register(user, pw),
|
|
44
|
+
* addClient: (svc, client, secret, ownerUserId) =>
|
|
45
|
+
* (svc as DrizzleClientService).register(client, secret, ownerUserId),
|
|
46
|
+
* });
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* @module
|
|
50
|
+
*/
|
|
51
|
+
import { assertEquals, assertStrictEquals } from "../../deps/jsr.io/@std/assert/1.0.19/mod.js";
|
|
52
|
+
import { beforeEach, describe, it } from "../../deps/jsr.io/@std/testing/1.0.20/bdd.js";
|
|
53
|
+
const defaultMakeUser = (seq) => ({ id: `u${seq}`, username: `user${seq}` });
|
|
54
|
+
const defaultMakeClient = (seq) => ({ id: `client-${seq}`, grants: ["client_credentials"] });
|
|
55
|
+
/** Runs the {@link ClientServiceInterface} contract suite. */
|
|
56
|
+
export function runClientServiceContractTests(options) {
|
|
57
|
+
const makeUser = options.makeUser ?? (defaultMakeUser);
|
|
58
|
+
const makeClient = options.makeClient ?? (defaultMakeClient);
|
|
59
|
+
describe(options.describeName ?? "ClientServiceInterface contract", () => {
|
|
60
|
+
let userService;
|
|
61
|
+
let clientService;
|
|
62
|
+
beforeEach(async () => {
|
|
63
|
+
const services = await options.makeServices();
|
|
64
|
+
userService = services.userService;
|
|
65
|
+
clientService = services.clientService;
|
|
66
|
+
});
|
|
67
|
+
describe("get", () => {
|
|
68
|
+
it("returns undefined for an unknown id", async () => {
|
|
69
|
+
assertStrictEquals(await clientService.get("missing"), undefined);
|
|
70
|
+
});
|
|
71
|
+
it("returns the registered client", async () => {
|
|
72
|
+
const client = makeClient(1);
|
|
73
|
+
await options.addClient(clientService, client);
|
|
74
|
+
const fetched = await clientService.get(client.id);
|
|
75
|
+
assertEquals(fetched?.id, client.id);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
describe("getAuthenticated", () => {
|
|
79
|
+
it("returns the public client without a secret", async () => {
|
|
80
|
+
const client = makeClient(2);
|
|
81
|
+
await options.addClient(clientService, client);
|
|
82
|
+
const auth = await clientService.getAuthenticated(client.id);
|
|
83
|
+
assertEquals(auth?.id, client.id);
|
|
84
|
+
});
|
|
85
|
+
it("returns undefined for a confidential client when no secret is supplied", async () => {
|
|
86
|
+
const client = makeClient(3);
|
|
87
|
+
await options.addClient(clientService, client, "secret");
|
|
88
|
+
assertStrictEquals(await clientService.getAuthenticated(client.id), undefined);
|
|
89
|
+
});
|
|
90
|
+
it("returns undefined for a confidential client with a wrong secret", async () => {
|
|
91
|
+
const client = makeClient(4);
|
|
92
|
+
await options.addClient(clientService, client, "correct");
|
|
93
|
+
assertStrictEquals(await clientService.getAuthenticated(client.id, "wrong"), undefined);
|
|
94
|
+
});
|
|
95
|
+
it("returns the confidential client with a matching secret", async () => {
|
|
96
|
+
const client = makeClient(5);
|
|
97
|
+
await options.addClient(clientService, client, "matching");
|
|
98
|
+
const auth = await clientService.getAuthenticated(client.id, "matching");
|
|
99
|
+
assertEquals(auth?.id, client.id);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
describe("getUser", () => {
|
|
103
|
+
it("resolves nothing for a client registered without a user, rather than failing", async () => {
|
|
104
|
+
const client = makeClient(6);
|
|
105
|
+
await options.addClient(clientService, client);
|
|
106
|
+
assertStrictEquals(await clientService.getUser(client), undefined);
|
|
107
|
+
});
|
|
108
|
+
it("returns the associated user when one was registered", async () => {
|
|
109
|
+
const user = makeUser(7);
|
|
110
|
+
const client = makeClient(7);
|
|
111
|
+
await options.addUser(userService, user, "pw");
|
|
112
|
+
await options.addClient(clientService, client, undefined, user.id);
|
|
113
|
+
const owner = await clientService.getUser(client);
|
|
114
|
+
assertEquals(owner?.id, user.id);
|
|
115
|
+
});
|
|
116
|
+
it("accepts a client id (not just a client object)", async () => {
|
|
117
|
+
const user = makeUser(8);
|
|
118
|
+
const client = makeClient(8);
|
|
119
|
+
await options.addUser(userService, user, "pw");
|
|
120
|
+
await options.addClient(clientService, client, undefined, user.id);
|
|
121
|
+
const owner = await clientService.getUser(client.id);
|
|
122
|
+
assertEquals(owner?.id, user.id);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contract test suite for {@link DeviceAuthorizationServiceInterface}
|
|
3
|
+
* implementations (RFC 8628).
|
|
4
|
+
*
|
|
5
|
+
* @module
|
|
6
|
+
*/
|
|
7
|
+
import type { ClientInterface } from "../../models/client.js";
|
|
8
|
+
import type { AbstractScope, BasicScope } from "../../models/scope.js";
|
|
9
|
+
import type { ClientServiceInterface } from "../../server/services/client.js";
|
|
10
|
+
import type { DeviceAuthorizationServiceInterface } from "../../server/services/device-authorization.js";
|
|
11
|
+
import type { UserServiceInterface } from "../../server/services/user.js";
|
|
12
|
+
import type { MemoryUserShape } from "../services.js";
|
|
13
|
+
/** Options for {@link runDeviceAuthorizationServiceContractTests}. */
|
|
14
|
+
export interface DeviceAuthorizationServiceContractOptions<C extends ClientInterface, U extends MemoryUserShape, S extends AbstractScope = BasicScope> {
|
|
15
|
+
/** Returns fresh services for each test. */
|
|
16
|
+
makeServices(): Promise<{
|
|
17
|
+
userService: UserServiceInterface<U>;
|
|
18
|
+
clientService: ClientServiceInterface<C, U>;
|
|
19
|
+
deviceAuthorizationService: DeviceAuthorizationServiceInterface<C, U, S>;
|
|
20
|
+
}> | {
|
|
21
|
+
userService: UserServiceInterface<U>;
|
|
22
|
+
clientService: ClientServiceInterface<C, U>;
|
|
23
|
+
deviceAuthorizationService: DeviceAuthorizationServiceInterface<C, U, S>;
|
|
24
|
+
};
|
|
25
|
+
/** Persists a user with the given password into the user service under test. */
|
|
26
|
+
addUser(service: UserServiceInterface<U>, user: U, password: string): Promise<void>;
|
|
27
|
+
/** Persists a client (optionally with a secret and owning user) into the client service under test. */
|
|
28
|
+
addClient(service: ClientServiceInterface<C, U>, client: C, secret?: string, ownerUserId?: string): Promise<void>;
|
|
29
|
+
/** Builds a distinct user fixture for the given sequence number. Defaults to a minimal `{ id, username }` shape. */
|
|
30
|
+
makeUser?(seq: number): U;
|
|
31
|
+
/** Builds a distinct client fixture for the given sequence number. Defaults to a client granting the device-code grant. */
|
|
32
|
+
makeClient?(seq: number): C;
|
|
33
|
+
/** Overrides the name passed to the outer `describe` block. */
|
|
34
|
+
describeName?: string;
|
|
35
|
+
}
|
|
36
|
+
/** Runs the {@link DeviceAuthorizationServiceInterface} contract suite. */
|
|
37
|
+
export declare function runDeviceAuthorizationServiceContractTests<C extends ClientInterface, U extends MemoryUserShape, S extends AbstractScope = BasicScope>(options: DeviceAuthorizationServiceContractOptions<C, U, S>): void;
|