@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-verifier.ts
CHANGED
@@ -94,8 +94,10 @@ export class OAuthVerifier {
|
|
94
94
|
: new ReplayStoreMemory(),
|
95
95
|
accessTokenType = AccessTokenType.jwt,
|
96
96
|
|
97
|
-
...
|
97
|
+
...rest
|
98
98
|
}: OAuthVerifierOptions) {
|
99
|
+
const dpopMgrOptions: DpopManagerOptions = rest
|
100
|
+
|
99
101
|
const issuerParsed = oauthIssuerIdentifierSchema.parse(issuer)
|
100
102
|
const issuerUrl = new URL(issuerParsed)
|
101
103
|
|
@@ -31,7 +31,7 @@ export type AuthorizationResultAuthorize = {
|
|
31
31
|
}
|
32
32
|
|
33
33
|
// TODO: find a way to share this type with the frontend code
|
34
|
-
// (app/backend-
|
34
|
+
// (app/backend-types.ts)
|
35
35
|
|
36
36
|
type Session = {
|
37
37
|
account: Account
|
@@ -47,7 +47,6 @@ export type AuthorizeData = {
|
|
47
47
|
clientMetadata: OAuthClientMetadata
|
48
48
|
clientTrusted: boolean
|
49
49
|
requestUri: string
|
50
|
-
csrfCookie: string
|
51
50
|
loginHint?: string
|
52
51
|
scopeDetails?: ScopeDetail[]
|
53
52
|
newSessionsRequireConsent: boolean
|
@@ -62,7 +61,6 @@ export function buildAuthorizeData(
|
|
62
61
|
clientMetadata: data.client.metadata,
|
63
62
|
clientTrusted: data.client.info.isTrusted,
|
64
63
|
requestUri: data.authorize.uri,
|
65
|
-
csrfCookie: `csrf-${data.authorize.uri}`,
|
66
64
|
loginHint: data.parameters.login_hint,
|
67
65
|
newSessionsRequireConsent: data.parameters.prompt === 'consent',
|
68
66
|
scopeDetails: data.authorize.scopeDetails,
|
@@ -0,0 +1,189 @@
|
|
1
|
+
import { z } from 'zod'
|
2
|
+
import { hcaptchaConfigSchema } from '../lib/hcaptcha.js'
|
3
|
+
import { isLinkRel } from '../lib/html/build-document.js'
|
4
|
+
import { multiLangStringSchema } from '../lib/locale.js'
|
5
|
+
export { type HcaptchaConfig, hcaptchaConfigSchema } from '../lib/hcaptcha.js'
|
6
|
+
|
7
|
+
// Matches colors defined in tailwind.config.js
|
8
|
+
export const colorNames = ['brand', 'error', 'warning', 'success'] as const
|
9
|
+
export const colorNameSchema = z.enum(colorNames)
|
10
|
+
export type ColorName = z.infer<typeof colorNameSchema>
|
11
|
+
|
12
|
+
export const ColorsDefinitionSchema = z.record(colorNameSchema, z.string())
|
13
|
+
export type ColorsDefinition = z.infer<typeof ColorsDefinitionSchema>
|
14
|
+
|
15
|
+
export const localizedStringSchema = z.union([
|
16
|
+
z.string(),
|
17
|
+
multiLangStringSchema,
|
18
|
+
])
|
19
|
+
export type LocalizedString = z.infer<typeof localizedStringSchema>
|
20
|
+
|
21
|
+
export const linkRelSchema = z.string().refine(isLinkRel, 'Invalid link rel')
|
22
|
+
export type LinkRel = z.infer<typeof linkRelSchema>
|
23
|
+
|
24
|
+
export const linkDefinitionSchema = z.object({
|
25
|
+
title: localizedStringSchema,
|
26
|
+
href: z.string().url(),
|
27
|
+
rel: linkRelSchema.optional(),
|
28
|
+
})
|
29
|
+
export type LinkDefinition = z.infer<typeof linkDefinitionSchema>
|
30
|
+
|
31
|
+
/**
|
32
|
+
* Aesthetic customization
|
33
|
+
*/
|
34
|
+
export const brandingConfigSchema = z.object({
|
35
|
+
name: z.string().optional(),
|
36
|
+
logo: z.string().optional(),
|
37
|
+
colors: ColorsDefinitionSchema.optional(),
|
38
|
+
links: z.array(linkDefinitionSchema).readonly().optional(),
|
39
|
+
})
|
40
|
+
export type BrandingConfig = z.infer<typeof brandingConfigSchema>
|
41
|
+
|
42
|
+
export const customizationSchema = z.object({
|
43
|
+
/**
|
44
|
+
* Available user domains that can be used to sign up. A non-empty array
|
45
|
+
* is required to enable the sign-up feature.
|
46
|
+
*/
|
47
|
+
availableUserDomains: z.array(z.string()).optional(),
|
48
|
+
/**
|
49
|
+
* UI customizations
|
50
|
+
*/
|
51
|
+
branding: brandingConfigSchema.optional(),
|
52
|
+
/**
|
53
|
+
* Is an invite code required to sign up?
|
54
|
+
*/
|
55
|
+
inviteCodeRequired: z.boolean().optional(),
|
56
|
+
/**
|
57
|
+
* Enables hCaptcha during sign-up.
|
58
|
+
*/
|
59
|
+
hcaptcha: hcaptchaConfigSchema.optional(),
|
60
|
+
})
|
61
|
+
export type Customization = z.infer<typeof customizationSchema>
|
62
|
+
|
63
|
+
export type CustomizationData = {
|
64
|
+
// Functional customization
|
65
|
+
hcaptchaSiteKey?: string
|
66
|
+
inviteCodeRequired?: boolean
|
67
|
+
availableUserDomains?: string[]
|
68
|
+
|
69
|
+
// Aesthetic customization
|
70
|
+
name?: string
|
71
|
+
logo?: string
|
72
|
+
links?: readonly LinkDefinition[]
|
73
|
+
}
|
74
|
+
|
75
|
+
export function buildCustomizationData({
|
76
|
+
branding,
|
77
|
+
availableUserDomains,
|
78
|
+
inviteCodeRequired,
|
79
|
+
hcaptcha,
|
80
|
+
}: Customization): CustomizationData {
|
81
|
+
// @NOTE the front end does not need colors here as they will be injected as
|
82
|
+
// CSS variables.
|
83
|
+
// @NOTE We only copy the values explicitly needed to avoid leaking sensitive
|
84
|
+
// data (in case the caller passed more than what we expect).
|
85
|
+
return {
|
86
|
+
availableUserDomains,
|
87
|
+
inviteCodeRequired,
|
88
|
+
hcaptchaSiteKey: hcaptcha?.siteKey,
|
89
|
+
name: branding?.name,
|
90
|
+
logo: branding?.logo,
|
91
|
+
links: branding?.links,
|
92
|
+
}
|
93
|
+
}
|
94
|
+
|
95
|
+
export function buildCustomizationCss({ branding }: Customization) {
|
96
|
+
const vars = Array.from(buildCustomizationVars(branding))
|
97
|
+
if (vars.length) return `:root { ${vars.join(' ')} }`
|
98
|
+
|
99
|
+
return ''
|
100
|
+
}
|
101
|
+
|
102
|
+
function* buildCustomizationVars(branding?: BrandingConfig) {
|
103
|
+
if (branding?.colors) {
|
104
|
+
for (const name of colorNames) {
|
105
|
+
const value = branding.colors[name]
|
106
|
+
if (!value) continue
|
107
|
+
|
108
|
+
// Skip undefined values
|
109
|
+
if (value === undefined) continue
|
110
|
+
|
111
|
+
const { r, g, b, a } = parseColor(value)
|
112
|
+
|
113
|
+
// Tailwind does not apply alpha values to base colors
|
114
|
+
if (a !== undefined) throw new TypeError('Alpha not supported')
|
115
|
+
|
116
|
+
const contrast = computeLuma({ r, g, b }) > 128 ? '0 0 0' : '255 255 255'
|
117
|
+
|
118
|
+
yield `--color-${name}: ${r} ${g} ${b};`
|
119
|
+
yield `--color-${name}-c: ${contrast};`
|
120
|
+
}
|
121
|
+
}
|
122
|
+
}
|
123
|
+
|
124
|
+
type RgbaColor = { r: number; g: number; b: number; a?: number }
|
125
|
+
function parseColor(color: unknown): RgbaColor {
|
126
|
+
if (typeof color !== 'string') {
|
127
|
+
throw new TypeError(`Invalid color value: ${typeof color}`)
|
128
|
+
}
|
129
|
+
|
130
|
+
if (color.startsWith('#')) {
|
131
|
+
if (color.length === 4 || color.length === 5) {
|
132
|
+
const r = parseUi8Hex(color.slice(1, 2))
|
133
|
+
const g = parseUi8Hex(color.slice(2, 3))
|
134
|
+
const b = parseUi8Hex(color.slice(3, 4))
|
135
|
+
const a = color.length > 4 ? parseUi8Hex(color.slice(4, 5)) : undefined
|
136
|
+
return { r, g, b, a }
|
137
|
+
}
|
138
|
+
|
139
|
+
if (color.length === 7 || color.length === 9) {
|
140
|
+
const r = parseUi8Hex(color.slice(1, 3))
|
141
|
+
const g = parseUi8Hex(color.slice(3, 5))
|
142
|
+
const b = parseUi8Hex(color.slice(5, 7))
|
143
|
+
const a = color.length > 8 ? parseUi8Hex(color.slice(7, 9)) : undefined
|
144
|
+
return { r, g, b, a }
|
145
|
+
}
|
146
|
+
|
147
|
+
throw new TypeError(`Invalid hex color: ${color}`)
|
148
|
+
}
|
149
|
+
|
150
|
+
const rgbMatch = color.match(
|
151
|
+
/^\s*rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)\s*$/,
|
152
|
+
)
|
153
|
+
if (rgbMatch) {
|
154
|
+
const r = parseUi8Dec(rgbMatch[1])
|
155
|
+
const g = parseUi8Dec(rgbMatch[2])
|
156
|
+
const b = parseUi8Dec(rgbMatch[3])
|
157
|
+
return { r, g, b }
|
158
|
+
}
|
159
|
+
|
160
|
+
const rgbaMatch = color.match(
|
161
|
+
/^\s*rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)\s*$/,
|
162
|
+
)
|
163
|
+
if (rgbaMatch) {
|
164
|
+
const r = parseUi8Dec(rgbaMatch[1])
|
165
|
+
const g = parseUi8Dec(rgbaMatch[2])
|
166
|
+
const b = parseUi8Dec(rgbaMatch[3])
|
167
|
+
const a = parseUi8Dec(rgbaMatch[4])
|
168
|
+
return { r, g, b, a }
|
169
|
+
}
|
170
|
+
|
171
|
+
throw new TypeError(`Unsupported color format: ${color}`)
|
172
|
+
}
|
173
|
+
|
174
|
+
function computeLuma({ r, g, b }: RgbaColor) {
|
175
|
+
return 0.299 * r + 0.587 * g + 0.114 * b
|
176
|
+
}
|
177
|
+
|
178
|
+
function parseUi8Hex(v: string) {
|
179
|
+
return asUi8(parseInt(v, 16))
|
180
|
+
}
|
181
|
+
|
182
|
+
function parseUi8Dec(v: string) {
|
183
|
+
return asUi8(parseInt(v, 10))
|
184
|
+
}
|
185
|
+
|
186
|
+
function asUi8(v: number) {
|
187
|
+
if (v >= 0 && v <= 255 && v === (v | 0)) return v
|
188
|
+
throw new TypeError(`Invalid color component: ${v}`)
|
189
|
+
}
|
@@ -1,86 +1,149 @@
|
|
1
1
|
import type { ServerResponse } from 'node:http'
|
2
2
|
import { Asset } from '../assets/asset.js'
|
3
|
-
import {
|
4
|
-
import {
|
3
|
+
import { enumerateAssets } from '../assets/index.js'
|
4
|
+
import { CspConfig, mergeCsp } from '../lib/csp/index.js'
|
5
|
+
import {
|
6
|
+
Html,
|
7
|
+
LinkAttrs,
|
8
|
+
MetaAttrs,
|
9
|
+
cssCode,
|
10
|
+
html,
|
11
|
+
isLinkRel,
|
12
|
+
} from '../lib/html/index.js'
|
13
|
+
import { AVAILABLE_LOCALES, Locale, isAvailableLocale } from '../lib/locale.js'
|
5
14
|
import {
|
6
15
|
AuthorizationResultAuthorize,
|
7
16
|
buildAuthorizeData,
|
8
17
|
} from './build-authorize-data.js'
|
9
|
-
import { buildErrorPayload, buildErrorStatus } from './build-error-payload.js'
|
10
18
|
import {
|
11
19
|
Customization,
|
20
|
+
LinkDefinition,
|
12
21
|
buildCustomizationCss,
|
13
22
|
buildCustomizationData,
|
14
|
-
} from './customization.js'
|
15
|
-
import {
|
23
|
+
} from './build-customization-data.js'
|
24
|
+
import { buildErrorPayload, buildErrorStatus } from './build-error-payload.js'
|
25
|
+
import { assetToCsp, declareBackendData, sendWebPage } from './send-web-page.js'
|
26
|
+
|
27
|
+
const HCAPTCHA_CSP = {
|
28
|
+
'script-src': ['https://hcaptcha.com', 'https://*.hcaptcha.com'],
|
29
|
+
'frame-src': ['https://hcaptcha.com', 'https://*.hcaptcha.com'],
|
30
|
+
'style-src': ['https://hcaptcha.com', 'https://*.hcaptcha.com'],
|
31
|
+
'connect-src': ['https://hcaptcha.com', 'https://*.hcaptcha.com'],
|
32
|
+
} as const satisfies CspConfig
|
33
|
+
|
34
|
+
export type SendPageOptions = {
|
35
|
+
preferredLocales?: readonly string[]
|
36
|
+
}
|
16
37
|
|
17
38
|
export class OutputManager {
|
18
|
-
readonly
|
19
|
-
readonly
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
39
|
+
readonly links?: readonly LinkDefinition[]
|
40
|
+
readonly meta: readonly MetaAttrs[] = [
|
41
|
+
{ name: 'robots', content: 'noindex' },
|
42
|
+
{ name: 'description', content: 'ATProto OAuth authorization page' },
|
43
|
+
]
|
44
|
+
readonly scripts: readonly (Asset | Html)[]
|
45
|
+
readonly styles: readonly (Asset | Html)[]
|
46
|
+
readonly csp: CspConfig
|
47
|
+
|
48
|
+
constructor(customization: Customization) {
|
49
|
+
this.links = customization.branding?.links
|
50
|
+
|
51
|
+
const scripts = Array.from(enumerateAssets('application/javascript'))
|
52
|
+
const styles = Array.from(enumerateAssets('text/css'))
|
53
|
+
|
54
|
+
// Note: building scripts/styles/csp here for two reasons:
|
33
55
|
// 1. To avoid re-building it on every request
|
34
|
-
// 2. To throw during init if the customization is invalid
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
56
|
+
// 2. To throw during init if the customization/config is invalid
|
57
|
+
|
58
|
+
this.scripts = [
|
59
|
+
declareBackendData('__availableLocales', AVAILABLE_LOCALES),
|
60
|
+
declareBackendData(
|
61
|
+
'__customizationData',
|
62
|
+
buildCustomizationData(customization),
|
63
|
+
),
|
64
|
+
// Last (to be able to read the "backend data" variables)
|
65
|
+
...scripts.filter((asset) => asset.isEntry),
|
66
|
+
]
|
67
|
+
|
68
|
+
this.styles = [
|
69
|
+
// First (to be overridden by customization)
|
70
|
+
...styles,
|
71
|
+
cssCode(buildCustomizationCss(customization)),
|
72
|
+
]
|
73
|
+
|
74
|
+
const customizationCsp = customization?.hcaptcha ? HCAPTCHA_CSP : undefined
|
75
|
+
const assetsCsp: CspConfig = {
|
76
|
+
'script-src': scripts.map(assetToCsp),
|
77
|
+
'style-src': styles.map(assetToCsp),
|
78
|
+
}
|
79
|
+
|
80
|
+
this.csp = mergeCsp(customizationCsp, assetsCsp)
|
41
81
|
}
|
42
82
|
|
43
83
|
async sendAuthorizePage(
|
44
84
|
res: ServerResponse,
|
45
85
|
data: AuthorizationResultAuthorize,
|
86
|
+
options?: SendPageOptions,
|
46
87
|
): Promise<void> {
|
47
|
-
const
|
88
|
+
const locale = negotiateLocale(
|
89
|
+
data.parameters.ui_locales?.split(' ') ?? options?.preferredLocales,
|
90
|
+
)
|
48
91
|
|
49
92
|
return sendWebPage(res, {
|
50
93
|
scripts: [
|
51
94
|
declareBackendData('__authorizeData', buildAuthorizeData(data)),
|
52
|
-
this.
|
53
|
-
jsAsset, // Last (to be able to read the "backend data" variables)
|
95
|
+
...this.scripts,
|
54
96
|
],
|
55
|
-
styles:
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
links: this.customizationLinks,
|
60
|
-
htmlAttrs: { lang: 'en' },
|
61
|
-
title: 'Authorize',
|
97
|
+
styles: this.styles,
|
98
|
+
meta: this.meta,
|
99
|
+
links: this.buildLinks(locale),
|
100
|
+
htmlAttrs: { lang: locale },
|
62
101
|
body: html`<div id="root"></div>`,
|
102
|
+
csp: this.csp,
|
63
103
|
})
|
64
104
|
}
|
65
105
|
|
66
|
-
async sendErrorPage(
|
67
|
-
|
106
|
+
async sendErrorPage(
|
107
|
+
res: ServerResponse,
|
108
|
+
err: unknown,
|
109
|
+
options?: SendPageOptions,
|
110
|
+
): Promise<void> {
|
111
|
+
const locale = negotiateLocale(options?.preferredLocales)
|
68
112
|
|
69
113
|
return sendWebPage(res, {
|
70
114
|
status: buildErrorStatus(err),
|
71
115
|
scripts: [
|
72
116
|
declareBackendData('__errorData', buildErrorPayload(err)),
|
73
|
-
this.
|
74
|
-
jsAsset, // Last (to be able to read the "backend data" variables)
|
117
|
+
...this.scripts,
|
75
118
|
],
|
76
|
-
styles:
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
links: this.customizationLinks,
|
81
|
-
htmlAttrs: { lang: 'en' },
|
82
|
-
title: 'Error',
|
119
|
+
styles: this.styles,
|
120
|
+
meta: this.meta,
|
121
|
+
links: this.buildLinks(locale),
|
122
|
+
htmlAttrs: { lang: locale },
|
83
123
|
body: html`<div id="root"></div>`,
|
124
|
+
csp: this.csp,
|
84
125
|
})
|
85
126
|
}
|
127
|
+
|
128
|
+
buildLinks(locale: Locale) {
|
129
|
+
return this.links
|
130
|
+
?.map(({ rel, href, title }: LinkDefinition): LinkAttrs | undefined =>
|
131
|
+
isLinkRel(rel)
|
132
|
+
? typeof title === 'string'
|
133
|
+
? { href, rel, title }
|
134
|
+
: { href, rel, title: title[locale] || title.en }
|
135
|
+
: undefined,
|
136
|
+
)
|
137
|
+
.filter((v) => v != null)
|
138
|
+
}
|
139
|
+
}
|
140
|
+
|
141
|
+
function negotiateLocale(desiredLocales?: readonly string[]): Locale {
|
142
|
+
if (desiredLocales) {
|
143
|
+
for (const locale of desiredLocales) {
|
144
|
+
if (locale === '*') break // use default
|
145
|
+
if (isAvailableLocale(locale)) return locale
|
146
|
+
}
|
147
|
+
}
|
148
|
+
return 'en'
|
86
149
|
}
|
@@ -11,31 +11,42 @@ import { sendWebPage } from './send-web-page.js'
|
|
11
11
|
// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-11#section-7.5.4
|
12
12
|
const REDIRECT_STATUS_CODE = 303
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
14
|
+
/**
|
15
|
+
* @note `iss` and `state` will be added from the
|
16
|
+
* {@link AuthorizationResultRedirect} object.
|
17
|
+
*/
|
18
|
+
export type AuthorizationRedirectParameters =
|
19
|
+
| {
|
20
|
+
// iss: string
|
21
|
+
// state?: string
|
22
|
+
code: Code
|
23
|
+
id_token?: string
|
24
|
+
access_token?: string
|
25
|
+
token_type?: OAuthTokenType
|
26
|
+
expires_in?: string
|
27
|
+
}
|
28
|
+
| {
|
29
|
+
// iss: string
|
30
|
+
// state?: string
|
31
|
+
error: string
|
32
|
+
error_description?: string
|
33
|
+
error_uri?: string
|
34
|
+
}
|
35
|
+
|
36
|
+
const SUCCESS_REDIRECT_KEYS = [
|
37
|
+
'code',
|
38
|
+
'id_token',
|
39
|
+
'access_token',
|
40
|
+
'expires_in',
|
41
|
+
'token_type',
|
42
|
+
] as const
|
43
|
+
|
44
|
+
const ERROR_REDIRECT_KEYS = ['error', 'error_description', 'error_uri'] as const
|
34
45
|
|
35
46
|
export type AuthorizationResultRedirect = {
|
36
47
|
issuer: string
|
37
48
|
parameters: OAuthAuthorizationRequestParameters
|
38
|
-
redirect:
|
49
|
+
redirect: AuthorizationRedirectParameters
|
39
50
|
}
|
40
51
|
|
41
52
|
export async function sendAuthorizeRedirect(
|
@@ -49,23 +60,19 @@ export async function sendAuthorizeRedirect(
|
|
49
60
|
|
50
61
|
const mode = parameters.response_mode || 'query' // @TODO: default should depend on response_type
|
51
62
|
|
52
|
-
const entries: [string, string][] =
|
53
|
-
iss
|
54
|
-
|
55
|
-
|
56
|
-
response: redirect.response, // FAPI JARM
|
57
|
-
session_state: redirect.session_state, // OIDC Session Management
|
63
|
+
const entries: [string, string][] = [
|
64
|
+
['iss', issuer], // rfc9207
|
65
|
+
]
|
58
66
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
expires_in: redirect.expires_in,
|
63
|
-
token_type: redirect.token_type,
|
67
|
+
if (parameters.state != null) {
|
68
|
+
entries.push(['state', parameters.state])
|
69
|
+
}
|
64
70
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
71
|
+
const keys = 'code' in redirect ? SUCCESS_REDIRECT_KEYS : ERROR_REDIRECT_KEYS
|
72
|
+
for (const key of keys) {
|
73
|
+
const value = redirect[key]
|
74
|
+
if (value != null) entries.push([key, value])
|
75
|
+
}
|
69
76
|
|
70
77
|
res.setHeader('Cache-Control', 'no-store')
|
71
78
|
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import { createHash } from 'node:crypto'
|
2
2
|
import type { ServerResponse } from 'node:http'
|
3
|
+
import { CspConfig, CspValue, buildCsp, mergeCsp } from '../lib/csp/index.js'
|
3
4
|
import {
|
4
5
|
AssetRef,
|
5
6
|
BuildDocumentOptions,
|
@@ -13,16 +14,38 @@ export function declareBackendData(name: string, data: unknown) {
|
|
13
14
|
// The script tag is removed after the data is assigned to the global variable
|
14
15
|
// to prevent other scripts from deducing the value of the variable. The "app"
|
15
16
|
// script will read the global variable and then unset it. See
|
16
|
-
// "readBackendData" in "src/assets/app/backend-
|
17
|
+
// "readBackendData" in "src/assets/app/backend-types.ts".
|
17
18
|
return js`window[${name}]=${data};document.currentScript.remove();`
|
18
19
|
}
|
19
20
|
|
20
|
-
export type SendWebPageOptions = BuildDocumentOptions &
|
21
|
+
export type SendWebPageOptions = BuildDocumentOptions &
|
22
|
+
WriteResponseOptions & {
|
23
|
+
csp?: CspConfig
|
24
|
+
}
|
21
25
|
|
22
26
|
export async function sendWebPage(
|
23
27
|
res: ServerResponse,
|
24
28
|
options: SendWebPageOptions,
|
25
29
|
): Promise<void> {
|
30
|
+
const csp = mergeCsp(options.csp, {
|
31
|
+
'default-src': ["'none'"],
|
32
|
+
'base-uri': options.base?.origin as undefined | `https://${string}`,
|
33
|
+
'script-src': ["'self'", ...assetsToCsp(options.scripts)],
|
34
|
+
'style-src': ["'self'", ...assetsToCsp(options.styles)],
|
35
|
+
'img-src': ["'self'", 'data:', 'https:'],
|
36
|
+
'connect-src': ["'self'"],
|
37
|
+
'upgrade-insecure-requests': true,
|
38
|
+
|
39
|
+
// Prevents the CSP to be embedded in a page <meta>:
|
40
|
+
'frame-ancestors': ["'none'"],
|
41
|
+
})
|
42
|
+
|
43
|
+
// @NOTE the csp string might become too long. However, since we need to
|
44
|
+
// specify the "frame-ancestors" directive, we can't use a meta tag. For that
|
45
|
+
// reason, we won't try to avoid too long headers and let the proxy throw
|
46
|
+
// in case of a too long header.
|
47
|
+
res.setHeader('Content-Security-Policy', buildCsp(csp))
|
48
|
+
|
26
49
|
// @TODO: make these headers configurable (?)
|
27
50
|
res.setHeader('Permissions-Policy', 'otp-credentials=*, document-domain=()')
|
28
51
|
res.setHeader('Cross-Origin-Embedder-Policy', 'credentialless')
|
@@ -33,36 +56,27 @@ export async function sendWebPage(
|
|
33
56
|
res.setHeader('X-Content-Type-Options', 'nosniff')
|
34
57
|
res.setHeader('X-XSS-Protection', '0')
|
35
58
|
res.setHeader('Strict-Transport-Security', 'max-age=63072000')
|
36
|
-
res.setHeader(
|
37
|
-
'Content-Security-Policy',
|
38
|
-
[
|
39
|
-
`default-src 'none'`,
|
40
|
-
`frame-ancestors 'none'`,
|
41
|
-
`form-action 'none'`,
|
42
|
-
`base-uri ${options.base?.origin || `'none'`}`,
|
43
|
-
`script-src 'self' ${
|
44
|
-
options.scripts?.map(assetToHash).map(hashToCspRule).join(' ') ?? ''
|
45
|
-
}`,
|
46
|
-
`style-src 'self' ${
|
47
|
-
options.styles?.map(assetToHash).map(hashToCspRule).join(' ') ?? ''
|
48
|
-
}`,
|
49
|
-
`img-src 'self' data: https:`,
|
50
|
-
`connect-src 'self'`,
|
51
|
-
`upgrade-insecure-requests`,
|
52
|
-
].join('; '),
|
53
|
-
)
|
54
59
|
|
55
60
|
const html = buildDocument(options)
|
56
61
|
|
57
62
|
return writeHtml(res, html.toString(), options)
|
58
63
|
}
|
59
64
|
|
60
|
-
function
|
61
|
-
|
62
|
-
|
63
|
-
|
65
|
+
export function* assetsToCsp(
|
66
|
+
assets?: Iterable<Html | AssetRef>,
|
67
|
+
): Generator<CspValue> {
|
68
|
+
if (assets) {
|
69
|
+
for (const asset of assets) {
|
70
|
+
yield assetToCsp(asset)
|
71
|
+
}
|
72
|
+
}
|
64
73
|
}
|
65
74
|
|
66
|
-
function
|
67
|
-
|
75
|
+
export function assetToCsp(asset: Html | AssetRef): CspValue {
|
76
|
+
if (asset instanceof Html) {
|
77
|
+
const hash = createHash('sha256').update(asset.toString()).digest('base64')
|
78
|
+
return `'sha256-${hash}'`
|
79
|
+
} else {
|
80
|
+
return `'sha256-${asset.sha256}'`
|
81
|
+
}
|
68
82
|
}
|
@@ -308,13 +308,13 @@ export class RequestManager {
|
|
308
308
|
|
309
309
|
async get(
|
310
310
|
uri: RequestUri,
|
311
|
-
clientId: ClientId,
|
312
311
|
deviceId: DeviceId,
|
312
|
+
clientId?: ClientId,
|
313
313
|
): Promise<RequestInfo> {
|
314
314
|
const id = decodeRequestUri(uri)
|
315
315
|
|
316
316
|
const data = await this.store.readRequest(id)
|
317
|
-
if (!data) throw new InvalidRequestError(
|
317
|
+
if (!data) throw new InvalidRequestError('Unknown request_uri')
|
318
318
|
|
319
319
|
const updates: UpdateRequestData = {}
|
320
320
|
|
@@ -336,7 +336,7 @@ export class RequestManager {
|
|
336
336
|
)
|
337
337
|
}
|
338
338
|
|
339
|
-
if (data.clientId !== clientId) {
|
339
|
+
if (clientId != null && data.clientId !== clientId) {
|
340
340
|
throw new AccessDeniedError(
|
341
341
|
data.parameters,
|
342
342
|
'This request was initiated for another client',
|
@@ -380,7 +380,7 @@ export class RequestManager {
|
|
380
380
|
const id = decodeRequestUri(uri)
|
381
381
|
|
382
382
|
const data = await this.store.readRequest(id)
|
383
|
-
if (!data) throw new InvalidRequestError(
|
383
|
+
if (!data) throw new InvalidRequestError('Unknown request_uri')
|
384
384
|
|
385
385
|
try {
|
386
386
|
if (data.expiresAt < new Date()) {
|