@atproto/oauth-provider 0.3.1 → 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 +29 -0
- package/LICENSE.txt +1 -1
- 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 +12 -13
- package/dist/device/device-manager.d.ts.map +1 -1
- package/dist/device/device-manager.js +5 -3
- 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 +18 -3
- package/dist/lib/http/request.d.ts.map +1 -1
- package/dist/lib/http/request.js +56 -23
- 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 +28 -22
- package/dist/oauth-provider.d.ts.map +1 -1
- package/dist/oauth-provider.js +212 -211
- 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 +46 -21
- 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 +14 -15
- 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 +81 -28
- 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 +410 -315
- 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
|
-
> =
|
138
|
-
onError?:
|
156
|
+
> = {
|
157
|
+
onError?: ErrorHandler<Req, Res>
|
139
158
|
}
|
140
159
|
|
141
160
|
export type OAuthProviderOptions = Override<
|
142
|
-
OAuthVerifierOptions & OAuthHooks,
|
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
|
@@ -223,19 +244,18 @@ export type OAuthProviderOptions = Override<
|
|
223
244
|
|
224
245
|
export class OAuthProvider extends OAuthVerifier {
|
225
246
|
public readonly metadata: OAuthAuthorizationServerMetadata
|
226
|
-
public readonly customization?: Customization
|
227
247
|
|
228
248
|
public readonly authenticationMaxAge: number
|
229
249
|
|
230
250
|
public readonly accountManager: AccountManager
|
231
|
-
public readonly
|
251
|
+
public readonly deviceManager: DeviceManager
|
232
252
|
public readonly clientManager: ClientManager
|
233
253
|
public readonly requestManager: RequestManager
|
234
254
|
public readonly tokenManager: TokenManager
|
255
|
+
public readonly outputManager: OutputManager
|
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 })
|
@@ -275,15 +315,19 @@ export class OAuthProvider extends OAuthVerifier {
|
|
275
315
|
|
276
316
|
this.authenticationMaxAge = authenticationMaxAge
|
277
317
|
this.metadata = buildMetadata(this.issuer, this.keyset, metadata)
|
278
|
-
this.customization = customization
|
279
|
-
|
280
|
-
this.deviceStore = deviceStore
|
281
318
|
|
282
|
-
this.
|
319
|
+
this.deviceManager = new DeviceManager(deviceStore, deviceManagerOptions)
|
320
|
+
this.outputManager = new OutputManager(customization)
|
321
|
+
this.accountManager = new AccountManager(
|
322
|
+
this.issuer,
|
323
|
+
accountStore,
|
324
|
+
hooks,
|
325
|
+
customization,
|
326
|
+
)
|
283
327
|
this.clientManager = new ClientManager(
|
284
328
|
this.metadata,
|
285
329
|
this.keyset,
|
286
|
-
|
330
|
+
hooks,
|
287
331
|
clientStore || null,
|
288
332
|
loopbackMetadata || null,
|
289
333
|
safeFetch,
|
@@ -294,12 +338,12 @@ export class OAuthProvider extends OAuthVerifier {
|
|
294
338
|
requestStore,
|
295
339
|
this.signer,
|
296
340
|
this.metadata,
|
297
|
-
|
341
|
+
hooks,
|
298
342
|
)
|
299
343
|
this.tokenManager = new TokenManager(
|
300
344
|
tokenStore,
|
301
345
|
this.signer,
|
302
|
-
|
346
|
+
hooks,
|
303
347
|
this.accessTokenType,
|
304
348
|
tokenMaxAge,
|
305
349
|
)
|
@@ -452,8 +496,7 @@ export class OAuthProvider extends OAuthVerifier {
|
|
452
496
|
// https://datatracker.ietf.org/doc/html/rfc9126#section-2.3-1
|
453
497
|
// > Since initial processing of the pushed authorization request does not
|
454
498
|
// > involve resource owner interaction, error codes related to user
|
455
|
-
// > interaction, such as
|
456
|
-
// > returned.
|
499
|
+
// > interaction, such as "access_denied", are never returned.
|
457
500
|
if (err instanceof AccessDeniedError) {
|
458
501
|
throw new InvalidRequestError(err.error_description, err)
|
459
502
|
}
|
@@ -471,7 +514,7 @@ export class OAuthProvider extends OAuthVerifier {
|
|
471
514
|
.parseAsync(query.request_uri, { path: ['query', 'request_uri'] })
|
472
515
|
.catch(throwInvalidRequest)
|
473
516
|
|
474
|
-
return this.requestManager.get(requestUri, client.id
|
517
|
+
return this.requestManager.get(requestUri, deviceId, client.id)
|
475
518
|
}
|
476
519
|
|
477
520
|
if ('request' in query) {
|
@@ -515,11 +558,11 @@ export class OAuthProvider extends OAuthVerifier {
|
|
515
558
|
}
|
516
559
|
|
517
560
|
private async deleteRequest(
|
518
|
-
|
561
|
+
requestUri: RequestUri,
|
519
562
|
parameters: OAuthAuthorizationRequestParameters,
|
520
563
|
) {
|
521
564
|
try {
|
522
|
-
await this.requestManager.delete(
|
565
|
+
await this.requestManager.delete(requestUri)
|
523
566
|
} catch (err) {
|
524
567
|
throw AccessDeniedError.from(parameters, err)
|
525
568
|
}
|
@@ -691,24 +734,46 @@ export class OAuthProvider extends OAuthVerifier {
|
|
691
734
|
}))
|
692
735
|
}
|
693
736
|
|
694
|
-
protected async
|
695
|
-
deviceId:
|
696
|
-
|
697
|
-
clientId: ClientId,
|
698
|
-
credentials: SignInCredentials,
|
737
|
+
protected async signUp(
|
738
|
+
{ requestUri, deviceId, deviceMetadata }: ApiContext,
|
739
|
+
data: SignUpData,
|
699
740
|
): Promise<{
|
700
741
|
account: Account
|
701
742
|
consentRequired: boolean
|
702
743
|
}> {
|
744
|
+
const { clientId } = await this.requestManager.get(requestUri, deviceId)
|
745
|
+
|
703
746
|
const client = await this.clientManager.getClient(clientId)
|
704
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
|
+
}> {
|
705
767
|
// Ensure the request is still valid (and update the request expiration)
|
706
768
|
// @TODO use the returned scopes to determine if consent is required
|
707
|
-
await this.requestManager.get(
|
769
|
+
const { clientId } = await this.requestManager.get(requestUri, deviceId)
|
770
|
+
|
771
|
+
const client = await this.clientManager.getClient(clientId)
|
708
772
|
|
709
773
|
const { account, info } = await this.accountManager.signIn(
|
710
|
-
|
774
|
+
data,
|
711
775
|
deviceId,
|
776
|
+
deviceMetadata,
|
712
777
|
)
|
713
778
|
|
714
779
|
return {
|
@@ -723,22 +788,21 @@ export class OAuthProvider extends OAuthVerifier {
|
|
723
788
|
}
|
724
789
|
|
725
790
|
protected async acceptRequest(
|
726
|
-
|
727
|
-
clientId: ClientId,
|
791
|
+
{ requestUri, deviceId, deviceMetadata }: ApiContext,
|
728
792
|
sub: string,
|
729
|
-
deviceId: DeviceId,
|
730
|
-
deviceMetadata: RequestMetadata,
|
731
793
|
): Promise<AuthorizationResultRedirect> {
|
732
794
|
const { issuer } = this
|
733
|
-
const client = await this.clientManager.getClient(clientId)
|
734
795
|
|
735
|
-
const { parameters, clientAuth } = await this.requestManager.get(
|
736
|
-
|
737
|
-
clientId,
|
796
|
+
const { parameters, clientId, clientAuth } = await this.requestManager.get(
|
797
|
+
requestUri,
|
738
798
|
deviceId,
|
739
799
|
)
|
740
800
|
|
801
|
+
const client = await this.clientManager.getClient(clientId)
|
802
|
+
|
741
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.
|
742
806
|
const { account, info } = await this.accountManager.get(deviceId, sub)
|
743
807
|
|
744
808
|
// The user is trying to authorize without a fresh login
|
@@ -750,7 +814,7 @@ export class OAuthProvider extends OAuthVerifier {
|
|
750
814
|
}
|
751
815
|
|
752
816
|
const code = await this.requestManager.setAuthorized(
|
753
|
-
|
817
|
+
requestUri,
|
754
818
|
client,
|
755
819
|
account,
|
756
820
|
deviceId,
|
@@ -766,24 +830,19 @@ export class OAuthProvider extends OAuthVerifier {
|
|
766
830
|
|
767
831
|
return { issuer, parameters, redirect: { code } }
|
768
832
|
} catch (err) {
|
769
|
-
await this.deleteRequest(
|
833
|
+
await this.deleteRequest(requestUri, parameters)
|
770
834
|
|
771
835
|
throw AccessDeniedError.from(parameters, err)
|
772
836
|
}
|
773
837
|
}
|
774
838
|
|
775
|
-
protected async rejectRequest(
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
const { parameters } = await this.requestManager.get(
|
781
|
-
uri,
|
782
|
-
clientId,
|
783
|
-
deviceId,
|
784
|
-
)
|
839
|
+
protected async rejectRequest({
|
840
|
+
requestUri,
|
841
|
+
deviceId,
|
842
|
+
}: ApiContext): Promise<AuthorizationResultRedirect> {
|
843
|
+
const { parameters } = await this.requestManager.get(requestUri, deviceId)
|
785
844
|
|
786
|
-
await this.deleteRequest(
|
845
|
+
await this.deleteRequest(requestUri, parameters)
|
787
846
|
|
788
847
|
return {
|
789
848
|
issuer: this.issuer,
|
@@ -1025,10 +1084,7 @@ export class OAuthProvider extends OAuthVerifier {
|
|
1025
1084
|
T = void,
|
1026
1085
|
Req extends IncomingMessage = IncomingMessage,
|
1027
1086
|
Res extends ServerResponse = ServerResponse,
|
1028
|
-
>(options?: RouterOptions<Req, Res>) {
|
1029
|
-
const deviceManager = new DeviceManager(this.deviceStore, options)
|
1030
|
-
const outputManager = new OutputManager(this.customization)
|
1031
|
-
|
1087
|
+
>(options?: RouterOptions<Req, Res>): Router<T, Req, Res> {
|
1032
1088
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
1033
1089
|
const server = this
|
1034
1090
|
const issuerUrl = new URL(server.issuer)
|
@@ -1037,57 +1093,67 @@ export class OAuthProvider extends OAuthVerifier {
|
|
1037
1093
|
|
1038
1094
|
// Utils
|
1039
1095
|
|
1040
|
-
const csrfCookie = (
|
1041
|
-
const onError =
|
1096
|
+
const csrfCookie = (requestUri: RequestUri) => `csrf-${requestUri}`
|
1097
|
+
const onError: null | ErrorHandler<Req, Res> =
|
1042
1098
|
options?.onError ??
|
1043
1099
|
(process.env['NODE_ENV'] === 'development'
|
1044
|
-
? (req, res, err, msg)
|
1100
|
+
? (req, res, err, msg) => {
|
1045
1101
|
console.error(`OAuthProvider error (${msg}):`, err)
|
1046
|
-
|
1102
|
+
}
|
1103
|
+
: null)
|
1047
1104
|
|
1048
|
-
|
1049
|
-
|
1050
|
-
|
1051
|
-
const staticJson = (json: unknown): Middleware<void, Req, Res> =>
|
1052
|
-
combineMiddlewares([
|
1053
|
-
function (req, res, next) {
|
1054
|
-
res.setHeader('Access-Control-Allow-Origin', '*')
|
1055
|
-
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
|
1056
1108
|
|
1057
|
-
|
1058
|
-
|
1059
|
-
|
1060
|
-
|
1061
|
-
|
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
|
+
])
|
1062
1139
|
|
1063
1140
|
/**
|
1064
1141
|
* Wrap an OAuth endpoint in a middleware that will set the appropriate
|
1065
1142
|
* response headers and format the response as JSON.
|
1066
1143
|
*/
|
1067
1144
|
const jsonHandler = <T, TReq extends Req, TRes extends Res, Json>(
|
1068
|
-
buildJson: (this: T, req: TReq, res: TRes) =>
|
1145
|
+
buildJson: (this: T, req: TReq, res: TRes) => Awaitable<Json>,
|
1069
1146
|
status?: number,
|
1070
1147
|
): Handler<T, TReq, TRes> =>
|
1071
1148
|
async function (req, res) {
|
1072
|
-
res.setHeader('Access-Control-Allow-Origin', '*')
|
1073
|
-
res.setHeader('Access-Control-Allow-Headers', '*')
|
1074
|
-
|
1075
|
-
// https://www.rfc-editor.org/rfc/rfc6749.html#section-5.1
|
1076
|
-
res.setHeader('Cache-Control', 'no-store')
|
1077
|
-
res.setHeader('Pragma', 'no-cache')
|
1078
|
-
|
1079
|
-
// https://datatracker.ietf.org/doc/html/rfc9449#section-8.2
|
1080
|
-
const dpopNonce = server.nextDpopNonce()
|
1081
|
-
if (dpopNonce) {
|
1082
|
-
const name = 'DPoP-Nonce'
|
1083
|
-
res.setHeader(name, dpopNonce)
|
1084
|
-
res.appendHeader('Access-Control-Expose-Headers', name)
|
1085
|
-
}
|
1086
|
-
|
1087
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
|
+
|
1088
1154
|
// Ensure we can agree on a content encoding & type before starting to
|
1089
1155
|
// build the JSON response.
|
1090
|
-
if (!
|
1156
|
+
if (!negotiateContent(req, ['application/json'])) {
|
1091
1157
|
throw createHttpError(406, 'Unsupported media type')
|
1092
1158
|
}
|
1093
1159
|
|
@@ -1098,39 +1164,107 @@ export class OAuthProvider extends OAuthVerifier {
|
|
1098
1164
|
res.writeHead(status ?? 204).end()
|
1099
1165
|
}
|
1100
1166
|
} catch (err) {
|
1101
|
-
|
1102
|
-
if (err instanceof WWWAuthenticateError) {
|
1103
|
-
const name = 'WWW-Authenticate'
|
1104
|
-
res.setHeader(name, err.wwwAuthenticateHeader)
|
1105
|
-
res.appendHeader('Access-Control-Expose-Headers', name)
|
1106
|
-
}
|
1167
|
+
onError?.(req, res, err, 'OAuth request error')
|
1107
1168
|
|
1169
|
+
if (!res.headersSent) {
|
1108
1170
|
const payload = buildErrorPayload(err)
|
1109
1171
|
const status = buildErrorStatus(err)
|
1110
1172
|
writeJson(res, payload, { status })
|
1111
1173
|
} else {
|
1112
1174
|
res.destroy()
|
1113
1175
|
}
|
1114
|
-
|
1115
|
-
// OAuthError are used to build expected responses, so we don't log
|
1116
|
-
// them as errors.
|
1117
|
-
if (!(err instanceof OAuthError) || err.statusCode >= 500) {
|
1118
|
-
onError?.(req, res, err, 'Unexpected error')
|
1119
|
-
}
|
1120
1176
|
}
|
1121
1177
|
}
|
1122
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
|
+
|
1123
1258
|
const navigationHandler = <T, TReq extends Req, TRes extends Res>(
|
1124
|
-
handler: (this: T, req: TReq, res: TRes) =>
|
1259
|
+
handler: (this: T, req: TReq, res: TRes) => Awaitable<void>,
|
1125
1260
|
): Handler<T, TReq, TRes> =>
|
1126
1261
|
async function (req, res) {
|
1127
|
-
|
1128
|
-
|
1262
|
+
try {
|
1263
|
+
res.setHeader('Cache-Control', 'no-store')
|
1264
|
+
res.setHeader('Pragma', 'no-cache')
|
1129
1265
|
|
1130
|
-
|
1131
|
-
res.setHeader('Pragma', 'no-cache')
|
1266
|
+
res.setHeader('Referrer-Policy', 'same-origin')
|
1132
1267
|
|
1133
|
-
try {
|
1134
1268
|
validateFetchMode(req, res, ['navigate'])
|
1135
1269
|
validateFetchDest(req, res, ['document'])
|
1136
1270
|
validateSameOrigin(req, res, issuerOrigin)
|
@@ -1145,11 +1279,78 @@ export class OAuthProvider extends OAuthVerifier {
|
|
1145
1279
|
)
|
1146
1280
|
|
1147
1281
|
if (!res.headersSent) {
|
1148
|
-
await outputManager.sendErrorPage(res, err
|
1282
|
+
await server.outputManager.sendErrorPage(res, err, {
|
1283
|
+
preferredLocales: extractLocales(req),
|
1284
|
+
})
|
1149
1285
|
}
|
1150
1286
|
}
|
1151
1287
|
}
|
1152
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
|
+
|
1153
1354
|
/**
|
1154
1355
|
* Provides a better UX when a request is denied by redirecting to the
|
1155
1356
|
* client with the error details. This will also log any error that caused
|
@@ -1176,44 +1377,26 @@ export class OAuthProvider extends OAuthVerifier {
|
|
1176
1377
|
|
1177
1378
|
//- Public OAuth endpoints
|
1178
1379
|
|
1380
|
+
router.options('/.well-known/oauth-authorization-server', corsPreflight)
|
1179
1381
|
router.get(
|
1180
1382
|
'/.well-known/oauth-authorization-server',
|
1181
|
-
|
1383
|
+
corsHeaders,
|
1384
|
+
cacheControlMiddleware(300),
|
1385
|
+
staticJsonMiddleware(server.metadata),
|
1182
1386
|
)
|
1183
1387
|
|
1184
|
-
|
1185
|
-
|
1186
|
-
|
1187
|
-
|
1188
|
-
|
1189
|
-
|
1190
|
-
|
1191
|
-
// > specified as a wildcard; the value tells browsers to allow
|
1192
|
-
// > requesting code from any origin to access the resource.
|
1193
|
-
// > Attempting to use the wildcard with credentials results in an
|
1194
|
-
// > error.
|
1195
|
-
//
|
1196
|
-
// A "*" is safer to use than reflecting the request origin.
|
1197
|
-
'Access-Control-Allow-Origin': '*',
|
1198
|
-
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods
|
1199
|
-
// > The value "*" only counts as a special wildcard value for
|
1200
|
-
// > requests without credentials (requests without HTTP cookies or
|
1201
|
-
// > HTTP authentication information). In requests with credentials,
|
1202
|
-
// > it is treated as the literal method name "*" without special
|
1203
|
-
// > semantics.
|
1204
|
-
'Access-Control-Allow-Methods': '*',
|
1205
|
-
'Access-Control-Allow-Headers': 'Content-Type,Authorization,DPoP',
|
1206
|
-
'Access-Control-Max-Age': '86400', // 1 day
|
1207
|
-
})
|
1208
|
-
.end()
|
1209
|
-
}
|
1210
|
-
|
1211
|
-
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
|
+
)
|
1212
1395
|
|
1213
1396
|
router.options('/oauth/par', corsPreflight)
|
1214
1397
|
router.post(
|
1215
1398
|
'/oauth/par',
|
1216
|
-
|
1399
|
+
oauthHandler(async function (req, _res) {
|
1217
1400
|
const payload = await parseHttpRequest(req, ['json', 'urlencoded'])
|
1218
1401
|
|
1219
1402
|
const credentials = await oauthClientCredentialsSchema
|
@@ -1237,11 +1420,9 @@ export class OAuthProvider extends OAuthVerifier {
|
|
1237
1420
|
)
|
1238
1421
|
}, 201),
|
1239
1422
|
)
|
1240
|
-
|
1241
1423
|
// https://datatracker.ietf.org/doc/html/rfc9126#section-2.3
|
1242
1424
|
// > If the request did not use the POST method, the authorization server
|
1243
1425
|
// > responds with an HTTP 405 (Method Not Allowed) status code.
|
1244
|
-
router.options('/oauth/par', corsPreflight)
|
1245
1426
|
router.all('/oauth/par', (req, res) => {
|
1246
1427
|
res.writeHead(405).end()
|
1247
1428
|
})
|
@@ -1249,10 +1430,11 @@ export class OAuthProvider extends OAuthVerifier {
|
|
1249
1430
|
router.options('/oauth/token', corsPreflight)
|
1250
1431
|
router.post(
|
1251
1432
|
'/oauth/token',
|
1252
|
-
|
1433
|
+
oauthHandler(async function (req, _res) {
|
1253
1434
|
const payload = await parseHttpRequest(req, ['json', 'urlencoded'])
|
1254
1435
|
|
1255
|
-
const clientMetadata =
|
1436
|
+
const clientMetadata =
|
1437
|
+
await server.deviceManager.getRequestMetadata(req)
|
1256
1438
|
|
1257
1439
|
const clientCredentials = await oauthClientCredentialsSchema
|
1258
1440
|
.parseAsync(payload, { path: ['body'] })
|
@@ -1280,7 +1462,7 @@ export class OAuthProvider extends OAuthVerifier {
|
|
1280
1462
|
router.options('/oauth/revoke', corsPreflight)
|
1281
1463
|
router.post(
|
1282
1464
|
'/oauth/revoke',
|
1283
|
-
|
1465
|
+
oauthHandler(async function (req, res) {
|
1284
1466
|
const payload = await parseHttpRequest(req, ['json', 'urlencoded'])
|
1285
1467
|
|
1286
1468
|
const tokenIdentification = await oauthTokenIdentificationSchema
|
@@ -1294,8 +1476,6 @@ export class OAuthProvider extends OAuthVerifier {
|
|
1294
1476
|
}
|
1295
1477
|
}),
|
1296
1478
|
)
|
1297
|
-
|
1298
|
-
router.options('/oauth/revoke', corsPreflight)
|
1299
1479
|
router.get(
|
1300
1480
|
'/oauth/revoke',
|
1301
1481
|
navigationHandler(async function (req, res) {
|
@@ -1320,9 +1500,10 @@ export class OAuthProvider extends OAuthVerifier {
|
|
1320
1500
|
}),
|
1321
1501
|
)
|
1322
1502
|
|
1503
|
+
router.options('/oauth/introspect', corsPreflight)
|
1323
1504
|
router.post(
|
1324
1505
|
'/oauth/introspect',
|
1325
|
-
|
1506
|
+
oauthHandler(async function (req, _res) {
|
1326
1507
|
const payload = await parseHttpRequest(req, ['json', 'urlencoded'])
|
1327
1508
|
|
1328
1509
|
const credentials = await oauthClientCredentialsSchema
|
@@ -1349,7 +1530,7 @@ export class OAuthProvider extends OAuthVerifier {
|
|
1349
1530
|
const query = Object.fromEntries(this.url.searchParams)
|
1350
1531
|
|
1351
1532
|
const clientCredentials = await oauthClientCredentialsSchema
|
1352
|
-
.parseAsync(query, { path: ['
|
1533
|
+
.parseAsync(query, { path: ['query'] })
|
1353
1534
|
.catch(throwInvalidRequest)
|
1354
1535
|
|
1355
1536
|
if ('client_secret' in clientCredentials) {
|
@@ -1360,9 +1541,14 @@ export class OAuthProvider extends OAuthVerifier {
|
|
1360
1541
|
.parseAsync(query, { path: ['query'] })
|
1361
1542
|
.catch(throwInvalidRequest)
|
1362
1543
|
|
1363
|
-
const { deviceId, deviceMetadata } = await deviceManager.load(
|
1544
|
+
const { deviceId, deviceMetadata } = await server.deviceManager.load(
|
1545
|
+
req,
|
1546
|
+
res,
|
1547
|
+
)
|
1364
1548
|
|
1365
|
-
const
|
1549
|
+
const result:
|
1550
|
+
| AuthorizationResultRedirect
|
1551
|
+
| AuthorizationResultAuthorize = await server
|
1366
1552
|
.authorize(
|
1367
1553
|
clientCredentials,
|
1368
1554
|
authorizationRequest,
|
@@ -1371,170 +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 deviceManager.load(req, res)
|
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')
|
1475
1620
|
|
1476
|
-
|
1477
|
-
.acceptRequest(
|
1478
|
-
input.request_uri,
|
1479
|
-
input.client_id,
|
1480
|
-
input.account_sub,
|
1481
|
-
deviceId,
|
1482
|
-
deviceMetadata,
|
1483
|
-
)
|
1621
|
+
return server
|
1622
|
+
.acceptRequest(ctx, sub)
|
1484
1623
|
.catch((err) => accessDeniedToRedirectCatcher(req, res, err))
|
1485
|
-
|
1486
|
-
return await sendAuthorizeRedirect(res, data)
|
1487
1624
|
}),
|
1488
1625
|
)
|
1489
1626
|
|
1490
|
-
const rejectQuerySchema = z.object({
|
1491
|
-
csrf_token: z.string(),
|
1492
|
-
request_uri: requestUriSchema,
|
1493
|
-
client_id: clientIdSchema,
|
1494
|
-
})
|
1495
|
-
|
1496
|
-
// Though this is a "no-cors" request, meaning that the browser will allow
|
1497
|
-
// any cross-origin request, with credentials, to be sent, the handler will
|
1498
|
-
// 1) validate the request origin,
|
1499
|
-
// 2) validate the CSRF token,
|
1500
|
-
// 3) validate the referer,
|
1501
|
-
// 4) validate the sec-fetch-site header,
|
1502
|
-
// 4) validate the sec-fetch-mode header,
|
1503
|
-
// 5) validate the sec-fetch-dest header (see navigationHandler).
|
1504
|
-
// And will error if any of these checks fail.
|
1505
1627
|
router.get(
|
1506
1628
|
'/oauth/authorize/reject',
|
1507
|
-
|
1508
|
-
|
1509
|
-
|
1510
|
-
const query = Object.fromEntries(this.url.searchParams)
|
1511
|
-
const input = await rejectQuerySchema.parseAsync(query, {
|
1512
|
-
path: ['query'],
|
1513
|
-
})
|
1514
|
-
|
1515
|
-
validateReferer(req, res, {
|
1516
|
-
origin: issuerOrigin,
|
1517
|
-
pathname: '/oauth/authorize',
|
1518
|
-
searchParams: [
|
1519
|
-
['request_uri', input.request_uri],
|
1520
|
-
['client_id', input.client_id],
|
1521
|
-
],
|
1522
|
-
})
|
1523
|
-
validateCsrfToken(
|
1524
|
-
req,
|
1525
|
-
res,
|
1526
|
-
input.csrf_token,
|
1527
|
-
csrfCookie(input.request_uri),
|
1528
|
-
true,
|
1529
|
-
)
|
1530
|
-
|
1531
|
-
const { deviceId } = await deviceManager.load(req, res)
|
1532
|
-
|
1533
|
-
const data = await server
|
1534
|
-
.rejectRequest(deviceId, input.request_uri, input.client_id)
|
1629
|
+
authorizeRedirectNavigationHandler(async function (req, res, ctx) {
|
1630
|
+
return server
|
1631
|
+
.rejectRequest(ctx)
|
1535
1632
|
.catch((err) => accessDeniedToRedirectCatcher(req, res, err))
|
1536
|
-
|
1537
|
-
return await sendAuthorizeRedirect(res, data)
|
1538
1633
|
}),
|
1539
1634
|
)
|
1540
1635
|
|