@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,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sign in with Apple connector. Apple is OpenID-Connect-shaped but does not fit
|
|
3
|
+
* {@link oidcProvider}: its client secret is a signed ES256 JWT rather than a
|
|
4
|
+
* static string (see {@link generateAppleClientSecret}), it authenticates the
|
|
5
|
+
* client in the token body (`client_secret_post`), and it requires
|
|
6
|
+
* `response_mode=form_post` whenever profile scopes are requested. This is a
|
|
7
|
+
* dedicated {@link ExternalProvider} that exchanges the code, then verifies the
|
|
8
|
+
* `id_token` signature against Apple's JWKS.
|
|
9
|
+
*
|
|
10
|
+
* The user's name is returned by Apple **only** on the first authorization, in
|
|
11
|
+
* the `form_post` body's `user` field — never in the `id_token`. This connector
|
|
12
|
+
* returns what the `id_token` carries (stable subject, email); capturing the
|
|
13
|
+
* first-authorization name is the callback route's responsibility, since only
|
|
14
|
+
* it sees the form body.
|
|
15
|
+
*
|
|
16
|
+
* @module
|
|
17
|
+
*/
|
|
18
|
+
import { toArrayBuffer } from "../../utils/_buffer.js";
|
|
19
|
+
import { base64urlDecode } from "../../utils/crypto.js";
|
|
20
|
+
import { assertIdTokenClaims, describeError } from "./_shared.js";
|
|
21
|
+
import { runTokenExchange } from "./_token-exchange.js";
|
|
22
|
+
import { createAppleClientSecretFactory, } from "./apple-client-secret.js";
|
|
23
|
+
import { ExternalAuthError } from "./errors.js";
|
|
24
|
+
const APPLE_ISSUER = "https://appleid.apple.com";
|
|
25
|
+
const APPLE_AUTHORIZE_URL = "https://appleid.apple.com/auth/authorize";
|
|
26
|
+
const APPLE_TOKEN_URL = "https://appleid.apple.com/auth/token";
|
|
27
|
+
const APPLE_JWKS_URL = "https://appleid.apple.com/auth/keys";
|
|
28
|
+
const encoder = new TextEncoder();
|
|
29
|
+
const decoder = new TextDecoder();
|
|
30
|
+
const JWKS_CACHE_TTL_MS = 60 * 60 * 1000;
|
|
31
|
+
const SIGNATURE_NOT_VERIFIED = `Apple id_token signature did not verify against Apple's JWKS — the ` +
|
|
32
|
+
`token is forged, corrupted, or signed by an unpublished key.`;
|
|
33
|
+
/**
|
|
34
|
+
* Creates a Sign in with Apple connector with id `"apple"`.
|
|
35
|
+
*
|
|
36
|
+
* The flow requests `response_mode=form_post` (Apple's requirement when `name`
|
|
37
|
+
* or `email` scope is present), exchanges the code at Apple's token endpoint
|
|
38
|
+
* with a freshly-signed client-secret JWT, and verifies the returned
|
|
39
|
+
* `id_token`'s RS256 signature against Apple's published keys before trusting
|
|
40
|
+
* its claims (`iss`, `aud`, `azp`, `exp`, and the `nonce` when one was sent,
|
|
41
|
+
* checked exactly as {@link oidcProvider} checks them). Apple's
|
|
42
|
+
* private-relay addresses (`@privaterelay.appleid.com`) are ordinary verified
|
|
43
|
+
* emails and are returned as-is. The raw `id_token` claims — including
|
|
44
|
+
* `is_private_email` — stay on {@link ExternalProfile.raw}.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* const flow = new ExternalAuthFlow({
|
|
49
|
+
* provider: appleProvider({
|
|
50
|
+
* clientId: "com.example.web",
|
|
51
|
+
* teamId: Deno.env.get("APPLE_TEAM_ID")!,
|
|
52
|
+
* keyId: Deno.env.get("APPLE_KEY_ID")!,
|
|
53
|
+
* privateKey: Deno.env.get("APPLE_PRIVATE_KEY")!,
|
|
54
|
+
* }),
|
|
55
|
+
* });
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export function appleProvider(options) {
|
|
59
|
+
const id = "apple";
|
|
60
|
+
const fetchImpl = (input, init) => (options.fetch ?? globalThis.fetch)(input, init);
|
|
61
|
+
const clientSecretFactory = options.clientSecret ??
|
|
62
|
+
createAppleClientSecretFactory({
|
|
63
|
+
providerId: id,
|
|
64
|
+
teamId: options.teamId,
|
|
65
|
+
keyId: options.keyId,
|
|
66
|
+
clientId: options.clientId,
|
|
67
|
+
privateKey: options.privateKey,
|
|
68
|
+
expiresInSeconds: options.expiresInSeconds,
|
|
69
|
+
});
|
|
70
|
+
let jwksCache = null;
|
|
71
|
+
async function exchangeCode(input) {
|
|
72
|
+
const clientSecret = await clientSecretFactory();
|
|
73
|
+
const { value } = await runTokenExchange({
|
|
74
|
+
provider: id,
|
|
75
|
+
displayName: "Apple",
|
|
76
|
+
endpoint: APPLE_TOKEN_URL,
|
|
77
|
+
body: new URLSearchParams({
|
|
78
|
+
grant_type: "authorization_code",
|
|
79
|
+
code: input.code,
|
|
80
|
+
redirect_uri: input.redirectUri,
|
|
81
|
+
client_id: options.clientId,
|
|
82
|
+
client_secret: clientSecret,
|
|
83
|
+
}),
|
|
84
|
+
requiredField: "id_token",
|
|
85
|
+
fetch: fetchImpl,
|
|
86
|
+
httpErrorHint: `An "invalid_client" here means the client-secret JWT ` +
|
|
87
|
+
`is wrong — check the team id, key id, and Services ID (client id).`,
|
|
88
|
+
tokenErrorHint: appleTokenErrorHint,
|
|
89
|
+
});
|
|
90
|
+
return value;
|
|
91
|
+
}
|
|
92
|
+
async function loadJwks() {
|
|
93
|
+
if (jwksCache && Date.now() - jwksCache.fetchedAt < JWKS_CACHE_TTL_MS) {
|
|
94
|
+
return jwksCache.keys;
|
|
95
|
+
}
|
|
96
|
+
let res;
|
|
97
|
+
try {
|
|
98
|
+
res = await fetchImpl(APPLE_JWKS_URL, {
|
|
99
|
+
headers: { Accept: "application/json" },
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
throw new ExternalAuthError(id, "provider_error", `could not reach Apple's JWKS endpoint (${describeError(error)}).`, { cause: error });
|
|
104
|
+
}
|
|
105
|
+
if (!res.ok) {
|
|
106
|
+
await res.body?.cancel();
|
|
107
|
+
throw new ExternalAuthError(id, "provider_error", `Apple's JWKS endpoint responded with HTTP ${res.status}.`);
|
|
108
|
+
}
|
|
109
|
+
let doc;
|
|
110
|
+
try {
|
|
111
|
+
doc = (await res.json());
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
throw new ExternalAuthError(id, "provider_error", `Apple's JWKS endpoint returned invalid JSON (${describeError(error)}).`, { cause: error });
|
|
115
|
+
}
|
|
116
|
+
if (!Array.isArray(doc.keys)) {
|
|
117
|
+
throw new ExternalAuthError(id, "provider_error", `Apple's JWKS endpoint returned no "keys" array.`);
|
|
118
|
+
}
|
|
119
|
+
const keys = doc.keys;
|
|
120
|
+
jwksCache = { keys, fetchedAt: Date.now() };
|
|
121
|
+
return keys;
|
|
122
|
+
}
|
|
123
|
+
async function verifyIdToken(idToken, expectedNonce) {
|
|
124
|
+
const parts = idToken.split(".");
|
|
125
|
+
if (parts.length !== 3) {
|
|
126
|
+
throw new ExternalAuthError(id, "provider_error", `Apple returned a malformed id_token (expected three segments).`);
|
|
127
|
+
}
|
|
128
|
+
const header = decodeJson(id, parts[0], "id_token header");
|
|
129
|
+
const kid = typeof header.kid === "string" ? header.kid : undefined;
|
|
130
|
+
if (header.alg !== "RS256") {
|
|
131
|
+
throw new ExternalAuthError(id, "provider_error", `Apple id_token is signed with "${String(header.alg)}", expected ` +
|
|
132
|
+
`RS256.`);
|
|
133
|
+
}
|
|
134
|
+
if (!kid) {
|
|
135
|
+
throw new ExternalAuthError(id, "provider_error", `Apple id_token header has no "kid" — refusing to bind it to an ` +
|
|
136
|
+
`arbitrary JWKS key. Apple always sets "kid" on a real token.`);
|
|
137
|
+
}
|
|
138
|
+
const jwk = await resolveVerificationKey(kid);
|
|
139
|
+
let key;
|
|
140
|
+
try {
|
|
141
|
+
key = await crypto.subtle.importKey("jwk", jwk, { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }, false, ["verify"]);
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
throw new ExternalAuthError(id, "provider_error", `Apple's signing key could not be imported (${describeError(error)}).`, { cause: error });
|
|
145
|
+
}
|
|
146
|
+
let signature;
|
|
147
|
+
try {
|
|
148
|
+
signature = base64urlDecode(parts[2]);
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
throw new ExternalAuthError(id, "provider_error", SIGNATURE_NOT_VERIFIED, {
|
|
152
|
+
cause: error,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
const valid = await crypto.subtle.verify({ name: "RSASSA-PKCS1-v1_5" }, key, toArrayBuffer(signature), encoder.encode(`${parts[0]}.${parts[1]}`));
|
|
156
|
+
if (!valid) {
|
|
157
|
+
throw new ExternalAuthError(id, "provider_error", SIGNATURE_NOT_VERIFIED);
|
|
158
|
+
}
|
|
159
|
+
const claims = decodeJson(id, parts[1], "id_token payload");
|
|
160
|
+
assertIdTokenClaims({
|
|
161
|
+
provider: id,
|
|
162
|
+
claims,
|
|
163
|
+
issuer: APPLE_ISSUER,
|
|
164
|
+
clientId: options.clientId,
|
|
165
|
+
expectedNonce,
|
|
166
|
+
});
|
|
167
|
+
return claims;
|
|
168
|
+
}
|
|
169
|
+
async function resolveVerificationKey(kid) {
|
|
170
|
+
const pick = (keys) => keys.find((key) => key.kid === kid);
|
|
171
|
+
let match = pick(await loadJwks());
|
|
172
|
+
if (!match) {
|
|
173
|
+
jwksCache = null;
|
|
174
|
+
match = pick(await loadJwks());
|
|
175
|
+
}
|
|
176
|
+
if (!match) {
|
|
177
|
+
throw new ExternalAuthError(id, "provider_error", `Apple's JWKS has no key with kid "${String(kid)}" — the id_token was ` +
|
|
178
|
+
`signed by a key Apple no longer publishes.`);
|
|
179
|
+
}
|
|
180
|
+
return match;
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
id,
|
|
184
|
+
displayName: "Apple",
|
|
185
|
+
defaultScopes: options.scopes ?? ["name", "email"],
|
|
186
|
+
usesPkce: false,
|
|
187
|
+
usesNonce: true,
|
|
188
|
+
buildAuthorizationUrl(input) {
|
|
189
|
+
const url = new URL(APPLE_AUTHORIZE_URL);
|
|
190
|
+
url.searchParams.set("response_type", "code");
|
|
191
|
+
url.searchParams.set("client_id", options.clientId);
|
|
192
|
+
url.searchParams.set("redirect_uri", input.redirectUri);
|
|
193
|
+
url.searchParams.set("scope", input.scopes.join(" "));
|
|
194
|
+
url.searchParams.set("state", input.state);
|
|
195
|
+
if (input.nonce)
|
|
196
|
+
url.searchParams.set("nonce", input.nonce);
|
|
197
|
+
if (input.scopes.length > 0) {
|
|
198
|
+
url.searchParams.set("response_mode", "form_post");
|
|
199
|
+
}
|
|
200
|
+
return Promise.resolve(url.toString());
|
|
201
|
+
},
|
|
202
|
+
async fetchProfile(input) {
|
|
203
|
+
const idToken = await exchangeCode(input);
|
|
204
|
+
const claims = await verifyIdToken(idToken, input.nonce);
|
|
205
|
+
return profileFromClaims(claims);
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
function profileFromClaims(claims) {
|
|
210
|
+
const subject = claims.sub;
|
|
211
|
+
if (typeof subject !== "string" || subject.length === 0) {
|
|
212
|
+
throw new ExternalAuthError("apple", "provider_error", `Apple id_token is missing the required "sub" claim.`);
|
|
213
|
+
}
|
|
214
|
+
return {
|
|
215
|
+
provider: "apple",
|
|
216
|
+
subject,
|
|
217
|
+
email: typeof claims.email === "string" ? claims.email : undefined,
|
|
218
|
+
emailVerified: isAppleTrue(claims.email_verified),
|
|
219
|
+
raw: claims,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
function isAppleTrue(value) {
|
|
223
|
+
return value === true || value === "true";
|
|
224
|
+
}
|
|
225
|
+
function appleTokenErrorHint(error) {
|
|
226
|
+
if (error === undefined) {
|
|
227
|
+
return `Ensure the sign-in requested the "openid" flow scopes.`;
|
|
228
|
+
}
|
|
229
|
+
if (error === "invalid_client") {
|
|
230
|
+
return `The client-secret JWT is wrong — check the team id, key id, and ` +
|
|
231
|
+
`Services ID (client id).`;
|
|
232
|
+
}
|
|
233
|
+
if (error === "invalid_grant") {
|
|
234
|
+
return `Apple rejected the code — it expired, was already used, or the ` +
|
|
235
|
+
`redirect URI does not match the Services ID configuration. Ask the ` +
|
|
236
|
+
`user to start again.`;
|
|
237
|
+
}
|
|
238
|
+
return `See Apple's "Generate and validate tokens" documentation for the ` +
|
|
239
|
+
`error's meaning.`;
|
|
240
|
+
}
|
|
241
|
+
function decodeJson(providerId, segment, what) {
|
|
242
|
+
try {
|
|
243
|
+
return JSON.parse(decoder.decode(base64urlDecode(segment)));
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
throw new ExternalAuthError(providerId, "provider_error", `Apple ${what} was not valid base64url JSON (${describeError(error)}).`, { cause: error });
|
|
247
|
+
}
|
|
248
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discord sign-in preset over the generic {@link oauth2Provider} connector.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
import type { ExternalProvider } from "./provider.js";
|
|
7
|
+
/** Options for {@link discordProvider}. */
|
|
8
|
+
export interface DiscordProviderOptions {
|
|
9
|
+
/** Client ID of the Discord application (OAuth2 tab). */
|
|
10
|
+
clientId: string;
|
|
11
|
+
/** The matching client secret. */
|
|
12
|
+
clientSecret: string;
|
|
13
|
+
/** Scopes requested by default. Defaults to `["identify", "email"]`. */
|
|
14
|
+
scopes?: string[];
|
|
15
|
+
/** Fetch implementation for all provider traffic. Defaults to `globalThis.fetch`. */
|
|
16
|
+
fetch?: typeof fetch;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Creates a Discord connector with id `"discord"`.
|
|
20
|
+
*
|
|
21
|
+
* Discord speaks plain OAuth2 (no discovery, no id_token), so this is a preset
|
|
22
|
+
* over {@link oauth2Provider} pinned to Discord's fixed endpoints. The profile
|
|
23
|
+
* is read from `GET /users/@me`: the subject is the Discord user id,
|
|
24
|
+
* `emailVerified` reflects Discord's `verified` flag for the account email, and
|
|
25
|
+
* the display name prefers the global display name over the legacy username.
|
|
26
|
+
* The `/users/@me` payload is available on {@link ExternalProfile.raw}.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* const flow = new ExternalAuthFlow({
|
|
31
|
+
* provider: discordProvider({
|
|
32
|
+
* clientId: Deno.env.get("DISCORD_CLIENT_ID")!,
|
|
33
|
+
* clientSecret: Deno.env.get("DISCORD_CLIENT_SECRET")!,
|
|
34
|
+
* }),
|
|
35
|
+
* });
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export declare function discordProvider(options: DiscordProviderOptions): ExternalProvider;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discord sign-in preset over the generic {@link oauth2Provider} connector.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
import { oauth2Provider } from "./oauth2.js";
|
|
7
|
+
const DISCORD_AUTHORIZE_URL = "https://discord.com/oauth2/authorize";
|
|
8
|
+
const DISCORD_TOKEN_URL = "https://discord.com/api/oauth2/token";
|
|
9
|
+
const DISCORD_USER_URL = "https://discord.com/api/users/@me";
|
|
10
|
+
function avatarUrl(user) {
|
|
11
|
+
const id = user.id;
|
|
12
|
+
const avatar = user.avatar;
|
|
13
|
+
if (typeof id === "string" && typeof avatar === "string" && avatar) {
|
|
14
|
+
const ext = avatar.startsWith("a_") ? "gif" : "png";
|
|
15
|
+
return `https://cdn.discordapp.com/avatars/${id}/${avatar}.${ext}`;
|
|
16
|
+
}
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Creates a Discord connector with id `"discord"`.
|
|
21
|
+
*
|
|
22
|
+
* Discord speaks plain OAuth2 (no discovery, no id_token), so this is a preset
|
|
23
|
+
* over {@link oauth2Provider} pinned to Discord's fixed endpoints. The profile
|
|
24
|
+
* is read from `GET /users/@me`: the subject is the Discord user id,
|
|
25
|
+
* `emailVerified` reflects Discord's `verified` flag for the account email, and
|
|
26
|
+
* the display name prefers the global display name over the legacy username.
|
|
27
|
+
* The `/users/@me` payload is available on {@link ExternalProfile.raw}.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* const flow = new ExternalAuthFlow({
|
|
32
|
+
* provider: discordProvider({
|
|
33
|
+
* clientId: Deno.env.get("DISCORD_CLIENT_ID")!,
|
|
34
|
+
* clientSecret: Deno.env.get("DISCORD_CLIENT_SECRET")!,
|
|
35
|
+
* }),
|
|
36
|
+
* });
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export function discordProvider(options) {
|
|
40
|
+
return oauth2Provider({
|
|
41
|
+
id: "discord",
|
|
42
|
+
displayName: "Discord",
|
|
43
|
+
authorizationEndpoint: DISCORD_AUTHORIZE_URL,
|
|
44
|
+
tokenEndpoint: DISCORD_TOKEN_URL,
|
|
45
|
+
userInfoEndpoint: DISCORD_USER_URL,
|
|
46
|
+
clientId: options.clientId,
|
|
47
|
+
clientSecret: options.clientSecret,
|
|
48
|
+
defaultScopes: options.scopes ?? ["identify", "email"],
|
|
49
|
+
fetch: options.fetch,
|
|
50
|
+
mapProfile: ({ profile }) => ({
|
|
51
|
+
subject: typeof profile.id === "string" ? profile.id : "",
|
|
52
|
+
email: typeof profile.email === "string" ? profile.email : undefined,
|
|
53
|
+
emailVerified: profile.verified === true,
|
|
54
|
+
name: typeof profile.global_name === "string" && profile.global_name
|
|
55
|
+
? profile.global_name
|
|
56
|
+
: typeof profile.username === "string"
|
|
57
|
+
? profile.username
|
|
58
|
+
: undefined,
|
|
59
|
+
picture: avatarUrl(profile),
|
|
60
|
+
}),
|
|
61
|
+
});
|
|
62
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The error contract for external-provider (social login) flows.
|
|
3
|
+
*
|
|
4
|
+
* Every failure in the external connector layer is thrown as an
|
|
5
|
+
* {@link ExternalAuthError} whose message names the provider, what failed, and
|
|
6
|
+
* the likely fix — misconfiguration is meant to be diagnosable from the
|
|
7
|
+
* message alone. The {@link ExternalAuthErrorCode} tells app code how to
|
|
8
|
+
* respond: `configuration` is a deploy-time bug to fix, everything else is a
|
|
9
|
+
* per-attempt failure to surface to the user (usually "sign in again").
|
|
10
|
+
*
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Machine-readable causes for an {@link ExternalAuthError}.
|
|
15
|
+
*
|
|
16
|
+
* - `configuration` — the connector is miswired (bad issuer, failed
|
|
17
|
+
* discovery, missing endpoint, provider/transient mismatch). Fix the
|
|
18
|
+
* deployment; retrying the sign-in will not help.
|
|
19
|
+
* - `provider_error` — the provider rejected the request (callback `error`
|
|
20
|
+
* param, token-endpoint error, profile-endpoint failure, malformed
|
|
21
|
+
* response). The message carries the provider's error code/description.
|
|
22
|
+
* - `invalid_callback` — the callback URL is missing `code` or `state`.
|
|
23
|
+
* - `state_mismatch` — the callback `state` does not match the transient
|
|
24
|
+
* (possible CSRF or a stale/mixed-up sign-in attempt).
|
|
25
|
+
* - `transient_expired` — the sign-in attempt outlived the configured max
|
|
26
|
+
* transient age; the user should start again.
|
|
27
|
+
* - `nonce_mismatch` — the id_token `nonce` claim does not match the
|
|
28
|
+
* transient (possible token replay); the user should start again.
|
|
29
|
+
*/
|
|
30
|
+
export type ExternalAuthErrorCode = "configuration" | "provider_error" | "invalid_callback" | "state_mismatch" | "transient_expired" | "nonce_mismatch";
|
|
31
|
+
/**
|
|
32
|
+
* A diagnosable error from an external-provider flow. The message is always
|
|
33
|
+
* prefixed with the provider id (e.g. `[google] …`) and states what failed
|
|
34
|
+
* plus the likely fix; switch on {@link ExternalAuthError.code} to decide
|
|
35
|
+
* whether to alert an operator (`configuration`) or restart the sign-in.
|
|
36
|
+
*/
|
|
37
|
+
export declare class ExternalAuthError extends Error {
|
|
38
|
+
/** Always `"ExternalAuthError"`, so instances are recognizable across realms. */
|
|
39
|
+
readonly name = "ExternalAuthError";
|
|
40
|
+
/** The machine-readable failure cause. */
|
|
41
|
+
readonly code: ExternalAuthErrorCode;
|
|
42
|
+
/** The id of the provider the failure belongs to (e.g. `"google"`). */
|
|
43
|
+
readonly provider: string;
|
|
44
|
+
/** Builds the error; `message` is prefixed with `[provider]` automatically. */
|
|
45
|
+
constructor(provider: string, code: ExternalAuthErrorCode, message: string, options?: ErrorOptions);
|
|
46
|
+
}
|
|
47
|
+
/** Type guard for {@link ExternalAuthError}. */
|
|
48
|
+
export declare function isExternalAuthError(value: unknown): value is ExternalAuthError;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The error contract for external-provider (social login) flows.
|
|
3
|
+
*
|
|
4
|
+
* Every failure in the external connector layer is thrown as an
|
|
5
|
+
* {@link ExternalAuthError} whose message names the provider, what failed, and
|
|
6
|
+
* the likely fix — misconfiguration is meant to be diagnosable from the
|
|
7
|
+
* message alone. The {@link ExternalAuthErrorCode} tells app code how to
|
|
8
|
+
* respond: `configuration` is a deploy-time bug to fix, everything else is a
|
|
9
|
+
* per-attempt failure to surface to the user (usually "sign in again").
|
|
10
|
+
*
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* A diagnosable error from an external-provider flow. The message is always
|
|
15
|
+
* prefixed with the provider id (e.g. `[google] …`) and states what failed
|
|
16
|
+
* plus the likely fix; switch on {@link ExternalAuthError.code} to decide
|
|
17
|
+
* whether to alert an operator (`configuration`) or restart the sign-in.
|
|
18
|
+
*/
|
|
19
|
+
export class ExternalAuthError extends Error {
|
|
20
|
+
/** Always `"ExternalAuthError"`, so instances are recognizable across realms. */
|
|
21
|
+
name = "ExternalAuthError";
|
|
22
|
+
/** The machine-readable failure cause. */
|
|
23
|
+
code;
|
|
24
|
+
/** The id of the provider the failure belongs to (e.g. `"google"`). */
|
|
25
|
+
provider;
|
|
26
|
+
/** Builds the error; `message` is prefixed with `[provider]` automatically. */
|
|
27
|
+
constructor(provider, code, message, options) {
|
|
28
|
+
super(`[${provider}] ${message}`, options);
|
|
29
|
+
this.provider = provider;
|
|
30
|
+
this.code = code;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** Type guard for {@link ExternalAuthError}. */
|
|
34
|
+
export function isExternalAuthError(value) {
|
|
35
|
+
return value instanceof ExternalAuthError;
|
|
36
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The provider-agnostic two-step flow for external (social) sign-in:
|
|
3
|
+
* {@link ExternalAuthFlow.start} builds the redirect and hands back a
|
|
4
|
+
* serializable transient; {@link ExternalAuthFlow.finish} verifies the
|
|
5
|
+
* callback and returns the normalized profile.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
import type { ExternalAuthTransient, ExternalProfile, ExternalProvider } from "./provider.js";
|
|
10
|
+
/** Default {@link ExternalAuthFlowOptions.maxTransientAgeMs}: 10 minutes. */
|
|
11
|
+
export declare const DEFAULT_MAX_TRANSIENT_AGE_MS: number;
|
|
12
|
+
/** Options for {@link ExternalAuthFlow}. */
|
|
13
|
+
export interface ExternalAuthFlowOptions {
|
|
14
|
+
/** The connector to drive (e.g. `googleProvider(…)`). */
|
|
15
|
+
provider: ExternalProvider;
|
|
16
|
+
/**
|
|
17
|
+
* Max age of a transient before {@link ExternalAuthFlow.finish} rejects the
|
|
18
|
+
* attempt as expired, in ms. Bounds the window in which a stolen transient
|
|
19
|
+
* or callback URL stays usable. Defaults to
|
|
20
|
+
* {@link DEFAULT_MAX_TRANSIENT_AGE_MS}.
|
|
21
|
+
*/
|
|
22
|
+
maxTransientAgeMs?: number;
|
|
23
|
+
}
|
|
24
|
+
/** Input to {@link ExternalAuthFlow.start}. */
|
|
25
|
+
export interface ExternalAuthStartInput {
|
|
26
|
+
/** The callback URL registered with the provider for this app. */
|
|
27
|
+
redirectUri: string;
|
|
28
|
+
/** Override the provider's default scopes. */
|
|
29
|
+
scopes?: string[];
|
|
30
|
+
/** OIDC `prompt` parameter (e.g. `"select_account"`). */
|
|
31
|
+
prompt?: string;
|
|
32
|
+
}
|
|
33
|
+
/** Result of {@link ExternalAuthFlow.start}. */
|
|
34
|
+
export interface ExternalAuthStartResult {
|
|
35
|
+
/** The provider authorization URL to redirect the user's browser to. */
|
|
36
|
+
url: string;
|
|
37
|
+
/**
|
|
38
|
+
* Per-attempt state to persist in a cookie/session and hand back to
|
|
39
|
+
* {@link ExternalAuthFlow.finish}. JSON-serializable; contains no tokens.
|
|
40
|
+
*/
|
|
41
|
+
transient: ExternalAuthTransient;
|
|
42
|
+
}
|
|
43
|
+
/** Input to {@link ExternalAuthFlow.finish}. */
|
|
44
|
+
export interface ExternalAuthFinishInput {
|
|
45
|
+
/** The callback URL's search params (or the full URL / query string). */
|
|
46
|
+
params: URL | URLSearchParams | string;
|
|
47
|
+
/** The transient produced by the matching {@link ExternalAuthFlow.start}. */
|
|
48
|
+
transient: ExternalAuthTransient;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Drives one external provider through the authorization-code redirect dance
|
|
52
|
+
* without the library storing anything: {@link start} returns the redirect URL
|
|
53
|
+
* plus a JSON-serializable {@link ExternalAuthTransient} for the caller to keep
|
|
54
|
+
* (sealed cookie or session), and {@link finish} takes the callback params and
|
|
55
|
+
* that transient back and returns the verified {@link ExternalProfile}.
|
|
56
|
+
*
|
|
57
|
+
* The flow itself performs no network I/O — inject `fetch` on the provider to
|
|
58
|
+
* stub the network in tests. Create one flow per configured provider.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```ts
|
|
62
|
+
* const flow = new ExternalAuthFlow({
|
|
63
|
+
* provider: googleProvider({ clientId, clientSecret }),
|
|
64
|
+
* });
|
|
65
|
+
*
|
|
66
|
+
* // GET /auth/social/google — redirect to Google, transient into a cookie.
|
|
67
|
+
* const { url, transient } = await flow.start({
|
|
68
|
+
* redirectUri: "https://app.example.com/auth/social/google/callback",
|
|
69
|
+
* });
|
|
70
|
+
*
|
|
71
|
+
* // GET /auth/social/google/callback — verify and normalize.
|
|
72
|
+
* const profile = await flow.finish({
|
|
73
|
+
* params: new URL(request.url).searchParams,
|
|
74
|
+
* transient,
|
|
75
|
+
* });
|
|
76
|
+
* // App code decides: look up (profile.provider, profile.subject), create
|
|
77
|
+
* // or link a user, then start your own session.
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
export declare class ExternalAuthFlow {
|
|
81
|
+
#private;
|
|
82
|
+
/** The connector this flow drives. */
|
|
83
|
+
readonly provider: ExternalProvider;
|
|
84
|
+
/** Creates a flow for one configured provider. */
|
|
85
|
+
constructor(options: ExternalAuthFlowOptions);
|
|
86
|
+
/**
|
|
87
|
+
* Begins a sign-in attempt: generates the per-attempt `state` (plus a PKCE
|
|
88
|
+
* verifier and OIDC nonce when the provider uses them), builds the
|
|
89
|
+
* provider's authorization URL, and returns both. Persist the transient
|
|
90
|
+
* (e.g. sealed cookie) and redirect the browser to `url`.
|
|
91
|
+
*
|
|
92
|
+
* @throws {ExternalAuthError} `configuration` when the provider cannot
|
|
93
|
+
* resolve its endpoints (e.g. OIDC discovery failed).
|
|
94
|
+
*/
|
|
95
|
+
start(input: ExternalAuthStartInput): Promise<ExternalAuthStartResult>;
|
|
96
|
+
/**
|
|
97
|
+
* Completes a sign-in attempt from the provider callback: enforces the max
|
|
98
|
+
* transient age, surfaces provider `error` callback params, verifies
|
|
99
|
+
* `state` with a timing-safe compare, exchanges the code, and (for OIDC
|
|
100
|
+
* providers) validates the id_token nonce — then returns the normalized
|
|
101
|
+
* profile.
|
|
102
|
+
*
|
|
103
|
+
* @throws {ExternalAuthError} with a code describing the failure — see
|
|
104
|
+
* {@link ExternalAuthErrorCode} for how app code should respond to each.
|
|
105
|
+
*/
|
|
106
|
+
finish(input: ExternalAuthFinishInput): Promise<ExternalProfile>;
|
|
107
|
+
}
|