hof 22.8.4 → 22.9.0-beta.v1
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/CHANGELOG.md +16 -0
- package/README.md +30 -0
- package/config/hof-defaults.js +6 -1
- package/frontend/govuk-template/govuk_template_generated.html +102 -0
- package/frontend/template-partials/views/layout.html +2 -8
- package/frontend/themes/gov-uk/styles/_cookie-banner.scss +0 -3
- package/lib/settings.js +1 -0
- package/package/.eslintignore +1 -0
- package/package/CHANGELOG.md +232 -0
- package/package/LICENSE +21 -0
- package/package/README.md +1887 -0
- package/package/bin/hof-build +10 -0
- package/package/bin/hof-transpiler +12 -0
- package/package/build/helpers/importer.js +29 -0
- package/package/build/helpers/local.js +35 -0
- package/package/build/helpers/resolver/import.js +32 -0
- package/package/build/helpers/resolver/nearest-package-root.js +33 -0
- package/package/build/helpers/resolver/package.js +29 -0
- package/package/build/helpers/resolver.js +16 -0
- package/package/build/index.js +49 -0
- package/package/build/lib/env.js +36 -0
- package/package/build/lib/mkdir.js +9 -0
- package/package/build/lib/run.js +17 -0
- package/package/build/lib/spawn.js +18 -0
- package/package/build/tasks/browserify/compress.js +15 -0
- package/package/build/tasks/browserify/index.js +48 -0
- package/package/build/tasks/build/index.js +6 -0
- package/package/build/tasks/images/index.js +27 -0
- package/package/build/tasks/index.js +8 -0
- package/package/build/tasks/sass/index.js +67 -0
- package/package/build/tasks/translate/index.js +20 -0
- package/package/build/tasks/watch/index.js +161 -0
- package/package/components/address-lookup/default-model.js +76 -0
- package/package/components/address-lookup/defaults.js +25 -0
- package/package/components/address-lookup/index.js +251 -0
- package/package/components/address-lookup/templates/address-lookup.html +14 -0
- package/package/components/address-lookup/templates/address.html +22 -0
- package/package/components/address-lookup/templates/postcode.html +9 -0
- package/package/components/clear-session/Readme.md +46 -0
- package/package/components/clear-session/index.js +26 -0
- package/package/components/combine-and-loop-fields/Readme.md +42 -0
- package/package/components/combine-and-loop-fields/index.js +156 -0
- package/package/components/date/fields.js +16 -0
- package/package/components/date/index.js +172 -0
- package/package/components/date/templates/date.html +20 -0
- package/package/components/emailer/assets/images/govuk_logotype_email.png +0 -0
- package/package/components/emailer/assets/images/ho_crest_27px.png +0 -0
- package/package/components/emailer/assets/images/spacer.gif +0 -0
- package/package/components/emailer/email-service.js +51 -0
- package/package/components/emailer/emailer.js +53 -0
- package/package/components/emailer/index.js +74 -0
- package/package/components/emailer/transports/debug.js +74 -0
- package/package/components/emailer/transports/index.js +8 -0
- package/package/components/emailer/transports/ses.js +36 -0
- package/package/components/emailer/transports/smtp.js +26 -0
- package/package/components/emailer/transports/stub.js +5 -0
- package/package/components/emailer/views/layout.html +63 -0
- package/package/components/homeoffice-countries/index.js +22 -0
- package/package/components/index.js +13 -0
- package/package/components/notify/index.js +62 -0
- package/package/components/notify/notify.js +51 -0
- package/package/components/session-timeout-warning/index.js +67 -0
- package/package/components/summary/index.js +237 -0
- package/package/config/builder-defaults.js +45 -0
- package/package/config/component-defaults.js +13 -0
- package/package/config/hof-defaults.js +65 -0
- package/package/config/rate-limits.js +20 -0
- package/package/config/sanitisation-rules.js +32 -0
- package/package/controller/base-controller.js +296 -0
- package/package/controller/behaviour-hooks.js +51 -0
- package/package/controller/behaviour-session.js +64 -0
- package/package/controller/controller.js +258 -0
- package/package/controller/deprecate-error.js +10 -0
- package/package/controller/formatting/formatters.js +70 -0
- package/package/controller/formatting/index.js +32 -0
- package/package/controller/index.js +17 -0
- package/package/controller/validation/email.js +30 -0
- package/package/controller/validation/index.js +101 -0
- package/package/controller/validation/validators.js +181 -0
- package/package/controller/validation-error.js +14 -0
- package/package/frontend/govuk-template/build/config.js +24 -0
- package/package/frontend/govuk-template/build/govuk_template.html +102 -0
- package/package/frontend/govuk-template/build/index.js +23 -0
- package/package/frontend/govuk-template/govuk_template_generated.html +102 -0
- package/package/frontend/govuk-template/index.js +29 -0
- package/package/frontend/index.js +9 -0
- package/package/frontend/template-mixins/mixins/helpers.js +103 -0
- package/package/frontend/template-mixins/mixins/index.js +37 -0
- package/package/frontend/template-mixins/mixins/render.js +12 -0
- package/package/frontend/template-mixins/mixins/template-mixins.js +520 -0
- package/package/frontend/template-mixins/partials/forms/checkbox-group.html +47 -0
- package/package/frontend/template-mixins/partials/forms/checkbox.html +16 -0
- package/package/frontend/template-mixins/partials/forms/input-submit.html +1 -0
- package/package/frontend/template-mixins/partials/forms/input-text-date.html +37 -0
- package/package/frontend/template-mixins/partials/forms/input-text-group.html +45 -0
- package/package/frontend/template-mixins/partials/forms/option-group.html +43 -0
- package/package/frontend/template-mixins/partials/forms/select.html +17 -0
- package/package/frontend/template-mixins/partials/forms/textarea-group.html +37 -0
- package/package/frontend/template-mixins/partials/mixins/panel.html +4 -0
- package/package/frontend/template-partials/index.js +9 -0
- package/package/frontend/template-partials/translations/index.js +26 -0
- package/package/frontend/template-partials/translations/src/cy/base.json +4 -0
- package/package/frontend/template-partials/translations/src/cy/buttons.json +6 -0
- package/package/frontend/template-partials/translations/src/cy/cookies.json +104 -0
- package/package/frontend/template-partials/translations/src/cy/errorlist.json +6 -0
- package/package/frontend/template-partials/translations/src/cy/errors.json +18 -0
- package/package/frontend/template-partials/translations/src/cy/terms.json +28 -0
- package/package/frontend/template-partials/translations/src/en/accessibility.json +43 -0
- package/package/frontend/template-partials/translations/src/en/base.json +5 -0
- package/package/frontend/template-partials/translations/src/en/buttons.json +10 -0
- package/package/frontend/template-partials/translations/src/en/cookies.json +116 -0
- package/package/frontend/template-partials/translations/src/en/errorlist.json +7 -0
- package/package/frontend/template-partials/translations/src/en/errors.json +35 -0
- package/package/frontend/template-partials/translations/src/en/exit.json +5 -0
- package/package/frontend/template-partials/translations/src/en/fields.json +5 -0
- package/package/frontend/template-partials/translations/src/en/journey.json +6 -0
- package/package/frontend/template-partials/translations/src/en/save-and-exit.json +4 -0
- package/package/frontend/template-partials/translations/src/en/terms.json +28 -0
- package/package/frontend/template-partials/views/404.html +9 -0
- package/package/frontend/template-partials/views/accessibility.html +55 -0
- package/package/frontend/template-partials/views/confirm.html +8 -0
- package/package/frontend/template-partials/views/confirmation.html +19 -0
- package/package/frontend/template-partials/views/content/en/what-happens-next.md +0 -0
- package/package/frontend/template-partials/views/cookie-error.html +1 -0
- package/package/frontend/template-partials/views/cookies.html +84 -0
- package/package/frontend/template-partials/views/email/data-row.html +4 -0
- package/package/frontend/template-partials/views/email/formatted.html +12 -0
- package/package/frontend/template-partials/views/email/layout.html +63 -0
- package/package/frontend/template-partials/views/email/raw.html +11 -0
- package/package/frontend/template-partials/views/email/section-row.html +3 -0
- package/package/frontend/template-partials/views/error.html +20 -0
- package/package/frontend/template-partials/views/exit.html +9 -0
- package/package/frontend/template-partials/views/feedback-submitted.html +11 -0
- package/package/frontend/template-partials/views/layout.html +55 -0
- package/package/frontend/template-partials/views/partials/analytics-table.html +25 -0
- package/package/frontend/template-partials/views/partials/back.html +5 -0
- package/package/frontend/template-partials/views/partials/bullet-list.html +7 -0
- package/package/frontend/template-partials/views/partials/confirmation-alert.html +6 -0
- package/package/frontend/template-partials/views/partials/continue.html +5 -0
- package/package/frontend/template-partials/views/partials/cookie-banner.html +29 -0
- package/package/frontend/template-partials/views/partials/cookie-notification.html +4 -0
- package/package/frontend/template-partials/views/partials/cookie-settings-button.html +1 -0
- package/package/frontend/template-partials/views/partials/cookie-settings-radio.html +8 -0
- package/package/frontend/template-partials/views/partials/details-summary.html +8 -0
- package/package/frontend/template-partials/views/partials/external-link.html +1 -0
- package/package/frontend/template-partials/views/partials/form.html +10 -0
- package/package/frontend/template-partials/views/partials/gatag.html +60 -0
- package/package/frontend/template-partials/views/partials/head.html +31 -0
- package/package/frontend/template-partials/views/partials/heading.html +11 -0
- package/package/frontend/template-partials/views/partials/items-table.html +32 -0
- package/package/frontend/template-partials/views/partials/maincontent-left.html +10 -0
- package/package/frontend/template-partials/views/partials/navigation.html +8 -0
- package/package/frontend/template-partials/views/partials/page.html +23 -0
- package/package/frontend/template-partials/views/partials/panel-indent.html +3 -0
- package/package/frontend/template-partials/views/partials/session-cookies-table.html +28 -0
- package/package/frontend/template-partials/views/partials/session-timeout-warning.html +38 -0
- package/package/frontend/template-partials/views/partials/summary-table-row.html +14 -0
- package/package/frontend/template-partials/views/partials/summary-table.html +8 -0
- package/package/frontend/template-partials/views/partials/table.html +18 -0
- package/package/frontend/template-partials/views/partials/validation-list.html +3 -0
- package/package/frontend/template-partials/views/partials/validation-summary.html +25 -0
- package/package/frontend/template-partials/views/partials/warn.html +7 -0
- package/package/frontend/template-partials/views/rate-limit-error.html +10 -0
- package/package/frontend/template-partials/views/save-and-exit.html +17 -0
- package/package/frontend/template-partials/views/service-unavailable.html +13 -0
- package/package/frontend/template-partials/views/session-timeout.html +7 -0
- package/package/frontend/template-partials/views/step.html +14 -0
- package/package/frontend/template-partials/views/terms.html +26 -0
- package/package/frontend/themes/gov-uk/client-js/cookieSettings.js +145 -0
- package/package/frontend/themes/gov-uk/client-js/govuk-cookies.js +121 -0
- package/package/frontend/themes/gov-uk/client-js/index.js +27 -0
- package/package/frontend/themes/gov-uk/client-js/session-timeout-dialog.js +348 -0
- package/package/frontend/themes/gov-uk/client-js/skip-to-main.js +19 -0
- package/package/frontend/themes/gov-uk/index.js +9 -0
- package/package/frontend/themes/gov-uk/styles/_base.scss +17 -0
- package/package/frontend/themes/gov-uk/styles/_check_your_answers.scss +155 -0
- package/package/frontend/themes/gov-uk/styles/_cookie-banner.scss +111 -0
- package/package/frontend/themes/gov-uk/styles/_cookie-settings.scss +33 -0
- package/package/frontend/themes/gov-uk/styles/_govuk_frontend_toolkit.scss +23 -0
- package/package/frontend/themes/gov-uk/styles/_helpers.scss +5 -0
- package/package/frontend/themes/gov-uk/styles/_layout.scss +125 -0
- package/package/frontend/themes/gov-uk/styles/_panel-indent.scss +27 -0
- package/package/frontend/themes/gov-uk/styles/_pdf.scss +42 -0
- package/package/frontend/themes/gov-uk/styles/_session-timeout-dialog.scss +121 -0
- package/package/frontend/themes/gov-uk/styles/_typography.scss +27 -0
- package/package/frontend/themes/gov-uk/styles/_variables.scss +11 -0
- package/package/frontend/themes/gov-uk/styles/govuk.scss +43 -0
- package/package/frontend/themes/gov-uk/styles/mixins.scss +16 -0
- package/package/frontend/themes/gov-uk/styles/modules/_alerts.scss +73 -0
- package/package/frontend/themes/gov-uk/styles/modules/_buttons.scss +5 -0
- package/package/frontend/themes/gov-uk/styles/modules/_character-count.scss +8 -0
- package/package/frontend/themes/gov-uk/styles/modules/_confirm-page.scss +20 -0
- package/package/frontend/themes/gov-uk/styles/modules/_lists.scss +5 -0
- package/package/frontend/themes/gov-uk/styles/modules/_progressive-reveal.scss +17 -0
- package/package/frontend/themes/gov-uk/styles/modules/_validation.scss +51 -0
- package/package/frontend/themes/index.js +5 -0
- package/package/frontend/toolkit/assets/images/passports/new-window-link-blue.png +0 -0
- package/package/frontend/toolkit/assets/images/passports/new-window-link.png +0 -0
- package/package/frontend/toolkit/assets/images/spinner.gif +0 -0
- package/package/frontend/toolkit/assets/javascript/character-count.js +99 -0
- package/package/frontend/toolkit/assets/javascript/form-focus.js +101 -0
- package/package/frontend/toolkit/assets/javascript/helpers.js +177 -0
- package/package/frontend/toolkit/assets/javascript/progressive-reveal.js +72 -0
- package/package/frontend/toolkit/assets/javascript/validation.js +71 -0
- package/package/frontend/toolkit/assets/javascript/vendor/details.polyfill.js +189 -0
- package/package/frontend/toolkit/assets/javascript/vendor/indexof.polyfill.js +39 -0
- package/package/frontend/toolkit/assets/javascript/vendor/safari-cachebuster.js +6 -0
- package/package/frontend/toolkit/assets/stylesheets/_base.scss +17 -0
- package/package/frontend/toolkit/assets/stylesheets/_helpers.scss +5 -0
- package/package/frontend/toolkit/assets/stylesheets/_layout.scss +118 -0
- package/package/frontend/toolkit/assets/stylesheets/_typography.scss +27 -0
- package/package/frontend/toolkit/assets/stylesheets/_variables.scss +11 -0
- package/package/frontend/toolkit/assets/stylesheets/app.scss +30 -0
- package/package/frontend/toolkit/assets/stylesheets/govuk-elements/_buttons.scss +47 -0
- package/package/frontend/toolkit/assets/stylesheets/govuk-elements/_details.scss +38 -0
- package/package/frontend/toolkit/assets/stylesheets/govuk-elements/_elements-typography.scss +175 -0
- package/package/frontend/toolkit/assets/stylesheets/govuk-elements/_forms.scss +167 -0
- package/package/frontend/toolkit/assets/stylesheets/govuk-elements/_helpers.scss +39 -0
- package/package/frontend/toolkit/assets/stylesheets/govuk-elements/_layout.scss +67 -0
- package/package/frontend/toolkit/assets/stylesheets/govuk-elements/_lists.scss +40 -0
- package/package/frontend/toolkit/assets/stylesheets/govuk-elements/_panels.scss +29 -0
- package/package/frontend/toolkit/assets/stylesheets/govuk-elements/_reset.scss +33 -0
- package/package/frontend/toolkit/assets/stylesheets/govuk-elements/_tables.scss +33 -0
- package/package/frontend/toolkit/assets/stylesheets/govuk-elements/forms/_form-block-labels.scss +63 -0
- package/package/frontend/toolkit/assets/stylesheets/govuk-elements/forms/_form-date.scss +39 -0
- package/package/frontend/toolkit/assets/stylesheets/govuk-elements/forms/_form-validation.scss +63 -0
- package/package/frontend/toolkit/assets/stylesheets/mixins.scss +16 -0
- package/package/frontend/toolkit/assets/stylesheets/modules/_alerts.scss +73 -0
- package/package/frontend/toolkit/assets/stylesheets/modules/_buttons.scss +5 -0
- package/package/frontend/toolkit/assets/stylesheets/modules/_confirm-page.scss +20 -0
- package/package/frontend/toolkit/assets/stylesheets/modules/_lists.scss +5 -0
- package/package/frontend/toolkit/assets/stylesheets/modules/_progressive-reveal.scss +17 -0
- package/package/frontend/toolkit/assets/stylesheets/modules/_validation.scss +51 -0
- package/package/frontend/toolkit/index.js +10 -0
- package/package/index.js +351 -0
- package/package/lib/deindex.js +18 -0
- package/package/lib/encryption.js +23 -0
- package/package/lib/ga-tag.js +89 -0
- package/package/lib/health.js +26 -0
- package/package/lib/helpers.js +66 -0
- package/package/lib/logger.js +65 -0
- package/package/lib/markdown.js +46 -0
- package/package/lib/router.js +111 -0
- package/package/lib/serve-static.js +12 -0
- package/package/lib/sessions.js +82 -0
- package/package/lib/settings.js +74 -0
- package/package/lib/which.js +28 -0
- package/package/middleware/cookies.js +43 -0
- package/package/middleware/deep-translate.js +32 -0
- package/package/middleware/errors.js +119 -0
- package/package/middleware/index.js +10 -0
- package/package/middleware/not-found.js +38 -0
- package/package/middleware/rate-limiter.js +98 -0
- package/package/middleware/service-unavailable.js +64 -0
- package/package/model/apis/axios-settings.js +21 -0
- package/package/model/apis/html-to-pdf-converter.js +35 -0
- package/package/model/apis/index.js +5 -0
- package/package/model/index.js +348 -0
- package/package/package.json +147 -0
- package/package/sandbox/README.md +66 -0
- package/package/sandbox/apps/sandbox/behaviours/clear-session.js +8 -0
- package/package/sandbox/apps/sandbox/behaviours/country-select.js +10 -0
- package/package/sandbox/apps/sandbox/behaviours/international-number.js +22 -0
- package/package/sandbox/apps/sandbox/fields.js +128 -0
- package/package/sandbox/apps/sandbox/index.js +99 -0
- package/package/sandbox/apps/sandbox/lib/staticAppealStages.js +189 -0
- package/package/sandbox/apps/sandbox/sections/summary-data-sections.js +43 -0
- package/package/sandbox/apps/sandbox/translations/src/en/errors.json +5 -0
- package/package/sandbox/apps/sandbox/translations/src/en/exit.json +4 -0
- package/package/sandbox/apps/sandbox/translations/src/en/fields.json +101 -0
- package/package/sandbox/apps/sandbox/translations/src/en/journey.json +7 -0
- package/package/sandbox/apps/sandbox/translations/src/en/pages.json +72 -0
- package/package/sandbox/apps/sandbox/translations/src/en/validation.json +54 -0
- package/package/sandbox/apps/sandbox/views/form-guidance-link.html +8 -0
- package/package/sandbox/apps/sandbox/views/save-and-exit.html +19 -0
- package/package/sandbox/assets/images/icons/icon-caret-left.png +0 -0
- package/package/sandbox/assets/images/icons/icon-complete.png +0 -0
- package/package/sandbox/assets/images/icons/icon-cross-remove-sign.png +0 -0
- package/package/sandbox/assets/js/index.js +70 -0
- package/package/sandbox/assets/scss/app.scss +27 -0
- package/package/sandbox/codecept.conf.js +15 -0
- package/package/sandbox/config.js +17 -0
- package/package/sandbox/package.json +26 -0
- package/package/sandbox/server.js +23 -0
- package/package/sandbox/yarn.lock +262 -0
- package/package/transpiler/index.js +2 -0
- package/package/transpiler/lib/aggregate.js +32 -0
- package/package/transpiler/lib/build.js +15 -0
- package/package/transpiler/lib/compile.js +12 -0
- package/package/transpiler/lib/expand-dirs.js +14 -0
- package/package/transpiler/lib/write-files.js +24 -0
- package/package/utilities/autofill/index.js +189 -0
- package/package/utilities/autofill/inputs.js +60 -0
- package/package/utilities/countries.js +12 -0
- package/package/utilities/helpers/index.js +189 -0
- package/package/utilities/index.js +9 -0
- package/package/utilities/reqres/index.js +18 -0
- package/package/utilities/test-data/data/domain.json +7 -0
- package/package/utilities/test-data/data/firstname.json +16 -0
- package/package/utilities/test-data/data/lastname.json +10 -0
- package/package/utilities/test-data/data/postcode.json +12 -0
- package/package/utilities/test-data/data/streetname.json +8 -0
- package/package/utilities/test-data/data/streetsuffix.json +7 -0
- package/package/utilities/test-data/data/town.json +9 -0
- package/package/utilities/test-data/index.js +67 -0
- package/package/wizard/behaviours/complete.js +20 -0
- package/package/wizard/behaviours/index.js +5 -0
- package/package/wizard/index.js +124 -0
- package/package/wizard/middleware/back-links.js +68 -0
- package/package/wizard/middleware/check-complete.js +13 -0
- package/package/wizard/middleware/check-progress.js +139 -0
- package/package/wizard/middleware/check-session.js +17 -0
- package/package/wizard/middleware/csrf.js +47 -0
- package/package/wizard/middleware/session-model.js +11 -0
- package/package/wizard/middleware/session.js +9 -0
- package/package/wizard/model.js +29 -0
- package/package/wizard/util/constants.js +5 -0
- package/package/wizard/util/helpers.js +19 -0
- package/package.json +5 -6
- package/.editorconfig +0 -10
- package/.github/workflows/automate-publish.yml +0 -38
- package/.github/workflows/automate-tag.yml +0 -78
- package/.istanbul.yml +0 -20
- package/codeReviewChecklist.md +0 -22
- package/pull_request_template.md +0 -16
@@ -0,0 +1,98 @@
|
|
1
|
+
|
2
|
+
const moment = require('moment');
|
3
|
+
const redis = require('redis');
|
4
|
+
const config = require('./../config/hof-defaults');
|
5
|
+
|
6
|
+
module.exports = (options, rateLimitType) => {
|
7
|
+
// eslint-disable-next-line no-console
|
8
|
+
const logger = options.logger || { log: (func, msg) => console[func](msg) };
|
9
|
+
const rateLimits = options.rateLimits[rateLimitType];
|
10
|
+
const timestampName = `${rateLimitType}TimeStamp`;
|
11
|
+
const countName = `${rateLimitType}Count`;
|
12
|
+
|
13
|
+
const WINDOW_SIZE_IN_MINUTES = rateLimits.windowSizeInMinutes;
|
14
|
+
const MAX_WINDOW_REQUEST_COUNT = rateLimits.maxWindowRequestCount;
|
15
|
+
const WINDOW_LOG_INTERVAL_IN_MINUTES = rateLimits.windowLogIntervalInMinutes;
|
16
|
+
const ERROR_CODE = rateLimits.errCode;
|
17
|
+
|
18
|
+
return async (req, res, next) => {
|
19
|
+
const redisClient = redis.createClient(config.redis);
|
20
|
+
|
21
|
+
// check that redis client exists
|
22
|
+
if (!redisClient) {
|
23
|
+
logger.log('error', 'Redis client does not exist!');
|
24
|
+
return next();
|
25
|
+
}
|
26
|
+
|
27
|
+
const closeConnection = async err => {
|
28
|
+
await redisClient.quit();
|
29
|
+
return next(err);
|
30
|
+
};
|
31
|
+
|
32
|
+
try {
|
33
|
+
// fetch records of current user using IP address, returns null when no record is found
|
34
|
+
return await redisClient.get(req.ip, async (err, record) => {
|
35
|
+
if (err) {
|
36
|
+
logger.log('error', `Error with requesting redis session for rate limiting: ${err}`);
|
37
|
+
return await closeConnection();
|
38
|
+
}
|
39
|
+
const currentRequestTime = moment();
|
40
|
+
const windowStartTimestamp = moment().subtract(WINDOW_SIZE_IN_MINUTES, 'minutes').unix();
|
41
|
+
let oldRecord = false;
|
42
|
+
let data;
|
43
|
+
// if no record is found , create a new record for user and store to redis
|
44
|
+
if (record) {
|
45
|
+
data = JSON.parse(record);
|
46
|
+
oldRecord = data[data.length - 1][timestampName] < windowStartTimestamp;
|
47
|
+
}
|
48
|
+
|
49
|
+
if (!record || oldRecord) {
|
50
|
+
const newRecord = [];
|
51
|
+
const requestLog = {
|
52
|
+
[timestampName]: currentRequestTime.unix(),
|
53
|
+
[countName]: 1
|
54
|
+
};
|
55
|
+
newRecord.push(requestLog);
|
56
|
+
await redisClient.set(req.ip, JSON.stringify(newRecord));
|
57
|
+
return await closeConnection();
|
58
|
+
}
|
59
|
+
// if record is found, parse it's value and calculate number of requests users has made within the last window
|
60
|
+
const requestsWithinWindow = data.filter(entry => entry[timestampName] > windowStartTimestamp);
|
61
|
+
|
62
|
+
const totalWindowRequestsCount = requestsWithinWindow.reduce((accumulator, entry) => {
|
63
|
+
return accumulator + entry[countName];
|
64
|
+
}, 0);
|
65
|
+
|
66
|
+
if (!options.rateLimits.env || options.rateLimits.env === 'development') {
|
67
|
+
const requestsRemaining = MAX_WINDOW_REQUEST_COUNT - totalWindowRequestsCount;
|
68
|
+
const msg = `Requests made by client: ${totalWindowRequestsCount}\nRequests remaining: ${requestsRemaining}`;
|
69
|
+
logger.log('info', msg);
|
70
|
+
}
|
71
|
+
// if number of requests made is greater than or equal to the desired maximum, return error
|
72
|
+
if (totalWindowRequestsCount >= MAX_WINDOW_REQUEST_COUNT) {
|
73
|
+
return await closeConnection({ code: ERROR_CODE });
|
74
|
+
}
|
75
|
+
// if number of requests made is less than allowed maximum, log new entry
|
76
|
+
const lastRequestLog = data[data.length - 1];
|
77
|
+
const potentialCurrentWindowIntervalStartTimeStamp = currentRequestTime
|
78
|
+
.subtract(WINDOW_LOG_INTERVAL_IN_MINUTES, 'minutes')
|
79
|
+
.unix();
|
80
|
+
// if interval has not passed since last request log, increment counter
|
81
|
+
if (lastRequestLog[timestampName] > potentialCurrentWindowIntervalStartTimeStamp) {
|
82
|
+
lastRequestLog[countName]++;
|
83
|
+
data[data.length - 1] = lastRequestLog;
|
84
|
+
} else {
|
85
|
+
// if interval has passed, log new entry for current user and timestamp
|
86
|
+
data.push({
|
87
|
+
[timestampName]: currentRequestTime.unix(),
|
88
|
+
[countName]: 1
|
89
|
+
});
|
90
|
+
}
|
91
|
+
await redisClient.set(req.ip, JSON.stringify(data));
|
92
|
+
return await closeConnection();
|
93
|
+
});
|
94
|
+
} catch (err) {
|
95
|
+
return await closeConnection(err);
|
96
|
+
}
|
97
|
+
};
|
98
|
+
};
|
@@ -0,0 +1,64 @@
|
|
1
|
+
/* eslint-disable consistent-return */
|
2
|
+
'use strict';
|
3
|
+
|
4
|
+
const getTranslations = translate => {
|
5
|
+
const translations = {
|
6
|
+
title: 'Sorry, this service is unavailable',
|
7
|
+
message: 'This service is temporarily unavailable',
|
8
|
+
'answers-saved': 'Your answers have not been saved'
|
9
|
+
};
|
10
|
+
|
11
|
+
if (translate) {
|
12
|
+
const contact = translate('errors.service-unavailable.contact');
|
13
|
+
const alternative = translate('errors.service-unavailable.alternative');
|
14
|
+
translations.serviceName = translate('journey.serviceName') || translate('journey.header');
|
15
|
+
translations.title = translate('errors.service-unavailable.title');
|
16
|
+
translations.message = translate('errors.service-unavailable.message');
|
17
|
+
translations['answers-saved'] = translate('errors.service-unavailable.answers-saved');
|
18
|
+
|
19
|
+
// Only render contact and alternative information if the key has a value set
|
20
|
+
if (contact === 'errors.service-unavailable.contact') {
|
21
|
+
translations.contact = '';
|
22
|
+
} else {
|
23
|
+
translations.contact = translate('errors.service-unavailable.contact');
|
24
|
+
}
|
25
|
+
if (alternative === 'errors.service-unavailable.alternative') {
|
26
|
+
translations.alternative = '';
|
27
|
+
} else {
|
28
|
+
translations.alternative = translate('errors.service-unavailable.alternative');
|
29
|
+
}
|
30
|
+
}
|
31
|
+
return translations;
|
32
|
+
};
|
33
|
+
|
34
|
+
module.exports = options => {
|
35
|
+
const opts = options || {};
|
36
|
+
const logger = opts.logger;
|
37
|
+
// These are paths that are allowed to bypass the "service unavailable" middleware.
|
38
|
+
// When the service is unavailable (for example, for maintenance), all routes except those listed here
|
39
|
+
// will return a paused response, typically a maintenance page.
|
40
|
+
//
|
41
|
+
// - '/assets': Static assets (CSS, JS, images) must still be served so the paused page displays correctly.
|
42
|
+
// - '/readyz' and '/livez': Health check endpoints must remain available for Kubernetes or other orchestration
|
43
|
+
// systems to determine if the container is healthy, even during maintenance.
|
44
|
+
const bypassPaths = opts.bypassPaths || ['/readyz', '/health', '/assets'];
|
45
|
+
|
46
|
+
return (req, res, next) => {
|
47
|
+
if (bypassPaths.some(path => req.path.startsWith(path))) {
|
48
|
+
return next();
|
49
|
+
}
|
50
|
+
const translate = opts.translate || req.translate;
|
51
|
+
const translations = getTranslations(translate);
|
52
|
+
if (logger && logger.warn) {
|
53
|
+
logger.warn('Service temporarily unavailable - service paused.');
|
54
|
+
}
|
55
|
+
res.status(503).render('service-unavailable', {
|
56
|
+
serviceName: translations.serviceName,
|
57
|
+
title: translations.title,
|
58
|
+
message: translations.message,
|
59
|
+
'answers-saved': translations['answers-saved'],
|
60
|
+
contact: translations.contact,
|
61
|
+
alternative: translations.alternative
|
62
|
+
});
|
63
|
+
};
|
64
|
+
};
|
@@ -0,0 +1,21 @@
|
|
1
|
+
'use strict';
|
2
|
+
const { format } = require('url'); // Destructure 'format' from 'url' module
|
3
|
+
|
4
|
+
module.exports = (settings = {}, body = null) => {
|
5
|
+
if (typeof settings !== 'object' || settings === null) {
|
6
|
+
throw new TypeError('settings must be a non-null object');
|
7
|
+
}
|
8
|
+
|
9
|
+
const {
|
10
|
+
uri,
|
11
|
+
url,
|
12
|
+
body: settingsBody,
|
13
|
+
data: settingsData,
|
14
|
+
...restSettings
|
15
|
+
} = settings;
|
16
|
+
|
17
|
+
return Object.assign({}, restSettings, {
|
18
|
+
url: uri || url || format(settings),
|
19
|
+
data: settingsBody || body || settingsData
|
20
|
+
});
|
21
|
+
};
|
@@ -0,0 +1,35 @@
|
|
1
|
+
'use strict';
|
2
|
+
|
3
|
+
const Model = require('..');
|
4
|
+
const isPdf = require('is-pdf');
|
5
|
+
const config = require('../../config/hof-defaults');
|
6
|
+
|
7
|
+
module.exports = class PDFModel extends Model {
|
8
|
+
requestConfig(options) {
|
9
|
+
const settings = super.requestConfig(options);
|
10
|
+
settings.encoding = null;
|
11
|
+
settings.rejectUnauthorized = false;
|
12
|
+
settings.responseType = 'arraybuffer';
|
13
|
+
return settings;
|
14
|
+
}
|
15
|
+
|
16
|
+
url() {
|
17
|
+
return config.apis.pdfConverter;
|
18
|
+
}
|
19
|
+
|
20
|
+
handleResponse(response, callback) {
|
21
|
+
if (isPdf(Buffer.from(response.data))) {
|
22
|
+
return this.parseResponse(response.status, response.data, callback);
|
23
|
+
}
|
24
|
+
const err = new Error();
|
25
|
+
|
26
|
+
if (parseInt(response.status, 10) === 400) {
|
27
|
+
err.title = response.status;
|
28
|
+
err.message = response.statusText;
|
29
|
+
} else {
|
30
|
+
err.body = response.data;
|
31
|
+
}
|
32
|
+
err.status = response.status;
|
33
|
+
return callback(err, null, response.status);
|
34
|
+
}
|
35
|
+
};
|
@@ -0,0 +1,348 @@
|
|
1
|
+
/* eslint-disable node/no-deprecated-api, no-param-reassign */
|
2
|
+
'use strict';
|
3
|
+
|
4
|
+
const _ = require('lodash');
|
5
|
+
const axios = require('axios').default;
|
6
|
+
const url = require('url');
|
7
|
+
const EventEmitter = require('events').EventEmitter;
|
8
|
+
const axiosSetting = require('./apis/axios-settings');
|
9
|
+
const REFERENCE = /^\$ref:/;
|
10
|
+
|
11
|
+
function timeDiff(from, to, d) {
|
12
|
+
let digits = d;
|
13
|
+
if (digits === undefined) {
|
14
|
+
digits = 3;
|
15
|
+
}
|
16
|
+
const ms = (to[0] - from[0]) * 1e3 + (to[1] - from[1]) * 1e-6;
|
17
|
+
return +ms.toFixed(digits);
|
18
|
+
}
|
19
|
+
|
20
|
+
const urlKeys = Object.keys(url.parse(''));
|
21
|
+
|
22
|
+
module.exports = class Model extends EventEmitter {
|
23
|
+
constructor(attributes, options) {
|
24
|
+
super(attributes, options);
|
25
|
+
this.options = options || {};
|
26
|
+
this.attributes = {};
|
27
|
+
this.set(attributes, {
|
28
|
+
silent: true
|
29
|
+
});
|
30
|
+
this._request = axios;
|
31
|
+
}
|
32
|
+
|
33
|
+
async save(options, callback) {
|
34
|
+
if (typeof options === 'function' && arguments.length === 1) {
|
35
|
+
callback = options;
|
36
|
+
options = {};
|
37
|
+
} else if (!options) {
|
38
|
+
options = {};
|
39
|
+
}
|
40
|
+
|
41
|
+
let data = await this.prepare();
|
42
|
+
data = JSON.stringify(data);
|
43
|
+
const reqConf = this.requestConfig(options);
|
44
|
+
reqConf.method = options.method || 'POST';
|
45
|
+
reqConf.headers = Object.assign({
|
46
|
+
'Content-Type': 'application/json',
|
47
|
+
'Content-Length': Buffer.byteLength(data)
|
48
|
+
}, reqConf.headers || {});
|
49
|
+
return await this.request(reqConf, data, callback);
|
50
|
+
}
|
51
|
+
|
52
|
+
async fetch(options, callback) {
|
53
|
+
if (typeof options === 'function' && arguments.length === 1) {
|
54
|
+
callback = options;
|
55
|
+
options = {};
|
56
|
+
} else if (!options) {
|
57
|
+
options = {};
|
58
|
+
}
|
59
|
+
const reqConf = this.requestConfig(options);
|
60
|
+
reqConf.method = options.method || 'GET';
|
61
|
+
return await this.request(reqConf, callback);
|
62
|
+
}
|
63
|
+
|
64
|
+
async delete(options, callback) {
|
65
|
+
if (typeof options === 'function' && arguments.length === 1) {
|
66
|
+
callback = options;
|
67
|
+
options = {};
|
68
|
+
} else if (!options) {
|
69
|
+
options = {};
|
70
|
+
}
|
71
|
+
const reqConf = this.requestConfig(options);
|
72
|
+
reqConf.method = options.method || 'DELETE';
|
73
|
+
return await this.request(reqConf, callback);
|
74
|
+
}
|
75
|
+
|
76
|
+
requestConfig(options) {
|
77
|
+
let reqConf = this.url(options);
|
78
|
+
if (typeof reqConf === 'string') {
|
79
|
+
reqConf = url.parse(reqConf);
|
80
|
+
}
|
81
|
+
return Object.assign(reqConf, {
|
82
|
+
headers: options.headers || reqConf.headers || this.options.headers
|
83
|
+
});
|
84
|
+
}
|
85
|
+
|
86
|
+
async request(originalSettings, body, callback) {
|
87
|
+
if (typeof body === 'function' && arguments.length === 2) {
|
88
|
+
callback = body;
|
89
|
+
body = undefined;
|
90
|
+
}
|
91
|
+
|
92
|
+
let settings = Object.assign({}, originalSettings);
|
93
|
+
settings.timeout = settings.timeout || this.options.timeout;
|
94
|
+
settings = axiosSetting(settings, body);
|
95
|
+
settings = _.omit(settings, urlKeys);
|
96
|
+
this.emit('sync', originalSettings);
|
97
|
+
|
98
|
+
try {
|
99
|
+
const authData = await this.auth();
|
100
|
+
let authVal = authData;
|
101
|
+
if (typeof authVal === 'string') {
|
102
|
+
const [user, ...rest] = authVal.split(':');
|
103
|
+
authVal = {
|
104
|
+
user,
|
105
|
+
pass: rest.join(':'),
|
106
|
+
sendImmediately: true
|
107
|
+
};
|
108
|
+
}
|
109
|
+
if (authVal) {
|
110
|
+
settings.headers = {
|
111
|
+
...settings.headers,
|
112
|
+
Authorization: `Bearer ${authVal.bearer}`
|
113
|
+
};
|
114
|
+
}
|
115
|
+
|
116
|
+
const startTime = process.hrtime();
|
117
|
+
let timeoutTimer;
|
118
|
+
|
119
|
+
if (timeoutTimer) {
|
120
|
+
clearTimeout(timeoutTimer);
|
121
|
+
timeoutTimer = null;
|
122
|
+
}
|
123
|
+
|
124
|
+
const data = await new Promise((resolve, reject) => {
|
125
|
+
const _callback = (err, responseData, statusCode) => {
|
126
|
+
if (timeoutTimer) {
|
127
|
+
clearTimeout(timeoutTimer);
|
128
|
+
timeoutTimer = null;
|
129
|
+
}
|
130
|
+
|
131
|
+
const endTime = process.hrtime();
|
132
|
+
const responseTime = timeDiff(startTime, endTime);
|
133
|
+
if (err) {
|
134
|
+
this.emit('fail', err, responseData, originalSettings, statusCode, responseTime);
|
135
|
+
reject(err);
|
136
|
+
} else {
|
137
|
+
this.emit('success', responseData, originalSettings, statusCode, responseTime);
|
138
|
+
resolve(responseData);
|
139
|
+
}
|
140
|
+
};
|
141
|
+
|
142
|
+
this._request(settings)
|
143
|
+
.then(response => {
|
144
|
+
return this.handleResponse(response)
|
145
|
+
.then(responseData => _callback(null, responseData, response.status))
|
146
|
+
.catch(error => {
|
147
|
+
error.headers = response.headers;
|
148
|
+
_callback(error, null, response.status);
|
149
|
+
});
|
150
|
+
})
|
151
|
+
.catch(err => {
|
152
|
+
if (err.code === 'ETIMEDOUT' || err.code === 'ESOCKETTIMEDOUT') {
|
153
|
+
err.message = 'Connection timed out';
|
154
|
+
err.status = 504;
|
155
|
+
}
|
156
|
+
err.status = err.status || 503;
|
157
|
+
return _callback(err, null, err.status);
|
158
|
+
});
|
159
|
+
});
|
160
|
+
|
161
|
+
if (typeof callback === 'function') {
|
162
|
+
callback(null, data);
|
163
|
+
}
|
164
|
+
return data;
|
165
|
+
} catch (error) {
|
166
|
+
if (typeof callback === 'function') {
|
167
|
+
callback(error);
|
168
|
+
}
|
169
|
+
return error;
|
170
|
+
}
|
171
|
+
}
|
172
|
+
|
173
|
+
async handleResponse(response) {
|
174
|
+
let data = null;
|
175
|
+
try {
|
176
|
+
if (typeof response.data === 'object') {
|
177
|
+
data = response.data;
|
178
|
+
} else if (typeof response.data === 'string' && response.data.trim() !== '') {
|
179
|
+
data = JSON.parse(response.data);
|
180
|
+
} else {
|
181
|
+
data = {};
|
182
|
+
}
|
183
|
+
} catch (err) {
|
184
|
+
err.message = 'Failed to parse response data';
|
185
|
+
err.status = response.status;
|
186
|
+
err.body = response.data;
|
187
|
+
throw err;
|
188
|
+
}
|
189
|
+
return await this.parseResponse(response.status, data);
|
190
|
+
}
|
191
|
+
|
192
|
+
async parseResponse(statusCode, data) {
|
193
|
+
if (statusCode < 400) {
|
194
|
+
try {
|
195
|
+
data = await this.parse(data);
|
196
|
+
return data;
|
197
|
+
} catch (err) {
|
198
|
+
throw err;
|
199
|
+
}
|
200
|
+
} else {
|
201
|
+
throw this.parseError(statusCode, data);
|
202
|
+
}
|
203
|
+
}
|
204
|
+
|
205
|
+
prepare() {
|
206
|
+
return Promise.resolve(this.toJSON());
|
207
|
+
}
|
208
|
+
|
209
|
+
parse(data) {
|
210
|
+
return data;
|
211
|
+
}
|
212
|
+
|
213
|
+
parseError(statusCode, data) {
|
214
|
+
return Object.assign({
|
215
|
+
status: statusCode
|
216
|
+
}, data);
|
217
|
+
}
|
218
|
+
|
219
|
+
resolveReference(value, map) {
|
220
|
+
if (typeof value === 'string' && value.match(REFERENCE)) {
|
221
|
+
const key = value.replace(REFERENCE, '');
|
222
|
+
return _.cloneDeep(map[key]);
|
223
|
+
}
|
224
|
+
return value;
|
225
|
+
}
|
226
|
+
|
227
|
+
get(key) {
|
228
|
+
const value = _.cloneDeep(this.attributes[key]);
|
229
|
+
return this.resolveReference(value, this.attributes);
|
230
|
+
}
|
231
|
+
|
232
|
+
set(key, value, options) {
|
233
|
+
let attrs = {};
|
234
|
+
|
235
|
+
if (typeof key === 'string') {
|
236
|
+
attrs[key] = value;
|
237
|
+
} else {
|
238
|
+
attrs = key;
|
239
|
+
options = value;
|
240
|
+
}
|
241
|
+
options = options || {};
|
242
|
+
|
243
|
+
const old = this.toJSON();
|
244
|
+
const changed = _.pickBy(attrs, (attr, attrKey) => attr !== old[attrKey] || attr !== this.attributes[attrKey]);
|
245
|
+
|
246
|
+
Object.assign(this.attributes, attrs);
|
247
|
+
|
248
|
+
const references = _.reduce(this.attributes, (map, val, k) => {
|
249
|
+
if (typeof val === 'string' && val.match(REFERENCE)) {
|
250
|
+
const reffed = val.replace(REFERENCE, '');
|
251
|
+
map[reffed] = map[reffed] || [];
|
252
|
+
map[reffed].push(k);
|
253
|
+
}
|
254
|
+
return map;
|
255
|
+
}, {});
|
256
|
+
|
257
|
+
if (!options.silent && _.size(changed)) {
|
258
|
+
_.each(changed, (changedValue, changedKey) => {
|
259
|
+
this.emit(`change:${changedKey}`, this.get(changedKey), old[changedKey]);
|
260
|
+
// emit change events for referenced fields
|
261
|
+
if (references[changedKey]) {
|
262
|
+
references[changedKey].forEach(k => {
|
263
|
+
this.emit(`change:${k}`, this.get(changedKey), old[k]);
|
264
|
+
});
|
265
|
+
}
|
266
|
+
});
|
267
|
+
// add references to changed field map
|
268
|
+
_.each(references, (fields, ref) => {
|
269
|
+
fields.forEach(f => {
|
270
|
+
changed[f] = changed[ref];
|
271
|
+
});
|
272
|
+
});
|
273
|
+
this.emit('change', changed);
|
274
|
+
}
|
275
|
+
|
276
|
+
return this;
|
277
|
+
}
|
278
|
+
|
279
|
+
unset(fields, options) {
|
280
|
+
options = options || {};
|
281
|
+
if (typeof fields === 'string') {
|
282
|
+
fields = [fields];
|
283
|
+
}
|
284
|
+
|
285
|
+
const old = this.toJSON();
|
286
|
+
const changed = fields.reduce((obj, key) => {
|
287
|
+
if (old[key] !== undefined) {
|
288
|
+
obj[key] = undefined;
|
289
|
+
delete this.attributes[key];
|
290
|
+
}
|
291
|
+
return obj;
|
292
|
+
}, {});
|
293
|
+
|
294
|
+
if (!options.silent && _.size(changed)) {
|
295
|
+
_.each(changed, (value, key) => {
|
296
|
+
this.emit('change:' + key, undefined, old[key]);
|
297
|
+
});
|
298
|
+
this.emit('change', changed);
|
299
|
+
}
|
300
|
+
|
301
|
+
return this;
|
302
|
+
}
|
303
|
+
|
304
|
+
increment(property, amount) {
|
305
|
+
if (!property || typeof property !== 'string') {
|
306
|
+
throw new Error('Trying to increment undefined property');
|
307
|
+
}
|
308
|
+
const val = this.get(property) || 0;
|
309
|
+
amount = amount || 1;
|
310
|
+
this.set(property, val + amount);
|
311
|
+
}
|
312
|
+
|
313
|
+
reset(options) {
|
314
|
+
options = options || {};
|
315
|
+
const keys = Object.keys(this.attributes);
|
316
|
+
this.attributes = {};
|
317
|
+
if (!options.silent) {
|
318
|
+
_.each(keys, key => {
|
319
|
+
this.emit('change:' + key, undefined);
|
320
|
+
});
|
321
|
+
this.emit('reset');
|
322
|
+
}
|
323
|
+
}
|
324
|
+
|
325
|
+
url(options) {
|
326
|
+
options = options || {};
|
327
|
+
|
328
|
+
// falback to this.options.url
|
329
|
+
options.url = options.url || this.options.url;
|
330
|
+
|
331
|
+
let opts = {};
|
332
|
+
if (options.url) {
|
333
|
+
opts = url.parse(options.url);
|
334
|
+
}
|
335
|
+
// passing a host to url.format overrides other options, so remove it
|
336
|
+
delete opts.host;
|
337
|
+
Object.assign(opts, options);
|
338
|
+
return url.format(opts);
|
339
|
+
}
|
340
|
+
|
341
|
+
auth() {
|
342
|
+
return;
|
343
|
+
}
|
344
|
+
|
345
|
+
toJSON() {
|
346
|
+
return _.mapValues(_.cloneDeep(this.attributes), (value, key, attrs) => this.resolveReference(value, attrs));
|
347
|
+
}
|
348
|
+
};
|