@udibo/oauth2 0.14.1 → 0.15.1
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/README.md +45 -24
- package/{esm → dist}/adapters/hono/_common.d.ts +5 -5
- package/{esm → dist}/adapters/hono/_common.js +1 -1
- package/{esm → dist}/adapters/hono/authorization-server.d.ts +6 -7
- package/{esm → dist}/adapters/hono/authorization-server.js +0 -1
- package/{esm → dist}/adapters/hono/bff/auth-request-store.d.ts +2 -2
- package/{esm → dist}/adapters/hono/bff/auth-request-store.js +2 -2
- package/{esm → dist}/adapters/hono/bff/bff.d.ts +8 -8
- package/{esm → dist}/adapters/hono/bff/bff.js +33 -27
- package/{esm → dist}/adapters/hono/bff/cookie-prefix.js +1 -2
- package/{esm → dist}/adapters/hono/bff/mod.d.ts +5 -6
- package/{esm → dist}/adapters/hono/bff/mod.js +1 -2
- package/{esm → dist}/adapters/hono/bff/proxy.js +2 -1
- package/{esm → dist}/adapters/hono/bff/session-store.d.ts +2 -2
- package/{esm → dist}/adapters/hono/bff/session-store.js +7 -6
- package/{esm → dist}/adapters/hono/bff/testing.d.ts +7 -8
- package/{esm → dist}/adapters/hono/bff/testing.js +19 -22
- package/{esm → dist}/adapters/hono/identity.d.ts +1 -2
- package/{esm → dist}/adapters/hono/identity.js +10 -10
- package/{esm → dist}/adapters/hono/log.d.ts +0 -1
- package/{esm → dist}/adapters/hono/log.js +0 -6
- package/{esm → dist}/adapters/hono/resource-server.d.ts +5 -6
- package/{esm → dist}/adapters/hono/resource-server.js +0 -1
- package/dist/cli/bin.d.ts +2 -0
- package/dist/cli/bin.js +3 -0
- package/dist/cli/idp/config.d.ts +110 -0
- package/dist/cli/idp/config.js +269 -0
- package/dist/cli/idp/mod.d.ts +20 -0
- package/dist/cli/idp/mod.js +175 -0
- package/dist/cli/idp/pages.d.ts +56 -0
- package/dist/cli/idp/pages.js +190 -0
- package/dist/cli/idp/server.d.ts +93 -0
- package/dist/cli/idp/server.js +559 -0
- package/dist/cli/keygen.d.ts +13 -0
- package/dist/cli/keygen.js +30 -0
- package/dist/cli/mod.d.ts +34 -0
- package/dist/cli/mod.js +94 -0
- package/{esm → dist}/client/_http.d.ts +1 -1
- package/{esm → dist}/client/base.d.ts +1 -1
- package/{esm → dist}/client/bff-client.d.ts +1 -1
- package/{esm → dist}/client/bff-client.js +7 -6
- package/{esm → dist}/client/browser-storage.d.ts +1 -1
- package/{esm → dist}/client/check.js +1 -1
- package/{esm → dist}/client/direct-client.d.ts +6 -6
- package/{esm → dist}/client/direct-client.js +11 -13
- package/{esm → dist}/client/discovery-cache.d.ts +1 -1
- package/{esm → dist}/client/discovery-cache.js +2 -2
- package/{esm → dist}/client/events.d.ts +1 -1
- package/{esm → dist}/client/mod.d.ts +11 -12
- package/{esm → dist}/client/mod.js +1 -1
- package/{esm → dist}/errors.d.ts +1 -1
- package/{esm → dist}/errors.js +1 -1
- package/{esm → dist}/identity/events.d.ts +1 -1
- package/{esm → dist}/identity/external/_shared.d.ts +1 -1
- package/{esm → dist}/identity/external/_shared.js +1 -2
- package/{esm → dist}/identity/external/apple-client-secret.d.ts +1 -1
- package/{esm → dist}/identity/external/apple-client-secret.js +6 -7
- package/{esm → dist}/identity/external/apple.d.ts +5 -5
- package/{esm → dist}/identity/external/apple.js +10 -11
- package/{esm → dist}/identity/external/discord.d.ts +3 -3
- package/{esm → dist}/identity/external/discord.js +2 -2
- package/{esm → dist}/identity/external/flow.d.ts +1 -1
- package/{esm → dist}/identity/external/flow.js +2 -2
- package/{esm → dist}/identity/external/github.d.ts +3 -3
- package/{esm → dist}/identity/external/github.js +12 -17
- package/{esm → dist}/identity/external/google.d.ts +5 -5
- package/{esm → dist}/identity/external/google.js +2 -2
- package/{esm → dist}/identity/external/mod.d.ts +12 -13
- package/{esm → dist}/identity/external/mod.js +0 -1
- package/{esm → dist}/identity/external/oauth2.d.ts +1 -1
- package/{esm → dist}/identity/external/oauth2.js +8 -8
- package/{esm → dist}/identity/external/oidc.d.ts +4 -4
- package/{esm → dist}/identity/external/oidc.js +9 -8
- package/{esm → dist}/identity/hibp.d.ts +1 -1
- package/{esm → dist}/identity/hibp.js +1 -1
- package/{esm → dist}/identity/lockout.js +1 -2
- package/{esm → dist}/identity/mfa/mod.d.ts +4 -5
- package/{esm → dist}/identity/mfa/mod.js +1 -2
- package/{esm → dist}/identity/mfa/service.d.ts +3 -3
- package/{esm → dist}/identity/mfa/service.js +4 -3
- package/{esm → dist}/identity/mfa/totp.d.ts +1 -1
- package/{esm → dist}/identity/mfa/totp.js +2 -2
- package/{esm → dist}/identity/migration.d.ts +2 -3
- package/{esm → dist}/identity/migration.js +3 -4
- package/{esm → dist}/identity/mod.d.ts +14 -15
- package/{esm → dist}/identity/mod.js +0 -1
- package/{esm → dist}/identity/otp.d.ts +1 -1
- package/{esm → dist}/identity/otp.js +4 -2
- package/{esm → dist}/identity/password.js +8 -5
- package/{esm → dist}/identity/rate-limit.d.ts +1 -1
- package/{esm → dist}/identity/service.d.ts +10 -10
- package/{esm → dist}/identity/service.js +17 -18
- package/{esm → dist}/identity/session.js +2 -2
- package/{esm → dist}/identity/token-flow.js +3 -2
- package/{esm → dist}/models/authorization-code.d.ts +3 -3
- package/{esm → dist}/models/authorization.d.ts +1 -1
- package/{esm → dist}/models/authorization.js +1 -2
- package/{esm → dist}/models/device-authorization.d.ts +2 -2
- package/{esm → dist}/models/token.d.ts +3 -3
- package/{esm → dist}/react/callback.d.ts +1 -1
- package/{esm → dist}/react/callback.js +0 -1
- package/{esm → dist}/react/components/class-names.d.ts +1 -1
- package/{esm → dist}/react/components/class-names.js +3 -2
- package/{esm → dist}/react/components/internal.d.ts +2 -2
- package/{esm → dist}/react/components/internal.js +5 -11
- package/{esm → dist}/react/components/mfa-challenge-form.d.ts +2 -2
- package/{esm → dist}/react/components/mfa-challenge-form.js +4 -6
- package/{esm → dist}/react/components/mfa-enrollment-form.d.ts +2 -2
- package/{esm → dist}/react/components/mfa-enrollment-form.js +2 -6
- package/{esm → dist}/react/components/mod.d.ts +20 -21
- package/{esm → dist}/react/components/mod.js +1 -2
- package/{esm → dist}/react/components/request-password-reset-form.d.ts +2 -2
- package/{esm → dist}/react/components/request-password-reset-form.js +1 -3
- package/{esm → dist}/react/components/reset-password-form.d.ts +2 -2
- package/{esm → dist}/react/components/reset-password-form.js +2 -2
- package/{esm → dist}/react/components/sign-in-form.d.ts +2 -2
- package/{esm → dist}/react/components/sign-in-form.js +1 -5
- package/{esm → dist}/react/components/sign-up-form.d.ts +2 -2
- package/{esm → dist}/react/components/sign-up-form.js +4 -10
- package/{esm → dist}/react/components/types.d.ts +2 -2
- package/{esm → dist}/react/components/use-auth-form.d.ts +3 -3
- package/{esm → dist}/react/components/use-auth-form.js +4 -7
- package/{esm → dist}/react/components/user-menu.d.ts +2 -2
- package/{esm → dist}/react/components/user-menu.js +9 -14
- package/{esm → dist}/react/context.d.ts +1 -1
- package/{esm → dist}/react/mod.d.ts +6 -7
- package/{esm → dist}/react/mod.js +0 -1
- package/{esm → dist}/react/provider.d.ts +2 -2
- package/{esm → dist}/react/require-auth.js +1 -3
- package/{esm → dist}/react/testing.d.ts +2 -3
- package/{esm → dist}/react/testing.js +1 -2
- package/{esm → dist}/react/use-authorization.d.ts +1 -1
- package/{esm → dist}/react/use-oauth2.d.ts +8 -1
- package/{esm → dist}/react/use-oauth2.js +0 -1
- package/{esm → dist}/server/_clock-skew.js +2 -1
- package/{esm → dist}/server/authorization-server.d.ts +40 -41
- package/{esm → dist}/server/authorization-server.js +32 -42
- package/{esm → dist}/server/client-authentication.d.ts +2 -2
- package/{esm → dist}/server/grants/authorization-code.d.ts +9 -9
- package/{esm → dist}/server/grants/authorization-code.js +5 -6
- package/{esm → dist}/server/grants/client-credentials.d.ts +4 -4
- package/{esm → dist}/server/grants/device-authorization.d.ts +6 -6
- package/{esm → dist}/server/grants/device-authorization.js +4 -4
- package/{esm → dist}/server/grants/grant.d.ts +7 -7
- package/{esm → dist}/server/grants/grant.js +7 -7
- package/{esm → dist}/server/grants/password.d.ts +5 -5
- package/{esm → dist}/server/grants/refresh-token.d.ts +4 -4
- package/{esm → dist}/server/grants/refresh-token.js +5 -6
- package/{esm → dist}/server/introspection-token-reader.d.ts +5 -5
- package/{esm → dist}/server/introspection-token-reader.js +4 -4
- package/{esm → dist}/server/jwks-token-reader.d.ts +5 -5
- package/{esm → dist}/server/jwks-token-reader.js +4 -4
- package/{esm → dist}/server/mod.d.ts +12 -13
- package/{esm → dist}/server/mod.js +0 -1
- package/{esm → dist}/server/public-suffix/mod.d.ts +2 -3
- package/{esm → dist}/server/public-suffix/mod.js +1 -2
- package/{esm → dist}/server/public-suffix/rules.d.ts +1 -1
- package/{esm → dist}/server/public-suffix/rules.js +1 -1
- package/{esm → dist}/server/redirect-uri.js +5 -4
- package/{esm → dist}/server/resource-server.d.ts +11 -12
- package/{esm → dist}/server/resource-server.js +12 -14
- package/{esm → dist}/server/services/authorization-code.d.ts +5 -5
- package/{esm → dist}/server/services/client.d.ts +1 -1
- package/{esm → dist}/server/services/device-authorization.d.ts +3 -3
- package/{esm → dist}/server/services/token.d.ts +6 -6
- package/{esm → dist}/server/services/token.js +3 -1
- package/{esm → dist}/server/services/user.d.ts +1 -1
- package/{esm → dist}/server/signing-keys.d.ts +1 -1
- package/{esm → dist}/server/signing-keys.js +8 -2
- package/{esm → dist}/server/utils/hash.js +1 -1
- package/{esm → dist}/testing/contract/auth-request-storage.d.ts +1 -1
- package/{esm → dist}/testing/contract/auth-request-storage.js +24 -29
- package/{esm → dist}/testing/contract/authorization-code.d.ts +6 -6
- package/{esm → dist}/testing/contract/authorization-code.js +16 -17
- package/{esm → dist}/testing/contract/client.d.ts +4 -4
- package/{esm → dist}/testing/contract/client.js +14 -15
- package/{esm → dist}/testing/contract/device-authorization.d.ts +6 -6
- package/{esm → dist}/testing/contract/device-authorization.js +37 -39
- package/{esm → dist}/testing/contract/identity-session.d.ts +6 -1
- package/{esm → dist}/testing/contract/identity-session.js +44 -36
- package/{esm → dist}/testing/contract/identity-user-store.d.ts +8 -2
- package/{esm → dist}/testing/contract/identity-user-store.js +48 -46
- package/{esm → dist}/testing/contract/lockout-store.d.ts +1 -1
- package/{esm → dist}/testing/contract/lockout-store.js +23 -24
- package/{esm → dist}/testing/contract/mfa-store.d.ts +1 -1
- package/{esm → dist}/testing/contract/mfa-store.js +53 -54
- package/{esm → dist}/testing/contract/mod.d.ts +15 -16
- package/{esm → dist}/testing/contract/mod.js +0 -1
- package/{esm → dist}/testing/contract/otp-store.d.ts +1 -1
- package/{esm → dist}/testing/contract/otp-store.js +33 -34
- package/{esm → dist}/testing/contract/rate-limit-store.d.ts +1 -1
- package/{esm → dist}/testing/contract/rate-limit-store.js +15 -16
- package/{esm → dist}/testing/contract/tenant.js +361 -330
- package/{esm → dist}/testing/contract/token-flow-store.d.ts +1 -1
- package/{esm → dist}/testing/contract/token-flow-store.js +27 -32
- package/{esm → dist}/testing/contract/token-reader.d.ts +3 -3
- package/{esm → dist}/testing/contract/token-reader.js +6 -7
- package/{esm → dist}/testing/contract/token.d.ts +7 -7
- package/{esm → dist}/testing/contract/token.js +48 -53
- package/{esm → dist}/testing/contract/user.d.ts +2 -2
- package/{esm → dist}/testing/contract/user.js +8 -9
- package/{esm → dist}/testing/mod.d.ts +5 -6
- package/{esm → dist}/testing/mod.js +2 -3
- package/{esm → dist}/testing/server.d.ts +7 -6
- package/{esm → dist}/testing/server.js +2 -1
- package/{esm → dist}/testing/services.d.ts +11 -11
- package/{esm → dist}/testing/services.js +8 -8
- package/{esm → dist}/testing/tenant.d.ts +11 -7
- package/{esm → dist}/testing/tenant.js +109 -94
- package/dist/utils/_constant-time.d.ts +12 -0
- package/dist/utils/_constant-time.js +19 -0
- package/{esm → dist}/utils/_default-fetch.js +4 -1
- package/dist/utils/_delay.d.ts +13 -0
- package/dist/utils/_delay.js +28 -0
- package/dist/utils/_encoding.d.ts +39 -0
- package/dist/utils/_encoding.js +126 -0
- package/dist/utils/_inbound-request.d.ts +15 -0
- package/dist/utils/_inbound-request.js +25 -0
- package/{esm → dist}/utils/authentication-context.d.ts +1 -1
- package/{esm → dist}/utils/crypto.d.ts +2 -3
- package/{esm → dist}/utils/crypto.js +3 -4
- package/{esm → dist}/utils/pkce.js +1 -1
- package/{esm → dist}/utils/text.js +1 -1
- package/{esm → dist}/utils/url.d.ts +0 -1
- package/{esm → dist}/utils/url.js +0 -1
- package/package.json +156 -33
- package/esm/_dnt.polyfills.d.ts +0 -31
- package/esm/_dnt.polyfills.js +0 -26
- package/esm/deps/jsr.io/@std/assert/1.0.19/almost_equals.d.ts +0 -29
- package/esm/deps/jsr.io/@std/assert/1.0.19/almost_equals.js +0 -47
- package/esm/deps/jsr.io/@std/assert/1.0.19/array_includes.d.ts +0 -28
- package/esm/deps/jsr.io/@std/assert/1.0.19/array_includes.js +0 -65
- package/esm/deps/jsr.io/@std/assert/1.0.19/assert.d.ts +0 -16
- package/esm/deps/jsr.io/@std/assert/1.0.19/assert.js +0 -23
- package/esm/deps/jsr.io/@std/assert/1.0.19/assertion_error.d.ts +0 -25
- package/esm/deps/jsr.io/@std/assert/1.0.19/assertion_error.js +0 -30
- package/esm/deps/jsr.io/@std/assert/1.0.19/equal.d.ts +0 -24
- package/esm/deps/jsr.io/@std/assert/1.0.19/equal.js +0 -193
- package/esm/deps/jsr.io/@std/assert/1.0.19/equals.d.ts +0 -36
- package/esm/deps/jsr.io/@std/assert/1.0.19/equals.js +0 -61
- package/esm/deps/jsr.io/@std/assert/1.0.19/exists.d.ts +0 -17
- package/esm/deps/jsr.io/@std/assert/1.0.19/exists.js +0 -27
- package/esm/deps/jsr.io/@std/assert/1.0.19/fail.d.ts +0 -14
- package/esm/deps/jsr.io/@std/assert/1.0.19/fail.js +0 -20
- package/esm/deps/jsr.io/@std/assert/1.0.19/false.d.ts +0 -18
- package/esm/deps/jsr.io/@std/assert/1.0.19/false.js +0 -23
- package/esm/deps/jsr.io/@std/assert/1.0.19/greater.d.ts +0 -19
- package/esm/deps/jsr.io/@std/assert/1.0.19/greater.js +0 -29
- package/esm/deps/jsr.io/@std/assert/1.0.19/greater_or_equal.d.ts +0 -19
- package/esm/deps/jsr.io/@std/assert/1.0.19/greater_or_equal.js +0 -29
- package/esm/deps/jsr.io/@std/assert/1.0.19/instance_of.d.ts +0 -22
- package/esm/deps/jsr.io/@std/assert/1.0.19/instance_of.js +0 -52
- package/esm/deps/jsr.io/@std/assert/1.0.19/is_error.d.ts +0 -24
- package/esm/deps/jsr.io/@std/assert/1.0.19/is_error.js +0 -53
- package/esm/deps/jsr.io/@std/assert/1.0.19/less.d.ts +0 -18
- package/esm/deps/jsr.io/@std/assert/1.0.19/less.js +0 -28
- package/esm/deps/jsr.io/@std/assert/1.0.19/less_or_equal.d.ts +0 -19
- package/esm/deps/jsr.io/@std/assert/1.0.19/less_or_equal.js +0 -29
- package/esm/deps/jsr.io/@std/assert/1.0.19/match.d.ts +0 -17
- package/esm/deps/jsr.io/@std/assert/1.0.19/match.js +0 -26
- package/esm/deps/jsr.io/@std/assert/1.0.19/mod.d.ts +0 -43
- package/esm/deps/jsr.io/@std/assert/1.0.19/mod.js +0 -45
- package/esm/deps/jsr.io/@std/assert/1.0.19/not_equals.d.ts +0 -26
- package/esm/deps/jsr.io/@std/assert/1.0.19/not_equals.js +0 -39
- package/esm/deps/jsr.io/@std/assert/1.0.19/not_instance_of.d.ts +0 -19
- package/esm/deps/jsr.io/@std/assert/1.0.19/not_instance_of.js +0 -29
- package/esm/deps/jsr.io/@std/assert/1.0.19/not_match.d.ts +0 -17
- package/esm/deps/jsr.io/@std/assert/1.0.19/not_match.js +0 -26
- package/esm/deps/jsr.io/@std/assert/1.0.19/not_strict_equals.d.ts +0 -22
- package/esm/deps/jsr.io/@std/assert/1.0.19/not_strict_equals.js +0 -32
- package/esm/deps/jsr.io/@std/assert/1.0.19/object_match.d.ts +0 -28
- package/esm/deps/jsr.io/@std/assert/1.0.19/object_match.js +0 -177
- package/esm/deps/jsr.io/@std/assert/1.0.19/rejects.d.ts +0 -41
- package/esm/deps/jsr.io/@std/assert/1.0.19/rejects.js +0 -53
- package/esm/deps/jsr.io/@std/assert/1.0.19/strict_equals.d.ts +0 -23
- package/esm/deps/jsr.io/@std/assert/1.0.19/strict_equals.js +0 -58
- package/esm/deps/jsr.io/@std/assert/1.0.19/string_includes.d.ts +0 -17
- package/esm/deps/jsr.io/@std/assert/1.0.19/string_includes.js +0 -26
- package/esm/deps/jsr.io/@std/assert/1.0.19/throws.d.ts +0 -44
- package/esm/deps/jsr.io/@std/assert/1.0.19/throws.js +0 -44
- package/esm/deps/jsr.io/@std/assert/1.0.19/unimplemented.d.ts +0 -14
- package/esm/deps/jsr.io/@std/assert/1.0.19/unimplemented.js +0 -20
- package/esm/deps/jsr.io/@std/assert/1.0.19/unreachable.d.ts +0 -14
- package/esm/deps/jsr.io/@std/assert/1.0.19/unreachable.js +0 -20
- package/esm/deps/jsr.io/@std/async/1.5.0/delay.d.ts +0 -44
- package/esm/deps/jsr.io/@std/async/1.5.0/delay.js +0 -84
- package/esm/deps/jsr.io/@std/crypto/1.1.0/timing_safe_equal.d.ts +0 -39
- package/esm/deps/jsr.io/@std/crypto/1.1.0/timing_safe_equal.js +0 -61
- package/esm/deps/jsr.io/@std/encoding/1.0.11/_common16.d.ts +0 -22
- package/esm/deps/jsr.io/@std/encoding/1.0.11/_common16.js +0 -51
- package/esm/deps/jsr.io/@std/encoding/1.0.11/_common32.d.ts +0 -34
- package/esm/deps/jsr.io/@std/encoding/1.0.11/_common32.js +0 -192
- package/esm/deps/jsr.io/@std/encoding/1.0.11/_common64.d.ts +0 -34
- package/esm/deps/jsr.io/@std/encoding/1.0.11/_common64.js +0 -113
- package/esm/deps/jsr.io/@std/encoding/1.0.11/_common_detach.d.ts +0 -3
- package/esm/deps/jsr.io/@std/encoding/1.0.11/_common_detach.js +0 -13
- package/esm/deps/jsr.io/@std/encoding/1.0.11/_types.d.ts +0 -8
- package/esm/deps/jsr.io/@std/encoding/1.0.11/_types.js +0 -2
- package/esm/deps/jsr.io/@std/encoding/1.0.11/_validate_binary_like.d.ts +0 -1
- package/esm/deps/jsr.io/@std/encoding/1.0.11/_validate_binary_like.js +0 -26
- package/esm/deps/jsr.io/@std/encoding/1.0.11/ascii85.d.ts +0 -60
- package/esm/deps/jsr.io/@std/encoding/1.0.11/ascii85.js +0 -152
- package/esm/deps/jsr.io/@std/encoding/1.0.11/base32.d.ts +0 -39
- package/esm/deps/jsr.io/@std/encoding/1.0.11/base32.js +0 -87
- package/esm/deps/jsr.io/@std/encoding/1.0.11/base58.d.ts +0 -39
- package/esm/deps/jsr.io/@std/encoding/1.0.11/base58.js +0 -131
- package/esm/deps/jsr.io/@std/encoding/1.0.11/base64.d.ts +0 -39
- package/esm/deps/jsr.io/@std/encoding/1.0.11/base64.js +0 -82
- package/esm/deps/jsr.io/@std/encoding/1.0.11/base64url.d.ts +0 -39
- package/esm/deps/jsr.io/@std/encoding/1.0.11/base64url.js +0 -72
- package/esm/deps/jsr.io/@std/encoding/1.0.11/hex.d.ts +0 -38
- package/esm/deps/jsr.io/@std/encoding/1.0.11/hex.js +0 -87
- package/esm/deps/jsr.io/@std/encoding/1.0.11/mod.d.ts +0 -97
- package/esm/deps/jsr.io/@std/encoding/1.0.11/mod.js +0 -99
- package/esm/deps/jsr.io/@std/encoding/1.0.11/varint.d.ts +0 -119
- package/esm/deps/jsr.io/@std/encoding/1.0.11/varint.js +0 -209
- package/esm/deps/jsr.io/@std/http/1.1.3/status.d.ts +0 -343
- package/esm/deps/jsr.io/@std/http/1.1.3/status.js +0 -343
- package/esm/deps/jsr.io/@std/internal/1.0.14/assertion_state.d.ts +0 -159
- package/esm/deps/jsr.io/@std/internal/1.0.14/assertion_state.js +0 -223
- package/esm/deps/jsr.io/@std/internal/1.0.14/build_message.d.ts +0 -79
- package/esm/deps/jsr.io/@std/internal/1.0.14/build_message.js +0 -116
- package/esm/deps/jsr.io/@std/internal/1.0.14/diff.d.ts +0 -139
- package/esm/deps/jsr.io/@std/internal/1.0.14/diff.js +0 -277
- package/esm/deps/jsr.io/@std/internal/1.0.14/diff_str.d.ts +0 -98
- package/esm/deps/jsr.io/@std/internal/1.0.14/diff_str.js +0 -181
- package/esm/deps/jsr.io/@std/internal/1.0.14/format.d.ts +0 -29
- package/esm/deps/jsr.io/@std/internal/1.0.14/format.js +0 -68
- package/esm/deps/jsr.io/@std/internal/1.0.14/styles.d.ts +0 -158
- package/esm/deps/jsr.io/@std/internal/1.0.14/styles.js +0 -206
- package/esm/deps/jsr.io/@std/internal/1.0.14/types.d.ts +0 -24
- package/esm/deps/jsr.io/@std/internal/1.0.14/types.js +0 -3
- package/esm/deps/jsr.io/@std/testing/1.0.20/_test_suite.d.ts +0 -91
- package/esm/deps/jsr.io/@std/testing/1.0.20/_test_suite.js +0 -347
- package/esm/deps/jsr.io/@std/testing/1.0.20/bdd.d.ts +0 -382
- package/esm/deps/jsr.io/@std/testing/1.0.20/bdd.js +0 -1110
- package/esm/deps/jsr.io/@udibo/http-error/0.11.1/mod.d.ts +0 -363
- package/esm/deps/jsr.io/@udibo/http-error/0.11.1/mod.js +0 -616
- package/esm/package.json +0 -3
- /package/{esm → dist}/adapters/hono/bff/cookie-prefix.d.ts +0 -0
- /package/{esm → dist}/adapters/hono/bff/proxy.d.ts +0 -0
- /package/{esm → dist}/client/_http.js +0 -0
- /package/{esm → dist}/client/base.js +0 -0
- /package/{esm → dist}/client/browser-storage.js +0 -0
- /package/{esm → dist}/client/check.d.ts +0 -0
- /package/{esm → dist}/client/events.js +0 -0
- /package/{esm → dist}/client/storage.d.ts +0 -0
- /package/{esm → dist}/client/storage.js +0 -0
- /package/{esm → dist}/identity/captcha.d.ts +0 -0
- /package/{esm → dist}/identity/captcha.js +0 -0
- /package/{esm → dist}/identity/delivery.d.ts +0 -0
- /package/{esm → dist}/identity/delivery.js +0 -0
- /package/{esm → dist}/identity/errors.d.ts +0 -0
- /package/{esm → dist}/identity/errors.js +0 -0
- /package/{esm → dist}/identity/events.js +0 -0
- /package/{esm → dist}/identity/external/_token-exchange.d.ts +0 -0
- /package/{esm → dist}/identity/external/_token-exchange.js +0 -0
- /package/{esm → dist}/identity/external/errors.d.ts +0 -0
- /package/{esm → dist}/identity/external/errors.js +0 -0
- /package/{esm → dist}/identity/external/provider.d.ts +0 -0
- /package/{esm → dist}/identity/external/provider.js +0 -0
- /package/{esm → dist}/identity/identifier.d.ts +0 -0
- /package/{esm → dist}/identity/identifier.js +0 -0
- /package/{esm → dist}/identity/lockout.d.ts +0 -0
- /package/{esm → dist}/identity/mfa/recovery-codes.d.ts +0 -0
- /package/{esm → dist}/identity/mfa/recovery-codes.js +0 -0
- /package/{esm → dist}/identity/password-policy.d.ts +0 -0
- /package/{esm → dist}/identity/password-policy.js +0 -0
- /package/{esm → dist}/identity/password.d.ts +0 -0
- /package/{esm → dist}/identity/rate-limit.js +0 -0
- /package/{esm → dist}/identity/session.d.ts +0 -0
- /package/{esm → dist}/identity/token-flow.d.ts +0 -0
- /package/{esm → dist}/models/authentication.d.ts +0 -0
- /package/{esm → dist}/models/authentication.js +0 -0
- /package/{esm → dist}/models/authorization-code.js +0 -0
- /package/{esm → dist}/models/client.d.ts +0 -0
- /package/{esm → dist}/models/client.js +0 -0
- /package/{esm → dist}/models/device-authorization.js +0 -0
- /package/{esm → dist}/models/responses.d.ts +0 -0
- /package/{esm → dist}/models/responses.js +0 -0
- /package/{esm → dist}/models/scope.d.ts +0 -0
- /package/{esm → dist}/models/scope.js +0 -0
- /package/{esm → dist}/models/token.js +0 -0
- /package/{esm → dist}/react/components/types.js +0 -0
- /package/{esm → dist}/react/context.js +0 -0
- /package/{esm → dist}/react/provider.js +0 -0
- /package/{esm → dist}/react/require-auth.d.ts +0 -0
- /package/{esm → dist}/react/use-authorization.js +0 -0
- /package/{esm → dist}/server/_clock-skew.d.ts +0 -0
- /package/{esm → dist}/server/client-authentication.js +0 -0
- /package/{esm → dist}/server/grants/client-credentials.js +0 -0
- /package/{esm → dist}/server/grants/password.js +0 -0
- /package/{esm → dist}/server/redirect-uri.d.ts +0 -0
- /package/{esm → dist}/server/services/authorization-code.js +0 -0
- /package/{esm → dist}/server/services/client.js +0 -0
- /package/{esm → dist}/server/services/device-authorization.js +0 -0
- /package/{esm → dist}/server/services/user.js +0 -0
- /package/{esm → dist}/server/utils/hash.d.ts +0 -0
- /package/{esm → dist}/testing/contract/_race.d.ts +0 -0
- /package/{esm → dist}/testing/contract/_race.js +0 -0
- /package/{esm → dist}/testing/contract/tenant.d.ts +0 -0
- /package/{esm → dist}/utils/_body.d.ts +0 -0
- /package/{esm → dist}/utils/_body.js +0 -0
- /package/{esm → dist}/utils/_buffer.d.ts +0 -0
- /package/{esm → dist}/utils/_buffer.js +0 -0
- /package/{esm → dist}/utils/_default-fetch.d.ts +0 -0
- /package/{esm → dist}/utils/_timing-safe.d.ts +0 -0
- /package/{esm → dist}/utils/_timing-safe.js +0 -0
- /package/{esm → dist}/utils/authentication-context.js +0 -0
- /package/{esm → dist}/utils/basic-auth.d.ts +0 -0
- /package/{esm → dist}/utils/basic-auth.js +0 -0
- /package/{esm → dist}/utils/pkce.d.ts +0 -0
- /package/{esm → dist}/utils/text.d.ts +0 -0
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The development identity provider itself: a real {@link AuthorizationServer}
|
|
3
|
+
* over the in-memory services, served on a socket with a sign-in page, an
|
|
4
|
+
* optional consent screen, and a test-control surface under `/__admin/`.
|
|
5
|
+
*
|
|
6
|
+
* Everything here is packaging — the protocol behavior is the same code an
|
|
7
|
+
* application runs in production. What makes it a development tool is the
|
|
8
|
+
* storage (memory, dropped on exit) and the admin endpoints, which mint tokens
|
|
9
|
+
* for any seeded user without their password.
|
|
10
|
+
*
|
|
11
|
+
* Three guardrails are enforced rather than documented: the admin endpoints
|
|
12
|
+
* require a startup token in a custom header (so a browser must preflight, and
|
|
13
|
+
* a drive-by page cannot), a non-loopback bind needs an explicit opt-in, and
|
|
14
|
+
* the issuer is pinned at startup instead of read from the request's `Host`.
|
|
15
|
+
*
|
|
16
|
+
* @module
|
|
17
|
+
*/
|
|
18
|
+
import { serve as serveNode } from "@hono/node-server";
|
|
19
|
+
import { AuthorizationServer, } from "../../server/authorization-server.js";
|
|
20
|
+
import { AuthorizationCodeGrant } from "../../server/grants/authorization-code.js";
|
|
21
|
+
import { ClientCredentialsGrant } from "../../server/grants/client-credentials.js";
|
|
22
|
+
import { PasswordGrant } from "../../server/grants/password.js";
|
|
23
|
+
import { RefreshTokenGrant } from "../../server/grants/refresh-token.js";
|
|
24
|
+
import { StaticSigningKeyProvider, } from "../../server/signing-keys.js";
|
|
25
|
+
import { generateCodeChallenge, generateCodeVerifier, generateState, } from "../../utils/pkce.js";
|
|
26
|
+
import { timingSafeEqualString } from "../../utils/crypto.js";
|
|
27
|
+
import { safeReturnTo } from "../../utils/url.js";
|
|
28
|
+
import { MemoryAuthorizationCodeService, MemoryClientService, MemoryTokenService, MemoryUserService, } from "../../testing/services.js";
|
|
29
|
+
import { consentPage, htmlResponse, loginPage, messagePage, statusPage, } from "./pages.js";
|
|
30
|
+
const SESSION_COOKIE = "dev_idp_session";
|
|
31
|
+
const CONSENT_COOKIE = "dev_idp_consent";
|
|
32
|
+
const CONSENT_LIFETIME = 120;
|
|
33
|
+
/** Header the admin endpoints require, carrying the token printed at startup. */
|
|
34
|
+
export const ADMIN_TOKEN_HEADER = "x-admin-token";
|
|
35
|
+
const LOOPBACK_HOSTNAMES = new Set(["localhost", "127.0.0.1", "::1", "[::1]"]);
|
|
36
|
+
const WILDCARD_HOSTNAMES = new Set(["0.0.0.0", "::", "[::]"]);
|
|
37
|
+
/**
|
|
38
|
+
* Whether a bind address only accepts connections from this machine. A
|
|
39
|
+
* non-loopback bind exposes the admin endpoints to the network, so it needs an
|
|
40
|
+
* explicit opt-in.
|
|
41
|
+
*/
|
|
42
|
+
export function isLoopbackHostname(hostname) {
|
|
43
|
+
return LOOPBACK_HOSTNAMES.has(hostname) || hostname.startsWith("127.");
|
|
44
|
+
}
|
|
45
|
+
async function createState(config, signingKey, issuer) {
|
|
46
|
+
const userService = new MemoryUserService();
|
|
47
|
+
const clientService = new MemoryClientService(userService);
|
|
48
|
+
const tokenService = new MemoryTokenService({
|
|
49
|
+
clientService,
|
|
50
|
+
userService,
|
|
51
|
+
accessTokenLifetime: config.accessTokenLifetime,
|
|
52
|
+
});
|
|
53
|
+
const authorizationCodeService = new MemoryAuthorizationCodeService({ clientService, userService });
|
|
54
|
+
const grants = {};
|
|
55
|
+
if (config.grants.authorization_code) {
|
|
56
|
+
grants.authorization_code = new AuthorizationCodeGrant({
|
|
57
|
+
resolve: () => ({
|
|
58
|
+
clientService,
|
|
59
|
+
tokenService,
|
|
60
|
+
authorizationCodeService,
|
|
61
|
+
}),
|
|
62
|
+
allowRefreshToken: config.grants.refresh_token,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
if (config.grants.client_credentials) {
|
|
66
|
+
grants.client_credentials = new ClientCredentialsGrant({
|
|
67
|
+
resolve: () => ({ clientService, tokenService }),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
if (config.grants.refresh_token) {
|
|
71
|
+
grants.refresh_token = new RefreshTokenGrant({
|
|
72
|
+
resolve: () => ({ clientService, tokenService }),
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
if (config.grants.password) {
|
|
76
|
+
grants.password = new PasswordGrant({
|
|
77
|
+
resolve: () => ({ clientService, tokenService, userService }),
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
const server = new AuthorizationServer({
|
|
81
|
+
resolve: () => ({
|
|
82
|
+
services: { clientService, tokenService },
|
|
83
|
+
issuer: issuer.value,
|
|
84
|
+
}),
|
|
85
|
+
grants,
|
|
86
|
+
scopesSupported: config.scopesSupported,
|
|
87
|
+
signingKeys: new StaticSigningKeyProvider(signingKey),
|
|
88
|
+
userClaims: (user) => ({
|
|
89
|
+
preferred_username: user.username,
|
|
90
|
+
...user.claims,
|
|
91
|
+
}),
|
|
92
|
+
});
|
|
93
|
+
const secrets = new Map();
|
|
94
|
+
for (const user of config.users) {
|
|
95
|
+
await userService.add({
|
|
96
|
+
id: user.id,
|
|
97
|
+
username: user.username,
|
|
98
|
+
claims: user.claims,
|
|
99
|
+
}, user.password);
|
|
100
|
+
}
|
|
101
|
+
for (const client of config.clients) {
|
|
102
|
+
await clientService.add({
|
|
103
|
+
id: client.id,
|
|
104
|
+
grants: client.grants,
|
|
105
|
+
redirectUris: client.redirectUris,
|
|
106
|
+
}, client.secret, client.ownerUserId);
|
|
107
|
+
if (client.secret)
|
|
108
|
+
secrets.set(client.id, client.secret);
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
server,
|
|
112
|
+
userService,
|
|
113
|
+
clientService,
|
|
114
|
+
sessions: new Map(),
|
|
115
|
+
consents: new Map(),
|
|
116
|
+
secrets,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function readCookie(request, name) {
|
|
120
|
+
const header = request.headers.get("cookie");
|
|
121
|
+
if (!header)
|
|
122
|
+
return undefined;
|
|
123
|
+
for (const entry of header.split(";")) {
|
|
124
|
+
const separator = entry.indexOf("=");
|
|
125
|
+
if (separator === -1)
|
|
126
|
+
continue;
|
|
127
|
+
if (entry.slice(0, separator).trim() === name) {
|
|
128
|
+
return decodeURIComponent(entry.slice(separator + 1).trim());
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return undefined;
|
|
132
|
+
}
|
|
133
|
+
function cookie(name, value, options = {}) {
|
|
134
|
+
const parts = [
|
|
135
|
+
`${name}=${encodeURIComponent(value)}`,
|
|
136
|
+
`Path=${options.path ?? "/"}`,
|
|
137
|
+
"HttpOnly",
|
|
138
|
+
"SameSite=Lax",
|
|
139
|
+
];
|
|
140
|
+
if (options.maxAge !== undefined)
|
|
141
|
+
parts.push(`Max-Age=${options.maxAge}`);
|
|
142
|
+
return parts.join("; ");
|
|
143
|
+
}
|
|
144
|
+
function redirect(location, headers = {}) {
|
|
145
|
+
return new Response(null, {
|
|
146
|
+
status: 303,
|
|
147
|
+
headers: { ...Object.fromEntries(new Headers(headers)), location },
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
function json(body, status = 200) {
|
|
151
|
+
return Response.json(body, {
|
|
152
|
+
status,
|
|
153
|
+
headers: { "cache-control": "no-store" },
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
function jsonError(status, message) {
|
|
157
|
+
return json({ error: message }, status);
|
|
158
|
+
}
|
|
159
|
+
function rejectUnauthorizedAdmin(request, adminToken) {
|
|
160
|
+
if (request.headers.has("origin")) {
|
|
161
|
+
return jsonError(403, "the admin API refuses browser cross-origin requests; call it from your test runner");
|
|
162
|
+
}
|
|
163
|
+
const presented = request.headers.get(ADMIN_TOKEN_HEADER);
|
|
164
|
+
if (!presented || !timingSafeEqualString(presented, adminToken)) {
|
|
165
|
+
return jsonError(401, `the admin API requires the ${ADMIN_TOKEN_HEADER} header; its value is printed at startup`);
|
|
166
|
+
}
|
|
167
|
+
if (request.method === "POST") {
|
|
168
|
+
const contentType = request.headers.get("content-type") ?? "";
|
|
169
|
+
if (!contentType.toLowerCase().startsWith("application/json")) {
|
|
170
|
+
return jsonError(415, "the admin API requires content-type: application/json");
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
function pathAndQuery(request) {
|
|
176
|
+
const { pathname, search } = new URL(request.url);
|
|
177
|
+
return `${pathname}${search}`;
|
|
178
|
+
}
|
|
179
|
+
function formValue(form, name) {
|
|
180
|
+
const value = form.get(name);
|
|
181
|
+
return typeof value === "string" ? value : undefined;
|
|
182
|
+
}
|
|
183
|
+
async function currentUser(state, request) {
|
|
184
|
+
const sessionId = readCookie(request, SESSION_COOKIE);
|
|
185
|
+
if (!sessionId)
|
|
186
|
+
return undefined;
|
|
187
|
+
const userId = state.sessions.get(sessionId);
|
|
188
|
+
if (!userId)
|
|
189
|
+
return undefined;
|
|
190
|
+
return await state.userService.get(userId);
|
|
191
|
+
}
|
|
192
|
+
function authenticateUserFor(state, config, request) {
|
|
193
|
+
return async () => {
|
|
194
|
+
const user = await currentUser(state, request);
|
|
195
|
+
if (user)
|
|
196
|
+
return { user };
|
|
197
|
+
return htmlResponse(loginPage({ returnTo: pathAndQuery(request), users: config.users }));
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
function handleConsentFor(state, request) {
|
|
201
|
+
return async (client, requestedScope) => {
|
|
202
|
+
const user = await currentUser(state, request);
|
|
203
|
+
if (!user)
|
|
204
|
+
return { approved: false };
|
|
205
|
+
const decisionId = readCookie(request, CONSENT_COOKIE);
|
|
206
|
+
const decision = decisionId ? state.consents.get(decisionId) : undefined;
|
|
207
|
+
if (decision && decisionId) {
|
|
208
|
+
state.consents.delete(decisionId);
|
|
209
|
+
if (decision.userId === user.id && decision.clientId === client.id) {
|
|
210
|
+
return { approved: decision.approved };
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return htmlResponse(consentPage({
|
|
214
|
+
clientId: client.id,
|
|
215
|
+
scope: requestedScope?.toString() || undefined,
|
|
216
|
+
username: user.username,
|
|
217
|
+
returnTo: pathAndQuery(request),
|
|
218
|
+
}));
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
async function handleLoginSubmission(state, config, request) {
|
|
222
|
+
const form = await request.formData();
|
|
223
|
+
const returnTo = safeReturnTo(formValue(form, "return_to"));
|
|
224
|
+
const quickSignIn = formValue(form, "as");
|
|
225
|
+
let user;
|
|
226
|
+
let error;
|
|
227
|
+
if (quickSignIn !== undefined && quickSignIn.length > 0) {
|
|
228
|
+
user = await state.userService.findByUsername(quickSignIn);
|
|
229
|
+
if (!user)
|
|
230
|
+
error = `No seeded user named "${quickSignIn}".`;
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
const username = formValue(form, "username");
|
|
234
|
+
const password = formValue(form, "password");
|
|
235
|
+
user =
|
|
236
|
+
username !== undefined && password !== undefined
|
|
237
|
+
? await state.userService.getAuthenticated(username, password)
|
|
238
|
+
: undefined;
|
|
239
|
+
if (!user)
|
|
240
|
+
error = "Unknown username or password.";
|
|
241
|
+
}
|
|
242
|
+
if (!user) {
|
|
243
|
+
return htmlResponse(loginPage({ returnTo, users: config.users, error }), 401);
|
|
244
|
+
}
|
|
245
|
+
const sessionId = crypto.randomUUID();
|
|
246
|
+
state.sessions.set(sessionId, user.id);
|
|
247
|
+
return redirect(returnTo, {
|
|
248
|
+
"set-cookie": cookie(SESSION_COOKIE, sessionId),
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
async function handleConsentSubmission(state, request) {
|
|
252
|
+
const user = await currentUser(state, request);
|
|
253
|
+
if (!user)
|
|
254
|
+
return redirect("/login");
|
|
255
|
+
const form = await request.formData();
|
|
256
|
+
const returnTo = safeReturnTo(formValue(form, "return_to"));
|
|
257
|
+
const target = new URL(returnTo, request.url);
|
|
258
|
+
const clientId = target.searchParams.get("client_id");
|
|
259
|
+
if (target.pathname !== "/authorize" || !clientId) {
|
|
260
|
+
return htmlResponse(messagePage("Invalid consent request", "Start again from your app."), 400);
|
|
261
|
+
}
|
|
262
|
+
const decisionId = crypto.randomUUID();
|
|
263
|
+
state.consents.set(decisionId, {
|
|
264
|
+
userId: user.id,
|
|
265
|
+
clientId,
|
|
266
|
+
approved: form.get("decision") === "approve",
|
|
267
|
+
});
|
|
268
|
+
return redirect(returnTo, {
|
|
269
|
+
"set-cookie": cookie(CONSENT_COOKIE, decisionId, {
|
|
270
|
+
maxAge: CONSENT_LIFETIME,
|
|
271
|
+
}),
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
function handleLogout(state, config, request) {
|
|
275
|
+
const sessionId = readCookie(request, SESSION_COOKIE);
|
|
276
|
+
if (sessionId)
|
|
277
|
+
state.sessions.delete(sessionId);
|
|
278
|
+
const expired = cookie(SESSION_COOKIE, "", { maxAge: 0 });
|
|
279
|
+
const postLogout = new URL(request.url).searchParams.get("post_logout_redirect_uri");
|
|
280
|
+
if (postLogout) {
|
|
281
|
+
const registered = config.clients.some((client) => client.redirectUris.includes(postLogout));
|
|
282
|
+
if (registered) {
|
|
283
|
+
return redirect(postLogout, { "set-cookie": expired });
|
|
284
|
+
}
|
|
285
|
+
return htmlResponse(messagePage("Invalid logout request", "post_logout_redirect_uri must exactly match a redirect URI registered by one of the configured clients."), 400);
|
|
286
|
+
}
|
|
287
|
+
return new Response(messagePage("Signed out", "The session was dropped."), {
|
|
288
|
+
status: 200,
|
|
289
|
+
headers: {
|
|
290
|
+
"content-type": "text/html; charset=utf-8",
|
|
291
|
+
"cache-control": "no-store",
|
|
292
|
+
"set-cookie": expired,
|
|
293
|
+
},
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
async function readJsonBody(request) {
|
|
297
|
+
const text = await request.text();
|
|
298
|
+
if (text.trim().length === 0)
|
|
299
|
+
return {};
|
|
300
|
+
const parsed = JSON.parse(text);
|
|
301
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
302
|
+
throw new Error("body must be a JSON object");
|
|
303
|
+
}
|
|
304
|
+
return parsed;
|
|
305
|
+
}
|
|
306
|
+
async function resolveRequestedUser(state, body) {
|
|
307
|
+
if (typeof body.userId === "string") {
|
|
308
|
+
return await state.userService.get(body.userId);
|
|
309
|
+
}
|
|
310
|
+
if (typeof body.username === "string") {
|
|
311
|
+
return await state.userService.findByUsername(body.username);
|
|
312
|
+
}
|
|
313
|
+
return undefined;
|
|
314
|
+
}
|
|
315
|
+
async function handleAdminSession(state, request) {
|
|
316
|
+
const body = await readJsonBody(request);
|
|
317
|
+
const user = await resolveRequestedUser(state, body);
|
|
318
|
+
if (!user) {
|
|
319
|
+
return jsonError(400, 'no seeded user matched "userId" or "username"');
|
|
320
|
+
}
|
|
321
|
+
const sessionId = crypto.randomUUID();
|
|
322
|
+
state.sessions.set(sessionId, user.id);
|
|
323
|
+
return new Response(JSON.stringify({
|
|
324
|
+
sessionId,
|
|
325
|
+
cookie: SESSION_COOKIE,
|
|
326
|
+
user: { id: user.id, username: user.username },
|
|
327
|
+
}), {
|
|
328
|
+
status: 200,
|
|
329
|
+
headers: {
|
|
330
|
+
"content-type": "application/json",
|
|
331
|
+
"cache-control": "no-store",
|
|
332
|
+
"set-cookie": cookie(SESSION_COOKIE, sessionId),
|
|
333
|
+
},
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
async function handleAdminTokens(state, config, request) {
|
|
337
|
+
const body = await readJsonBody(request);
|
|
338
|
+
const user = await resolveRequestedUser(state, body);
|
|
339
|
+
if (!user) {
|
|
340
|
+
return jsonError(400, 'no seeded user matched "userId" or "username"');
|
|
341
|
+
}
|
|
342
|
+
if (typeof body.clientId !== "string") {
|
|
343
|
+
return jsonError(400, '"clientId" is required');
|
|
344
|
+
}
|
|
345
|
+
const client = await state.clientService.get(body.clientId);
|
|
346
|
+
if (!client)
|
|
347
|
+
return jsonError(400, `unknown client "${body.clientId}"`);
|
|
348
|
+
if (!client.grants?.includes("authorization_code")) {
|
|
349
|
+
return jsonError(400, `client "${client.id}" is not registered for the authorization_code grant`);
|
|
350
|
+
}
|
|
351
|
+
const redirectUri = typeof body.redirectUri === "string"
|
|
352
|
+
? body.redirectUri
|
|
353
|
+
: client.redirectUris?.[0];
|
|
354
|
+
if (!redirectUri) {
|
|
355
|
+
return jsonError(400, `client "${client.id}" has no redirect URI`);
|
|
356
|
+
}
|
|
357
|
+
const scope = typeof body.scope === "string"
|
|
358
|
+
? body.scope
|
|
359
|
+
: config.scopesSupported.join(" ");
|
|
360
|
+
const verifier = generateCodeVerifier();
|
|
361
|
+
const origin = new URL(request.url).origin;
|
|
362
|
+
const authorizeUrl = new URL("/authorize", origin);
|
|
363
|
+
authorizeUrl.search = new URLSearchParams({
|
|
364
|
+
response_type: "code",
|
|
365
|
+
client_id: client.id,
|
|
366
|
+
redirect_uri: redirectUri,
|
|
367
|
+
scope,
|
|
368
|
+
state: generateState(),
|
|
369
|
+
code_challenge: await generateCodeChallenge(verifier),
|
|
370
|
+
code_challenge_method: "S256",
|
|
371
|
+
}).toString();
|
|
372
|
+
const authorized = await state.server.handleAuthorizeRequest(new Request(authorizeUrl), () => Promise.resolve({ user }));
|
|
373
|
+
const location = authorized.headers.get("location");
|
|
374
|
+
if (!location) {
|
|
375
|
+
return jsonError(400, "the authorization request was rejected");
|
|
376
|
+
}
|
|
377
|
+
const authorizeParams = new URL(location).searchParams;
|
|
378
|
+
const code = authorizeParams.get("code");
|
|
379
|
+
if (!code) {
|
|
380
|
+
return jsonError(400, authorizeParams.get("error_description") ??
|
|
381
|
+
authorizeParams.get("error") ??
|
|
382
|
+
"the authorization request failed");
|
|
383
|
+
}
|
|
384
|
+
const form = new URLSearchParams({
|
|
385
|
+
grant_type: "authorization_code",
|
|
386
|
+
code,
|
|
387
|
+
redirect_uri: redirectUri,
|
|
388
|
+
client_id: client.id,
|
|
389
|
+
code_verifier: verifier,
|
|
390
|
+
});
|
|
391
|
+
const secret = state.secrets.get(client.id);
|
|
392
|
+
if (secret)
|
|
393
|
+
form.set("client_secret", secret);
|
|
394
|
+
return await state.server.handleTokenRequest(new Request(new URL("/token", origin), {
|
|
395
|
+
method: "POST",
|
|
396
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
397
|
+
body: form,
|
|
398
|
+
}));
|
|
399
|
+
}
|
|
400
|
+
function adminState(state, config, issuer) {
|
|
401
|
+
return json({
|
|
402
|
+
issuer,
|
|
403
|
+
users: config.users.map((user) => ({
|
|
404
|
+
id: user.id,
|
|
405
|
+
username: user.username,
|
|
406
|
+
})),
|
|
407
|
+
clients: config.clients.map((client) => ({
|
|
408
|
+
id: client.id,
|
|
409
|
+
confidential: client.secret !== undefined,
|
|
410
|
+
redirectUris: client.redirectUris,
|
|
411
|
+
grants: client.grants,
|
|
412
|
+
})),
|
|
413
|
+
sessions: state.sessions.size,
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Starts the development identity provider on a socket and resolves once it is
|
|
418
|
+
* accepting connections.
|
|
419
|
+
*
|
|
420
|
+
* The caller owns the lifetime: keep the returned {@link DevIdentityProvider}
|
|
421
|
+
* and call `shutdown()` (a test's `finally`, a CI teardown step). Nothing is
|
|
422
|
+
* written to disk and no state survives the process.
|
|
423
|
+
*
|
|
424
|
+
* **This server is for development and CI only.** Its `/__admin/` endpoints
|
|
425
|
+
* mint tokens for any seeded user without their password; they are gated by
|
|
426
|
+
* {@link DevIdentityProvider.adminToken}, not by anything stronger.
|
|
427
|
+
*
|
|
428
|
+
* @throws {Error} When the config binds a non-loopback address without
|
|
429
|
+
* `allowRemoteAccess`.
|
|
430
|
+
*
|
|
431
|
+
* @example
|
|
432
|
+
* ```ts
|
|
433
|
+
* const idp = await startDevIdentityProvider({
|
|
434
|
+
* config: defaultDevIdpConfig(),
|
|
435
|
+
* signingKey: await generateSigningKey(),
|
|
436
|
+
* });
|
|
437
|
+
* try {
|
|
438
|
+
* const discovery = await fetch(
|
|
439
|
+
* `${idp.url}/.well-known/openid-configuration`,
|
|
440
|
+
* );
|
|
441
|
+
* // ...point the app under test at the discovered endpoints.
|
|
442
|
+
* } finally {
|
|
443
|
+
* await idp.shutdown();
|
|
444
|
+
* }
|
|
445
|
+
* ```
|
|
446
|
+
*/
|
|
447
|
+
export async function startDevIdentityProvider(options) {
|
|
448
|
+
const { config, signingKey } = options;
|
|
449
|
+
if (!isLoopbackHostname(config.hostname) && !options.allowRemoteAccess) {
|
|
450
|
+
throw new Error(`refusing to bind ${config.hostname}: a non-loopback address exposes the ` +
|
|
451
|
+
`admin endpoints, which mint tokens for any seeded user. Pass ` +
|
|
452
|
+
`--unsafe-remote-access if the network really is one you control.`);
|
|
453
|
+
}
|
|
454
|
+
const adminToken = options.adminToken ?? crypto.randomUUID();
|
|
455
|
+
const issuer = { value: config.issuer ?? "" };
|
|
456
|
+
let state = await createState(config, signingKey, issuer);
|
|
457
|
+
const handler = async (request) => {
|
|
458
|
+
const { pathname } = new URL(request.url);
|
|
459
|
+
const { method } = request;
|
|
460
|
+
if (pathname.startsWith("/__admin/")) {
|
|
461
|
+
const rejected = rejectUnauthorizedAdmin(request, adminToken);
|
|
462
|
+
if (rejected)
|
|
463
|
+
return rejected;
|
|
464
|
+
}
|
|
465
|
+
try {
|
|
466
|
+
switch (pathname) {
|
|
467
|
+
case "/":
|
|
468
|
+
return htmlResponse(statusPage({
|
|
469
|
+
issuer: issuer.value,
|
|
470
|
+
users: config.users,
|
|
471
|
+
clients: config.clients,
|
|
472
|
+
}));
|
|
473
|
+
case "/authorize":
|
|
474
|
+
return await state.server.handleAuthorizeRequest(request, authenticateUserFor(state, config, request), config.consent === "prompt"
|
|
475
|
+
? handleConsentFor(state, request)
|
|
476
|
+
: undefined);
|
|
477
|
+
case "/token":
|
|
478
|
+
return await state.server.handleTokenRequest(request);
|
|
479
|
+
case "/revoke":
|
|
480
|
+
return await state.server.handleRevocationRequest(request);
|
|
481
|
+
case "/introspect":
|
|
482
|
+
return await state.server.handleIntrospectionRequest(request);
|
|
483
|
+
case "/userinfo":
|
|
484
|
+
return await state.server.handleUserInfoRequest(request);
|
|
485
|
+
case "/jwks":
|
|
486
|
+
return await state.server.handleJwksRequest(request);
|
|
487
|
+
case "/.well-known/openid-configuration":
|
|
488
|
+
return await state.server.handleOidcMetadataRequest(request);
|
|
489
|
+
case "/.well-known/oauth-authorization-server":
|
|
490
|
+
return await state.server.handleMetadataRequest(request);
|
|
491
|
+
case "/login":
|
|
492
|
+
if (method === "POST") {
|
|
493
|
+
return await handleLoginSubmission(state, config, request);
|
|
494
|
+
}
|
|
495
|
+
return htmlResponse(loginPage({
|
|
496
|
+
returnTo: safeReturnTo(new URL(request.url).searchParams.get("return_to")),
|
|
497
|
+
users: config.users,
|
|
498
|
+
}));
|
|
499
|
+
case "/consent":
|
|
500
|
+
if (method !== "POST")
|
|
501
|
+
return jsonError(405, "method must be POST");
|
|
502
|
+
return await handleConsentSubmission(state, request);
|
|
503
|
+
case "/logout":
|
|
504
|
+
return handleLogout(state, config, request);
|
|
505
|
+
case "/__admin/reset":
|
|
506
|
+
if (method !== "POST")
|
|
507
|
+
return jsonError(405, "method must be POST");
|
|
508
|
+
state = await createState(config, signingKey, issuer);
|
|
509
|
+
return json({ reset: true });
|
|
510
|
+
case "/__admin/session":
|
|
511
|
+
if (method !== "POST")
|
|
512
|
+
return jsonError(405, "method must be POST");
|
|
513
|
+
return await handleAdminSession(state, request);
|
|
514
|
+
case "/__admin/tokens":
|
|
515
|
+
if (method !== "POST")
|
|
516
|
+
return jsonError(405, "method must be POST");
|
|
517
|
+
return await handleAdminTokens(state, config, request);
|
|
518
|
+
case "/__admin/state":
|
|
519
|
+
return adminState(state, config, issuer.value);
|
|
520
|
+
default:
|
|
521
|
+
return jsonError(404, `no route for ${pathname}`);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
catch (error) {
|
|
525
|
+
return jsonError(400, error instanceof Error ? error.message : String(error));
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
const server = serveNode({
|
|
529
|
+
fetch: handler,
|
|
530
|
+
port: config.port,
|
|
531
|
+
hostname: config.hostname,
|
|
532
|
+
overrideGlobalObjects: false,
|
|
533
|
+
});
|
|
534
|
+
await new Promise((resolve, reject) => {
|
|
535
|
+
server.once("listening", resolve);
|
|
536
|
+
server.once("error", reject);
|
|
537
|
+
});
|
|
538
|
+
const { address: hostname, port } = server.address();
|
|
539
|
+
const host = hostname.includes(":") ? `[${hostname}]` : hostname;
|
|
540
|
+
const url = `http://${host}:${port}`;
|
|
541
|
+
issuer.value ||= `http://${WILDCARD_HOSTNAMES.has(hostname) ? "localhost" : host}:${port}`;
|
|
542
|
+
const finished = new Promise((resolve) => {
|
|
543
|
+
server.once("close", resolve);
|
|
544
|
+
});
|
|
545
|
+
const shutdown = () => {
|
|
546
|
+
server.close();
|
|
547
|
+
server.closeIdleConnections();
|
|
548
|
+
return finished;
|
|
549
|
+
};
|
|
550
|
+
return {
|
|
551
|
+
url,
|
|
552
|
+
issuer: issuer.value,
|
|
553
|
+
adminToken,
|
|
554
|
+
hostname,
|
|
555
|
+
port,
|
|
556
|
+
finished,
|
|
557
|
+
shutdown,
|
|
558
|
+
};
|
|
559
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `oidc keygen` command: the ES256 signing key an OIDC provider persists
|
|
3
|
+
* as `OIDC_SIGNING_KEY`.
|
|
4
|
+
*
|
|
5
|
+
* @module
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Generates a fresh ES256 signing key, prints its private JWK as one line on
|
|
9
|
+
* stdout, and prints operator guidance on stderr. Nothing is written to disk.
|
|
10
|
+
*
|
|
11
|
+
* Takes no arguments; throws when given any.
|
|
12
|
+
*/
|
|
13
|
+
export declare function oidcKeygen(args: string[]): Promise<void>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `oidc keygen` command: the ES256 signing key an OIDC provider persists
|
|
3
|
+
* as `OIDC_SIGNING_KEY`.
|
|
4
|
+
*
|
|
5
|
+
* @module
|
|
6
|
+
*/
|
|
7
|
+
import { exportSigningKeyJwk, generateSigningKey, } from "../server/signing-keys.js";
|
|
8
|
+
const GUIDANCE = [
|
|
9
|
+
"Store the JWK printed on stdout as the OIDC_SIGNING_KEY secret in your",
|
|
10
|
+
"production secret store. It contains private key material: never commit it,",
|
|
11
|
+
"never paste it into a log, and give every instance the same value so they",
|
|
12
|
+
"all sign consistently.",
|
|
13
|
+
"",
|
|
14
|
+
"Leave OIDC_SIGNING_KEY unset in dev and test — the server falls back to an",
|
|
15
|
+
"ephemeral per-process key there.",
|
|
16
|
+
].join("\n");
|
|
17
|
+
/**
|
|
18
|
+
* Generates a fresh ES256 signing key, prints its private JWK as one line on
|
|
19
|
+
* stdout, and prints operator guidance on stderr. Nothing is written to disk.
|
|
20
|
+
*
|
|
21
|
+
* Takes no arguments; throws when given any.
|
|
22
|
+
*/
|
|
23
|
+
export async function oidcKeygen(args) {
|
|
24
|
+
if (args.length > 0) {
|
|
25
|
+
throw new Error(`oidc keygen takes no arguments, got: ${args.join(" ")}`);
|
|
26
|
+
}
|
|
27
|
+
const key = await generateSigningKey();
|
|
28
|
+
console.log(JSON.stringify(await exportSigningKeyJwk(key)));
|
|
29
|
+
console.error(`Generated an ES256 OIDC signing key (kid ${key.kid}).\n\n${GUIDANCE}`);
|
|
30
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command table for the `udibo-oauth2` executable: the one-off operator
|
|
3
|
+
* commands a deployment needs, so generating key material never means pasting
|
|
4
|
+
* a scratch script into a terminal.
|
|
5
|
+
*
|
|
6
|
+
* ```sh
|
|
7
|
+
* npx udibo-oauth2 oidc keygen
|
|
8
|
+
* npx udibo-oauth2 idp dev
|
|
9
|
+
* ```
|
|
10
|
+
*
|
|
11
|
+
* No command writes to disk. `idp dev` serves a socket and reads its config
|
|
12
|
+
* file and `OIDC_SIGNING_KEY` from the environment.
|
|
13
|
+
*
|
|
14
|
+
* @module
|
|
15
|
+
*/
|
|
16
|
+
/** Options for {@link runCli}. */
|
|
17
|
+
export interface RunCliOptions {
|
|
18
|
+
/** Stops a long-running command, such as `idp dev`, so the run resolves. */
|
|
19
|
+
signal?: AbortSignal;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Runs one CLI invocation and resolves with the exit code the process should
|
|
23
|
+
* use: `0` when the command succeeded or help was requested, `1` for no
|
|
24
|
+
* command, an unknown command, a bad argument, or a command failure.
|
|
25
|
+
*
|
|
26
|
+
* Help text and development-provider banners use stdout. Missing or unknown
|
|
27
|
+
* commands and command failures report on stderr. Successful `oidc keygen`
|
|
28
|
+
* without help writes one JWK line on stdout and guidance on stderr. Use that
|
|
29
|
+
* exact invocation and check its exit code before storing stdout as a secret.
|
|
30
|
+
*
|
|
31
|
+
* The `udibo-oauth2` executable calls this with `process.argv.slice(2)` and
|
|
32
|
+
* sets `process.exitCode` to the result.
|
|
33
|
+
*/
|
|
34
|
+
export declare function runCli(args: string[], options?: RunCliOptions): Promise<number>;
|