@atproto/oauth-provider 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.linguirc +57 -0
- package/CHANGELOG.md +29 -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 +241 -0
- package/dist/output/build-customization-data.d.ts.map +1 -0
- package/dist/output/build-customization-data.js +174 -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 +403 -307
- package/src/oauth-verifier.ts +3 -1
- package/src/output/build-authorize-data.ts +1 -3
- package/src/output/build-customization-data.ts +228 -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,28 @@ 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
|
+
Branding,
|
107
|
+
BrandingInput,
|
108
|
+
Customization,
|
109
|
+
CustomizationInput,
|
110
|
+
customizationSchema,
|
111
|
+
} from './output/build-customization-data.js'
|
94
112
|
import {
|
95
113
|
buildErrorPayload,
|
96
114
|
buildErrorStatus,
|
97
115
|
} from './output/build-error-payload.js'
|
98
|
-
import { Customization } from './output/customization.js'
|
99
116
|
import { OutputManager } from './output/output-manager.js'
|
100
117
|
import {
|
101
118
|
AuthorizationResultRedirect,
|
@@ -114,32 +131,38 @@ import { TokenManager } from './token/token-manager.js'
|
|
114
131
|
import { TokenStore, asTokenStore } from './token/token-store.js'
|
115
132
|
import { VerifyTokenClaimsOptions } from './token/verify-token-claims.js'
|
116
133
|
|
117
|
-
export type OAuthProviderStore = Partial<
|
118
|
-
ClientStore &
|
119
|
-
AccountStore &
|
120
|
-
DeviceStore &
|
121
|
-
TokenStore &
|
122
|
-
RequestStore &
|
123
|
-
ReplayStore
|
124
|
-
>
|
125
|
-
|
126
134
|
export {
|
135
|
+
type Branding,
|
136
|
+
type BrandingInput,
|
127
137
|
type CustomMetadata,
|
128
138
|
type Customization,
|
139
|
+
type CustomizationInput,
|
129
140
|
type Handler,
|
141
|
+
type HcaptchaConfig,
|
130
142
|
Keyset,
|
131
143
|
type OAuthAuthorizationServerMetadata,
|
132
144
|
}
|
133
145
|
|
146
|
+
type ApiContext = {
|
147
|
+
requestUri: RequestUri
|
148
|
+
deviceId: DeviceId
|
149
|
+
deviceMetadata: RequestMetadata
|
150
|
+
}
|
151
|
+
|
152
|
+
export type ErrorHandler<
|
153
|
+
Req extends IncomingMessage = IncomingMessage,
|
154
|
+
Res extends ServerResponse = ServerResponse,
|
155
|
+
> = (req: Req, res: Res, err: unknown, message: string) => void
|
156
|
+
|
134
157
|
export type RouterOptions<
|
135
158
|
Req extends IncomingMessage = IncomingMessage,
|
136
159
|
Res extends ServerResponse = ServerResponse,
|
137
160
|
> = {
|
138
|
-
onError?:
|
161
|
+
onError?: ErrorHandler<Req, Res>
|
139
162
|
}
|
140
163
|
|
141
164
|
export type OAuthProviderOptions = Override<
|
142
|
-
OAuthVerifierOptions & OAuthHooks & DeviceManagerOptions,
|
165
|
+
OAuthVerifierOptions & OAuthHooks & DeviceManagerOptions & CustomizationInput,
|
143
166
|
{
|
144
167
|
/**
|
145
168
|
* Maximum age a device/account session can be before requiring
|
@@ -157,11 +180,6 @@ export type OAuthProviderOptions = Override<
|
|
157
180
|
*/
|
158
181
|
metadata?: CustomMetadata
|
159
182
|
|
160
|
-
/**
|
161
|
-
* UI customizations
|
162
|
-
*/
|
163
|
-
customization?: Customization
|
164
|
-
|
165
183
|
/**
|
166
184
|
* A custom fetch function that can be used to fetch the client metadata from
|
167
185
|
* the internet. By default, the fetch function is a safeFetchWrap() function
|
@@ -184,11 +202,18 @@ export type OAuthProviderOptions = Override<
|
|
184
202
|
* this store implements all the interfaces not provided in the other
|
185
203
|
* `<name>Store` options.
|
186
204
|
*/
|
187
|
-
store?:
|
205
|
+
store?: Partial<
|
206
|
+
AccountStore &
|
207
|
+
ClientStore &
|
208
|
+
DeviceStore &
|
209
|
+
ReplayStore &
|
210
|
+
RequestStore &
|
211
|
+
TokenStore
|
212
|
+
>
|
188
213
|
|
189
214
|
accountStore?: AccountStore
|
190
|
-
deviceStore?: DeviceStore
|
191
215
|
clientStore?: ClientStore
|
216
|
+
deviceStore?: DeviceStore
|
192
217
|
replayStore?: ReplayStore
|
193
218
|
requestStore?: RequestStore
|
194
219
|
tokenStore?: TokenStore
|
@@ -235,7 +260,6 @@ export class OAuthProvider extends OAuthVerifier {
|
|
235
260
|
|
236
261
|
public constructor({
|
237
262
|
metadata,
|
238
|
-
customization = undefined,
|
239
263
|
authenticationMaxAge = AUTHENTICATION_MAX_AGE,
|
240
264
|
tokenMaxAge = TOKEN_MAX_AGE,
|
241
265
|
|
@@ -264,10 +288,30 @@ export class OAuthProvider extends OAuthVerifier {
|
|
264
288
|
|
265
289
|
loopbackMetadata = atprotoLoopbackClientMetadata,
|
266
290
|
|
267
|
-
// OAuthHooks &
|
291
|
+
// OAuthHooks &
|
292
|
+
// OAuthVerifierOptions &
|
293
|
+
// DeviceManagerOptions &
|
294
|
+
// Customization
|
268
295
|
...rest
|
269
296
|
}: OAuthProviderOptions) {
|
270
|
-
|
297
|
+
const customization: Customization = customizationSchema.parse(rest)
|
298
|
+
const deviceManagerOptions: DeviceManagerOptions =
|
299
|
+
deviceManagerOptionsSchema.parse(rest)
|
300
|
+
|
301
|
+
// @NOTE: hooks don't really need a type parser, as all zod can actually
|
302
|
+
// check at runtime is the fact that the values are functions. The only way
|
303
|
+
// we would benefit from zod here would be to wrap the functions with a
|
304
|
+
// validator for the provided function's return types, which we do not add
|
305
|
+
// because it would impact runtime performance and we trust the users of
|
306
|
+
// this lib (basically ourselves) to rely on the typing system to ensure the
|
307
|
+
// correct types are returned.
|
308
|
+
const hooks: OAuthHooks = rest
|
309
|
+
|
310
|
+
// @NOTE: validation of super params (if we wanted to implement it) should
|
311
|
+
// be the responsibility of the super class.
|
312
|
+
const superOptions: OAuthVerifierOptions = rest
|
313
|
+
|
314
|
+
super({ replayStore, redis, ...superOptions })
|
271
315
|
|
272
316
|
requestStore ??= redis
|
273
317
|
? new RequestStoreRedis({ redis })
|
@@ -276,13 +320,18 @@ export class OAuthProvider extends OAuthVerifier {
|
|
276
320
|
this.authenticationMaxAge = authenticationMaxAge
|
277
321
|
this.metadata = buildMetadata(this.issuer, this.keyset, metadata)
|
278
322
|
|
279
|
-
this.deviceManager = new DeviceManager(deviceStore,
|
323
|
+
this.deviceManager = new DeviceManager(deviceStore, deviceManagerOptions)
|
280
324
|
this.outputManager = new OutputManager(customization)
|
281
|
-
this.accountManager = new AccountManager(
|
325
|
+
this.accountManager = new AccountManager(
|
326
|
+
this.issuer,
|
327
|
+
accountStore,
|
328
|
+
hooks,
|
329
|
+
customization,
|
330
|
+
)
|
282
331
|
this.clientManager = new ClientManager(
|
283
332
|
this.metadata,
|
284
333
|
this.keyset,
|
285
|
-
|
334
|
+
hooks,
|
286
335
|
clientStore || null,
|
287
336
|
loopbackMetadata || null,
|
288
337
|
safeFetch,
|
@@ -293,12 +342,12 @@ export class OAuthProvider extends OAuthVerifier {
|
|
293
342
|
requestStore,
|
294
343
|
this.signer,
|
295
344
|
this.metadata,
|
296
|
-
|
345
|
+
hooks,
|
297
346
|
)
|
298
347
|
this.tokenManager = new TokenManager(
|
299
348
|
tokenStore,
|
300
349
|
this.signer,
|
301
|
-
|
350
|
+
hooks,
|
302
351
|
this.accessTokenType,
|
303
352
|
tokenMaxAge,
|
304
353
|
)
|
@@ -451,8 +500,7 @@ export class OAuthProvider extends OAuthVerifier {
|
|
451
500
|
// https://datatracker.ietf.org/doc/html/rfc9126#section-2.3-1
|
452
501
|
// > Since initial processing of the pushed authorization request does not
|
453
502
|
// > involve resource owner interaction, error codes related to user
|
454
|
-
// > interaction, such as
|
455
|
-
// > returned.
|
503
|
+
// > interaction, such as "access_denied", are never returned.
|
456
504
|
if (err instanceof AccessDeniedError) {
|
457
505
|
throw new InvalidRequestError(err.error_description, err)
|
458
506
|
}
|
@@ -470,7 +518,7 @@ export class OAuthProvider extends OAuthVerifier {
|
|
470
518
|
.parseAsync(query.request_uri, { path: ['query', 'request_uri'] })
|
471
519
|
.catch(throwInvalidRequest)
|
472
520
|
|
473
|
-
return this.requestManager.get(requestUri, client.id
|
521
|
+
return this.requestManager.get(requestUri, deviceId, client.id)
|
474
522
|
}
|
475
523
|
|
476
524
|
if ('request' in query) {
|
@@ -514,11 +562,11 @@ export class OAuthProvider extends OAuthVerifier {
|
|
514
562
|
}
|
515
563
|
|
516
564
|
private async deleteRequest(
|
517
|
-
|
565
|
+
requestUri: RequestUri,
|
518
566
|
parameters: OAuthAuthorizationRequestParameters,
|
519
567
|
) {
|
520
568
|
try {
|
521
|
-
await this.requestManager.delete(
|
569
|
+
await this.requestManager.delete(requestUri)
|
522
570
|
} catch (err) {
|
523
571
|
throw AccessDeniedError.from(parameters, err)
|
524
572
|
}
|
@@ -690,24 +738,46 @@ export class OAuthProvider extends OAuthVerifier {
|
|
690
738
|
}))
|
691
739
|
}
|
692
740
|
|
693
|
-
protected async
|
694
|
-
deviceId:
|
695
|
-
|
696
|
-
clientId: ClientId,
|
697
|
-
credentials: SignInCredentials,
|
741
|
+
protected async signUp(
|
742
|
+
{ requestUri, deviceId, deviceMetadata }: ApiContext,
|
743
|
+
data: SignUpData,
|
698
744
|
): Promise<{
|
699
745
|
account: Account
|
700
746
|
consentRequired: boolean
|
701
747
|
}> {
|
748
|
+
const { clientId } = await this.requestManager.get(requestUri, deviceId)
|
749
|
+
|
702
750
|
const client = await this.clientManager.getClient(clientId)
|
703
751
|
|
752
|
+
const { account } = await this.accountManager.signUp(
|
753
|
+
data,
|
754
|
+
deviceId,
|
755
|
+
deviceMetadata,
|
756
|
+
)
|
757
|
+
|
758
|
+
return {
|
759
|
+
account,
|
760
|
+
consentRequired: !client.info.isFirstParty,
|
761
|
+
}
|
762
|
+
}
|
763
|
+
|
764
|
+
protected async signIn(
|
765
|
+
{ requestUri, deviceId, deviceMetadata }: ApiContext,
|
766
|
+
data: SignInData,
|
767
|
+
): Promise<{
|
768
|
+
account: Account
|
769
|
+
consentRequired: boolean
|
770
|
+
}> {
|
704
771
|
// Ensure the request is still valid (and update the request expiration)
|
705
772
|
// @TODO use the returned scopes to determine if consent is required
|
706
|
-
await this.requestManager.get(
|
773
|
+
const { clientId } = await this.requestManager.get(requestUri, deviceId)
|
774
|
+
|
775
|
+
const client = await this.clientManager.getClient(clientId)
|
707
776
|
|
708
777
|
const { account, info } = await this.accountManager.signIn(
|
709
|
-
|
778
|
+
data,
|
710
779
|
deviceId,
|
780
|
+
deviceMetadata,
|
711
781
|
)
|
712
782
|
|
713
783
|
return {
|
@@ -722,22 +792,21 @@ export class OAuthProvider extends OAuthVerifier {
|
|
722
792
|
}
|
723
793
|
|
724
794
|
protected async acceptRequest(
|
725
|
-
|
726
|
-
clientId: ClientId,
|
795
|
+
{ requestUri, deviceId, deviceMetadata }: ApiContext,
|
727
796
|
sub: string,
|
728
|
-
deviceId: DeviceId,
|
729
|
-
deviceMetadata: RequestMetadata,
|
730
797
|
): Promise<AuthorizationResultRedirect> {
|
731
798
|
const { issuer } = this
|
732
|
-
const client = await this.clientManager.getClient(clientId)
|
733
799
|
|
734
|
-
const { parameters, clientAuth } = await this.requestManager.get(
|
735
|
-
|
736
|
-
clientId,
|
800
|
+
const { parameters, clientId, clientAuth } = await this.requestManager.get(
|
801
|
+
requestUri,
|
737
802
|
deviceId,
|
738
803
|
)
|
739
804
|
|
805
|
+
const client = await this.clientManager.getClient(clientId)
|
806
|
+
|
740
807
|
try {
|
808
|
+
// @TODO Currently, a user can "accept" a request for any did that sing-in
|
809
|
+
// on the device, even if "remember" was set to false.
|
741
810
|
const { account, info } = await this.accountManager.get(deviceId, sub)
|
742
811
|
|
743
812
|
// The user is trying to authorize without a fresh login
|
@@ -749,7 +818,7 @@ export class OAuthProvider extends OAuthVerifier {
|
|
749
818
|
}
|
750
819
|
|
751
820
|
const code = await this.requestManager.setAuthorized(
|
752
|
-
|
821
|
+
requestUri,
|
753
822
|
client,
|
754
823
|
account,
|
755
824
|
deviceId,
|
@@ -765,24 +834,19 @@ export class OAuthProvider extends OAuthVerifier {
|
|
765
834
|
|
766
835
|
return { issuer, parameters, redirect: { code } }
|
767
836
|
} catch (err) {
|
768
|
-
await this.deleteRequest(
|
837
|
+
await this.deleteRequest(requestUri, parameters)
|
769
838
|
|
770
839
|
throw AccessDeniedError.from(parameters, err)
|
771
840
|
}
|
772
841
|
}
|
773
842
|
|
774
|
-
protected async rejectRequest(
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
const { parameters } = await this.requestManager.get(
|
780
|
-
uri,
|
781
|
-
clientId,
|
782
|
-
deviceId,
|
783
|
-
)
|
843
|
+
protected async rejectRequest({
|
844
|
+
requestUri,
|
845
|
+
deviceId,
|
846
|
+
}: ApiContext): Promise<AuthorizationResultRedirect> {
|
847
|
+
const { parameters } = await this.requestManager.get(requestUri, deviceId)
|
784
848
|
|
785
|
-
await this.deleteRequest(
|
849
|
+
await this.deleteRequest(requestUri, parameters)
|
786
850
|
|
787
851
|
return {
|
788
852
|
issuer: this.issuer,
|
@@ -1033,57 +1097,67 @@ export class OAuthProvider extends OAuthVerifier {
|
|
1033
1097
|
|
1034
1098
|
// Utils
|
1035
1099
|
|
1036
|
-
const csrfCookie = (
|
1037
|
-
const onError =
|
1100
|
+
const csrfCookie = (requestUri: RequestUri) => `csrf-${requestUri}`
|
1101
|
+
const onError: null | ErrorHandler<Req, Res> =
|
1038
1102
|
options?.onError ??
|
1039
1103
|
(process.env['NODE_ENV'] === 'development'
|
1040
|
-
? (req, res, err, msg)
|
1104
|
+
? (req, res, err, msg) => {
|
1041
1105
|
console.error(`OAuthProvider error (${msg}):`, err)
|
1042
|
-
|
1106
|
+
}
|
1107
|
+
: null)
|
1043
1108
|
|
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', '*')
|
1109
|
+
// CORS preflight
|
1110
|
+
const corsHeaders: Middleware = function (req, res, next) {
|
1111
|
+
res.setHeader('Access-Control-Max-Age', '86400') // 1 day
|
1052
1112
|
|
1053
|
-
|
1054
|
-
|
1055
|
-
|
1056
|
-
|
1057
|
-
|
1113
|
+
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
|
1114
|
+
//
|
1115
|
+
// > For requests without credentials, the literal value "*" can be
|
1116
|
+
// > specified as a wildcard; the value tells browsers to allow
|
1117
|
+
// > requesting code from any origin to access the resource.
|
1118
|
+
// > Attempting to use the wildcard with credentials results in an
|
1119
|
+
// > error.
|
1120
|
+
//
|
1121
|
+
// A "*" is safer to use than reflecting the request origin.
|
1122
|
+
res.setHeader('Access-Control-Allow-Origin', '*')
|
1123
|
+
|
1124
|
+
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods
|
1125
|
+
// > The value "*" only counts as a special wildcard value for
|
1126
|
+
// > requests without credentials (requests without HTTP cookies or
|
1127
|
+
// > HTTP authentication information). In requests with credentials,
|
1128
|
+
// > it is treated as the literal method name "*" without special
|
1129
|
+
// > semantics.
|
1130
|
+
res.setHeader('Access-Control-Allow-Methods', '*')
|
1131
|
+
|
1132
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type,DPoP')
|
1133
|
+
|
1134
|
+
next()
|
1135
|
+
}
|
1136
|
+
|
1137
|
+
const corsPreflight: Middleware = combineMiddlewares([
|
1138
|
+
corsHeaders,
|
1139
|
+
(req, res) => {
|
1140
|
+
res.writeHead(200).end()
|
1141
|
+
},
|
1142
|
+
])
|
1058
1143
|
|
1059
1144
|
/**
|
1060
1145
|
* Wrap an OAuth endpoint in a middleware that will set the appropriate
|
1061
1146
|
* response headers and format the response as JSON.
|
1062
1147
|
*/
|
1063
1148
|
const jsonHandler = <T, TReq extends Req, TRes extends Res, Json>(
|
1064
|
-
buildJson: (this: T, req: TReq, res: TRes) =>
|
1149
|
+
buildJson: (this: T, req: TReq, res: TRes) => Awaitable<Json>,
|
1065
1150
|
status?: number,
|
1066
1151
|
): Handler<T, TReq, TRes> =>
|
1067
1152
|
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
1153
|
try {
|
1154
|
+
// https://www.rfc-editor.org/rfc/rfc6749.html#section-5.1
|
1155
|
+
res.setHeader('Cache-Control', 'no-store')
|
1156
|
+
res.setHeader('Pragma', 'no-cache')
|
1157
|
+
|
1084
1158
|
// Ensure we can agree on a content encoding & type before starting to
|
1085
1159
|
// build the JSON response.
|
1086
|
-
if (!
|
1160
|
+
if (!negotiateContent(req, ['application/json'])) {
|
1087
1161
|
throw createHttpError(406, 'Unsupported media type')
|
1088
1162
|
}
|
1089
1163
|
|
@@ -1094,39 +1168,107 @@ export class OAuthProvider extends OAuthVerifier {
|
|
1094
1168
|
res.writeHead(status ?? 204).end()
|
1095
1169
|
}
|
1096
1170
|
} 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
|
-
}
|
1171
|
+
onError?.(req, res, err, 'OAuth request error')
|
1103
1172
|
|
1173
|
+
if (!res.headersSent) {
|
1104
1174
|
const payload = buildErrorPayload(err)
|
1105
1175
|
const status = buildErrorStatus(err)
|
1106
1176
|
writeJson(res, payload, { status })
|
1107
1177
|
} else {
|
1108
1178
|
res.destroy()
|
1109
1179
|
}
|
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
1180
|
}
|
1117
1181
|
}
|
1118
1182
|
|
1183
|
+
const oauthHandler = <T, TReq extends Req, TRes extends Res, Json>(
|
1184
|
+
buildOAuthResponse: (this: T, req: TReq, res: TRes) => Awaitable<Json>,
|
1185
|
+
status?: number,
|
1186
|
+
) =>
|
1187
|
+
combineMiddlewares([
|
1188
|
+
corsHeaders,
|
1189
|
+
jsonHandler<T, TReq, TRes, Json>(async function (req, res) {
|
1190
|
+
try {
|
1191
|
+
// https://datatracker.ietf.org/doc/html/rfc9449#section-8.2
|
1192
|
+
const dpopNonce = server.nextDpopNonce()
|
1193
|
+
if (dpopNonce) {
|
1194
|
+
const name = 'DPoP-Nonce'
|
1195
|
+
res.setHeader(name, dpopNonce)
|
1196
|
+
res.appendHeader('Access-Control-Expose-Headers', name)
|
1197
|
+
}
|
1198
|
+
|
1199
|
+
return await buildOAuthResponse.call(this, req, res)
|
1200
|
+
} catch (err) {
|
1201
|
+
if (!res.headersSent && err instanceof WWWAuthenticateError) {
|
1202
|
+
const name = 'WWW-Authenticate'
|
1203
|
+
res.setHeader(name, err.wwwAuthenticateHeader)
|
1204
|
+
res.appendHeader('Access-Control-Expose-Headers', name)
|
1205
|
+
}
|
1206
|
+
|
1207
|
+
throw err
|
1208
|
+
}
|
1209
|
+
}, status),
|
1210
|
+
])
|
1211
|
+
|
1212
|
+
const apiHandler = <
|
1213
|
+
T,
|
1214
|
+
TReq extends Req,
|
1215
|
+
TRes extends Res,
|
1216
|
+
S extends z.ZodTypeAny,
|
1217
|
+
Json,
|
1218
|
+
>(
|
1219
|
+
inputSchema: S,
|
1220
|
+
buildJson: (
|
1221
|
+
this: T,
|
1222
|
+
req: TReq,
|
1223
|
+
res: TRes,
|
1224
|
+
input: z.infer<S>,
|
1225
|
+
context: ApiContext,
|
1226
|
+
) => Json | Promise<Json>,
|
1227
|
+
status?: number,
|
1228
|
+
) =>
|
1229
|
+
jsonHandler<T, TReq, TRes, Json>(async function (req, res) {
|
1230
|
+
validateFetchMode(req, res, ['same-origin'])
|
1231
|
+
validateFetchSite(req, res, ['same-origin'])
|
1232
|
+
validateSameOrigin(req, res, issuerOrigin)
|
1233
|
+
const referer = validateReferer(req, res, {
|
1234
|
+
origin: issuerOrigin,
|
1235
|
+
pathname: '/oauth/authorize',
|
1236
|
+
})
|
1237
|
+
|
1238
|
+
const requestUri = await requestUriSchema.parseAsync(
|
1239
|
+
referer.searchParams.get('request_uri'),
|
1240
|
+
{ path: ['query', 'request_uri'] },
|
1241
|
+
)
|
1242
|
+
|
1243
|
+
validateCsrfToken(
|
1244
|
+
req,
|
1245
|
+
res,
|
1246
|
+
req.headers['x-csrf-token'],
|
1247
|
+
csrfCookie(requestUri),
|
1248
|
+
)
|
1249
|
+
|
1250
|
+
const { deviceId, deviceMetadata } = await server.deviceManager.load(
|
1251
|
+
req,
|
1252
|
+
res,
|
1253
|
+
)
|
1254
|
+
|
1255
|
+
const payload = await parseHttpRequest(req, ['json'])
|
1256
|
+
const input = await inputSchema.parseAsync(payload, { path: ['body'] })
|
1257
|
+
|
1258
|
+
const context: ApiContext = { requestUri, deviceId, deviceMetadata }
|
1259
|
+
return buildJson.call(this, req, res, input, context)
|
1260
|
+
}, status)
|
1261
|
+
|
1119
1262
|
const navigationHandler = <T, TReq extends Req, TRes extends Res>(
|
1120
|
-
handler: (this: T, req: TReq, res: TRes) =>
|
1263
|
+
handler: (this: T, req: TReq, res: TRes) => Awaitable<void>,
|
1121
1264
|
): Handler<T, TReq, TRes> =>
|
1122
1265
|
async function (req, res) {
|
1123
|
-
|
1124
|
-
|
1266
|
+
try {
|
1267
|
+
res.setHeader('Cache-Control', 'no-store')
|
1268
|
+
res.setHeader('Pragma', 'no-cache')
|
1125
1269
|
|
1126
|
-
|
1127
|
-
res.setHeader('Pragma', 'no-cache')
|
1270
|
+
res.setHeader('Referrer-Policy', 'same-origin')
|
1128
1271
|
|
1129
|
-
try {
|
1130
1272
|
validateFetchMode(req, res, ['navigate'])
|
1131
1273
|
validateFetchDest(req, res, ['document'])
|
1132
1274
|
validateSameOrigin(req, res, issuerOrigin)
|
@@ -1141,11 +1283,78 @@ export class OAuthProvider extends OAuthVerifier {
|
|
1141
1283
|
)
|
1142
1284
|
|
1143
1285
|
if (!res.headersSent) {
|
1144
|
-
await server.outputManager.sendErrorPage(res, err
|
1286
|
+
await server.outputManager.sendErrorPage(res, err, {
|
1287
|
+
preferredLocales: extractLocales(req),
|
1288
|
+
})
|
1145
1289
|
}
|
1146
1290
|
}
|
1147
1291
|
}
|
1148
1292
|
|
1293
|
+
// Simple GET requests fall under the category of "no-cors" request, meaning
|
1294
|
+
// that the browser will allow any cross-origin request, with credentials,
|
1295
|
+
// to be sent to the oauth server. The OAuth Server will, however:
|
1296
|
+
// 1) validate the request origin (see navigationHandler),
|
1297
|
+
// 2) validate the CSRF token,
|
1298
|
+
// 3) validate the referer,
|
1299
|
+
// 4) validate the sec-fetch-site header,
|
1300
|
+
// 4) validate the sec-fetch-mode header (see navigationHandler),
|
1301
|
+
// 5) validate the sec-fetch-dest header (see navigationHandler).
|
1302
|
+
// And will error (refuse to serve the request) if any of these checks fail.
|
1303
|
+
const sameOriginNavigationHandler = <
|
1304
|
+
T extends { url: URL },
|
1305
|
+
TReq extends Req,
|
1306
|
+
TRes extends Res,
|
1307
|
+
>(
|
1308
|
+
handler: (
|
1309
|
+
this: T,
|
1310
|
+
req: TReq,
|
1311
|
+
res: TRes,
|
1312
|
+
deviceInfo: DeviceInfo,
|
1313
|
+
) => Awaitable<void>,
|
1314
|
+
): Handler<T, TReq, TRes> =>
|
1315
|
+
navigationHandler(async function (req, res) {
|
1316
|
+
validateFetchSite(req, res, ['same-origin'])
|
1317
|
+
|
1318
|
+
const deviceInfo = await server.deviceManager.load(req, res)
|
1319
|
+
|
1320
|
+
return handler.call(this, req, res, deviceInfo)
|
1321
|
+
})
|
1322
|
+
|
1323
|
+
const authorizeRedirectNavigationHandler = <
|
1324
|
+
T extends { url: URL },
|
1325
|
+
TReq extends Req,
|
1326
|
+
TRes extends Res,
|
1327
|
+
>(
|
1328
|
+
handler: (
|
1329
|
+
this: T,
|
1330
|
+
req: TReq,
|
1331
|
+
res: TRes,
|
1332
|
+
context: ApiContext,
|
1333
|
+
) => Awaitable<AuthorizationResultRedirect>,
|
1334
|
+
): Handler<T, TReq, TRes> =>
|
1335
|
+
sameOriginNavigationHandler(async function (req, res, deviceInfo) {
|
1336
|
+
const referer = validateReferer(req, res, {
|
1337
|
+
origin: issuerOrigin,
|
1338
|
+
pathname: '/oauth/authorize',
|
1339
|
+
})
|
1340
|
+
|
1341
|
+
const requestUri = await requestUriSchema.parseAsync(
|
1342
|
+
referer.searchParams.get('request_uri'),
|
1343
|
+
)
|
1344
|
+
|
1345
|
+
const csrfToken = this.url.searchParams.get('csrf_token')
|
1346
|
+
const csrfCookieName = csrfCookie(requestUri)
|
1347
|
+
|
1348
|
+
// Next line will "clear" the CSRF token cookie, preventing replay of
|
1349
|
+
// this request (navigating "back" will result in an error).
|
1350
|
+
validateCsrfToken(req, res, csrfToken, csrfCookieName, true)
|
1351
|
+
|
1352
|
+
const context: ApiContext = { ...deviceInfo, requestUri }
|
1353
|
+
|
1354
|
+
const redirect = await handler.call(this, req, res, context)
|
1355
|
+
return sendAuthorizeRedirect(res, redirect)
|
1356
|
+
})
|
1357
|
+
|
1149
1358
|
/**
|
1150
1359
|
* Provides a better UX when a request is denied by redirecting to the
|
1151
1360
|
* client with the error details. This will also log any error that caused
|
@@ -1172,44 +1381,26 @@ export class OAuthProvider extends OAuthVerifier {
|
|
1172
1381
|
|
1173
1382
|
//- Public OAuth endpoints
|
1174
1383
|
|
1384
|
+
router.options('/.well-known/oauth-authorization-server', corsPreflight)
|
1175
1385
|
router.get(
|
1176
1386
|
'/.well-known/oauth-authorization-server',
|
1177
|
-
|
1387
|
+
corsHeaders,
|
1388
|
+
cacheControlMiddleware(300),
|
1389
|
+
staticJsonMiddleware(server.metadata),
|
1178
1390
|
)
|
1179
1391
|
|
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))
|
1392
|
+
router.options('/oauth/jwks', corsPreflight)
|
1393
|
+
router.get(
|
1394
|
+
'/oauth/jwks',
|
1395
|
+
corsHeaders,
|
1396
|
+
cacheControlMiddleware(300),
|
1397
|
+
staticJsonMiddleware(server.jwks),
|
1398
|
+
)
|
1208
1399
|
|
1209
1400
|
router.options('/oauth/par', corsPreflight)
|
1210
1401
|
router.post(
|
1211
1402
|
'/oauth/par',
|
1212
|
-
|
1403
|
+
oauthHandler(async function (req, _res) {
|
1213
1404
|
const payload = await parseHttpRequest(req, ['json', 'urlencoded'])
|
1214
1405
|
|
1215
1406
|
const credentials = await oauthClientCredentialsSchema
|
@@ -1233,11 +1424,9 @@ export class OAuthProvider extends OAuthVerifier {
|
|
1233
1424
|
)
|
1234
1425
|
}, 201),
|
1235
1426
|
)
|
1236
|
-
|
1237
1427
|
// https://datatracker.ietf.org/doc/html/rfc9126#section-2.3
|
1238
1428
|
// > If the request did not use the POST method, the authorization server
|
1239
1429
|
// > responds with an HTTP 405 (Method Not Allowed) status code.
|
1240
|
-
router.options('/oauth/par', corsPreflight)
|
1241
1430
|
router.all('/oauth/par', (req, res) => {
|
1242
1431
|
res.writeHead(405).end()
|
1243
1432
|
})
|
@@ -1245,7 +1434,7 @@ export class OAuthProvider extends OAuthVerifier {
|
|
1245
1434
|
router.options('/oauth/token', corsPreflight)
|
1246
1435
|
router.post(
|
1247
1436
|
'/oauth/token',
|
1248
|
-
|
1437
|
+
oauthHandler(async function (req, _res) {
|
1249
1438
|
const payload = await parseHttpRequest(req, ['json', 'urlencoded'])
|
1250
1439
|
|
1251
1440
|
const clientMetadata =
|
@@ -1277,7 +1466,7 @@ export class OAuthProvider extends OAuthVerifier {
|
|
1277
1466
|
router.options('/oauth/revoke', corsPreflight)
|
1278
1467
|
router.post(
|
1279
1468
|
'/oauth/revoke',
|
1280
|
-
|
1469
|
+
oauthHandler(async function (req, res) {
|
1281
1470
|
const payload = await parseHttpRequest(req, ['json', 'urlencoded'])
|
1282
1471
|
|
1283
1472
|
const tokenIdentification = await oauthTokenIdentificationSchema
|
@@ -1291,8 +1480,6 @@ export class OAuthProvider extends OAuthVerifier {
|
|
1291
1480
|
}
|
1292
1481
|
}),
|
1293
1482
|
)
|
1294
|
-
|
1295
|
-
router.options('/oauth/revoke', corsPreflight)
|
1296
1483
|
router.get(
|
1297
1484
|
'/oauth/revoke',
|
1298
1485
|
navigationHandler(async function (req, res) {
|
@@ -1317,9 +1504,10 @@ export class OAuthProvider extends OAuthVerifier {
|
|
1317
1504
|
}),
|
1318
1505
|
)
|
1319
1506
|
|
1507
|
+
router.options('/oauth/introspect', corsPreflight)
|
1320
1508
|
router.post(
|
1321
1509
|
'/oauth/introspect',
|
1322
|
-
|
1510
|
+
oauthHandler(async function (req, _res) {
|
1323
1511
|
const payload = await parseHttpRequest(req, ['json', 'urlencoded'])
|
1324
1512
|
|
1325
1513
|
const credentials = await oauthClientCredentialsSchema
|
@@ -1346,7 +1534,7 @@ export class OAuthProvider extends OAuthVerifier {
|
|
1346
1534
|
const query = Object.fromEntries(this.url.searchParams)
|
1347
1535
|
|
1348
1536
|
const clientCredentials = await oauthClientCredentialsSchema
|
1349
|
-
.parseAsync(query, { path: ['
|
1537
|
+
.parseAsync(query, { path: ['query'] })
|
1350
1538
|
.catch(throwInvalidRequest)
|
1351
1539
|
|
1352
1540
|
if ('client_secret' in clientCredentials) {
|
@@ -1362,7 +1550,9 @@ export class OAuthProvider extends OAuthVerifier {
|
|
1362
1550
|
res,
|
1363
1551
|
)
|
1364
1552
|
|
1365
|
-
const
|
1553
|
+
const result:
|
1554
|
+
| AuthorizationResultRedirect
|
1555
|
+
| AuthorizationResultAuthorize = await server
|
1366
1556
|
.authorize(
|
1367
1557
|
clientCredentials,
|
1368
1558
|
authorizationRequest,
|
@@ -1371,173 +1561,79 @@ export class OAuthProvider extends OAuthVerifier {
|
|
1371
1561
|
)
|
1372
1562
|
.catch((err) => accessDeniedToRedirectCatcher(req, res, err))
|
1373
1563
|
|
1374
|
-
|
1375
|
-
|
1376
|
-
|
1377
|
-
|
1378
|
-
|
1379
|
-
|
1380
|
-
|
1381
|
-
}
|
1382
|
-
default: {
|
1383
|
-
// Should never happen
|
1384
|
-
throw new Error('Unexpected authorization result')
|
1385
|
-
}
|
1564
|
+
if ('redirect' in result) {
|
1565
|
+
return sendAuthorizeRedirect(res, result)
|
1566
|
+
} else {
|
1567
|
+
await setupCsrfToken(req, res, csrfCookie(result.authorize.uri))
|
1568
|
+
return server.outputManager.sendAuthorizePage(res, result, {
|
1569
|
+
preferredLocales: extractLocales(req),
|
1570
|
+
})
|
1386
1571
|
}
|
1387
1572
|
}),
|
1388
1573
|
)
|
1389
1574
|
|
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
1575
|
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
|
-
)
|
1576
|
+
'/oauth/authorize/verify-handle-availability',
|
1577
|
+
apiHandler(
|
1578
|
+
z.object({ handle: handleSchema }).strict(),
|
1579
|
+
async function (req, res, data) {
|
1580
|
+
return server.accountManager.verifyHandleAvailability(data.handle)
|
1581
|
+
},
|
1582
|
+
),
|
1583
|
+
)
|
1420
1584
|
|
1421
|
-
|
1585
|
+
router.post(
|
1586
|
+
'/oauth/authorize/sign-up',
|
1587
|
+
apiHandler(signUpDataSchema, async function (req, res, data, ctx) {
|
1588
|
+
return server.signUp(ctx, data)
|
1589
|
+
}),
|
1590
|
+
)
|
1422
1591
|
|
1423
|
-
|
1424
|
-
|
1425
|
-
|
1426
|
-
|
1427
|
-
input.credentials,
|
1428
|
-
)
|
1592
|
+
router.post(
|
1593
|
+
'/oauth/authorize/sign-in',
|
1594
|
+
apiHandler(signInDataSchema, async function (req, res, data, ctx) {
|
1595
|
+
return server.signIn(ctx, data)
|
1429
1596
|
}),
|
1430
1597
|
)
|
1431
1598
|
|
1432
|
-
|
1433
|
-
|
1434
|
-
|
1435
|
-
|
1436
|
-
|
1437
|
-
|
1599
|
+
router.post(
|
1600
|
+
'/oauth/authorize/reset-password-request',
|
1601
|
+
apiHandler(
|
1602
|
+
resetPasswordRequestDataSchema,
|
1603
|
+
async function (req, res, data) {
|
1604
|
+
await server.accountManager.resetPasswordRequest(data)
|
1605
|
+
},
|
1606
|
+
),
|
1607
|
+
)
|
1608
|
+
|
1609
|
+
router.post(
|
1610
|
+
'/oauth/authorize/reset-password-confirm',
|
1611
|
+
apiHandler(
|
1612
|
+
resetPasswordConfirmDataSchema,
|
1613
|
+
async function (req, res, data) {
|
1614
|
+
await server.accountManager.resetPasswordConfirm(data)
|
1615
|
+
},
|
1616
|
+
),
|
1617
|
+
)
|
1438
1618
|
|
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
1619
|
router.get(
|
1449
1620
|
'/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
|
-
)
|
1621
|
+
authorizeRedirectNavigationHandler(async function (req, res, ctx) {
|
1622
|
+
const sub = this.url.searchParams.get('account_sub')
|
1623
|
+
if (!sub) throw new InvalidRequestError('Account sub not provided')
|
1478
1624
|
|
1479
|
-
|
1480
|
-
.acceptRequest(
|
1481
|
-
input.request_uri,
|
1482
|
-
input.client_id,
|
1483
|
-
input.account_sub,
|
1484
|
-
deviceId,
|
1485
|
-
deviceMetadata,
|
1486
|
-
)
|
1625
|
+
return server
|
1626
|
+
.acceptRequest(ctx, sub)
|
1487
1627
|
.catch((err) => accessDeniedToRedirectCatcher(req, res, err))
|
1488
|
-
|
1489
|
-
return await sendAuthorizeRedirect(res, data)
|
1490
1628
|
}),
|
1491
1629
|
)
|
1492
1630
|
|
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
1631
|
router.get(
|
1509
1632
|
'/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)
|
1633
|
+
authorizeRedirectNavigationHandler(async function (req, res, ctx) {
|
1634
|
+
return server
|
1635
|
+
.rejectRequest(ctx)
|
1538
1636
|
.catch((err) => accessDeniedToRedirectCatcher(req, res, err))
|
1539
|
-
|
1540
|
-
return await sendAuthorizeRedirect(res, data)
|
1541
1637
|
}),
|
1542
1638
|
)
|
1543
1639
|
|