From c0da6fba859d78c66d533ff13b339f333760c0f4 Mon Sep 17 00:00:00 2001 From: George Goodall Date: Tue, 20 Aug 2024 09:51:23 +0100 Subject: [PATCH 01/17] increased file size restrictions --- config/default.yaml | 3 +++ src/controllers/submitUrlController.js | 6 ++---- src/controllers/uploadFileController.js | 4 +--- src/filters/validationMessageLookup.js | 4 ++-- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/config/default.yaml b/config/default.yaml index 4fb2166c..7511fb57 100644 --- a/config/default.yaml +++ b/config/default.yaml @@ -32,4 +32,7 @@ email: { }, dataManagementEmail: 'fakeemail@fakemail.com' } +validations: { + maxFileSize: 100000000 +} diff --git a/src/controllers/submitUrlController.js b/src/controllers/submitUrlController.js index 72847907..e928cc66 100644 --- a/src/controllers/submitUrlController.js +++ b/src/controllers/submitUrlController.js @@ -4,6 +4,7 @@ import { URL } from 'url' import logger from '../utils/logger.js' import axios from 'axios' import { allowedFileTypes } from '../utils/utils.js' +import config from '../../config/index.js' class SubmitUrlController extends UploadController { async post (req, res, next) { @@ -91,10 +92,7 @@ class SubmitUrlController extends UploadController { try { const contentLength = response.headers['content-length'] - // Convert content length to MB - const sizeInMB = contentLength / (1024 * 1024) - - return sizeInMB <= 10 + return contentLength <= config.validations.maxFileSize } catch (err) { console.warn(err) return true // for now we will allow this file as we can't be sure diff --git a/src/controllers/uploadFileController.js b/src/controllers/uploadFileController.js index bcbe7d00..132322a8 100644 --- a/src/controllers/uploadFileController.js +++ b/src/controllers/uploadFileController.js @@ -123,9 +123,7 @@ class UploadFileController extends UploadController { } static sizeIsValid (datafile) { - const maxSize = 10 * 1024 * 1024 // 10MB - - if (datafile.size > maxSize) { + if (datafile.size > config.validations.maxFileSize) { return false } diff --git a/src/filters/validationMessageLookup.js b/src/filters/validationMessageLookup.js index f57a76d4..9ac128da 100644 --- a/src/filters/validationMessageLookup.js +++ b/src/filters/validationMessageLookup.js @@ -17,7 +17,7 @@ const validationMessages = { datafile: { required: 'Select a file', fileType: 'The selected file must be a CSV, GeoJSON, GML or GeoPackage file', - fileSize: 'The selected file must be smaller than 10MB', + fileSize: 'The selected file must be smaller than 100MB', fileNameTooLong: 'The selected file name must be less than 100 characters', fileNameInvalidCharacters: 'The selected file name must not contain any of the following characters: / \\ : * ? " < > |', fileNameDoubleExtension: 'The selected file name must not contain two file extensions', @@ -30,7 +30,7 @@ const validationMessages = { length: 'The URL must be less than 2048 characters', exists: 'The URL does not exist', filetype: 'The file referenced by URL must be a CSV, GeoJSON, GML or GeoPackage file', - size: 'The file referenced by URL must be smaller than 10MB' + size: 'The file referenced by URL must be smaller than 100MB' }, validationResult: { required: 'Unable to contact the API' From 4fb00d5d160cfd497816e2d9b273a273fd6809ea Mon Sep 17 00:00:00 2001 From: George Goodall Date: Tue, 20 Aug 2024 09:57:10 +0100 Subject: [PATCH 02/17] fix tests --- test/unit/submitUrlController.test.js | 7 ++++--- test/unit/uploadFileController.test.js | 6 ++++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/test/unit/submitUrlController.test.js b/test/unit/submitUrlController.test.js index 6f84397f..7626f835 100644 --- a/test/unit/submitUrlController.test.js +++ b/test/unit/submitUrlController.test.js @@ -1,6 +1,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest' import SubmitUrlController from '../../src/controllers/submitUrlController.js' +import config from '../../config/index.js' describe('SubmitUrlController', async () => { vi.mock('@/services/asyncRequestApi.js') @@ -163,12 +164,12 @@ describe('SubmitUrlController', async () => { }) describe('urlSize', () => { - it('should return true for URLs with a response smaller than 10MB', async () => { + it('should return true for URLs with a response smaller than max file size', async () => { expect(SubmitUrlController.urlResponseIsNotTooLarge({ headers: { 'content-length': '1' } })).toBe(true) }) - it('should return false for URLs with a response larger than 10MB', async () => { - expect(SubmitUrlController.urlResponseIsNotTooLarge({ headers: { 'content-length': '11000000' } })).toBe(false) + it('should return false for URLs with a response larger than the max file size', async () => { + expect(SubmitUrlController.urlResponseIsNotTooLarge({ headers: { 'content-length': config.validations.maxFileSize + 21 } })).toBe(false) }) }) diff --git a/test/unit/uploadFileController.test.js b/test/unit/uploadFileController.test.js index 2ae08dd7..36dd38e2 100644 --- a/test/unit/uploadFileController.test.js +++ b/test/unit/uploadFileController.test.js @@ -2,6 +2,8 @@ import { describe, it, expect, vi, beforeEach } from 'vitest' import UploadFileController from '../../src/controllers/uploadFileController.js' +import config from '../../config/index.js' + describe('UploadFileController', () => { let uploadFileController let asyncRequestApi @@ -195,11 +197,11 @@ describe('UploadFileController', () => { describe('file size', () => { it('should return true if the file size is less than the max file size', () => { - expect(UploadFileController.sizeIsValid({ size: 1000 })).toEqual(true) + expect(UploadFileController.sizeIsValid({ size: 1 })).toEqual(true) }) it('should return false if the file size is greater than the max file size', () => { - expect(UploadFileController.sizeIsValid({ size: 100000000 })).toEqual(false) + expect(UploadFileController.sizeIsValid({ size: config.validations.maxFileSize + 10 })).toEqual(false) }) }) From c4500dbb491c6eaf2a706b11eebc42beff099134 Mon Sep 17 00:00:00 2001 From: Roland Sadowski Date: Thu, 15 Aug 2024 16:55:31 +0100 Subject: [PATCH 03/17] schemas for template params Introduces schema validation for template params via a wrapper function that should be called in place of nunjucks.render(). --- src/controllers/OrganisationsController.js | 22 ++- src/controllers/submitUrlController.js | 8 +- src/controllers/uploadFileController.js | 6 +- .../makeDatasetSlugToReadableNameFilter.js | 1 - src/routes/accessibility.js | 3 +- src/routes/cookies.js | 3 +- src/routes/manage.js | 3 +- src/routes/privacy.js | 3 +- src/routes/schemas.js | 154 ++++++++++++++++++ src/serverSetup/errorHandlers.js | 7 +- src/serverSetup/middlewares.js | 3 +- 11 files changed, 194 insertions(+), 19 deletions(-) create mode 100644 src/routes/schemas.js diff --git a/src/controllers/OrganisationsController.js b/src/controllers/OrganisationsController.js index e7634ee9..4e8afcbb 100644 --- a/src/controllers/OrganisationsController.js +++ b/src/controllers/OrganisationsController.js @@ -4,6 +4,7 @@ import logger from '../utils/logger.js' import { types } from '../utils/logging.js' import { dataSubjects } from '../utils/utils.js' import { statusToTagClass } from '../filters/filters.js' +import { render, OrgGetStarted, OrgOverviewPage, OrgFindPage, OrgDatasetTaskList, OrgEndpointError, OrgIssueDetails } from '../routes/schemas.js' // get a list of available datasets const availableDatasets = Object.values(dataSubjects) @@ -94,7 +95,7 @@ const organisationsController = { datasetsWithErrors } - res.render('organisations/overview.html', params) + render(res, 'organisations/overview.html', OrgOverviewPage, params) } catch (error) { logger.warn('organisationsController.getOverview(): ' + error.message ?? error.errorMessage, { type: types.App }) next(error) @@ -124,7 +125,7 @@ const organisationsController = { return acc }, {}) - res.render('organisations/find.html', { alphabetisedOrgs }) + render(res, 'organisations/find.html', OrgFindPage, { alphabetisedOrgs }) } catch (err) { logger.warn('organisationsController.getOrganisations(): ' + err.message ?? err.errorMessage, { type: types.App }) next(err) @@ -157,9 +158,15 @@ const organisationsController = { dataset } - res.render('organisations/get-started.html', params) + render(res, 'organisations/get-started.html', OrgGetStarted, params) } catch (err) { - logger.error(err) + logger.warn({ + message: `OrganisationsController.getStarted(): ${err.message}`, + endpoint: req.originalUrl, + errorStack: err.stack, + errorMessage: err.message, + type: types.App + }) next(err) } }, @@ -202,8 +209,7 @@ const organisationsController = { organisation, dataset } - - res.render('organisations/datasetTaskList.html', params) + render(res, 'organisations/datasetTaskList.html', OrgDatasetTaskList, params) } catch (e) { logger.warn(`getDAtasetTaskList() failed for lpa='${lpa}', datasetId='${datasetId}'`, { type: types.App }) next(e) @@ -235,7 +241,7 @@ const organisationsController = { latest_200_date: last200Datetime } } - res.render('organisations/http-error.html', params) + render(res, 'organisations/http-error.html', OrgEndpointError, params) } catch (e) { logger.warn(`conditionalTaskListHandler() failed for lpa='${lpa}', datasetId='${datasetId}'`, { type: types.App }) next(e) @@ -373,7 +379,7 @@ const organisationsController = { issueType } - res.render('organisations/issueDetails.html', params) + render(res, 'organisations/issueDetails.html', OrgIssueDetails, params) } catch (e) { logger.warn(`getIssueDetails() failed for lpa='${lpa}', datasetId='${datasetId}', issue=${issueType}, entityNumber=${entityNumber}, resourceId=${resourceId}`, { type: types.App }) next(e) diff --git a/src/controllers/submitUrlController.js b/src/controllers/submitUrlController.js index e928cc66..f4eeb8bc 100644 --- a/src/controllers/submitUrlController.js +++ b/src/controllers/submitUrlController.js @@ -2,6 +2,7 @@ import UploadController from './uploadController.js' import { postUrlRequest } from '../services/asyncRequestApi.js' import { URL } from 'url' import logger from '../utils/logger.js' +import { types } from '../utils/logging.js' import axios from 'axios' import { allowedFileTypes } from '../utils/utils.js' import config from '../../config/index.js' @@ -18,7 +19,12 @@ class SubmitUrlController extends UploadController { const errors = { url: new SubmitUrlController.Error(error.key, error, req, res) } - logger.warn('SubmitUrlController: local validation failed during url submission', error) + logger.warn({ + message: 'SubmitUrlController: local validation failed during url submission', + error: JSON.stringify(error), + submittedUrl: `${req.body.url ?? ''}`, + type: types.DataValidation + }) return next(errors) } diff --git a/src/controllers/uploadFileController.js b/src/controllers/uploadFileController.js index 132322a8..7f6ab7e9 100644 --- a/src/controllers/uploadFileController.js +++ b/src/controllers/uploadFileController.js @@ -47,7 +47,11 @@ class UploadFileController extends UploadController { const errors = { datafile: new UploadFileController.Error(error.key, error, req, res) } - logger.warn('UploadFileController: local validation failed during file upload', error) + logger.warn({ + message: 'UploadFileController: local validation failed during file upload', + error, + type: types.DataValidation + }) return next(errors) } diff --git a/src/filters/makeDatasetSlugToReadableNameFilter.js b/src/filters/makeDatasetSlugToReadableNameFilter.js index 9114555a..03d3b920 100644 --- a/src/filters/makeDatasetSlugToReadableNameFilter.js +++ b/src/filters/makeDatasetSlugToReadableNameFilter.js @@ -18,7 +18,6 @@ export const makeDatasetSlugToReadableNameFilter = (datasetNameMapping) => { return (slug) => { const name = datasetNameMapping.get(slug) if (!name) { - // throw new Error(`Can't find a name for ${slug}`) // ToDo: work out what to do here? potentially update it with data from datasette logger.warn(`can't find a name for ${slug}`) return slug diff --git a/src/routes/accessibility.js b/src/routes/accessibility.js index 361afb21..e37e58d6 100644 --- a/src/routes/accessibility.js +++ b/src/routes/accessibility.js @@ -1,10 +1,11 @@ import express from 'express' import nunjucks from 'nunjucks' +import { render, EmptyParams } from './schemas.js' const router = express.Router() router.get('/', (req, res) => { - const accessibilityPage = nunjucks.render('accessibility.html', {}) + const accessibilityPage = render(nunjucks, 'accessibility.html', EmptyParams, {}) res.send(accessibilityPage) }) diff --git a/src/routes/cookies.js b/src/routes/cookies.js index 21139986..dd683162 100644 --- a/src/routes/cookies.js +++ b/src/routes/cookies.js @@ -1,10 +1,11 @@ import express from 'express' import nunjucks from 'nunjucks' +import { EmptyParams, render } from './schemas.js' const router = express.Router() router.get('/', (req, res) => { - const cookiesPage = nunjucks.render('cookies.html', {}) + const cookiesPage = render(nunjucks, 'cookies.html', EmptyParams, {}) res.send(cookiesPage) }) diff --git a/src/routes/manage.js b/src/routes/manage.js index 3c037beb..42f85cd9 100644 --- a/src/routes/manage.js +++ b/src/routes/manage.js @@ -1,10 +1,11 @@ import express from 'express' import nunjucks from 'nunjucks' +import { render, EmptyParams } from './schemas.js' const router = express.Router() router.get('/', (req, res) => { - const manage = nunjucks.render('start.html', {}) + const manage = render(nunjucks, 'start.html', EmptyParams, {}) res.send(manage) }) diff --git a/src/routes/privacy.js b/src/routes/privacy.js index e2f5a44a..28b1f97c 100644 --- a/src/routes/privacy.js +++ b/src/routes/privacy.js @@ -1,10 +1,11 @@ import express from 'express' import nunjucks from 'nunjucks' +import { render, EmptyParams } from './schemas.js' const router = express.Router() router.get('/', (req, res) => { - const privacyPage = nunjucks.render('privacy-notice.html', {}) + const privacyPage = render(nunjucks, 'privacy-notice.html', EmptyParams, {}) res.send(privacyPage) }) diff --git a/src/routes/schemas.js b/src/routes/schemas.js new file mode 100644 index 00000000..12ed0294 --- /dev/null +++ b/src/routes/schemas.js @@ -0,0 +1,154 @@ +/** + * This module provides code a 'render()' method wrapper that enforces + * a schema on the parameters passed to a template. + */ + +import * as v from 'valibot' +import config from '../../config/index.js' +import logger from '../utils/logger.js' +import { types } from '../utils/logging.js' +import { ValiError } from 'valibot' + +export const EmptyParams = v.object({}) +export const UptimeParams = v.object({ + upTime: v.string() +}) + +export const ErrorParams = v.strictObject({ + err: v.object({}) +}) + +const NonEmptyString = v.pipe(v.string(), v.nonEmpty()) + +export const Base = v.object({ + // pageTitle: NonEmptyString, + pageName: v.optional(NonEmptyString) + // serviceName: NonEmptyString, +}) + +export const StartPage = v.object({ + ...Base.entries +}) + +/** + * The values of this enum should match values of the 'status' column + * in the query in `performanceDbApi.getLpaOverview()` + */ +const datasetStatusEnum = { + Live: 'Live', + 'Needs fixing': 'Needs fixing', + Warning: 'Warning', + Error: 'Error', + 'Not submitted': 'Not submitted' +} + +const OrgNameField = v.strictObject({ name: NonEmptyString }) +const DatasetNameField = v.strictObject({ name: NonEmptyString }) + +export const OrgOverviewPage = v.strictObject({ + organisation: v.strictObject({ + name: NonEmptyString, + organisation: NonEmptyString + }), + datasets: v.array(v.strictObject({ + endpoint: v.url(), + status: v.enum(datasetStatusEnum), + slug: NonEmptyString + })), + totalDatasets: v.integer(), + datasetsWithEndpoints: v.integer(), + datasetsWithIssues: v.integer(), + datasetsWithErrors: v.integer() +}) + +export const OrgFindPage = v.strictObject({ + alphabetisedOrgs: v.record(NonEmptyString, + v.array(v.strictObject({ + name: NonEmptyString, + organisation: NonEmptyString + }))) +}) + +export const OrgGetStarted = v.strictObject({ + organisation: OrgNameField, + dataset: DatasetNameField +}) + +export const OrgDatasetTaskList = v.strictObject({ + taskList: v.array(v.strictObject({ + title: { text: NonEmptyString }, + href: v.url(), + status: NonEmptyString + })), + organisation: OrgNameField, + dataset: DatasetNameField +}) + +export const OrgEndpointError = v.strictObject({ + organisation: OrgNameField, + dataset: DatasetNameField, + errorData: v.strictObject({ + endpoint_url: v.url(), + http_status: v.integer(), + latest_log_entry_date: v.isoDateTime(), + latest_200_date: v.isoDateTime() + }) +}) + +export const OrgIssueDetails = v.strictObject({ + organisation: OrgNameField, + dataset: DatasetNameField, + errorHeading: NonEmptyString, + issueItems: v.array(v.strictObject({ + html: v.string(), + href: v.url() + })), + issueType: NonEmptyString, + entry: v.strictObject({ + title: NonEmptyString, + fields: v.array(v.strictObject({ + key: v.strictObject({ text: NonEmptyString }), + value: v.strictObject({ html: v.string() }), + classes: v.string() + })) + }) +}) + +/** + * @param {ValiError} error + * @returns {[]} + */ +export const invalidSchemaPaths = (error) => { + if (error instanceof ValiError) { + return error.issues.map(issue => issue.path.flatMap(p => p.key)) + } + throw new TypeError(`error is not a validation error: ${error.name}`) +} + +/** + * + * Note: Relies on {@link config.environment} + * + * @param {Response | { render: (template: string, params: object) => void} } renderer + * @param {string} template path to template + * @param {object} schema valibot schema + * @param {object} params + */ +export const render = (renderer, template, schema, params) => { + let parsed = params + try { + parsed = v.parse(schema, params) + } catch (error) { + if (error instanceof v.ValiError && config.environment !== 'production') { + // console.debug({ params, message: 'failed validation input' }) + const numIssues = error.issues.length + logger.warn(`Found ${numIssues} validation issue${numIssues === 1 ? '' : 's'} in template params for '${template}'`, { + errorMessage: `${error.message}`, + pathsWithIssues: invalidSchemaPaths(error), + type: types.App + }) + throw error + } + } + renderer.render(template, parsed) +} diff --git a/src/serverSetup/errorHandlers.js b/src/serverSetup/errorHandlers.js index 6f9f4921..e9bfbbf2 100644 --- a/src/serverSetup/errorHandlers.js +++ b/src/serverSetup/errorHandlers.js @@ -1,5 +1,6 @@ import logger from '../utils/logger.js' import { types } from '../utils/logging.js' +import { render, EmptyParams, ErrorParams } from '../routes/schemas.js' export function setupErrorHandlers (app) { app.use((err, req, res, next) => { @@ -21,9 +22,9 @@ export function setupErrorHandlers (app) { err.status = err.status || 500 try { - res.status(err.status).render(err.template, { err }) + render(res.status(err.status), err.template, ErrorParams, { err }) } catch (e) { - res.status(err.status).render('errorPages/500', { err }) + render(res.status(err.status), 'errorPages/500', ErrorParams, { err }) } }) @@ -34,6 +35,6 @@ export function setupErrorHandlers (app) { endpoint: req.originalUrl, message: 'not found' }) - res.status(404).render('errorPages/404') + render(res.status(404), 'errorPages/404', EmptyParams, {}) }) } diff --git a/src/serverSetup/middlewares.js b/src/serverSetup/middlewares.js index acd5154e..cc0f56fd 100644 --- a/src/serverSetup/middlewares.js +++ b/src/serverSetup/middlewares.js @@ -5,6 +5,7 @@ import logger from '../utils/logger.js' import { types } from '../utils/logging.js' import hash from '../utils/hasher.js' import config from '../../config/index.js' +import { render, UptimeParams } from '../routes/schemas.js' export function setupMiddlewares (app) { app.use((req, res, next) => { @@ -31,7 +32,7 @@ export function setupMiddlewares (app) { app.use((req, res, next) => { const serviceDown = config.maintenance.serviceUnavailable || false if (serviceDown) { - res.status(503).render('errorPages/503', { upTime: config.maintenance.upTime }) + render(res.status(503), 'errorPages/503', UptimeParams, { upTime: config.maintenance.upTime }) } else { next() } From d3397109008293d0427dcd8203a5c61f44fee2d8 Mon Sep 17 00:00:00 2001 From: Roland Sadowski Date: Fri, 16 Aug 2024 13:14:02 +0100 Subject: [PATCH 04/17] revert replacement of render() --- src/controllers/OrganisationsController.js | 14 +++++++------- src/routes/accessibility.js | 3 +-- src/routes/cookies.js | 3 +-- src/routes/manage.js | 3 +-- src/routes/privacy.js | 3 +-- src/serverSetup/errorHandlers.js | 7 +++---- src/serverSetup/middlewares.js | 3 +-- 7 files changed, 15 insertions(+), 21 deletions(-) diff --git a/src/controllers/OrganisationsController.js b/src/controllers/OrganisationsController.js index 4e8afcbb..a1ddfba2 100644 --- a/src/controllers/OrganisationsController.js +++ b/src/controllers/OrganisationsController.js @@ -4,7 +4,6 @@ import logger from '../utils/logger.js' import { types } from '../utils/logging.js' import { dataSubjects } from '../utils/utils.js' import { statusToTagClass } from '../filters/filters.js' -import { render, OrgGetStarted, OrgOverviewPage, OrgFindPage, OrgDatasetTaskList, OrgEndpointError, OrgIssueDetails } from '../routes/schemas.js' // get a list of available datasets const availableDatasets = Object.values(dataSubjects) @@ -95,7 +94,7 @@ const organisationsController = { datasetsWithErrors } - render(res, 'organisations/overview.html', OrgOverviewPage, params) + res.render('organisations/overview.html', params) } catch (error) { logger.warn('organisationsController.getOverview(): ' + error.message ?? error.errorMessage, { type: types.App }) next(error) @@ -125,7 +124,7 @@ const organisationsController = { return acc }, {}) - render(res, 'organisations/find.html', OrgFindPage, { alphabetisedOrgs }) + res.render('organisations/find.html', { alphabetisedOrgs }) } catch (err) { logger.warn('organisationsController.getOrganisations(): ' + err.message ?? err.errorMessage, { type: types.App }) next(err) @@ -158,7 +157,7 @@ const organisationsController = { dataset } - render(res, 'organisations/get-started.html', OrgGetStarted, params) + res.render('organisations/get-started.html', params) } catch (err) { logger.warn({ message: `OrganisationsController.getStarted(): ${err.message}`, @@ -209,7 +208,8 @@ const organisationsController = { organisation, dataset } - render(res, 'organisations/datasetTaskList.html', OrgDatasetTaskList, params) + + res.render('organisations/datasetTaskList.html', params) } catch (e) { logger.warn(`getDAtasetTaskList() failed for lpa='${lpa}', datasetId='${datasetId}'`, { type: types.App }) next(e) @@ -241,7 +241,7 @@ const organisationsController = { latest_200_date: last200Datetime } } - render(res, 'organisations/http-error.html', OrgEndpointError, params) + res.render('organisations/http-error.html', params) } catch (e) { logger.warn(`conditionalTaskListHandler() failed for lpa='${lpa}', datasetId='${datasetId}'`, { type: types.App }) next(e) @@ -379,7 +379,7 @@ const organisationsController = { issueType } - render(res, 'organisations/issueDetails.html', OrgIssueDetails, params) + res.render('organisations/issueDetails.html', params) } catch (e) { logger.warn(`getIssueDetails() failed for lpa='${lpa}', datasetId='${datasetId}', issue=${issueType}, entityNumber=${entityNumber}, resourceId=${resourceId}`, { type: types.App }) next(e) diff --git a/src/routes/accessibility.js b/src/routes/accessibility.js index e37e58d6..361afb21 100644 --- a/src/routes/accessibility.js +++ b/src/routes/accessibility.js @@ -1,11 +1,10 @@ import express from 'express' import nunjucks from 'nunjucks' -import { render, EmptyParams } from './schemas.js' const router = express.Router() router.get('/', (req, res) => { - const accessibilityPage = render(nunjucks, 'accessibility.html', EmptyParams, {}) + const accessibilityPage = nunjucks.render('accessibility.html', {}) res.send(accessibilityPage) }) diff --git a/src/routes/cookies.js b/src/routes/cookies.js index dd683162..21139986 100644 --- a/src/routes/cookies.js +++ b/src/routes/cookies.js @@ -1,11 +1,10 @@ import express from 'express' import nunjucks from 'nunjucks' -import { EmptyParams, render } from './schemas.js' const router = express.Router() router.get('/', (req, res) => { - const cookiesPage = render(nunjucks, 'cookies.html', EmptyParams, {}) + const cookiesPage = nunjucks.render('cookies.html', {}) res.send(cookiesPage) }) diff --git a/src/routes/manage.js b/src/routes/manage.js index 42f85cd9..3c037beb 100644 --- a/src/routes/manage.js +++ b/src/routes/manage.js @@ -1,11 +1,10 @@ import express from 'express' import nunjucks from 'nunjucks' -import { render, EmptyParams } from './schemas.js' const router = express.Router() router.get('/', (req, res) => { - const manage = render(nunjucks, 'start.html', EmptyParams, {}) + const manage = nunjucks.render('start.html', {}) res.send(manage) }) diff --git a/src/routes/privacy.js b/src/routes/privacy.js index 28b1f97c..e2f5a44a 100644 --- a/src/routes/privacy.js +++ b/src/routes/privacy.js @@ -1,11 +1,10 @@ import express from 'express' import nunjucks from 'nunjucks' -import { render, EmptyParams } from './schemas.js' const router = express.Router() router.get('/', (req, res) => { - const privacyPage = render(nunjucks, 'privacy-notice.html', EmptyParams, {}) + const privacyPage = nunjucks.render('privacy-notice.html', {}) res.send(privacyPage) }) diff --git a/src/serverSetup/errorHandlers.js b/src/serverSetup/errorHandlers.js index e9bfbbf2..6f9f4921 100644 --- a/src/serverSetup/errorHandlers.js +++ b/src/serverSetup/errorHandlers.js @@ -1,6 +1,5 @@ import logger from '../utils/logger.js' import { types } from '../utils/logging.js' -import { render, EmptyParams, ErrorParams } from '../routes/schemas.js' export function setupErrorHandlers (app) { app.use((err, req, res, next) => { @@ -22,9 +21,9 @@ export function setupErrorHandlers (app) { err.status = err.status || 500 try { - render(res.status(err.status), err.template, ErrorParams, { err }) + res.status(err.status).render(err.template, { err }) } catch (e) { - render(res.status(err.status), 'errorPages/500', ErrorParams, { err }) + res.status(err.status).render('errorPages/500', { err }) } }) @@ -35,6 +34,6 @@ export function setupErrorHandlers (app) { endpoint: req.originalUrl, message: 'not found' }) - render(res.status(404), 'errorPages/404', EmptyParams, {}) + res.status(404).render('errorPages/404') }) } diff --git a/src/serverSetup/middlewares.js b/src/serverSetup/middlewares.js index cc0f56fd..acd5154e 100644 --- a/src/serverSetup/middlewares.js +++ b/src/serverSetup/middlewares.js @@ -5,7 +5,6 @@ import logger from '../utils/logger.js' import { types } from '../utils/logging.js' import hash from '../utils/hasher.js' import config from '../../config/index.js' -import { render, UptimeParams } from '../routes/schemas.js' export function setupMiddlewares (app) { app.use((req, res, next) => { @@ -32,7 +31,7 @@ export function setupMiddlewares (app) { app.use((req, res, next) => { const serviceDown = config.maintenance.serviceUnavailable || false if (serviceDown) { - render(res.status(503), 'errorPages/503', UptimeParams, { upTime: config.maintenance.upTime }) + res.status(503).render('errorPages/503', { upTime: config.maintenance.upTime }) } else { next() } From 43e3897a6eb2864ec4995a50f9f289193a5825ab Mon Sep 17 00:00:00 2001 From: Roland Sadowski Date: Mon, 19 Aug 2024 15:40:35 +0100 Subject: [PATCH 05/17] template params schema validation - overrides the .render() method on nunjucks to call our wrapper that also validates (depending on 'environment') - for certain cases we resort to manually calling our render function (express doesn't make it easy to fully customize the behaviour) --- src/controllers/OrganisationsController.js | 28 +++- .../makeDatasetSlugToReadableNameFilter.js | 2 +- src/models/requestData.js | 10 +- src/models/responseDetails.js | 8 +- src/routes/schemas.js | 124 +++++++++++------- src/serverSetup/nunjucks.js | 43 ++++-- src/utils/custom-renderer.js | 68 ++++++++++ test/unit/check-answers.test.js | 3 +- test/unit/dataset-details.test.js | 8 +- test/unit/lpaOverviewPage.test.js | 22 +--- ...akeDatasetSlugToReadableNameFilter.test.js | 12 +- 11 files changed, 224 insertions(+), 104 deletions(-) create mode 100644 src/utils/custom-renderer.js diff --git a/src/controllers/OrganisationsController.js b/src/controllers/OrganisationsController.js index a1ddfba2..5e664079 100644 --- a/src/controllers/OrganisationsController.js +++ b/src/controllers/OrganisationsController.js @@ -4,6 +4,9 @@ import logger from '../utils/logger.js' import { types } from '../utils/logging.js' import { dataSubjects } from '../utils/utils.js' import { statusToTagClass } from '../filters/filters.js' +import { render } from '../utils/custom-renderer.js' +import { templateSchema } from '../routes/schemas.js' +import * as v from 'valibot' // get a list of available datasets const availableDatasets = Object.values(dataSubjects) @@ -13,6 +16,12 @@ const availableDatasets = Object.values(dataSubjects) .map(dataset => dataset.value) ) +function validateAndRender (res, name, params) { + const schema = templateSchema.get(name) ?? v.any() + logger.info(`rendering '${name}' with schema=<${schema ? 'defined' : 'any'}>`, { type: types.App }) + return render(res, name, schema, params) +} + /** * Returns a status tag object with a text label and a CSS class based on the status. * @@ -94,7 +103,7 @@ const organisationsController = { datasetsWithErrors } - res.render('organisations/overview.html', params) + validateAndRender(res, 'organisations/overview.html', params) } catch (error) { logger.warn('organisationsController.getOverview(): ' + error.message ?? error.errorMessage, { type: types.App }) next(error) @@ -124,7 +133,7 @@ const organisationsController = { return acc }, {}) - res.render('organisations/find.html', { alphabetisedOrgs }) + validateAndRender(res, 'organisations/find.html', { alphabetisedOrgs }) } catch (err) { logger.warn('organisationsController.getOrganisations(): ' + err.message ?? err.errorMessage, { type: types.App }) next(err) @@ -157,7 +166,7 @@ const organisationsController = { dataset } - res.render('organisations/get-started.html', params) + validateAndRender(res, 'organisations/get-started.html', params) } catch (err) { logger.warn({ message: `OrganisationsController.getStarted(): ${err.message}`, @@ -209,9 +218,14 @@ const organisationsController = { dataset } - res.render('organisations/datasetTaskList.html', params) + validateAndRender(res, 'organisations/datasetTaskList.html', params) } catch (e) { - logger.warn(`getDAtasetTaskList() failed for lpa='${lpa}', datasetId='${datasetId}'`, { type: types.App }) + logger.warn(`getDatasetTaskList() failed for lpa='${lpa}', datasetId='${datasetId}'`, + { + type: types.App, + errorMessage: e.message, + errorStack: e.stack + }) next(e) } }, @@ -241,7 +255,7 @@ const organisationsController = { latest_200_date: last200Datetime } } - res.render('organisations/http-error.html', params) + validateAndRender(res, 'organisations/http-error.html', params) } catch (e) { logger.warn(`conditionalTaskListHandler() failed for lpa='${lpa}', datasetId='${datasetId}'`, { type: types.App }) next(e) @@ -379,7 +393,7 @@ const organisationsController = { issueType } - res.render('organisations/issueDetails.html', params) + validateAndRender(res, 'organisations/issueDetails.html', params) } catch (e) { logger.warn(`getIssueDetails() failed for lpa='${lpa}', datasetId='${datasetId}', issue=${issueType}, entityNumber=${entityNumber}, resourceId=${resourceId}`, { type: types.App }) next(e) diff --git a/src/filters/makeDatasetSlugToReadableNameFilter.js b/src/filters/makeDatasetSlugToReadableNameFilter.js index 03d3b920..1388d07a 100644 --- a/src/filters/makeDatasetSlugToReadableNameFilter.js +++ b/src/filters/makeDatasetSlugToReadableNameFilter.js @@ -19,7 +19,7 @@ export const makeDatasetSlugToReadableNameFilter = (datasetNameMapping) => { const name = datasetNameMapping.get(slug) if (!name) { // ToDo: work out what to do here? potentially update it with data from datasette - logger.warn(`can't find a name for ${slug}`) + logger.debug(`can't find a name for ${slug}`) return slug } return name diff --git a/src/models/requestData.js b/src/models/requestData.js index 3ad503c6..689cecf1 100644 --- a/src/models/requestData.js +++ b/src/models/requestData.js @@ -29,7 +29,7 @@ export default class RequestData { getErrorSummary () { if (!this.response || !this.response.data || !this.response.data['error-summary']) { - logger.warn('trying to get error summary when there is none: request id: ' + this.id) + logger.warn('trying to get error summary when there is none', { requestId: this.id }) return [] } return this.response.data['error-summary'] @@ -45,7 +45,7 @@ export default class RequestData { getError () { if (!this.response) { - logger.warn('trying to get error when there are none: request id: ' + this.id) + logger.warn('trying to get error when there are none', { requestId: this.id }) return { message: 'An unknown error occurred.' } } @@ -54,11 +54,11 @@ export default class RequestData { hasErrors () { if (!this.response || !this.response.data) { - logger.warn('trying to check for errors when there are none: request id: ' + this.id) + logger.warn('trying to check for errors when there are none', { requestId: this.id }) return true } if (this.response.data['error-summary'] == null) { - logger.warn('trying to check for errors but there is no error-summary: request id: ' + this.id) + logger.warn('trying to check for errors but there is no error-summary', { requestId: this.id }) return true } return this.response.data['error-summary'].length > 0 @@ -71,7 +71,7 @@ export default class RequestData { getColumnFieldLog () { if (!this.response || !this.response.data || !this.response.data['column-field-log']) { - logger.warn('trying to get column field log when there is none: request id: ' + this.id) + logger.warn('trying to get column field log when there is none', { requestId: this.id }) return [] } return this.response.data['column-field-log'] diff --git a/src/models/responseDetails.js b/src/models/responseDetails.js index 028b037a..3e046855 100644 --- a/src/models/responseDetails.js +++ b/src/models/responseDetails.js @@ -11,7 +11,7 @@ export default class ResponseDetails { getRows () { if (!this.response) { - logger.warn('trying to get response details when there are none: request id: ' + this.id) + logger.warn('trying to get response details when there are none', { requestId: this.id }) return [] } return this.response @@ -19,7 +19,7 @@ export default class ResponseDetails { getColumnFieldLog () { if (!this.columnFieldLog) { - logger.warn('trying to get column field log when there is none: request id: ' + this.id) + logger.warn('trying to get column field log when there is none', { requestId: this.id }) return [] } return this.columnFieldLog @@ -69,7 +69,7 @@ export default class ResponseDetails { // This function returns an array of rows with verbose columns getRowsWithVerboseColumns (filterNonErrors = false) { if (!this.response) { - logger.warn('trying to get response details when there are none: request id: ' + this.id) + logger.warn('trying to get response details when there are none', { requestId: this.id }) return [] } @@ -105,7 +105,7 @@ export default class ResponseDetails { getGeometries () { if (!this.response) { - logger.warn('trying to get response details when there are none: request id: ' + this.id) + logger.warn('trying to get response details when there are none', { requestId: this.id }) return undefined } diff --git a/src/routes/schemas.js b/src/routes/schemas.js index 12ed0294..f91bb024 100644 --- a/src/routes/schemas.js +++ b/src/routes/schemas.js @@ -1,13 +1,9 @@ /** - * This module provides code a 'render()' method wrapper that enforces - * a schema on the parameters passed to a template. + * This module provides code a set of schemas for params passed to + * the nunjuck templates in `./src/views` */ import * as v from 'valibot' -import config from '../../config/index.js' -import logger from '../utils/logger.js' -import { types } from '../utils/logging.js' -import { ValiError } from 'valibot' export const EmptyParams = v.object({}) export const UptimeParams = v.object({ @@ -21,9 +17,9 @@ export const ErrorParams = v.strictObject({ const NonEmptyString = v.pipe(v.string(), v.nonEmpty()) export const Base = v.object({ + // serviceName: NonEmptyString, // pageTitle: NonEmptyString, pageName: v.optional(NonEmptyString) - // serviceName: NonEmptyString, }) export const StartPage = v.object({ @@ -51,9 +47,13 @@ export const OrgOverviewPage = v.strictObject({ organisation: NonEmptyString }), datasets: v.array(v.strictObject({ - endpoint: v.url(), + endpoint: v.optional(v.url()), status: v.enum(datasetStatusEnum), - slug: NonEmptyString + slug: NonEmptyString, + issue_count: v.optional(v.number()), + error: v.optional(v.nullable(NonEmptyString)), + http_error: v.optional(NonEmptyString), + issue: v.optional(NonEmptyString) })), totalDatasets: v.integer(), datasetsWithEndpoints: v.integer(), @@ -76,9 +76,14 @@ export const OrgGetStarted = v.strictObject({ export const OrgDatasetTaskList = v.strictObject({ taskList: v.array(v.strictObject({ - title: { text: NonEmptyString }, + title: v.strictObject({ text: NonEmptyString }), href: v.url(), - status: NonEmptyString + status: v.strictObject({ + tag: v.strictObject({ + classes: NonEmptyString, + text: NonEmptyString + }) + }) })), organisation: OrgNameField, dataset: DatasetNameField @@ -114,41 +119,68 @@ export const OrgIssueDetails = v.strictObject({ }) }) -/** - * @param {ValiError} error - * @returns {[]} - */ -export const invalidSchemaPaths = (error) => { - if (error instanceof ValiError) { - return error.issues.map(issue => issue.path.flatMap(p => p.key)) - } - throw new TypeError(`error is not a validation error: ${error.name}`) -} +export const CheckAnswers = v.strictObject({ + values: v.strictObject({ + lpa: NonEmptyString, + name: NonEmptyString, + email: v.pipe(v.string(), v.email()), + dataset: NonEmptyString, + 'endpoint-url': v.url(), + 'documentation-url': v.url(), + hasLicence: NonEmptyString + }) +}) + +export const ChooseDataset = v.strictObject({ + errors: v.strictObject({ + dataset: v.optional(v.strictObject({ + type: v.enum({ + required: 'required' + }) + })) + }) +}) + +export const DatasetDetails = v.strictObject({ + organisation: v.strictObject({ + name: NonEmptyString, + organisation: NonEmptyString + }), + dataset: v.strictObject({ + name: NonEmptyString, + dataset: NonEmptyString + }), + values: v.strictObject({ + dataset: NonEmptyString + }), + errors: v.record(NonEmptyString, v.strictObject({ + type: NonEmptyString + })) +}) /** - * - * Note: Relies on {@link config.environment} - * - * @param {Response | { render: (template: string, params: object) => void} } renderer - * @param {string} template path to template - * @param {object} schema valibot schema - * @param {object} params + * This acts as a registry of template -> schema for convenience. */ -export const render = (renderer, template, schema, params) => { - let parsed = params - try { - parsed = v.parse(schema, params) - } catch (error) { - if (error instanceof v.ValiError && config.environment !== 'production') { - // console.debug({ params, message: 'failed validation input' }) - const numIssues = error.issues.length - logger.warn(`Found ${numIssues} validation issue${numIssues === 1 ? '' : 's'} in template params for '${template}'`, { - errorMessage: `${error.message}`, - pathsWithIssues: invalidSchemaPaths(error), - type: types.App - }) - throw error - } - } - renderer.render(template, parsed) -} +export const templateSchema = new Map([ + ['dataset-details.html', DatasetDetails], + ['check-answers.html', CheckAnswers], + ['choose-dataset.html', ChooseDataset], + ['lpa-details.html', v.any()], + + ['submit/confirmation.html', v.any()], + + ['organisations/overview.html', OrgOverviewPage], + ['organisations/find.html', OrgFindPage], + ['organisations/get-started.html', OrgGetStarted], + ['organisations/datasetTaskList.html', OrgDatasetTaskList], + ['organisations/http-error.html', OrgEndpointError], + ['organisations/issueDetails.html', OrgIssueDetails], + + ['errorPages/503', UptimeParams], + ['errorPages/500', ErrorParams], + ['errorPages/404', EmptyParams], + ['privacy-notice.html', EmptyParams], + ['start.html', EmptyParams], + ['cookies.html', EmptyParams], + ['accessibility.html', EmptyParams] +]) diff --git a/src/serverSetup/nunjucks.js b/src/serverSetup/nunjucks.js index c9c26875..4caee7a5 100644 --- a/src/serverSetup/nunjucks.js +++ b/src/serverSetup/nunjucks.js @@ -1,24 +1,49 @@ import nunjucks from 'nunjucks' import config from '../../config/index.js' import addFilters from '../filters/filters.js' +import logger from '../utils/logger.js' +import * as customRenderer from '../utils/custom-renderer.js' +import * as schemas from '../routes/schemas.js' +import * as v from 'valibot' +/** + * We wanto to override nunjucks.render() with a function that + * validates the params against a schema. + */ +const proto = { + render (name, context) { + const schema = schemas.templateSchema.get(name) + logger.info(`rendering: ${name} with schema=<${schema ? 'defined' : 'any'}>`) + return customRenderer.render(nunjucks, name, schema ?? v.any(), context) + }, + configure (paths, options) { + return nunjucks.configure(paths, options) + } +} + +/** + * + * @param {{datasetNameMapping, app: object?}} param0 + * @returns + */ export function setupNunjucks ({ app, datasetNameMapping }) { + const options = { dev: true, noCache: true, watch: true } if (app) { - app.set('view engine', 'html') + options.express = app } - const nunjucksEnv = nunjucks.configure([ + const customNunjucks = Object.create(proto) + const nunjucksEnv = customNunjucks.configure([ 'src/views', 'src/views/check', 'src/views/submit', 'node_modules/govuk-frontend/dist/', 'node_modules/@x-govuk/govuk-prototype-components/' - ], { - express: app, - dev: true, - noCache: true, - watch: true - }) + ], options) + + if (app) { + app.set('view engine', 'html') + } const globalValues = { serviceName: config.serviceNames.submit, @@ -35,5 +60,5 @@ export function setupNunjucks ({ app, datasetNameMapping }) { }) addFilters(nunjucksEnv, { datasetNameMapping }) - return nunjucks + return customNunjucks } diff --git a/src/utils/custom-renderer.js b/src/utils/custom-renderer.js new file mode 100644 index 00000000..07a88940 --- /dev/null +++ b/src/utils/custom-renderer.js @@ -0,0 +1,68 @@ +import * as v from 'valibot' +import config from '../../config/index.js' +import logger from './logger.js' +import { types } from './logging.js' + +/** + * @param {ValiError} error + * @returns {[string][]} + */ +export const invalidSchemaPaths = (error) => { + if (error instanceof v.ValiError) { + return error.issues.map((issue) => issue.path.flatMap((p) => p.key)) + } + throw new TypeError(`error is not a validation error: ${error.name}`) +} + +/** + * Depending on the config, validates the params against the given schema before rendering the template. + * + * If validation error is raised, it's logged and re-thrown. + * + * Motivation: we want to ensure in developmen/test environments, that the data passed to + * our templates is valid. This will help us ensure that we're testing the right thing. + * + * Note: Relies on {@link config.environment} + * + * @param {Response | { render: (template: string, params: object) => string} } renderer + * @param {string} template path to template + * @param {object} schema valibot schema + * @param {object} params + * @returns {string} + */ +export const render = (renderer, template, schema, params) => { + let parsed = params + if (config.environment !== 'production' && config.environment !== 'staging') { + try { + parsed = v.parse(schema, params) + } catch (error) { + if (error instanceof v.ValiError) { + // the below will only show up in the terminal when testing + console.debug({ params, message: 'failed validation input' }) + logger.warn( + validationErrorMessage(error, template), + { + errorMessage: `${error.message}`, + pathsWithIssues: invalidSchemaPaths(error), + type: types.App + } + ) + } + throw error + } + } + return renderer.render(template, parsed) +} + +/** + * Creates an error message from the given error + * + * @param {ValiError} error validation error + * @param {string} template template name + * @returns string + */ +function validationErrorMessage (error, template) { + const numIssues = error.issues.length + const message = `Found ${numIssues} validation issue${numIssues === 1 ? '' : 's'} in template params for '${template}'` + return message +} diff --git a/test/unit/check-answers.test.js b/test/unit/check-answers.test.js index 1c852e99..3bf450f4 100644 --- a/test/unit/check-answers.test.js +++ b/test/unit/check-answers.test.js @@ -10,7 +10,7 @@ describe('check-answers View', async () => { values: { lpa: 'mockLpa', name: 'mockName', - email: 'mockEmail', + email: 'mockEmail@example.com', dataset: 'mockDataset', 'endpoint-url': 'mockEndpointUrl', 'documentation-url': 'mockDocumentationUrl', @@ -67,6 +67,7 @@ describe('check-answers View', async () => { } } const html = stripWhitespace(nunjucks.render('check-answers.html', noLicenseParams)) + const hasLicenceRegex = new RegExp('
.*Licence.*False.*Change.*
', 'g') expect(html).toMatch(hasLicenceRegex) }) diff --git a/test/unit/dataset-details.test.js b/test/unit/dataset-details.test.js index 054e48a5..add2d819 100644 --- a/test/unit/dataset-details.test.js +++ b/test/unit/dataset-details.test.js @@ -6,6 +6,8 @@ import { runGenericPageTests } from './generic-page.js' import { stripWhitespace } from '../utils/stripWhiteSpace.js' import { testValidationErrorMessage } from './validation-tests.js' import { mockDataSubjects } from './data.js' +import { render } from '../../src/utils/custom-renderer.js' +import * as v from 'valibot' const nunjucks = setupNunjucks({ datasetNameMapping: new Map() }) @@ -18,6 +20,8 @@ function errorTestFn ({ }) { return () => { const errorParams = { + dataset: params.dataset, + organisation: params.organisation, values: params.values, errors: { [fieldId]: { @@ -26,7 +30,9 @@ function errorTestFn ({ } } - const html = nunjucks.render(template, errorParams) + // NOTE(rosdo): we're using `any` schema here because we + // want to test output for incomplete data + const html = render(nunjucks, template, v.any(), errorParams) testValidationErrorMessage(html, fieldId, expectedMessage) } diff --git a/test/unit/lpaOverviewPage.test.js b/test/unit/lpaOverviewPage.test.js index 2c89567a..32bf8609 100644 --- a/test/unit/lpaOverviewPage.test.js +++ b/test/unit/lpaOverviewPage.test.js @@ -1,27 +1,11 @@ import { describe, it, expect } from 'vitest' -import nunjucks from 'nunjucks' -import addFilters from '../../src/filters/filters' +import { setupNunjucks } from '../../src/serverSetup/nunjucks.js' import { runGenericPageTests } from './generic-page.js' import jsdom from 'jsdom' import { makeDatasetSlugToReadableNameFilter } from '../../src/filters/makeDatasetSlugToReadableNameFilter.js' -const nunjucksEnv = nunjucks.configure([ - 'src/views', - 'src/views/check', - 'src/views/submit', - 'node_modules/govuk-frontend/dist/', - 'node_modules/@x-govuk/govuk-prototype-components/' -], { - dev: true, - noCache: true, - watch: true -}) - -const datasetNameMapping = new Map([ - -]) - -addFilters(nunjucksEnv, { datasetNameMapping }) +const datasetNameMapping = new Map() +const nunjucks = setupNunjucks({ datasetNameMapping }) describe('LPA Overview Page', () => { const params = { diff --git a/test/unit/makeDatasetSlugToReadableNameFilter.test.js b/test/unit/makeDatasetSlugToReadableNameFilter.test.js index 12d26b73..3193b08f 100644 --- a/test/unit/makeDatasetSlugToReadableNameFilter.test.js +++ b/test/unit/makeDatasetSlugToReadableNameFilter.test.js @@ -1,6 +1,5 @@ // makeDatasetSlugToReadableNameFilter.test.js -import { vi, it, describe, expect } from 'vitest' -import logger from '../../src/utils/logger.js' +import { it, describe, expect } from 'vitest' import { makeDatasetSlugToReadableNameFilter } from '../../src/filters/makeDatasetSlugToReadableNameFilter' describe('makeDatasetSlugToReadableNameFilter', () => { @@ -19,13 +18,4 @@ describe('makeDatasetSlugToReadableNameFilter', () => { it('returns the original slug if it is not found in the dataset name mapping', () => { expect(filter('unknown-slug')).toBe('unknown-slug') }) - - it('logs an warn if the provided slug is not found in the dataset name mapping', () => { - const loggerWarningSpy = vi.spyOn(logger, 'warn') - - filter('unknown-slug') - - expect(loggerWarningSpy).toHaveBeenCalledTimes(1) - expect(loggerWarningSpy).toHaveBeenCalledWith('can\'t find a name for unknown-slug') - }) }) From 0753adb7266fd75515032a2d4db73cad00c67528 Mon Sep 17 00:00:00 2001 From: George Goodall Date: Tue, 20 Aug 2024 15:29:15 +0100 Subject: [PATCH 06/17] update schemas --- src/routes/schemas.js | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/routes/schemas.js b/src/routes/schemas.js index f91bb024..9953739a 100644 --- a/src/routes/schemas.js +++ b/src/routes/schemas.js @@ -85,12 +85,20 @@ export const OrgDatasetTaskList = v.strictObject({ }) }) })), - organisation: OrgNameField, - dataset: DatasetNameField + organisation: v.strictObject({ + name: NonEmptyString, + organisation: NonEmptyString + }), + dataset: v.strictObject({ + name: NonEmptyString + }), }) export const OrgEndpointError = v.strictObject({ - organisation: OrgNameField, + organisation: v.strictObject({ + name: NonEmptyString, + organisation: NonEmptyString + }), dataset: DatasetNameField, errorData: v.strictObject({ endpoint_url: v.url(), @@ -101,8 +109,14 @@ export const OrgEndpointError = v.strictObject({ }) export const OrgIssueDetails = v.strictObject({ - organisation: OrgNameField, - dataset: DatasetNameField, + organisation: v.strictObject({ + name: NonEmptyString, + organisation: NonEmptyString + }), + dataset: v.object({ + name: NonEmptyString, + dataset: v.string() + }), errorHeading: NonEmptyString, issueItems: v.array(v.strictObject({ html: v.string(), From f37c6c7a9db497c1374efe826768acecf661bc2d Mon Sep 17 00:00:00 2001 From: George Goodall Date: Tue, 20 Aug 2024 15:29:24 +0100 Subject: [PATCH 07/17] linting --- src/routes/schemas.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/routes/schemas.js b/src/routes/schemas.js index 9953739a..cf34a5c7 100644 --- a/src/routes/schemas.js +++ b/src/routes/schemas.js @@ -85,13 +85,13 @@ export const OrgDatasetTaskList = v.strictObject({ }) }) })), - organisation: v.strictObject({ + organisation: v.strictObject({ name: NonEmptyString, organisation: NonEmptyString }), - dataset: v.strictObject({ + dataset: v.strictObject({ name: NonEmptyString - }), + }) }) export const OrgEndpointError = v.strictObject({ @@ -109,11 +109,11 @@ export const OrgEndpointError = v.strictObject({ }) export const OrgIssueDetails = v.strictObject({ - organisation: v.strictObject({ + organisation: v.strictObject({ name: NonEmptyString, organisation: NonEmptyString }), - dataset: v.object({ + dataset: v.object({ name: NonEmptyString, dataset: v.string() }), From 0ccdf0bb849f324a998752aa495b596cfa71e95c Mon Sep 17 00:00:00 2001 From: George Goodall Date: Tue, 20 Aug 2024 16:55:42 +0100 Subject: [PATCH 08/17] fixed tests --- test/unit/organisationsController.test.js | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/test/unit/organisationsController.test.js b/test/unit/organisationsController.test.js index c185e3f1..201d43c2 100644 --- a/test/unit/organisationsController.test.js +++ b/test/unit/organisationsController.test.js @@ -203,7 +203,7 @@ describe('OrganisationsController.js', () => { const next = vi.fn() vi.mocked(datasette.runQuery).mockResolvedValueOnce({ - formattedData: [{ name: 'Example Organisation' }] + formattedData: [{ name: 'Example Organisation', organisation: 'ORG' }] }).mockResolvedValueOnce({ formattedData: [{ name: 'Example Dataset' }] }) @@ -235,7 +235,7 @@ describe('OrganisationsController.js', () => { } } }], - organisation: { name: 'Example Organisation' }, + organisation: { name: 'Example Organisation', organisation: 'ORG' }, dataset: { name: 'Example Dataset' } }) }) @@ -246,7 +246,7 @@ describe('OrganisationsController.js', () => { const next = vi.fn() vi.mocked(datasette.runQuery).mockResolvedValueOnce({ - formattedData: [{ name: 'Example Organisation' }] + formattedData: [{ name: 'Example Organisation', organisation: 'ORG' }] }).mockResolvedValueOnce({ formattedData: [{ name: 'Example Dataset' }] }) @@ -298,7 +298,7 @@ describe('OrganisationsController.js', () => { } } ], - organisation: { name: 'Example Organisation' }, + organisation: { name: 'Example Organisation', organisation: 'ORG' }, dataset: { name: 'Example Dataset' } }) }) @@ -337,8 +337,8 @@ describe('OrganisationsController.js', () => { const next = vi.fn() vi.mocked(datasette.runQuery) - .mockReturnValueOnce({ formattedData: [{ name: 'mock lpa' }] }) - .mockReturnValueOnce({ formattedData: [{ name: 'mock dataset' }] }) + .mockReturnValueOnce({ formattedData: [{ name: 'mock lpa', organisation: 'ORG' }] }) + .mockReturnValueOnce({ formattedData: [{ name: 'mock dataset', dataset: 'mock-dataset' }] }) vi.mocked(performanceDbApi.getLatestResource).mockResolvedValueOnce({ resource: 'mockResourceId' }) @@ -370,10 +370,12 @@ describe('OrganisationsController.js', () => { expect(res.render).toHaveBeenCalledTimes(1) expect(res.render).toHaveBeenCalledWith('organisations/issueDetails.html', { organisation: { - name: 'mock lpa' + name: 'mock lpa', + organisation: 'ORG' }, dataset: { - name: 'mock dataset' + name: 'mock dataset', + dataset: 'mock-dataset' }, errorHeading: 'mock task message 1', issueItems: [ @@ -427,7 +429,7 @@ describe('OrganisationsController.js', () => { const next = vi.fn() const resourceStatus = { status: '404', days_since_200: 3, endpoint_url: 'https://example.com', latest_log_entry_date: '2022-01-01T12:00:00.000Z' } - vi.mocked(datasette.runQuery).mockResolvedValueOnce({ formattedData: [{ name: 'Example Organisation' }] }) + vi.mocked(datasette.runQuery).mockResolvedValueOnce({ formattedData: [{ name: 'Example Organisation', organisation: 'ORG' }] }) vi.mocked(datasette.runQuery).mockResolvedValueOnce({ formattedData: [{ name: 'Example Dataset' }] }) await organisationsController.getEndpointError(req, res, next, { resourceStatus }) @@ -437,7 +439,7 @@ describe('OrganisationsController.js', () => { expect(res.render).toHaveBeenCalledTimes(1) const renderArgs = res.render.mock.calls[0] expect(renderArgs[0]).toEqual('organisations/http-error.html') - expect(renderArgs[1].organisation).toEqual({ name: 'Example Organisation' }) + expect(renderArgs[1].organisation).toEqual({ name: 'Example Organisation', organisation: 'ORG' }) expect(renderArgs[1].dataset).toEqual({ name: 'Example Dataset' }) expect(renderArgs[1].errorData.endpoint_url).toEqual('https://example.com') expect(renderArgs[1].errorData.http_status).toEqual('404') From a0ab73cceb3087f634383fdd5f2071c500048786 Mon Sep 17 00:00:00 2001 From: Roland Sadowski Date: Tue, 20 Aug 2024 12:28:51 +0100 Subject: [PATCH 09/17] add xml to supported mime types --- src/controllers/uploadFileController.js | 7 +++---- src/utils/utils.js | 1 + 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/controllers/uploadFileController.js b/src/controllers/uploadFileController.js index 7f6ab7e9..674cbc04 100644 --- a/src/controllers/uploadFileController.js +++ b/src/controllers/uploadFileController.js @@ -47,10 +47,9 @@ class UploadFileController extends UploadController { const errors = { datafile: new UploadFileController.Error(error.key, error, req, res) } - logger.warn({ - message: 'UploadFileController: local validation failed during file upload', - error, - type: types.DataValidation + logger.info('UploadFileController: local validation failed during file upload', { + type: types.App, + fileValidationError: error }) return next(errors) } diff --git a/src/utils/utils.js b/src/utils/utils.js index 5e429735..0484a209 100644 --- a/src/utils/utils.js +++ b/src/utils/utils.js @@ -88,6 +88,7 @@ export const allowedFileTypes = { csv: ['text/csv', 'text/plain', 'application/octet-stream', 'binary/octet-stream'], xls: ['application/vnd.ms-excel', 'application/octet-stream', 'binary/octet-stream'], xlsx: ['application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/octet-stream', 'binary/octet-stream'], + xml: ['application/xml', 'text/xml'], json: ['application/json', 'application/octet-stream', 'binary/octet-stream'], geojson: ['application/vnd.geo+json', 'application/octet-stream', 'binary/octet-stream', 'application/geo+json'], gml: ['application/gml+xml', 'application/octet-stream', 'binary/octet-stream'], From 5d1996f2fe655a5d2c78d6986924801bb7a1df4b Mon Sep 17 00:00:00 2001 From: George Goodall Date: Thu, 22 Aug 2024 10:14:08 +0100 Subject: [PATCH 10/17] update step by step content --- src/views/organisations/get-started.html | 33 ++++++++++++++---------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/src/views/organisations/get-started.html b/src/views/organisations/get-started.html index 3c607134..1157d848 100644 --- a/src/views/organisations/get-started.html +++ b/src/views/organisations/get-started.html @@ -161,23 +161,28 @@

-

Use the submit - data service to submit your - data URL, your documentation URL - and that the data is provided under the Open Government Licence.

+

To submit your data URL, send an email to digitalland@communities.gov.uk.

+ The email must include: +
    +
  • + your full name +
  • +
  • + the name of your planning authority +
  • +
  • + a link to the webpage on your official planning authority website +
  • +
  • + a link to the data URL +
  • +
-

We’ll process your submission and add your dataset to the planning data - platform.

+

+ We'll process your submission and add your dataset to the planning data platform. +

-
    -
  1. - Submit - your data -
  2. -
-
  • From c920c1412768ce9a3ab91cc112b003673a16f37d Mon Sep 17 00:00:00 2001 From: George Goodall Date: Thu, 22 Aug 2024 10:23:12 +0100 Subject: [PATCH 11/17] add breadcrumbs to get started page, and update schemas --- src/controllers/OrganisationsController.js | 2 +- src/routes/schemas.js | 35 +++++----------------- src/views/organisations/get-started.html | 21 +++++++++++++ 3 files changed, 30 insertions(+), 28 deletions(-) diff --git a/src/controllers/OrganisationsController.js b/src/controllers/OrganisationsController.js index 5e664079..339dd2d3 100644 --- a/src/controllers/OrganisationsController.js +++ b/src/controllers/OrganisationsController.js @@ -153,7 +153,7 @@ const organisationsController = { try { // get the organisation name const lpa = req.params.lpa - const organisationResult = await datasette.runQuery(`SELECT name FROM organisation WHERE organisation = '${lpa}'`) + const organisationResult = await datasette.runQuery(`SELECT name, organisation FROM organisation WHERE organisation = '${lpa}'`) const organisation = organisationResult.formattedData[0] // get the dataset name diff --git a/src/routes/schemas.js b/src/routes/schemas.js index cf34a5c7..7c575c52 100644 --- a/src/routes/schemas.js +++ b/src/routes/schemas.js @@ -38,14 +38,11 @@ const datasetStatusEnum = { 'Not submitted': 'Not submitted' } -const OrgNameField = v.strictObject({ name: NonEmptyString }) +const OrgField = v.strictObject({ name: NonEmptyString, organisation: NonEmptyString }) const DatasetNameField = v.strictObject({ name: NonEmptyString }) export const OrgOverviewPage = v.strictObject({ - organisation: v.strictObject({ - name: NonEmptyString, - organisation: NonEmptyString - }), + organisation: OrgField, datasets: v.array(v.strictObject({ endpoint: v.optional(v.url()), status: v.enum(datasetStatusEnum), @@ -62,15 +59,11 @@ export const OrgOverviewPage = v.strictObject({ }) export const OrgFindPage = v.strictObject({ - alphabetisedOrgs: v.record(NonEmptyString, - v.array(v.strictObject({ - name: NonEmptyString, - organisation: NonEmptyString - }))) + alphabetisedOrgs: v.record(NonEmptyString,v.array(OrgField)) }) export const OrgGetStarted = v.strictObject({ - organisation: OrgNameField, + organisation: OrgField, dataset: DatasetNameField }) @@ -85,20 +78,14 @@ export const OrgDatasetTaskList = v.strictObject({ }) }) })), - organisation: v.strictObject({ - name: NonEmptyString, - organisation: NonEmptyString - }), + organisation: OrgField, dataset: v.strictObject({ name: NonEmptyString }) }) export const OrgEndpointError = v.strictObject({ - organisation: v.strictObject({ - name: NonEmptyString, - organisation: NonEmptyString - }), + organisation: OrgField, dataset: DatasetNameField, errorData: v.strictObject({ endpoint_url: v.url(), @@ -109,10 +96,7 @@ export const OrgEndpointError = v.strictObject({ }) export const OrgIssueDetails = v.strictObject({ - organisation: v.strictObject({ - name: NonEmptyString, - organisation: NonEmptyString - }), + organisation: OrgField, dataset: v.object({ name: NonEmptyString, dataset: v.string() @@ -156,10 +140,7 @@ export const ChooseDataset = v.strictObject({ }) export const DatasetDetails = v.strictObject({ - organisation: v.strictObject({ - name: NonEmptyString, - organisation: NonEmptyString - }), + organisation: OrgField, dataset: v.strictObject({ name: NonEmptyString, dataset: NonEmptyString diff --git a/src/views/organisations/get-started.html b/src/views/organisations/get-started.html index 3c607134..4cbab3b7 100644 --- a/src/views/organisations/get-started.html +++ b/src/views/organisations/get-started.html @@ -1,3 +1,4 @@ +{% from "govuk/components/breadcrumbs/macro.njk" import govukBreadcrumbs %} {% extends "layouts/main.html" %} {% set serviceType = 'Submit' %} @@ -9,6 +10,26 @@ {{ super() }} +{{ govukBreadcrumbs({ + items: [ + { + text: "Home", + href: "/manage" + }, + { + text: "Organisations", + href: '/organisations' + }, + { + text: organisation.name | capitalize, + href: '/organisations/' + organisation.organisation + }, + { + text: 'Get Started' + } + ] +}) }} + {% endblock %} {% block content %} From 476ec274c962394e1c5d6f6621edd6d5378bfe68 Mon Sep 17 00:00:00 2001 From: George Goodall Date: Thu, 22 Aug 2024 10:23:27 +0100 Subject: [PATCH 12/17] linting --- src/routes/schemas.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/schemas.js b/src/routes/schemas.js index 7c575c52..fecb893f 100644 --- a/src/routes/schemas.js +++ b/src/routes/schemas.js @@ -59,7 +59,7 @@ export const OrgOverviewPage = v.strictObject({ }) export const OrgFindPage = v.strictObject({ - alphabetisedOrgs: v.record(NonEmptyString,v.array(OrgField)) + alphabetisedOrgs: v.record(NonEmptyString, v.array(OrgField)) }) export const OrgGetStarted = v.strictObject({ From 1e8539a7c59fd863c19ae37a65967724ff95d3cc Mon Sep 17 00:00:00 2001 From: George Goodall Date: Thu, 22 Aug 2024 10:32:17 +0100 Subject: [PATCH 13/17] fixed tests --- test/unit/organisationsController.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unit/organisationsController.test.js b/test/unit/organisationsController.test.js index 201d43c2..e320805d 100644 --- a/test/unit/organisationsController.test.js +++ b/test/unit/organisationsController.test.js @@ -158,7 +158,7 @@ describe('OrganisationsController.js', () => { if (query.includes('example-lpa')) { return { formattedData: [ - { name: 'Example LPA' } + { name: 'Example LPA', organisation: 'LPA' } ] } } else if (query.includes('example-dataset')) { @@ -174,7 +174,7 @@ describe('OrganisationsController.js', () => { expect(res.render).toHaveBeenCalledTimes(1) expect(res.render).toHaveBeenCalledWith('organisations/get-started.html', { - organisation: { name: 'Example LPA' }, + organisation: { name: 'Example LPA', organisation: 'LPA' }, dataset: { name: 'Example Dataset' } }) }) From 1eb3444ffe0bda1a658cb16dcc48ecd75fa25342 Mon Sep 17 00:00:00 2001 From: Dilwoar Hussain Date: Wed, 28 Aug 2024 09:44:22 +0100 Subject: [PATCH 14/17] Fix vulnerabilities in the npm packages Before this fix, there were: 12 vulnerabilities (2 moderate, 10 high) After this fix, there are now 0 vulnerabilities --- package-lock.json | 564 ++++++++++++++++++++++++++++------------------ 1 file changed, 349 insertions(+), 215 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3afc6f36..3a90d370 100644 --- a/package-lock.json +++ b/package-lock.json @@ -78,6 +78,8 @@ }, "node_modules/@aws-crypto/crc32": { "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/util": "^5.2.0", @@ -90,6 +92,8 @@ }, "node_modules/@aws-crypto/crc32c": { "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz", + "integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/util": "^5.2.0", @@ -143,6 +147,8 @@ }, "node_modules/@aws-crypto/sha256-browser": { "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-js": "^5.2.0", @@ -156,6 +162,8 @@ }, "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -166,6 +174,8 @@ }, "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^2.2.0", @@ -177,6 +187,8 @@ }, "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", "license": "Apache-2.0", "dependencies": { "@smithy/util-buffer-from": "^2.2.0", @@ -188,6 +200,8 @@ }, "node_modules/@aws-crypto/sha256-js": { "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/util": "^5.2.0", @@ -247,64 +261,66 @@ } }, "node_modules/@aws-sdk/client-s3": { - "version": "3.614.0", + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.637.0.tgz", + "integrity": "sha512-y6UC94fsMvhKbf0dzfnjVP1HePeGjplfcYfilZU1COIJLyTkMcUv4XcT4I407CGIrvgEafONHkiC09ygqUauNA==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.614.0", - "@aws-sdk/client-sts": "3.614.0", - "@aws-sdk/core": "3.614.0", - "@aws-sdk/credential-provider-node": "3.614.0", - "@aws-sdk/middleware-bucket-endpoint": "3.614.0", - "@aws-sdk/middleware-expect-continue": "3.609.0", - "@aws-sdk/middleware-flexible-checksums": "3.614.0", - "@aws-sdk/middleware-host-header": "3.609.0", + "@aws-sdk/client-sso-oidc": "3.637.0", + "@aws-sdk/client-sts": "3.637.0", + "@aws-sdk/core": "3.635.0", + "@aws-sdk/credential-provider-node": "3.637.0", + "@aws-sdk/middleware-bucket-endpoint": "3.620.0", + "@aws-sdk/middleware-expect-continue": "3.620.0", + "@aws-sdk/middleware-flexible-checksums": "3.620.0", + "@aws-sdk/middleware-host-header": "3.620.0", "@aws-sdk/middleware-location-constraint": "3.609.0", "@aws-sdk/middleware-logger": "3.609.0", - "@aws-sdk/middleware-recursion-detection": "3.609.0", - "@aws-sdk/middleware-sdk-s3": "3.614.0", - "@aws-sdk/middleware-signing": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-sdk-s3": "3.635.0", "@aws-sdk/middleware-ssec": "3.609.0", - "@aws-sdk/middleware-user-agent": "3.614.0", + "@aws-sdk/middleware-user-agent": "3.637.0", "@aws-sdk/region-config-resolver": "3.614.0", - "@aws-sdk/signature-v4-multi-region": "3.614.0", + "@aws-sdk/signature-v4-multi-region": "3.635.0", "@aws-sdk/types": "3.609.0", - "@aws-sdk/util-endpoints": "3.614.0", + "@aws-sdk/util-endpoints": "3.637.0", "@aws-sdk/util-user-agent-browser": "3.609.0", "@aws-sdk/util-user-agent-node": "3.614.0", "@aws-sdk/xml-builder": "3.609.0", "@smithy/config-resolver": "^3.0.5", - "@smithy/core": "^2.2.6", - "@smithy/eventstream-serde-browser": "^3.0.4", + "@smithy/core": "^2.4.0", + "@smithy/eventstream-serde-browser": "^3.0.6", "@smithy/eventstream-serde-config-resolver": "^3.0.3", - "@smithy/eventstream-serde-node": "^3.0.4", - "@smithy/fetch-http-handler": "^3.2.1", + "@smithy/eventstream-serde-node": "^3.0.5", + "@smithy/fetch-http-handler": "^3.2.4", "@smithy/hash-blob-browser": "^3.1.2", "@smithy/hash-node": "^3.0.3", "@smithy/hash-stream-node": "^3.1.2", "@smithy/invalid-dependency": "^3.0.3", "@smithy/md5-js": "^3.0.3", - "@smithy/middleware-content-length": "^3.0.3", - "@smithy/middleware-endpoint": "^3.0.5", - "@smithy/middleware-retry": "^3.0.9", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", "@smithy/middleware-serde": "^3.0.3", "@smithy/middleware-stack": "^3.0.3", "@smithy/node-config-provider": "^3.1.4", - "@smithy/node-http-handler": "^3.1.2", - "@smithy/protocol-http": "^4.0.3", - "@smithy/smithy-client": "^3.1.7", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", "@smithy/types": "^3.3.0", "@smithy/url-parser": "^3.0.3", "@smithy/util-base64": "^3.0.0", "@smithy/util-body-length-browser": "^3.0.0", "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.9", - "@smithy/util-defaults-mode-node": "^3.0.9", + "@smithy/util-defaults-mode-browser": "^3.0.15", + "@smithy/util-defaults-mode-node": "^3.0.15", "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", "@smithy/util-retry": "^3.0.3", - "@smithy/util-stream": "^3.0.6", + "@smithy/util-stream": "^3.1.3", "@smithy/util-utf8": "^3.0.0", "@smithy/util-waiter": "^3.1.2", "tslib": "^2.6.2" @@ -314,42 +330,44 @@ } }, "node_modules/@aws-sdk/client-sso": { - "version": "3.614.0", + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.637.0.tgz", + "integrity": "sha512-+KjLvgX5yJYROWo3TQuwBJlHCY0zz9PsLuEolmXQn0BVK1L/m9GteZHtd+rEdAoDGBpE0Xqjy1oz5+SmtsaRUw==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.614.0", - "@aws-sdk/middleware-host-header": "3.609.0", + "@aws-sdk/core": "3.635.0", + "@aws-sdk/middleware-host-header": "3.620.0", "@aws-sdk/middleware-logger": "3.609.0", - "@aws-sdk/middleware-recursion-detection": "3.609.0", - "@aws-sdk/middleware-user-agent": "3.614.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.637.0", "@aws-sdk/region-config-resolver": "3.614.0", "@aws-sdk/types": "3.609.0", - "@aws-sdk/util-endpoints": "3.614.0", + "@aws-sdk/util-endpoints": "3.637.0", "@aws-sdk/util-user-agent-browser": "3.609.0", "@aws-sdk/util-user-agent-node": "3.614.0", "@smithy/config-resolver": "^3.0.5", - "@smithy/core": "^2.2.6", - "@smithy/fetch-http-handler": "^3.2.1", + "@smithy/core": "^2.4.0", + "@smithy/fetch-http-handler": "^3.2.4", "@smithy/hash-node": "^3.0.3", "@smithy/invalid-dependency": "^3.0.3", - "@smithy/middleware-content-length": "^3.0.3", - "@smithy/middleware-endpoint": "^3.0.5", - "@smithy/middleware-retry": "^3.0.9", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", "@smithy/middleware-serde": "^3.0.3", "@smithy/middleware-stack": "^3.0.3", "@smithy/node-config-provider": "^3.1.4", - "@smithy/node-http-handler": "^3.1.2", - "@smithy/protocol-http": "^4.0.3", - "@smithy/smithy-client": "^3.1.7", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", "@smithy/types": "^3.3.0", "@smithy/url-parser": "^3.0.3", "@smithy/util-base64": "^3.0.0", "@smithy/util-body-length-browser": "^3.0.0", "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.9", - "@smithy/util-defaults-mode-node": "^3.0.9", + "@smithy/util-defaults-mode-browser": "^3.0.15", + "@smithy/util-defaults-mode-node": "^3.0.15", "@smithy/util-endpoints": "^2.0.5", "@smithy/util-middleware": "^3.0.3", "@smithy/util-retry": "^3.0.3", @@ -361,43 +379,45 @@ } }, "node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.614.0", + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.637.0.tgz", + "integrity": "sha512-27bHALN6Qb6m6KZmPvRieJ/QRlj1lyac/GT2Rn5kJpre8Mpp+yxrtvp3h9PjNBty4lCeFEENfY4dGNSozBuBcw==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.614.0", - "@aws-sdk/credential-provider-node": "3.614.0", - "@aws-sdk/middleware-host-header": "3.609.0", + "@aws-sdk/core": "3.635.0", + "@aws-sdk/credential-provider-node": "3.637.0", + "@aws-sdk/middleware-host-header": "3.620.0", "@aws-sdk/middleware-logger": "3.609.0", - "@aws-sdk/middleware-recursion-detection": "3.609.0", - "@aws-sdk/middleware-user-agent": "3.614.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.637.0", "@aws-sdk/region-config-resolver": "3.614.0", "@aws-sdk/types": "3.609.0", - "@aws-sdk/util-endpoints": "3.614.0", + "@aws-sdk/util-endpoints": "3.637.0", "@aws-sdk/util-user-agent-browser": "3.609.0", "@aws-sdk/util-user-agent-node": "3.614.0", "@smithy/config-resolver": "^3.0.5", - "@smithy/core": "^2.2.6", - "@smithy/fetch-http-handler": "^3.2.1", + "@smithy/core": "^2.4.0", + "@smithy/fetch-http-handler": "^3.2.4", "@smithy/hash-node": "^3.0.3", "@smithy/invalid-dependency": "^3.0.3", - "@smithy/middleware-content-length": "^3.0.3", - "@smithy/middleware-endpoint": "^3.0.5", - "@smithy/middleware-retry": "^3.0.9", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", "@smithy/middleware-serde": "^3.0.3", "@smithy/middleware-stack": "^3.0.3", "@smithy/node-config-provider": "^3.1.4", - "@smithy/node-http-handler": "^3.1.2", - "@smithy/protocol-http": "^4.0.3", - "@smithy/smithy-client": "^3.1.7", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", "@smithy/types": "^3.3.0", "@smithy/url-parser": "^3.0.3", "@smithy/util-base64": "^3.0.0", "@smithy/util-body-length-browser": "^3.0.0", "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.9", - "@smithy/util-defaults-mode-node": "^3.0.9", + "@smithy/util-defaults-mode-browser": "^3.0.15", + "@smithy/util-defaults-mode-node": "^3.0.15", "@smithy/util-endpoints": "^2.0.5", "@smithy/util-middleware": "^3.0.3", "@smithy/util-retry": "^3.0.3", @@ -408,48 +428,50 @@ "node": ">=16.0.0" }, "peerDependencies": { - "@aws-sdk/client-sts": "^3.614.0" + "@aws-sdk/client-sts": "^3.637.0" } }, "node_modules/@aws-sdk/client-sts": { - "version": "3.614.0", + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.637.0.tgz", + "integrity": "sha512-xUi7x4qDubtA8QREtlblPuAcn91GS/09YVEY/RwU7xCY0aqGuFwgszAANlha4OUIqva8oVj2WO4gJuG+iaSnhw==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.614.0", - "@aws-sdk/core": "3.614.0", - "@aws-sdk/credential-provider-node": "3.614.0", - "@aws-sdk/middleware-host-header": "3.609.0", + "@aws-sdk/client-sso-oidc": "3.637.0", + "@aws-sdk/core": "3.635.0", + "@aws-sdk/credential-provider-node": "3.637.0", + "@aws-sdk/middleware-host-header": "3.620.0", "@aws-sdk/middleware-logger": "3.609.0", - "@aws-sdk/middleware-recursion-detection": "3.609.0", - "@aws-sdk/middleware-user-agent": "3.614.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.637.0", "@aws-sdk/region-config-resolver": "3.614.0", "@aws-sdk/types": "3.609.0", - "@aws-sdk/util-endpoints": "3.614.0", + "@aws-sdk/util-endpoints": "3.637.0", "@aws-sdk/util-user-agent-browser": "3.609.0", "@aws-sdk/util-user-agent-node": "3.614.0", "@smithy/config-resolver": "^3.0.5", - "@smithy/core": "^2.2.6", - "@smithy/fetch-http-handler": "^3.2.1", + "@smithy/core": "^2.4.0", + "@smithy/fetch-http-handler": "^3.2.4", "@smithy/hash-node": "^3.0.3", "@smithy/invalid-dependency": "^3.0.3", - "@smithy/middleware-content-length": "^3.0.3", - "@smithy/middleware-endpoint": "^3.0.5", - "@smithy/middleware-retry": "^3.0.9", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", "@smithy/middleware-serde": "^3.0.3", "@smithy/middleware-stack": "^3.0.3", "@smithy/node-config-provider": "^3.1.4", - "@smithy/node-http-handler": "^3.1.2", - "@smithy/protocol-http": "^4.0.3", - "@smithy/smithy-client": "^3.1.7", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", "@smithy/types": "^3.3.0", "@smithy/url-parser": "^3.0.3", "@smithy/util-base64": "^3.0.0", "@smithy/util-body-length-browser": "^3.0.0", "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.9", - "@smithy/util-defaults-mode-node": "^3.0.9", + "@smithy/util-defaults-mode-browser": "^3.0.15", + "@smithy/util-defaults-mode-node": "^3.0.15", "@smithy/util-endpoints": "^2.0.5", "@smithy/util-middleware": "^3.0.3", "@smithy/util-retry": "^3.0.3", @@ -461,15 +483,20 @@ } }, "node_modules/@aws-sdk/core": { - "version": "3.614.0", + "version": "3.635.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.635.0.tgz", + "integrity": "sha512-i1x/E/sgA+liUE1XJ7rj1dhyXpAKO1UKFUcTTHXok2ARjWTvszHnSXMOsB77aPbmn0fUp1JTx2kHUAZ1LVt5Bg==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^2.2.6", - "@smithy/protocol-http": "^4.0.3", - "@smithy/signature-v4": "^3.1.2", - "@smithy/smithy-client": "^3.1.7", + "@smithy/core": "^2.4.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/property-provider": "^3.1.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/signature-v4": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", "@smithy/types": "^3.3.0", - "fast-xml-parser": "4.2.5", + "@smithy/util-middleware": "^3.0.3", + "fast-xml-parser": "4.4.1", "tslib": "^2.6.2" }, "engines": { @@ -477,7 +504,9 @@ } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.609.0", + "version": "3.620.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.620.1.tgz", + "integrity": "sha512-ExuILJ2qLW5ZO+rgkNRj0xiAipKT16Rk77buvPP8csR7kkCflT/gXTyzRe/uzIiETTxM7tr8xuO9MP/DQXqkfg==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.609.0", @@ -490,17 +519,19 @@ } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.614.0", + "version": "3.635.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.635.0.tgz", + "integrity": "sha512-iJyRgEjOCQlBMXqtwPLIKYc7Bsc6nqjrZybdMDenPDa+kmLg7xh8LxHsu9088e+2/wtLicE34FsJJIfzu3L82g==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.609.0", - "@smithy/fetch-http-handler": "^3.2.1", - "@smithy/node-http-handler": "^3.1.2", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/node-http-handler": "^3.1.4", "@smithy/property-provider": "^3.1.3", - "@smithy/protocol-http": "^4.0.3", - "@smithy/smithy-client": "^3.1.7", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", "@smithy/types": "^3.3.0", - "@smithy/util-stream": "^3.0.6", + "@smithy/util-stream": "^3.1.3", "tslib": "^2.6.2" }, "engines": { @@ -508,16 +539,18 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.614.0", + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.637.0.tgz", + "integrity": "sha512-h+PFCWfZ0Q3Dx84SppET/TFpcQHmxFW8/oV9ArEvMilw4EBN+IlxgbL0CnHwjHW64szcmrM0mbebjEfHf4FXmw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "3.609.0", - "@aws-sdk/credential-provider-http": "3.614.0", - "@aws-sdk/credential-provider-process": "3.614.0", - "@aws-sdk/credential-provider-sso": "3.614.0", - "@aws-sdk/credential-provider-web-identity": "3.609.0", + "@aws-sdk/credential-provider-env": "3.620.1", + "@aws-sdk/credential-provider-http": "3.635.0", + "@aws-sdk/credential-provider-process": "3.620.1", + "@aws-sdk/credential-provider-sso": "3.637.0", + "@aws-sdk/credential-provider-web-identity": "3.621.0", "@aws-sdk/types": "3.609.0", - "@smithy/credential-provider-imds": "^3.1.4", + "@smithy/credential-provider-imds": "^3.2.0", "@smithy/property-provider": "^3.1.3", "@smithy/shared-ini-file-loader": "^3.1.4", "@smithy/types": "^3.3.0", @@ -527,21 +560,23 @@ "node": ">=16.0.0" }, "peerDependencies": { - "@aws-sdk/client-sts": "^3.614.0" + "@aws-sdk/client-sts": "^3.637.0" } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.614.0", + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.637.0.tgz", + "integrity": "sha512-yoEhoxJJfs7sPVQ6Is939BDQJZpZCoUgKr/ySse4YKOZ24t4VqgHA6+wV7rYh+7IW24Rd91UTvEzSuHYTlxlNA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "3.609.0", - "@aws-sdk/credential-provider-http": "3.614.0", - "@aws-sdk/credential-provider-ini": "3.614.0", - "@aws-sdk/credential-provider-process": "3.614.0", - "@aws-sdk/credential-provider-sso": "3.614.0", - "@aws-sdk/credential-provider-web-identity": "3.609.0", + "@aws-sdk/credential-provider-env": "3.620.1", + "@aws-sdk/credential-provider-http": "3.635.0", + "@aws-sdk/credential-provider-ini": "3.637.0", + "@aws-sdk/credential-provider-process": "3.620.1", + "@aws-sdk/credential-provider-sso": "3.637.0", + "@aws-sdk/credential-provider-web-identity": "3.621.0", "@aws-sdk/types": "3.609.0", - "@smithy/credential-provider-imds": "^3.1.4", + "@smithy/credential-provider-imds": "^3.2.0", "@smithy/property-provider": "^3.1.3", "@smithy/shared-ini-file-loader": "^3.1.4", "@smithy/types": "^3.3.0", @@ -552,7 +587,9 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.614.0", + "version": "3.620.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.620.1.tgz", + "integrity": "sha512-hWqFMidqLAkaV9G460+1at6qa9vySbjQKKc04p59OT7lZ5cO5VH5S4aI05e+m4j364MBROjjk2ugNvfNf/8ILg==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.609.0", @@ -566,10 +603,12 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.614.0", + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.637.0.tgz", + "integrity": "sha512-Mvz+h+e62/tl+dVikLafhv+qkZJ9RUb8l2YN/LeKMWkxQylPT83CPk9aimVhCV89zth1zpREArl97+3xsfgQvA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-sso": "3.614.0", + "@aws-sdk/client-sso": "3.637.0", "@aws-sdk/token-providers": "3.614.0", "@aws-sdk/types": "3.609.0", "@smithy/property-provider": "^3.1.3", @@ -582,7 +621,9 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.609.0", + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.621.0.tgz", + "integrity": "sha512-w7ASSyfNvcx7+bYGep3VBgC3K6vEdLmlpjT7nSIHxxQf+WSdvy+HynwJosrpZax0sK5q0D1Jpn/5q+r5lwwW6w==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.609.0", @@ -594,17 +635,19 @@ "node": ">=16.0.0" }, "peerDependencies": { - "@aws-sdk/client-sts": "^3.609.0" + "@aws-sdk/client-sts": "^3.621.0" } }, "node_modules/@aws-sdk/middleware-bucket-endpoint": { - "version": "3.614.0", + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.620.0.tgz", + "integrity": "sha512-eGLL0W6L3HDb3OACyetZYOWpHJ+gLo0TehQKeQyy2G8vTYXqNTeqYhuI6up9HVjBzU9eQiULVQETmgQs7TFaRg==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.609.0", "@aws-sdk/util-arn-parser": "3.568.0", "@smithy/node-config-provider": "^3.1.4", - "@smithy/protocol-http": "^4.0.3", + "@smithy/protocol-http": "^4.1.0", "@smithy/types": "^3.3.0", "@smithy/util-config-provider": "^3.0.0", "tslib": "^2.6.2" @@ -614,11 +657,13 @@ } }, "node_modules/@aws-sdk/middleware-expect-continue": { - "version": "3.609.0", + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.620.0.tgz", + "integrity": "sha512-QXeRFMLfyQ31nAHLbiTLtk0oHzG9QLMaof5jIfqcUwnOkO8YnQdeqzakrg1Alpy/VQ7aqzIi8qypkBe2KXZz0A==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.609.0", - "@smithy/protocol-http": "^4.0.3", + "@smithy/protocol-http": "^4.1.0", "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, @@ -627,14 +672,16 @@ } }, "node_modules/@aws-sdk/middleware-flexible-checksums": { - "version": "3.614.0", + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.620.0.tgz", + "integrity": "sha512-ftz+NW7qka2sVuwnnO1IzBku5ccP+s5qZGeRTPgrKB7OzRW85gthvIo1vQR2w+OwHFk7WJbbhhWwbCbktnP4UA==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-sdk/types": "3.609.0", "@smithy/is-array-buffer": "^3.0.0", - "@smithy/protocol-http": "^4.0.3", + "@smithy/protocol-http": "^4.1.0", "@smithy/types": "^3.3.0", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" @@ -644,11 +691,13 @@ } }, "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.609.0", + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.620.0.tgz", + "integrity": "sha512-VMtPEZwqYrII/oUkffYsNWY9PZ9xpNJpMgmyU0rlDQ25O1c0Hk3fJmZRe6pEkAJ0omD7kLrqGl1DUjQVxpd/Rg==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.609.0", - "@smithy/protocol-http": "^4.0.3", + "@smithy/protocol-http": "^4.1.0", "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, @@ -670,6 +719,8 @@ }, "node_modules/@aws-sdk/middleware-logger": { "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.609.0.tgz", + "integrity": "sha512-S62U2dy4jMDhDFDK5gZ4VxFdWzCtLzwbYyFZx2uvPYTECkepLUfzLic2BHg2Qvtu4QjX+oGE3P/7fwaGIsGNuQ==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.609.0", @@ -681,11 +732,13 @@ } }, "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.609.0", + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.620.0.tgz", + "integrity": "sha512-nh91S7aGK3e/o1ck64sA/CyoFw+gAYj2BDOnoNa6ouyCrVJED96ZXWbhye/fz9SgmNUZR2g7GdVpiLpMKZoI5w==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.609.0", - "@smithy/protocol-http": "^4.0.3", + "@smithy/protocol-http": "^4.1.0", "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, @@ -694,33 +747,24 @@ } }, "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.614.0", + "version": "3.635.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.635.0.tgz", + "integrity": "sha512-RLdYJPEV4JL/7NBoFUs7VlP90X++5FlJdxHz0DzCjmiD3qCviKy+Cym3qg1gBgHwucs5XisuClxDrGokhAdTQw==", "license": "Apache-2.0", "dependencies": { + "@aws-sdk/core": "3.635.0", "@aws-sdk/types": "3.609.0", "@aws-sdk/util-arn-parser": "3.568.0", + "@smithy/core": "^2.4.0", "@smithy/node-config-provider": "^3.1.4", - "@smithy/protocol-http": "^4.0.3", - "@smithy/signature-v4": "^3.1.2", - "@smithy/smithy-client": "^3.1.7", + "@smithy/protocol-http": "^4.1.0", + "@smithy/signature-v4": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", "@smithy/types": "^3.3.0", "@smithy/util-config-provider": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/middleware-signing": { - "version": "3.609.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/property-provider": "^3.1.3", - "@smithy/protocol-http": "^4.0.3", - "@smithy/signature-v4": "^3.1.2", - "@smithy/types": "^3.3.0", "@smithy/util-middleware": "^3.0.3", + "@smithy/util-stream": "^3.1.3", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { @@ -740,12 +784,14 @@ } }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.614.0", + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.637.0.tgz", + "integrity": "sha512-EYo0NE9/da/OY8STDsK2LvM4kNa79DBsf4YVtaG4P5pZ615IeFsD8xOHZeuJmUrSMlVQ8ywPRX7WMucUybsKug==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.609.0", - "@aws-sdk/util-endpoints": "3.614.0", - "@smithy/protocol-http": "^4.0.3", + "@aws-sdk/util-endpoints": "3.637.0", + "@smithy/protocol-http": "^4.1.0", "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, @@ -755,6 +801,8 @@ }, "node_modules/@aws-sdk/region-config-resolver": { "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.614.0.tgz", + "integrity": "sha512-vDCeMXvic/LU0KFIUjpC3RiSTIkkvESsEfbVHiHH0YINfl8HnEqR5rj+L8+phsCeVg2+LmYwYxd5NRz4PHxt5g==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.609.0", @@ -769,13 +817,15 @@ } }, "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.614.0", + "version": "3.635.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.635.0.tgz", + "integrity": "sha512-J6QY4/invOkpogCHjSaDON1hF03viPpOnsrzVuCvJMmclS/iG62R4EY0wq1alYll0YmSdmKlpJwHMWwGtqK63Q==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-sdk-s3": "3.614.0", + "@aws-sdk/middleware-sdk-s3": "3.635.0", "@aws-sdk/types": "3.609.0", - "@smithy/protocol-http": "^4.0.3", - "@smithy/signature-v4": "^3.1.2", + "@smithy/protocol-http": "^4.1.0", + "@smithy/signature-v4": "^4.1.0", "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, @@ -785,6 +835,8 @@ }, "node_modules/@aws-sdk/token-providers": { "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.614.0.tgz", + "integrity": "sha512-okItqyY6L9IHdxqs+Z116y5/nda7rHxLvROxtAJdLavWTYDydxrZstImNgGWTeVdmc0xX2gJCI77UYUTQWnhRw==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.609.0", @@ -813,6 +865,8 @@ }, "node_modules/@aws-sdk/util-arn-parser": { "version": "3.568.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.568.0.tgz", + "integrity": "sha512-XUKJWWo+KOB7fbnPP0+g/o5Ulku/X53t7i/h+sPHr5xxYTJJ9CYnbToo95mzxe7xWvkLrsNtJ8L+MnNn9INs2w==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -822,7 +876,9 @@ } }, "node_modules/@aws-sdk/util-endpoints": { - "version": "3.614.0", + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.637.0.tgz", + "integrity": "sha512-pAqOKUHeVWHEXXDIp/qoMk/6jyxIb6GGjnK1/f8dKHtKIEs4tKsnnL563gceEvdad53OPXIt86uoevCcCzmBnw==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.609.0", @@ -846,6 +902,8 @@ }, "node_modules/@aws-sdk/util-user-agent-browser": { "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.609.0.tgz", + "integrity": "sha512-fojPU+mNahzQ0YHYBsx0ZIhmMA96H+ZIZ665ObU9tl+SGdbLneVZVikGve+NmHTQwHzwkFsZYYnVKAkreJLAtA==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.609.0", @@ -856,6 +914,8 @@ }, "node_modules/@aws-sdk/util-user-agent-node": { "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.614.0.tgz", + "integrity": "sha512-15ElZT88peoHnq5TEoEtZwoXTXRxNrk60TZNdpl/TUBJ5oNJ9Dqb5Z4ryb8ofN6nm9aFf59GVAerFDz8iUoHBA==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.609.0", @@ -1984,6 +2044,8 @@ }, "node_modules/@smithy/config-resolver": { "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-3.0.5.tgz", + "integrity": "sha512-SkW5LxfkSI1bUC74OtfBbdz+grQXYiPYolyu8VfpLIjEoN/sHVBlLeGXMQ1vX4ejkgfv6sxVbQJ32yF2cl1veA==", "license": "Apache-2.0", "dependencies": { "@smithy/node-config-provider": "^3.1.4", @@ -1997,16 +2059,20 @@ } }, "node_modules/@smithy/core": { - "version": "2.2.6", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-2.4.0.tgz", + "integrity": "sha512-cHXq+FneIF/KJbt4q4pjN186+Jf4ZB0ZOqEaZMBhT79srEyGDDBV31NqBRBjazz8ppQ1bJbDJMY9ba5wKFV36w==", "license": "Apache-2.0", "dependencies": { - "@smithy/middleware-endpoint": "^3.0.5", - "@smithy/middleware-retry": "^3.0.9", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", "@smithy/middleware-serde": "^3.0.3", - "@smithy/protocol-http": "^4.0.3", - "@smithy/smithy-client": "^3.1.7", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", "@smithy/types": "^3.3.0", + "@smithy/util-body-length-browser": "^3.0.0", "@smithy/util-middleware": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { @@ -2014,7 +2080,9 @@ } }, "node_modules/@smithy/credential-provider-imds": { - "version": "3.1.4", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-3.2.0.tgz", + "integrity": "sha512-0SCIzgd8LYZ9EJxUjLXBmEKSZR/P/w6l7Rz/pab9culE/RWuqelAKGJvn5qUOl8BgX8Yj5HWM50A5hiB/RzsgA==", "license": "Apache-2.0", "dependencies": { "@smithy/node-config-provider": "^3.1.4", @@ -2029,6 +2097,8 @@ }, "node_modules/@smithy/eventstream-codec": { "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-3.1.2.tgz", + "integrity": "sha512-0mBcu49JWt4MXhrhRAlxASNy0IjDRFU+aWNDRal9OtUJvJNiwDuyKMUONSOjLjSCeGwZaE0wOErdqULer8r7yw==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/crc32": "5.2.0", @@ -2038,10 +2108,12 @@ } }, "node_modules/@smithy/eventstream-serde-browser": { - "version": "3.0.4", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-3.0.6.tgz", + "integrity": "sha512-2hM54UWQUOrki4BtsUI1WzmD13/SeaqT/AB3EUJKbcver/WgKNaiJ5y5F5XXuVe6UekffVzuUDrBZVAA3AWRpQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/eventstream-serde-universal": "^3.0.4", + "@smithy/eventstream-serde-universal": "^3.0.5", "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, @@ -2061,10 +2133,12 @@ } }, "node_modules/@smithy/eventstream-serde-node": { - "version": "3.0.4", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-3.0.5.tgz", + "integrity": "sha512-+upXvnHNyZP095s11jF5dhGw/Ihzqwl5G+/KtMnoQOpdfC3B5HYCcDVG9EmgkhJMXJlM64PyN5gjJl0uXFQehQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/eventstream-serde-universal": "^3.0.4", + "@smithy/eventstream-serde-universal": "^3.0.5", "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, @@ -2073,7 +2147,9 @@ } }, "node_modules/@smithy/eventstream-serde-universal": { - "version": "3.0.4", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-3.0.5.tgz", + "integrity": "sha512-5u/nXbyoh1s4QxrvNre9V6vfyoLWuiVvvd5TlZjGThIikc3G+uNiG9uOTCWweSRjv1asdDIWK7nOmN7le4RYHQ==", "license": "Apache-2.0", "dependencies": { "@smithy/eventstream-codec": "^3.1.2", @@ -2085,10 +2161,12 @@ } }, "node_modules/@smithy/fetch-http-handler": { - "version": "3.2.1", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-3.2.4.tgz", + "integrity": "sha512-kBprh5Gs5h7ug4nBWZi1FZthdqSM+T7zMmsZxx0IBvWUn7dK3diz2SHn7Bs4dQGFDk8plDv375gzenDoNwrXjg==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^4.0.3", + "@smithy/protocol-http": "^4.1.0", "@smithy/querystring-builder": "^3.0.3", "@smithy/types": "^3.3.0", "@smithy/util-base64": "^3.0.0", @@ -2107,6 +2185,8 @@ }, "node_modules/@smithy/hash-node": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-3.0.3.tgz", + "integrity": "sha512-2ctBXpPMG+B3BtWSGNnKELJ7SH9e4TNefJS0cd2eSkOOROeBnnVBnAy9LtJ8tY4vUEoe55N4CNPxzbWvR39iBw==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.3.0", @@ -2132,6 +2212,8 @@ }, "node_modules/@smithy/invalid-dependency": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-3.0.3.tgz", + "integrity": "sha512-ID1eL/zpDULmHJbflb864k72/SNOZCADRc9i7Exq3RUNJw6raWUSlFEQ+3PX3EYs++bTxZB2dE9mEHTQLv61tw==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.3.0", @@ -2158,10 +2240,12 @@ } }, "node_modules/@smithy/middleware-content-length": { - "version": "3.0.3", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-3.0.5.tgz", + "integrity": "sha512-ILEzC2eyxx6ncej3zZSwMpB5RJ0zuqH7eMptxC4KN3f+v9bqT8ohssKbhNR78k/2tWW+KS5Spw+tbPF4Ejyqvw==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^4.0.3", + "@smithy/protocol-http": "^4.1.0", "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, @@ -2170,7 +2254,9 @@ } }, "node_modules/@smithy/middleware-endpoint": { - "version": "3.0.5", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.1.0.tgz", + "integrity": "sha512-5y5aiKCEwg9TDPB4yFE7H6tYvGFf1OJHNczeY10/EFF8Ir8jZbNntQJxMWNfeQjC1mxPsaQ6mR9cvQbf+0YeMw==", "license": "Apache-2.0", "dependencies": { "@smithy/middleware-serde": "^3.0.3", @@ -2186,13 +2272,15 @@ } }, "node_modules/@smithy/middleware-retry": { - "version": "3.0.9", + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.15.tgz", + "integrity": "sha512-iTMedvNt1ApdvkaoE8aSDuwaoc+BhvHqttbA/FO4Ty+y/S5hW6Ci/CTScG7vam4RYJWZxdTElc3MEfHRVH6cgQ==", "license": "Apache-2.0", "dependencies": { "@smithy/node-config-provider": "^3.1.4", - "@smithy/protocol-http": "^4.0.3", + "@smithy/protocol-http": "^4.1.0", "@smithy/service-error-classification": "^3.0.3", - "@smithy/smithy-client": "^3.1.7", + "@smithy/smithy-client": "^3.2.0", "@smithy/types": "^3.3.0", "@smithy/util-middleware": "^3.0.3", "@smithy/util-retry": "^3.0.3", @@ -2205,6 +2293,8 @@ }, "node_modules/@smithy/middleware-serde": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.3.tgz", + "integrity": "sha512-puUbyJQBcg9eSErFXjKNiGILJGtiqmuuNKEYNYfUD57fUl4i9+mfmThtQhvFXU0hCVG0iEJhvQUipUf+/SsFdA==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.3.0", @@ -2216,6 +2306,8 @@ }, "node_modules/@smithy/middleware-stack": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.3.tgz", + "integrity": "sha512-r4klY9nFudB0r9UdSMaGSyjyQK5adUyPnQN/ZM6M75phTxOdnc/AhpvGD1fQUvgmqjQEBGCwpnPbDm8pH5PapA==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.3.0", @@ -2227,6 +2319,8 @@ }, "node_modules/@smithy/node-config-provider": { "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.4.tgz", + "integrity": "sha512-YvnElQy8HR4vDcAjoy7Xkx9YT8xZP4cBXcbJSgm/kxmiQu08DwUwj8rkGnyoJTpfl/3xYHH+d8zE+eHqoDCSdQ==", "license": "Apache-2.0", "dependencies": { "@smithy/property-provider": "^3.1.3", @@ -2239,11 +2333,13 @@ } }, "node_modules/@smithy/node-http-handler": { - "version": "3.1.2", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.1.4.tgz", + "integrity": "sha512-+UmxgixgOr/yLsUxcEKGH0fMNVteJFGkmRltYFHnBMlogyFdpzn2CwqWmxOrfJELhV34v0WSlaqG1UtE1uXlJg==", "license": "Apache-2.0", "dependencies": { "@smithy/abort-controller": "^3.1.1", - "@smithy/protocol-http": "^4.0.3", + "@smithy/protocol-http": "^4.1.0", "@smithy/querystring-builder": "^3.0.3", "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -2254,6 +2350,8 @@ }, "node_modules/@smithy/property-provider": { "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.3.tgz", + "integrity": "sha512-zahyOVR9Q4PEoguJ/NrFP4O7SMAfYO1HLhB18M+q+Z4KFd4V2obiMnlVoUFzFLSPeVt1POyNWneHHrZaTMoc/g==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.3.0", @@ -2264,7 +2362,9 @@ } }, "node_modules/@smithy/protocol-http": { - "version": "4.0.3", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", + "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.3.0", @@ -2276,6 +2376,8 @@ }, "node_modules/@smithy/querystring-builder": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.3.tgz", + "integrity": "sha512-vyWckeUeesFKzCDaRwWLUA1Xym9McaA6XpFfAK5qI9DKJ4M33ooQGqvM4J+LalH4u/Dq9nFiC8U6Qn1qi0+9zw==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.3.0", @@ -2288,6 +2390,8 @@ }, "node_modules/@smithy/querystring-parser": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.3.tgz", + "integrity": "sha512-zahM1lQv2YjmznnfQsWbYojFe55l0SLG/988brlLv1i8z3dubloLF+75ATRsqPBboUXsW6I9CPGE5rQgLfY0vQ==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.3.0", @@ -2299,6 +2403,8 @@ }, "node_modules/@smithy/service-error-classification": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.3.tgz", + "integrity": "sha512-Jn39sSl8cim/VlkLsUhRFq/dKDnRUFlfRkvhOJaUbLBXUsLRLNf9WaxDv/z9BjuQ3A6k/qE8af1lsqcwm7+DaQ==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.3.0" @@ -2309,6 +2415,8 @@ }, "node_modules/@smithy/shared-ini-file-loader": { "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.4.tgz", + "integrity": "sha512-qMxS4hBGB8FY2GQqshcRUy1K6k8aBWP5vwm8qKkCT3A9K2dawUwOIJfqh9Yste/Bl0J2lzosVyrXDj68kLcHXQ==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.3.0", @@ -2319,10 +2427,13 @@ } }, "node_modules/@smithy/signature-v4": { - "version": "3.1.2", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.1.0.tgz", + "integrity": "sha512-aRryp2XNZeRcOtuJoxjydO6QTaVhxx/vjaR+gx7ZjaFgrgPRyZ3HCTbfwqYj6ZWEBHkCSUfcaymKPURaByukag==", "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^3.0.0", + "@smithy/protocol-http": "^4.1.0", "@smithy/types": "^3.3.0", "@smithy/util-hex-encoding": "^3.0.0", "@smithy/util-middleware": "^3.0.3", @@ -2335,14 +2446,16 @@ } }, "node_modules/@smithy/smithy-client": { - "version": "3.1.7", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.2.0.tgz", + "integrity": "sha512-pDbtxs8WOhJLJSeaF/eAbPgXg4VVYFlRcL/zoNYA5WbG3wBL06CHtBSg53ppkttDpAJ/hdiede+xApip1CwSLw==", "license": "Apache-2.0", "dependencies": { - "@smithy/middleware-endpoint": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", "@smithy/middleware-stack": "^3.0.3", - "@smithy/protocol-http": "^4.0.3", + "@smithy/protocol-http": "^4.1.0", "@smithy/types": "^3.3.0", - "@smithy/util-stream": "^3.0.6", + "@smithy/util-stream": "^3.1.3", "tslib": "^2.6.2" }, "engines": { @@ -2361,6 +2474,8 @@ }, "node_modules/@smithy/url-parser": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.3.tgz", + "integrity": "sha512-pw3VtZtX2rg+s6HMs6/+u9+hu6oY6U7IohGhVNnjbgKy86wcIsSZwgHrFR+t67Uyxvp4Xz3p3kGXXIpTNisq8A==", "license": "Apache-2.0", "dependencies": { "@smithy/querystring-parser": "^3.0.3", @@ -2382,6 +2497,8 @@ }, "node_modules/@smithy/util-body-length-browser": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-3.0.0.tgz", + "integrity": "sha512-cbjJs2A1mLYmqmyVl80uoLTJhAcfzMOyPgjwAYusWKMdLeNtzmMz9YxNl3/jRLoxSS3wkqkf0jwNdtXWtyEBaQ==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -2389,6 +2506,8 @@ }, "node_modules/@smithy/util-body-length-node": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-3.0.0.tgz", + "integrity": "sha512-Tj7pZ4bUloNUP6PzwhN7K386tmSmEET9QtQg0TgdNOnxhZvCssHji+oZTUIuzxECRfG8rdm2PMw2WCFs6eIYkA==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -2410,6 +2529,8 @@ }, "node_modules/@smithy/util-config-provider": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-3.0.0.tgz", + "integrity": "sha512-pbjk4s0fwq3Di/ANL+rCvJMKM5bzAQdE5S/6RL5NXgMExFAi6UgQMPOm5yPaIWPpr+EOXKXRonJ3FoxKf4mCJQ==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -2419,11 +2540,13 @@ } }, "node_modules/@smithy/util-defaults-mode-browser": { - "version": "3.0.9", + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.15.tgz", + "integrity": "sha512-FZ4Psa3vjp8kOXcd3HJOiDPBCWtiilLl57r0cnNtq/Ga9RSDrM5ERL6xt+tO43+2af6Pn5Yp92x2n5vPuduNfg==", "license": "Apache-2.0", "dependencies": { "@smithy/property-provider": "^3.1.3", - "@smithy/smithy-client": "^3.1.7", + "@smithy/smithy-client": "^3.2.0", "@smithy/types": "^3.3.0", "bowser": "^2.11.0", "tslib": "^2.6.2" @@ -2433,14 +2556,16 @@ } }, "node_modules/@smithy/util-defaults-mode-node": { - "version": "3.0.9", + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.15.tgz", + "integrity": "sha512-KSyAAx2q6d0t6f/S4XB2+3+6aQacm3aLMhs9aLMqn18uYGUepbdssfogW5JQZpc6lXNBnp0tEnR5e9CEKmEd7A==", "license": "Apache-2.0", "dependencies": { "@smithy/config-resolver": "^3.0.5", - "@smithy/credential-provider-imds": "^3.1.4", + "@smithy/credential-provider-imds": "^3.2.0", "@smithy/node-config-provider": "^3.1.4", "@smithy/property-provider": "^3.1.3", - "@smithy/smithy-client": "^3.1.7", + "@smithy/smithy-client": "^3.2.0", "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, @@ -2450,6 +2575,8 @@ }, "node_modules/@smithy/util-endpoints": { "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-2.0.5.tgz", + "integrity": "sha512-ReQP0BWihIE68OAblC/WQmDD40Gx+QY1Ez8mTdFMXpmjfxSyz2fVQu3A4zXRfQU9sZXtewk3GmhfOHswvX+eNg==", "license": "Apache-2.0", "dependencies": { "@smithy/node-config-provider": "^3.1.4", @@ -2462,6 +2589,8 @@ }, "node_modules/@smithy/util-hex-encoding": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz", + "integrity": "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -2472,6 +2601,8 @@ }, "node_modules/@smithy/util-middleware": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", + "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.3.0", @@ -2483,6 +2614,8 @@ }, "node_modules/@smithy/util-retry": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.3.tgz", + "integrity": "sha512-AFw+hjpbtVApzpNDhbjNG5NA3kyoMs7vx0gsgmlJF4s+yz1Zlepde7J58zpIRIsdjc+emhpAITxA88qLkPF26w==", "license": "Apache-2.0", "dependencies": { "@smithy/service-error-classification": "^3.0.3", @@ -2494,11 +2627,13 @@ } }, "node_modules/@smithy/util-stream": { - "version": "3.0.6", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.1.3.tgz", + "integrity": "sha512-FIv/bRhIlAxC0U7xM1BCnF2aDRPq0UaelqBHkM2lsCp26mcBbgI0tCVTv+jGdsQLUmAMybua/bjDsSu8RQHbmw==", "license": "Apache-2.0", "dependencies": { - "@smithy/fetch-http-handler": "^3.2.1", - "@smithy/node-http-handler": "^3.1.2", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/node-http-handler": "^3.1.4", "@smithy/types": "^3.3.0", "@smithy/util-base64": "^3.0.0", "@smithy/util-buffer-from": "^3.0.0", @@ -2512,6 +2647,8 @@ }, "node_modules/@smithy/util-uri-escape": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", + "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -2652,24 +2789,6 @@ "@types/ssh2": "*" } }, - "node_modules/@types/eslint": { - "version": "8.56.10", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.7", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, "node_modules/@types/estree": { "version": "1.0.5", "dev": true, @@ -3835,7 +3954,9 @@ } }, "node_modules/axios": { - "version": "1.7.2", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.5.tgz", + "integrity": "sha512-fZu86yCo+svH3uqJ/yTdQ0QHpQu5oL+/QE+QPSv6BZSkDAoky9vytxp7u5qk83OJFS3kEBcesWni9WTZAv3tSw==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", @@ -4014,6 +4135,8 @@ }, "node_modules/bowser": { "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", + "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==", "license": "MIT" }, "node_modules/brace-expansion": { @@ -5668,7 +5791,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.17.0", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", "dev": true, "license": "MIT", "dependencies": { @@ -6720,15 +6845,17 @@ "license": "MIT" }, "node_modules/fast-xml-parser": { - "version": "4.2.5", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", "funding": [ - { - "type": "paypal", - "url": "https://paypal.me/naturalintelligence" - }, { "type": "github", "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" } ], "license": "MIT", @@ -9046,7 +9173,9 @@ } }, "node_modules/micromatch": { - "version": "4.0.7", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -11964,6 +12093,8 @@ }, "node_modules/strnum": { "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", "license": "MIT" }, "node_modules/superagent": { @@ -12086,6 +12217,8 @@ }, "node_modules/tapable": { "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", "dev": true, "license": "MIT", "engines": { @@ -13201,11 +13334,12 @@ } }, "node_modules/webpack": { - "version": "5.93.0", + "version": "5.94.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", + "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", "dev": true, "license": "MIT", "dependencies": { - "@types/eslint-scope": "^3.7.3", "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.12.1", "@webassemblyjs/wasm-edit": "^1.12.1", @@ -13214,7 +13348,7 @@ "acorn-import-attributes": "^1.9.5", "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.0", + "enhanced-resolve": "^5.17.1", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", From 12fc58a77f5fbb11b23da6694c960b2c7b00e2b9 Mon Sep 17 00:00:00 2001 From: Dilwoar Hussain Date: Wed, 28 Aug 2024 11:00:55 +0100 Subject: [PATCH 15/17] Add instructions for Mac users This repo uses port 5000 for local development which is also used by AirPlay Receiver. Following the instructions documented fixes this problem. --- readme.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 6985288c..8e1087a9 100644 --- a/readme.md +++ b/readme.md @@ -8,7 +8,7 @@ This is the frontend for the LPA Data Validator application. It is a nodeJS expr - Install the node packages ``` npm install - ``` + ``` - setup husky pre-commit hooks ``` npm run prepare @@ -34,4 +34,15 @@ This is the frontend for the LPA Data Validator application. It is a nodeJS expr - run the application, using a local api in watch mode ``` npm run start:local:watch - ``` \ No newline at end of file + ``` + +## Mac users only + +If you are a Mac user, please note that port 5000 is used by AirPlay Receiver. In order to use the application, you will need to switch off AirPlay Receiver. + +To switch off AirPlay Receiver, follow these steps: +1. Open System Preferences on your Mac. +2. Search for "AirDrop & Handoff". +3. In the "AirPlay Receiver" toggle, select "Off". + +Once you have switched off AirPlay Receiver, you should be able to use the application without any issues. From dc7ee3df908ac157f3c73364a199c1342a622448 Mon Sep 17 00:00:00 2001 From: Dilwoar Hussain Date: Wed, 28 Aug 2024 13:45:02 +0100 Subject: [PATCH 16/17] Add in docker instructions This change promotes the docker way of running this as the primary method of running the application. This was done to ensure that all the required services such as Request API, Localstack etc are running along with the application. --- readme.md | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 8e1087a9..793bb503 100644 --- a/readme.md +++ b/readme.md @@ -19,6 +19,16 @@ This is the frontend for the LPA Data Validator application. It is a nodeJS expr ``` ## Running the application + +**Prerequisite**: Install [Docker Desktop](https://www.docker.com/products/docker-desktop/) + +```sh +docker-compose -f docker-compose-real-backend-minus-frontend.yml up -d; + +npm run start:local; +``` + +### Alternative methods of starting application - Run the application ``` npm run start @@ -27,14 +37,22 @@ This is the frontend for the LPA Data Validator application. It is a nodeJS expr ``` npm run start ``` -- run the application, using a local api +- Run the application, using a local api ``` npm run start:local ``` -- run the application, using a local api in watch mode +- Run the application, using a local api in watch mode ``` npm run start:local:watch ``` +- Run the application using docker + ``` + docker-compose -f docker-compose-real-backend.yml up + ``` +- Run the application (without the frontend) using docker + ``` + docker-compose -f docker-compose-real-backend-minus-frontend.yml up + ``` ## Mac users only From ee874b323bb33f7b58a2c168eda9960d7a0d3a92 Mon Sep 17 00:00:00 2001 From: Dilwoar Hussain Date: Wed, 28 Aug 2024 16:42:36 +0100 Subject: [PATCH 17/17] Remove `...` from the body This was added in unintentionally. --- src/views/layouts/main.html | 1 - 1 file changed, 1 deletion(-) diff --git a/src/views/layouts/main.html b/src/views/layouts/main.html index 3181102e..e518e7f3 100644 --- a/src/views/layouts/main.html +++ b/src/views/layouts/main.html @@ -95,7 +95,6 @@

    Get help

    {{ super()}} {%block scripts %} - ...