@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,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Device Authorization Grant implementation (RFC 8628).
|
|
3
|
+
*
|
|
4
|
+
* This grant type allows devices with limited input capabilities
|
|
5
|
+
* to obtain user authorization by having the user authorize
|
|
6
|
+
* the request on a secondary device.
|
|
7
|
+
*
|
|
8
|
+
* @see https://datatracker.ietf.org/doc/html/rfc8628
|
|
9
|
+
* @module
|
|
10
|
+
*/
|
|
11
|
+
import type { ClientInterface } from "../../models/client.js";
|
|
12
|
+
import type { Token } from "../../models/token.js";
|
|
13
|
+
import type { AbstractScope, BasicScope } from "../../models/scope.js";
|
|
14
|
+
import type { DeviceAuthorization } from "../../models/device-authorization.js";
|
|
15
|
+
import type { DeviceAuthorizationServiceInterface } from "../services/device-authorization.js";
|
|
16
|
+
import { AbstractGrant, type GrantOptions, type GrantServices } from "./grant.js";
|
|
17
|
+
/**
|
|
18
|
+
* Services required by the device authorization grant.
|
|
19
|
+
*/
|
|
20
|
+
export interface DeviceAuthorizationGrantServices<Client extends ClientInterface, User, S extends AbstractScope> extends GrantServices<Client, User, S> {
|
|
21
|
+
/** Generates, stores, and polls device/user codes for the device flow. */
|
|
22
|
+
deviceAuthorizationService: DeviceAuthorizationServiceInterface<Client, User, S>;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Options for configuring the device authorization grant.
|
|
26
|
+
*
|
|
27
|
+
* @see https://datatracker.ietf.org/doc/html/rfc8628
|
|
28
|
+
*/
|
|
29
|
+
export interface DeviceAuthorizationGrantOptions<Client extends ClientInterface, User, S extends AbstractScope> extends GrantOptions<Client, User, S, DeviceAuthorizationGrantServices<Client, User, S>> {
|
|
30
|
+
/** Allow optional refresh token. Defaults to true. */
|
|
31
|
+
allowRefreshToken?: boolean;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The Device Authorization Grant type (RFC 8628).
|
|
35
|
+
*
|
|
36
|
+
* This grant is used for devices that lack a browser or have limited
|
|
37
|
+
* input capabilities (TVs, game consoles, CLI tools, etc.).
|
|
38
|
+
*
|
|
39
|
+
* Flow:
|
|
40
|
+
* 1. Device requests authorization, receives device_code and user_code
|
|
41
|
+
* 2. User visits verification_uri on another device and enters user_code
|
|
42
|
+
* 3. User authorizes or denies the request
|
|
43
|
+
* 4. Device polls token endpoint with device_code until authorized/denied/expired
|
|
44
|
+
*
|
|
45
|
+
* @see https://datatracker.ietf.org/doc/html/rfc8628
|
|
46
|
+
*/
|
|
47
|
+
export declare class DeviceAuthorizationGrant<Client extends ClientInterface, User, S extends AbstractScope = BasicScope> extends AbstractGrant<Client, User, S, DeviceAuthorizationGrantServices<Client, User, S>> {
|
|
48
|
+
#private;
|
|
49
|
+
/**
|
|
50
|
+
* The grant type identifier.
|
|
51
|
+
* @see https://datatracker.ietf.org/doc/html/rfc8628#section-3.4
|
|
52
|
+
*/
|
|
53
|
+
readonly grantType = "urn:ietf:params:oauth:grant-type:device_code";
|
|
54
|
+
/** Creates the grant from its {@linkcode DeviceAuthorizationGrantOptions}. */
|
|
55
|
+
constructor(options: DeviceAuthorizationGrantOptions<Client, User, S>);
|
|
56
|
+
/**
|
|
57
|
+
* Initiates a device authorization request.
|
|
58
|
+
*
|
|
59
|
+
* Called by the device authorization endpoint to create a new
|
|
60
|
+
* pending authorization that the user can approve. Resolves its own
|
|
61
|
+
* `deviceAuthorizationService` from the request.
|
|
62
|
+
*
|
|
63
|
+
* @see https://datatracker.ietf.org/doc/html/rfc8628#section-3.1
|
|
64
|
+
*/
|
|
65
|
+
initiateDeviceAuthorization(client: Client, request: Request, scope?: S | null): Promise<DeviceAuthorization<Client, User, S>>;
|
|
66
|
+
/**
|
|
67
|
+
* Handles a token request for the device authorization grant.
|
|
68
|
+
*
|
|
69
|
+
* The client polls this endpoint with the device_code until:
|
|
70
|
+
* - The user authorizes the request (returns tokens)
|
|
71
|
+
* - The user denies the request (returns access_denied)
|
|
72
|
+
* - The device_code expires (returns expired_token)
|
|
73
|
+
* - The client polls too frequently (returns slow_down)
|
|
74
|
+
*
|
|
75
|
+
* Ownership is authenticated before the authorization is acted on: a client
|
|
76
|
+
* polling a device code issued to a different client is refused
|
|
77
|
+
* `invalid_grant` whether that code is live or expired, and the record is
|
|
78
|
+
* left untouched, so a poll the caller is not entitled to make can neither
|
|
79
|
+
* destroy the authorization nor distinguish its state.
|
|
80
|
+
*
|
|
81
|
+
* The device code is claimed by revoking it once the user has approved, so
|
|
82
|
+
* concurrent polls of one approved device code cannot both receive a token.
|
|
83
|
+
* Recording the poll time is bookkeeping for `slow_down` only: it is skipped
|
|
84
|
+
* on every outcome that revokes the authorization. When the store **raises**
|
|
85
|
+
* for the vanished record — as it must, if a lost race is to be
|
|
86
|
+
* distinguishable — the poll is refused with `invalid_grant` rather than
|
|
87
|
+
* surfacing the store's own error. A store whose poll write instead resolves
|
|
88
|
+
* for a record that is gone leaves that poll answering
|
|
89
|
+
* `authorization_pending`, and the next one gets `invalid_grant` from the
|
|
90
|
+
* existence check; the code is still single-use either way, because only the
|
|
91
|
+
* caller whose `revoke` resolved `true` is issued a token.
|
|
92
|
+
*
|
|
93
|
+
* @throws {InvalidRequestError} If `device_code` is missing.
|
|
94
|
+
* @throws {InvalidGrantError} If the device code is unknown, was issued to another client, or was already claimed by another poll.
|
|
95
|
+
* @throws {ExpiredTokenError} If the device code has expired.
|
|
96
|
+
* @throws {SlowDownError} If the client polls faster than the interval allows.
|
|
97
|
+
* @throws {AccessDeniedError} If the user denied the request.
|
|
98
|
+
* @throws {AuthorizationPendingError} If the user has not yet approved the request.
|
|
99
|
+
* @see https://datatracker.ietf.org/doc/html/rfc8628#section-3.4
|
|
100
|
+
* @see https://datatracker.ietf.org/doc/html/rfc8628#section-3.5
|
|
101
|
+
*/
|
|
102
|
+
token(request: Request, client: Client, body: FormData): Promise<Token<Client, User, S>>;
|
|
103
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Device Authorization Grant implementation (RFC 8628).
|
|
3
|
+
*
|
|
4
|
+
* This grant type allows devices with limited input capabilities
|
|
5
|
+
* to obtain user authorization by having the user authorize
|
|
6
|
+
* the request on a secondary device.
|
|
7
|
+
*
|
|
8
|
+
* @see https://datatracker.ietf.org/doc/html/rfc8628
|
|
9
|
+
* @module
|
|
10
|
+
*/
|
|
11
|
+
import { AccessDeniedError, AuthorizationPendingError, ExpiredTokenError, InvalidGrantError, InvalidRequestError, SlowDownError, } from "../../errors.js";
|
|
12
|
+
import { AbstractGrant, } from "./grant.js";
|
|
13
|
+
/**
|
|
14
|
+
* The Device Authorization Grant type (RFC 8628).
|
|
15
|
+
*
|
|
16
|
+
* This grant is used for devices that lack a browser or have limited
|
|
17
|
+
* input capabilities (TVs, game consoles, CLI tools, etc.).
|
|
18
|
+
*
|
|
19
|
+
* Flow:
|
|
20
|
+
* 1. Device requests authorization, receives device_code and user_code
|
|
21
|
+
* 2. User visits verification_uri on another device and enters user_code
|
|
22
|
+
* 3. User authorizes or denies the request
|
|
23
|
+
* 4. Device polls token endpoint with device_code until authorized/denied/expired
|
|
24
|
+
*
|
|
25
|
+
* @see https://datatracker.ietf.org/doc/html/rfc8628
|
|
26
|
+
*/
|
|
27
|
+
export class DeviceAuthorizationGrant extends AbstractGrant {
|
|
28
|
+
/**
|
|
29
|
+
* The grant type identifier.
|
|
30
|
+
* @see https://datatracker.ietf.org/doc/html/rfc8628#section-3.4
|
|
31
|
+
*/
|
|
32
|
+
grantType = "urn:ietf:params:oauth:grant-type:device_code";
|
|
33
|
+
/** Creates the grant from its {@linkcode DeviceAuthorizationGrantOptions}. */
|
|
34
|
+
constructor(options) {
|
|
35
|
+
super({
|
|
36
|
+
...options,
|
|
37
|
+
allowRefreshToken: options.allowRefreshToken ?? true,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Initiates a device authorization request.
|
|
42
|
+
*
|
|
43
|
+
* Called by the device authorization endpoint to create a new
|
|
44
|
+
* pending authorization that the user can approve. Resolves its own
|
|
45
|
+
* `deviceAuthorizationService` from the request.
|
|
46
|
+
*
|
|
47
|
+
* @see https://datatracker.ietf.org/doc/html/rfc8628#section-3.1
|
|
48
|
+
*/
|
|
49
|
+
async initiateDeviceAuthorization(client, request, scope) {
|
|
50
|
+
const { deviceAuthorizationService } = await this.resolveServices(request);
|
|
51
|
+
const deviceAuthorization = {
|
|
52
|
+
deviceCode: await deviceAuthorizationService.generateDeviceCode(client, scope),
|
|
53
|
+
userCode: await deviceAuthorizationService.generateUserCode(client, scope),
|
|
54
|
+
expiresAt: await deviceAuthorizationService.expiresAt(client, scope),
|
|
55
|
+
client,
|
|
56
|
+
scope,
|
|
57
|
+
interval: deviceAuthorizationService.interval,
|
|
58
|
+
};
|
|
59
|
+
return await deviceAuthorizationService.save(deviceAuthorization);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Handles a token request for the device authorization grant.
|
|
63
|
+
*
|
|
64
|
+
* The client polls this endpoint with the device_code until:
|
|
65
|
+
* - The user authorizes the request (returns tokens)
|
|
66
|
+
* - The user denies the request (returns access_denied)
|
|
67
|
+
* - The device_code expires (returns expired_token)
|
|
68
|
+
* - The client polls too frequently (returns slow_down)
|
|
69
|
+
*
|
|
70
|
+
* Ownership is authenticated before the authorization is acted on: a client
|
|
71
|
+
* polling a device code issued to a different client is refused
|
|
72
|
+
* `invalid_grant` whether that code is live or expired, and the record is
|
|
73
|
+
* left untouched, so a poll the caller is not entitled to make can neither
|
|
74
|
+
* destroy the authorization nor distinguish its state.
|
|
75
|
+
*
|
|
76
|
+
* The device code is claimed by revoking it once the user has approved, so
|
|
77
|
+
* concurrent polls of one approved device code cannot both receive a token.
|
|
78
|
+
* Recording the poll time is bookkeeping for `slow_down` only: it is skipped
|
|
79
|
+
* on every outcome that revokes the authorization. When the store **raises**
|
|
80
|
+
* for the vanished record — as it must, if a lost race is to be
|
|
81
|
+
* distinguishable — the poll is refused with `invalid_grant` rather than
|
|
82
|
+
* surfacing the store's own error. A store whose poll write instead resolves
|
|
83
|
+
* for a record that is gone leaves that poll answering
|
|
84
|
+
* `authorization_pending`, and the next one gets `invalid_grant` from the
|
|
85
|
+
* existence check; the code is still single-use either way, because only the
|
|
86
|
+
* caller whose `revoke` resolved `true` is issued a token.
|
|
87
|
+
*
|
|
88
|
+
* @throws {InvalidRequestError} If `device_code` is missing.
|
|
89
|
+
* @throws {InvalidGrantError} If the device code is unknown, was issued to another client, or was already claimed by another poll.
|
|
90
|
+
* @throws {ExpiredTokenError} If the device code has expired.
|
|
91
|
+
* @throws {SlowDownError} If the client polls faster than the interval allows.
|
|
92
|
+
* @throws {AccessDeniedError} If the user denied the request.
|
|
93
|
+
* @throws {AuthorizationPendingError} If the user has not yet approved the request.
|
|
94
|
+
* @see https://datatracker.ietf.org/doc/html/rfc8628#section-3.4
|
|
95
|
+
* @see https://datatracker.ietf.org/doc/html/rfc8628#section-3.5
|
|
96
|
+
*/
|
|
97
|
+
async token(request, client, body) {
|
|
98
|
+
const { deviceAuthorizationService, tokenService } = await this
|
|
99
|
+
.resolveServices(request);
|
|
100
|
+
const deviceCode = body.get("device_code");
|
|
101
|
+
if (typeof deviceCode !== "string") {
|
|
102
|
+
throw new InvalidRequestError("device_code parameter required");
|
|
103
|
+
}
|
|
104
|
+
const authorization = await deviceAuthorizationService.getByDeviceCode(deviceCode);
|
|
105
|
+
if (!authorization) {
|
|
106
|
+
throw new InvalidGrantError("invalid device_code");
|
|
107
|
+
}
|
|
108
|
+
if (authorization.client.id !== client.id) {
|
|
109
|
+
throw new InvalidGrantError("device_code was issued to another client");
|
|
110
|
+
}
|
|
111
|
+
if (authorization.expiresAt < new Date()) {
|
|
112
|
+
await deviceAuthorizationService.revoke(authorization);
|
|
113
|
+
throw new ExpiredTokenError("device_code has expired");
|
|
114
|
+
}
|
|
115
|
+
if (authorization.lastPolled) {
|
|
116
|
+
const elapsed = Date.now() - authorization.lastPolled.getTime();
|
|
117
|
+
const minInterval = authorization.interval * 1000;
|
|
118
|
+
if (elapsed < minInterval) {
|
|
119
|
+
authorization.interval += 5;
|
|
120
|
+
await this.#recordPoll(deviceAuthorizationService, authorization, deviceCode);
|
|
121
|
+
throw new SlowDownError("polling too frequently");
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (authorization.denied) {
|
|
125
|
+
await deviceAuthorizationService.revoke(authorization);
|
|
126
|
+
throw new AccessDeniedError("authorization request was denied");
|
|
127
|
+
}
|
|
128
|
+
if (!authorization.authorized || !authorization.user) {
|
|
129
|
+
await this.#recordPoll(deviceAuthorizationService, authorization, deviceCode);
|
|
130
|
+
throw new AuthorizationPendingError("authorization request is pending user approval");
|
|
131
|
+
}
|
|
132
|
+
const { user, scope } = authorization;
|
|
133
|
+
if (!await deviceAuthorizationService.revoke(authorization)) {
|
|
134
|
+
throw new InvalidGrantError("device_code already used");
|
|
135
|
+
}
|
|
136
|
+
const token = await this.generateToken(client, user, scope, tokenService);
|
|
137
|
+
return await tokenService.save(token);
|
|
138
|
+
}
|
|
139
|
+
async #recordPoll(deviceAuthorizationService, authorization, deviceCode) {
|
|
140
|
+
try {
|
|
141
|
+
await deviceAuthorizationService.updateLastPolled(authorization);
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
let stillStored;
|
|
145
|
+
try {
|
|
146
|
+
stillStored = await deviceAuthorizationService.getByDeviceCode(deviceCode);
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
throw error;
|
|
150
|
+
}
|
|
151
|
+
if (stillStored)
|
|
152
|
+
throw error;
|
|
153
|
+
throw new InvalidGrantError("device_code already used");
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base grant type implementation for OAuth2.
|
|
3
|
+
*
|
|
4
|
+
* Provides the abstract base class and the framework-facing grant contract for
|
|
5
|
+
* implementing OAuth2 grant types per RFC 6749.
|
|
6
|
+
*
|
|
7
|
+
* **Service resolution.** A grant holds no storage. It is constructed with a
|
|
8
|
+
* `resolve(request) => services` function that returns the full set of services
|
|
9
|
+
* that grant needs, and the base resolves them per request through
|
|
10
|
+
* `resolveServices`. The grant infers its `Client`/`User`/`Scope` types from
|
|
11
|
+
* that `resolve` return, so a grant can be built standalone (e.g. for tests) and
|
|
12
|
+
* the {@link AuthorizationServer} infers its own generics from the grants. The
|
|
13
|
+
* base provides stateless helpers (`acceptedScope`, `generateToken`) that take
|
|
14
|
+
* the resolved services as arguments.
|
|
15
|
+
*
|
|
16
|
+
* @see https://datatracker.ietf.org/doc/html/rfc6749#section-4
|
|
17
|
+
* @module
|
|
18
|
+
*/
|
|
19
|
+
import type { ClientCredentials, ClientInterface } from "../../models/client.js";
|
|
20
|
+
import type { RefreshToken, Token } from "../../models/token.js";
|
|
21
|
+
import type { AuthenticationContext } from "../../models/authentication.js";
|
|
22
|
+
import type { AbstractScope, ScopeConstructor } from "../../models/scope.js";
|
|
23
|
+
import { BasicScope } from "../../models/scope.js";
|
|
24
|
+
import type { ClientServiceInterface } from "../services/client.js";
|
|
25
|
+
import type { TokenServiceInterface } from "../services/token.js";
|
|
26
|
+
/**
|
|
27
|
+
* The core services every grant needs: client lookup/authentication and token
|
|
28
|
+
* storage. Each grant defines its own services interface by extending this with
|
|
29
|
+
* the grant-specific services it resolves (e.g. `authorizationCodeService`).
|
|
30
|
+
*/
|
|
31
|
+
export interface GrantServices<Client extends ClientInterface, User, Scope extends AbstractScope = BasicScope> {
|
|
32
|
+
/** Looks up and authenticates clients presenting a token request. */
|
|
33
|
+
clientService: ClientServiceInterface<Client, User>;
|
|
34
|
+
/** Generates and persists access and refresh tokens. */
|
|
35
|
+
tokenService: TokenServiceInterface<Client, User, Scope>;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The framework-facing grant contract that {@link AuthorizationServer}
|
|
39
|
+
* dispatches through. It carries no grant-specific service types, so any grant —
|
|
40
|
+
* including a third-party grant with its own services — is assignable to it and
|
|
41
|
+
* lives in the server's grant map without `any`. Grants resolve their own
|
|
42
|
+
* services from the request, so these methods take no services argument.
|
|
43
|
+
*/
|
|
44
|
+
export interface DispatchableGrant<Client extends ClientInterface, User, Scope extends AbstractScope = BasicScope> {
|
|
45
|
+
/** The OAuth2 `grant_type` value this grant handles (e.g. `authorization_code`). */
|
|
46
|
+
readonly grantType: string;
|
|
47
|
+
/**
|
|
48
|
+
* Whether this grant's tokens represent an end-user authentication, making
|
|
49
|
+
* them eligible for an id_token. Treated as `true` when absent; grants with
|
|
50
|
+
* no authentication event (client_credentials) set `false`.
|
|
51
|
+
*/
|
|
52
|
+
readonly issuesIdToken?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Authenticates the client presenting this grant's token request. `body` is
|
|
55
|
+
* the request's form body, parsed once by the token endpoint — a grant reads
|
|
56
|
+
* its parameters from it instead of consuming the request.
|
|
57
|
+
*/
|
|
58
|
+
getAuthenticatedClient(request: Request, body: FormData): Promise<Client>;
|
|
59
|
+
/**
|
|
60
|
+
* Exchanges the request for a token. `body` is the request's form body,
|
|
61
|
+
* parsed once by the token endpoint.
|
|
62
|
+
*/
|
|
63
|
+
token(request: Request, client: Client, body: FormData): Promise<Token<Client, User, Scope>>;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Configuration shared by all grants: how to reach the services the grant
|
|
67
|
+
* needs, plus the scope and refresh-token settings. Each grant's own options
|
|
68
|
+
* type extends this with its grant-specific settings.
|
|
69
|
+
*/
|
|
70
|
+
export interface GrantOptions<Client extends ClientInterface, User, Scope extends AbstractScope = BasicScope, Services extends GrantServices<Client, User, Scope> = GrantServices<Client, User, Scope>> {
|
|
71
|
+
/** Resolves the services this grant needs for a request. */
|
|
72
|
+
resolve: (request: Request) => Services | Promise<Services>;
|
|
73
|
+
/** Scope constructor used to parse scope strings. Defaults to {@linkcode BasicScope}. */
|
|
74
|
+
Scope?: ScopeConstructor<Scope>;
|
|
75
|
+
/** Allow optional refresh token. */
|
|
76
|
+
allowRefreshToken?: boolean;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Abstract base class for grant type implementations.
|
|
80
|
+
*
|
|
81
|
+
* Extend this to create custom grant types, parameterized by the services
|
|
82
|
+
* interface the grant resolves. A subclass implements {@link token}, reaches
|
|
83
|
+
* its services through {@link resolveServices}, and overrides
|
|
84
|
+
* {@link getAuthenticatedClient} only when it authenticates clients by
|
|
85
|
+
* something other than their registered credentials (as the authorization-code
|
|
86
|
+
* grant does for PKCE).
|
|
87
|
+
*/
|
|
88
|
+
export declare abstract class AbstractGrant<Client extends ClientInterface, User, Scope extends AbstractScope = BasicScope, Services extends GrantServices<Client, User, Scope> = GrantServices<Client, User, Scope>> implements DispatchableGrant<Client, User, Scope> {
|
|
89
|
+
#private;
|
|
90
|
+
/** The OAuth2 `grant_type` value this grant handles (e.g. `authorization_code`). */
|
|
91
|
+
abstract readonly grantType: string;
|
|
92
|
+
/** Constructor used to parse scope strings into {@linkcode Scope} objects. */
|
|
93
|
+
Scope: ScopeConstructor<Scope>;
|
|
94
|
+
/** Allow optional refresh token. Defaults to false. */
|
|
95
|
+
allowRefreshToken: boolean;
|
|
96
|
+
/** Creates a grant from the shared {@linkcode GrantOptions}. */
|
|
97
|
+
constructor(options: GrantOptions<Client, User, Scope, Services>);
|
|
98
|
+
/** The services this grant needs for `request`, from its `resolve` option. */
|
|
99
|
+
protected resolveServices(request: Request): Promise<Services>;
|
|
100
|
+
/** Parses a scope string into a Scope object. */
|
|
101
|
+
parseScope(scopeText?: string | null): Scope | undefined;
|
|
102
|
+
/**
|
|
103
|
+
* Validates and returns the accepted scope for the client and user, per the
|
|
104
|
+
* `TokenServiceInterface.acceptedScope` contract: a returned scope replaces
|
|
105
|
+
* the requested one (narrowing), `null`/`undefined` accepts the requested
|
|
106
|
+
* scope as-is, and `false` rejects.
|
|
107
|
+
*
|
|
108
|
+
* `user` is `undefined` when the client is acting as its own resource owner
|
|
109
|
+
* (client credentials, RFC 6749 §4.4), so the token service decides scope
|
|
110
|
+
* from the client alone.
|
|
111
|
+
*
|
|
112
|
+
* Throws InvalidScopeError if the scope is rejected.
|
|
113
|
+
*/
|
|
114
|
+
acceptedScope(client: Client, user: User | undefined, scope: Scope | null | undefined, tokenService: TokenServiceInterface<Client, User, Scope>): Promise<Scope | null | undefined>;
|
|
115
|
+
/**
|
|
116
|
+
* Extracts client credentials from a request: HTTP Basic first, then the
|
|
117
|
+
* `client_id`/`client_secret` of the already-parsed `body`. Override to add
|
|
118
|
+
* grant-specific credential rules (as the authorization-code grant does for
|
|
119
|
+
* PKCE).
|
|
120
|
+
*
|
|
121
|
+
* @throws {InvalidClientError} If the request presents no usable credentials.
|
|
122
|
+
*/
|
|
123
|
+
protected getClientCredentials(request: Request, body: FormData): ClientCredentials;
|
|
124
|
+
/**
|
|
125
|
+
* Clamps a token's expiries to the absolute expiry of its rotation family,
|
|
126
|
+
* so nothing derived from a family outlives it — an access token issued a
|
|
127
|
+
* moment before the ceiling included. Mutates `token`; a token with no
|
|
128
|
+
* expiry of its own takes the family's.
|
|
129
|
+
*/
|
|
130
|
+
protected capAtFamilyExpiry(token: Token<Client, User, Scope> | RefreshToken<Client, User, Scope>, familyExpiresAt: Date): void;
|
|
131
|
+
/**
|
|
132
|
+
* Generates a token for a client and user from a resolved `tokenService`.
|
|
133
|
+
* Includes a refresh token if allowed, which starts a new rotation family:
|
|
134
|
+
* `familyId` plus the `familyCreatedAt` anchor the refresh grant caps
|
|
135
|
+
* rotations against. When the token service caps families
|
|
136
|
+
* ({@linkcode TokenServiceInterface.refreshTokenFamilyExpiresAt}), both
|
|
137
|
+
* expiries are clamped to the new family's ceiling.
|
|
138
|
+
*
|
|
139
|
+
* Pass `user: undefined` for a token the client holds as its own resource
|
|
140
|
+
* owner (client credentials, RFC 6749 §4.4). Such a token is never issued a
|
|
141
|
+
* refresh token whatever `allowRefreshToken` says — there is no resource
|
|
142
|
+
* owner whose authorization a rotation could carry forward.
|
|
143
|
+
* `authenticationContext` is verified event evidence, snapshotted before
|
|
144
|
+
* generation and carried to storage. Omit it when unknown; it is discarded
|
|
145
|
+
* for tokens with no user.
|
|
146
|
+
*/
|
|
147
|
+
generateToken(client: Client, user: User | undefined, scope: Scope | null | undefined, tokenService: TokenServiceInterface<Client, User, Scope>, authenticationContext?: AuthenticationContext): Promise<Token<Client, User, Scope>>;
|
|
148
|
+
/**
|
|
149
|
+
* Authenticates the client presenting this grant's token request against the
|
|
150
|
+
* grant's own `clientService`, from the credentials the request presents.
|
|
151
|
+
*
|
|
152
|
+
* Override to apply grant-specific authentication rules — the
|
|
153
|
+
* authorization-code grant accepts a PKCE `code_verifier` in place of a
|
|
154
|
+
* client secret for public clients.
|
|
155
|
+
*
|
|
156
|
+
* @throws {InvalidClientError} If authentication fails.
|
|
157
|
+
*/
|
|
158
|
+
getAuthenticatedClient(request: Request, body: FormData): Promise<Client>;
|
|
159
|
+
/**
|
|
160
|
+
* Handles a token request. `body` is the request's form body, parsed once by
|
|
161
|
+
* the token endpoint; the grant resolves the services it needs from the
|
|
162
|
+
* request internally.
|
|
163
|
+
*/
|
|
164
|
+
abstract token(request: Request, client: Client, body: FormData): Promise<Token<Client, User, Scope>>;
|
|
165
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base grant type implementation for OAuth2.
|
|
3
|
+
*
|
|
4
|
+
* Provides the abstract base class and the framework-facing grant contract for
|
|
5
|
+
* implementing OAuth2 grant types per RFC 6749.
|
|
6
|
+
*
|
|
7
|
+
* **Service resolution.** A grant holds no storage. It is constructed with a
|
|
8
|
+
* `resolve(request) => services` function that returns the full set of services
|
|
9
|
+
* that grant needs, and the base resolves them per request through
|
|
10
|
+
* `resolveServices`. The grant infers its `Client`/`User`/`Scope` types from
|
|
11
|
+
* that `resolve` return, so a grant can be built standalone (e.g. for tests) and
|
|
12
|
+
* the {@link AuthorizationServer} infers its own generics from the grants. The
|
|
13
|
+
* base provides stateless helpers (`acceptedScope`, `generateToken`) that take
|
|
14
|
+
* the resolved services as arguments.
|
|
15
|
+
*
|
|
16
|
+
* @see https://datatracker.ietf.org/doc/html/rfc6749#section-4
|
|
17
|
+
* @module
|
|
18
|
+
*/
|
|
19
|
+
import { snapshotAuthenticationContext } from "../../utils/authentication-context.js";
|
|
20
|
+
import { BasicScope } from "../../models/scope.js";
|
|
21
|
+
import { InvalidScopeError } from "../../errors.js";
|
|
22
|
+
import { authenticateClientCredentials, extractClientCredentials, } from "../client-authentication.js";
|
|
23
|
+
/**
|
|
24
|
+
* Abstract base class for grant type implementations.
|
|
25
|
+
*
|
|
26
|
+
* Extend this to create custom grant types, parameterized by the services
|
|
27
|
+
* interface the grant resolves. A subclass implements {@link token}, reaches
|
|
28
|
+
* its services through {@link resolveServices}, and overrides
|
|
29
|
+
* {@link getAuthenticatedClient} only when it authenticates clients by
|
|
30
|
+
* something other than their registered credentials (as the authorization-code
|
|
31
|
+
* grant does for PKCE).
|
|
32
|
+
*/
|
|
33
|
+
export class AbstractGrant {
|
|
34
|
+
/** Constructor used to parse scope strings into {@linkcode Scope} objects. */
|
|
35
|
+
Scope;
|
|
36
|
+
/** Allow optional refresh token. Defaults to false. */
|
|
37
|
+
allowRefreshToken;
|
|
38
|
+
#resolve;
|
|
39
|
+
/** Creates a grant from the shared {@linkcode GrantOptions}. */
|
|
40
|
+
constructor(options) {
|
|
41
|
+
this.allowRefreshToken = options.allowRefreshToken ?? false;
|
|
42
|
+
this.Scope = options.Scope ??
|
|
43
|
+
BasicScope;
|
|
44
|
+
this.#resolve = options.resolve;
|
|
45
|
+
}
|
|
46
|
+
/** The services this grant needs for `request`, from its `resolve` option. */
|
|
47
|
+
resolveServices(request) {
|
|
48
|
+
return Promise.resolve(this.#resolve(request));
|
|
49
|
+
}
|
|
50
|
+
/** Parses a scope string into a Scope object. */
|
|
51
|
+
parseScope(scopeText) {
|
|
52
|
+
return scopeText ? new this.Scope(scopeText) : undefined;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Validates and returns the accepted scope for the client and user, per the
|
|
56
|
+
* `TokenServiceInterface.acceptedScope` contract: a returned scope replaces
|
|
57
|
+
* the requested one (narrowing), `null`/`undefined` accepts the requested
|
|
58
|
+
* scope as-is, and `false` rejects.
|
|
59
|
+
*
|
|
60
|
+
* `user` is `undefined` when the client is acting as its own resource owner
|
|
61
|
+
* (client credentials, RFC 6749 §4.4), so the token service decides scope
|
|
62
|
+
* from the client alone.
|
|
63
|
+
*
|
|
64
|
+
* Throws InvalidScopeError if the scope is rejected.
|
|
65
|
+
*/
|
|
66
|
+
async acceptedScope(client, user, scope, tokenService) {
|
|
67
|
+
const acceptedScope = await tokenService.acceptedScope(client, user, scope);
|
|
68
|
+
if (acceptedScope === false) {
|
|
69
|
+
throw new InvalidScopeError(scope ? "invalid scope" : "scope required");
|
|
70
|
+
}
|
|
71
|
+
return acceptedScope ?? scope;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Extracts client credentials from a request: HTTP Basic first, then the
|
|
75
|
+
* `client_id`/`client_secret` of the already-parsed `body`. Override to add
|
|
76
|
+
* grant-specific credential rules (as the authorization-code grant does for
|
|
77
|
+
* PKCE).
|
|
78
|
+
*
|
|
79
|
+
* @throws {InvalidClientError} If the request presents no usable credentials.
|
|
80
|
+
*/
|
|
81
|
+
getClientCredentials(request, body) {
|
|
82
|
+
return extractClientCredentials(request, body);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Clamps a token's expiries to the absolute expiry of its rotation family,
|
|
86
|
+
* so nothing derived from a family outlives it — an access token issued a
|
|
87
|
+
* moment before the ceiling included. Mutates `token`; a token with no
|
|
88
|
+
* expiry of its own takes the family's.
|
|
89
|
+
*/
|
|
90
|
+
capAtFamilyExpiry(token, familyExpiresAt) {
|
|
91
|
+
if (!token.accessTokenExpiresAt ||
|
|
92
|
+
familyExpiresAt < token.accessTokenExpiresAt) {
|
|
93
|
+
token.accessTokenExpiresAt = familyExpiresAt;
|
|
94
|
+
}
|
|
95
|
+
if ("refreshToken" in token) {
|
|
96
|
+
const refreshToken = token;
|
|
97
|
+
if (!refreshToken.refreshTokenExpiresAt ||
|
|
98
|
+
familyExpiresAt < refreshToken.refreshTokenExpiresAt) {
|
|
99
|
+
refreshToken.refreshTokenExpiresAt = familyExpiresAt;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Generates a token for a client and user from a resolved `tokenService`.
|
|
105
|
+
* Includes a refresh token if allowed, which starts a new rotation family:
|
|
106
|
+
* `familyId` plus the `familyCreatedAt` anchor the refresh grant caps
|
|
107
|
+
* rotations against. When the token service caps families
|
|
108
|
+
* ({@linkcode TokenServiceInterface.refreshTokenFamilyExpiresAt}), both
|
|
109
|
+
* expiries are clamped to the new family's ceiling.
|
|
110
|
+
*
|
|
111
|
+
* Pass `user: undefined` for a token the client holds as its own resource
|
|
112
|
+
* owner (client credentials, RFC 6749 §4.4). Such a token is never issued a
|
|
113
|
+
* refresh token whatever `allowRefreshToken` says — there is no resource
|
|
114
|
+
* owner whose authorization a rotation could carry forward.
|
|
115
|
+
* `authenticationContext` is verified event evidence, snapshotted before
|
|
116
|
+
* generation and carried to storage. Omit it when unknown; it is discarded
|
|
117
|
+
* for tokens with no user.
|
|
118
|
+
*/
|
|
119
|
+
async generateToken(client, user, scope, tokenService, authenticationContext) {
|
|
120
|
+
const event = user === undefined
|
|
121
|
+
? undefined
|
|
122
|
+
: snapshotAuthenticationContext(authenticationContext);
|
|
123
|
+
const token = {
|
|
124
|
+
accessToken: await tokenService.generateAccessToken(client, user, scope, event),
|
|
125
|
+
client,
|
|
126
|
+
};
|
|
127
|
+
if (event)
|
|
128
|
+
token.authenticationContext = event;
|
|
129
|
+
if (user !== undefined)
|
|
130
|
+
token.user = user;
|
|
131
|
+
if (scope)
|
|
132
|
+
token.scope = scope;
|
|
133
|
+
const accessTokenExpiresAt = await tokenService.accessTokenExpiresAt(client, user, scope);
|
|
134
|
+
if (accessTokenExpiresAt)
|
|
135
|
+
token.accessTokenExpiresAt = accessTokenExpiresAt;
|
|
136
|
+
if (this.allowRefreshToken && user !== undefined) {
|
|
137
|
+
const refreshToken = await tokenService.generateRefreshToken(client, user, scope);
|
|
138
|
+
if (refreshToken) {
|
|
139
|
+
const familyCreatedAt = new Date();
|
|
140
|
+
const result = {
|
|
141
|
+
...token,
|
|
142
|
+
refreshToken,
|
|
143
|
+
familyId: crypto.randomUUID(),
|
|
144
|
+
familyCreatedAt,
|
|
145
|
+
};
|
|
146
|
+
const refreshTokenExpiresAt = await tokenService.refreshTokenExpiresAt(client, user, scope);
|
|
147
|
+
if (refreshTokenExpiresAt) {
|
|
148
|
+
result.refreshTokenExpiresAt = refreshTokenExpiresAt;
|
|
149
|
+
}
|
|
150
|
+
const familyExpiresAt = await tokenService
|
|
151
|
+
.refreshTokenFamilyExpiresAt?.(client, user, familyCreatedAt, scope);
|
|
152
|
+
if (familyExpiresAt)
|
|
153
|
+
this.capAtFamilyExpiry(result, familyExpiresAt);
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return token;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Authenticates the client presenting this grant's token request against the
|
|
161
|
+
* grant's own `clientService`, from the credentials the request presents.
|
|
162
|
+
*
|
|
163
|
+
* Override to apply grant-specific authentication rules — the
|
|
164
|
+
* authorization-code grant accepts a PKCE `code_verifier` in place of a
|
|
165
|
+
* client secret for public clients.
|
|
166
|
+
*
|
|
167
|
+
* @throws {InvalidClientError} If authentication fails.
|
|
168
|
+
*/
|
|
169
|
+
async getAuthenticatedClient(request, body) {
|
|
170
|
+
const { clientService } = await this.resolveServices(request);
|
|
171
|
+
return await authenticateClientCredentials(this.getClientCredentials(request, body), clientService);
|
|
172
|
+
}
|
|
173
|
+
}
|