@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
@@ -0,0 +1,182 @@
|
|
1
|
+
import { createHash } from 'node:crypto'
|
2
|
+
import { z } from 'zod'
|
3
|
+
import {
|
4
|
+
Fetch,
|
5
|
+
FetchBound,
|
6
|
+
bindFetch,
|
7
|
+
fetchJsonProcessor,
|
8
|
+
fetchJsonZodProcessor,
|
9
|
+
fetchOkProcessor,
|
10
|
+
} from '@atproto-labs/fetch'
|
11
|
+
import { pipe } from '@atproto-labs/pipe'
|
12
|
+
|
13
|
+
export const hcaptchaTokenSchema = z.string().min(1)
|
14
|
+
export type HcaptchaToken = z.infer<typeof hcaptchaTokenSchema>
|
15
|
+
|
16
|
+
export const hcaptchaConfigSchema = z.object({
|
17
|
+
/**
|
18
|
+
* The hCaptcha site key to use for the sign-up form.
|
19
|
+
*/
|
20
|
+
siteKey: z.string().min(1),
|
21
|
+
/**
|
22
|
+
* The hCaptcha secret key to use for the sign-up form.
|
23
|
+
*/
|
24
|
+
secretKey: z.string().min(1),
|
25
|
+
/**
|
26
|
+
* A salt to use when hashing client tokens.
|
27
|
+
*/
|
28
|
+
tokenSalt: z.string().min(1),
|
29
|
+
/**
|
30
|
+
* The risk score over which the user is considered a threat and will be
|
31
|
+
* denied access. This will be ignored if the enterprise features are not
|
32
|
+
* available.
|
33
|
+
*/
|
34
|
+
scoreThreshold: z.number().optional(),
|
35
|
+
})
|
36
|
+
export type HcaptchaConfig = z.infer<typeof hcaptchaConfigSchema>
|
37
|
+
|
38
|
+
/**
|
39
|
+
* @see {@link https://docs.hcaptcha.com/#verify-the-user-response-server-side hCaptcha API}
|
40
|
+
*/
|
41
|
+
export const hcaptchaVerifyResultSchema = z.object({
|
42
|
+
/**
|
43
|
+
* is the passcode valid, and does it meet security criteria you specified, e.g. sitekey?
|
44
|
+
*/
|
45
|
+
success: z.boolean(),
|
46
|
+
/**
|
47
|
+
* timestamp of the challenge (ISO format yyyy-MM-dd'T'HH:mm:ssZZ)
|
48
|
+
*/
|
49
|
+
challenge_ts: z.string(),
|
50
|
+
/**
|
51
|
+
* the hostname of the site where the challenge was passed
|
52
|
+
*/
|
53
|
+
hostname: z.string(),
|
54
|
+
/**
|
55
|
+
* optional: any error codes
|
56
|
+
*/
|
57
|
+
'error-codes': z.array(z.string()),
|
58
|
+
/**
|
59
|
+
* ENTERPRISE feature: a score denoting malicious activity. Value ranges from
|
60
|
+
* 0.0 (no risk) to 1.0 (confirmed threat).
|
61
|
+
*/
|
62
|
+
score: z.number().optional(),
|
63
|
+
/**
|
64
|
+
* ENTERPRISE feature: reason(s) for score.
|
65
|
+
*/
|
66
|
+
score_reason: z.array(z.string()).optional(),
|
67
|
+
/**
|
68
|
+
* sitekey of the request
|
69
|
+
*/
|
70
|
+
sitekey: z.string().optional(),
|
71
|
+
/**
|
72
|
+
* obj of form: {'ip_device': 1, .. etc}
|
73
|
+
*/
|
74
|
+
behavior_counts: z.record(z.unknown()).optional(),
|
75
|
+
/**
|
76
|
+
* how similar is this? (0.0 - 1.0, -1 on err)
|
77
|
+
*/
|
78
|
+
similarity: z.number().optional(),
|
79
|
+
/**
|
80
|
+
* count of similar_tokens not processed
|
81
|
+
*/
|
82
|
+
similarity_failures: z.number().optional(),
|
83
|
+
/**
|
84
|
+
* array of strings for any similarity errors
|
85
|
+
*/
|
86
|
+
similarity_error_details: z.array(z.string()).optional(),
|
87
|
+
/**
|
88
|
+
* encoded clientID
|
89
|
+
*/
|
90
|
+
scoped_uid_0: z.string().optional(),
|
91
|
+
/**
|
92
|
+
* encoded IP
|
93
|
+
*/
|
94
|
+
scoped_uid_1: z.string().optional(),
|
95
|
+
/**
|
96
|
+
* encoded IP (APT)
|
97
|
+
*/
|
98
|
+
scoped_uid_2: z.string().optional(),
|
99
|
+
/**
|
100
|
+
* Risk Insights (APT + RI)
|
101
|
+
*/
|
102
|
+
risk_insights: z.record(z.unknown()).optional(),
|
103
|
+
/**
|
104
|
+
* Advanced Threat Signatures (APT)
|
105
|
+
*/
|
106
|
+
sigs: z.record(z.unknown()).optional(),
|
107
|
+
/**
|
108
|
+
* tags added via Rules
|
109
|
+
*/
|
110
|
+
tags: z.array(z.string()).optional(),
|
111
|
+
})
|
112
|
+
|
113
|
+
export type HcaptchaVerifyResult = z.infer<typeof hcaptchaVerifyResultSchema>
|
114
|
+
|
115
|
+
const fetchSuccessHandler = pipe(
|
116
|
+
fetchOkProcessor(),
|
117
|
+
fetchJsonProcessor(),
|
118
|
+
fetchJsonZodProcessor(hcaptchaVerifyResultSchema),
|
119
|
+
)
|
120
|
+
|
121
|
+
export class HCaptchaClient {
|
122
|
+
protected readonly fetch: FetchBound
|
123
|
+
constructor(
|
124
|
+
private readonly hostname: string,
|
125
|
+
private readonly config: HcaptchaConfig,
|
126
|
+
fetch: Fetch = globalThis.fetch,
|
127
|
+
) {
|
128
|
+
this.fetch = bindFetch(fetch)
|
129
|
+
}
|
130
|
+
|
131
|
+
async verify(
|
132
|
+
behaviorType: 'login' | 'signup',
|
133
|
+
response: string,
|
134
|
+
remoteip: string,
|
135
|
+
handle: string,
|
136
|
+
userAgent?: string,
|
137
|
+
) {
|
138
|
+
const result = await this.fetch('https://api.hcaptcha.com/siteverify', {
|
139
|
+
method: 'POST',
|
140
|
+
headers: {
|
141
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
142
|
+
},
|
143
|
+
body: new URLSearchParams({
|
144
|
+
secret: this.config.secretKey,
|
145
|
+
sitekey: this.config.siteKey,
|
146
|
+
behavior_type: behaviorType,
|
147
|
+
response,
|
148
|
+
remoteip,
|
149
|
+
client_tokens: JSON.stringify({
|
150
|
+
hashedIp: this.hashToken(remoteip),
|
151
|
+
hashedHandle: this.hashToken(handle),
|
152
|
+
hashedUserAgent: userAgent ? this.hashToken(userAgent) : undefined,
|
153
|
+
}),
|
154
|
+
}).toString(),
|
155
|
+
}).then(fetchSuccessHandler)
|
156
|
+
|
157
|
+
return {
|
158
|
+
allowed: this.isAllowed(result),
|
159
|
+
result,
|
160
|
+
}
|
161
|
+
}
|
162
|
+
|
163
|
+
isAllowed({ success, hostname, score }: HcaptchaVerifyResult) {
|
164
|
+
return (
|
165
|
+
success &&
|
166
|
+
// Fool-proofing: If this is false, the user is trying to use a token
|
167
|
+
// generated for the same siteKey, but on another domain.
|
168
|
+
hostname === this.hostname &&
|
169
|
+
// Ignore if enterprise feature is not enabled
|
170
|
+
score != null &&
|
171
|
+
this.config.scoreThreshold != null &&
|
172
|
+
score < this.config.scoreThreshold
|
173
|
+
)
|
174
|
+
}
|
175
|
+
|
176
|
+
hashToken(value: string) {
|
177
|
+
const hash = createHash('sha256')
|
178
|
+
hash.update(this.config.tokenSalt)
|
179
|
+
hash.update(value)
|
180
|
+
return hash.digest().toString('base64')
|
181
|
+
}
|
182
|
+
}
|
@@ -8,7 +8,43 @@ export type AssetRef = {
|
|
8
8
|
}
|
9
9
|
|
10
10
|
export type Attrs = Record<string, boolean | string | undefined>
|
11
|
-
|
11
|
+
|
12
|
+
/**
|
13
|
+
* @see {@link https://developer.mozilla.org/fr/docs/Web/HTML/Attributes/rel}
|
14
|
+
*/
|
15
|
+
const ALLOWED_LINK_REL_VALUES = Object.freeze([
|
16
|
+
'alternate',
|
17
|
+
'author',
|
18
|
+
'canonical',
|
19
|
+
'dns-prefetch',
|
20
|
+
'external',
|
21
|
+
'expect',
|
22
|
+
'help',
|
23
|
+
'icon',
|
24
|
+
'license',
|
25
|
+
'manifest',
|
26
|
+
'me',
|
27
|
+
'modulepreload',
|
28
|
+
'next',
|
29
|
+
'pingback',
|
30
|
+
'preconnect',
|
31
|
+
'prefetch',
|
32
|
+
'preload',
|
33
|
+
'prerender',
|
34
|
+
'prev',
|
35
|
+
'privacy-policy',
|
36
|
+
'search',
|
37
|
+
'stylesheet',
|
38
|
+
'terms-of-service',
|
39
|
+
] as const)
|
40
|
+
export type LinkRel = (typeof ALLOWED_LINK_REL_VALUES)[number]
|
41
|
+
export const isLinkRel = (rel: unknown): rel is LinkRel =>
|
42
|
+
(ALLOWED_LINK_REL_VALUES as readonly unknown[]).includes(rel)
|
43
|
+
|
44
|
+
export type LinkAttrs = Attrs & {
|
45
|
+
href: string
|
46
|
+
rel: LinkRel
|
47
|
+
}
|
12
48
|
export type MetaAttrs =
|
13
49
|
| { name: string; content: string }
|
14
50
|
| { 'http-equiv': string; content: string }
|
@@ -27,7 +63,7 @@ export type BuildDocumentOptions = {
|
|
27
63
|
title?: HtmlValue
|
28
64
|
scripts?: readonly (Html | AssetRef)[]
|
29
65
|
styles?: readonly (Html | AssetRef)[]
|
30
|
-
body
|
66
|
+
body?: HtmlValue
|
31
67
|
bodyAttrs?: Attrs
|
32
68
|
}
|
33
69
|
|
@@ -50,12 +86,13 @@ export const buildDocument = ({
|
|
50
86
|
${base && html`<base href="${base.href}" />`}
|
51
87
|
${meta?.some(isViewportMeta) ? null : defaultViewport}
|
52
88
|
${meta?.map(metaToHtml)}
|
89
|
+
${styles?.map(linkPreload('style'))}
|
90
|
+
${scripts?.map(linkPreload('script'))}
|
53
91
|
${links?.map(linkToHtml)}
|
54
|
-
${head}
|
92
|
+
${head}
|
93
|
+
${styles?.map(styleToHtml)}
|
55
94
|
</head>
|
56
|
-
<body${attrsToHtml(bodyAttrs)}>
|
57
|
-
${body} ${scripts?.map(scriptToHtml)}
|
58
|
-
</body>
|
95
|
+
<body${attrsToHtml(bodyAttrs)}>${body}${scripts?.map(scriptToHtml)}</body>
|
59
96
|
</html>`
|
60
97
|
|
61
98
|
function isViewportMeta<T extends MetaAttrs>(
|
@@ -64,12 +101,12 @@ function isViewportMeta<T extends MetaAttrs>(
|
|
64
101
|
return 'name' in attrs && attrs.name === 'viewport'
|
65
102
|
}
|
66
103
|
|
67
|
-
function
|
68
|
-
|
104
|
+
function linkToHtml(attrs: LinkAttrs) {
|
105
|
+
return html`<link${attrsToHtml(attrs)} />`
|
69
106
|
}
|
70
107
|
|
71
|
-
function
|
72
|
-
|
108
|
+
function metaToHtml(attrs: MetaAttrs) {
|
109
|
+
return html`<meta${attrsToHtml(attrs)} />`
|
73
110
|
}
|
74
111
|
|
75
112
|
function* attrsToHtml(attrs?: Attrs) {
|
@@ -83,16 +120,23 @@ function* attrsToHtml(attrs?: Attrs) {
|
|
83
120
|
}
|
84
121
|
}
|
85
122
|
|
86
|
-
function
|
87
|
-
|
123
|
+
function linkPreload(as: 'script' | 'style') {
|
124
|
+
return (style: Html | AssetRef) =>
|
125
|
+
style instanceof Html
|
126
|
+
? undefined
|
127
|
+
: html`<link rel="preload" href="${style.url}" as="${as}" />`
|
128
|
+
}
|
129
|
+
|
130
|
+
function scriptToHtml(script: Html | AssetRef) {
|
131
|
+
return script instanceof Html
|
88
132
|
? // prettier-ignore
|
89
133
|
html`<script>${script}</script>` // hash validity requires no space around the content
|
90
|
-
: html`<script type="module" src="${script.url}
|
134
|
+
: html`<script type="module" src="${script.url}"></script>`
|
91
135
|
}
|
92
136
|
|
93
|
-
function
|
94
|
-
|
137
|
+
function styleToHtml(style: Html | AssetRef) {
|
138
|
+
return style instanceof Html
|
95
139
|
? // prettier-ignore
|
96
140
|
html`<style>${style}</style>` // hash validity requires no space around the content
|
97
|
-
: html`<link rel="stylesheet" href="${style.url}
|
141
|
+
: html`<link rel="stylesheet" href="${style.url}" />`
|
98
142
|
}
|
@@ -2,6 +2,8 @@ import type { IncomingMessage, ServerResponse } from 'node:http'
|
|
2
2
|
import { writeJson } from './response.js'
|
3
3
|
import { Handler, Middleware, NextFunction } from './types.js'
|
4
4
|
|
5
|
+
const isNonNullable = <X>(x: X): x is NonNullable<X> => x != null
|
6
|
+
|
5
7
|
export function combineMiddlewares<M extends Middleware<any, any, any>>(
|
6
8
|
middlewares: Iterable<null | undefined | M>,
|
7
9
|
options?: { skipKeyword?: string },
|
@@ -15,12 +17,11 @@ export function combineMiddlewares(
|
|
15
17
|
middlewares: Iterable<null | undefined | Middleware<unknown>>,
|
16
18
|
{ skipKeyword }: { skipKeyword?: string } = {},
|
17
19
|
): Middleware<unknown> {
|
18
|
-
const middlewaresArray = Array.from(middlewares).filter(
|
19
|
-
(x): x is NonNullable<typeof x> => x != null,
|
20
|
-
)
|
20
|
+
const middlewaresArray = Array.from(middlewares).filter(isNonNullable)
|
21
21
|
|
22
22
|
// Optimization: if there are no middlewares, return a noop middleware.
|
23
23
|
if (middlewaresArray.length === 0) return (req, res, next) => void next()
|
24
|
+
if (middlewaresArray.length === 1) return middlewaresArray[0]
|
24
25
|
|
25
26
|
return function (req, res, next) {
|
26
27
|
let i = 0
|
package/src/lib/http/request.ts
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
import { randomBytes } from 'node:crypto'
|
2
2
|
import type { IncomingMessage, ServerResponse } from 'node:http'
|
3
|
+
import { languages, mediaType } from '@hapi/accept'
|
3
4
|
import { parse as parseCookie, serialize as serializeCookie } from 'cookie'
|
5
|
+
import forwarded from 'forwarded'
|
4
6
|
import createHttpError from 'http-errors'
|
5
7
|
import { appendHeader } from './response.js'
|
6
8
|
import { UrlReference, urlMatch } from './url.js'
|
@@ -78,6 +80,18 @@ export function validateFetchSite(
|
|
78
80
|
validateHeaderValue(req, 'sec-fetch-site', expectedSite)
|
79
81
|
}
|
80
82
|
|
83
|
+
export function validateReferer(
|
84
|
+
req: IncomingMessage,
|
85
|
+
res: ServerResponse,
|
86
|
+
reference: UrlReference,
|
87
|
+
allowNull: true,
|
88
|
+
): URL | null
|
89
|
+
export function validateReferer(
|
90
|
+
req: IncomingMessage,
|
91
|
+
res: ServerResponse,
|
92
|
+
reference: UrlReference,
|
93
|
+
allowNull?: false,
|
94
|
+
): URL
|
81
95
|
export function validateReferer(
|
82
96
|
req: IncomingMessage,
|
83
97
|
res: ServerResponse,
|
@@ -89,6 +103,7 @@ export function validateReferer(
|
|
89
103
|
if (refererUrl ? !urlMatch(refererUrl, reference) : !allowNull) {
|
90
104
|
throw createHttpError(400, `Invalid referer ${referer}`)
|
91
105
|
}
|
106
|
+
return refererUrl
|
92
107
|
}
|
93
108
|
|
94
109
|
export async function setupCsrfToken(
|
@@ -125,12 +140,13 @@ export function validateSameOrigin(
|
|
125
140
|
export function validateCsrfToken(
|
126
141
|
req: IncomingMessage,
|
127
142
|
res: ServerResponse,
|
128
|
-
csrfToken:
|
143
|
+
csrfToken: unknown,
|
129
144
|
cookieName = 'csrf_token',
|
130
145
|
clearCookie = false,
|
131
146
|
) {
|
132
147
|
const cookies = parseHttpCookies(req)
|
133
148
|
if (
|
149
|
+
typeof csrfToken !== 'string' ||
|
134
150
|
!csrfToken ||
|
135
151
|
!cookies ||
|
136
152
|
!cookieName ||
|
@@ -164,7 +180,19 @@ export function parseHttpCookies(
|
|
164
180
|
}
|
165
181
|
|
166
182
|
export type ExtractRequestMetadataOptions = {
|
167
|
-
|
183
|
+
/**
|
184
|
+
* A function that determines whether a given IP address is trusted. The
|
185
|
+
* function is called with the IP addresses and its index in the list of
|
186
|
+
* forwarded addresses (starting from 0, 0 corresponding to the ip of the
|
187
|
+
* incoming HTTP connection, and the last item being the first proxied IP
|
188
|
+
* address in the proxy chain, deduced from the `X-Forwarded-For` header). The
|
189
|
+
* function should return `true` if the IP address is trusted, and `false`
|
190
|
+
* otherwise.
|
191
|
+
*
|
192
|
+
* @see {@link https://www.npmjs.com/package/proxy-addr} for a utility that
|
193
|
+
* allows you to create a trust function.
|
194
|
+
*/
|
195
|
+
trustProxy?: (addr: string, i: number) => boolean
|
168
196
|
}
|
169
197
|
|
170
198
|
export type RequestMetadata = {
|
@@ -177,42 +205,49 @@ export function extractRequestMetadata(
|
|
177
205
|
req: IncomingMessage,
|
178
206
|
options?: ExtractRequestMetadataOptions,
|
179
207
|
): RequestMetadata {
|
180
|
-
const
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
throw new Error('Could not determine IP address')
|
208
|
+
const ip = extractIp(req, options)
|
209
|
+
return {
|
210
|
+
userAgent: req.headers['user-agent'],
|
211
|
+
ipAddress: ip,
|
212
|
+
port: extractPort(req, ip),
|
186
213
|
}
|
187
|
-
|
188
|
-
return { userAgent, ipAddress, port }
|
189
214
|
}
|
190
215
|
|
191
|
-
function
|
216
|
+
function extractIp(
|
192
217
|
req: IncomingMessage,
|
193
218
|
options?: ExtractRequestMetadataOptions,
|
194
|
-
): string
|
195
|
-
|
196
|
-
if (
|
197
|
-
|
219
|
+
): string {
|
220
|
+
const trust = options?.trustProxy
|
221
|
+
if (trust) {
|
222
|
+
const ips = forwarded(req)
|
223
|
+
for (let i = 0; i < ips.length; i++) {
|
224
|
+
const isTrusted = trust(ips[i], i)
|
225
|
+
if (!isTrusted) return ips[i]
|
226
|
+
}
|
227
|
+
// Let's return the last ("furthest") IP address in the chain if all of them
|
228
|
+
// are trusted. Note that this may indicate an issue with either the trust
|
229
|
+
// function (too permissive), or the proxy configuration (one of them not
|
230
|
+
// setting the X-Forwarded-For header).
|
231
|
+
const ip = ips[ips.length - 1]
|
232
|
+
if (ip) return ip
|
198
233
|
}
|
199
234
|
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
if (firstForward) return firstForward
|
205
|
-
}
|
235
|
+
// Express app compatibility (see "trust proxy" setting)
|
236
|
+
if ('ip' in req) {
|
237
|
+
const ip = req.ip
|
238
|
+
if (typeof ip === 'string') return ip
|
206
239
|
}
|
207
240
|
|
208
|
-
|
241
|
+
const ip = req.socket.remoteAddress
|
242
|
+
if (ip) return ip
|
243
|
+
|
244
|
+
throw new Error('Could not determine IP address')
|
209
245
|
}
|
210
246
|
|
211
|
-
function extractPort(
|
212
|
-
req
|
213
|
-
|
214
|
-
|
215
|
-
if (options?.trustProxy) {
|
247
|
+
function extractPort(req: IncomingMessage, ip: string): number {
|
248
|
+
if (ip !== req.socket.remoteAddress) {
|
249
|
+
// Trust the X-Forwarded-Port header only if the IP address was a trusted
|
250
|
+
// proxied IP.
|
216
251
|
const forwardedPort = req.headers['x-forwarded-port']
|
217
252
|
if (typeof forwardedPort === 'string') {
|
218
253
|
const port = Number(forwardedPort.trim())
|
@@ -223,5 +258,23 @@ function extractPort(
|
|
223
258
|
}
|
224
259
|
}
|
225
260
|
|
226
|
-
|
261
|
+
const port = req.socket.remotePort
|
262
|
+
if (port != null) return port
|
263
|
+
|
264
|
+
throw new Error('Could not determine port')
|
265
|
+
}
|
266
|
+
|
267
|
+
export function extractLocales(req: IncomingMessage) {
|
268
|
+
const acceptLanguage = req.headers['accept-language']
|
269
|
+
return acceptLanguage ? languages(acceptLanguage) : []
|
270
|
+
}
|
271
|
+
|
272
|
+
export function negotiateResponseContent<T extends string>(
|
273
|
+
req: IncomingMessage,
|
274
|
+
types: readonly T[],
|
275
|
+
): T | undefined {
|
276
|
+
const type = mediaType(req.headers['accept'], types)
|
277
|
+
if (type) return type as T
|
278
|
+
|
279
|
+
return undefined
|
227
280
|
}
|
package/src/lib/http/response.ts
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import type { ServerResponse } from 'node:http'
|
2
2
|
import { type Readable, pipeline } from 'node:stream'
|
3
|
-
import { Handler } from './types.js'
|
3
|
+
import type { Handler, Middleware } from './types.js'
|
4
4
|
|
5
5
|
export function appendHeader(
|
6
6
|
res: ServerResponse,
|
@@ -53,22 +53,27 @@ export function writeStream(
|
|
53
53
|
export function writeBuffer(
|
54
54
|
res: ServerResponse,
|
55
55
|
chunk: string | Buffer,
|
56
|
-
|
57
|
-
status = 200,
|
58
|
-
contentType = 'application/octet-stream',
|
59
|
-
}: WriteResponseOptions = {},
|
56
|
+
opts: WriteResponseOptions,
|
60
57
|
): void {
|
61
|
-
res.statusCode = status
|
62
|
-
res.setHeader('content-type', contentType)
|
58
|
+
if (opts?.status != null) res.statusCode = opts.status
|
59
|
+
res.setHeader('content-type', opts?.contentType || 'application/octet-stream')
|
63
60
|
res.end(chunk)
|
64
61
|
}
|
65
62
|
|
63
|
+
export function toJsonBuffer(value: unknown): Buffer {
|
64
|
+
try {
|
65
|
+
return Buffer.from(JSON.stringify(value))
|
66
|
+
} catch (cause) {
|
67
|
+
throw new Error(`Failed to serialize as JSON`, { cause })
|
68
|
+
}
|
69
|
+
}
|
70
|
+
|
66
71
|
export function writeJson(
|
67
72
|
res: ServerResponse,
|
68
73
|
payload: unknown,
|
69
74
|
{ contentType = 'application/json', ...options }: WriteResponseOptions = {},
|
70
75
|
): void {
|
71
|
-
const buffer =
|
76
|
+
const buffer = toJsonBuffer(payload)
|
72
77
|
writeBuffer(res, buffer, { ...options, contentType })
|
73
78
|
}
|
74
79
|
|
@@ -76,7 +81,7 @@ export function staticJsonMiddleware(
|
|
76
81
|
value: unknown,
|
77
82
|
{ contentType = 'application/json', ...options }: WriteResponseOptions = {},
|
78
83
|
): Handler<unknown> {
|
79
|
-
const buffer =
|
84
|
+
const buffer = toJsonBuffer(value)
|
80
85
|
const staticOptions: WriteResponseOptions = { ...options, contentType }
|
81
86
|
return function (req, res) {
|
82
87
|
writeBuffer(res, buffer, staticOptions)
|
@@ -90,3 +95,11 @@ export function writeHtml(
|
|
90
95
|
): void {
|
91
96
|
writeBuffer(res, html, { ...options, contentType })
|
92
97
|
}
|
98
|
+
|
99
|
+
export function cacheControlMiddleware(maxAge: number): Middleware<void> {
|
100
|
+
const header = `max-age=${maxAge}`
|
101
|
+
return function (req, res, next) {
|
102
|
+
res.setHeader('Cache-Control', header)
|
103
|
+
next()
|
104
|
+
}
|
105
|
+
}
|
@@ -0,0 +1,21 @@
|
|
1
|
+
import { z } from 'zod'
|
2
|
+
|
3
|
+
export const localeSchema = z
|
4
|
+
.string()
|
5
|
+
.regex(/^[a-z]{2,3}(-[A-Z]{2})?$/, 'Invalid locale')
|
6
|
+
export type Locale = z.infer<typeof localeSchema>
|
7
|
+
|
8
|
+
export const multiLangStringSchema = z.intersection(
|
9
|
+
z.object({ en: z.string() }), // en is required
|
10
|
+
z.record(localeSchema, z.union([z.string(), z.undefined()])),
|
11
|
+
)
|
12
|
+
export type MultiLangString = z.infer<typeof multiLangStringSchema>
|
13
|
+
|
14
|
+
export const AVAILABLE_LOCALES = [
|
15
|
+
// TODO: Add more in this list as translations are added in the PO files
|
16
|
+
'en',
|
17
|
+
'fr',
|
18
|
+
] as const satisfies readonly Locale[]
|
19
|
+
export type AvailableLocale = (typeof AVAILABLE_LOCALES)[number]
|
20
|
+
export const isAvailableLocale = (v: unknown): v is AvailableLocale =>
|
21
|
+
(AVAILABLE_LOCALES as readonly unknown[]).includes(v)
|
package/src/lib/util/function.ts
CHANGED
@@ -6,17 +6,14 @@
|
|
6
6
|
* particularly useful when the function is a member of a "private" object.
|
7
7
|
*/
|
8
8
|
export async function callAsync<F extends (...args: any[]) => unknown>(
|
9
|
-
this: ThisParameterType<F>,
|
10
9
|
fn: F,
|
11
10
|
...args: Parameters<F>
|
12
11
|
): Promise<Awaited<ReturnType<F>>>
|
13
12
|
export async function callAsync<F extends (...args: any[]) => unknown>(
|
14
|
-
this: ThisParameterType<F>,
|
15
13
|
fn?: F,
|
16
14
|
...args: Parameters<F>
|
17
15
|
): Promise<Awaited<ReturnType<F>> | undefined>
|
18
16
|
export async function callAsync<F extends (...args: any[]) => unknown>(
|
19
|
-
this: ThisParameterType<F>,
|
20
17
|
fn?: F,
|
21
18
|
...args: Parameters<F>
|
22
19
|
): Promise<Awaited<ReturnType<F>> | undefined> {
|