@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,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resource Owner Password Credentials grant type implementation.
|
|
3
|
+
* RFC 6749 Section 4.3.
|
|
4
|
+
*
|
|
5
|
+
* @deprecated The password grant is **NOT RECOMMENDED** for use and is
|
|
6
|
+
* removed in OAuth 2.1. It exposes user credentials directly to clients
|
|
7
|
+
* and cannot support modern security features like MFA.
|
|
8
|
+
*
|
|
9
|
+
* Consider using the authorization code grant with PKCE instead.
|
|
10
|
+
*
|
|
11
|
+
* This grant performs **no second-factor check**: a token request is
|
|
12
|
+
* non-interactive, so there is nowhere to challenge the user. Wiring it means
|
|
13
|
+
* enforcing your own MFA policy inside `getAuthenticated`, or accepting that a
|
|
14
|
+
* password alone mints tokens for every user — including ones enrolled in MFA.
|
|
15
|
+
*
|
|
16
|
+
* @see https://datatracker.ietf.org/doc/html/rfc6749#section-4.3
|
|
17
|
+
* @see https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics#section-2.4
|
|
18
|
+
* @module
|
|
19
|
+
*/
|
|
20
|
+
import type { ClientInterface } from "../../models/client.js";
|
|
21
|
+
import type { Token } from "../../models/token.js";
|
|
22
|
+
import type { AbstractScope, BasicScope } from "../../models/scope.js";
|
|
23
|
+
import type { UserServiceInterface } from "../services/user.js";
|
|
24
|
+
import { AbstractGrant, type GrantOptions, type GrantServices } from "./grant.js";
|
|
25
|
+
/** Services required by the password grant. */
|
|
26
|
+
export interface PasswordGrantServices<Client extends ClientInterface, User, S extends AbstractScope> extends GrantServices<Client, User, S> {
|
|
27
|
+
/** Authenticates the resource owner by username and password. */
|
|
28
|
+
userService: UserServiceInterface<User>;
|
|
29
|
+
}
|
|
30
|
+
/** Options for configuring the password grant. */
|
|
31
|
+
export interface PasswordGrantOptions<Client extends ClientInterface, User, S extends AbstractScope> extends GrantOptions<Client, User, S, PasswordGrantServices<Client, User, S>> {
|
|
32
|
+
/** Allow optional refresh token. Defaults to true. */
|
|
33
|
+
allowRefreshToken?: boolean;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The resource owner password credentials grant type.
|
|
37
|
+
*
|
|
38
|
+
* Allows exchanging a user's username and password directly for tokens.
|
|
39
|
+
* This grant type should only be used when there is a high degree of trust
|
|
40
|
+
* between the resource owner and the client.
|
|
41
|
+
*
|
|
42
|
+
* @deprecated This grant is **NOT RECOMMENDED** and is removed in OAuth 2.1.
|
|
43
|
+
* Use the authorization code grant with PKCE instead. Only use this for:
|
|
44
|
+
* - Legacy system migration
|
|
45
|
+
* - First-party trusted applications where no alternative exists
|
|
46
|
+
*
|
|
47
|
+
* Security concerns:
|
|
48
|
+
* - Exposes user credentials directly to the client application
|
|
49
|
+
* - Cannot support MFA or other modern authentication flows
|
|
50
|
+
* - No user consent UI for scope approval
|
|
51
|
+
*
|
|
52
|
+
* **MFA is not enforced here and cannot be.** The grant treats a truthy
|
|
53
|
+
* {@linkcode UserServiceInterface.getAuthenticated} result as sufficient to
|
|
54
|
+
* issue tokens; it never asks whether the user has a second factor, and a
|
|
55
|
+
* token request has no interactive step in which to challenge one. If any of
|
|
56
|
+
* your users can enroll in MFA, either reject them from `getAuthenticated`
|
|
57
|
+
* (fail closed) or accept that their password alone yields full tokens.
|
|
58
|
+
*
|
|
59
|
+
* @see https://datatracker.ietf.org/doc/html/rfc6749#section-4.3
|
|
60
|
+
* @see https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics#section-2.4
|
|
61
|
+
*/
|
|
62
|
+
export declare class PasswordGrant<Client extends ClientInterface, User, S extends AbstractScope = BasicScope> extends AbstractGrant<Client, User, S, PasswordGrantServices<Client, User, S>> {
|
|
63
|
+
/** The OAuth2 grant type: `password`. */
|
|
64
|
+
readonly grantType = "password";
|
|
65
|
+
/** Creates the grant from its {@linkcode PasswordGrantOptions}. */
|
|
66
|
+
constructor(options: PasswordGrantOptions<Client, User, S>);
|
|
67
|
+
/**
|
|
68
|
+
* Handles a token request for the password grant.
|
|
69
|
+
* Validates the username/password and issues tokens.
|
|
70
|
+
*
|
|
71
|
+
* The password is the only factor checked. Any second-factor policy must be
|
|
72
|
+
* applied by {@linkcode UserServiceInterface.getAuthenticated}, whose errors
|
|
73
|
+
* propagate to the token response unchanged.
|
|
74
|
+
*
|
|
75
|
+
* @throws {InvalidRequestError} If `username` or `password` is missing.
|
|
76
|
+
* @throws {InvalidGrantError} If user authentication fails.
|
|
77
|
+
* @throws {InvalidScopeError} If the requested scope is rejected.
|
|
78
|
+
*/
|
|
79
|
+
token(request: Request, client: Client, body: FormData): Promise<Token<Client, User, S>>;
|
|
80
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resource Owner Password Credentials grant type implementation.
|
|
3
|
+
* RFC 6749 Section 4.3.
|
|
4
|
+
*
|
|
5
|
+
* @deprecated The password grant is **NOT RECOMMENDED** for use and is
|
|
6
|
+
* removed in OAuth 2.1. It exposes user credentials directly to clients
|
|
7
|
+
* and cannot support modern security features like MFA.
|
|
8
|
+
*
|
|
9
|
+
* Consider using the authorization code grant with PKCE instead.
|
|
10
|
+
*
|
|
11
|
+
* This grant performs **no second-factor check**: a token request is
|
|
12
|
+
* non-interactive, so there is nowhere to challenge the user. Wiring it means
|
|
13
|
+
* enforcing your own MFA policy inside `getAuthenticated`, or accepting that a
|
|
14
|
+
* password alone mints tokens for every user — including ones enrolled in MFA.
|
|
15
|
+
*
|
|
16
|
+
* @see https://datatracker.ietf.org/doc/html/rfc6749#section-4.3
|
|
17
|
+
* @see https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics#section-2.4
|
|
18
|
+
* @module
|
|
19
|
+
*/
|
|
20
|
+
import { InvalidGrantError, InvalidRequestError } from "../../errors.js";
|
|
21
|
+
import { AbstractGrant, } from "./grant.js";
|
|
22
|
+
/**
|
|
23
|
+
* The resource owner password credentials grant type.
|
|
24
|
+
*
|
|
25
|
+
* Allows exchanging a user's username and password directly for tokens.
|
|
26
|
+
* This grant type should only be used when there is a high degree of trust
|
|
27
|
+
* between the resource owner and the client.
|
|
28
|
+
*
|
|
29
|
+
* @deprecated This grant is **NOT RECOMMENDED** and is removed in OAuth 2.1.
|
|
30
|
+
* Use the authorization code grant with PKCE instead. Only use this for:
|
|
31
|
+
* - Legacy system migration
|
|
32
|
+
* - First-party trusted applications where no alternative exists
|
|
33
|
+
*
|
|
34
|
+
* Security concerns:
|
|
35
|
+
* - Exposes user credentials directly to the client application
|
|
36
|
+
* - Cannot support MFA or other modern authentication flows
|
|
37
|
+
* - No user consent UI for scope approval
|
|
38
|
+
*
|
|
39
|
+
* **MFA is not enforced here and cannot be.** The grant treats a truthy
|
|
40
|
+
* {@linkcode UserServiceInterface.getAuthenticated} result as sufficient to
|
|
41
|
+
* issue tokens; it never asks whether the user has a second factor, and a
|
|
42
|
+
* token request has no interactive step in which to challenge one. If any of
|
|
43
|
+
* your users can enroll in MFA, either reject them from `getAuthenticated`
|
|
44
|
+
* (fail closed) or accept that their password alone yields full tokens.
|
|
45
|
+
*
|
|
46
|
+
* @see https://datatracker.ietf.org/doc/html/rfc6749#section-4.3
|
|
47
|
+
* @see https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics#section-2.4
|
|
48
|
+
*/
|
|
49
|
+
export class PasswordGrant extends AbstractGrant {
|
|
50
|
+
/** The OAuth2 grant type: `password`. */
|
|
51
|
+
grantType = "password";
|
|
52
|
+
/** Creates the grant from its {@linkcode PasswordGrantOptions}. */
|
|
53
|
+
constructor(options) {
|
|
54
|
+
super({
|
|
55
|
+
...options,
|
|
56
|
+
allowRefreshToken: options.allowRefreshToken ?? true,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Handles a token request for the password grant.
|
|
61
|
+
* Validates the username/password and issues tokens.
|
|
62
|
+
*
|
|
63
|
+
* The password is the only factor checked. Any second-factor policy must be
|
|
64
|
+
* applied by {@linkcode UserServiceInterface.getAuthenticated}, whose errors
|
|
65
|
+
* propagate to the token response unchanged.
|
|
66
|
+
*
|
|
67
|
+
* @throws {InvalidRequestError} If `username` or `password` is missing.
|
|
68
|
+
* @throws {InvalidGrantError} If user authentication fails.
|
|
69
|
+
* @throws {InvalidScopeError} If the requested scope is rejected.
|
|
70
|
+
*/
|
|
71
|
+
async token(request, client, body) {
|
|
72
|
+
const { tokenService, userService } = await this.resolveServices(request);
|
|
73
|
+
const scopeText = body.get("scope");
|
|
74
|
+
let scope = this.parseScope(typeof scopeText === "string" ? scopeText : undefined);
|
|
75
|
+
const username = body.get("username");
|
|
76
|
+
if (!username || typeof username !== "string") {
|
|
77
|
+
throw new InvalidRequestError("username parameter required");
|
|
78
|
+
}
|
|
79
|
+
const password = body.get("password");
|
|
80
|
+
if (!password || typeof password !== "string") {
|
|
81
|
+
throw new InvalidRequestError("password parameter required");
|
|
82
|
+
}
|
|
83
|
+
const user = await userService.getAuthenticated(username, password);
|
|
84
|
+
if (!user) {
|
|
85
|
+
throw new InvalidGrantError("user authentication failed");
|
|
86
|
+
}
|
|
87
|
+
const acceptedScope = await this.acceptedScope(client, user, scope, tokenService);
|
|
88
|
+
scope = acceptedScope ?? undefined;
|
|
89
|
+
const token = await this.generateToken(client, user, scope, tokenService);
|
|
90
|
+
return await tokenService.save(token);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Refresh Token grant type implementation.
|
|
3
|
+
* RFC 6749 Section 6.
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
import type { ClientInterface } from "../../models/client.js";
|
|
7
|
+
import type { RefreshToken } from "../../models/token.js";
|
|
8
|
+
import type { AbstractScope, BasicScope } from "../../models/scope.js";
|
|
9
|
+
import { AbstractGrant, type GrantOptions, type GrantServices } from "./grant.js";
|
|
10
|
+
/** Services required by the refresh token grant. */
|
|
11
|
+
export type RefreshTokenGrantServices<Client extends ClientInterface, User, S extends AbstractScope> = GrantServices<Client, User, S>;
|
|
12
|
+
/**
|
|
13
|
+
* What the refresh grant observed when a rotated-out refresh token was
|
|
14
|
+
* replayed. Passed to {@link RefreshTokenGrantOptions.onTokenReuse}.
|
|
15
|
+
*/
|
|
16
|
+
export interface TokenReuseEvent<Client extends ClientInterface, User> {
|
|
17
|
+
/** The rotation family the replayed token belonged to. */
|
|
18
|
+
familyId: string;
|
|
19
|
+
/** The client the replayed token was issued to. */
|
|
20
|
+
client: Client;
|
|
21
|
+
/** The user the replayed token was issued for, if any. */
|
|
22
|
+
user?: User;
|
|
23
|
+
/** Whether {@linkcode TokenServiceInterface.revokeFamily} revoked anything. */
|
|
24
|
+
familyRevoked: boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Options for configuring the refresh token grant. The grant always issues a
|
|
28
|
+
* refresh token, so `allowRefreshToken` is not offered.
|
|
29
|
+
*/
|
|
30
|
+
export interface RefreshTokenGrantOptions<Client extends ClientInterface, User, S extends AbstractScope> extends Omit<GrantOptions<Client, User, S, RefreshTokenGrantServices<Client, User, S>>, "allowRefreshToken"> {
|
|
31
|
+
/**
|
|
32
|
+
* Called when reuse detection catches a rotated-out refresh token being
|
|
33
|
+
* replayed (after the family was revoked) — record it as a security event.
|
|
34
|
+
*
|
|
35
|
+
* Trigger point `token-reuse` (see `docs/trigger-points.md`).
|
|
36
|
+
* Fire-and-forget: it is awaited, but a throw is caught and logged, never
|
|
37
|
+
* rethrown, so a failing security-logging hook cannot change the
|
|
38
|
+
* client-facing `invalid_grant` a replayed token always yields.
|
|
39
|
+
*/
|
|
40
|
+
onTokenReuse?: (event: TokenReuseEvent<Client, User>) => void | Promise<void>;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The refresh token grant type.
|
|
44
|
+
* https://datatracker.ietf.org/doc/html/rfc6749.html#section-6
|
|
45
|
+
*
|
|
46
|
+
* Allows clients to obtain a new access token using a refresh token
|
|
47
|
+
* without requiring the user to re-authenticate.
|
|
48
|
+
*/
|
|
49
|
+
export declare class RefreshTokenGrant<Client extends ClientInterface, User, S extends AbstractScope = BasicScope> extends AbstractGrant<Client, User, S, RefreshTokenGrantServices<Client, User, S>> {
|
|
50
|
+
#private;
|
|
51
|
+
/** The OAuth2 grant type: `refresh_token`. */
|
|
52
|
+
readonly grantType = "refresh_token";
|
|
53
|
+
/** Creates the grant from its {@linkcode RefreshTokenGrantOptions}. */
|
|
54
|
+
constructor(options: RefreshTokenGrantOptions<Client, User, S>);
|
|
55
|
+
/**
|
|
56
|
+
* Handles a token request for the refresh token grant. Issues a new access
|
|
57
|
+
* token (and rotates the refresh token when the token service supports it)
|
|
58
|
+
* from a valid, unexpired refresh token.
|
|
59
|
+
*
|
|
60
|
+
* Rotation revokes the exchanged token through the token service's
|
|
61
|
+
* `revokeRotated` when implemented, falling back to `revoke` — so a service
|
|
62
|
+
* whose `revoke` ends the owning session can keep this bookkeeping step
|
|
63
|
+
* from signing the person out.
|
|
64
|
+
*
|
|
65
|
+
* Rotation renews the refresh token's sliding lifetime, bounded by the
|
|
66
|
+
* family's absolute cap when the token service implements
|
|
67
|
+
* {@linkcode TokenServiceInterface.refreshTokenFamilyExpiresAt} and the
|
|
68
|
+
* record carries a {@linkcode RefreshToken.familyCreatedAt} anchor: both the
|
|
69
|
+
* access and refresh expiries are clamped to the family's ceiling, and once
|
|
70
|
+
* the cap has passed the exchange fails, so the client must obtain a fresh
|
|
71
|
+
* authorization.
|
|
72
|
+
*
|
|
73
|
+
* @throws {InvalidRequestError} If `refresh_token` is missing.
|
|
74
|
+
* @throws {InvalidGrantError} If the refresh token is unknown or expired, or
|
|
75
|
+
* its family has outlived the absolute cap.
|
|
76
|
+
* @throws {InvalidClientError} If the token was issued to another client.
|
|
77
|
+
* @throws {InvalidScopeError} If the requested scope exceeds the original grant.
|
|
78
|
+
*/
|
|
79
|
+
token(request: Request, client: Client, body: FormData): Promise<RefreshToken<Client, User, S>>;
|
|
80
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Refresh Token grant type implementation.
|
|
3
|
+
* RFC 6749 Section 6.
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
import { InvalidClientError, InvalidGrantError, InvalidRequestError, InvalidScopeError, } from "../../errors.js";
|
|
7
|
+
import { AbstractGrant, } from "./grant.js";
|
|
8
|
+
/**
|
|
9
|
+
* The refresh token grant type.
|
|
10
|
+
* https://datatracker.ietf.org/doc/html/rfc6749.html#section-6
|
|
11
|
+
*
|
|
12
|
+
* Allows clients to obtain a new access token using a refresh token
|
|
13
|
+
* without requiring the user to re-authenticate.
|
|
14
|
+
*/
|
|
15
|
+
export class RefreshTokenGrant extends AbstractGrant {
|
|
16
|
+
/** The OAuth2 grant type: `refresh_token`. */
|
|
17
|
+
grantType = "refresh_token";
|
|
18
|
+
#onTokenReuse;
|
|
19
|
+
/** Creates the grant from its {@linkcode RefreshTokenGrantOptions}. */
|
|
20
|
+
constructor(options) {
|
|
21
|
+
super({ ...options, allowRefreshToken: true });
|
|
22
|
+
this.#onTokenReuse = options.onTokenReuse;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Handles a token request for the refresh token grant. Issues a new access
|
|
26
|
+
* token (and rotates the refresh token when the token service supports it)
|
|
27
|
+
* from a valid, unexpired refresh token.
|
|
28
|
+
*
|
|
29
|
+
* Rotation revokes the exchanged token through the token service's
|
|
30
|
+
* `revokeRotated` when implemented, falling back to `revoke` — so a service
|
|
31
|
+
* whose `revoke` ends the owning session can keep this bookkeeping step
|
|
32
|
+
* from signing the person out.
|
|
33
|
+
*
|
|
34
|
+
* Rotation renews the refresh token's sliding lifetime, bounded by the
|
|
35
|
+
* family's absolute cap when the token service implements
|
|
36
|
+
* {@linkcode TokenServiceInterface.refreshTokenFamilyExpiresAt} and the
|
|
37
|
+
* record carries a {@linkcode RefreshToken.familyCreatedAt} anchor: both the
|
|
38
|
+
* access and refresh expiries are clamped to the family's ceiling, and once
|
|
39
|
+
* the cap has passed the exchange fails, so the client must obtain a fresh
|
|
40
|
+
* authorization.
|
|
41
|
+
*
|
|
42
|
+
* @throws {InvalidRequestError} If `refresh_token` is missing.
|
|
43
|
+
* @throws {InvalidGrantError} If the refresh token is unknown or expired, or
|
|
44
|
+
* its family has outlived the absolute cap.
|
|
45
|
+
* @throws {InvalidClientError} If the token was issued to another client.
|
|
46
|
+
* @throws {InvalidScopeError} If the requested scope exceeds the original grant.
|
|
47
|
+
*/
|
|
48
|
+
async token(request, client, body) {
|
|
49
|
+
const { tokenService } = await this.resolveServices(request);
|
|
50
|
+
const refreshTokenStr = body.get("refresh_token");
|
|
51
|
+
if (typeof refreshTokenStr !== "string") {
|
|
52
|
+
throw new InvalidRequestError("refresh_token parameter required");
|
|
53
|
+
}
|
|
54
|
+
const currentToken = await tokenService.getRefreshToken(refreshTokenStr);
|
|
55
|
+
if (!currentToken) {
|
|
56
|
+
// Reuse detection: an unknown-but-previously-rotated token means the
|
|
57
|
+
// old member of a rotation family was replayed (stolen or raced) —
|
|
58
|
+
// revoke the entire family so neither party keeps a live session.
|
|
59
|
+
if (tokenService.getRevokedRefreshToken && tokenService.revokeFamily) {
|
|
60
|
+
const replayed = await tokenService.getRevokedRefreshToken(refreshTokenStr);
|
|
61
|
+
if (replayed?.familyId &&
|
|
62
|
+
replayed.client.id.toString() === client.id.toString()) {
|
|
63
|
+
const familyRevoked = await tokenService.revokeFamily(replayed.familyId);
|
|
64
|
+
await this.#notifyTokenReuse({
|
|
65
|
+
familyId: replayed.familyId,
|
|
66
|
+
client: replayed.client,
|
|
67
|
+
user: replayed.user,
|
|
68
|
+
familyRevoked,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
throw new InvalidGrantError("invalid refresh_token");
|
|
73
|
+
}
|
|
74
|
+
if (currentToken.refreshTokenExpiresAt &&
|
|
75
|
+
currentToken.refreshTokenExpiresAt < new Date()) {
|
|
76
|
+
throw new InvalidGrantError("invalid refresh_token");
|
|
77
|
+
}
|
|
78
|
+
const { client: tokenClient, user, scope } = currentToken;
|
|
79
|
+
if (client.id.toString() !== tokenClient.id.toString()) {
|
|
80
|
+
throw new InvalidClientError("refresh_token was issued to another client");
|
|
81
|
+
}
|
|
82
|
+
// RFC 6749 §6: a requested scope may only narrow, never exceed, the original grant.
|
|
83
|
+
let nextScope = scope;
|
|
84
|
+
const requestedScopeText = body.get("scope");
|
|
85
|
+
if (typeof requestedScopeText === "string") {
|
|
86
|
+
const requestedScope = this.parseScope(requestedScopeText);
|
|
87
|
+
if (requestedScope) {
|
|
88
|
+
if (!scope || !scope.has(requestedScope)) {
|
|
89
|
+
throw new InvalidScopeError("requested scope exceeds the scope of the original grant");
|
|
90
|
+
}
|
|
91
|
+
nextScope = requestedScope;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const familyExpiresAt = currentToken.familyCreatedAt &&
|
|
95
|
+
tokenService.refreshTokenFamilyExpiresAt
|
|
96
|
+
? await tokenService.refreshTokenFamilyExpiresAt(client, user, currentToken.familyCreatedAt, nextScope)
|
|
97
|
+
: undefined;
|
|
98
|
+
if (familyExpiresAt && familyExpiresAt.getTime() <= Date.now()) {
|
|
99
|
+
throw new InvalidGrantError("refresh_token family expired");
|
|
100
|
+
}
|
|
101
|
+
const nextToken = await this.generateToken(client, user, nextScope, tokenService, currentToken.authenticationContext);
|
|
102
|
+
const nextRefreshToken = "refreshToken" in nextToken
|
|
103
|
+
? nextToken
|
|
104
|
+
: {
|
|
105
|
+
...nextToken,
|
|
106
|
+
refreshToken: currentToken.refreshToken,
|
|
107
|
+
refreshTokenExpiresAt: currentToken.refreshTokenExpiresAt,
|
|
108
|
+
};
|
|
109
|
+
// A rotation stays in its ancestor's family so replaying any rotated-out
|
|
110
|
+
// member revokes the whole chain.
|
|
111
|
+
nextRefreshToken.familyId = currentToken.familyId ??
|
|
112
|
+
nextRefreshToken.familyId;
|
|
113
|
+
nextRefreshToken.familyCreatedAt = currentToken.familyCreatedAt ??
|
|
114
|
+
nextRefreshToken.familyCreatedAt;
|
|
115
|
+
if (familyExpiresAt) {
|
|
116
|
+
this.capAtFamilyExpiry(nextRefreshToken, familyExpiresAt);
|
|
117
|
+
}
|
|
118
|
+
const claimed = tokenService.revokeRotated
|
|
119
|
+
? await tokenService.revokeRotated(currentToken)
|
|
120
|
+
: await tokenService.revoke(currentToken);
|
|
121
|
+
if (!claimed) {
|
|
122
|
+
throw new InvalidGrantError("refresh_token was already consumed");
|
|
123
|
+
}
|
|
124
|
+
return await tokenService.save(nextRefreshToken);
|
|
125
|
+
}
|
|
126
|
+
async #notifyTokenReuse(event) {
|
|
127
|
+
if (!this.#onTokenReuse)
|
|
128
|
+
return;
|
|
129
|
+
try {
|
|
130
|
+
await this.#onTokenReuse(event);
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
console.error("[@udibo/oauth2] onTokenReuse hook failed:", error instanceof Error ? error.message : error);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token reader that validates tokens via an OAuth2 introspection endpoint.
|
|
3
|
+
*
|
|
4
|
+
* This is the standard way for resource servers to validate tokens when
|
|
5
|
+
* they are separate from the authorization server. The reader calls the
|
|
6
|
+
* authorization server's introspection endpoint (RFC 7662) to check
|
|
7
|
+
* whether a token is active and retrieve its metadata.
|
|
8
|
+
*
|
|
9
|
+
* @see https://datatracker.ietf.org/doc/html/rfc7662
|
|
10
|
+
* @module
|
|
11
|
+
*/
|
|
12
|
+
import type { ClientInterface } from "../models/client.js";
|
|
13
|
+
import type { AbstractScope, ScopeConstructor } from "../models/scope.js";
|
|
14
|
+
import { BasicScope } from "../models/scope.js";
|
|
15
|
+
import type { IntrospectionResponse, Token } from "../models/token.js";
|
|
16
|
+
import type { TokenReaderInterface } from "./services/token.js";
|
|
17
|
+
/**
|
|
18
|
+
* Options for configuring the introspection token reader.
|
|
19
|
+
*
|
|
20
|
+
* The {@linkcode getClient} and {@linkcode getUser} mappers project the
|
|
21
|
+
* introspection response into the resource server's `Client` / `User`
|
|
22
|
+
* types — RFC 7662 only fixes a minimal set of fields, and most
|
|
23
|
+
* authorization servers carry app-specific claims as extension fields.
|
|
24
|
+
*/
|
|
25
|
+
export interface IntrospectionTokenReaderOptions<Client extends ClientInterface, User, S extends AbstractScope = BasicScope> {
|
|
26
|
+
/** URL of the authorization server's introspection endpoint. */
|
|
27
|
+
introspectionEndpoint: string;
|
|
28
|
+
/** Client ID used to authenticate introspection requests. */
|
|
29
|
+
clientId: string;
|
|
30
|
+
/** Client secret used to authenticate introspection requests. */
|
|
31
|
+
clientSecret: string;
|
|
32
|
+
/** Optional scope constructor for parsing scope strings. Defaults to BasicScope. */
|
|
33
|
+
Scope?: ScopeConstructor<S>;
|
|
34
|
+
/**
|
|
35
|
+
* Projects an active introspection response into a typed `Client`.
|
|
36
|
+
* Required because the client shape is app-specific: RFC 7662 only
|
|
37
|
+
* guarantees `client_id` may be present, and authorization servers may
|
|
38
|
+
* include extension fields.
|
|
39
|
+
*
|
|
40
|
+
* May be async — return a promise to enrich from a DB lookup or a
|
|
41
|
+
* downstream API call (e.g. fetching a client record by `client_id`).
|
|
42
|
+
*/
|
|
43
|
+
getClient: (data: IntrospectionResponse) => Client | Promise<Client>;
|
|
44
|
+
/**
|
|
45
|
+
* Projects an active introspection response into a typed `User`. Return
|
|
46
|
+
* `undefined` when the token doesn't represent a user. Omit entirely if
|
|
47
|
+
* your app never reads user info off the token.
|
|
48
|
+
*
|
|
49
|
+
* A machine token (client credentials) carries no `sub`, so `data.sub` is
|
|
50
|
+
* present only when there is a resource owner — `data.sub ? {…} : undefined`
|
|
51
|
+
* is the whole discriminator, and a client-credentials caller resolves to no
|
|
52
|
+
* user.
|
|
53
|
+
*
|
|
54
|
+
* May be async — return a promise to enrich from a DB lookup or the
|
|
55
|
+
* authorization server's OIDC UserInfo endpoint, using `data.sub` as
|
|
56
|
+
* the lookup key.
|
|
57
|
+
*/
|
|
58
|
+
getUser?: (data: IntrospectionResponse) => User | undefined | Promise<User | undefined>;
|
|
59
|
+
/**
|
|
60
|
+
* Deadline in milliseconds for an introspection request. Defaults to 5000.
|
|
61
|
+
* Without it an authorization server that accepts the connection and then
|
|
62
|
+
* stops answering hangs every request this resource server is serving, since
|
|
63
|
+
* introspection sits on the hot path of each one. Ignored by an injected
|
|
64
|
+
* {@linkcode fetch} that doesn't honour `AbortSignal`.
|
|
65
|
+
*/
|
|
66
|
+
fetchTimeoutMs?: number;
|
|
67
|
+
/**
|
|
68
|
+
* `fetch` implementation used to call the introspection endpoint. Defaults
|
|
69
|
+
* to the global `fetch`. Inject it to route through a custom client (mTLS,
|
|
70
|
+
* timeouts, retries) or to stub the network in tests.
|
|
71
|
+
*/
|
|
72
|
+
fetch?: typeof fetch;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Validates tokens by calling an OAuth2 introspection endpoint (RFC 7662).
|
|
76
|
+
*
|
|
77
|
+
* Use this with {@linkcode ResourceServer} when your resource server is
|
|
78
|
+
* separate from the authorization server. The reader authenticates with
|
|
79
|
+
* the introspection endpoint using client credentials (HTTP Basic Auth)
|
|
80
|
+
* and projects the response into the resource server's `Client` / `User`
|
|
81
|
+
* types via the {@linkcode IntrospectionTokenReaderOptions.getClient} and
|
|
82
|
+
* {@linkcode IntrospectionTokenReaderOptions.getUser} mappers.
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```ts
|
|
86
|
+
* import {
|
|
87
|
+
* IntrospectionTokenReader,
|
|
88
|
+
* ResourceServer,
|
|
89
|
+
* } from "@udibo/oauth2/server/resource";
|
|
90
|
+
*
|
|
91
|
+
* interface MyClient { id: string }
|
|
92
|
+
* interface MyUser { id: string; email?: string }
|
|
93
|
+
*
|
|
94
|
+
* const tokenReader = new IntrospectionTokenReader<MyClient, MyUser>({
|
|
95
|
+
* introspectionEndpoint: "https://auth.example.com/introspect",
|
|
96
|
+
* clientId: "my-api",
|
|
97
|
+
* clientSecret: "my-secret",
|
|
98
|
+
* getClient: (data) => ({ id: data.client_id! }),
|
|
99
|
+
* getUser: (data) =>
|
|
100
|
+
* data.sub
|
|
101
|
+
* ? { id: data.sub, email: data.email as string | undefined }
|
|
102
|
+
* : undefined,
|
|
103
|
+
* });
|
|
104
|
+
*
|
|
105
|
+
* const resourceServer = new ResourceServer<MyClient, MyUser>({
|
|
106
|
+
* resolve: () => ({ services: { tokenService: tokenReader } }),
|
|
107
|
+
* });
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
export declare class IntrospectionTokenReader<Client extends ClientInterface, User, S extends AbstractScope = BasicScope> implements TokenReaderInterface<Client, User, S> {
|
|
111
|
+
#private;
|
|
112
|
+
private introspectionEndpoint;
|
|
113
|
+
private clientId;
|
|
114
|
+
private clientSecret;
|
|
115
|
+
private Scope;
|
|
116
|
+
private getClient;
|
|
117
|
+
private getUser?;
|
|
118
|
+
private fetchTimeoutMs;
|
|
119
|
+
private customFetch?;
|
|
120
|
+
/**
|
|
121
|
+
* `Scope` defaults to {@linkcode BasicScope}, `fetchTimeoutMs` to 5000, and
|
|
122
|
+
* `fetch` to the global `fetch` when omitted from `options`.
|
|
123
|
+
*/
|
|
124
|
+
constructor(options: IntrospectionTokenReaderOptions<Client, User, S>);
|
|
125
|
+
/**
|
|
126
|
+
* Validates an access token by calling the introspection endpoint.
|
|
127
|
+
*
|
|
128
|
+
* Sends a POST request with the token to the configured introspection
|
|
129
|
+
* endpoint, authenticated with client credentials via HTTP Basic Auth.
|
|
130
|
+
*
|
|
131
|
+
* @returns The token with client/user/scope info, or `undefined` when the
|
|
132
|
+
* endpoint reports the token inactive (RFC 7662 `{ active: false }`) or
|
|
133
|
+
* does not identify it as a bearer access token.
|
|
134
|
+
* @throws {TemporarilyUnavailableError} If the introspection endpoint is
|
|
135
|
+
* unreachable, takes longer than
|
|
136
|
+
* {@linkcode IntrospectionTokenReaderOptions.fetchTimeoutMs} to answer, or
|
|
137
|
+
* returns a 5xx — a down authorization server must be distinguishable from
|
|
138
|
+
* a genuinely invalid token (which is `undefined` → `invalid_token`), not
|
|
139
|
+
* collapsed into it.
|
|
140
|
+
* @throws {ServerError} If the endpoint returns a 4xx (e.g. the reader's own
|
|
141
|
+
* introspection credentials are rejected) — a misconfiguration, not a
|
|
142
|
+
* verdict on the caller's token.
|
|
143
|
+
*
|
|
144
|
+
* An active response is only accepted when its `token_type` names a bearer
|
|
145
|
+
* token. RFC 7662 answers for refresh tokens too, and a refresh token's
|
|
146
|
+
* response is otherwise indistinguishable from an access token's — without
|
|
147
|
+
* this check a refresh token presented as `Authorization: Bearer …` would
|
|
148
|
+
* authenticate at the resource server, bypassing rotation and reuse
|
|
149
|
+
* detection. An authorization server that omits `token_type` from an active
|
|
150
|
+
* response cannot be read by this reader.
|
|
151
|
+
*/
|
|
152
|
+
getToken(accessToken: string): Promise<Token<Client, User, S> | undefined>;
|
|
153
|
+
}
|