@atproto/oauth-provider 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.linguirc +57 -0
- package/CHANGELOG.md +21 -0
- package/dist/account/account-manager.d.ts +17 -3
- package/dist/account/account-manager.d.ts.map +1 -1
- package/dist/account/account-manager.js +102 -8
- package/dist/account/account-manager.js.map +1 -1
- package/dist/account/account-store.d.ts +81 -15
- package/dist/account/account-store.d.ts.map +1 -1
- package/dist/account/account-store.js +70 -19
- package/dist/account/account-store.js.map +1 -1
- package/dist/account/sign-in-data.d.ts +28 -0
- package/dist/account/sign-in-data.d.ts.map +1 -0
- package/dist/account/sign-in-data.js +16 -0
- package/dist/account/sign-in-data.js.map +1 -0
- package/dist/account/sign-up-data.d.ts +26 -0
- package/dist/account/sign-up-data.d.ts.map +1 -0
- package/dist/account/sign-up-data.js +11 -0
- package/dist/account/sign-up-data.js.map +1 -0
- package/dist/assets/app/bundle-manifest.json +598 -6
- package/dist/assets/app/index-ItwwtJ8r.js +36 -0
- package/dist/assets/app/index-ItwwtJ8r.js.map +1 -0
- package/dist/assets/app/main-B_dNxQo_.js +4 -0
- package/dist/assets/app/main-B_dNxQo_.js.map +1 -0
- package/dist/assets/app/main-CSatvmRR.css +3 -0
- package/dist/assets/app/main-CSatvmRR.js +306 -0
- package/dist/assets/app/main-CSatvmRR.js.map +1 -0
- package/dist/assets/app/messages-BQeltXSF.js +4 -0
- package/dist/assets/app/messages-BQeltXSF.js.map +1 -0
- package/dist/assets/app/messages-BQkEhfjg.js +4 -0
- package/dist/assets/app/messages-BQkEhfjg.js.map +1 -0
- package/dist/assets/app/messages-BUjKj_UJ.js +4 -0
- package/dist/assets/app/messages-BUjKj_UJ.js.map +1 -0
- package/dist/assets/app/messages-BWIQa8fO.js +4 -0
- package/dist/assets/app/messages-BWIQa8fO.js.map +1 -0
- package/dist/assets/app/messages-BaNVb0bp.js +4 -0
- package/dist/assets/app/messages-BaNVb0bp.js.map +1 -0
- package/dist/assets/app/messages-BaizVXcF.js +4 -0
- package/dist/assets/app/messages-BaizVXcF.js.map +1 -0
- package/dist/assets/app/messages-BfoClA1Y.js +4 -0
- package/dist/assets/app/messages-BfoClA1Y.js.map +1 -0
- package/dist/assets/app/messages-BsKGDZnC.js +4 -0
- package/dist/assets/app/messages-BsKGDZnC.js.map +1 -0
- package/dist/assets/app/messages-Bu-TJhml.js +4 -0
- package/dist/assets/app/messages-Bu-TJhml.js.map +1 -0
- package/dist/assets/app/messages-BvOKnBQk.js +4 -0
- package/dist/assets/app/messages-BvOKnBQk.js.map +1 -0
- package/dist/assets/app/messages-BxDzCiWz.js +4 -0
- package/dist/assets/app/messages-BxDzCiWz.js.map +1 -0
- package/dist/assets/app/messages-CDgFOy4S.js +4 -0
- package/dist/assets/app/messages-CDgFOy4S.js.map +1 -0
- package/dist/assets/app/messages-CLbTz0o9.js +4 -0
- package/dist/assets/app/messages-CLbTz0o9.js.map +1 -0
- package/dist/assets/app/messages-CNwSh0t7.js +4 -0
- package/dist/assets/app/messages-CNwSh0t7.js.map +1 -0
- package/dist/assets/app/messages-CSMNJ6P8.js +4 -0
- package/dist/assets/app/messages-CSMNJ6P8.js.map +1 -0
- package/dist/assets/app/messages-CZQUw3mp.js +4 -0
- package/dist/assets/app/messages-CZQUw3mp.js.map +1 -0
- package/dist/assets/app/messages-CZT41oVp.js +4 -0
- package/dist/assets/app/messages-CZT41oVp.js.map +1 -0
- package/dist/assets/app/messages-C_b-d3t8.js +4 -0
- package/dist/assets/app/messages-C_b-d3t8.js.map +1 -0
- package/dist/assets/app/messages-C_u3MTc2.js +4 -0
- package/dist/assets/app/messages-C_u3MTc2.js.map +1 -0
- package/dist/assets/app/messages-Cn8nHZic.js +4 -0
- package/dist/assets/app/messages-Cn8nHZic.js.map +1 -0
- package/dist/assets/app/messages-CtDywJUm.js +4 -0
- package/dist/assets/app/messages-CtDywJUm.js.map +1 -0
- package/dist/assets/app/messages-CurtIjBF.js +4 -0
- package/dist/assets/app/messages-CurtIjBF.js.map +1 -0
- package/dist/assets/app/messages-Cv6zIbaP.js +4 -0
- package/dist/assets/app/messages-Cv6zIbaP.js.map +1 -0
- package/dist/assets/app/messages-D1eLQuPE.js +4 -0
- package/dist/assets/app/messages-D1eLQuPE.js.map +1 -0
- package/dist/assets/app/messages-D8vHEaYW.js +4 -0
- package/dist/assets/app/messages-D8vHEaYW.js.map +1 -0
- package/dist/assets/app/messages-DJ1Q4GeC.js +4 -0
- package/dist/assets/app/messages-DJ1Q4GeC.js.map +1 -0
- package/dist/assets/app/messages-DRL3exqd.js +4 -0
- package/dist/assets/app/messages-DRL3exqd.js.map +1 -0
- package/dist/assets/app/messages-DWLPQRTp.js +4 -0
- package/dist/assets/app/messages-DWLPQRTp.js.map +1 -0
- package/dist/assets/app/messages-DjVaE9YE.js +4 -0
- package/dist/assets/app/messages-DjVaE9YE.js.map +1 -0
- package/dist/assets/app/messages-DqpMfFJR.js +4 -0
- package/dist/assets/app/messages-DqpMfFJR.js.map +1 -0
- package/dist/assets/app/messages-ETjhJBEN.js +4 -0
- package/dist/assets/app/messages-ETjhJBEN.js.map +1 -0
- package/dist/assets/app/messages-EUKrgrGn.js +4 -0
- package/dist/assets/app/messages-EUKrgrGn.js.map +1 -0
- package/dist/assets/app/messages-QQrOUcPW.js +4 -0
- package/dist/assets/app/messages-QQrOUcPW.js.map +1 -0
- package/dist/assets/app/messages-e2QGqFL6.js +4 -0
- package/dist/assets/app/messages-e2QGqFL6.js.map +1 -0
- package/dist/assets/app/messages-p61py7gD.js +4 -0
- package/dist/assets/app/messages-p61py7gD.js.map +1 -0
- package/dist/assets/asset.d.ts +1 -0
- package/dist/assets/asset.d.ts.map +1 -1
- package/dist/assets/assets-middleware.d.ts.map +1 -1
- package/dist/assets/assets-middleware.js +12 -7
- package/dist/assets/assets-middleware.js.map +1 -1
- package/dist/assets/index.d.ts +3 -2
- package/dist/assets/index.d.ts.map +1 -1
- package/dist/assets/index.js +13 -1
- package/dist/assets/index.js.map +1 -1
- package/dist/client/client-store.d.ts +3 -3
- package/dist/client/client-store.d.ts.map +1 -1
- package/dist/client/client-store.js +6 -5
- package/dist/client/client-store.js.map +1 -1
- package/dist/device/device-manager.d.ts +9 -8
- package/dist/device/device-manager.d.ts.map +1 -1
- package/dist/device/device-manager.js.map +1 -1
- package/dist/device/device-store.d.ts +3 -3
- package/dist/device/device-store.d.ts.map +1 -1
- package/dist/device/device-store.js +10 -9
- package/dist/device/device-store.js.map +1 -1
- package/dist/dpop/dpop-manager.d.ts +15 -7
- package/dist/dpop/dpop-manager.d.ts.map +1 -1
- package/dist/dpop/dpop-manager.js +17 -3
- package/dist/dpop/dpop-manager.js.map +1 -1
- package/dist/dpop/dpop-nonce.d.ts +11 -5
- package/dist/dpop/dpop-nonce.d.ts.map +1 -1
- package/dist/dpop/dpop-nonce.js +47 -38
- package/dist/dpop/dpop-nonce.js.map +1 -1
- package/dist/errors/handle-unavailable-error.d.ts +11 -0
- package/dist/errors/handle-unavailable-error.d.ts.map +1 -0
- package/dist/errors/handle-unavailable-error.js +19 -0
- package/dist/errors/handle-unavailable-error.js.map +1 -0
- package/dist/errors/invalid-request-error.d.ts +6 -8
- package/dist/errors/invalid-request-error.d.ts.map +1 -1
- package/dist/errors/invalid-request-error.js +10 -8
- package/dist/errors/invalid-request-error.js.map +1 -1
- package/dist/lib/csp/index.d.ts +18 -0
- package/dist/lib/csp/index.d.ts.map +1 -0
- package/dist/lib/csp/index.js +72 -0
- package/dist/lib/csp/index.js.map +1 -0
- package/dist/lib/hcaptcha.d.ts +177 -0
- package/dist/lib/hcaptcha.d.ts.map +1 -0
- package/dist/lib/hcaptcha.js +155 -0
- package/dist/lib/hcaptcha.js.map +1 -0
- package/dist/lib/html/build-document.d.ts +11 -3
- package/dist/lib/html/build-document.d.ts.map +1 -1
- package/dist/lib/html/build-document.js +51 -15
- package/dist/lib/html/build-document.js.map +1 -1
- package/dist/lib/http/middleware.d.ts.map +1 -1
- package/dist/lib/http/middleware.js +4 -1
- package/dist/lib/http/middleware.js.map +1 -1
- package/dist/lib/http/request.d.ts +5 -2
- package/dist/lib/http/request.d.ts.map +1 -1
- package/dist/lib/http/request.js +16 -1
- package/dist/lib/http/request.js.map +1 -1
- package/dist/lib/http/response.d.ts +4 -2
- package/dist/lib/http/response.d.ts.map +1 -1
- package/dist/lib/http/response.js +23 -5
- package/dist/lib/http/response.js.map +1 -1
- package/dist/lib/locale.d.ts +15 -0
- package/dist/lib/locale.d.ts.map +1 -0
- package/dist/lib/locale.js +17 -0
- package/dist/lib/locale.js.map +1 -0
- package/dist/lib/util/function.d.ts +2 -2
- package/dist/lib/util/function.d.ts.map +1 -1
- package/dist/lib/util/function.js.map +1 -1
- package/dist/lib/util/type.d.ts +88 -1
- package/dist/lib/util/type.d.ts.map +1 -1
- package/dist/lib/util/type.js +41 -0
- package/dist/lib/util/type.js.map +1 -1
- package/dist/metadata/build-metadata.d.ts +2 -2
- package/dist/metadata/build-metadata.d.ts.map +1 -1
- package/dist/metadata/build-metadata.js.map +1 -1
- package/dist/oauth-errors.d.ts +1 -0
- package/dist/oauth-errors.d.ts.map +1 -1
- package/dist/oauth-errors.js +3 -1
- package/dist/oauth-errors.js.map +1 -1
- package/dist/oauth-hooks.d.ts +60 -3
- package/dist/oauth-hooks.d.ts.map +1 -1
- package/dist/oauth-hooks.js +3 -3
- package/dist/oauth-hooks.js.map +1 -1
- package/dist/oauth-provider.d.ts +23 -18
- package/dist/oauth-provider.d.ts.map +1 -1
- package/dist/oauth-provider.js +207 -204
- package/dist/oauth-provider.js.map +1 -1
- package/dist/oauth-verifier.d.ts +1 -1
- package/dist/oauth-verifier.d.ts.map +1 -1
- package/dist/oauth-verifier.js +2 -1
- package/dist/oauth-verifier.js.map +1 -1
- package/dist/output/build-authorize-data.d.ts +0 -1
- package/dist/output/build-authorize-data.d.ts.map +1 -1
- package/dist/output/build-authorize-data.js +0 -1
- package/dist/output/build-authorize-data.js.map +1 -1
- package/dist/output/build-customization-data.d.ts +232 -0
- package/dist/output/build-customization-data.d.ts.map +1 -0
- package/dist/output/build-customization-data.js +145 -0
- package/dist/output/build-customization-data.js.map +1 -0
- package/dist/output/output-manager.d.ts +16 -9
- package/dist/output/output-manager.d.ts.map +1 -1
- package/dist/output/output-manager.js +78 -42
- package/dist/output/output-manager.js.map +1 -1
- package/dist/output/send-authorize-redirect.d.ts +9 -6
- package/dist/output/send-authorize-redirect.d.ts.map +1 -1
- package/dist/output/send-authorize-redirect.js +20 -14
- package/dist/output/send-authorize-redirect.js.map +1 -1
- package/dist/output/send-web-page.d.ts +7 -2
- package/dist/output/send-web-page.d.ts.map +1 -1
- package/dist/output/send-web-page.js +37 -21
- package/dist/output/send-web-page.js.map +1 -1
- package/dist/request/request-manager.d.ts +1 -1
- package/dist/request/request-manager.d.ts.map +1 -1
- package/dist/request/request-manager.js +4 -4
- package/dist/request/request-manager.js.map +1 -1
- package/dist/request/request-store.d.ts +3 -3
- package/dist/request/request-store.d.ts.map +1 -1
- package/dist/request/request-store.js +11 -10
- package/dist/request/request-store.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 +13 -12
- package/dist/token/token-store.js.map +1 -1
- package/package.json +43 -20
- package/rollup.config.js +61 -17
- package/src/account/account-manager.ts +159 -8
- package/src/account/account-store.ts +127 -32
- package/src/account/sign-in-data.ts +15 -0
- package/src/account/sign-up-data.ts +11 -0
- package/src/assets/app/app.tsx +31 -16
- package/src/assets/app/backend-data.ts +15 -60
- package/src/assets/app/backend-types.ts +66 -0
- package/src/assets/app/components/forms/button-toggle-visibility.tsx +43 -0
- package/src/assets/app/components/forms/button.tsx +60 -0
- package/src/assets/app/components/forms/fieldset.tsx +55 -0
- package/src/assets/app/components/forms/form-card-async.tsx +103 -0
- package/src/assets/app/components/forms/form-card.tsx +49 -0
- package/src/assets/app/components/forms/input-checkbox.tsx +73 -0
- package/src/assets/app/components/forms/input-container.tsx +107 -0
- package/src/assets/app/components/forms/input-email-address.tsx +66 -0
- package/src/assets/app/components/forms/input-new-password.tsx +62 -0
- package/src/assets/app/components/forms/input-password.tsx +88 -0
- package/src/assets/app/components/forms/input-text.tsx +76 -0
- package/src/assets/app/components/forms/input-token.tsx +94 -0
- package/src/assets/app/components/forms/wizard-card.tsx +116 -0
- package/src/assets/app/components/layouts/layout-title-page.tsx +77 -0
- package/src/assets/app/components/layouts/layout-welcome.tsx +73 -0
- package/src/assets/app/components/utils/account-identifier.tsx +23 -0
- package/src/assets/app/components/utils/account-image.tsx +33 -0
- package/src/assets/app/components/utils/admonition.tsx +52 -0
- package/src/assets/app/components/utils/client-name.tsx +45 -0
- package/src/assets/app/components/utils/error-card.tsx +93 -0
- package/src/assets/app/components/utils/error-message.tsx +62 -0
- package/src/assets/app/components/utils/help-card.tsx +46 -0
- package/src/assets/app/components/utils/icons.tsx +88 -0
- package/src/assets/app/components/utils/link-anchor.tsx +28 -0
- package/src/assets/app/components/utils/link-title.tsx +26 -0
- package/src/assets/app/components/utils/multi-lang-string.tsx +56 -0
- package/src/assets/app/components/utils/password-strength-label.tsx +37 -0
- package/src/assets/app/components/utils/password-strength-meter.tsx +58 -0
- package/src/assets/app/components/{url-viewer.tsx → utils/url-viewer.tsx} +9 -6
- package/src/assets/app/hooks/use-api.ts +128 -55
- package/src/assets/app/hooks/use-async-action.ts +120 -0
- package/src/assets/app/hooks/use-browser-color-scheme.ts +31 -0
- package/src/assets/app/hooks/use-csrf-token.ts +1 -1
- package/src/assets/app/hooks/use-random-string.ts +37 -0
- package/src/assets/app/hooks/use-stepper.ts +87 -0
- package/src/assets/app/index.html +182 -0
- package/src/assets/app/lib/api.ts +248 -79
- package/src/assets/app/lib/clsx.ts +5 -8
- package/src/assets/app/lib/json-client.ts +94 -0
- package/src/assets/app/lib/password.ts +98 -0
- package/src/assets/app/lib/ref.ts +17 -0
- package/src/assets/app/locales/an/messages.po +492 -0
- package/src/assets/app/locales/ast/messages.po +492 -0
- package/src/assets/app/locales/ca/messages.po +492 -0
- package/src/assets/app/locales/da/messages.po +492 -0
- package/src/assets/app/locales/de/messages.po +492 -0
- package/src/assets/app/locales/el/messages.po +492 -0
- package/src/assets/app/locales/en/messages.po +492 -0
- package/src/assets/app/locales/en-GB/messages.po +492 -0
- package/src/assets/app/locales/es/messages.po +492 -0
- package/src/assets/app/locales/eu/messages.po +492 -0
- package/src/assets/app/locales/fi/messages.po +492 -0
- package/src/assets/app/locales/fr/messages.po +492 -0
- package/src/assets/app/locales/ga/messages.po +492 -0
- package/src/assets/app/locales/gl/messages.po +492 -0
- package/src/assets/app/locales/hi/messages.po +492 -0
- package/src/assets/app/locales/hu/messages.po +492 -0
- package/src/assets/app/locales/ia/messages.po +492 -0
- package/src/assets/app/locales/id/messages.po +492 -0
- package/src/assets/app/locales/it/messages.po +492 -0
- package/src/assets/app/locales/ja/messages.po +492 -0
- package/src/assets/app/locales/km/messages.po +492 -0
- package/src/assets/app/locales/ko/messages.po +492 -0
- package/src/assets/app/locales/load.ts +8 -0
- package/src/assets/app/locales/locale-context.ts +19 -0
- package/src/assets/app/locales/locale-provider.tsx +112 -0
- package/src/assets/app/locales/locale-selector.tsx +58 -0
- package/src/assets/app/locales/locales.ts +168 -0
- package/src/assets/app/locales/ne/messages.po +492 -0
- package/src/assets/app/locales/nl/messages.po +492 -0
- package/src/assets/app/locales/pl/messages.po +492 -0
- package/src/assets/app/locales/pt-BR/messages.po +492 -0
- package/src/assets/app/locales/ro/messages.po +492 -0
- package/src/assets/app/locales/ru/messages.po +492 -0
- package/src/assets/app/locales/sv/messages.po +492 -0
- package/src/assets/app/locales/th/messages.po +492 -0
- package/src/assets/app/locales/tr/messages.po +492 -0
- package/src/assets/app/locales/uk/messages.po +492 -0
- package/src/assets/app/locales/vi/messages.po +492 -0
- package/src/assets/app/locales/zh-CN/messages.po +492 -0
- package/src/assets/app/locales/zh-HK/messages.po +492 -0
- package/src/assets/app/locales/zh-TW/messages.po +492 -0
- package/src/assets/app/main.css +23 -2
- package/src/assets/app/main.tsx +24 -8
- package/src/assets/app/views/authorize/accept/accept-form.tsx +150 -0
- package/src/assets/app/views/authorize/accept/accept-view.tsx +70 -0
- package/src/assets/app/views/authorize/authorize-view.tsx +180 -0
- package/src/assets/app/views/authorize/reset-password/reset-password-confirm-form.tsx +88 -0
- package/src/assets/app/views/authorize/reset-password/reset-password-request-form.tsx +80 -0
- package/src/assets/app/views/authorize/reset-password/reset-password-view.tsx +127 -0
- package/src/assets/app/views/authorize/sign-in/sign-in-form.tsx +244 -0
- package/src/assets/app/views/authorize/sign-in/sign-in-picker.tsx +116 -0
- package/src/assets/app/views/authorize/sign-in/sign-in-view.tsx +145 -0
- package/src/assets/app/views/authorize/sign-up/sign-up-account-form.tsx +140 -0
- package/src/assets/app/views/authorize/sign-up/sign-up-disclaimer.tsx +51 -0
- package/src/assets/app/views/authorize/sign-up/sign-up-handle-form.tsx +289 -0
- package/src/assets/app/views/authorize/sign-up/sign-up-hcaptcha-form.tsx +108 -0
- package/src/assets/app/views/authorize/sign-up/sign-up-view.tsx +158 -0
- package/src/assets/app/views/authorize/welcome/welcome-view.tsx +56 -0
- package/src/assets/app/views/error/error-view.tsx +31 -0
- package/src/assets/asset.ts +1 -0
- package/src/assets/assets-middleware.ts +13 -8
- package/src/assets/index.ts +15 -2
- package/src/client/client-store.ts +10 -12
- package/src/device/device-manager.ts +8 -12
- package/src/device/device-store.ts +9 -15
- package/src/dpop/dpop-manager.ts +20 -8
- package/src/dpop/dpop-nonce.ts +58 -40
- package/src/errors/handle-unavailable-error.ts +18 -0
- package/src/errors/invalid-request-error.ts +10 -8
- package/src/lib/csp/index.ts +98 -0
- package/src/lib/hcaptcha.ts +182 -0
- package/src/lib/html/build-document.ts +60 -16
- package/src/lib/http/middleware.ts +4 -3
- package/src/lib/http/request.ts +31 -1
- package/src/lib/http/response.ts +22 -9
- package/src/lib/locale.ts +21 -0
- package/src/lib/util/function.ts +0 -3
- package/src/lib/util/type.ts +130 -1
- package/src/metadata/build-metadata.ts +2 -1
- package/src/oauth-errors.ts +1 -0
- package/src/oauth-hooks.ts +69 -3
- package/src/oauth-provider.ts +399 -307
- package/src/oauth-verifier.ts +3 -1
- package/src/output/build-authorize-data.ts +1 -3
- package/src/output/build-customization-data.ts +189 -0
- package/src/output/output-manager.ts +111 -48
- package/src/output/send-authorize-redirect.ts +43 -36
- package/src/output/send-web-page.ts +40 -26
- package/src/request/request-manager.ts +4 -4
- package/src/request/request-store.ts +12 -16
- package/src/token/token-store.ts +14 -18
- package/tailwind.config.js +5 -0
- package/tsconfig.backend.tsbuildinfo +1 -1
- package/tsconfig.frontend.tsbuildinfo +1 -1
- package/tsconfig.tools.tsbuildinfo +1 -1
- package/vite.config.mjs +16 -0
- package/.postcssrc.yml +0 -3
- package/dist/assets/app/main.css +0 -3
- package/dist/assets/app/main.js +0 -20
- package/dist/assets/app/main.js.map +0 -1
- package/dist/output/customization.d.ts +0 -27
- package/dist/output/customization.d.ts.map +0 -1
- package/dist/output/customization.js +0 -88
- package/dist/output/customization.js.map +0 -1
- package/src/assets/app/components/accept-form.tsx +0 -137
- package/src/assets/app/components/account-identifier.tsx +0 -18
- package/src/assets/app/components/account-picker.tsx +0 -127
- package/src/assets/app/components/button.tsx +0 -34
- package/src/assets/app/components/client-name.tsx +0 -37
- package/src/assets/app/components/fieldset.tsx +0 -26
- package/src/assets/app/components/form-card.tsx +0 -47
- package/src/assets/app/components/help-card.tsx +0 -42
- package/src/assets/app/components/icons/alert-icon.tsx +0 -5
- package/src/assets/app/components/icons/at-symbol-icon.tsx +0 -5
- package/src/assets/app/components/icons/caret-right-icon.tsx +0 -5
- package/src/assets/app/components/icons/lock-icon.tsx +0 -5
- package/src/assets/app/components/icons/token-icon.tsx +0 -5
- package/src/assets/app/components/icons/util.tsx +0 -17
- package/src/assets/app/components/info-card.tsx +0 -45
- package/src/assets/app/components/input-checkbox.tsx +0 -47
- package/src/assets/app/components/input-container.tsx +0 -37
- package/src/assets/app/components/input-layout.tsx +0 -47
- package/src/assets/app/components/input-text.tsx +0 -69
- package/src/assets/app/components/layout-title-page.tsx +0 -60
- package/src/assets/app/components/layout-welcome.tsx +0 -74
- package/src/assets/app/components/sign-in-form.tsx +0 -337
- package/src/assets/app/components/sign-up-account-form.tsx +0 -194
- package/src/assets/app/components/sign-up-disclaimer.tsx +0 -44
- package/src/assets/app/views/accept-view.tsx +0 -55
- package/src/assets/app/views/authorize-view.tsx +0 -106
- package/src/assets/app/views/error-view.tsx +0 -36
- package/src/assets/app/views/sign-in-view.tsx +0 -111
- package/src/assets/app/views/sign-up-view.tsx +0 -86
- package/src/assets/app/views/welcome-view.tsx +0 -54
- package/src/output/customization.ts +0 -118
package/src/oauth-provider.ts
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from 'node:http'
|
2
|
-
import { mediaType } from '@hapi/accept'
|
3
2
|
import createHttpError from 'http-errors'
|
4
3
|
import type { Redis, RedisOptions } from 'ioredis'
|
5
4
|
import { ZodError, z } from 'zod'
|
@@ -39,14 +38,16 @@ import { AccountManager } from './account/account-manager.js'
|
|
39
38
|
import {
|
40
39
|
AccountStore,
|
41
40
|
DeviceAccountInfo,
|
42
|
-
SignInCredentials,
|
43
41
|
asAccountStore,
|
44
|
-
|
42
|
+
handleSchema,
|
43
|
+
resetPasswordConfirmDataSchema,
|
44
|
+
resetPasswordRequestDataSchema,
|
45
45
|
} from './account/account-store.js'
|
46
46
|
import { Account } from './account/account.js'
|
47
|
+
import { signInDataSchema } from './account/sign-in-data.js'
|
48
|
+
import { signUpDataSchema } from './account/sign-up-data.js'
|
47
49
|
import { authorizeAssetsMiddleware } from './assets/assets-middleware.js'
|
48
50
|
import { ClientAuth, authJwkThumbprint } from './client/client-auth.js'
|
49
|
-
import { ClientId, clientIdSchema } from './client/client-id.js'
|
50
51
|
import {
|
51
52
|
ClientManager,
|
52
53
|
LoopbackMetadataGetter,
|
@@ -55,7 +56,12 @@ import { ClientStore, ifClientStore } from './client/client-store.js'
|
|
55
56
|
import { Client } from './client/client.js'
|
56
57
|
import { AUTHENTICATION_MAX_AGE, TOKEN_MAX_AGE } from './constants.js'
|
57
58
|
import { DeviceId } from './device/device-id.js'
|
58
|
-
import {
|
59
|
+
import {
|
60
|
+
DeviceInfo,
|
61
|
+
DeviceManager,
|
62
|
+
DeviceManagerOptions,
|
63
|
+
deviceManagerOptionsSchema,
|
64
|
+
} from './device/device-manager.js'
|
59
65
|
import { DeviceStore, asDeviceStore } from './device/device-store.js'
|
60
66
|
import { AccessDeniedError } from './errors/access-denied-error.js'
|
61
67
|
import { AccountSelectionRequiredError } from './errors/account-selection-required-error.js'
|
@@ -65,13 +71,14 @@ import { InvalidGrantError } from './errors/invalid-grant-error.js'
|
|
65
71
|
import { InvalidParametersError } from './errors/invalid-parameters-error.js'
|
66
72
|
import { InvalidRequestError } from './errors/invalid-request-error.js'
|
67
73
|
import { LoginRequiredError } from './errors/login-required-error.js'
|
68
|
-
import { OAuthError } from './errors/oauth-error.js'
|
69
74
|
import { UnauthorizedClientError } from './errors/unauthorized-client-error.js'
|
70
75
|
import { WWWAuthenticateError } from './errors/www-authenticate-error.js'
|
76
|
+
import { HcaptchaConfig } from './lib/hcaptcha.js'
|
71
77
|
import {
|
72
78
|
Handler,
|
73
79
|
Middleware,
|
74
80
|
Router,
|
81
|
+
cacheControlMiddleware,
|
75
82
|
combineMiddlewares,
|
76
83
|
parseHttpRequest,
|
77
84
|
setupCsrfToken,
|
@@ -84,18 +91,26 @@ import {
|
|
84
91
|
validateSameOrigin,
|
85
92
|
writeJson,
|
86
93
|
} from './lib/http/index.js'
|
87
|
-
import {
|
94
|
+
import {
|
95
|
+
RequestMetadata,
|
96
|
+
extractLocales,
|
97
|
+
negotiateResponseContent as negotiateContent,
|
98
|
+
} from './lib/http/request.js'
|
88
99
|
import { dateToEpoch, dateToRelativeSeconds } from './lib/util/date.js'
|
89
|
-
import { Override } from './lib/util/type.js'
|
100
|
+
import { Awaitable, Override } from './lib/util/type.js'
|
90
101
|
import { CustomMetadata, buildMetadata } from './metadata/build-metadata.js'
|
91
|
-
import { OAuthHooks } from './oauth-hooks.js'
|
102
|
+
import { OAuthHooks, SignInData, SignUpData } from './oauth-hooks.js'
|
92
103
|
import { OAuthVerifier, OAuthVerifierOptions } from './oauth-verifier.js'
|
93
104
|
import { AuthorizationResultAuthorize } from './output/build-authorize-data.js'
|
105
|
+
import {
|
106
|
+
BrandingConfig,
|
107
|
+
Customization,
|
108
|
+
customizationSchema,
|
109
|
+
} from './output/build-customization-data.js'
|
94
110
|
import {
|
95
111
|
buildErrorPayload,
|
96
112
|
buildErrorStatus,
|
97
113
|
} from './output/build-error-payload.js'
|
98
|
-
import { Customization } from './output/customization.js'
|
99
114
|
import { OutputManager } from './output/output-manager.js'
|
100
115
|
import {
|
101
116
|
AuthorizationResultRedirect,
|
@@ -114,32 +129,36 @@ import { TokenManager } from './token/token-manager.js'
|
|
114
129
|
import { TokenStore, asTokenStore } from './token/token-store.js'
|
115
130
|
import { VerifyTokenClaimsOptions } from './token/verify-token-claims.js'
|
116
131
|
|
117
|
-
export type OAuthProviderStore = Partial<
|
118
|
-
ClientStore &
|
119
|
-
AccountStore &
|
120
|
-
DeviceStore &
|
121
|
-
TokenStore &
|
122
|
-
RequestStore &
|
123
|
-
ReplayStore
|
124
|
-
>
|
125
|
-
|
126
132
|
export {
|
133
|
+
type BrandingConfig,
|
127
134
|
type CustomMetadata,
|
128
135
|
type Customization,
|
129
136
|
type Handler,
|
137
|
+
type HcaptchaConfig,
|
130
138
|
Keyset,
|
131
139
|
type OAuthAuthorizationServerMetadata,
|
132
140
|
}
|
133
141
|
|
142
|
+
type ApiContext = {
|
143
|
+
requestUri: RequestUri
|
144
|
+
deviceId: DeviceId
|
145
|
+
deviceMetadata: RequestMetadata
|
146
|
+
}
|
147
|
+
|
148
|
+
export type ErrorHandler<
|
149
|
+
Req extends IncomingMessage = IncomingMessage,
|
150
|
+
Res extends ServerResponse = ServerResponse,
|
151
|
+
> = (req: Req, res: Res, err: unknown, message: string) => void
|
152
|
+
|
134
153
|
export type RouterOptions<
|
135
154
|
Req extends IncomingMessage = IncomingMessage,
|
136
155
|
Res extends ServerResponse = ServerResponse,
|
137
156
|
> = {
|
138
|
-
onError?:
|
157
|
+
onError?: ErrorHandler<Req, Res>
|
139
158
|
}
|
140
159
|
|
141
160
|
export type OAuthProviderOptions = Override<
|
142
|
-
OAuthVerifierOptions & OAuthHooks & DeviceManagerOptions,
|
161
|
+
OAuthVerifierOptions & OAuthHooks & DeviceManagerOptions & Customization,
|
143
162
|
{
|
144
163
|
/**
|
145
164
|
* Maximum age a device/account session can be before requiring
|
@@ -157,11 +176,6 @@ export type OAuthProviderOptions = Override<
|
|
157
176
|
*/
|
158
177
|
metadata?: CustomMetadata
|
159
178
|
|
160
|
-
/**
|
161
|
-
* UI customizations
|
162
|
-
*/
|
163
|
-
customization?: Customization
|
164
|
-
|
165
179
|
/**
|
166
180
|
* A custom fetch function that can be used to fetch the client metadata from
|
167
181
|
* the internet. By default, the fetch function is a safeFetchWrap() function
|
@@ -184,11 +198,18 @@ export type OAuthProviderOptions = Override<
|
|
184
198
|
* this store implements all the interfaces not provided in the other
|
185
199
|
* `<name>Store` options.
|
186
200
|
*/
|
187
|
-
store?:
|
201
|
+
store?: Partial<
|
202
|
+
AccountStore &
|
203
|
+
ClientStore &
|
204
|
+
DeviceStore &
|
205
|
+
ReplayStore &
|
206
|
+
RequestStore &
|
207
|
+
TokenStore
|
208
|
+
>
|
188
209
|
|
189
210
|
accountStore?: AccountStore
|
190
|
-
deviceStore?: DeviceStore
|
191
211
|
clientStore?: ClientStore
|
212
|
+
deviceStore?: DeviceStore
|
192
213
|
replayStore?: ReplayStore
|
193
214
|
requestStore?: RequestStore
|
194
215
|
tokenStore?: TokenStore
|
@@ -235,7 +256,6 @@ export class OAuthProvider extends OAuthVerifier {
|
|
235
256
|
|
236
257
|
public constructor({
|
237
258
|
metadata,
|
238
|
-
customization = undefined,
|
239
259
|
authenticationMaxAge = AUTHENTICATION_MAX_AGE,
|
240
260
|
tokenMaxAge = TOKEN_MAX_AGE,
|
241
261
|
|
@@ -264,10 +284,30 @@ export class OAuthProvider extends OAuthVerifier {
|
|
264
284
|
|
265
285
|
loopbackMetadata = atprotoLoopbackClientMetadata,
|
266
286
|
|
267
|
-
// OAuthHooks &
|
287
|
+
// OAuthHooks &
|
288
|
+
// OAuthVerifierOptions &
|
289
|
+
// DeviceManagerOptions &
|
290
|
+
// Customization
|
268
291
|
...rest
|
269
292
|
}: OAuthProviderOptions) {
|
270
|
-
|
293
|
+
const customization: Customization = customizationSchema.parse(rest)
|
294
|
+
const deviceManagerOptions: DeviceManagerOptions =
|
295
|
+
deviceManagerOptionsSchema.parse(rest)
|
296
|
+
|
297
|
+
// @NOTE: hooks don't really need a type parser, as all zod can actually
|
298
|
+
// check at runtime is the fact that the values are functions. The only way
|
299
|
+
// we would benefit from zod here would be to wrap the functions with a
|
300
|
+
// validator for the provided function's return types, which we do not add
|
301
|
+
// because it would impact runtime performance and we trust the users of
|
302
|
+
// this lib (basically ourselves) to rely on the typing system to ensure the
|
303
|
+
// correct types are returned.
|
304
|
+
const hooks: OAuthHooks = rest
|
305
|
+
|
306
|
+
// @NOTE: validation of super params (if we wanted to implement it) should
|
307
|
+
// be the responsibility of the super class.
|
308
|
+
const superOptions: OAuthVerifierOptions = rest
|
309
|
+
|
310
|
+
super({ replayStore, redis, ...superOptions })
|
271
311
|
|
272
312
|
requestStore ??= redis
|
273
313
|
? new RequestStoreRedis({ redis })
|
@@ -276,13 +316,18 @@ export class OAuthProvider extends OAuthVerifier {
|
|
276
316
|
this.authenticationMaxAge = authenticationMaxAge
|
277
317
|
this.metadata = buildMetadata(this.issuer, this.keyset, metadata)
|
278
318
|
|
279
|
-
this.deviceManager = new DeviceManager(deviceStore,
|
319
|
+
this.deviceManager = new DeviceManager(deviceStore, deviceManagerOptions)
|
280
320
|
this.outputManager = new OutputManager(customization)
|
281
|
-
this.accountManager = new AccountManager(
|
321
|
+
this.accountManager = new AccountManager(
|
322
|
+
this.issuer,
|
323
|
+
accountStore,
|
324
|
+
hooks,
|
325
|
+
customization,
|
326
|
+
)
|
282
327
|
this.clientManager = new ClientManager(
|
283
328
|
this.metadata,
|
284
329
|
this.keyset,
|
285
|
-
|
330
|
+
hooks,
|
286
331
|
clientStore || null,
|
287
332
|
loopbackMetadata || null,
|
288
333
|
safeFetch,
|
@@ -293,12 +338,12 @@ export class OAuthProvider extends OAuthVerifier {
|
|
293
338
|
requestStore,
|
294
339
|
this.signer,
|
295
340
|
this.metadata,
|
296
|
-
|
341
|
+
hooks,
|
297
342
|
)
|
298
343
|
this.tokenManager = new TokenManager(
|
299
344
|
tokenStore,
|
300
345
|
this.signer,
|
301
|
-
|
346
|
+
hooks,
|
302
347
|
this.accessTokenType,
|
303
348
|
tokenMaxAge,
|
304
349
|
)
|
@@ -451,8 +496,7 @@ export class OAuthProvider extends OAuthVerifier {
|
|
451
496
|
// https://datatracker.ietf.org/doc/html/rfc9126#section-2.3-1
|
452
497
|
// > Since initial processing of the pushed authorization request does not
|
453
498
|
// > involve resource owner interaction, error codes related to user
|
454
|
-
// > interaction, such as
|
455
|
-
// > returned.
|
499
|
+
// > interaction, such as "access_denied", are never returned.
|
456
500
|
if (err instanceof AccessDeniedError) {
|
457
501
|
throw new InvalidRequestError(err.error_description, err)
|
458
502
|
}
|
@@ -470,7 +514,7 @@ export class OAuthProvider extends OAuthVerifier {
|
|
470
514
|
.parseAsync(query.request_uri, { path: ['query', 'request_uri'] })
|
471
515
|
.catch(throwInvalidRequest)
|
472
516
|
|
473
|
-
return this.requestManager.get(requestUri, client.id
|
517
|
+
return this.requestManager.get(requestUri, deviceId, client.id)
|
474
518
|
}
|
475
519
|
|
476
520
|
if ('request' in query) {
|
@@ -514,11 +558,11 @@ export class OAuthProvider extends OAuthVerifier {
|
|
514
558
|
}
|
515
559
|
|
516
560
|
private async deleteRequest(
|
517
|
-
|
561
|
+
requestUri: RequestUri,
|
518
562
|
parameters: OAuthAuthorizationRequestParameters,
|
519
563
|
) {
|
520
564
|
try {
|
521
|
-
await this.requestManager.delete(
|
565
|
+
await this.requestManager.delete(requestUri)
|
522
566
|
} catch (err) {
|
523
567
|
throw AccessDeniedError.from(parameters, err)
|
524
568
|
}
|
@@ -690,24 +734,46 @@ export class OAuthProvider extends OAuthVerifier {
|
|
690
734
|
}))
|
691
735
|
}
|
692
736
|
|
693
|
-
protected async
|
694
|
-
deviceId:
|
695
|
-
|
696
|
-
clientId: ClientId,
|
697
|
-
credentials: SignInCredentials,
|
737
|
+
protected async signUp(
|
738
|
+
{ requestUri, deviceId, deviceMetadata }: ApiContext,
|
739
|
+
data: SignUpData,
|
698
740
|
): Promise<{
|
699
741
|
account: Account
|
700
742
|
consentRequired: boolean
|
701
743
|
}> {
|
744
|
+
const { clientId } = await this.requestManager.get(requestUri, deviceId)
|
745
|
+
|
702
746
|
const client = await this.clientManager.getClient(clientId)
|
703
747
|
|
748
|
+
const { account } = await this.accountManager.signUp(
|
749
|
+
data,
|
750
|
+
deviceId,
|
751
|
+
deviceMetadata,
|
752
|
+
)
|
753
|
+
|
754
|
+
return {
|
755
|
+
account,
|
756
|
+
consentRequired: !client.info.isFirstParty,
|
757
|
+
}
|
758
|
+
}
|
759
|
+
|
760
|
+
protected async signIn(
|
761
|
+
{ requestUri, deviceId, deviceMetadata }: ApiContext,
|
762
|
+
data: SignInData,
|
763
|
+
): Promise<{
|
764
|
+
account: Account
|
765
|
+
consentRequired: boolean
|
766
|
+
}> {
|
704
767
|
// Ensure the request is still valid (and update the request expiration)
|
705
768
|
// @TODO use the returned scopes to determine if consent is required
|
706
|
-
await this.requestManager.get(
|
769
|
+
const { clientId } = await this.requestManager.get(requestUri, deviceId)
|
770
|
+
|
771
|
+
const client = await this.clientManager.getClient(clientId)
|
707
772
|
|
708
773
|
const { account, info } = await this.accountManager.signIn(
|
709
|
-
|
774
|
+
data,
|
710
775
|
deviceId,
|
776
|
+
deviceMetadata,
|
711
777
|
)
|
712
778
|
|
713
779
|
return {
|
@@ -722,22 +788,21 @@ export class OAuthProvider extends OAuthVerifier {
|
|
722
788
|
}
|
723
789
|
|
724
790
|
protected async acceptRequest(
|
725
|
-
|
726
|
-
clientId: ClientId,
|
791
|
+
{ requestUri, deviceId, deviceMetadata }: ApiContext,
|
727
792
|
sub: string,
|
728
|
-
deviceId: DeviceId,
|
729
|
-
deviceMetadata: RequestMetadata,
|
730
793
|
): Promise<AuthorizationResultRedirect> {
|
731
794
|
const { issuer } = this
|
732
|
-
const client = await this.clientManager.getClient(clientId)
|
733
795
|
|
734
|
-
const { parameters, clientAuth } = await this.requestManager.get(
|
735
|
-
|
736
|
-
clientId,
|
796
|
+
const { parameters, clientId, clientAuth } = await this.requestManager.get(
|
797
|
+
requestUri,
|
737
798
|
deviceId,
|
738
799
|
)
|
739
800
|
|
801
|
+
const client = await this.clientManager.getClient(clientId)
|
802
|
+
|
740
803
|
try {
|
804
|
+
// @TODO Currently, a user can "accept" a request for any did that sing-in
|
805
|
+
// on the device, even if "remember" was set to false.
|
741
806
|
const { account, info } = await this.accountManager.get(deviceId, sub)
|
742
807
|
|
743
808
|
// The user is trying to authorize without a fresh login
|
@@ -749,7 +814,7 @@ export class OAuthProvider extends OAuthVerifier {
|
|
749
814
|
}
|
750
815
|
|
751
816
|
const code = await this.requestManager.setAuthorized(
|
752
|
-
|
817
|
+
requestUri,
|
753
818
|
client,
|
754
819
|
account,
|
755
820
|
deviceId,
|
@@ -765,24 +830,19 @@ export class OAuthProvider extends OAuthVerifier {
|
|
765
830
|
|
766
831
|
return { issuer, parameters, redirect: { code } }
|
767
832
|
} catch (err) {
|
768
|
-
await this.deleteRequest(
|
833
|
+
await this.deleteRequest(requestUri, parameters)
|
769
834
|
|
770
835
|
throw AccessDeniedError.from(parameters, err)
|
771
836
|
}
|
772
837
|
}
|
773
838
|
|
774
|
-
protected async rejectRequest(
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
const { parameters } = await this.requestManager.get(
|
780
|
-
uri,
|
781
|
-
clientId,
|
782
|
-
deviceId,
|
783
|
-
)
|
839
|
+
protected async rejectRequest({
|
840
|
+
requestUri,
|
841
|
+
deviceId,
|
842
|
+
}: ApiContext): Promise<AuthorizationResultRedirect> {
|
843
|
+
const { parameters } = await this.requestManager.get(requestUri, deviceId)
|
784
844
|
|
785
|
-
await this.deleteRequest(
|
845
|
+
await this.deleteRequest(requestUri, parameters)
|
786
846
|
|
787
847
|
return {
|
788
848
|
issuer: this.issuer,
|
@@ -1033,57 +1093,67 @@ export class OAuthProvider extends OAuthVerifier {
|
|
1033
1093
|
|
1034
1094
|
// Utils
|
1035
1095
|
|
1036
|
-
const csrfCookie = (
|
1037
|
-
const onError =
|
1096
|
+
const csrfCookie = (requestUri: RequestUri) => `csrf-${requestUri}`
|
1097
|
+
const onError: null | ErrorHandler<Req, Res> =
|
1038
1098
|
options?.onError ??
|
1039
1099
|
(process.env['NODE_ENV'] === 'development'
|
1040
|
-
? (req, res, err, msg)
|
1100
|
+
? (req, res, err, msg) => {
|
1041
1101
|
console.error(`OAuthProvider error (${msg}):`, err)
|
1042
|
-
|
1102
|
+
}
|
1103
|
+
: null)
|
1043
1104
|
|
1044
|
-
|
1045
|
-
|
1046
|
-
|
1047
|
-
const staticJson = (json: unknown): Middleware<void, Req, Res> =>
|
1048
|
-
combineMiddlewares([
|
1049
|
-
function (req, res, next) {
|
1050
|
-
res.setHeader('Access-Control-Allow-Origin', '*')
|
1051
|
-
res.setHeader('Access-Control-Allow-Headers', '*')
|
1105
|
+
// CORS preflight
|
1106
|
+
const corsHeaders: Middleware = function (req, res, next) {
|
1107
|
+
res.setHeader('Access-Control-Max-Age', '86400') // 1 day
|
1052
1108
|
|
1053
|
-
|
1054
|
-
|
1055
|
-
|
1056
|
-
|
1057
|
-
|
1109
|
+
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
|
1110
|
+
//
|
1111
|
+
// > For requests without credentials, the literal value "*" can be
|
1112
|
+
// > specified as a wildcard; the value tells browsers to allow
|
1113
|
+
// > requesting code from any origin to access the resource.
|
1114
|
+
// > Attempting to use the wildcard with credentials results in an
|
1115
|
+
// > error.
|
1116
|
+
//
|
1117
|
+
// A "*" is safer to use than reflecting the request origin.
|
1118
|
+
res.setHeader('Access-Control-Allow-Origin', '*')
|
1119
|
+
|
1120
|
+
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods
|
1121
|
+
// > The value "*" only counts as a special wildcard value for
|
1122
|
+
// > requests without credentials (requests without HTTP cookies or
|
1123
|
+
// > HTTP authentication information). In requests with credentials,
|
1124
|
+
// > it is treated as the literal method name "*" without special
|
1125
|
+
// > semantics.
|
1126
|
+
res.setHeader('Access-Control-Allow-Methods', '*')
|
1127
|
+
|
1128
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type,DPoP')
|
1129
|
+
|
1130
|
+
next()
|
1131
|
+
}
|
1132
|
+
|
1133
|
+
const corsPreflight: Middleware = combineMiddlewares([
|
1134
|
+
corsHeaders,
|
1135
|
+
(req, res) => {
|
1136
|
+
res.writeHead(200).end()
|
1137
|
+
},
|
1138
|
+
])
|
1058
1139
|
|
1059
1140
|
/**
|
1060
1141
|
* Wrap an OAuth endpoint in a middleware that will set the appropriate
|
1061
1142
|
* response headers and format the response as JSON.
|
1062
1143
|
*/
|
1063
1144
|
const jsonHandler = <T, TReq extends Req, TRes extends Res, Json>(
|
1064
|
-
buildJson: (this: T, req: TReq, res: TRes) =>
|
1145
|
+
buildJson: (this: T, req: TReq, res: TRes) => Awaitable<Json>,
|
1065
1146
|
status?: number,
|
1066
1147
|
): Handler<T, TReq, TRes> =>
|
1067
1148
|
async function (req, res) {
|
1068
|
-
res.setHeader('Access-Control-Allow-Origin', '*')
|
1069
|
-
res.setHeader('Access-Control-Allow-Headers', '*')
|
1070
|
-
|
1071
|
-
// https://www.rfc-editor.org/rfc/rfc6749.html#section-5.1
|
1072
|
-
res.setHeader('Cache-Control', 'no-store')
|
1073
|
-
res.setHeader('Pragma', 'no-cache')
|
1074
|
-
|
1075
|
-
// https://datatracker.ietf.org/doc/html/rfc9449#section-8.2
|
1076
|
-
const dpopNonce = server.nextDpopNonce()
|
1077
|
-
if (dpopNonce) {
|
1078
|
-
const name = 'DPoP-Nonce'
|
1079
|
-
res.setHeader(name, dpopNonce)
|
1080
|
-
res.appendHeader('Access-Control-Expose-Headers', name)
|
1081
|
-
}
|
1082
|
-
|
1083
1149
|
try {
|
1150
|
+
// https://www.rfc-editor.org/rfc/rfc6749.html#section-5.1
|
1151
|
+
res.setHeader('Cache-Control', 'no-store')
|
1152
|
+
res.setHeader('Pragma', 'no-cache')
|
1153
|
+
|
1084
1154
|
// Ensure we can agree on a content encoding & type before starting to
|
1085
1155
|
// build the JSON response.
|
1086
|
-
if (!
|
1156
|
+
if (!negotiateContent(req, ['application/json'])) {
|
1087
1157
|
throw createHttpError(406, 'Unsupported media type')
|
1088
1158
|
}
|
1089
1159
|
|
@@ -1094,39 +1164,107 @@ export class OAuthProvider extends OAuthVerifier {
|
|
1094
1164
|
res.writeHead(status ?? 204).end()
|
1095
1165
|
}
|
1096
1166
|
} catch (err) {
|
1097
|
-
|
1098
|
-
if (err instanceof WWWAuthenticateError) {
|
1099
|
-
const name = 'WWW-Authenticate'
|
1100
|
-
res.setHeader(name, err.wwwAuthenticateHeader)
|
1101
|
-
res.appendHeader('Access-Control-Expose-Headers', name)
|
1102
|
-
}
|
1167
|
+
onError?.(req, res, err, 'OAuth request error')
|
1103
1168
|
|
1169
|
+
if (!res.headersSent) {
|
1104
1170
|
const payload = buildErrorPayload(err)
|
1105
1171
|
const status = buildErrorStatus(err)
|
1106
1172
|
writeJson(res, payload, { status })
|
1107
1173
|
} else {
|
1108
1174
|
res.destroy()
|
1109
1175
|
}
|
1110
|
-
|
1111
|
-
// OAuthError are used to build expected responses, so we don't log
|
1112
|
-
// them as errors.
|
1113
|
-
if (!(err instanceof OAuthError) || err.statusCode >= 500) {
|
1114
|
-
onError?.(req, res, err, 'Unexpected error')
|
1115
|
-
}
|
1116
1176
|
}
|
1117
1177
|
}
|
1118
1178
|
|
1179
|
+
const oauthHandler = <T, TReq extends Req, TRes extends Res, Json>(
|
1180
|
+
buildOAuthResponse: (this: T, req: TReq, res: TRes) => Awaitable<Json>,
|
1181
|
+
status?: number,
|
1182
|
+
) =>
|
1183
|
+
combineMiddlewares([
|
1184
|
+
corsHeaders,
|
1185
|
+
jsonHandler<T, TReq, TRes, Json>(async function (req, res) {
|
1186
|
+
try {
|
1187
|
+
// https://datatracker.ietf.org/doc/html/rfc9449#section-8.2
|
1188
|
+
const dpopNonce = server.nextDpopNonce()
|
1189
|
+
if (dpopNonce) {
|
1190
|
+
const name = 'DPoP-Nonce'
|
1191
|
+
res.setHeader(name, dpopNonce)
|
1192
|
+
res.appendHeader('Access-Control-Expose-Headers', name)
|
1193
|
+
}
|
1194
|
+
|
1195
|
+
return await buildOAuthResponse.call(this, req, res)
|
1196
|
+
} catch (err) {
|
1197
|
+
if (!res.headersSent && err instanceof WWWAuthenticateError) {
|
1198
|
+
const name = 'WWW-Authenticate'
|
1199
|
+
res.setHeader(name, err.wwwAuthenticateHeader)
|
1200
|
+
res.appendHeader('Access-Control-Expose-Headers', name)
|
1201
|
+
}
|
1202
|
+
|
1203
|
+
throw err
|
1204
|
+
}
|
1205
|
+
}, status),
|
1206
|
+
])
|
1207
|
+
|
1208
|
+
const apiHandler = <
|
1209
|
+
T,
|
1210
|
+
TReq extends Req,
|
1211
|
+
TRes extends Res,
|
1212
|
+
S extends z.ZodTypeAny,
|
1213
|
+
Json,
|
1214
|
+
>(
|
1215
|
+
inputSchema: S,
|
1216
|
+
buildJson: (
|
1217
|
+
this: T,
|
1218
|
+
req: TReq,
|
1219
|
+
res: TRes,
|
1220
|
+
input: z.infer<S>,
|
1221
|
+
context: ApiContext,
|
1222
|
+
) => Json | Promise<Json>,
|
1223
|
+
status?: number,
|
1224
|
+
) =>
|
1225
|
+
jsonHandler<T, TReq, TRes, Json>(async function (req, res) {
|
1226
|
+
validateFetchMode(req, res, ['same-origin'])
|
1227
|
+
validateFetchSite(req, res, ['same-origin'])
|
1228
|
+
validateSameOrigin(req, res, issuerOrigin)
|
1229
|
+
const referer = validateReferer(req, res, {
|
1230
|
+
origin: issuerOrigin,
|
1231
|
+
pathname: '/oauth/authorize',
|
1232
|
+
})
|
1233
|
+
|
1234
|
+
const requestUri = await requestUriSchema.parseAsync(
|
1235
|
+
referer.searchParams.get('request_uri'),
|
1236
|
+
{ path: ['query', 'request_uri'] },
|
1237
|
+
)
|
1238
|
+
|
1239
|
+
validateCsrfToken(
|
1240
|
+
req,
|
1241
|
+
res,
|
1242
|
+
req.headers['x-csrf-token'],
|
1243
|
+
csrfCookie(requestUri),
|
1244
|
+
)
|
1245
|
+
|
1246
|
+
const { deviceId, deviceMetadata } = await server.deviceManager.load(
|
1247
|
+
req,
|
1248
|
+
res,
|
1249
|
+
)
|
1250
|
+
|
1251
|
+
const payload = await parseHttpRequest(req, ['json'])
|
1252
|
+
const input = await inputSchema.parseAsync(payload, { path: ['body'] })
|
1253
|
+
|
1254
|
+
const context: ApiContext = { requestUri, deviceId, deviceMetadata }
|
1255
|
+
return buildJson.call(this, req, res, input, context)
|
1256
|
+
}, status)
|
1257
|
+
|
1119
1258
|
const navigationHandler = <T, TReq extends Req, TRes extends Res>(
|
1120
|
-
handler: (this: T, req: TReq, res: TRes) =>
|
1259
|
+
handler: (this: T, req: TReq, res: TRes) => Awaitable<void>,
|
1121
1260
|
): Handler<T, TReq, TRes> =>
|
1122
1261
|
async function (req, res) {
|
1123
|
-
|
1124
|
-
|
1262
|
+
try {
|
1263
|
+
res.setHeader('Cache-Control', 'no-store')
|
1264
|
+
res.setHeader('Pragma', 'no-cache')
|
1125
1265
|
|
1126
|
-
|
1127
|
-
res.setHeader('Pragma', 'no-cache')
|
1266
|
+
res.setHeader('Referrer-Policy', 'same-origin')
|
1128
1267
|
|
1129
|
-
try {
|
1130
1268
|
validateFetchMode(req, res, ['navigate'])
|
1131
1269
|
validateFetchDest(req, res, ['document'])
|
1132
1270
|
validateSameOrigin(req, res, issuerOrigin)
|
@@ -1141,11 +1279,78 @@ export class OAuthProvider extends OAuthVerifier {
|
|
1141
1279
|
)
|
1142
1280
|
|
1143
1281
|
if (!res.headersSent) {
|
1144
|
-
await server.outputManager.sendErrorPage(res, err
|
1282
|
+
await server.outputManager.sendErrorPage(res, err, {
|
1283
|
+
preferredLocales: extractLocales(req),
|
1284
|
+
})
|
1145
1285
|
}
|
1146
1286
|
}
|
1147
1287
|
}
|
1148
1288
|
|
1289
|
+
// Simple GET requests fall under the category of "no-cors" request, meaning
|
1290
|
+
// that the browser will allow any cross-origin request, with credentials,
|
1291
|
+
// to be sent to the oauth server. The OAuth Server will, however:
|
1292
|
+
// 1) validate the request origin (see navigationHandler),
|
1293
|
+
// 2) validate the CSRF token,
|
1294
|
+
// 3) validate the referer,
|
1295
|
+
// 4) validate the sec-fetch-site header,
|
1296
|
+
// 4) validate the sec-fetch-mode header (see navigationHandler),
|
1297
|
+
// 5) validate the sec-fetch-dest header (see navigationHandler).
|
1298
|
+
// And will error (refuse to serve the request) if any of these checks fail.
|
1299
|
+
const sameOriginNavigationHandler = <
|
1300
|
+
T extends { url: URL },
|
1301
|
+
TReq extends Req,
|
1302
|
+
TRes extends Res,
|
1303
|
+
>(
|
1304
|
+
handler: (
|
1305
|
+
this: T,
|
1306
|
+
req: TReq,
|
1307
|
+
res: TRes,
|
1308
|
+
deviceInfo: DeviceInfo,
|
1309
|
+
) => Awaitable<void>,
|
1310
|
+
): Handler<T, TReq, TRes> =>
|
1311
|
+
navigationHandler(async function (req, res) {
|
1312
|
+
validateFetchSite(req, res, ['same-origin'])
|
1313
|
+
|
1314
|
+
const deviceInfo = await server.deviceManager.load(req, res)
|
1315
|
+
|
1316
|
+
return handler.call(this, req, res, deviceInfo)
|
1317
|
+
})
|
1318
|
+
|
1319
|
+
const authorizeRedirectNavigationHandler = <
|
1320
|
+
T extends { url: URL },
|
1321
|
+
TReq extends Req,
|
1322
|
+
TRes extends Res,
|
1323
|
+
>(
|
1324
|
+
handler: (
|
1325
|
+
this: T,
|
1326
|
+
req: TReq,
|
1327
|
+
res: TRes,
|
1328
|
+
context: ApiContext,
|
1329
|
+
) => Awaitable<AuthorizationResultRedirect>,
|
1330
|
+
): Handler<T, TReq, TRes> =>
|
1331
|
+
sameOriginNavigationHandler(async function (req, res, deviceInfo) {
|
1332
|
+
const referer = validateReferer(req, res, {
|
1333
|
+
origin: issuerOrigin,
|
1334
|
+
pathname: '/oauth/authorize',
|
1335
|
+
})
|
1336
|
+
|
1337
|
+
const requestUri = await requestUriSchema.parseAsync(
|
1338
|
+
referer.searchParams.get('request_uri'),
|
1339
|
+
)
|
1340
|
+
|
1341
|
+
const csrfToken = this.url.searchParams.get('csrf_token')
|
1342
|
+
const csrfCookieName = csrfCookie(requestUri)
|
1343
|
+
|
1344
|
+
// Next line will "clear" the CSRF token cookie, preventing replay of
|
1345
|
+
// this request (navigating "back" will result in an error).
|
1346
|
+
validateCsrfToken(req, res, csrfToken, csrfCookieName, true)
|
1347
|
+
|
1348
|
+
const context: ApiContext = { ...deviceInfo, requestUri }
|
1349
|
+
|
1350
|
+
const redirect = await handler.call(this, req, res, context)
|
1351
|
+
return sendAuthorizeRedirect(res, redirect)
|
1352
|
+
})
|
1353
|
+
|
1149
1354
|
/**
|
1150
1355
|
* Provides a better UX when a request is denied by redirecting to the
|
1151
1356
|
* client with the error details. This will also log any error that caused
|
@@ -1172,44 +1377,26 @@ export class OAuthProvider extends OAuthVerifier {
|
|
1172
1377
|
|
1173
1378
|
//- Public OAuth endpoints
|
1174
1379
|
|
1380
|
+
router.options('/.well-known/oauth-authorization-server', corsPreflight)
|
1175
1381
|
router.get(
|
1176
1382
|
'/.well-known/oauth-authorization-server',
|
1177
|
-
|
1383
|
+
corsHeaders,
|
1384
|
+
cacheControlMiddleware(300),
|
1385
|
+
staticJsonMiddleware(server.metadata),
|
1178
1386
|
)
|
1179
1387
|
|
1180
|
-
|
1181
|
-
|
1182
|
-
|
1183
|
-
|
1184
|
-
|
1185
|
-
|
1186
|
-
|
1187
|
-
// > specified as a wildcard; the value tells browsers to allow
|
1188
|
-
// > requesting code from any origin to access the resource.
|
1189
|
-
// > Attempting to use the wildcard with credentials results in an
|
1190
|
-
// > error.
|
1191
|
-
//
|
1192
|
-
// A "*" is safer to use than reflecting the request origin.
|
1193
|
-
'Access-Control-Allow-Origin': '*',
|
1194
|
-
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods
|
1195
|
-
// > The value "*" only counts as a special wildcard value for
|
1196
|
-
// > requests without credentials (requests without HTTP cookies or
|
1197
|
-
// > HTTP authentication information). In requests with credentials,
|
1198
|
-
// > it is treated as the literal method name "*" without special
|
1199
|
-
// > semantics.
|
1200
|
-
'Access-Control-Allow-Methods': '*',
|
1201
|
-
'Access-Control-Allow-Headers': 'Content-Type,Authorization,DPoP',
|
1202
|
-
'Access-Control-Max-Age': '86400', // 1 day
|
1203
|
-
})
|
1204
|
-
.end()
|
1205
|
-
}
|
1206
|
-
|
1207
|
-
router.get('/oauth/jwks', staticJson(server.jwks))
|
1388
|
+
router.options('/oauth/jwks', corsPreflight)
|
1389
|
+
router.get(
|
1390
|
+
'/oauth/jwks',
|
1391
|
+
corsHeaders,
|
1392
|
+
cacheControlMiddleware(300),
|
1393
|
+
staticJsonMiddleware(server.jwks),
|
1394
|
+
)
|
1208
1395
|
|
1209
1396
|
router.options('/oauth/par', corsPreflight)
|
1210
1397
|
router.post(
|
1211
1398
|
'/oauth/par',
|
1212
|
-
|
1399
|
+
oauthHandler(async function (req, _res) {
|
1213
1400
|
const payload = await parseHttpRequest(req, ['json', 'urlencoded'])
|
1214
1401
|
|
1215
1402
|
const credentials = await oauthClientCredentialsSchema
|
@@ -1233,11 +1420,9 @@ export class OAuthProvider extends OAuthVerifier {
|
|
1233
1420
|
)
|
1234
1421
|
}, 201),
|
1235
1422
|
)
|
1236
|
-
|
1237
1423
|
// https://datatracker.ietf.org/doc/html/rfc9126#section-2.3
|
1238
1424
|
// > If the request did not use the POST method, the authorization server
|
1239
1425
|
// > responds with an HTTP 405 (Method Not Allowed) status code.
|
1240
|
-
router.options('/oauth/par', corsPreflight)
|
1241
1426
|
router.all('/oauth/par', (req, res) => {
|
1242
1427
|
res.writeHead(405).end()
|
1243
1428
|
})
|
@@ -1245,7 +1430,7 @@ export class OAuthProvider extends OAuthVerifier {
|
|
1245
1430
|
router.options('/oauth/token', corsPreflight)
|
1246
1431
|
router.post(
|
1247
1432
|
'/oauth/token',
|
1248
|
-
|
1433
|
+
oauthHandler(async function (req, _res) {
|
1249
1434
|
const payload = await parseHttpRequest(req, ['json', 'urlencoded'])
|
1250
1435
|
|
1251
1436
|
const clientMetadata =
|
@@ -1277,7 +1462,7 @@ export class OAuthProvider extends OAuthVerifier {
|
|
1277
1462
|
router.options('/oauth/revoke', corsPreflight)
|
1278
1463
|
router.post(
|
1279
1464
|
'/oauth/revoke',
|
1280
|
-
|
1465
|
+
oauthHandler(async function (req, res) {
|
1281
1466
|
const payload = await parseHttpRequest(req, ['json', 'urlencoded'])
|
1282
1467
|
|
1283
1468
|
const tokenIdentification = await oauthTokenIdentificationSchema
|
@@ -1291,8 +1476,6 @@ export class OAuthProvider extends OAuthVerifier {
|
|
1291
1476
|
}
|
1292
1477
|
}),
|
1293
1478
|
)
|
1294
|
-
|
1295
|
-
router.options('/oauth/revoke', corsPreflight)
|
1296
1479
|
router.get(
|
1297
1480
|
'/oauth/revoke',
|
1298
1481
|
navigationHandler(async function (req, res) {
|
@@ -1317,9 +1500,10 @@ export class OAuthProvider extends OAuthVerifier {
|
|
1317
1500
|
}),
|
1318
1501
|
)
|
1319
1502
|
|
1503
|
+
router.options('/oauth/introspect', corsPreflight)
|
1320
1504
|
router.post(
|
1321
1505
|
'/oauth/introspect',
|
1322
|
-
|
1506
|
+
oauthHandler(async function (req, _res) {
|
1323
1507
|
const payload = await parseHttpRequest(req, ['json', 'urlencoded'])
|
1324
1508
|
|
1325
1509
|
const credentials = await oauthClientCredentialsSchema
|
@@ -1346,7 +1530,7 @@ export class OAuthProvider extends OAuthVerifier {
|
|
1346
1530
|
const query = Object.fromEntries(this.url.searchParams)
|
1347
1531
|
|
1348
1532
|
const clientCredentials = await oauthClientCredentialsSchema
|
1349
|
-
.parseAsync(query, { path: ['
|
1533
|
+
.parseAsync(query, { path: ['query'] })
|
1350
1534
|
.catch(throwInvalidRequest)
|
1351
1535
|
|
1352
1536
|
if ('client_secret' in clientCredentials) {
|
@@ -1362,7 +1546,9 @@ export class OAuthProvider extends OAuthVerifier {
|
|
1362
1546
|
res,
|
1363
1547
|
)
|
1364
1548
|
|
1365
|
-
const
|
1549
|
+
const result:
|
1550
|
+
| AuthorizationResultRedirect
|
1551
|
+
| AuthorizationResultAuthorize = await server
|
1366
1552
|
.authorize(
|
1367
1553
|
clientCredentials,
|
1368
1554
|
authorizationRequest,
|
@@ -1371,173 +1557,79 @@ export class OAuthProvider extends OAuthVerifier {
|
|
1371
1557
|
)
|
1372
1558
|
.catch((err) => accessDeniedToRedirectCatcher(req, res, err))
|
1373
1559
|
|
1374
|
-
|
1375
|
-
|
1376
|
-
|
1377
|
-
|
1378
|
-
|
1379
|
-
|
1380
|
-
|
1381
|
-
}
|
1382
|
-
default: {
|
1383
|
-
// Should never happen
|
1384
|
-
throw new Error('Unexpected authorization result')
|
1385
|
-
}
|
1560
|
+
if ('redirect' in result) {
|
1561
|
+
return sendAuthorizeRedirect(res, result)
|
1562
|
+
} else {
|
1563
|
+
await setupCsrfToken(req, res, csrfCookie(result.authorize.uri))
|
1564
|
+
return server.outputManager.sendAuthorizePage(res, result, {
|
1565
|
+
preferredLocales: extractLocales(req),
|
1566
|
+
})
|
1386
1567
|
}
|
1387
1568
|
}),
|
1388
1569
|
)
|
1389
1570
|
|
1390
|
-
const signInPayloadSchema = z.object({
|
1391
|
-
csrf_token: z.string(),
|
1392
|
-
request_uri: requestUriSchema,
|
1393
|
-
client_id: clientIdSchema,
|
1394
|
-
credentials: signInCredentialsSchema,
|
1395
|
-
})
|
1396
|
-
|
1397
|
-
router.options('/oauth/authorize/sign-in', corsPreflight)
|
1398
1571
|
router.post(
|
1399
|
-
'/oauth/authorize/
|
1400
|
-
|
1401
|
-
|
1402
|
-
|
1403
|
-
|
1404
|
-
|
1405
|
-
|
1406
|
-
|
1407
|
-
path: ['body'],
|
1408
|
-
})
|
1409
|
-
|
1410
|
-
validateReferer(req, res, {
|
1411
|
-
origin: issuerOrigin,
|
1412
|
-
pathname: '/oauth/authorize',
|
1413
|
-
})
|
1414
|
-
validateCsrfToken(
|
1415
|
-
req,
|
1416
|
-
res,
|
1417
|
-
input.csrf_token,
|
1418
|
-
csrfCookie(input.request_uri),
|
1419
|
-
)
|
1572
|
+
'/oauth/authorize/verify-handle-availability',
|
1573
|
+
apiHandler(
|
1574
|
+
z.object({ handle: handleSchema }).strict(),
|
1575
|
+
async function (req, res, data) {
|
1576
|
+
return server.accountManager.verifyHandleAvailability(data.handle)
|
1577
|
+
},
|
1578
|
+
),
|
1579
|
+
)
|
1420
1580
|
|
1421
|
-
|
1581
|
+
router.post(
|
1582
|
+
'/oauth/authorize/sign-up',
|
1583
|
+
apiHandler(signUpDataSchema, async function (req, res, data, ctx) {
|
1584
|
+
return server.signUp(ctx, data)
|
1585
|
+
}),
|
1586
|
+
)
|
1422
1587
|
|
1423
|
-
|
1424
|
-
|
1425
|
-
|
1426
|
-
|
1427
|
-
input.credentials,
|
1428
|
-
)
|
1588
|
+
router.post(
|
1589
|
+
'/oauth/authorize/sign-in',
|
1590
|
+
apiHandler(signInDataSchema, async function (req, res, data, ctx) {
|
1591
|
+
return server.signIn(ctx, data)
|
1429
1592
|
}),
|
1430
1593
|
)
|
1431
1594
|
|
1432
|
-
|
1433
|
-
|
1434
|
-
|
1435
|
-
|
1436
|
-
|
1437
|
-
|
1595
|
+
router.post(
|
1596
|
+
'/oauth/authorize/reset-password-request',
|
1597
|
+
apiHandler(
|
1598
|
+
resetPasswordRequestDataSchema,
|
1599
|
+
async function (req, res, data) {
|
1600
|
+
await server.accountManager.resetPasswordRequest(data)
|
1601
|
+
},
|
1602
|
+
),
|
1603
|
+
)
|
1604
|
+
|
1605
|
+
router.post(
|
1606
|
+
'/oauth/authorize/reset-password-confirm',
|
1607
|
+
apiHandler(
|
1608
|
+
resetPasswordConfirmDataSchema,
|
1609
|
+
async function (req, res, data) {
|
1610
|
+
await server.accountManager.resetPasswordConfirm(data)
|
1611
|
+
},
|
1612
|
+
),
|
1613
|
+
)
|
1438
1614
|
|
1439
|
-
// Though this is a "no-cors" request, meaning that the browser will allow
|
1440
|
-
// any cross-origin request, with credentials, to be sent, the handler will
|
1441
|
-
// 1) validate the request origin,
|
1442
|
-
// 2) validate the CSRF token,
|
1443
|
-
// 3) validate the referer,
|
1444
|
-
// 4) validate the sec-fetch-site header,
|
1445
|
-
// 4) validate the sec-fetch-mode header,
|
1446
|
-
// 5) validate the sec-fetch-dest header (see navigationHandler).
|
1447
|
-
// And will error if any of these checks fail.
|
1448
1615
|
router.get(
|
1449
1616
|
'/oauth/authorize/accept',
|
1450
|
-
|
1451
|
-
|
1452
|
-
|
1453
|
-
const query = Object.fromEntries(this.url.searchParams)
|
1454
|
-
const input = await acceptQuerySchema.parseAsync(query, {
|
1455
|
-
path: ['query'],
|
1456
|
-
})
|
1457
|
-
|
1458
|
-
validateReferer(req, res, {
|
1459
|
-
origin: issuerOrigin,
|
1460
|
-
pathname: '/oauth/authorize',
|
1461
|
-
searchParams: [
|
1462
|
-
['request_uri', input.request_uri],
|
1463
|
-
['client_id', input.client_id],
|
1464
|
-
],
|
1465
|
-
})
|
1466
|
-
validateCsrfToken(
|
1467
|
-
req,
|
1468
|
-
res,
|
1469
|
-
input.csrf_token,
|
1470
|
-
csrfCookie(input.request_uri),
|
1471
|
-
true,
|
1472
|
-
)
|
1473
|
-
|
1474
|
-
const { deviceId, deviceMetadata } = await server.deviceManager.load(
|
1475
|
-
req,
|
1476
|
-
res,
|
1477
|
-
)
|
1617
|
+
authorizeRedirectNavigationHandler(async function (req, res, ctx) {
|
1618
|
+
const sub = this.url.searchParams.get('account_sub')
|
1619
|
+
if (!sub) throw new InvalidRequestError('Account sub not provided')
|
1478
1620
|
|
1479
|
-
|
1480
|
-
.acceptRequest(
|
1481
|
-
input.request_uri,
|
1482
|
-
input.client_id,
|
1483
|
-
input.account_sub,
|
1484
|
-
deviceId,
|
1485
|
-
deviceMetadata,
|
1486
|
-
)
|
1621
|
+
return server
|
1622
|
+
.acceptRequest(ctx, sub)
|
1487
1623
|
.catch((err) => accessDeniedToRedirectCatcher(req, res, err))
|
1488
|
-
|
1489
|
-
return await sendAuthorizeRedirect(res, data)
|
1490
1624
|
}),
|
1491
1625
|
)
|
1492
1626
|
|
1493
|
-
const rejectQuerySchema = z.object({
|
1494
|
-
csrf_token: z.string(),
|
1495
|
-
request_uri: requestUriSchema,
|
1496
|
-
client_id: clientIdSchema,
|
1497
|
-
})
|
1498
|
-
|
1499
|
-
// Though this is a "no-cors" request, meaning that the browser will allow
|
1500
|
-
// any cross-origin request, with credentials, to be sent, the handler will
|
1501
|
-
// 1) validate the request origin,
|
1502
|
-
// 2) validate the CSRF token,
|
1503
|
-
// 3) validate the referer,
|
1504
|
-
// 4) validate the sec-fetch-site header,
|
1505
|
-
// 4) validate the sec-fetch-mode header,
|
1506
|
-
// 5) validate the sec-fetch-dest header (see navigationHandler).
|
1507
|
-
// And will error if any of these checks fail.
|
1508
1627
|
router.get(
|
1509
1628
|
'/oauth/authorize/reject',
|
1510
|
-
|
1511
|
-
|
1512
|
-
|
1513
|
-
const query = Object.fromEntries(this.url.searchParams)
|
1514
|
-
const input = await rejectQuerySchema.parseAsync(query, {
|
1515
|
-
path: ['query'],
|
1516
|
-
})
|
1517
|
-
|
1518
|
-
validateReferer(req, res, {
|
1519
|
-
origin: issuerOrigin,
|
1520
|
-
pathname: '/oauth/authorize',
|
1521
|
-
searchParams: [
|
1522
|
-
['request_uri', input.request_uri],
|
1523
|
-
['client_id', input.client_id],
|
1524
|
-
],
|
1525
|
-
})
|
1526
|
-
validateCsrfToken(
|
1527
|
-
req,
|
1528
|
-
res,
|
1529
|
-
input.csrf_token,
|
1530
|
-
csrfCookie(input.request_uri),
|
1531
|
-
true,
|
1532
|
-
)
|
1533
|
-
|
1534
|
-
const { deviceId } = await server.deviceManager.load(req, res)
|
1535
|
-
|
1536
|
-
const data = await server
|
1537
|
-
.rejectRequest(deviceId, input.request_uri, input.client_id)
|
1629
|
+
authorizeRedirectNavigationHandler(async function (req, res, ctx) {
|
1630
|
+
return server
|
1631
|
+
.rejectRequest(ctx)
|
1538
1632
|
.catch((err) => accessDeniedToRedirectCatcher(req, res, err))
|
1539
|
-
|
1540
|
-
return await sendAuthorizeRedirect(res, data)
|
1541
1633
|
}),
|
1542
1634
|
)
|
1543
1635
|
|