@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,331 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redirect URI matching for the authorization server.
|
|
3
|
+
*
|
|
4
|
+
* RFC 6749 §3.1.2.3 and the OAuth 2.0 Security BCP require `redirect_uri` to be
|
|
5
|
+
* compared against a pre-registered value by exact string match, and that is
|
|
6
|
+
* what {@link matchRedirectUri} does for every ordinary registration. A
|
|
7
|
+
* registration may instead be a **pattern** — a single `*` in the leftmost host
|
|
8
|
+
* label — which lets one registration cover the per-deploy hostnames a preview
|
|
9
|
+
* environment produces.
|
|
10
|
+
*
|
|
11
|
+
* Patterns are deliberately narrow, because a wildcard that reaches beyond the
|
|
12
|
+
* registrant's own namespace hands authorization codes to whoever can obtain a
|
|
13
|
+
* neighbouring host. The rules are enforced by {@link checkRedirectUriPattern},
|
|
14
|
+
* and {@link matchRedirectUri} refuses to match a pattern that violates any of
|
|
15
|
+
* them, so an invalid pattern in storage is inert rather than dangerous.
|
|
16
|
+
*
|
|
17
|
+
* The narrowest of those rules — the domain under the wildcard has to be one
|
|
18
|
+
* registrant's — is decided by the Public Suffix List, injected as
|
|
19
|
+
* {@link IsPublicSuffix} so this module carries no data. Pass `isPublicSuffix`
|
|
20
|
+
* from `@udibo/oauth2/server/public-suffix`; a server that never accepts
|
|
21
|
+
* patterns can leave it out and refuse them all.
|
|
22
|
+
*
|
|
23
|
+
* A registration whose host is a **loopback IP literal** (`127.0.0.1` or
|
|
24
|
+
* `[::1]`) is the one other departure from exact matching: RFC 8252 §7.3
|
|
25
|
+
* requires the authorization server to allow any port at request time, because
|
|
26
|
+
* a native app binds an OS-assigned port it cannot know when it registers.
|
|
27
|
+
* Only the port is relaxed, and only for the IP literals — `localhost` is a
|
|
28
|
+
* name resolved by a host file this server does not control (RFC 8252 §8.3),
|
|
29
|
+
* so it keeps exact matching.
|
|
30
|
+
*
|
|
31
|
+
* This module has no notion of environments. Deciding *which* clients may
|
|
32
|
+
* register a pattern at all is the application's call — the usual policy is to
|
|
33
|
+
* permit them for development registrations and refuse them for production
|
|
34
|
+
* ones, which an application enforces by validating at registration time and by
|
|
35
|
+
* not handing a pattern to the authorization server for a production client.
|
|
36
|
+
* The loopback relaxation is likewise only reachable once the application has
|
|
37
|
+
* chosen to register a loopback URI.
|
|
38
|
+
*
|
|
39
|
+
* @module
|
|
40
|
+
*/
|
|
41
|
+
/**
|
|
42
|
+
* A label used to ask whether the *children* of a parent domain are public
|
|
43
|
+
* suffixes, which is a different question from whether the parent is one.
|
|
44
|
+
*
|
|
45
|
+
* `r.appspot.com` is a registrable domain by the list's reckoning, but the rule
|
|
46
|
+
* `*.r.appspot.com` makes every one of its subdomains a public suffix belonging
|
|
47
|
+
* to a different registrant — so a wildcard over it hands codes to strangers
|
|
48
|
+
* even though the parent itself passes. Probing with a label no registry has
|
|
49
|
+
* ever issued answers that in one lookup.
|
|
50
|
+
*/
|
|
51
|
+
const CHILD_PROBE_LABEL = "udibo-public-suffix-probe";
|
|
52
|
+
/**
|
|
53
|
+
* Split a hostname into labels, or `null` when it is not usable for matching.
|
|
54
|
+
*
|
|
55
|
+
* A single trailing dot is the DNS root and is dropped, so `example.com.` and
|
|
56
|
+
* `example.com` are the same host — without this, the extra empty label would
|
|
57
|
+
* shift every label the public-suffix check looks at and let `*.deno.net.`
|
|
58
|
+
* through. Any *other* empty label (`a..b`, a leading `.`) is refused outright
|
|
59
|
+
* rather than normalized: it cannot resolve, and an empty leftmost label would
|
|
60
|
+
* otherwise satisfy any prefix/suffix test.
|
|
61
|
+
*/
|
|
62
|
+
function hostLabels(hostname) {
|
|
63
|
+
const normalized = hostname.endsWith(".") ? hostname.slice(0, -1) : hostname;
|
|
64
|
+
if (normalized.length === 0)
|
|
65
|
+
return null;
|
|
66
|
+
const labels = normalized.split(".");
|
|
67
|
+
if (labels.some((label) => label.length === 0))
|
|
68
|
+
return null;
|
|
69
|
+
return labels;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Whether a registered redirect URI is a wildcard pattern rather than a literal
|
|
73
|
+
* URI. Says nothing about whether the pattern is *valid* — use
|
|
74
|
+
* {@link checkRedirectUriPattern} for that.
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```ts
|
|
78
|
+
* import { isRedirectUriPattern } from "@udibo/oauth2/server";
|
|
79
|
+
*
|
|
80
|
+
* isRedirectUriPattern("https://myapp-*.myorg.deno.net/cb"); // true
|
|
81
|
+
* isRedirectUriPattern("https://myapp.example.com/cb"); // false
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export function isRedirectUriPattern(redirectUri) {
|
|
85
|
+
return redirectUri.includes("*");
|
|
86
|
+
}
|
|
87
|
+
function violation(rule, message) {
|
|
88
|
+
return { rule, message };
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Check a registered redirect URI pattern against the pattern rules.
|
|
92
|
+
*
|
|
93
|
+
* Returns `null` when the value is safe to register — which includes any value
|
|
94
|
+
* with no `*` in it at all, since a literal redirect URI is matched exactly and
|
|
95
|
+
* has no pattern rules to break. Otherwise it returns the first
|
|
96
|
+
* {@link RedirectUriPatternViolation}, whose `message` is written to be shown
|
|
97
|
+
* to whoever is registering the URI.
|
|
98
|
+
*
|
|
99
|
+
* Use this at registration time to reject a bad pattern where the person
|
|
100
|
+
* responsible can fix it. {@link matchRedirectUri} applies the same check at
|
|
101
|
+
* request time, so a pattern that slipped into storage never matches.
|
|
102
|
+
*
|
|
103
|
+
* **Patterns need `isPublicSuffix`.** Without it there is no way to tell
|
|
104
|
+
* `myorg.deno.net` (yours) from `deno.net` (everyone's), so every pattern is
|
|
105
|
+
* refused. Pass `isPublicSuffix` from `@udibo/oauth2/server/public-suffix`, or
|
|
106
|
+
* omit it deliberately on a server that only accepts literal redirect URIs.
|
|
107
|
+
*
|
|
108
|
+
* @param pattern The registered redirect URI to check.
|
|
109
|
+
* @param isPublicSuffix Decides whether the domain under the wildcard is a
|
|
110
|
+
* public suffix. Omitting it refuses every pattern.
|
|
111
|
+
* @returns The violated rule and an explanation, or `null` when there is none.
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* ```ts
|
|
115
|
+
* import { checkRedirectUriPattern } from "@udibo/oauth2/server";
|
|
116
|
+
* import { isPublicSuffix } from "@udibo/oauth2/server/public-suffix";
|
|
117
|
+
*
|
|
118
|
+
* checkRedirectUriPattern("https://myapp-*.myorg.deno.net/cb", isPublicSuffix); // null
|
|
119
|
+
* checkRedirectUriPattern("https://*.deno.net/cb", isPublicSuffix)?.rule; // "parent-domain"
|
|
120
|
+
* checkRedirectUriPattern("http://*.a.example.com/cb", isPublicSuffix)?.rule; // "scheme"
|
|
121
|
+
* ```
|
|
122
|
+
*/
|
|
123
|
+
export function checkRedirectUriPattern(pattern, isPublicSuffix) {
|
|
124
|
+
if (!isRedirectUriPattern(pattern))
|
|
125
|
+
return null;
|
|
126
|
+
let url;
|
|
127
|
+
try {
|
|
128
|
+
url = new URL(pattern);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return violation("syntax", `"${pattern}" is not a valid absolute URL, so it cannot be matched ` +
|
|
132
|
+
`against a redirect_uri. Register the full callback URL, for example ` +
|
|
133
|
+
`https://myapp-*.myorg.example.com/auth/callback.`);
|
|
134
|
+
}
|
|
135
|
+
if (url.protocol !== "https:") {
|
|
136
|
+
return violation("scheme", `"${pattern}" must use https. A wildcard registration covers hosts that ` +
|
|
137
|
+
`do not exist yet, so the authorization code it receives has to be ` +
|
|
138
|
+
`protected in transit.`);
|
|
139
|
+
}
|
|
140
|
+
if ((pattern.match(/\*/g) ?? []).length !== 1) {
|
|
141
|
+
return violation("wildcard-count", `"${pattern}" must contain exactly one "*". More than one wildcard ` +
|
|
142
|
+
`widens the set of matching hosts past what can be reasoned about.`);
|
|
143
|
+
}
|
|
144
|
+
const labels = hostLabels(url.hostname);
|
|
145
|
+
if (!labels) {
|
|
146
|
+
return violation("syntax", `"${pattern}" has an empty host label. Remove the repeated or leading ` +
|
|
147
|
+
`"." — a host like "a..example.com" cannot resolve, and an empty ` +
|
|
148
|
+
`label beside the wildcard would match anything.`);
|
|
149
|
+
}
|
|
150
|
+
if (!url.hostname.includes("*") || !labels[0].includes("*")) {
|
|
151
|
+
return violation("wildcard-placement", `"${pattern}" may only use "*" in the leftmost part of the host — never ` +
|
|
152
|
+
`in the scheme, port, path, query, or fragment. A wildcard elsewhere ` +
|
|
153
|
+
`would let an attacker choose where the authorization code is sent.`);
|
|
154
|
+
}
|
|
155
|
+
const parentDomain = labels.slice(1).join(".");
|
|
156
|
+
if (!isPublicSuffix) {
|
|
157
|
+
return violation("parent-domain", `"${pattern}" cannot be registered: this server has no Public Suffix ` +
|
|
158
|
+
`List, so it cannot tell whether "${parentDomain}" is a namespace ` +
|
|
159
|
+
`your organization controls or one anyone can obtain a host under. ` +
|
|
160
|
+
`Register the callback URLs in full, or ask the operator to configure ` +
|
|
161
|
+
`isPublicSuffix.`);
|
|
162
|
+
}
|
|
163
|
+
if (isPublicSuffix(parentDomain)) {
|
|
164
|
+
return violation("parent-domain", `"${pattern}" wildcards "${parentDomain}", which is a public suffix — ` +
|
|
165
|
+
`anyone can register a name directly under it, obtain a neighbouring ` +
|
|
166
|
+
`host, and receive your authorization codes. Put the wildcard under a ` +
|
|
167
|
+
`domain your organization registered, for example ` +
|
|
168
|
+
`https://myapp-*.myorg.${parentDomain}/callback.`);
|
|
169
|
+
}
|
|
170
|
+
if (isPublicSuffix(`${CHILD_PROBE_LABEL}.${parentDomain}`)) {
|
|
171
|
+
return violation("parent-domain", `"${pattern}" wildcards "${parentDomain}", whose subdomains are each a ` +
|
|
172
|
+
`public suffix belonging to a different registrant, so a neighbouring ` +
|
|
173
|
+
`host is available to anyone. Put the wildcard under a domain your ` +
|
|
174
|
+
`organization registered.`);
|
|
175
|
+
}
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* The loopback IP literals RFC 8252 §7.3 covers, in the form
|
|
180
|
+
* `URL.hostname` normalizes them to. `localhost` is deliberately absent: RFC
|
|
181
|
+
* 8252 §8.3 advises against it because its resolution depends on a host file
|
|
182
|
+
* the authorization server does not control.
|
|
183
|
+
*/
|
|
184
|
+
const LOOPBACK_HOSTNAMES = new Set(["127.0.0.1", "[::1]"]);
|
|
185
|
+
function matchesLoopback(registration, redirectUri) {
|
|
186
|
+
if (isRedirectUriPattern(registration))
|
|
187
|
+
return false;
|
|
188
|
+
let registeredUrl;
|
|
189
|
+
let url;
|
|
190
|
+
try {
|
|
191
|
+
registeredUrl = new URL(registration);
|
|
192
|
+
url = new URL(redirectUri);
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
if (!LOOPBACK_HOSTNAMES.has(registeredUrl.hostname))
|
|
198
|
+
return false;
|
|
199
|
+
return registeredUrl.protocol === url.protocol &&
|
|
200
|
+
registeredUrl.hostname === url.hostname &&
|
|
201
|
+
registeredUrl.username === url.username &&
|
|
202
|
+
registeredUrl.password === url.password &&
|
|
203
|
+
registeredUrl.pathname === url.pathname &&
|
|
204
|
+
registeredUrl.search === url.search &&
|
|
205
|
+
registeredUrl.hash === url.hash;
|
|
206
|
+
}
|
|
207
|
+
function matchesPattern(pattern, redirectUri, isPublicSuffix) {
|
|
208
|
+
if (checkRedirectUriPattern(pattern, isPublicSuffix) !== null)
|
|
209
|
+
return false;
|
|
210
|
+
let patternUrl;
|
|
211
|
+
let url;
|
|
212
|
+
try {
|
|
213
|
+
patternUrl = new URL(pattern);
|
|
214
|
+
url = new URL(redirectUri);
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
if (url.protocol !== "https:")
|
|
220
|
+
return false;
|
|
221
|
+
if (url.port !== patternUrl.port)
|
|
222
|
+
return false;
|
|
223
|
+
if (url.username !== patternUrl.username)
|
|
224
|
+
return false;
|
|
225
|
+
if (url.password !== patternUrl.password)
|
|
226
|
+
return false;
|
|
227
|
+
if (url.pathname !== patternUrl.pathname)
|
|
228
|
+
return false;
|
|
229
|
+
if (url.search !== patternUrl.search)
|
|
230
|
+
return false;
|
|
231
|
+
if (url.hash !== patternUrl.hash)
|
|
232
|
+
return false;
|
|
233
|
+
if (url.hostname.includes("*"))
|
|
234
|
+
return false;
|
|
235
|
+
const patternLabels = hostLabels(patternUrl.hostname);
|
|
236
|
+
const labels = hostLabels(url.hostname);
|
|
237
|
+
if (!patternLabels || !labels)
|
|
238
|
+
return false;
|
|
239
|
+
if (patternLabels.length !== labels.length)
|
|
240
|
+
return false;
|
|
241
|
+
for (let i = 1; i < patternLabels.length; i++) {
|
|
242
|
+
if (patternLabels[i] !== labels[i])
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
const [prefix, suffix] = patternLabels[0].split("*");
|
|
246
|
+
const label = labels[0];
|
|
247
|
+
return label.length >= prefix.length + suffix.length &&
|
|
248
|
+
label.startsWith(prefix) && label.endsWith(suffix);
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Find the registered redirect URI that authorizes a requested `redirect_uri`.
|
|
252
|
+
*
|
|
253
|
+
* Literal registrations are compared by exact string match (RFC 6749
|
|
254
|
+
* §3.1.2.3), and an exact match always wins.
|
|
255
|
+
*
|
|
256
|
+
* A registration whose host is a **loopback IP literal** — `127.0.0.1` or
|
|
257
|
+
* `[::1]`, after `URL` normalizes forms like `127.1` and
|
|
258
|
+
* `[0:0:0:0:0:0:0:1]` — is tried next, matching the request on every part
|
|
259
|
+
* *except* the port (RFC 8252 §7.3: "the authorization server MUST allow any
|
|
260
|
+
* port to be specified at the time of the request for loopback IP redirect
|
|
261
|
+
* URIs"). Scheme, host, userinfo, path, query, and fragment must still be
|
|
262
|
+
* equal, and `localhost` is not covered — it is a name resolved by a host file
|
|
263
|
+
* this server does not control (RFC 8252 §8.3), so it matches exactly like any
|
|
264
|
+
* other host.
|
|
265
|
+
*
|
|
266
|
+
* Only if neither matches are the pattern registrations tried, under the rules
|
|
267
|
+
* in {@link checkRedirectUriPattern} plus:
|
|
268
|
+
*
|
|
269
|
+
* - The requested URI must be `https`, so a pattern can never be satisfied over
|
|
270
|
+
* plaintext.
|
|
271
|
+
* - `*` matches within one host label and never across a `.`, so
|
|
272
|
+
* `https://a-*.example.com/cb` does not match
|
|
273
|
+
* `https://a-x.evil.example.com/cb`.
|
|
274
|
+
* - Port, userinfo, path, query, and fragment must match the pattern exactly.
|
|
275
|
+
* - A host label may never be empty, and a single trailing dot is treated as
|
|
276
|
+
* the same host, so `example.com.` cannot smuggle an extra label past the
|
|
277
|
+
* parent-domain rule.
|
|
278
|
+
* - A requested URI containing `*` never matches anything, checked both on the
|
|
279
|
+
* raw string and on the percent-decoded host, so a pattern can never be
|
|
280
|
+
* replayed back as a literal redirect target.
|
|
281
|
+
*
|
|
282
|
+
* That last rule is a deliberate tightening: `*` is a legal host character, so
|
|
283
|
+
* a registration containing one that was previously matched by exact string
|
|
284
|
+
* comparison no longer matches at all. Such a registration cannot be created
|
|
285
|
+
* any more — a `*` now makes the value a pattern, which must satisfy the rules
|
|
286
|
+
* above — so this only affects rows written before patterns existed.
|
|
287
|
+
*
|
|
288
|
+
* @param registered The client's registered redirect URIs.
|
|
289
|
+
* @param redirectUri The `redirect_uri` from the authorization request.
|
|
290
|
+
* @param isPublicSuffix Decides whether the domain under a wildcard is a public
|
|
291
|
+
* suffix. Omitting it makes every pattern registration inert; literal
|
|
292
|
+
* registrations are unaffected.
|
|
293
|
+
* @returns The registered entry that authorized the request, or `undefined`
|
|
294
|
+
* when none did.
|
|
295
|
+
*
|
|
296
|
+
* @example
|
|
297
|
+
* ```ts
|
|
298
|
+
* import { matchRedirectUri } from "@udibo/oauth2/server";
|
|
299
|
+
* import { isPublicSuffix } from "@udibo/oauth2/server/public-suffix";
|
|
300
|
+
*
|
|
301
|
+
* const registered = ["https://myapp-*.myorg.deno.net/auth/callback"];
|
|
302
|
+
* matchRedirectUri(registered, "https://myapp-a1b2.myorg.deno.net/auth/callback", isPublicSuffix);
|
|
303
|
+
* // "https://myapp-*.myorg.deno.net/auth/callback"
|
|
304
|
+
* matchRedirectUri(registered, "https://myapp-a1b2.evil.myorg.deno.net/auth/callback", isPublicSuffix);
|
|
305
|
+
* // undefined
|
|
306
|
+
*
|
|
307
|
+
* matchRedirectUri(["http://127.0.0.1/callback"], "http://127.0.0.1:53211/callback");
|
|
308
|
+
* // "http://127.0.0.1/callback"
|
|
309
|
+
* matchRedirectUri(["http://localhost/callback"], "http://localhost:53211/callback");
|
|
310
|
+
* // undefined
|
|
311
|
+
* ```
|
|
312
|
+
*/
|
|
313
|
+
export function matchRedirectUri(registered, redirectUri, isPublicSuffix) {
|
|
314
|
+
if (isRedirectUriPattern(redirectUri))
|
|
315
|
+
return undefined;
|
|
316
|
+
for (const entry of registered) {
|
|
317
|
+
if (entry === redirectUri)
|
|
318
|
+
return entry;
|
|
319
|
+
}
|
|
320
|
+
for (const entry of registered) {
|
|
321
|
+
if (matchesLoopback(entry, redirectUri))
|
|
322
|
+
return entry;
|
|
323
|
+
}
|
|
324
|
+
for (const entry of registered) {
|
|
325
|
+
if (isRedirectUriPattern(entry) &&
|
|
326
|
+
matchesPattern(entry, redirectUri, isPublicSuffix)) {
|
|
327
|
+
return entry;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return undefined;
|
|
331
|
+
}
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resource Server implementation for protecting resources with OAuth2 tokens.
|
|
3
|
+
*
|
|
4
|
+
* Implements bearer token validation per RFC 6750.
|
|
5
|
+
*
|
|
6
|
+
* @see https://datatracker.ietf.org/doc/html/rfc6750
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
import "../_dnt.polyfills.js";
|
|
10
|
+
import type { ClientInterface } from "../models/client.js";
|
|
11
|
+
import type { Token } from "../models/token.js";
|
|
12
|
+
import type { AbstractScope, ScopeConstructor } from "../models/scope.js";
|
|
13
|
+
import { BasicScope } from "../models/scope.js";
|
|
14
|
+
import { type OAuth2Error } from "../errors.js";
|
|
15
|
+
import { type Authorization, type RequireConditions } from "../models/authorization.js";
|
|
16
|
+
import type { TokenReaderInterface } from "./services/token.js";
|
|
17
|
+
/** Bearer token regex pattern. */
|
|
18
|
+
export declare const BEARER_TOKEN: RegExp;
|
|
19
|
+
/** Services required by the resource server. */
|
|
20
|
+
export interface ResourceServerServices<Client extends ClientInterface, User, S extends AbstractScope> {
|
|
21
|
+
/** Reads and validates access tokens for this request. */
|
|
22
|
+
tokenService: TokenReaderInterface<Client, User, S>;
|
|
23
|
+
}
|
|
24
|
+
/** Per-request context resolved by a {@link ResourceServer}. */
|
|
25
|
+
export interface ResourceServerContext<Client extends ClientInterface, User, S extends AbstractScope> {
|
|
26
|
+
/** Tenant-scoped services to use for this request. */
|
|
27
|
+
services: ResourceServerServices<Client, User, S>;
|
|
28
|
+
}
|
|
29
|
+
/** Options for configuring the resource server. */
|
|
30
|
+
export interface ResourceServerOptions<Client extends ClientInterface, User, S extends AbstractScope> {
|
|
31
|
+
/**
|
|
32
|
+
* Resolves the per-request context — the (e.g. tenant-scoped) {@link
|
|
33
|
+
* ResourceServerServices} for THIS request — so a single instance serves
|
|
34
|
+
* every tenant/origin. This is the only services seam: for a single-tenant
|
|
35
|
+
* server it is a constant function returning the same services every time
|
|
36
|
+
* (create the services once, outside `resolve`, and return them here).
|
|
37
|
+
*/
|
|
38
|
+
resolve: (request: Request) => ResourceServerContext<Client, User, S> | Promise<ResourceServerContext<Client, User, S>>;
|
|
39
|
+
/** Scope constructor used to parse and compare scopes. Defaults to {@linkcode BasicScope}. */
|
|
40
|
+
Scope?: ScopeConstructor<S>;
|
|
41
|
+
/** Protection space named in `WWW-Authenticate` challenges. Defaults to `"Service"`. */
|
|
42
|
+
realm?: string;
|
|
43
|
+
/**
|
|
44
|
+
* Leeway in seconds applied to the access token's expiry, to absorb clock
|
|
45
|
+
* drift between whoever issued the token and this server. Defaults to `0`:
|
|
46
|
+
* a token whose expiry has passed is refused immediately.
|
|
47
|
+
*
|
|
48
|
+
* This is the one expiry check every token reader's result passes through,
|
|
49
|
+
* which is why the leeway belongs here rather than in a reader:
|
|
50
|
+
* {@linkcode IntrospectionTokenReader} reports the authorization server's
|
|
51
|
+
* `exp` verbatim and has no notion of skew at all, and
|
|
52
|
+
* {@linkcode JwksTokenReaderOptions.clockSkewSeconds} governs only that
|
|
53
|
+
* reader's own claim checks. Set this whenever you need drift tolerance, no
|
|
54
|
+
* matter which reader is in play.
|
|
55
|
+
*/
|
|
56
|
+
clockSkewSeconds?: number;
|
|
57
|
+
/**
|
|
58
|
+
* Format for error responses.
|
|
59
|
+
* - `"oauth2"` (default): Standard OAuth2 error format with `error`,
|
|
60
|
+
* `error_description`, and `error_uri` fields.
|
|
61
|
+
* - `"problem-details"`: RFC 9457 Problem Details format with OAuth2
|
|
62
|
+
* extensions. `type` and `error_uri` carry the same URI; `detail` and
|
|
63
|
+
* `error_description` carry the same message.
|
|
64
|
+
*/
|
|
65
|
+
errorFormat?: "oauth2" | "problem-details";
|
|
66
|
+
/**
|
|
67
|
+
* If true, handlers rethrow errors (as {@link OAuth2Error}) instead of
|
|
68
|
+
* catching them and returning a response. Framework adapters that want to
|
|
69
|
+
* build responses themselves should enable this. Default: false.
|
|
70
|
+
*/
|
|
71
|
+
throwOnError?: boolean;
|
|
72
|
+
}
|
|
73
|
+
/** Authentication context returned by the resource server. */
|
|
74
|
+
export interface AuthenticatedContext<Client extends ClientInterface, User, S extends AbstractScope> {
|
|
75
|
+
/** The validated access token backing this request. */
|
|
76
|
+
token: Token<Client, User, S>;
|
|
77
|
+
/** The client the token was issued to. */
|
|
78
|
+
client: Client;
|
|
79
|
+
/** The resource owner, absent for client-credentials tokens. */
|
|
80
|
+
user?: User;
|
|
81
|
+
/** The scope granted to the token, if any. */
|
|
82
|
+
scope?: S | null;
|
|
83
|
+
/**
|
|
84
|
+
* The token's claims as one checkable object — scope, roles, permissions,
|
|
85
|
+
* and the active organization. Built from {@linkcode Token.claims}; a token
|
|
86
|
+
* with no claims yields an authorization that answers scope checks only.
|
|
87
|
+
*/
|
|
88
|
+
authorization: Authorization;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Validates OAuth2 bearer access tokens (RFC 6750) so API endpoints can require
|
|
92
|
+
* authentication. Framework-agnostic: it works on web `Request`/`Response`, so
|
|
93
|
+
* an adapter (Hono, etc.) wires {@linkcode authenticate} into its middleware.
|
|
94
|
+
*
|
|
95
|
+
* A single instance serves every tenant/origin; per-request services come from
|
|
96
|
+
* the `resolve` callback in {@linkcode ResourceServerOptions}. For a
|
|
97
|
+
* single-tenant server, `resolve` is a constant function returning the same
|
|
98
|
+
* services every time.
|
|
99
|
+
*
|
|
100
|
+
* {@linkcode authenticate} throws {@linkcode HttpError} subclasses on failure
|
|
101
|
+
* ({@linkcode AccessDeniedError} when no token is sent,
|
|
102
|
+
* {@linkcode InvalidTokenError} for an invalid/expired token, and
|
|
103
|
+
* {@linkcode InsufficientScopeError} when scope is lacking); use
|
|
104
|
+
* {@linkcode handleAuthError} to turn them into RFC 6750 responses.
|
|
105
|
+
*
|
|
106
|
+
* @template Client The client entity type.
|
|
107
|
+
* @template User The resource-owner entity type.
|
|
108
|
+
* @template S The scope type used for granted/required scope comparisons.
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```ts
|
|
112
|
+
* import { Hono } from "hono";
|
|
113
|
+
* import type { ClientInterface, TokenReaderInterface } from "@udibo/oauth2/server";
|
|
114
|
+
* import { ResourceServer } from "@udibo/oauth2/server/resource";
|
|
115
|
+
*
|
|
116
|
+
* interface User { id: string }
|
|
117
|
+
*
|
|
118
|
+
* declare const app: Hono;
|
|
119
|
+
* declare const tokenService: TokenReaderInterface<ClientInterface, User>;
|
|
120
|
+
*
|
|
121
|
+
* const server = new ResourceServer({
|
|
122
|
+
* resolve: () => ({ services: { tokenService } }),
|
|
123
|
+
* });
|
|
124
|
+
*
|
|
125
|
+
* app.get("/api/me", async (c) => {
|
|
126
|
+
* try {
|
|
127
|
+
* const { user } = await server.authenticate(c.req.raw, "identity:read");
|
|
128
|
+
* return c.json(user ?? null);
|
|
129
|
+
* } catch (error) {
|
|
130
|
+
* return server.handleAuthError(error);
|
|
131
|
+
* }
|
|
132
|
+
* });
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
export declare class ResourceServer<Client extends ClientInterface, User, S extends AbstractScope = BasicScope> {
|
|
136
|
+
#private;
|
|
137
|
+
/** Scope constructor used to parse and compare scopes. */
|
|
138
|
+
Scope: ScopeConstructor<S>;
|
|
139
|
+
/** Protection space named in `WWW-Authenticate` challenges. */
|
|
140
|
+
realm: string;
|
|
141
|
+
/** Leeway in seconds applied to the access token's expiry. Defaults to `0`. */
|
|
142
|
+
clockSkewSeconds: number;
|
|
143
|
+
/** Body format used for error responses: OAuth2 or RFC 9457 problem details. */
|
|
144
|
+
errorFormat: "oauth2" | "problem-details";
|
|
145
|
+
/** When true, handler boundaries rethrow {@linkcode OAuth2Error} instead of returning a response. */
|
|
146
|
+
throwOnError: boolean;
|
|
147
|
+
/** Resolves the per-request {@link ResourceServerContext}. */
|
|
148
|
+
protected resolveContext: (request: Request) => ResourceServerContext<Client, User, S> | Promise<ResourceServerContext<Client, User, S>>;
|
|
149
|
+
/** Creates a resource server from the given {@linkcode ResourceServerOptions}. */
|
|
150
|
+
constructor(options: ResourceServerOptions<Client, User, S>);
|
|
151
|
+
/**
|
|
152
|
+
* Builds a WWW-Authenticate header value per RFC 6750 Section 3.
|
|
153
|
+
*
|
|
154
|
+
* The header includes:
|
|
155
|
+
* - realm: The protection space
|
|
156
|
+
* - error: The error code (for 401/403 responses)
|
|
157
|
+
* - error_description: Human-readable error description
|
|
158
|
+
* - scope: Required scope (for insufficient_scope errors)
|
|
159
|
+
*
|
|
160
|
+
* RFC 6750 Section 3.1 registers exactly three challenge codes —
|
|
161
|
+
* `invalid_request`, `invalid_token`, `insufficient_scope` — so an error
|
|
162
|
+
* carrying anything else (including the `access_denied` this server raises
|
|
163
|
+
* when a request presents no credentials at all, which the same section says
|
|
164
|
+
* MUST NOT be given a code) produces a bare `realm` challenge. Pick the error
|
|
165
|
+
* class accordingly: a client reads a coded challenge and acts on it, and
|
|
166
|
+
* {@linkcode InsufficientScopeError} is the one that tells it "your token is
|
|
167
|
+
* fine, it just lacks scope" rather than sending it off to refresh.
|
|
168
|
+
*
|
|
169
|
+
* @see https://datatracker.ietf.org/doc/html/rfc6750#section-3
|
|
170
|
+
*/
|
|
171
|
+
buildWwwAuthenticate(error?: OAuth2Error, requiredScope?: string): string;
|
|
172
|
+
/**
|
|
173
|
+
* Creates an error response for an OAuth2 error.
|
|
174
|
+
*
|
|
175
|
+
* The response body is formatted according to {@link errorFormat}:
|
|
176
|
+
* - `"oauth2"`: `{ error, error_description?, error_uri? }` as JSON.
|
|
177
|
+
* - `"problem-details"`: RFC 9457 Problem Details with OAuth2 extensions.
|
|
178
|
+
*
|
|
179
|
+
* Does **not** set any response headers beyond `Content-Type`. Any extra
|
|
180
|
+
* response headers (`WWW-Authenticate`, `Cache-Control`, `Pragma`, …) must
|
|
181
|
+
* be set on the error via its `headers` property before calling this
|
|
182
|
+
* method; they are copied through verbatim.
|
|
183
|
+
*/
|
|
184
|
+
createErrorResponse(error: unknown): Response;
|
|
185
|
+
/**
|
|
186
|
+
* Normalizes an error at a handler boundary and either rethrows it (when
|
|
187
|
+
* {@link throwOnError} is true) or converts it to a response.
|
|
188
|
+
*
|
|
189
|
+
* The optional `prepare` callback lets a handler annotate the error —
|
|
190
|
+
* usually by setting headers like `Cache-Control` or `WWW-Authenticate`
|
|
191
|
+
* that belong on both the thrown error and the response — before the
|
|
192
|
+
* throw/respond decision is made.
|
|
193
|
+
*/
|
|
194
|
+
handleError(error: unknown, prepare?: (error: OAuth2Error) => void): Response;
|
|
195
|
+
/**
|
|
196
|
+
* {@link handleError} variant that automatically attaches a
|
|
197
|
+
* `WWW-Authenticate` header (per RFC 6750 Section 3) when the error's
|
|
198
|
+
* status is 401 or 403. Use this at bearer-token boundaries to avoid the
|
|
199
|
+
* boilerplate of building the header for each error response.
|
|
200
|
+
*
|
|
201
|
+
* @example
|
|
202
|
+
* ```ts
|
|
203
|
+
* import { Hono } from "hono";
|
|
204
|
+
* import type { ClientInterface } from "@udibo/oauth2/server";
|
|
205
|
+
* import { ResourceServer } from "@udibo/oauth2/server/resource";
|
|
206
|
+
*
|
|
207
|
+
* interface User { id: string }
|
|
208
|
+
*
|
|
209
|
+
* declare const app: Hono;
|
|
210
|
+
* declare const server: ResourceServer<ClientInterface, User>;
|
|
211
|
+
*
|
|
212
|
+
* app.get("/api/me", async (c) => {
|
|
213
|
+
* try {
|
|
214
|
+
* const ctx = await server.authenticate(c.req.raw);
|
|
215
|
+
* return c.json(ctx.user ?? null);
|
|
216
|
+
* } catch (error) {
|
|
217
|
+
* return server.handleAuthError(error);
|
|
218
|
+
* }
|
|
219
|
+
* });
|
|
220
|
+
* ```
|
|
221
|
+
*/
|
|
222
|
+
handleAuthError(error: unknown): Response;
|
|
223
|
+
/**
|
|
224
|
+
* Extracts an access token from a request.
|
|
225
|
+
* Looks in the Authorization header (Bearer token) first,
|
|
226
|
+
* then in the request body for form-encoded requests.
|
|
227
|
+
*/
|
|
228
|
+
getAccessToken(request: Request): Promise<string | null>;
|
|
229
|
+
/**
|
|
230
|
+
* Retrieves and validates a token by access token string.
|
|
231
|
+
*
|
|
232
|
+
* The token's expiry is compared against now with {@linkcode
|
|
233
|
+
* clockSkewSeconds} of leeway, so the same tolerance applies whichever
|
|
234
|
+
* token reader produced the token.
|
|
235
|
+
*
|
|
236
|
+
* @throws {InvalidTokenError} If the token is invalid, expired beyond the
|
|
237
|
+
* configured leeway, or revoked
|
|
238
|
+
* @see https://datatracker.ietf.org/doc/html/rfc6750#section-3.1
|
|
239
|
+
*/
|
|
240
|
+
getToken(accessToken: string, services: ResourceServerServices<Client, User, S>): Promise<Token<Client, User, S>>;
|
|
241
|
+
/**
|
|
242
|
+
* Authenticates a request and verifies the token has the required scope.
|
|
243
|
+
*
|
|
244
|
+
* @param request The incoming request
|
|
245
|
+
* @param requiredScope Optional scope required to access the resource
|
|
246
|
+
* @returns The authenticated context with token, client, user, and scope
|
|
247
|
+
* @throws {AccessDeniedError} If no token is provided
|
|
248
|
+
* @throws {InvalidTokenError} If the token is invalid or expired
|
|
249
|
+
* @throws {InsufficientScopeError} If the token lacks required scope (HTTP 403)
|
|
250
|
+
* @see https://datatracker.ietf.org/doc/html/rfc6750#section-3.1
|
|
251
|
+
*/
|
|
252
|
+
authenticate(request: Request, requiredScope?: S | string): Promise<AuthenticatedContext<Client, User, S>>;
|
|
253
|
+
/**
|
|
254
|
+
* Throws unless the authenticated context satisfies every condition —
|
|
255
|
+
* the assertion behind the adapters' `require()` middleware, public so a
|
|
256
|
+
* handler authenticating manually can reuse the same checks.
|
|
257
|
+
*
|
|
258
|
+
* Scope conditions go through {@linkcode assertScope}, so their failure is
|
|
259
|
+
* the same RFC 6750 `insufficient_scope` challenge `requireScope` sends.
|
|
260
|
+
* Every other condition is answered by the context's
|
|
261
|
+
* {@linkcode AuthenticatedContext.authorization}; the first unmet one
|
|
262
|
+
* throws a plain 403 naming the failed check.
|
|
263
|
+
*
|
|
264
|
+
* @throws {InsufficientScopeError} If the token lacks a required scope (403,
|
|
265
|
+
* `insufficient_scope` challenge).
|
|
266
|
+
* @throws {InsufficientPermissionsError} If a permission, role, or
|
|
267
|
+
* organization condition fails (403, no challenge code).
|
|
268
|
+
*/
|
|
269
|
+
assertAuthorized(context: AuthenticatedContext<Client, User, S>, conditions: RequireConditions): void;
|
|
270
|
+
/**
|
|
271
|
+
* Throws {@link InsufficientScopeError} unless `granted` contains
|
|
272
|
+
* `requiredScope`. Shared by {@link authenticate} and the adapters'
|
|
273
|
+
* scope-only guards (`requireScope`) so the comparison and the RFC 6750
|
|
274
|
+
* error stay identical — a guard that checks an already-authenticated
|
|
275
|
+
* request's scope (without re-validating the token) calls this with the
|
|
276
|
+
* context's `scope`. Public so a handler authenticating manually can reuse
|
|
277
|
+
* the same check.
|
|
278
|
+
*
|
|
279
|
+
* @throws {InsufficientScopeError} If `granted` lacks `requiredScope` (403).
|
|
280
|
+
*/
|
|
281
|
+
assertScope(granted: S | null | undefined, requiredScope: S | string): void;
|
|
282
|
+
}
|
|
283
|
+
export { IntrospectionTokenReader } from "./introspection-token-reader.js";
|
|
284
|
+
export type { IntrospectionTokenReaderOptions } from "./introspection-token-reader.js";
|
|
285
|
+
export { JwksTokenReader } from "./jwks-token-reader.js";
|
|
286
|
+
export type { JwksTokenReaderOptions, JwtAccessTokenClaims, } from "./jwks-token-reader.js";
|