@atproto/oauth-provider 0.6.6 → 0.7.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/CHANGELOG.md +49 -0
- package/dist/access-token/access-token-mode.d.ts +5 -0
- package/dist/access-token/access-token-mode.d.ts.map +1 -0
- package/dist/access-token/access-token-mode.js +9 -0
- package/dist/access-token/access-token-mode.js.map +1 -0
- package/dist/account/account-manager.d.ts +13 -7
- package/dist/account/account-manager.d.ts.map +1 -1
- package/dist/account/account-manager.js +69 -52
- package/dist/account/account-manager.js.map +1 -1
- package/dist/account/account-store.d.ts +88 -77
- package/dist/account/account-store.d.ts.map +1 -1
- package/dist/account/account-store.js +24 -73
- package/dist/account/account-store.js.map +1 -1
- package/dist/account/sign-in-data.d.ts +4 -13
- package/dist/account/sign-in-data.d.ts.map +1 -1
- package/dist/account/sign-in-data.js +9 -9
- package/dist/account/sign-in-data.js.map +1 -1
- package/dist/account/sign-up-input.d.ts +4 -5
- package/dist/account/sign-up-input.d.ts.map +1 -1
- package/dist/account/sign-up-input.js +13 -3
- package/dist/account/sign-up-input.js.map +1 -1
- package/dist/client/client-manager.d.ts +4 -1
- package/dist/client/client-manager.d.ts.map +1 -1
- package/dist/client/client-manager.js +13 -1
- package/dist/client/client-manager.js.map +1 -1
- package/dist/client/client-store.d.ts +1 -1
- package/dist/client/client-store.d.ts.map +1 -1
- package/dist/constants.d.ts +5 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +6 -2
- package/dist/constants.js.map +1 -1
- package/dist/customization/branding.d.ts +54 -0
- package/dist/customization/branding.d.ts.map +1 -0
- package/dist/customization/branding.js +13 -0
- package/dist/customization/branding.js.map +1 -0
- package/dist/customization/build-customization-css.d.ts +3 -0
- package/dist/customization/build-customization-css.d.ts.map +1 -0
- package/dist/customization/build-customization-css.js +27 -0
- package/dist/customization/build-customization-css.js.map +1 -0
- package/dist/customization/build-customization-data.d.ts +4 -0
- package/dist/customization/build-customization-data.d.ts.map +1 -0
- package/dist/customization/build-customization-data.js +18 -0
- package/dist/customization/build-customization-data.js.map +1 -0
- package/dist/customization/colors.d.ts +7 -0
- package/dist/customization/colors.d.ts.map +1 -0
- package/dist/customization/colors.js +27 -0
- package/dist/customization/colors.js.map +1 -0
- package/dist/customization/customization.d.ts +129 -0
- package/dist/customization/customization.d.ts.map +1 -0
- package/dist/customization/customization.js +26 -0
- package/dist/customization/customization.js.map +1 -0
- package/dist/customization/links.d.ts +26 -0
- package/dist/customization/links.d.ts.map +1 -0
- package/dist/customization/links.js +12 -0
- package/dist/customization/links.js.map +1 -0
- package/dist/device/device-id.d.ts +1 -0
- package/dist/device/device-id.d.ts.map +1 -1
- package/dist/device/device-id.js +4 -0
- package/dist/device/device-id.js.map +1 -1
- package/dist/device/device-manager.d.ts +6 -36
- package/dist/device/device-manager.d.ts.map +1 -1
- package/dist/device/device-manager.js +49 -43
- package/dist/device/device-manager.js.map +1 -1
- package/dist/device/device-store.d.ts +1 -0
- package/dist/device/device-store.d.ts.map +1 -1
- package/dist/device/device-store.js.map +1 -1
- package/dist/dpop/dpop-manager.d.ts +3 -3
- package/dist/dpop/dpop-nonce.d.ts +3 -3
- package/dist/dpop/dpop-nonce.d.ts.map +1 -1
- package/dist/errors/access-denied-error.d.ts +4 -3
- package/dist/errors/access-denied-error.d.ts.map +1 -1
- package/dist/errors/access-denied-error.js +5 -6
- package/dist/errors/access-denied-error.js.map +1 -1
- package/dist/{output/build-error-payload.d.ts → errors/error-parser.d.ts} +1 -1
- package/dist/errors/error-parser.d.ts.map +1 -0
- package/dist/{output/build-error-payload.js → errors/error-parser.js} +2 -2
- package/dist/errors/error-parser.js.map +1 -0
- package/dist/errors/invalid-grant-error.d.ts +1 -0
- package/dist/errors/invalid-grant-error.d.ts.map +1 -1
- package/dist/errors/invalid-grant-error.js +5 -0
- package/dist/errors/invalid-grant-error.js.map +1 -1
- package/dist/errors/login-required-error.d.ts +1 -0
- package/dist/errors/login-required-error.d.ts.map +1 -1
- package/dist/errors/login-required-error.js +5 -0
- package/dist/errors/login-required-error.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/html/build-document.d.ts +2 -2
- package/dist/lib/html/build-document.d.ts.map +1 -1
- package/dist/lib/html/build-document.js +4 -0
- package/dist/lib/html/build-document.js.map +1 -1
- package/dist/lib/html/hydration-data.d.ts +4 -0
- package/dist/lib/html/hydration-data.d.ts.map +1 -0
- package/dist/{output/backend-data.js → lib/html/hydration-data.js} +8 -8
- package/dist/lib/html/hydration-data.js.map +1 -0
- package/dist/lib/html/tags.d.ts +1 -1
- package/dist/lib/html/tags.d.ts.map +1 -1
- package/dist/lib/html/tags.js +1 -1
- package/dist/lib/html/tags.js.map +1 -1
- package/dist/lib/http/accept.d.ts +2 -2
- package/dist/lib/http/accept.d.ts.map +1 -1
- package/dist/lib/http/accept.js +1 -1
- package/dist/lib/http/accept.js.map +1 -1
- package/dist/lib/http/context.d.ts +2 -4
- package/dist/lib/http/context.d.ts.map +1 -1
- package/dist/lib/http/context.js +29 -4
- package/dist/lib/http/context.js.map +1 -1
- package/dist/lib/http/headers.d.ts +3 -0
- package/dist/lib/http/headers.d.ts.map +1 -0
- package/dist/lib/http/headers.js +14 -0
- package/dist/lib/http/headers.js.map +1 -0
- package/dist/lib/http/index.d.ts +1 -0
- package/dist/lib/http/index.d.ts.map +1 -1
- package/dist/lib/http/index.js +1 -0
- package/dist/lib/http/index.js.map +1 -1
- package/dist/lib/http/middleware.d.ts +1 -1
- package/dist/lib/http/middleware.d.ts.map +1 -1
- package/dist/lib/http/middleware.js +8 -24
- package/dist/lib/http/middleware.js.map +1 -1
- package/dist/lib/http/parser.d.ts +3 -3
- package/dist/lib/http/parser.d.ts.map +1 -1
- package/dist/lib/http/request.d.ts +13 -9
- package/dist/lib/http/request.d.ts.map +1 -1
- package/dist/lib/http/request.js +27 -49
- package/dist/lib/http/request.js.map +1 -1
- package/dist/lib/http/response.d.ts +6 -2
- package/dist/lib/http/response.d.ts.map +1 -1
- package/dist/lib/http/response.js +31 -11
- package/dist/lib/http/response.js.map +1 -1
- package/dist/lib/http/route.d.ts +3 -3
- package/dist/lib/http/route.d.ts.map +1 -1
- package/dist/lib/http/route.js +1 -1
- package/dist/lib/http/route.js.map +1 -1
- package/dist/lib/http/router.d.ts +12 -11
- package/dist/lib/http/router.d.ts.map +1 -1
- package/dist/lib/http/router.js +26 -34
- package/dist/lib/http/router.js.map +1 -1
- package/dist/lib/http/security-headers.js +1 -1
- package/dist/lib/http/security-headers.js.map +1 -1
- package/dist/lib/http/stream.d.ts +3 -3
- package/dist/lib/http/stream.d.ts.map +1 -1
- package/dist/lib/http/types.d.ts +1 -1
- package/dist/lib/http/types.d.ts.map +1 -1
- package/dist/lib/send-web-page.d.ts +8 -0
- package/dist/lib/send-web-page.d.ts.map +1 -0
- package/dist/{output → lib}/send-web-page.js +9 -7
- package/dist/lib/send-web-page.js.map +1 -0
- package/dist/lib/util/authorization-header.d.ts.map +1 -1
- package/dist/lib/util/color.d.ts +32 -0
- package/dist/lib/util/color.d.ts.map +1 -0
- package/dist/lib/util/color.js +116 -0
- package/dist/lib/util/color.js.map +1 -0
- package/dist/lib/util/crypto.d.ts +1 -0
- package/dist/lib/util/crypto.d.ts.map +1 -1
- package/dist/lib/util/crypto.js +8 -3
- package/dist/lib/util/crypto.js.map +1 -1
- package/dist/lib/util/function.d.ts +1 -0
- package/dist/lib/util/function.d.ts.map +1 -1
- package/dist/lib/util/function.js +12 -0
- package/dist/lib/util/function.js.map +1 -1
- package/dist/lib/util/locale.d.ts +20 -0
- package/dist/lib/util/locale.d.ts.map +1 -0
- package/dist/lib/util/locale.js +14 -0
- package/dist/lib/util/locale.js.map +1 -0
- package/dist/lib/util/time.d.ts +1 -1
- package/dist/lib/util/time.d.ts.map +1 -1
- package/dist/lib/util/time.js +1 -1
- package/dist/lib/util/time.js.map +1 -1
- package/dist/lib/util/type.d.ts +22 -0
- package/dist/lib/util/type.d.ts.map +1 -1
- package/dist/lib/util/type.js.map +1 -1
- package/dist/lib/util/ui8.d.ts +4 -0
- package/dist/lib/util/ui8.d.ts.map +1 -0
- package/dist/lib/util/ui8.js +17 -0
- package/dist/lib/util/ui8.js.map +1 -0
- package/dist/lib/util/zod-error.d.ts +2 -0
- package/dist/lib/util/zod-error.d.ts.map +1 -0
- package/dist/lib/util/zod-error.js +16 -0
- package/dist/lib/util/zod-error.js.map +1 -0
- package/dist/oauth-errors.d.ts +22 -22
- package/dist/oauth-errors.d.ts.map +1 -1
- package/dist/oauth-errors.js +37 -45
- package/dist/oauth-errors.js.map +1 -1
- package/dist/oauth-hooks.d.ts +11 -23
- package/dist/oauth-hooks.d.ts.map +1 -1
- package/dist/oauth-hooks.js.map +1 -1
- package/dist/oauth-middleware.d.ts +12 -0
- package/dist/oauth-middleware.d.ts.map +1 -0
- package/dist/oauth-middleware.js +32 -0
- package/dist/oauth-middleware.js.map +1 -0
- package/dist/oauth-provider.d.ts +109 -113
- package/dist/oauth-provider.d.ts.map +1 -1
- package/dist/oauth-provider.js +124 -542
- package/dist/oauth-provider.js.map +1 -1
- package/dist/oauth-verifier.d.ts +7 -26
- package/dist/oauth-verifier.d.ts.map +1 -1
- package/dist/oauth-verifier.js +6 -16
- package/dist/oauth-verifier.js.map +1 -1
- package/dist/request/code.d.ts.map +1 -1
- package/dist/request/request-data.d.ts +2 -4
- package/dist/request/request-data.d.ts.map +1 -1
- package/dist/request/request-data.js.map +1 -1
- package/dist/request/request-manager.d.ts +4 -2
- package/dist/request/request-manager.d.ts.map +1 -1
- package/dist/request/request-manager.js +9 -8
- package/dist/request/request-manager.js.map +1 -1
- package/dist/request/request-store.d.ts +6 -0
- package/dist/request/request-store.d.ts.map +1 -1
- package/dist/request/request-store.js +3 -1
- package/dist/request/request-store.js.map +1 -1
- package/dist/result/authorization-redirect-parameters.d.ts +18 -0
- package/dist/result/authorization-redirect-parameters.d.ts.map +1 -0
- package/dist/result/authorization-redirect-parameters.js +3 -0
- package/dist/result/authorization-redirect-parameters.js.map +1 -0
- package/dist/result/authorization-result-authorize-page.d.ts +13 -0
- package/dist/result/authorization-result-authorize-page.d.ts.map +1 -0
- package/dist/result/authorization-result-authorize-page.js +3 -0
- package/dist/result/authorization-result-authorize-page.js.map +1 -0
- package/dist/result/authorization-result-redirect.d.ts +8 -0
- package/dist/result/authorization-result-redirect.d.ts.map +1 -0
- package/dist/result/authorization-result-redirect.js +3 -0
- package/dist/result/authorization-result-redirect.js.map +1 -0
- package/dist/router/assets/assets-manifest.d.ts +10 -0
- package/dist/router/assets/assets-manifest.d.ts.map +1 -0
- package/dist/router/assets/assets-manifest.js +77 -0
- package/dist/router/assets/assets-manifest.js.map +1 -0
- package/dist/router/assets/assets.d.ts +16 -0
- package/dist/router/assets/assets.d.ts.map +1 -0
- package/dist/router/assets/assets.js +43 -0
- package/dist/router/assets/assets.js.map +1 -0
- package/dist/router/assets/csrf.d.ts +4 -0
- package/dist/router/assets/csrf.d.ts.map +1 -0
- package/dist/router/assets/csrf.js +51 -0
- package/dist/router/assets/csrf.js.map +1 -0
- package/dist/router/assets/send-account-page.d.ts +7 -0
- package/dist/router/assets/send-account-page.d.ts.map +1 -0
- package/dist/router/assets/send-account-page.js +34 -0
- package/dist/router/assets/send-account-page.js.map +1 -0
- package/dist/router/assets/send-authorization-page.d.ts +5 -0
- package/dist/router/assets/send-authorization-page.d.ts.map +1 -0
- package/dist/router/assets/send-authorization-page.js +49 -0
- package/dist/router/assets/send-authorization-page.js.map +1 -0
- package/dist/router/assets/send-error-page.d.ts +4 -0
- package/dist/router/assets/send-error-page.d.ts.map +1 -0
- package/dist/router/assets/send-error-page.js +34 -0
- package/dist/router/assets/send-error-page.js.map +1 -0
- package/dist/router/create-account-page-middleware.d.ts +6 -0
- package/dist/router/create-account-page-middleware.d.ts.map +1 -0
- package/dist/router/create-account-page-middleware.js +39 -0
- package/dist/router/create-account-page-middleware.js.map +1 -0
- package/dist/router/create-api-middleware.d.ts +8 -0
- package/dist/router/create-api-middleware.d.ts.map +1 -0
- package/dist/router/create-api-middleware.js +501 -0
- package/dist/router/create-api-middleware.js.map +1 -0
- package/dist/router/create-authorization-page-middleware.d.ts +6 -0
- package/dist/router/create-authorization-page-middleware.d.ts.map +1 -0
- package/dist/router/create-authorization-page-middleware.js +104 -0
- package/dist/router/create-authorization-page-middleware.js.map +1 -0
- package/dist/router/create-oauth-middleware.d.ts +6 -0
- package/dist/router/create-oauth-middleware.d.ts.map +1 -0
- package/dist/router/create-oauth-middleware.js +142 -0
- package/dist/router/create-oauth-middleware.js.map +1 -0
- package/dist/router/error-handler.d.ts +3 -0
- package/dist/router/error-handler.d.ts.map +1 -0
- package/dist/{account/account.js → router/error-handler.js} +1 -1
- package/dist/router/error-handler.js.map +1 -0
- package/dist/router/middleware-options.d.ts +6 -0
- package/dist/router/middleware-options.d.ts.map +1 -0
- package/dist/router/middleware-options.js +3 -0
- package/dist/router/middleware-options.js.map +1 -0
- package/dist/router/send-redirect.d.ts +16 -0
- package/dist/router/send-redirect.d.ts.map +1 -0
- package/dist/{output/send-authorize-redirect.js → router/send-redirect.js} +40 -24
- package/dist/router/send-redirect.js.map +1 -0
- package/dist/{token/token-claims.d.ts → signer/api-token-payload.d.ts} +237 -232
- package/dist/signer/api-token-payload.d.ts.map +1 -0
- package/dist/signer/api-token-payload.js +17 -0
- package/dist/signer/api-token-payload.js.map +1 -0
- package/dist/signer/signed-token-payload.d.ts +164 -159
- package/dist/signer/signed-token-payload.d.ts.map +1 -1
- package/dist/signer/signed-token-payload.js +10 -16
- package/dist/signer/signed-token-payload.js.map +1 -1
- package/dist/signer/signer.d.ts +42 -11246
- package/dist/signer/signer.d.ts.map +1 -1
- package/dist/signer/signer.js +30 -15
- package/dist/signer/signer.js.map +1 -1
- package/dist/token/refresh-token.d.ts.map +1 -1
- package/dist/token/token-data.d.ts +1 -1
- package/dist/token/token-data.d.ts.map +1 -1
- package/dist/token/token-id.d.ts.map +1 -1
- package/dist/token/token-manager.d.ts +28 -26
- package/dist/token/token-manager.d.ts.map +1 -1
- package/dist/token/token-manager.js +138 -196
- package/dist/token/token-manager.js.map +1 -1
- package/dist/token/token-store.d.ts +4 -4
- package/dist/token/token-store.d.ts.map +1 -1
- package/dist/token/token-store.js +1 -0
- package/dist/token/token-store.js.map +1 -1
- package/dist/token/verify-token-claims.d.ts +3 -3
- package/dist/token/verify-token-claims.d.ts.map +1 -1
- package/dist/token/verify-token-claims.js +1 -1
- package/dist/token/verify-token-claims.js.map +1 -1
- package/dist/types/email-otp.d.ts +3 -0
- package/dist/types/email-otp.d.ts.map +1 -0
- package/dist/types/email-otp.js +6 -0
- package/dist/types/email-otp.js.map +1 -0
- package/dist/types/email.d.ts +3 -0
- package/dist/types/email.d.ts.map +1 -0
- package/dist/types/email.js +29 -0
- package/dist/types/email.js.map +1 -0
- package/dist/types/handle.d.ts +3 -0
- package/dist/types/handle.d.ts.map +1 -0
- package/dist/types/handle.js +22 -0
- package/dist/types/handle.js.map +1 -0
- package/dist/types/invite-code.d.ts +4 -0
- package/dist/types/invite-code.d.ts.map +1 -0
- package/dist/types/invite-code.js +6 -0
- package/dist/types/invite-code.js.map +1 -0
- package/dist/types/password.d.ts +4 -0
- package/dist/types/password.d.ts.map +1 -0
- package/dist/types/password.js +7 -0
- package/dist/types/password.js.map +1 -0
- package/package.json +11 -14
- package/src/access-token/access-token-mode.ts +4 -0
- package/src/account/account-manager.ts +105 -75
- package/src/account/account-store.ts +118 -114
- package/src/account/sign-in-data.ts +10 -10
- package/src/account/sign-up-input.ts +13 -4
- package/src/client/client-manager.ts +34 -2
- package/src/client/client-store.ts +1 -1
- package/src/constants.ts +6 -1
- package/src/customization/branding.ts +12 -0
- package/src/customization/build-customization-css.ts +30 -0
- package/src/customization/build-customization-data.ts +22 -0
- package/src/customization/colors.ts +30 -0
- package/src/customization/customization.ts +25 -0
- package/src/customization/links.ts +10 -0
- package/src/device/device-id.ts +5 -0
- package/src/device/device-manager.ts +76 -66
- package/src/device/device-store.ts +2 -0
- package/src/errors/access-denied-error.ts +24 -17
- package/src/{output/build-error-payload.ts → errors/error-parser.ts} +1 -1
- package/src/errors/invalid-grant-error.ts +5 -0
- package/src/errors/login-required-error.ts +10 -0
- package/src/index.ts +1 -0
- package/src/lib/html/build-document.ts +6 -4
- package/src/{output/backend-data.ts → lib/html/hydration-data.ts} +7 -5
- package/src/lib/html/tags.ts +2 -2
- package/src/lib/http/accept.ts +3 -3
- package/src/lib/http/context.ts +41 -10
- package/src/lib/http/headers.ts +15 -0
- package/src/lib/http/index.ts +1 -0
- package/src/lib/http/middleware.ts +8 -23
- package/src/lib/http/request.ts +40 -75
- package/src/lib/http/response.ts +39 -15
- package/src/lib/http/route.ts +8 -5
- package/src/lib/http/router.ts +40 -46
- package/src/lib/http/security-headers.ts +1 -1
- package/src/lib/http/types.ts +1 -6
- package/src/{output → lib}/send-web-page.ts +10 -9
- package/src/lib/util/color.ts +132 -0
- package/src/lib/util/crypto.ts +9 -4
- package/src/lib/util/function.ts +14 -0
- package/src/lib/util/locale.ts +18 -0
- package/src/lib/util/time.ts +3 -4
- package/src/lib/util/type.ts +24 -0
- package/src/lib/util/ui8.ts +14 -0
- package/src/lib/util/zod-error.ts +14 -0
- package/src/oauth-errors.ts +22 -22
- package/src/oauth-hooks.ts +11 -24
- package/src/oauth-middleware.ts +53 -0
- package/src/oauth-provider.ts +290 -1061
- package/src/oauth-verifier.ts +9 -55
- package/src/request/request-data.ts +5 -4
- package/src/request/request-manager.ts +11 -11
- package/src/request/request-store.ts +7 -0
- package/src/result/authorization-redirect-parameters.ts +24 -0
- package/src/result/authorization-result-authorize-page.ts +14 -0
- package/src/result/authorization-result-redirect.ts +8 -0
- package/src/router/assets/assets-manifest.ts +115 -0
- package/src/router/assets/assets.ts +54 -0
- package/src/router/assets/csrf.ts +63 -0
- package/src/router/assets/send-account-page.ts +43 -0
- package/src/router/assets/send-authorization-page.ts +62 -0
- package/src/router/assets/send-error-page.ts +42 -0
- package/src/router/create-account-page-middleware.ts +69 -0
- package/src/router/create-api-middleware.ts +814 -0
- package/src/router/create-authorization-page-middleware.ts +173 -0
- package/src/router/create-oauth-middleware.ts +247 -0
- package/src/router/error-handler.ts +6 -0
- package/src/router/middleware-options.ts +9 -0
- package/src/router/send-redirect.ts +142 -0
- package/src/signer/api-token-payload.ts +18 -0
- package/src/signer/signed-token-payload.ts +18 -28
- package/src/signer/signer.ts +49 -34
- package/src/token/token-data.ts +1 -1
- package/src/token/token-manager.ts +190 -239
- package/src/token/token-store.ts +6 -4
- package/src/token/verify-token-claims.ts +4 -4
- package/src/types/email-otp.ts +3 -0
- package/src/types/email.ts +26 -0
- package/src/types/handle.ts +18 -0
- package/src/types/invite-code.ts +4 -0
- package/src/types/password.ts +4 -0
- package/tsconfig.build.tsbuildinfo +1 -0
- package/tsconfig.json +1 -1
- package/dist/access-token/access-token-type.d.ts +0 -6
- package/dist/access-token/access-token-type.d.ts.map +0 -1
- package/dist/access-token/access-token-type.js +0 -10
- package/dist/access-token/access-token-type.js.map +0 -1
- package/dist/account/account.d.ts +0 -2
- package/dist/account/account.d.ts.map +0 -1
- package/dist/account/account.js.map +0 -1
- package/dist/assets/assets-middleware.d.ts +0 -5
- package/dist/assets/assets-middleware.d.ts.map +0 -1
- package/dist/assets/assets-middleware.js +0 -41
- package/dist/assets/assets-middleware.js.map +0 -1
- package/dist/lib/locale.d.ts +0 -15
- package/dist/lib/locale.d.ts.map +0 -1
- package/dist/lib/locale.js +0 -17
- package/dist/lib/locale.js.map +0 -1
- package/dist/output/backend-data.d.ts +0 -4
- package/dist/output/backend-data.d.ts.map +0 -1
- package/dist/output/backend-data.js.map +0 -1
- package/dist/output/build-authorize-data.d.ts +0 -29
- package/dist/output/build-authorize-data.d.ts.map +0 -1
- package/dist/output/build-authorize-data.js +0 -21
- package/dist/output/build-authorize-data.js.map +0 -1
- package/dist/output/build-customization-data.d.ts +0 -234
- package/dist/output/build-customization-data.d.ts.map +0 -1
- package/dist/output/build-customization-data.js +0 -174
- package/dist/output/build-customization-data.js.map +0 -1
- package/dist/output/build-error-data.d.ts +0 -3
- package/dist/output/build-error-data.d.ts.map +0 -1
- package/dist/output/build-error-data.js +0 -10
- package/dist/output/build-error-data.js.map +0 -1
- package/dist/output/build-error-payload.d.ts.map +0 -1
- package/dist/output/build-error-payload.js.map +0 -1
- package/dist/output/output-manager.d.ts +0 -28
- package/dist/output/output-manager.d.ts.map +0 -1
- package/dist/output/output-manager.js +0 -134
- package/dist/output/output-manager.js.map +0 -1
- package/dist/output/send-authorize-redirect.d.ts +0 -25
- package/dist/output/send-authorize-redirect.d.ts.map +0 -1
- package/dist/output/send-authorize-redirect.js.map +0 -1
- package/dist/output/send-web-page.d.ts +0 -8
- package/dist/output/send-web-page.d.ts.map +0 -1
- package/dist/output/send-web-page.js.map +0 -1
- package/dist/token/token-claims.d.ts.map +0 -1
- package/dist/token/token-claims.js +0 -27
- package/dist/token/token-claims.js.map +0 -1
- package/src/access-token/access-token-type.ts +0 -5
- package/src/account/account.ts +0 -1
- package/src/assets/assets-middleware.ts +0 -44
- package/src/lib/locale.ts +0 -21
- package/src/output/build-authorize-data.ts +0 -53
- package/src/output/build-customization-data.ts +0 -217
- package/src/output/build-error-data.ts +0 -8
- package/src/output/output-manager.ts +0 -188
- package/src/output/send-authorize-redirect.ts +0 -137
- package/src/token/token-claims.ts +0 -30
- package/tsconfig.backend.tsbuildinfo +0 -1
- /package/{tsconfig.backend.json → tsconfig.build.json} +0 -0
package/src/oauth-provider.ts
CHANGED
@@ -1,8 +1,6 @@
|
|
1
|
-
import type { IncomingMessage, ServerResponse } from 'node:http'
|
2
|
-
import createHttpError from 'http-errors'
|
3
1
|
import type { Redis, RedisOptions } from 'ioredis'
|
4
|
-
import { ZodError, z } from 'zod'
|
5
2
|
import { Jwks, Keyset } from '@atproto/jwk'
|
3
|
+
import type { Account } from '@atproto/oauth-provider-api'
|
6
4
|
import {
|
7
5
|
CLIENT_ASSERTION_TYPE_JWT_BEARER,
|
8
6
|
OAuthAccessToken,
|
@@ -15,7 +13,6 @@ import {
|
|
15
13
|
OAuthClientCredentials,
|
16
14
|
OAuthClientCredentialsNone,
|
17
15
|
OAuthClientMetadata,
|
18
|
-
OAuthIntrospectionResponse,
|
19
16
|
OAuthParResponse,
|
20
17
|
OAuthRefreshTokenGrantTokenRequest,
|
21
18
|
OAuthTokenIdentification,
|
@@ -23,31 +20,21 @@ import {
|
|
23
20
|
OAuthTokenResponse,
|
24
21
|
OAuthTokenType,
|
25
22
|
atprotoLoopbackClientMetadata,
|
26
|
-
oauthAuthorizationRequestParSchema,
|
27
23
|
oauthAuthorizationRequestParametersSchema,
|
28
|
-
oauthAuthorizationRequestQuerySchema,
|
29
|
-
oauthClientCredentialsSchema,
|
30
|
-
oauthTokenIdentificationSchema,
|
31
|
-
oauthTokenRequestSchema,
|
32
24
|
} from '@atproto/oauth-types'
|
33
25
|
import { safeFetchWrap } from '@atproto-labs/fetch-node'
|
34
26
|
import { SimpleStore } from '@atproto-labs/simple-store'
|
35
27
|
import { SimpleStoreMemory } from '@atproto-labs/simple-store-memory'
|
36
|
-
import {
|
28
|
+
import { AccessTokenMode } from './access-token/access-token-mode.js'
|
37
29
|
import { AccountManager } from './account/account-manager.js'
|
38
30
|
import {
|
39
31
|
AccountStore,
|
40
|
-
|
32
|
+
AuthorizedClientData,
|
33
|
+
DeviceAccount,
|
41
34
|
asAccountStore,
|
42
|
-
handleSchema,
|
43
|
-
resetPasswordConfirmDataSchema,
|
44
|
-
resetPasswordRequestDataSchema,
|
45
35
|
} from './account/account-store.js'
|
46
|
-
import { Account } from './account/account.js'
|
47
|
-
import { signInDataSchema } from './account/sign-in-data.js'
|
48
|
-
import { signUpInputSchema } from './account/sign-up-input.js'
|
49
|
-
import { authorizeAssetsMiddleware } from './assets/assets-middleware.js'
|
50
36
|
import { ClientAuth, authJwkThumbprint } from './client/client-auth.js'
|
37
|
+
import { ClientId } from './client/client-id.js'
|
51
38
|
import {
|
52
39
|
ClientManager,
|
53
40
|
LoopbackMetadataGetter,
|
@@ -55,9 +42,14 @@ import {
|
|
55
42
|
import { ClientStore, ifClientStore } from './client/client-store.js'
|
56
43
|
import { Client } from './client/client.js'
|
57
44
|
import { AUTHENTICATION_MAX_AGE, TOKEN_MAX_AGE } from './constants.js'
|
45
|
+
import { Branding, BrandingInput } from './customization/branding.js'
|
46
|
+
import {
|
47
|
+
Customization,
|
48
|
+
CustomizationInput,
|
49
|
+
customizationSchema,
|
50
|
+
} from './customization/customization.js'
|
58
51
|
import { DeviceId } from './device/device-id.js'
|
59
52
|
import {
|
60
|
-
DeviceInfo,
|
61
53
|
DeviceManager,
|
62
54
|
DeviceManagerOptions,
|
63
55
|
deviceManagerOptionsSchema,
|
@@ -66,58 +58,18 @@ import { DeviceStore, asDeviceStore } from './device/device-store.js'
|
|
66
58
|
import { AccessDeniedError } from './errors/access-denied-error.js'
|
67
59
|
import { AccountSelectionRequiredError } from './errors/account-selection-required-error.js'
|
68
60
|
import { ConsentRequiredError } from './errors/consent-required-error.js'
|
69
|
-
import { InvalidClientError } from './errors/invalid-client-error.js'
|
70
61
|
import { InvalidGrantError } from './errors/invalid-grant-error.js'
|
71
62
|
import { InvalidParametersError } from './errors/invalid-parameters-error.js'
|
72
63
|
import { InvalidRequestError } from './errors/invalid-request-error.js'
|
73
64
|
import { LoginRequiredError } from './errors/login-required-error.js'
|
74
|
-
import { UnauthorizedClientError } from './errors/unauthorized-client-error.js'
|
75
|
-
import { WWWAuthenticateError } from './errors/www-authenticate-error.js'
|
76
65
|
import { HcaptchaConfig } from './lib/hcaptcha.js'
|
77
|
-
import {
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
cacheControlMiddleware,
|
82
|
-
combineMiddlewares,
|
83
|
-
parseHttpRequest,
|
84
|
-
setupCsrfToken,
|
85
|
-
staticJsonMiddleware,
|
86
|
-
validateCsrfToken,
|
87
|
-
validateFetchDest,
|
88
|
-
validateFetchMode,
|
89
|
-
validateFetchSite,
|
90
|
-
validateReferer,
|
91
|
-
validateSameOrigin,
|
92
|
-
writeJson,
|
93
|
-
} from './lib/http/index.js'
|
94
|
-
import {
|
95
|
-
RequestMetadata,
|
96
|
-
extractLocales,
|
97
|
-
negotiateResponseContent as negotiateContent,
|
98
|
-
} from './lib/http/request.js'
|
99
|
-
import { dateToEpoch, dateToRelativeSeconds } from './lib/util/date.js'
|
100
|
-
import { Awaitable, Override } from './lib/util/type.js'
|
66
|
+
import { RequestMetadata } from './lib/http/request.js'
|
67
|
+
import { dateToRelativeSeconds } from './lib/util/date.js'
|
68
|
+
import { LocalizedString, MultiLangString } from './lib/util/locale.js'
|
69
|
+
import { extractZodErrorMessage } from './lib/util/zod-error.js'
|
101
70
|
import { CustomMetadata, buildMetadata } from './metadata/build-metadata.js'
|
102
|
-
import { OAuthHooks
|
71
|
+
import { OAuthHooks } from './oauth-hooks.js'
|
103
72
|
import { OAuthVerifier, OAuthVerifierOptions } from './oauth-verifier.js'
|
104
|
-
import { AuthorizationResultAuthorize } from './output/build-authorize-data.js'
|
105
|
-
import {
|
106
|
-
Branding,
|
107
|
-
BrandingInput,
|
108
|
-
Customization,
|
109
|
-
CustomizationInput,
|
110
|
-
customizationSchema,
|
111
|
-
} from './output/build-customization-data.js'
|
112
|
-
import {
|
113
|
-
buildErrorPayload,
|
114
|
-
buildErrorStatus,
|
115
|
-
} from './output/build-error-payload.js'
|
116
|
-
import { OutputManager } from './output/output-manager.js'
|
117
|
-
import {
|
118
|
-
AuthorizationResultRedirect,
|
119
|
-
sendAuthorizeRedirect,
|
120
|
-
} from './output/send-authorize-redirect.js'
|
121
73
|
import { ReplayStore, ifReplayStore } from './replay/replay-store.js'
|
122
74
|
import { codeSchema } from './request/code.js'
|
123
75
|
import { RequestInfo } from './request/request-info.js'
|
@@ -125,129 +77,149 @@ import { RequestManager } from './request/request-manager.js'
|
|
125
77
|
import { RequestStoreMemory } from './request/request-store-memory.js'
|
126
78
|
import { RequestStoreRedis } from './request/request-store-redis.js'
|
127
79
|
import { RequestStore, ifRequestStore } from './request/request-store.js'
|
128
|
-
import {
|
129
|
-
import {
|
80
|
+
import { requestUriSchema } from './request/request-uri.js'
|
81
|
+
import { AuthorizationRedirectParameters } from './result/authorization-redirect-parameters.js'
|
82
|
+
import { AuthorizationResultAuthorizePage } from './result/authorization-result-authorize-page.js'
|
83
|
+
import { AuthorizationResultRedirect } from './result/authorization-result-redirect.js'
|
84
|
+
import { ErrorHandler } from './router/error-handler.js'
|
130
85
|
import { TokenManager } from './token/token-manager.js'
|
131
86
|
import { TokenStore, asTokenStore } from './token/token-store.js'
|
132
|
-
import {
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
87
|
+
import {
|
88
|
+
VerifyTokenClaimsOptions,
|
89
|
+
VerifyTokenClaimsResult,
|
90
|
+
} from './token/verify-token-claims.js'
|
91
|
+
|
92
|
+
export { AccessTokenMode, Keyset }
|
93
|
+
export type {
|
94
|
+
AuthorizationRedirectParameters,
|
95
|
+
AuthorizationResultAuthorizePage as AuthorizationResultAuthorize,
|
96
|
+
AuthorizationResultRedirect,
|
97
|
+
Branding,
|
98
|
+
BrandingInput,
|
99
|
+
CustomMetadata,
|
100
|
+
Customization,
|
101
|
+
CustomizationInput,
|
102
|
+
ErrorHandler,
|
103
|
+
HcaptchaConfig,
|
104
|
+
LocalizedString,
|
105
|
+
MultiLangString,
|
106
|
+
OAuthAuthorizationServerMetadata,
|
144
107
|
}
|
145
108
|
|
146
|
-
type
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
109
|
+
type OAuthProviderConfig = {
|
110
|
+
/**
|
111
|
+
* Maximum age a device/account session can be before requiring
|
112
|
+
* re-authentication.
|
113
|
+
*/
|
114
|
+
authenticationMaxAge?: number
|
115
|
+
|
116
|
+
/**
|
117
|
+
* Maximum age an ephemeral session (one where "remember me" was not
|
118
|
+
* checked) can be before requiring re-authentication.
|
119
|
+
*/
|
120
|
+
|
121
|
+
/**
|
122
|
+
* Maximum age access & id tokens can be before requiring a refresh.
|
123
|
+
*/
|
124
|
+
tokenMaxAge?: number
|
125
|
+
|
126
|
+
/**
|
127
|
+
* If set to {@link AccessTokenMode.stateless}, the generated access tokens
|
128
|
+
* will contain all the necessary information to validate the token without
|
129
|
+
* needing to query the database. This is useful for cases where the Resource
|
130
|
+
* Server is on a different host/server than the Authorization Server.
|
131
|
+
*
|
132
|
+
* When set to {@link AccessTokenMode.light}, the access tokens will contain
|
133
|
+
* only the necessary information to validate the token, but the token id
|
134
|
+
* will need to be queried from the database to retrieve the full token
|
135
|
+
* information (scope, audience, etc.)
|
136
|
+
*
|
137
|
+
* @see {@link AccessTokenMode}
|
138
|
+
* @default {AccessTokenMode.stateless}
|
139
|
+
*/
|
140
|
+
accessTokenMode?: AccessTokenMode
|
141
|
+
|
142
|
+
/**
|
143
|
+
* Additional metadata to be included in the discovery document.
|
144
|
+
*/
|
145
|
+
metadata?: CustomMetadata
|
146
|
+
|
147
|
+
/**
|
148
|
+
* A custom fetch function that can be used to fetch the client metadata from
|
149
|
+
* the internet. By default, the fetch function is a safeFetchWrap() function
|
150
|
+
* that protects against SSRF attacks, large responses & known bad domains. If
|
151
|
+
* you want to disable all protections, you can provide `globalThis.fetch` as
|
152
|
+
* fetch function.
|
153
|
+
*/
|
154
|
+
safeFetch?: typeof globalThis.fetch
|
155
|
+
|
156
|
+
/**
|
157
|
+
* A redis instance to use for replay protection. If not provided, replay
|
158
|
+
* protection will use memory storage.
|
159
|
+
*/
|
160
|
+
redis?: Redis | RedisOptions | string
|
151
161
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
162
|
+
/**
|
163
|
+
* This will be used as the default store for all the stores. If a store is
|
164
|
+
* not provided, this store will be used instead. If the `store` does not
|
165
|
+
* implement a specific store, a runtime error will be thrown. Make sure that
|
166
|
+
* this store implements all the interfaces not provided in the other
|
167
|
+
* `<name>Store` options.
|
168
|
+
*/
|
169
|
+
store?: Partial<
|
170
|
+
AccountStore &
|
171
|
+
ClientStore &
|
172
|
+
DeviceStore &
|
173
|
+
ReplayStore &
|
174
|
+
RequestStore &
|
175
|
+
TokenStore
|
176
|
+
>
|
177
|
+
|
178
|
+
accountStore?: AccountStore
|
179
|
+
clientStore?: ClientStore
|
180
|
+
deviceStore?: DeviceStore
|
181
|
+
replayStore?: ReplayStore
|
182
|
+
requestStore?: RequestStore
|
183
|
+
tokenStore?: TokenStore
|
184
|
+
|
185
|
+
/**
|
186
|
+
* In order to speed up the client fetching process, you can provide a cache
|
187
|
+
* to store HTTP responses.
|
188
|
+
*
|
189
|
+
* @note the cached entries should automatically expire after a certain time (typically 10 minutes)
|
190
|
+
*/
|
191
|
+
clientJwksCache?: SimpleStore<string, Jwks>
|
156
192
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
193
|
+
/**
|
194
|
+
* In order to speed up the client fetching process, you can provide a cache
|
195
|
+
* to store HTTP responses.
|
196
|
+
*
|
197
|
+
* @note the cached entries should automatically expire after a certain time (typically 10 minutes)
|
198
|
+
*/
|
199
|
+
clientMetadataCache?: SimpleStore<string, OAuthClientMetadata>
|
200
|
+
|
201
|
+
/**
|
202
|
+
* In order to enable loopback clients, you can provide a function that
|
203
|
+
* returns the client metadata for a given loopback URL. This is useful for
|
204
|
+
* development and testing purposes. This function is not called for internet
|
205
|
+
* clients.
|
206
|
+
*
|
207
|
+
* @default is as specified by ATPROTO
|
208
|
+
*/
|
209
|
+
loopbackMetadata?: null | false | LoopbackMetadataGetter
|
162
210
|
}
|
163
211
|
|
164
|
-
export type OAuthProviderOptions =
|
165
|
-
OAuthVerifierOptions &
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
* re-authentication.
|
170
|
-
*/
|
171
|
-
authenticationMaxAge?: number
|
172
|
-
|
173
|
-
/**
|
174
|
-
* Maximum age access & id tokens can be before requiring a refresh.
|
175
|
-
*/
|
176
|
-
tokenMaxAge?: number
|
177
|
-
|
178
|
-
/**
|
179
|
-
* Additional metadata to be included in the discovery document.
|
180
|
-
*/
|
181
|
-
metadata?: CustomMetadata
|
182
|
-
|
183
|
-
/**
|
184
|
-
* A custom fetch function that can be used to fetch the client metadata from
|
185
|
-
* the internet. By default, the fetch function is a safeFetchWrap() function
|
186
|
-
* that protects against SSRF attacks, large responses & known bad domains. If
|
187
|
-
* you want to disable all protections, you can provide `globalThis.fetch` as
|
188
|
-
* fetch function.
|
189
|
-
*/
|
190
|
-
safeFetch?: typeof globalThis.fetch
|
191
|
-
|
192
|
-
/**
|
193
|
-
* A redis instance to use for replay protection. If not provided, replay
|
194
|
-
* protection will use memory storage.
|
195
|
-
*/
|
196
|
-
redis?: Redis | RedisOptions | string
|
197
|
-
|
198
|
-
/**
|
199
|
-
* This will be used as the default store for all the stores. If a store is
|
200
|
-
* not provided, this store will be used instead. If the `store` does not
|
201
|
-
* implement a specific store, a runtime error will be thrown. Make sure that
|
202
|
-
* this store implements all the interfaces not provided in the other
|
203
|
-
* `<name>Store` options.
|
204
|
-
*/
|
205
|
-
store?: Partial<
|
206
|
-
AccountStore &
|
207
|
-
ClientStore &
|
208
|
-
DeviceStore &
|
209
|
-
ReplayStore &
|
210
|
-
RequestStore &
|
211
|
-
TokenStore
|
212
|
-
>
|
213
|
-
|
214
|
-
accountStore?: AccountStore
|
215
|
-
clientStore?: ClientStore
|
216
|
-
deviceStore?: DeviceStore
|
217
|
-
replayStore?: ReplayStore
|
218
|
-
requestStore?: RequestStore
|
219
|
-
tokenStore?: TokenStore
|
220
|
-
|
221
|
-
/**
|
222
|
-
* In order to speed up the client fetching process, you can provide a cache
|
223
|
-
* to store HTTP responses.
|
224
|
-
*
|
225
|
-
* @note the cached entries should automatically expire after a certain time (typically 10 minutes)
|
226
|
-
*/
|
227
|
-
clientJwksCache?: SimpleStore<string, Jwks>
|
228
|
-
|
229
|
-
/**
|
230
|
-
* In order to speed up the client fetching process, you can provide a cache
|
231
|
-
* to store HTTP responses.
|
232
|
-
*
|
233
|
-
* @note the cached entries should automatically expire after a certain time (typically 10 minutes)
|
234
|
-
*/
|
235
|
-
clientMetadataCache?: SimpleStore<string, OAuthClientMetadata>
|
236
|
-
|
237
|
-
/**
|
238
|
-
* In order to enable loopback clients, you can provide a function that
|
239
|
-
* returns the client metadata for a given loopback URL. This is useful for
|
240
|
-
* development and testing purposes. This function is not called for internet
|
241
|
-
* clients.
|
242
|
-
*
|
243
|
-
* @default is as specified by ATPROTO
|
244
|
-
*/
|
245
|
-
loopbackMetadata?: null | false | LoopbackMetadataGetter
|
246
|
-
}
|
247
|
-
>
|
212
|
+
export type OAuthProviderOptions = OAuthProviderConfig &
|
213
|
+
OAuthVerifierOptions &
|
214
|
+
OAuthHooks &
|
215
|
+
DeviceManagerOptions &
|
216
|
+
CustomizationInput
|
248
217
|
|
249
218
|
export class OAuthProvider extends OAuthVerifier {
|
219
|
+
protected readonly accessTokenMode: AccessTokenMode
|
220
|
+
|
250
221
|
public readonly metadata: OAuthAuthorizationServerMetadata
|
222
|
+
public readonly customization: Customization
|
251
223
|
|
252
224
|
public readonly authenticationMaxAge: number
|
253
225
|
|
@@ -256,12 +228,14 @@ export class OAuthProvider extends OAuthVerifier {
|
|
256
228
|
public readonly clientManager: ClientManager
|
257
229
|
public readonly requestManager: RequestManager
|
258
230
|
public readonly tokenManager: TokenManager
|
259
|
-
public readonly outputManager: OutputManager
|
260
231
|
|
261
232
|
public constructor({
|
262
|
-
|
233
|
+
// OAuthProviderConfig
|
263
234
|
authenticationMaxAge = AUTHENTICATION_MAX_AGE,
|
264
235
|
tokenMaxAge = TOKEN_MAX_AGE,
|
236
|
+
accessTokenMode = AccessTokenMode.stateless,
|
237
|
+
|
238
|
+
metadata,
|
265
239
|
|
266
240
|
safeFetch = safeFetchWrap(),
|
267
241
|
redis,
|
@@ -294,7 +268,6 @@ export class OAuthProvider extends OAuthVerifier {
|
|
294
268
|
// Customization
|
295
269
|
...rest
|
296
270
|
}: OAuthProviderOptions) {
|
297
|
-
const customization: Customization = customizationSchema.parse(rest)
|
298
271
|
const deviceManagerOptions: DeviceManagerOptions =
|
299
272
|
deviceManagerOptionsSchema.parse(rest)
|
300
273
|
|
@@ -317,16 +290,17 @@ export class OAuthProvider extends OAuthVerifier {
|
|
317
290
|
? new RequestStoreRedis({ redis })
|
318
291
|
: new RequestStoreMemory()
|
319
292
|
|
293
|
+
this.accessTokenMode = accessTokenMode
|
320
294
|
this.authenticationMaxAge = authenticationMaxAge
|
321
295
|
this.metadata = buildMetadata(this.issuer, this.keyset, metadata)
|
296
|
+
this.customization = customizationSchema.parse(rest)
|
322
297
|
|
323
298
|
this.deviceManager = new DeviceManager(deviceStore, deviceManagerOptions)
|
324
|
-
this.outputManager = new OutputManager(customization)
|
325
299
|
this.accountManager = new AccountManager(
|
326
300
|
this.issuer,
|
327
301
|
accountStore,
|
328
302
|
hooks,
|
329
|
-
customization,
|
303
|
+
this.customization,
|
330
304
|
)
|
331
305
|
this.clientManager = new ClientManager(
|
332
306
|
this.metadata,
|
@@ -348,7 +322,7 @@ export class OAuthProvider extends OAuthVerifier {
|
|
348
322
|
tokenStore,
|
349
323
|
this.signer,
|
350
324
|
hooks,
|
351
|
-
this.
|
325
|
+
this.accessTokenMode,
|
352
326
|
tokenMaxAge,
|
353
327
|
)
|
354
328
|
}
|
@@ -357,20 +331,31 @@ export class OAuthProvider extends OAuthVerifier {
|
|
357
331
|
return this.keyset.publicJwks
|
358
332
|
}
|
359
333
|
|
360
|
-
|
361
|
-
|
334
|
+
/**
|
335
|
+
* @returns true if the user's consent is required for the requested scopes
|
336
|
+
*/
|
337
|
+
public checkConsentRequired(
|
362
338
|
parameters: OAuthAuthorizationRequestParameters,
|
363
|
-
|
339
|
+
clientData?: AuthorizedClientData,
|
364
340
|
) {
|
365
|
-
|
366
|
-
|
341
|
+
// Client was never authorized before
|
342
|
+
if (!clientData) return true
|
367
343
|
|
368
|
-
//
|
369
|
-
if (
|
370
|
-
return true
|
371
|
-
}
|
344
|
+
// Client explicitly asked for consent
|
345
|
+
if (parameters.prompt === 'consent') return true
|
372
346
|
|
373
|
-
|
347
|
+
// No scope requested, and client is known by user, no consent required
|
348
|
+
const requestedScopes = parameters.scope?.split(' ')
|
349
|
+
if (requestedScopes == null) return false
|
350
|
+
|
351
|
+
// Ensure that all requested scopes were previously authorized by the user
|
352
|
+
const { authorizedScopes } = clientData
|
353
|
+
return !requestedScopes.every((scope) => authorizedScopes.includes(scope))
|
354
|
+
}
|
355
|
+
|
356
|
+
public checkLoginRequired(deviceAccount: DeviceAccount) {
|
357
|
+
const authAge = Date.now() - deviceAccount.updatedAt.getTime()
|
358
|
+
return authAge > this.authenticationMaxAge
|
374
359
|
}
|
375
360
|
|
376
361
|
protected async authenticateClient(
|
@@ -470,7 +455,7 @@ export class OAuthProvider extends OAuthVerifier {
|
|
470
455
|
/**
|
471
456
|
* @see {@link https://datatracker.ietf.org/doc/html/rfc9126}
|
472
457
|
*/
|
473
|
-
|
458
|
+
public async pushedAuthorizationRequest(
|
474
459
|
credentials: OAuthClientCredentials,
|
475
460
|
authorizationRequest: OAuthAuthorizationRequestPar,
|
476
461
|
dpopJkt: null | string,
|
@@ -516,7 +501,12 @@ export class OAuthProvider extends OAuthVerifier {
|
|
516
501
|
if ('request_uri' in query) {
|
517
502
|
const requestUri = await requestUriSchema
|
518
503
|
.parseAsync(query.request_uri, { path: ['query', 'request_uri'] })
|
519
|
-
.catch(
|
504
|
+
.catch((err) => {
|
505
|
+
throw new InvalidRequestError(
|
506
|
+
extractZodErrorMessage(err) ?? 'Input validation error',
|
507
|
+
err,
|
508
|
+
)
|
509
|
+
})
|
520
510
|
|
521
511
|
return this.requestManager.get(requestUri, deviceId, client.id)
|
522
512
|
}
|
@@ -561,26 +551,15 @@ export class OAuthProvider extends OAuthVerifier {
|
|
561
551
|
)
|
562
552
|
}
|
563
553
|
|
564
|
-
private async deleteRequest(
|
565
|
-
requestUri: RequestUri,
|
566
|
-
parameters: OAuthAuthorizationRequestParameters,
|
567
|
-
) {
|
568
|
-
try {
|
569
|
-
await this.requestManager.delete(requestUri)
|
570
|
-
} catch (err) {
|
571
|
-
throw AccessDeniedError.from(parameters, err)
|
572
|
-
}
|
573
|
-
}
|
574
|
-
|
575
554
|
/**
|
576
555
|
* @see {@link https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-11#section-4.1.1}
|
577
556
|
*/
|
578
|
-
|
557
|
+
public async authorize(
|
579
558
|
clientCredentials: OAuthClientCredentialsNone,
|
580
559
|
query: OAuthAuthorizationRequestQuery,
|
581
560
|
deviceId: DeviceId,
|
582
561
|
deviceMetadata: RequestMetadata,
|
583
|
-
): Promise<AuthorizationResultRedirect |
|
562
|
+
): Promise<AuthorizationResultRedirect | AuthorizationResultAuthorizePage> {
|
584
563
|
const { issuer } = this
|
585
564
|
|
586
565
|
// If there is a chance to redirect the user to the client, let's do
|
@@ -597,18 +576,14 @@ export class OAuthProvider extends OAuthVerifier {
|
|
597
576
|
.getClient(clientCredentials.client_id)
|
598
577
|
.catch(accessDeniedCatcher)
|
599
578
|
|
600
|
-
const {
|
601
|
-
|
602
|
-
|
603
|
-
|
579
|
+
const { parameters, uri } = await this.processAuthorizationRequest(
|
580
|
+
client,
|
581
|
+
deviceId,
|
582
|
+
query,
|
583
|
+
).catch(accessDeniedCatcher)
|
604
584
|
|
605
585
|
try {
|
606
|
-
const sessions = await this.getSessions(
|
607
|
-
client,
|
608
|
-
clientAuth,
|
609
|
-
deviceId,
|
610
|
-
parameters,
|
611
|
-
)
|
586
|
+
const sessions = await this.getSessions(client.id, deviceId, parameters)
|
612
587
|
|
613
588
|
if (parameters.prompt === 'none') {
|
614
589
|
const ssoSessions = sessions.filter((s) => s.matchesHint)
|
@@ -635,7 +610,7 @@ export class OAuthProvider extends OAuthVerifier {
|
|
635
610
|
deviceMetadata,
|
636
611
|
)
|
637
612
|
|
638
|
-
return { issuer,
|
613
|
+
return { issuer, parameters, redirect: { code } }
|
639
614
|
}
|
640
615
|
|
641
616
|
// Automatic SSO when a did was provided
|
@@ -652,7 +627,7 @@ export class OAuthProvider extends OAuthVerifier {
|
|
652
627
|
deviceMetadata,
|
653
628
|
)
|
654
629
|
|
655
|
-
return { issuer,
|
630
|
+
return { issuer, parameters, redirect: { code } }
|
656
631
|
}
|
657
632
|
}
|
658
633
|
}
|
@@ -661,39 +636,48 @@ export class OAuthProvider extends OAuthVerifier {
|
|
661
636
|
issuer,
|
662
637
|
client,
|
663
638
|
parameters,
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
639
|
+
uri,
|
640
|
+
sessions: sessions.map((session) => ({
|
641
|
+
// Map to avoid leaking other data that might be present in the session
|
642
|
+
account: session.account,
|
643
|
+
selected: session.selected,
|
644
|
+
loginRequired: session.loginRequired,
|
645
|
+
consentRequired: session.consentRequired,
|
646
|
+
})),
|
647
|
+
scopeDetails: parameters.scope
|
648
|
+
?.split(/\s+/)
|
649
|
+
.filter(Boolean)
|
650
|
+
.sort((a, b) => a.localeCompare(b))
|
651
|
+
.map((scope) => ({
|
652
|
+
scope,
|
653
|
+
// @TODO Allow to customize the scope descriptions (e.g.
|
654
|
+
// using a hook)
|
655
|
+
description: undefined,
|
656
|
+
})),
|
678
657
|
}
|
679
658
|
} catch (err) {
|
680
|
-
|
659
|
+
try {
|
660
|
+
await this.requestManager.delete(uri)
|
661
|
+
} catch {
|
662
|
+
// There are two error here. Better keep the outer one.
|
663
|
+
//
|
664
|
+
// @TODO Maybe move this entire code to the /authorize endpoint
|
665
|
+
// (allowing to log this error)
|
666
|
+
}
|
681
667
|
|
682
668
|
// Not using accessDeniedCatcher here because "parameters" will most
|
683
669
|
// likely contain the redirect_uri (using the client default).
|
684
|
-
throw AccessDeniedError.from(parameters, err)
|
670
|
+
throw AccessDeniedError.from(parameters, err, 'server_error')
|
685
671
|
}
|
686
672
|
}
|
687
673
|
|
688
674
|
protected async getSessions(
|
689
|
-
|
690
|
-
clientAuth: ClientAuth,
|
675
|
+
clientId: ClientId,
|
691
676
|
deviceId: DeviceId,
|
692
677
|
parameters: OAuthAuthorizationRequestParameters,
|
693
678
|
): Promise<
|
694
679
|
{
|
695
680
|
account: Account
|
696
|
-
info: DeviceAccountInfo
|
697
681
|
|
698
682
|
selected: boolean
|
699
683
|
loginRequired: boolean
|
@@ -702,163 +686,34 @@ export class OAuthProvider extends OAuthVerifier {
|
|
702
686
|
matchesHint: boolean
|
703
687
|
}[]
|
704
688
|
> {
|
705
|
-
const
|
689
|
+
const deviceAccounts =
|
690
|
+
await this.accountManager.listDeviceAccounts(deviceId)
|
706
691
|
|
707
692
|
const hint = parameters.login_hint
|
708
693
|
const matchesHint = (account: Account): boolean =>
|
709
694
|
(!!account.sub && account.sub === hint) ||
|
710
695
|
(!!account.preferred_username && account.preferred_username === hint)
|
711
696
|
|
712
|
-
return
|
713
|
-
account,
|
714
|
-
info,
|
697
|
+
return deviceAccounts.map((deviceAccount) => ({
|
698
|
+
account: deviceAccount.account,
|
715
699
|
|
716
700
|
selected:
|
717
701
|
parameters.prompt !== 'select_account' &&
|
718
|
-
matchesHint(account)
|
719
|
-
|
720
|
-
|
721
|
-
// selecting the account automatically may have unexpected results (i.e.
|
722
|
-
// not able to login using desired account).
|
723
|
-
accounts.reduce(
|
724
|
-
(acc, a) => acc + (matchesHint(a.account) ? 1 : 0),
|
725
|
-
0,
|
726
|
-
) === 1,
|
702
|
+
matchesHint(deviceAccount.account),
|
703
|
+
// @TODO Return the session expiration date instead of a boolean to
|
704
|
+
// avoid having to rely on a leeway when "accepting" the request.
|
727
705
|
loginRequired:
|
728
|
-
parameters.prompt === 'login' ||
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
// were already authorized for the client. Otherwise a client could
|
734
|
-
// use silent authentication to get additional scopes without consent.
|
735
|
-
!info.authorizedClients.includes(client.id),
|
736
|
-
|
737
|
-
matchesHint: hint == null || matchesHint(account),
|
738
|
-
}))
|
739
|
-
}
|
740
|
-
|
741
|
-
protected async signUp(
|
742
|
-
{ requestUri, deviceId, deviceMetadata }: ApiContext,
|
743
|
-
data: SignUpData,
|
744
|
-
): Promise<{
|
745
|
-
account: Account
|
746
|
-
consentRequired: boolean
|
747
|
-
}> {
|
748
|
-
const { clientId } = await this.requestManager.get(requestUri, deviceId)
|
749
|
-
|
750
|
-
const client = await this.clientManager.getClient(clientId)
|
751
|
-
|
752
|
-
const { account } = await this.accountManager.signUp(
|
753
|
-
data,
|
754
|
-
deviceId,
|
755
|
-
deviceMetadata,
|
756
|
-
)
|
757
|
-
|
758
|
-
return {
|
759
|
-
account,
|
760
|
-
consentRequired: !client.info.isFirstParty,
|
761
|
-
}
|
762
|
-
}
|
763
|
-
|
764
|
-
protected async signIn(
|
765
|
-
{ requestUri, deviceId, deviceMetadata }: ApiContext,
|
766
|
-
data: SignInData,
|
767
|
-
): Promise<{
|
768
|
-
account: Account
|
769
|
-
consentRequired: boolean
|
770
|
-
}> {
|
771
|
-
// Ensure the request is still valid (and update the request expiration)
|
772
|
-
// @TODO use the returned scopes to determine if consent is required
|
773
|
-
const { clientId } = await this.requestManager.get(requestUri, deviceId)
|
774
|
-
|
775
|
-
const client = await this.clientManager.getClient(clientId)
|
776
|
-
|
777
|
-
const { account, info } = await this.accountManager.signIn(
|
778
|
-
data,
|
779
|
-
deviceId,
|
780
|
-
deviceMetadata,
|
781
|
-
)
|
782
|
-
|
783
|
-
return {
|
784
|
-
account,
|
785
|
-
consentRequired: client.info.isFirstParty
|
786
|
-
? false
|
787
|
-
: // @TODO: the "authorizedClients" should also include the scopes that
|
788
|
-
// were already authorized for the client. Otherwise a client could
|
789
|
-
// use silent authentication to get additional scopes without consent.
|
790
|
-
!info.authorizedClients.includes(client.id),
|
791
|
-
}
|
792
|
-
}
|
793
|
-
|
794
|
-
protected async acceptRequest(
|
795
|
-
{ requestUri, deviceId, deviceMetadata }: ApiContext,
|
796
|
-
sub: string,
|
797
|
-
): Promise<AuthorizationResultRedirect> {
|
798
|
-
const { issuer } = this
|
799
|
-
|
800
|
-
const { parameters, clientId, clientAuth } = await this.requestManager.get(
|
801
|
-
requestUri,
|
802
|
-
deviceId,
|
803
|
-
)
|
804
|
-
|
805
|
-
const client = await this.clientManager.getClient(clientId)
|
806
|
-
|
807
|
-
try {
|
808
|
-
// @TODO Currently, a user can "accept" a request for any did that sing-in
|
809
|
-
// on the device, even if "remember" was set to false.
|
810
|
-
const { account, info } = await this.accountManager.get(deviceId, sub)
|
811
|
-
|
812
|
-
// The user is trying to authorize without a fresh login
|
813
|
-
if (this.loginRequired(client, parameters, info)) {
|
814
|
-
throw new LoginRequiredError(
|
815
|
-
parameters,
|
816
|
-
'Account authentication required.',
|
817
|
-
)
|
818
|
-
}
|
819
|
-
|
820
|
-
const code = await this.requestManager.setAuthorized(
|
821
|
-
requestUri,
|
822
|
-
client,
|
823
|
-
account,
|
824
|
-
deviceId,
|
825
|
-
deviceMetadata,
|
826
|
-
)
|
827
|
-
|
828
|
-
await this.accountManager.addAuthorizedClient(
|
829
|
-
deviceId,
|
830
|
-
account,
|
831
|
-
client,
|
832
|
-
clientAuth,
|
833
|
-
)
|
834
|
-
|
835
|
-
return { issuer, parameters, redirect: { code } }
|
836
|
-
} catch (err) {
|
837
|
-
await this.deleteRequest(requestUri, parameters)
|
838
|
-
|
839
|
-
throw AccessDeniedError.from(parameters, err)
|
840
|
-
}
|
841
|
-
}
|
706
|
+
parameters.prompt === 'login' || this.checkLoginRequired(deviceAccount),
|
707
|
+
consentRequired: this.checkConsentRequired(
|
708
|
+
parameters,
|
709
|
+
deviceAccount.authorizedClients.get(clientId),
|
710
|
+
),
|
842
711
|
|
843
|
-
|
844
|
-
|
845
|
-
deviceId,
|
846
|
-
}: ApiContext): Promise<AuthorizationResultRedirect> {
|
847
|
-
const { parameters } = await this.requestManager.get(requestUri, deviceId)
|
848
|
-
|
849
|
-
await this.deleteRequest(requestUri, parameters)
|
850
|
-
|
851
|
-
return {
|
852
|
-
issuer: this.issuer,
|
853
|
-
parameters: parameters,
|
854
|
-
redirect: {
|
855
|
-
error: 'access_denied',
|
856
|
-
error_description: 'Access denied',
|
857
|
-
},
|
858
|
-
}
|
712
|
+
matchesHint: hint == null || matchesHint(deviceAccount.account),
|
713
|
+
}))
|
859
714
|
}
|
860
715
|
|
861
|
-
|
716
|
+
public async token(
|
862
717
|
clientCredentials: OAuthClientCredentials,
|
863
718
|
clientMetadata: RequestMetadata,
|
864
719
|
request: OAuthTokenRequest,
|
@@ -911,9 +766,8 @@ export class OAuthProvider extends OAuthVerifier {
|
|
911
766
|
input: OAuthAuthorizationCodeGrantTokenRequest,
|
912
767
|
dpopJkt: null | string,
|
913
768
|
): Promise<OAuthTokenResponse> {
|
769
|
+
const code = codeSchema.parse(input.code)
|
914
770
|
try {
|
915
|
-
const code = codeSchema.parse(input.code)
|
916
|
-
|
917
771
|
const { sub, deviceId, parameters } = await this.requestManager.findCode(
|
918
772
|
client,
|
919
773
|
clientAuth,
|
@@ -933,27 +787,24 @@ export class OAuthProvider extends OAuthVerifier {
|
|
933
787
|
// a good enough incentive to follow the best practices, until we have a
|
934
788
|
// better implementation.
|
935
789
|
//
|
936
|
-
// @TODO
|
790
|
+
// @TODO Use tokenManager to ensure uniqueness of code_challenge
|
937
791
|
if (parameters.code_challenge) {
|
938
792
|
const unique = await this.replayManager.uniqueCodeChallenge(
|
939
793
|
parameters.code_challenge,
|
940
794
|
)
|
941
795
|
if (!unique) {
|
942
|
-
throw new InvalidGrantError(
|
943
|
-
'code_challenge',
|
944
|
-
'Code challenge already used',
|
945
|
-
)
|
796
|
+
throw new InvalidGrantError('Code challenge already used')
|
946
797
|
}
|
947
798
|
}
|
948
799
|
|
949
|
-
const { account
|
800
|
+
const { account } = await this.accountManager.getAccount(sub)
|
950
801
|
|
951
802
|
return await this.tokenManager.create(
|
952
803
|
client,
|
953
804
|
clientAuth,
|
954
805
|
clientMetadata,
|
955
806
|
account,
|
956
|
-
|
807
|
+
deviceId,
|
957
808
|
parameters,
|
958
809
|
input,
|
959
810
|
dpopJkt,
|
@@ -962,10 +813,18 @@ export class OAuthProvider extends OAuthVerifier {
|
|
962
813
|
// If a token is replayed, requestManager.findCode will throw. In that
|
963
814
|
// case, we need to revoke any token that was issued for this code.
|
964
815
|
|
965
|
-
await this.tokenManager.
|
816
|
+
const tokenInfo = await this.tokenManager.findByCode(code)
|
817
|
+
if (tokenInfo) {
|
818
|
+
await this.tokenManager.deleteToken(tokenInfo.id)
|
966
819
|
|
967
|
-
|
968
|
-
|
820
|
+
// As an additional security measure, we also sign the device out, so
|
821
|
+
// that the device cannot be used to access the account anymore without
|
822
|
+
// a new authentication.
|
823
|
+
const { deviceId, sub } = tokenInfo.data
|
824
|
+
if (deviceId) {
|
825
|
+
await this.accountManager.removeDeviceAccount(deviceId, sub)
|
826
|
+
}
|
827
|
+
}
|
969
828
|
|
970
829
|
throw err
|
971
830
|
}
|
@@ -990,693 +849,63 @@ export class OAuthProvider extends OAuthVerifier {
|
|
990
849
|
/**
|
991
850
|
* @see {@link https://datatracker.ietf.org/doc/html/rfc7009#section-2.1 rfc7009}
|
992
851
|
*/
|
993
|
-
|
994
|
-
// @TODO this should also remove the account-device association (or, at
|
995
|
-
// least, mark it as expired)
|
996
|
-
await this.tokenManager.revoke(token)
|
997
|
-
}
|
998
|
-
|
999
|
-
/**
|
1000
|
-
* @see {@link https://datatracker.ietf.org/doc/html/rfc7662#section-2.1 rfc7662}
|
1001
|
-
*/
|
1002
|
-
protected async introspect(
|
852
|
+
public async revoke(
|
1003
853
|
credentials: OAuthClientCredentials,
|
1004
854
|
{ token }: OAuthTokenIdentification,
|
1005
|
-
)
|
855
|
+
) {
|
856
|
+
// > The authorization server first validates the client credentials (in
|
857
|
+
// > case of a confidential client)
|
1006
858
|
const [client, clientAuth] = await this.authenticateClient(credentials)
|
1007
859
|
|
1008
|
-
|
1009
|
-
//
|
1010
|
-
// > To prevent token scanning attacks, the endpoint MUST also require some
|
1011
|
-
// > form of authorization to access this endpoint, such as client
|
1012
|
-
// > authentication as described in OAuth 2.0 [RFC6749] or a separate OAuth
|
1013
|
-
// > 2.0 access token such as the bearer token described in OAuth 2.0 Bearer
|
1014
|
-
// > Token Usage [RFC6750]. The methods of managing and validating these
|
1015
|
-
// > authentication credentials are out of scope of this specification.
|
1016
|
-
if (clientAuth.method === 'none') {
|
1017
|
-
throw new UnauthorizedClientError('Client authentication required')
|
1018
|
-
}
|
860
|
+
const tokenInfo = await this.tokenManager.findToken(token)
|
1019
861
|
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
1025
|
-
token,
|
1026
|
-
)
|
1027
|
-
|
1028
|
-
return {
|
1029
|
-
active: true,
|
1030
|
-
|
1031
|
-
scope: tokenInfo.data.parameters.scope,
|
1032
|
-
client_id: tokenInfo.data.clientId,
|
1033
|
-
username: tokenInfo.account.preferred_username,
|
1034
|
-
token_type: tokenInfo.data.parameters.dpop_jkt ? 'DPoP' : 'Bearer',
|
1035
|
-
authorization_details: tokenInfo.data.details ?? undefined,
|
1036
|
-
|
1037
|
-
aud: tokenInfo.account.aud,
|
1038
|
-
exp: dateToEpoch(tokenInfo.data.expiresAt),
|
1039
|
-
iat: dateToEpoch(tokenInfo.data.updatedAt),
|
1040
|
-
iss: this.signer.issuer,
|
1041
|
-
jti: tokenInfo.id,
|
1042
|
-
sub: tokenInfo.account.sub,
|
1043
|
-
}
|
1044
|
-
} catch (err) {
|
1045
|
-
// Prevent brute force & timing attack (only for inactive tokens)
|
1046
|
-
await new Promise((r) => setTimeout(r, 750 - (Date.now() - start)))
|
862
|
+
// > [...] and then verifies whether the token was issued to the client
|
863
|
+
// > making the revocation request. If this validation fails, the request is
|
864
|
+
// > refused and the client is informed of the error by the authorization
|
865
|
+
// > server as described below.
|
866
|
+
await this.tokenManager.validateAccess(client, clientAuth, tokenInfo)
|
1047
867
|
|
1048
|
-
|
1049
|
-
|
1050
|
-
|
1051
|
-
|
868
|
+
// > In the next step, the authorization server invalidates the token. The
|
869
|
+
// > invalidation takes place immediately, and the token cannot be used
|
870
|
+
// > again after the revocation.
|
871
|
+
await this.tokenManager.deleteToken(tokenInfo.id)
|
1052
872
|
}
|
1053
873
|
|
1054
|
-
protected override async
|
874
|
+
protected override async verifyToken(
|
1055
875
|
tokenType: OAuthTokenType,
|
1056
876
|
token: OAuthAccessToken,
|
1057
877
|
dpopJkt: string | null,
|
1058
878
|
verifyOptions?: VerifyTokenClaimsOptions,
|
1059
|
-
) {
|
1060
|
-
if (
|
1061
|
-
|
879
|
+
): Promise<VerifyTokenClaimsResult> {
|
880
|
+
if (this.accessTokenMode === AccessTokenMode.stateless) {
|
881
|
+
return super.verifyToken(tokenType, token, dpopJkt, verifyOptions)
|
882
|
+
}
|
1062
883
|
|
1063
|
-
|
884
|
+
if (this.accessTokenMode === AccessTokenMode.light) {
|
885
|
+
const { claims } = await super.verifyToken(
|
1064
886
|
tokenType,
|
1065
887
|
token,
|
1066
888
|
dpopJkt,
|
1067
|
-
|
889
|
+
// Do not verify the scope and audience in case of "light" tokens.
|
890
|
+
// these will be checked through the tokenManager hereafter.
|
891
|
+
undefined,
|
1068
892
|
)
|
1069
|
-
}
|
1070
|
-
|
1071
|
-
return super.authenticateToken(tokenType, token, dpopJkt, verifyOptions)
|
1072
|
-
}
|
1073
|
-
|
1074
|
-
/**
|
1075
|
-
* @returns An http request handler that can be used with node's http server
|
1076
|
-
* or as a middleware with express / connect.
|
1077
|
-
*/
|
1078
|
-
public httpHandler<
|
1079
|
-
T = void,
|
1080
|
-
Req extends IncomingMessage = IncomingMessage,
|
1081
|
-
Res extends ServerResponse = ServerResponse,
|
1082
|
-
>(options?: RouterOptions<Req, Res>): Handler<T, Req, Res> {
|
1083
|
-
const router = this.buildRouter<T, Req, Res>(options)
|
1084
|
-
return router.buildHandler()
|
1085
|
-
}
|
1086
893
|
|
1087
|
-
|
1088
|
-
T = void,
|
1089
|
-
Req extends IncomingMessage = IncomingMessage,
|
1090
|
-
Res extends ServerResponse = ServerResponse,
|
1091
|
-
>(options?: RouterOptions<Req, Res>): Router<T, Req, Res> {
|
1092
|
-
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
1093
|
-
const server = this
|
1094
|
-
const issuerUrl = new URL(server.issuer)
|
1095
|
-
const issuerOrigin = issuerUrl.origin
|
1096
|
-
const router = new Router<T, Req, Res>(issuerUrl)
|
1097
|
-
|
1098
|
-
// Utils
|
1099
|
-
|
1100
|
-
const csrfCookie = (requestUri: RequestUri) => `csrf-${requestUri}`
|
1101
|
-
const onError: null | ErrorHandler<Req, Res> =
|
1102
|
-
options?.onError ??
|
1103
|
-
(process.env['NODE_ENV'] === 'development'
|
1104
|
-
? (req, res, err, msg) => {
|
1105
|
-
console.error(`OAuthProvider error (${msg}):`, err)
|
1106
|
-
}
|
1107
|
-
: null)
|
1108
|
-
|
1109
|
-
// CORS preflight
|
1110
|
-
const corsHeaders: Middleware = function (req, res, next) {
|
1111
|
-
res.setHeader('Access-Control-Max-Age', '86400') // 1 day
|
1112
|
-
|
1113
|
-
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
|
1114
|
-
//
|
1115
|
-
// > For requests without credentials, the literal value "*" can be
|
1116
|
-
// > specified as a wildcard; the value tells browsers to allow
|
1117
|
-
// > requesting code from any origin to access the resource.
|
1118
|
-
// > Attempting to use the wildcard with credentials results in an
|
1119
|
-
// > error.
|
1120
|
-
//
|
1121
|
-
// A "*" is safer to use than reflecting the request origin.
|
1122
|
-
res.setHeader('Access-Control-Allow-Origin', '*')
|
894
|
+
const tokenId = claims.jti
|
1123
895
|
|
1124
|
-
//
|
1125
|
-
//
|
1126
|
-
//
|
1127
|
-
|
1128
|
-
|
1129
|
-
|
1130
|
-
|
1131
|
-
|
1132
|
-
|
1133
|
-
|
1134
|
-
next()
|
1135
|
-
}
|
1136
|
-
|
1137
|
-
const corsPreflight: Middleware = combineMiddlewares([
|
1138
|
-
corsHeaders,
|
1139
|
-
(req, res) => {
|
1140
|
-
res.writeHead(200).end()
|
1141
|
-
},
|
1142
|
-
])
|
1143
|
-
|
1144
|
-
/**
|
1145
|
-
* Wrap an OAuth endpoint in a middleware that will set the appropriate
|
1146
|
-
* response headers and format the response as JSON.
|
1147
|
-
*/
|
1148
|
-
const jsonHandler = <T, TReq extends Req, TRes extends Res, Payload>(
|
1149
|
-
buildJson: (
|
1150
|
-
this: T,
|
1151
|
-
req: TReq,
|
1152
|
-
res: TRes,
|
1153
|
-
) => Awaitable<{ payload: Payload; status?: number }>,
|
1154
|
-
): Handler<T, TReq, TRes> =>
|
1155
|
-
async function (req, res) {
|
1156
|
-
// https://www.rfc-editor.org/rfc/rfc6749.html#section-5.1
|
1157
|
-
res.setHeader('Cache-Control', 'no-store')
|
1158
|
-
res.setHeader('Pragma', 'no-cache')
|
1159
|
-
|
1160
|
-
// Ensure we can agree on a content encoding & type before starting to
|
1161
|
-
// build the JSON response.
|
1162
|
-
if (!negotiateContent(req, ['application/json'])) {
|
1163
|
-
throw createHttpError(406, 'Unsupported media type')
|
1164
|
-
}
|
1165
|
-
|
1166
|
-
try {
|
1167
|
-
const { payload, status = 200 } = await buildJson.call(this, req, res)
|
1168
|
-
writeJson(res, payload, { status })
|
1169
|
-
} catch (err) {
|
1170
|
-
onError?.(req, res, err, 'OAuth request error')
|
1171
|
-
|
1172
|
-
if (!res.headersSent) {
|
1173
|
-
const payload = buildErrorPayload(err)
|
1174
|
-
const status = buildErrorStatus(err)
|
1175
|
-
writeJson(res, payload, { status })
|
1176
|
-
} else {
|
1177
|
-
res.destroy()
|
1178
|
-
}
|
1179
|
-
}
|
1180
|
-
}
|
1181
|
-
|
1182
|
-
const oauthHandler = <T, TReq extends Req, TRes extends Res, Payload>(
|
1183
|
-
buildOAuthResponse: (this: T, req: TReq, res: TRes) => Awaitable<Payload>,
|
1184
|
-
status?: number,
|
1185
|
-
) =>
|
1186
|
-
combineMiddlewares([
|
1187
|
-
corsHeaders,
|
1188
|
-
jsonHandler<T, TReq, TRes, Payload>(async function (req, res) {
|
1189
|
-
try {
|
1190
|
-
// https://datatracker.ietf.org/doc/html/rfc9449#section-8.2
|
1191
|
-
const dpopNonce = server.nextDpopNonce()
|
1192
|
-
if (dpopNonce) {
|
1193
|
-
const name = 'DPoP-Nonce'
|
1194
|
-
res.setHeader(name, dpopNonce)
|
1195
|
-
res.appendHeader('Access-Control-Expose-Headers', name)
|
1196
|
-
}
|
1197
|
-
|
1198
|
-
const payload = await buildOAuthResponse.call(this, req, res)
|
1199
|
-
return { payload, status }
|
1200
|
-
} catch (err) {
|
1201
|
-
if (!res.headersSent && err instanceof WWWAuthenticateError) {
|
1202
|
-
const name = 'WWW-Authenticate'
|
1203
|
-
res.setHeader(name, err.wwwAuthenticateHeader)
|
1204
|
-
res.appendHeader('Access-Control-Expose-Headers', name)
|
1205
|
-
}
|
1206
|
-
|
1207
|
-
throw err
|
1208
|
-
}
|
1209
|
-
}),
|
1210
|
-
])
|
1211
|
-
|
1212
|
-
const apiHandler = <
|
1213
|
-
T,
|
1214
|
-
TReq extends Req,
|
1215
|
-
TRes extends Res,
|
1216
|
-
S extends z.ZodTypeAny,
|
1217
|
-
Payload,
|
1218
|
-
>(
|
1219
|
-
inputSchema: S,
|
1220
|
-
buildJson: (
|
1221
|
-
this: T,
|
1222
|
-
req: TReq,
|
1223
|
-
res: TRes,
|
1224
|
-
input: z.infer<S>,
|
1225
|
-
context: ApiContext,
|
1226
|
-
) => Awaitable<Payload>,
|
1227
|
-
status?: number,
|
1228
|
-
) =>
|
1229
|
-
jsonHandler<T, TReq, TRes, Payload>(async function (req, res) {
|
1230
|
-
validateFetchMode(req, res, ['same-origin'])
|
1231
|
-
validateFetchSite(req, res, ['same-origin'])
|
1232
|
-
validateSameOrigin(req, res, issuerOrigin)
|
1233
|
-
const referer = validateReferer(req, res, {
|
1234
|
-
origin: issuerOrigin,
|
1235
|
-
pathname: '/oauth/authorize',
|
1236
|
-
})
|
1237
|
-
|
1238
|
-
const requestUri = await requestUriSchema.parseAsync(
|
1239
|
-
referer.searchParams.get('request_uri'),
|
1240
|
-
{ path: ['query', 'request_uri'] },
|
1241
|
-
)
|
1242
|
-
|
1243
|
-
validateCsrfToken(
|
1244
|
-
req,
|
1245
|
-
res,
|
1246
|
-
req.headers['x-csrf-token'],
|
1247
|
-
csrfCookie(requestUri),
|
1248
|
-
)
|
1249
|
-
|
1250
|
-
const { deviceId, deviceMetadata } = await server.deviceManager.load(
|
1251
|
-
req,
|
1252
|
-
res,
|
1253
|
-
)
|
1254
|
-
|
1255
|
-
const inputRaw = await parseHttpRequest(req, ['json'])
|
1256
|
-
const input = await inputSchema.parseAsync(inputRaw, { path: ['body'] })
|
1257
|
-
|
1258
|
-
const context: ApiContext = { requestUri, deviceId, deviceMetadata }
|
1259
|
-
const payload = await buildJson.call(this, req, res, input, context)
|
1260
|
-
return { payload, status }
|
1261
|
-
})
|
1262
|
-
|
1263
|
-
const navigationHandler = <T, TReq extends Req, TRes extends Res>(
|
1264
|
-
handler: (this: T, req: TReq, res: TRes) => Awaitable<void>,
|
1265
|
-
): Handler<T, TReq, TRes> =>
|
1266
|
-
async function (req, res) {
|
1267
|
-
try {
|
1268
|
-
res.setHeader('Cache-Control', 'no-store')
|
1269
|
-
res.setHeader('Pragma', 'no-cache')
|
1270
|
-
|
1271
|
-
res.setHeader('Referrer-Policy', 'same-origin')
|
1272
|
-
|
1273
|
-
validateFetchMode(req, res, ['navigate'])
|
1274
|
-
validateFetchDest(req, res, ['document'])
|
1275
|
-
validateSameOrigin(req, res, issuerOrigin)
|
1276
|
-
|
1277
|
-
await handler.call(this, req, res)
|
1278
|
-
} catch (err) {
|
1279
|
-
onError?.(
|
1280
|
-
req,
|
1281
|
-
res,
|
1282
|
-
err,
|
1283
|
-
`Failed to handle navigation request to "${req.url}"`,
|
1284
|
-
)
|
1285
|
-
|
1286
|
-
if (!res.headersSent) {
|
1287
|
-
await server.outputManager.sendErrorPage(res, err, {
|
1288
|
-
preferredLocales: extractLocales(req),
|
1289
|
-
})
|
1290
|
-
}
|
1291
|
-
}
|
1292
|
-
}
|
1293
|
-
|
1294
|
-
// Simple GET requests fall under the category of "no-cors" request, meaning
|
1295
|
-
// that the browser will allow any cross-origin request, with credentials,
|
1296
|
-
// to be sent to the oauth server. The OAuth Server will, however:
|
1297
|
-
// 1) validate the request origin (see navigationHandler),
|
1298
|
-
// 2) validate the CSRF token,
|
1299
|
-
// 3) validate the referer,
|
1300
|
-
// 4) validate the sec-fetch-site header,
|
1301
|
-
// 4) validate the sec-fetch-mode header (see navigationHandler),
|
1302
|
-
// 5) validate the sec-fetch-dest header (see navigationHandler).
|
1303
|
-
// And will error (refuse to serve the request) if any of these checks fail.
|
1304
|
-
const sameOriginNavigationHandler = <
|
1305
|
-
T extends { url: URL },
|
1306
|
-
TReq extends Req,
|
1307
|
-
TRes extends Res,
|
1308
|
-
>(
|
1309
|
-
handler: (
|
1310
|
-
this: T,
|
1311
|
-
req: TReq,
|
1312
|
-
res: TRes,
|
1313
|
-
deviceInfo: DeviceInfo,
|
1314
|
-
) => Awaitable<void>,
|
1315
|
-
): Handler<T, TReq, TRes> =>
|
1316
|
-
navigationHandler(async function (req, res) {
|
1317
|
-
validateFetchSite(req, res, ['same-origin'])
|
1318
|
-
|
1319
|
-
const deviceInfo = await server.deviceManager.load(req, res)
|
1320
|
-
|
1321
|
-
return handler.call(this, req, res, deviceInfo)
|
1322
|
-
})
|
1323
|
-
|
1324
|
-
const authorizeRedirectNavigationHandler = <
|
1325
|
-
T extends { url: URL },
|
1326
|
-
TReq extends Req,
|
1327
|
-
TRes extends Res,
|
1328
|
-
>(
|
1329
|
-
handler: (
|
1330
|
-
this: T,
|
1331
|
-
req: TReq,
|
1332
|
-
res: TRes,
|
1333
|
-
context: ApiContext,
|
1334
|
-
) => Awaitable<AuthorizationResultRedirect>,
|
1335
|
-
): Handler<T, TReq, TRes> =>
|
1336
|
-
sameOriginNavigationHandler(async function (req, res, deviceInfo) {
|
1337
|
-
const referer = validateReferer(req, res, {
|
1338
|
-
origin: issuerOrigin,
|
1339
|
-
pathname: '/oauth/authorize',
|
1340
|
-
})
|
1341
|
-
|
1342
|
-
const requestUri = await requestUriSchema.parseAsync(
|
1343
|
-
referer.searchParams.get('request_uri'),
|
1344
|
-
)
|
1345
|
-
|
1346
|
-
const csrfToken = this.url.searchParams.get('csrf_token')
|
1347
|
-
const csrfCookieName = csrfCookie(requestUri)
|
1348
|
-
|
1349
|
-
// Next line will "clear" the CSRF token cookie, preventing replay of
|
1350
|
-
// this request (navigating "back" will result in an error).
|
1351
|
-
validateCsrfToken(req, res, csrfToken, csrfCookieName, true)
|
1352
|
-
|
1353
|
-
const context: ApiContext = { ...deviceInfo, requestUri }
|
1354
|
-
|
1355
|
-
const redirect = await handler.call(this, req, res, context)
|
1356
|
-
return sendAuthorizeRedirect(res, redirect)
|
1357
|
-
})
|
1358
|
-
|
1359
|
-
/**
|
1360
|
-
* Provides a better UX when a request is denied by redirecting to the
|
1361
|
-
* client with the error details. This will also log any error that caused
|
1362
|
-
* the access to be denied (such as system errors).
|
1363
|
-
*/
|
1364
|
-
const accessDeniedToRedirectCatcher = (
|
1365
|
-
req: Req,
|
1366
|
-
res: Res,
|
1367
|
-
err: unknown,
|
1368
|
-
): AuthorizationResultRedirect => {
|
1369
|
-
if (err instanceof AccessDeniedError && err.parameters.redirect_uri) {
|
1370
|
-
const { cause } = err
|
1371
|
-
if (cause) onError?.(req, res, cause, 'Access denied')
|
1372
|
-
|
1373
|
-
return {
|
1374
|
-
issuer: server.issuer,
|
1375
|
-
parameters: err.parameters,
|
1376
|
-
redirect: err.toJSON(),
|
1377
|
-
}
|
1378
|
-
}
|
1379
|
-
|
1380
|
-
throw err
|
896
|
+
// In addition to verifying the signature (through the verifier above), we
|
897
|
+
// also verify the tokenId is still valid using a database to fetch
|
898
|
+
// missing data from "light" token.
|
899
|
+
return this.tokenManager.verifyToken(
|
900
|
+
token,
|
901
|
+
tokenType,
|
902
|
+
tokenId,
|
903
|
+
dpopJkt,
|
904
|
+
verifyOptions,
|
905
|
+
)
|
1381
906
|
}
|
1382
907
|
|
1383
|
-
|
1384
|
-
|
1385
|
-
router.options('/.well-known/oauth-authorization-server', corsPreflight)
|
1386
|
-
router.get(
|
1387
|
-
'/.well-known/oauth-authorization-server',
|
1388
|
-
corsHeaders,
|
1389
|
-
cacheControlMiddleware(300),
|
1390
|
-
staticJsonMiddleware(server.metadata),
|
1391
|
-
)
|
1392
|
-
|
1393
|
-
router.options('/oauth/jwks', corsPreflight)
|
1394
|
-
router.get(
|
1395
|
-
'/oauth/jwks',
|
1396
|
-
corsHeaders,
|
1397
|
-
cacheControlMiddleware(300),
|
1398
|
-
staticJsonMiddleware(server.jwks),
|
1399
|
-
)
|
1400
|
-
|
1401
|
-
router.options('/oauth/par', corsPreflight)
|
1402
|
-
router.post(
|
1403
|
-
'/oauth/par',
|
1404
|
-
oauthHandler(async function (req, _res) {
|
1405
|
-
const payload = await parseHttpRequest(req, ['json', 'urlencoded'])
|
1406
|
-
|
1407
|
-
const credentials = await oauthClientCredentialsSchema
|
1408
|
-
.parseAsync(payload, { path: ['body'] })
|
1409
|
-
.catch(throwInvalidRequest)
|
1410
|
-
|
1411
|
-
const authorizationRequest = await oauthAuthorizationRequestParSchema
|
1412
|
-
.parseAsync(payload, { path: ['body'] })
|
1413
|
-
.catch(throwInvalidRequest)
|
1414
|
-
|
1415
|
-
const dpopJkt = await server.checkDpopProof(
|
1416
|
-
req.headers['dpop'],
|
1417
|
-
req.method!,
|
1418
|
-
this.url,
|
1419
|
-
)
|
1420
|
-
|
1421
|
-
return server.pushedAuthorizationRequest(
|
1422
|
-
credentials,
|
1423
|
-
authorizationRequest,
|
1424
|
-
dpopJkt,
|
1425
|
-
)
|
1426
|
-
}, 201),
|
1427
|
-
)
|
1428
|
-
// https://datatracker.ietf.org/doc/html/rfc9126#section-2.3
|
1429
|
-
// > If the request did not use the POST method, the authorization server
|
1430
|
-
// > responds with an HTTP 405 (Method Not Allowed) status code.
|
1431
|
-
router.all('/oauth/par', (req, res) => {
|
1432
|
-
res.writeHead(405).end()
|
1433
|
-
})
|
1434
|
-
|
1435
|
-
router.options('/oauth/token', corsPreflight)
|
1436
|
-
router.post(
|
1437
|
-
'/oauth/token',
|
1438
|
-
oauthHandler(async function (req, _res) {
|
1439
|
-
const payload = await parseHttpRequest(req, ['json', 'urlencoded'])
|
1440
|
-
|
1441
|
-
const clientMetadata =
|
1442
|
-
await server.deviceManager.getRequestMetadata(req)
|
1443
|
-
|
1444
|
-
const clientCredentials = await oauthClientCredentialsSchema
|
1445
|
-
.parseAsync(payload, { path: ['body'] })
|
1446
|
-
.catch(throwInvalidClient)
|
1447
|
-
|
1448
|
-
const tokenRequest = await oauthTokenRequestSchema
|
1449
|
-
.parseAsync(payload, { path: ['body'] })
|
1450
|
-
.catch(throwInvalidGrant)
|
1451
|
-
|
1452
|
-
const dpopJkt = await server.checkDpopProof(
|
1453
|
-
req.headers['dpop'],
|
1454
|
-
req.method!,
|
1455
|
-
this.url,
|
1456
|
-
)
|
1457
|
-
|
1458
|
-
return server.token(
|
1459
|
-
clientCredentials,
|
1460
|
-
clientMetadata,
|
1461
|
-
tokenRequest,
|
1462
|
-
dpopJkt,
|
1463
|
-
)
|
1464
|
-
}),
|
1465
|
-
)
|
1466
|
-
|
1467
|
-
router.options('/oauth/revoke', corsPreflight)
|
1468
|
-
router.post(
|
1469
|
-
'/oauth/revoke',
|
1470
|
-
oauthHandler(async function (req, res) {
|
1471
|
-
const payload = await parseHttpRequest(req, ['json', 'urlencoded'])
|
1472
|
-
|
1473
|
-
const tokenIdentification = await oauthTokenIdentificationSchema
|
1474
|
-
.parseAsync(payload, { path: ['body'] })
|
1475
|
-
.catch(throwInvalidRequest)
|
1476
|
-
|
1477
|
-
try {
|
1478
|
-
await server.revoke(tokenIdentification)
|
1479
|
-
} catch (err) {
|
1480
|
-
onError?.(req, res, err, 'Failed to revoke token')
|
1481
|
-
}
|
1482
|
-
|
1483
|
-
return {}
|
1484
|
-
}),
|
1485
|
-
)
|
1486
|
-
router.get(
|
1487
|
-
'/oauth/revoke',
|
1488
|
-
navigationHandler(async function (req, res) {
|
1489
|
-
const query = Object.fromEntries(this.url.searchParams)
|
1490
|
-
|
1491
|
-
const tokenIdentification = await oauthTokenIdentificationSchema
|
1492
|
-
.parseAsync(query, { path: ['query'] })
|
1493
|
-
.catch(throwInvalidRequest)
|
1494
|
-
|
1495
|
-
try {
|
1496
|
-
await server.revoke(tokenIdentification)
|
1497
|
-
} catch (err) {
|
1498
|
-
onError?.(req, res, err, 'Failed to revoke token')
|
1499
|
-
}
|
1500
|
-
|
1501
|
-
// Same as POST + redirect to callback URL
|
1502
|
-
// todo: generate JSONP response (if "callback" is provided)
|
1503
|
-
|
1504
|
-
throw new Error(
|
1505
|
-
'You are successfully logged out. Redirect not implemented',
|
1506
|
-
)
|
1507
|
-
}),
|
1508
|
-
)
|
1509
|
-
|
1510
|
-
router.options('/oauth/introspect', corsPreflight)
|
1511
|
-
router.post(
|
1512
|
-
'/oauth/introspect',
|
1513
|
-
oauthHandler(async function (req, _res) {
|
1514
|
-
const payload = await parseHttpRequest(req, ['json', 'urlencoded'])
|
1515
|
-
|
1516
|
-
const credentials = await oauthClientCredentialsSchema
|
1517
|
-
.parseAsync(payload, { path: ['body'] })
|
1518
|
-
.catch(throwInvalidRequest)
|
1519
|
-
|
1520
|
-
const tokenIdentification = await oauthTokenIdentificationSchema
|
1521
|
-
.parseAsync(payload, { path: ['body'] })
|
1522
|
-
.catch(throwInvalidRequest)
|
1523
|
-
|
1524
|
-
return server.introspect(credentials, tokenIdentification)
|
1525
|
-
}),
|
1526
|
-
)
|
1527
|
-
|
1528
|
-
//- Private authorization endpoints
|
1529
|
-
|
1530
|
-
router.use(authorizeAssetsMiddleware())
|
1531
|
-
|
1532
|
-
router.get(
|
1533
|
-
'/oauth/authorize',
|
1534
|
-
navigationHandler(async function (req, res) {
|
1535
|
-
validateFetchSite(req, res, ['cross-site', 'none'])
|
1536
|
-
|
1537
|
-
const query = Object.fromEntries(this.url.searchParams)
|
1538
|
-
|
1539
|
-
const clientCredentials = await oauthClientCredentialsSchema
|
1540
|
-
.parseAsync(query, { path: ['query'] })
|
1541
|
-
.catch(throwInvalidRequest)
|
1542
|
-
|
1543
|
-
if ('client_secret' in clientCredentials) {
|
1544
|
-
throw new InvalidRequestError('Client secret must not be provided')
|
1545
|
-
}
|
1546
|
-
|
1547
|
-
const authorizationRequest = await oauthAuthorizationRequestQuerySchema
|
1548
|
-
.parseAsync(query, { path: ['query'] })
|
1549
|
-
.catch(throwInvalidRequest)
|
1550
|
-
|
1551
|
-
const { deviceId, deviceMetadata } = await server.deviceManager.load(
|
1552
|
-
req,
|
1553
|
-
res,
|
1554
|
-
)
|
1555
|
-
|
1556
|
-
const result:
|
1557
|
-
| AuthorizationResultRedirect
|
1558
|
-
| AuthorizationResultAuthorize = await server
|
1559
|
-
.authorize(
|
1560
|
-
clientCredentials,
|
1561
|
-
authorizationRequest,
|
1562
|
-
deviceId,
|
1563
|
-
deviceMetadata,
|
1564
|
-
)
|
1565
|
-
.catch((err) => accessDeniedToRedirectCatcher(req, res, err))
|
1566
|
-
|
1567
|
-
if ('redirect' in result) {
|
1568
|
-
return sendAuthorizeRedirect(res, result)
|
1569
|
-
} else {
|
1570
|
-
await setupCsrfToken(req, res, csrfCookie(result.authorize.uri))
|
1571
|
-
return server.outputManager.sendAuthorizePage(res, result, {
|
1572
|
-
preferredLocales: extractLocales(req),
|
1573
|
-
})
|
1574
|
-
}
|
1575
|
-
}),
|
1576
|
-
)
|
1577
|
-
|
1578
|
-
router.post(
|
1579
|
-
'/oauth/authorize/verify-handle-availability',
|
1580
|
-
apiHandler(
|
1581
|
-
z.object({ handle: handleSchema }).strict(),
|
1582
|
-
async function (req, res, data) {
|
1583
|
-
await server.accountManager.verifyHandleAvailability(data.handle)
|
1584
|
-
return { available: true }
|
1585
|
-
},
|
1586
|
-
),
|
1587
|
-
)
|
1588
|
-
|
1589
|
-
router.post(
|
1590
|
-
'/oauth/authorize/sign-up',
|
1591
|
-
apiHandler(signUpInputSchema, async function (req, res, data, ctx) {
|
1592
|
-
return server.signUp(ctx, data)
|
1593
|
-
}),
|
1594
|
-
)
|
1595
|
-
|
1596
|
-
router.post(
|
1597
|
-
'/oauth/authorize/sign-in',
|
1598
|
-
apiHandler(signInDataSchema, async function (req, res, data, ctx) {
|
1599
|
-
return server.signIn(ctx, data)
|
1600
|
-
}),
|
1601
|
-
)
|
1602
|
-
|
1603
|
-
router.post(
|
1604
|
-
'/oauth/authorize/reset-password-request',
|
1605
|
-
apiHandler(
|
1606
|
-
resetPasswordRequestDataSchema,
|
1607
|
-
async function (req, res, data) {
|
1608
|
-
await server.accountManager.resetPasswordRequest(data)
|
1609
|
-
return { success: true }
|
1610
|
-
},
|
1611
|
-
),
|
1612
|
-
)
|
1613
|
-
|
1614
|
-
router.post(
|
1615
|
-
'/oauth/authorize/reset-password-confirm',
|
1616
|
-
apiHandler(
|
1617
|
-
resetPasswordConfirmDataSchema,
|
1618
|
-
async function (req, res, data) {
|
1619
|
-
await server.accountManager.resetPasswordConfirm(data)
|
1620
|
-
return { success: true }
|
1621
|
-
},
|
1622
|
-
),
|
1623
|
-
)
|
1624
|
-
|
1625
|
-
router.get(
|
1626
|
-
'/oauth/authorize/accept',
|
1627
|
-
authorizeRedirectNavigationHandler(async function (req, res, ctx) {
|
1628
|
-
const sub = this.url.searchParams.get('account_sub')
|
1629
|
-
if (!sub) throw new InvalidRequestError('Account sub not provided')
|
1630
|
-
|
1631
|
-
return server
|
1632
|
-
.acceptRequest(ctx, sub)
|
1633
|
-
.catch((err) => accessDeniedToRedirectCatcher(req, res, err))
|
1634
|
-
}),
|
1635
|
-
)
|
1636
|
-
|
1637
|
-
router.get(
|
1638
|
-
'/oauth/authorize/reject',
|
1639
|
-
authorizeRedirectNavigationHandler(async function (req, res, ctx) {
|
1640
|
-
return server
|
1641
|
-
.rejectRequest(ctx)
|
1642
|
-
.catch((err) => accessDeniedToRedirectCatcher(req, res, err))
|
1643
|
-
}),
|
1644
|
-
)
|
1645
|
-
|
1646
|
-
return router
|
1647
|
-
}
|
1648
|
-
}
|
1649
|
-
|
1650
|
-
function throwInvalidGrant(err: unknown): never {
|
1651
|
-
throw new InvalidGrantError(
|
1652
|
-
extractZodErrorMessage(err) || 'Invalid grant',
|
1653
|
-
err,
|
1654
|
-
)
|
1655
|
-
}
|
1656
|
-
|
1657
|
-
function throwInvalidClient(err: unknown): never {
|
1658
|
-
throw new InvalidClientError(
|
1659
|
-
extractZodErrorMessage(err) || 'Client authentication failed',
|
1660
|
-
err,
|
1661
|
-
)
|
1662
|
-
}
|
1663
|
-
|
1664
|
-
function throwInvalidRequest(err: unknown): never {
|
1665
|
-
throw new InvalidRequestError(
|
1666
|
-
extractZodErrorMessage(err) || 'Input validation error',
|
1667
|
-
err,
|
1668
|
-
)
|
1669
|
-
}
|
1670
|
-
|
1671
|
-
function extractZodErrorMessage(err: unknown): string | undefined {
|
1672
|
-
if (err instanceof ZodError) {
|
1673
|
-
const issue = err.issues[0]
|
1674
|
-
if (issue?.path.length) {
|
1675
|
-
// "part" will typically be "body" or "query"
|
1676
|
-
const [part, ...path] = issue.path
|
1677
|
-
return `Validation of "${path.join('.')}" ${part} parameter failed: ${issue.message}`
|
1678
|
-
}
|
908
|
+
// Fool-proof
|
909
|
+
throw new Error('Invalid access token mode')
|
1679
910
|
}
|
1680
|
-
|
1681
|
-
return undefined
|
1682
911
|
}
|