@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,508 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token reader that validates JWT access tokens locally against the
|
|
3
|
+
* issuer's published JWKS, with no per-request network hop.
|
|
4
|
+
*
|
|
5
|
+
* This is the validating half of RFC 9068 (`at+jwt`): the authorization
|
|
6
|
+
* server signs access tokens and publishes its public keys at a `jwks_uri`;
|
|
7
|
+
* the resource server fetches those keys once, caches them, and verifies
|
|
8
|
+
* every subsequent token in-process. Signature verification uses Web Crypto
|
|
9
|
+
* only — the package takes no JOSE dependency.
|
|
10
|
+
*
|
|
11
|
+
* @see https://datatracker.ietf.org/doc/html/rfc9068
|
|
12
|
+
* @see https://datatracker.ietf.org/doc/html/rfc7517
|
|
13
|
+
* @module
|
|
14
|
+
*/
|
|
15
|
+
import { ServerError, TemporarilyUnavailableError } from "../errors.js";
|
|
16
|
+
import { BasicScope } from "../models/scope.js";
|
|
17
|
+
import { toArrayBuffer } from "../utils/_buffer.js";
|
|
18
|
+
import { base64urlDecode } from "../utils/crypto.js";
|
|
19
|
+
const ALGORITHMS = {
|
|
20
|
+
ES256: {
|
|
21
|
+
kty: "EC",
|
|
22
|
+
crv: "P-256",
|
|
23
|
+
importParams: { name: "ECDSA", namedCurve: "P-256" },
|
|
24
|
+
verifyParams: { name: "ECDSA", hash: "SHA-256" },
|
|
25
|
+
},
|
|
26
|
+
ES384: {
|
|
27
|
+
kty: "EC",
|
|
28
|
+
crv: "P-384",
|
|
29
|
+
importParams: { name: "ECDSA", namedCurve: "P-384" },
|
|
30
|
+
verifyParams: { name: "ECDSA", hash: "SHA-384" },
|
|
31
|
+
},
|
|
32
|
+
ES512: {
|
|
33
|
+
kty: "EC",
|
|
34
|
+
crv: "P-521",
|
|
35
|
+
importParams: { name: "ECDSA", namedCurve: "P-521" },
|
|
36
|
+
verifyParams: { name: "ECDSA", hash: "SHA-512" },
|
|
37
|
+
},
|
|
38
|
+
RS256: {
|
|
39
|
+
kty: "RSA",
|
|
40
|
+
importParams: { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
|
|
41
|
+
verifyParams: { name: "RSASSA-PKCS1-v1_5" },
|
|
42
|
+
},
|
|
43
|
+
RS384: {
|
|
44
|
+
kty: "RSA",
|
|
45
|
+
importParams: { name: "RSASSA-PKCS1-v1_5", hash: "SHA-384" },
|
|
46
|
+
verifyParams: { name: "RSASSA-PKCS1-v1_5" },
|
|
47
|
+
},
|
|
48
|
+
RS512: {
|
|
49
|
+
kty: "RSA",
|
|
50
|
+
importParams: { name: "RSASSA-PKCS1-v1_5", hash: "SHA-512" },
|
|
51
|
+
verifyParams: { name: "RSASSA-PKCS1-v1_5" },
|
|
52
|
+
},
|
|
53
|
+
PS256: {
|
|
54
|
+
kty: "RSA",
|
|
55
|
+
importParams: { name: "RSA-PSS", hash: "SHA-256" },
|
|
56
|
+
verifyParams: { name: "RSA-PSS", saltLength: 32 },
|
|
57
|
+
},
|
|
58
|
+
PS384: {
|
|
59
|
+
kty: "RSA",
|
|
60
|
+
importParams: { name: "RSA-PSS", hash: "SHA-384" },
|
|
61
|
+
verifyParams: { name: "RSA-PSS", saltLength: 48 },
|
|
62
|
+
},
|
|
63
|
+
PS512: {
|
|
64
|
+
kty: "RSA",
|
|
65
|
+
importParams: { name: "RSA-PSS", hash: "SHA-512" },
|
|
66
|
+
verifyParams: { name: "RSA-PSS", saltLength: 64 },
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
const encoder = new TextEncoder();
|
|
70
|
+
const decoder = new TextDecoder();
|
|
71
|
+
function parseJws(jwt) {
|
|
72
|
+
const parts = jwt.split(".");
|
|
73
|
+
if (parts.length !== 3)
|
|
74
|
+
return undefined;
|
|
75
|
+
const [headerPart, payloadPart, signaturePart] = parts;
|
|
76
|
+
try {
|
|
77
|
+
const header = JSON.parse(decoder.decode(base64urlDecode(headerPart)));
|
|
78
|
+
const payload = JSON.parse(decoder.decode(base64urlDecode(payloadPart)));
|
|
79
|
+
if (!isRecord(header) || !isRecord(payload))
|
|
80
|
+
return undefined;
|
|
81
|
+
return {
|
|
82
|
+
header,
|
|
83
|
+
payload,
|
|
84
|
+
signingInput: toArrayBuffer(encoder.encode(`${headerPart}.${payloadPart}`)),
|
|
85
|
+
signature: toArrayBuffer(base64urlDecode(signaturePart)),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function isRecord(value) {
|
|
93
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
94
|
+
}
|
|
95
|
+
const MEDIA_TYPE_PREFIX = "application/";
|
|
96
|
+
function normalizeType(value) {
|
|
97
|
+
const lowered = value.toLowerCase();
|
|
98
|
+
return lowered.startsWith(MEDIA_TYPE_PREFIX)
|
|
99
|
+
? lowered.slice(MEDIA_TYPE_PREFIX.length)
|
|
100
|
+
: lowered;
|
|
101
|
+
}
|
|
102
|
+
function verifyJwkOf(jwk) {
|
|
103
|
+
return jwk.kty === "EC"
|
|
104
|
+
? { kty: jwk.kty, crv: jwk.crv, x: jwk.x, y: jwk.y, ext: true }
|
|
105
|
+
: { kty: jwk.kty, n: jwk.n, e: jwk.e, ext: true };
|
|
106
|
+
}
|
|
107
|
+
function usableForVerification(jwk) {
|
|
108
|
+
if (jwk.kty !== "EC" && jwk.kty !== "RSA")
|
|
109
|
+
return false;
|
|
110
|
+
if (jwk.use !== undefined && jwk.use !== "sig")
|
|
111
|
+
return false;
|
|
112
|
+
if (jwk.key_ops !== undefined) {
|
|
113
|
+
if (!Array.isArray(jwk.key_ops) || !jwk.key_ops.includes("verify")) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (jwk.alg !== undefined) {
|
|
118
|
+
if (typeof jwk.alg !== "string" || !(jwk.alg in ALGORITHMS))
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
const ID_TOKEN_ONLY_CLAIMS = ["nonce", "at_hash", "c_hash", "s_hash"];
|
|
124
|
+
function metadataUrlsFor(issuer) {
|
|
125
|
+
let url;
|
|
126
|
+
try {
|
|
127
|
+
url = new URL(issuer);
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
throw new TypeError("`issuer` must be an absolute URL");
|
|
131
|
+
}
|
|
132
|
+
const path = url.pathname.replace(/\/$/, "");
|
|
133
|
+
return [
|
|
134
|
+
...new Set([
|
|
135
|
+
`${url.origin}/.well-known/oauth-authorization-server${path}`,
|
|
136
|
+
`${url.origin}/.well-known/openid-configuration${path}`,
|
|
137
|
+
`${url.origin}${path}/.well-known/openid-configuration`,
|
|
138
|
+
]),
|
|
139
|
+
];
|
|
140
|
+
}
|
|
141
|
+
function scopeTextOf(claims) {
|
|
142
|
+
if (typeof claims.scope === "string" && claims.scope)
|
|
143
|
+
return claims.scope;
|
|
144
|
+
if (Array.isArray(claims.scp))
|
|
145
|
+
return claims.scp.join(" ") || undefined;
|
|
146
|
+
if (typeof claims.scp === "string" && claims.scp)
|
|
147
|
+
return claims.scp;
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Validates JWT access tokens (RFC 9068) against the issuer's remote JWKS.
|
|
152
|
+
*
|
|
153
|
+
* Use this with {@linkcode ResourceServer} when the authorization server
|
|
154
|
+
* issues **signed** access tokens: keys are fetched once and cached on the
|
|
155
|
+
* instance, so validation costs no network round-trip per request — the
|
|
156
|
+
* trade-off against {@linkcode IntrospectionTokenReader}, which sees
|
|
157
|
+
* revocation immediately but calls the authorization server every time. A
|
|
158
|
+
* revoked JWT stays valid here until it expires, so keep access-token
|
|
159
|
+
* lifetimes short, or introspect on the endpoints where revocation latency
|
|
160
|
+
* matters.
|
|
161
|
+
*
|
|
162
|
+
* A token is accepted only when its signature verifies against a published
|
|
163
|
+
* key whose algorithm the header matches, and its `typ`, `iss`, `aud`, `exp`
|
|
164
|
+
* and `nbf` all satisfy the configuration. Anything else is `undefined` —
|
|
165
|
+
* the same `invalid_token` surface as an unknown opaque token. Unreachable
|
|
166
|
+
* key material throws instead, so a down issuer is never reported as a bad
|
|
167
|
+
* token.
|
|
168
|
+
*
|
|
169
|
+
* **Token substitution.** An access token must never be confused with
|
|
170
|
+
* another JWT the same issuer signs. Three checks enforce that
|
|
171
|
+
* independently: `typ` must be `at+jwt`
|
|
172
|
+
* ({@linkcode JwksTokenReaderOptions.types}), the client-id claim must be
|
|
173
|
+
* present ({@linkcode JwksTokenReaderOptions.clientIdClaim}, REQUIRED by
|
|
174
|
+
* RFC 9068 §2.2 and absent from an `id_token`), and `id_token`-only claims
|
|
175
|
+
* (`nonce`, `at_hash`, `c_hash`, `s_hash`) are rejected outright.
|
|
176
|
+
* `auth_time`, `acr` and `amr` are **not** in that list — RFC 9068 §2.2.1
|
|
177
|
+
* permits them on an access token. A `crit` header is rejected per RFC 7515
|
|
178
|
+
* §4.1.11, since this reader understands no JWS extensions.
|
|
179
|
+
*
|
|
180
|
+
* The cache lives on the instance: construct one reader and reuse it.
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* ```ts
|
|
184
|
+
* import { JwksTokenReader, ResourceServer } from "@udibo/oauth2/server/resource";
|
|
185
|
+
*
|
|
186
|
+
* interface MyClient { id: string }
|
|
187
|
+
* interface MyUser { id: string }
|
|
188
|
+
*
|
|
189
|
+
* const tokenReader = new JwksTokenReader<MyClient, MyUser>({
|
|
190
|
+
* issuer: "https://auth.example.com",
|
|
191
|
+
* audience: "https://api.example.com",
|
|
192
|
+
* getClient: (claims) => ({ id: String(claims.client_id) }),
|
|
193
|
+
* getUser: (claims) => claims.sub ? { id: claims.sub } : undefined,
|
|
194
|
+
* });
|
|
195
|
+
*
|
|
196
|
+
* const resourceServer = new ResourceServer<MyClient, MyUser>({
|
|
197
|
+
* resolve: () => ({ services: { tokenService: tokenReader } }),
|
|
198
|
+
* });
|
|
199
|
+
* ```
|
|
200
|
+
*/
|
|
201
|
+
export class JwksTokenReader {
|
|
202
|
+
#issuer;
|
|
203
|
+
#audience;
|
|
204
|
+
#algorithms;
|
|
205
|
+
#types;
|
|
206
|
+
#clientIdClaim;
|
|
207
|
+
#clockSkewSeconds;
|
|
208
|
+
#cacheMaxAgeMs;
|
|
209
|
+
#minFetchIntervalMs;
|
|
210
|
+
#fetchTimeoutMs;
|
|
211
|
+
#metadataUrls;
|
|
212
|
+
#Scope;
|
|
213
|
+
#getClient;
|
|
214
|
+
#getUser;
|
|
215
|
+
#customFetch;
|
|
216
|
+
#jwksUri;
|
|
217
|
+
#entries = [];
|
|
218
|
+
#fetchedAt = 0;
|
|
219
|
+
#lastAttemptAt = 0;
|
|
220
|
+
#inFlight;
|
|
221
|
+
/**
|
|
222
|
+
* Defaults: every Web Crypto asymmetric algorithm accepted, `at+jwt`
|
|
223
|
+
* required as `typ`, `client_id` as the required client-id claim, 30
|
|
224
|
+
* seconds of clock skew, a 10-minute cache with a 30-second minimum
|
|
225
|
+
* between fetches, a 5-second request deadline, {@linkcode BasicScope},
|
|
226
|
+
* and the global `fetch`.
|
|
227
|
+
*
|
|
228
|
+
* @throws {TypeError} If `issuer` is not an absolute URL, `audience` is
|
|
229
|
+
* empty, or `algorithms` names an algorithm this package cannot verify
|
|
230
|
+
* (a silently ignored entry would read as a policy that isn't enforced).
|
|
231
|
+
*/
|
|
232
|
+
constructor(options) {
|
|
233
|
+
this.#issuer = options.issuer;
|
|
234
|
+
this.#metadataUrls = metadataUrlsFor(options.issuer);
|
|
235
|
+
this.#audience = Array.isArray(options.audience)
|
|
236
|
+
? [...options.audience]
|
|
237
|
+
: [options.audience];
|
|
238
|
+
if (this.#audience.length === 0) {
|
|
239
|
+
throw new TypeError("`audience` must name at least one value");
|
|
240
|
+
}
|
|
241
|
+
this.#algorithms = options.algorithms ?? Object.keys(ALGORITHMS);
|
|
242
|
+
for (const algorithm of this.#algorithms) {
|
|
243
|
+
if (!(algorithm in ALGORITHMS)) {
|
|
244
|
+
throw new TypeError(`unsupported algorithm: ${algorithm}`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
this.#types = (options.types ?? ["at+jwt"]).map(normalizeType);
|
|
248
|
+
this.#clientIdClaim = options.clientIdClaim ?? "client_id";
|
|
249
|
+
this.#clockSkewSeconds = options.clockSkewSeconds ?? 30;
|
|
250
|
+
this.#cacheMaxAgeMs = options.cacheMaxAgeMs ?? 600_000;
|
|
251
|
+
this.#minFetchIntervalMs = options.minFetchIntervalMs ?? 30_000;
|
|
252
|
+
this.#fetchTimeoutMs = options.fetchTimeoutMs ?? 5_000;
|
|
253
|
+
this.#Scope = options.Scope ??
|
|
254
|
+
BasicScope;
|
|
255
|
+
this.#getClient = options.getClient;
|
|
256
|
+
this.#getUser = options.getUser;
|
|
257
|
+
this.#customFetch = options.fetch;
|
|
258
|
+
this.#jwksUri = options.jwksUri;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Validates a JWT access token against the cached JWKS.
|
|
262
|
+
*
|
|
263
|
+
* Refreshes the keys when no published key verifies the signature — at
|
|
264
|
+
* most one fetch per {@linkcode
|
|
265
|
+
* JwksTokenReaderOptions.minFetchIntervalMs}, shared by concurrent
|
|
266
|
+
* callers. A token whose `kid` matched a cached key and still failed
|
|
267
|
+
* verification is a forgery, not a rotation, and provokes no fetch. A
|
|
268
|
+
* stale cache refreshes in the background: requests are served from the
|
|
269
|
+
* cached keys rather than blocked on the issuer.
|
|
270
|
+
*
|
|
271
|
+
* @returns The token with client/user/scope info, or `undefined` when the
|
|
272
|
+
* token is malformed, signed by an unknown or unaccepted key, or fails
|
|
273
|
+
* any claim check.
|
|
274
|
+
* @throws {TemporarilyUnavailableError} If the JWKS (or discovery
|
|
275
|
+
* document) is unreachable or returns a 5xx **and** no cached keys are
|
|
276
|
+
* available — a down issuer must be distinguishable from a genuinely
|
|
277
|
+
* invalid token, not collapsed into it. Once keys are cached, a failed
|
|
278
|
+
* refresh is absorbed and the cached keys keep serving.
|
|
279
|
+
* @throws {ServerError} If the JWKS endpoint returns a 4xx or a body that
|
|
280
|
+
* is not a JWKS, or discovery reports no `jwks_uri` — a
|
|
281
|
+
* misconfiguration, not a verdict on the caller's token.
|
|
282
|
+
*/
|
|
283
|
+
async getToken(accessToken) {
|
|
284
|
+
const parsed = parseJws(accessToken);
|
|
285
|
+
if (!parsed)
|
|
286
|
+
return undefined;
|
|
287
|
+
const { alg, kid, typ, crit } = parsed.header;
|
|
288
|
+
if (crit !== undefined)
|
|
289
|
+
return undefined;
|
|
290
|
+
if (typeof alg !== "string" || !this.#algorithms.includes(alg)) {
|
|
291
|
+
return undefined;
|
|
292
|
+
}
|
|
293
|
+
if (!this.#acceptsType(typ))
|
|
294
|
+
return undefined;
|
|
295
|
+
const verified = await this.#verified(parsed, alg, typeof kid === "string" ? kid : undefined);
|
|
296
|
+
if (!verified)
|
|
297
|
+
return undefined;
|
|
298
|
+
const claims = parsed.payload;
|
|
299
|
+
if (!this.#validClaims(claims))
|
|
300
|
+
return undefined;
|
|
301
|
+
const [client, user] = await Promise.all([
|
|
302
|
+
this.#getClient(claims),
|
|
303
|
+
this.#getUser?.(claims),
|
|
304
|
+
]);
|
|
305
|
+
const scopeText = scopeTextOf(claims);
|
|
306
|
+
return {
|
|
307
|
+
accessToken,
|
|
308
|
+
accessTokenExpiresAt: new Date(claims.exp * 1000),
|
|
309
|
+
client,
|
|
310
|
+
user,
|
|
311
|
+
scope: scopeText ? new this.#Scope(scopeText) : undefined,
|
|
312
|
+
claims: claims,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
#acceptsType(typ) {
|
|
316
|
+
if (this.#types.length === 0)
|
|
317
|
+
return true;
|
|
318
|
+
if (typeof typ !== "string")
|
|
319
|
+
return false;
|
|
320
|
+
return this.#types.includes(normalizeType(typ));
|
|
321
|
+
}
|
|
322
|
+
#validClaims(claims) {
|
|
323
|
+
if (claims.iss !== this.#issuer)
|
|
324
|
+
return false;
|
|
325
|
+
const clientId = claims[this.#clientIdClaim];
|
|
326
|
+
if (typeof clientId !== "string" || !clientId)
|
|
327
|
+
return false;
|
|
328
|
+
for (const claim of ID_TOKEN_ONLY_CLAIMS) {
|
|
329
|
+
if (claims[claim] !== undefined)
|
|
330
|
+
return false;
|
|
331
|
+
}
|
|
332
|
+
const audience = Array.isArray(claims.aud)
|
|
333
|
+
? claims.aud
|
|
334
|
+
: typeof claims.aud === "string"
|
|
335
|
+
? [claims.aud]
|
|
336
|
+
: [];
|
|
337
|
+
if (!audience.some((value) => this.#audience.includes(value)))
|
|
338
|
+
return false;
|
|
339
|
+
const now = Math.floor(Date.now() / 1000);
|
|
340
|
+
if (typeof claims.exp !== "number" || !Number.isFinite(claims.exp)) {
|
|
341
|
+
return false;
|
|
342
|
+
}
|
|
343
|
+
if (now >= claims.exp + this.#clockSkewSeconds)
|
|
344
|
+
return false;
|
|
345
|
+
if (claims.nbf !== undefined) {
|
|
346
|
+
if (typeof claims.nbf !== "number" || !Number.isFinite(claims.nbf)) {
|
|
347
|
+
return false;
|
|
348
|
+
}
|
|
349
|
+
if (claims.nbf - this.#clockSkewSeconds > now)
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
return true;
|
|
353
|
+
}
|
|
354
|
+
async #verifySignature(candidates, alg, parsed) {
|
|
355
|
+
const { verifyParams } = ALGORITHMS[alg];
|
|
356
|
+
for (const entry of candidates) {
|
|
357
|
+
const key = await this.#importKey(entry, alg);
|
|
358
|
+
if (!key)
|
|
359
|
+
continue;
|
|
360
|
+
const valid = await crypto.subtle.verify(verifyParams, key, parsed.signature, parsed.signingInput);
|
|
361
|
+
if (valid)
|
|
362
|
+
return true;
|
|
363
|
+
}
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
366
|
+
async #importKey(entry, alg) {
|
|
367
|
+
const cached = entry.imported.get(alg);
|
|
368
|
+
if (cached)
|
|
369
|
+
return cached;
|
|
370
|
+
try {
|
|
371
|
+
const key = await crypto.subtle.importKey("jwk", verifyJwkOf(entry.jwk), ALGORITHMS[alg].importParams, false, ["verify"]);
|
|
372
|
+
entry.imported.set(alg, key);
|
|
373
|
+
return key;
|
|
374
|
+
}
|
|
375
|
+
catch {
|
|
376
|
+
return undefined;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
#match(kid, alg) {
|
|
380
|
+
const spec = ALGORITHMS[alg];
|
|
381
|
+
return this.#entries.filter((entry) => {
|
|
382
|
+
if (kid !== undefined && entry.kid !== kid)
|
|
383
|
+
return false;
|
|
384
|
+
if (entry.alg !== undefined && entry.alg !== alg)
|
|
385
|
+
return false;
|
|
386
|
+
if (entry.jwk.kty !== spec.kty)
|
|
387
|
+
return false;
|
|
388
|
+
if (spec.crv !== undefined && entry.jwk.crv !== spec.crv)
|
|
389
|
+
return false;
|
|
390
|
+
return true;
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
async #verified(parsed, alg, kid) {
|
|
394
|
+
await this.#ensureKeys();
|
|
395
|
+
const candidates = this.#match(kid, alg);
|
|
396
|
+
if (await this.#verifySignature(candidates, alg, parsed))
|
|
397
|
+
return true;
|
|
398
|
+
if (kid !== undefined && candidates.length > 0)
|
|
399
|
+
return false;
|
|
400
|
+
await this.#refreshQuietly();
|
|
401
|
+
return await this.#verifySignature(this.#match(kid, alg), alg, parsed);
|
|
402
|
+
}
|
|
403
|
+
async #ensureKeys() {
|
|
404
|
+
if (this.#entries.length === 0) {
|
|
405
|
+
await this.#refresh();
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
if (Date.now() - this.#fetchedAt > this.#cacheMaxAgeMs) {
|
|
409
|
+
void this.#refreshQuietly();
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
async #refreshQuietly() {
|
|
413
|
+
await this.#refresh().catch(() => { });
|
|
414
|
+
}
|
|
415
|
+
async #refresh() {
|
|
416
|
+
if (this.#inFlight)
|
|
417
|
+
return await this.#inFlight;
|
|
418
|
+
if (Date.now() - this.#lastAttemptAt < this.#minFetchIntervalMs) {
|
|
419
|
+
if (this.#entries.length === 0) {
|
|
420
|
+
throw new TemporarilyUnavailableError("jwks is unavailable");
|
|
421
|
+
}
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
const promise = this.#load();
|
|
425
|
+
this.#inFlight = promise;
|
|
426
|
+
try {
|
|
427
|
+
await promise;
|
|
428
|
+
}
|
|
429
|
+
finally {
|
|
430
|
+
this.#inFlight = undefined;
|
|
431
|
+
this.#lastAttemptAt = Date.now();
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
async #load() {
|
|
435
|
+
const jwksUri = this.#jwksUri ?? await this.#discoverJwksUri();
|
|
436
|
+
const body = await this.#fetchJson(jwksUri, "jwks");
|
|
437
|
+
if (!isRecord(body) || !Array.isArray(body.keys)) {
|
|
438
|
+
throw new ServerError("jwks response has no `keys` array");
|
|
439
|
+
}
|
|
440
|
+
this.#entries = body.keys
|
|
441
|
+
.filter((jwk) => isRecord(jwk))
|
|
442
|
+
.filter(usableForVerification)
|
|
443
|
+
.map((jwk) => ({
|
|
444
|
+
jwk,
|
|
445
|
+
kid: typeof jwk.kid === "string" ? jwk.kid : undefined,
|
|
446
|
+
alg: typeof jwk.alg === "string" ? jwk.alg : undefined,
|
|
447
|
+
imported: new Map(),
|
|
448
|
+
}));
|
|
449
|
+
this.#fetchedAt = Date.now();
|
|
450
|
+
}
|
|
451
|
+
async #discoverJwksUri() {
|
|
452
|
+
let transientError;
|
|
453
|
+
let lastError;
|
|
454
|
+
for (const url of this.#metadataUrls) {
|
|
455
|
+
try {
|
|
456
|
+
const metadata = await this.#fetchJson(url, "discovery");
|
|
457
|
+
const jwksUri = isRecord(metadata) ? metadata.jwks_uri : undefined;
|
|
458
|
+
if (typeof jwksUri !== "string") {
|
|
459
|
+
throw new ServerError("discovery metadata has no `jwks_uri`");
|
|
460
|
+
}
|
|
461
|
+
this.#jwksUri = jwksUri;
|
|
462
|
+
return jwksUri;
|
|
463
|
+
}
|
|
464
|
+
catch (error) {
|
|
465
|
+
if (error instanceof TemporarilyUnavailableError &&
|
|
466
|
+
transientError === undefined) {
|
|
467
|
+
transientError = error;
|
|
468
|
+
}
|
|
469
|
+
lastError = error;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
throw transientError ?? lastError ?? new ServerError("discovery failed");
|
|
473
|
+
}
|
|
474
|
+
async #fetchJson(url, label) {
|
|
475
|
+
const fetchImpl = this.#customFetch ?? globalThis.fetch;
|
|
476
|
+
const controller = new AbortController();
|
|
477
|
+
const deadline = setTimeout(() => controller.abort(), this.#fetchTimeoutMs);
|
|
478
|
+
try {
|
|
479
|
+
let response;
|
|
480
|
+
try {
|
|
481
|
+
response = await fetchImpl(url, {
|
|
482
|
+
headers: { "Accept": "application/json" },
|
|
483
|
+
signal: controller.signal,
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
catch (cause) {
|
|
487
|
+
throw new TemporarilyUnavailableError(`${label} request failed`, {
|
|
488
|
+
cause,
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
if (!response.ok) {
|
|
492
|
+
await response.body?.cancel();
|
|
493
|
+
throw response.status >= 500
|
|
494
|
+
? new TemporarilyUnavailableError(`${label} request failed (HTTP ${response.status})`)
|
|
495
|
+
: new ServerError(`${label} request failed (HTTP ${response.status})`);
|
|
496
|
+
}
|
|
497
|
+
try {
|
|
498
|
+
return await response.json();
|
|
499
|
+
}
|
|
500
|
+
catch (cause) {
|
|
501
|
+
throw new ServerError(`${label} response was not JSON`, { cause });
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
finally {
|
|
505
|
+
clearTimeout(deadline);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for OAuth2 servers (both resource and authorization servers).
|
|
3
|
+
*
|
|
4
|
+
* This module exports errors, models, service interfaces, and utilities
|
|
5
|
+
* common to both server types. Import server-specific classes from:
|
|
6
|
+
* - `@udibo/oauth2/server/resource` for ResourceServer
|
|
7
|
+
* - `@udibo/oauth2/server/authorization` for AuthorizationServer
|
|
8
|
+
*
|
|
9
|
+
* @module
|
|
10
|
+
*/
|
|
11
|
+
import "../_dnt.polyfills.js";
|
|
12
|
+
export type { ClientCredentials, ClientInterface } from "../models/client.js";
|
|
13
|
+
export type { AuthenticationContext } from "../models/authentication.js";
|
|
14
|
+
export type { IntrospectionResponse, RefreshToken, Token, TokenResponse, } from "../models/token.js";
|
|
15
|
+
export { AbstractScope, BasicScope } from "../models/scope.js";
|
|
16
|
+
export type { ScopeConstructor } from "../models/scope.js";
|
|
17
|
+
export { Authorization, authorizationFromClaims, } from "../models/authorization.js";
|
|
18
|
+
export type { AuthorizationFailure, AuthorizationInit, OrganizationContext, RequireConditions, } from "../models/authorization.js";
|
|
19
|
+
export { AccessDeniedError, AuthorizationPendingError, ExpiredTokenError, InsufficientPermissionsError, InsufficientScopeError, InvalidClientError, InvalidGrantError, InvalidRequestError, InvalidScopeError, InvalidTokenError, isOAuth2Error, OAuth2Error, ServerError, SlowDownError, TemporarilyUnavailableError, toOAuth2Error, UnauthorizedClientError, UnsupportedGrantTypeError, UnsupportedResponseTypeError, UnsupportedTokenTypeError, } from "../errors.js";
|
|
20
|
+
export type { OAuth2ErrorClass, OAuth2ProblemDetailsExtensions, } from "../errors.js";
|
|
21
|
+
export type { TokenReaderInterface } from "./services/token.js";
|
|
22
|
+
export { checkRedirectUriPattern, isRedirectUriPattern, matchRedirectUri, } from "./redirect-uri.js";
|
|
23
|
+
export type { IsPublicSuffix, RedirectUriPatternRule, RedirectUriPatternViolation, } from "./redirect-uri.js";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for OAuth2 servers (both resource and authorization servers).
|
|
3
|
+
*
|
|
4
|
+
* This module exports errors, models, service interfaces, and utilities
|
|
5
|
+
* common to both server types. Import server-specific classes from:
|
|
6
|
+
* - `@udibo/oauth2/server/resource` for ResourceServer
|
|
7
|
+
* - `@udibo/oauth2/server/authorization` for AuthorizationServer
|
|
8
|
+
*
|
|
9
|
+
* @module
|
|
10
|
+
*/
|
|
11
|
+
import "../_dnt.polyfills.js";
|
|
12
|
+
export { AbstractScope, BasicScope } from "../models/scope.js";
|
|
13
|
+
export { Authorization, authorizationFromClaims, } from "../models/authorization.js";
|
|
14
|
+
export { AccessDeniedError, AuthorizationPendingError, ExpiredTokenError, InsufficientPermissionsError, InsufficientScopeError, InvalidClientError, InvalidGrantError, InvalidRequestError, InvalidScopeError, InvalidTokenError, isOAuth2Error, OAuth2Error, ServerError, SlowDownError, TemporarilyUnavailableError, toOAuth2Error, UnauthorizedClientError, UnsupportedGrantTypeError, UnsupportedResponseTypeError, UnsupportedTokenTypeError, } from "../errors.js";
|
|
15
|
+
export { checkRedirectUriPattern, isRedirectUriPattern, matchRedirectUri, } from "./redirect-uri.js";
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The bundled Public Suffix List, as an {@link isPublicSuffix} implementation.
|
|
3
|
+
*
|
|
4
|
+
* A *public suffix* is a domain you can only register names **under**, never
|
|
5
|
+
* **at** — `com`, `co.uk`, `deno.net`, `s3.amazonaws.com`. The authorization
|
|
6
|
+
* server needs the distinction to bound a wildcard redirect URI: everything a
|
|
7
|
+
* wildcard covers must belong to one registrant, which is only true when the
|
|
8
|
+
* domain beneath it is a registrable name rather than a shared suffix.
|
|
9
|
+
*
|
|
10
|
+
* This entrypoint is separate from `@udibo/oauth2/server` on purpose. The list
|
|
11
|
+
* is roughly 150 kB of generated source, and a server that never accepts
|
|
12
|
+
* wildcard registrations should not carry it — so
|
|
13
|
+
* `checkRedirectUriPattern` takes the check as an injected seam and
|
|
14
|
+
* refuses every pattern when none is supplied. Wire it once, where you build
|
|
15
|
+
* the authorization server:
|
|
16
|
+
*
|
|
17
|
+
* ```ts
|
|
18
|
+
* import { AuthorizationServer } from "@udibo/oauth2/server/authorization";
|
|
19
|
+
* import { isPublicSuffix } from "@udibo/oauth2/server/public-suffix";
|
|
20
|
+
*
|
|
21
|
+
* const server = new AuthorizationServer({ isPublicSuffix, ...options });
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* The snapshot covers both the ICANN and the private sections. The private
|
|
25
|
+
* section is the security-relevant half here: every shared hosting platform a
|
|
26
|
+
* wildcard must not straddle (`github.io`, `vercel.app`, `blob.core.windows.net`)
|
|
27
|
+
* is listed there and nowhere else.
|
|
28
|
+
*
|
|
29
|
+
* Regenerate it with `deno task psl:update`; `deno task psl:check` fails when
|
|
30
|
+
* the snapshot has drifted from publicsuffix.org and runs on a schedule in CI.
|
|
31
|
+
*
|
|
32
|
+
* @module
|
|
33
|
+
*/
|
|
34
|
+
import "../../_dnt.polyfills.js";
|
|
35
|
+
import { publicSuffixListInfo } from "./rules.js";
|
|
36
|
+
export { publicSuffixListInfo };
|
|
37
|
+
/**
|
|
38
|
+
* Whether a domain is a public suffix — a name others register *under* rather
|
|
39
|
+
* than a registrable domain someone owns.
|
|
40
|
+
*
|
|
41
|
+
* Implements the publicsuffix.org algorithm against the bundled snapshot,
|
|
42
|
+
* including wildcard rules (`*.ck`), exception rules (`!www.ck`), and the
|
|
43
|
+
* implicit `*` rule that makes any unlisted top-level domain a public suffix.
|
|
44
|
+
* The argument may be Unicode or punycode, upper or lower case, with or
|
|
45
|
+
* without a trailing dot; a host that cannot be normalized (empty, or with an
|
|
46
|
+
* empty label) is reported as a public suffix so a caller bounding a wildcard
|
|
47
|
+
* fails closed.
|
|
48
|
+
*
|
|
49
|
+
* @param domain The hostname to classify.
|
|
50
|
+
* @returns `true` when the whole domain is a public suffix.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```ts
|
|
54
|
+
* import { isPublicSuffix } from "@udibo/oauth2/server/public-suffix";
|
|
55
|
+
*
|
|
56
|
+
* isPublicSuffix("co.uk"); // true
|
|
57
|
+
* isPublicSuffix("deno.net"); // true — anyone can take a subdomain
|
|
58
|
+
* isPublicSuffix("myorg.deno.net"); // false — one organization's namespace
|
|
59
|
+
* isPublicSuffix("example.com"); // false
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export declare function isPublicSuffix(domain: string): boolean;
|