From fb024232870673d94456faca18c958e9c00b8f3f Mon Sep 17 00:00:00 2001
From: Alex Anderson <191496+alxndrsn@users.noreply.github.com>
Date: Mon, 11 Nov 2024 20:11:14 +0300
Subject: [PATCH 01/10] Content-Security-Policy: allow images from data: URLs
(#772)
QR displayed in odk-central-frontend are displayed with src=data:...
Closes #629
---
files/nginx/common-headers.conf | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/files/nginx/common-headers.conf b/files/nginx/common-headers.conf
index 7661bb01..7fa6eefa 100644
--- a/files/nginx/common-headers.conf
+++ b/files/nginx/common-headers.conf
@@ -6,7 +6,7 @@
# They are included here to ease interpretation of violation reports.
#
# N.B. a separate CSP is defined for Enketo in odk.conf.template
-add_header Content-Security-Policy-Report-Only "default-src 'none'; connect-src 'self'; font-src 'self'; frame-src 'self' https://getodk.github.io/central/news.html; img-src *; manifest-src 'none'; media-src 'none'; object-src 'none'; script-src 'self'; style-src 'self'; style-src-attr 'unsafe-inline'; report-uri /csp-report";
+add_header Content-Security-Policy-Report-Only "default-src 'none'; connect-src 'self'; font-src 'self'; frame-src 'self' https://getodk.github.io/central/news.html; img-src * data:; manifest-src 'none'; media-src 'none'; object-src 'none'; script-src 'self'; style-src 'self'; style-src-attr 'unsafe-inline'; report-uri /csp-report";
# If changing these headers, please apply the same updates to enketo
# location(s) in odk.conf.template
From 2e5c4f7de60c6db39c3205320cedba95d49a9aa3 Mon Sep 17 00:00:00 2001
From: Alex Anderson <191496+alxndrsn@users.noreply.github.com>
Date: Wed, 20 Nov 2024 08:48:02 +0300
Subject: [PATCH 02/10] service: remove --legacy-peer-deps from backend init
(#791)
Removed from backend codebase in https://github.com/getodk/central-backend/pull/1293
---
service.dockerfile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/service.dockerfile b/service.dockerfile
index a2ede997..82d5848f 100644
--- a/service.dockerfile
+++ b/service.dockerfile
@@ -54,7 +54,7 @@ RUN apt-get update \
postgresql-client-14 \
netcat-traditional \
&& rm -rf /var/lib/apt/lists/* \
- && npm clean-install --omit=dev --legacy-peer-deps --no-audit \
+ && npm clean-install --omit=dev --no-audit \
--fund=false --update-notifier=false
COPY server/ ./
From 51a3e10fdf188473ce38b24313962c674188c9ff Mon Sep 17 00:00:00 2001
From: Alex Anderson <191496+alxndrsn@users.noreply.github.com>
Date: Mon, 25 Nov 2024 13:12:01 +0300
Subject: [PATCH 03/10] enketo: relax Content-Security-Policy for inline style
attributes (#771)
---
files/nginx/odk.conf.template | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/files/nginx/odk.conf.template b/files/nginx/odk.conf.template
index 663cb874..6362ab80 100644
--- a/files/nginx/odk.conf.template
+++ b/files/nginx/odk.conf.template
@@ -36,7 +36,7 @@ server {
# More lax CSP for enketo-express:
# Google Maps API: https://developers.google.com/maps/documentation/javascript/content-security-policy
- add_header Content-Security-Policy-Report-Only "default-src 'none'; connect-src 'self' blob: https://maps.googleapis.com/maps/ https://maps.google.com/ https://maps.gstatic.com/mapfiles/ https://fonts.gstatic.com/ https://fonts.googleapis.com/; font-src 'self' https://fonts.gstatic.com/; frame-src 'none'; img-src data: blob: jr: 'self' https://maps.google.com/maps/ https://maps.gstatic.com/mapfiles/ https://maps.googleapis.com/maps/; manifest-src 'none'; media-src blob: jr: 'self'; object-src 'none'; script-src 'unsafe-inline' 'self' https://maps.googleapis.com/maps/api/js/ https://maps.google.com/maps/ https://maps.google.com/maps-api-v3/api/js/; style-src 'unsafe-inline' 'self' https://fonts.googleapis.com/css; style-src-attr 'none'; report-uri /csp-report";
+ add_header Content-Security-Policy-Report-Only "default-src 'none'; connect-src 'self' blob: https://maps.googleapis.com/maps/ https://maps.google.com/ https://maps.gstatic.com/mapfiles/ https://fonts.gstatic.com/ https://fonts.googleapis.com/; font-src 'self' https://fonts.gstatic.com/; frame-src 'none'; img-src data: blob: jr: 'self' https://maps.google.com/maps/ https://maps.gstatic.com/mapfiles/ https://maps.googleapis.com/maps/; manifest-src 'none'; media-src blob: jr: 'self'; object-src 'none'; script-src 'unsafe-inline' 'self' https://maps.googleapis.com/maps/api/js/ https://maps.google.com/maps/ https://maps.google.com/maps-api-v3/api/js/; style-src 'unsafe-inline' 'self' https://fonts.googleapis.com/css; style-src-attr 'unsafe-inline'; report-uri /csp-report";
#
# Rules set to 'none' here would fallback to default-src if excluded.
# They are included here to ease interpretation of violation reports.
From 5bb4def40dfc5af31ea5bae5f781476f54e21716 Mon Sep 17 00:00:00 2001
From: Alex Anderson <191496+alxndrsn@users.noreply.github.com>
Date: Thu, 5 Dec 2024 08:02:15 +0300
Subject: [PATCH 04/10] nginx: skip frontend build in tests (#813)
This significantly decreases test turnaround time.
Local results (`./run-tests; time ./run-tests.sh`):
* with this PR: 14 seconds
* without this: 90 seconds
Cons:
* complicates build script (`build-frontend.sh`)
---
files/prebuild/build-frontend.sh | 18 ++++++++++++++++--
nginx.dockerfile | 2 ++
test/nginx.test.docker-compose.yml | 2 ++
3 files changed, 20 insertions(+), 2 deletions(-)
diff --git a/files/prebuild/build-frontend.sh b/files/prebuild/build-frontend.sh
index 6122c321..699d7d09 100755
--- a/files/prebuild/build-frontend.sh
+++ b/files/prebuild/build-frontend.sh
@@ -1,4 +1,18 @@
#!/bin/bash -eu
+
cd client
-npm clean-install --no-audit --fund=false --update-notifier=false
-npm run build
+
+if [[ ${SKIP_FRONTEND_BUILD-} != "" ]]; then
+ echo "[build-frontend] Skipping frontend build."
+
+ # Create minimal fake frontend to allow tests to pass:
+ mkdir -p dist
+ echo > dist/blank.html
+ echo > dist/favicon.ico
+ echo > dist/index.html '
---
.circleci/config.yml | 4 +-
files/nginx/odk.conf.template | 9 ++
files/nginx/redirector.conf | 12 ++-
files/nginx/setup-odk.sh | 13 ++-
test/run-tests.sh | 2 +-
test/test-nginx.js | 153 ++++++++++++++++++++++++++++++++--
6 files changed, 180 insertions(+), 13 deletions(-)
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 547304ae..01960b5e 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -30,11 +30,11 @@ jobs:
docker compose up -d
CONTAINER_NAME=$(docker inspect -f '{{.Name}}' $(docker compose ps -q nginx) | cut -c2-)
docker run --network container:$CONTAINER_NAME \
- appropriate/curl -4 --insecure --retry 30 --retry-delay 10 --retry-connrefused https://localhost/ \
+ appropriate/curl -4 --insecure --retry 30 --retry-delay 10 --retry-connrefused https://localhost/ -H 'Host: local' \
| tee /dev/tty \
| grep -q 'ODK Central'
docker run --network container:$CONTAINER_NAME \
- appropriate/curl -4 --insecure --retry 20 --retry-delay 2 --retry-connrefused https://localhost/v1/projects \
+ appropriate/curl -4 --insecure --retry 20 --retry-delay 2 --retry-connrefused https://localhost/v1/projects -H 'Host: local' \
| tee /dev/tty \
| grep -q '\[\]'
- run:
diff --git a/files/nginx/odk.conf.template b/files/nginx/odk.conf.template
index 5fbdaf08..8f268ae4 100644
--- a/files/nginx/odk.conf.template
+++ b/files/nginx/odk.conf.template
@@ -1,3 +1,12 @@
+server {
+ listen 443 default_server ssl;
+
+ ssl_certificate /etc/nginx/ssl/nginx.default.crt;
+ ssl_certificate_key /etc/nginx/ssl/nginx.default.key;
+
+ return 421;
+}
+
server {
listen 443 ssl;
server_name ${DOMAIN};
diff --git a/files/nginx/redirector.conf b/files/nginx/redirector.conf
index 08e33bd5..ba56723f 100644
--- a/files/nginx/redirector.conf
+++ b/files/nginx/redirector.conf
@@ -1,8 +1,9 @@
server {
# Listen on plain old HTTP and catch all requests so they can be redirected
# to HTTPS instead.
- listen 80 default_server reuseport;
- listen [::]:80 default_server reuseport;
+ listen 80 reuseport;
+ listen [::]:80 reuseport;
+ server_name ${DOMAIN};
# Anything requesting this particular URL should be served content from
# Certbot's folder so the HTTP-01 ACME challenges can be completed for the
@@ -18,3 +19,10 @@ server {
return 301 https://$http_host$request_uri;
}
}
+
+server {
+ listen 80 default_server;
+ listen [::]:80 default_server;
+
+ return 421;
+}
diff --git a/files/nginx/setup-odk.sh b/files/nginx/setup-odk.sh
index db0f9356..87416c4b 100644
--- a/files/nginx/setup-odk.sh
+++ b/files/nginx/setup-odk.sh
@@ -9,6 +9,15 @@ fi
envsubst < /usr/share/odk/nginx/client-config.json.template > /usr/share/nginx/html/client-config.json
+# Generate self-signed keys for the incorrect (catch-all) HTTPS listener. This
+# cert should never be seen by legitimate users, so it's not a big deal that
+# it's self-signed and won't expire for 1,000 years.
+mkdir -p /etc/nginx/ssl
+openssl req -x509 -nodes -newkey rsa:2048 \
+ -subj "/" \
+ -keyout /etc/nginx/ssl/nginx.default.key \
+ -out /etc/nginx/ssl/nginx.default.crt \
+ -days 365000
DH_PATH=/etc/dh/nginx.pem
if [ "$SSL_TYPE" != "upstream" ] && [ ! -s "$DH_PATH" ]; then
@@ -28,7 +37,9 @@ fi
# start from fresh templates in case ssl type has changed
echo "writing fresh nginx templates..."
# redirector.conf gets deleted if using upstream SSL so copy it back
-cp /usr/share/odk/nginx/redirector.conf /etc/nginx/conf.d/redirector.conf
+envsubst '$DOMAIN' \
+ < /usr/share/odk/nginx/redirector.conf \
+ > /etc/nginx/conf.d/redirector.conf
CERT_DOMAIN=$( [ "$SSL_TYPE" = "customssl" ] && echo "local" || echo "$DOMAIN") \
envsubst '$SSL_TYPE $CERT_DOMAIN $DOMAIN $SENTRY_ORG_SUBDOMAIN $SENTRY_KEY $SENTRY_PROJECT' \
diff --git a/test/run-tests.sh b/test/run-tests.sh
index f9ee7828..0c09276f 100755
--- a/test/run-tests.sh
+++ b/test/run-tests.sh
@@ -34,7 +34,7 @@ wait_for_http_response 5 localhost:8383/health 200
log "Waiting for mock enketo..."
wait_for_http_response 5 localhost:8005/health 200
log "Waiting for nginx..."
-wait_for_http_response 90 localhost:9000 301
+wait_for_http_response 90 localhost:9000 421
npm run test:nginx
diff --git a/test/test-nginx.js b/test/test-nginx.js
index 2125adc7..0c38aa6c 100644
--- a/test/test-nginx.js
+++ b/test/test-nginx.js
@@ -1,3 +1,5 @@
+const tls = require('node:tls');
+const { Readable } = require('stream');
const { assert } = require('chai');
describe('nginx config', () => {
@@ -12,7 +14,16 @@ describe('nginx config', () => {
// then
assert.equal(res.status, 301);
- assert.equal(res.headers.get('location'), 'https://localhost:9000/');
+ assert.equal(res.headers.get('location'), 'https://odk-nginx.example.test/');
+ });
+
+ it('should forward HTTP to HTTPS (IPv6)', async () => {
+ // when
+ const res = await fetchHttp6('/');
+
+ // then
+ assert.equal(res.status, 301);
+ assert.equal(res.headers.get('location'), 'https://odk-nginx.example.test/');
});
it('should serve generated client-config.json', async () => {
@@ -25,6 +36,16 @@ describe('nginx config', () => {
assert.equal(await res.headers.get('cache-control'), 'no-cache');
});
+ it('should serve generated client-config.json (IPv6)', async () => {
+ // when
+ const res = await fetchHttps6('/client-config.json');
+
+ // then
+ assert.equal(res.status, 200);
+ assert.deepEqual(await res.json(), { oidcEnabled: false });
+ assert.equal(await res.headers.get('cache-control'), 'no-cache');
+ });
+
[
[ '/index.html', /<\/div>/ ],
[ '/version.txt', /^versions:/ ],
@@ -83,7 +104,7 @@ describe('nginx config', () => {
it('should set x-forwarded-proto header to "https"', async () => {
// when
- const res = await fetch(`https://localhost:9001/v1/reflect-headers`);
+ const res = await fetchHttps('/v1/reflect-headers');
// then
assert.equal(res.status, 200);
@@ -95,7 +116,7 @@ describe('nginx config', () => {
it('should override supplied x-forwarded-proto header', async () => {
// when
- const res = await fetch(`https://localhost:9001/v1/reflect-headers`, {
+ const res = await fetchHttps('/v1/reflect-headers', {
headers: {
'x-forwarded-proto': 'http',
},
@@ -108,16 +129,78 @@ describe('nginx config', () => {
// then
assert.equal(body['x-forwarded-proto'], 'https');
});
+
+ it('should reject HTTP requests with incorrect host header supplied', async () => {
+ // when
+ const res = await fetchHttp('/', { headers:{ host:'bad.example.com' } });
+
+ // then
+ assert.equal(res.status, 421);
+ });
+
+ it('should reject HTTP requests with incorrect host header supplied (IPv6)', async () => {
+ // when
+ const res = await fetchHttp6('/', { headers:{ host:'bad.example.com' } });
+
+ // then
+ assert.equal(res.status, 421);
+ });
+
+ it('should reject HTTPS requests with incorrect host header supplied', async () => {
+ // when
+ const res = await fetchHttps('/', { headers:{ host:'bad.example.com' } });
+
+ // then
+ assert.equal(res.status, 421);
+ });
+
+ it('should reject HTTPS requests with incorrect host header supplied (IPv6)', async () => {
+ // when
+ const res = await fetchHttps6('/', { headers:{ host:'bad.example.com' } });
+
+ // then
+ assert.equal(res.status, 421);
+ });
+
+ it('should serve long-lived certificate to HTTPS requests with incorrect host header', () => new Promise((resolve, reject) => {
+ const socket = tls.connect(9001, { host:'localhost', servername:'bad.example.com', rejectUnauthorized:false }, () => {
+ try {
+ const certificate = socket.getPeerCertificate();
+ const validUntilRaw = certificate.valid_to;
+
+ // Dates look like RFC-822 format - probably direct output of `openssl`. NodeJS Date.parse()
+ // seems to support this format.
+ const validUntil = new Date(validUntilRaw);
+ assert.isFalse(isNaN(validUntil), `Could not parse certificate's valid_to value as a date ('${validUntilRaw}')`);
+ assert.isAbove(validUntil.getFullYear(), 3000, 'The provided certificate expires too soon.');
+ socket.end();
+ } catch(err) {
+ socket.destroy(err);
+ }
+ });
+ socket.on('end', resolve);
+ socket.on('error', reject);
+ }));
});
function fetchHttp(path, options) {
if(!path.startsWith('/')) throw new Error('Invalid path.');
- return fetch(`http://localhost:9000${path}`, { redirect:'manual', ...options });
+ return request(`http://127.0.0.1:9000${path}`, options);
+}
+
+function fetchHttp6(path, options) {
+ if(!path.startsWith('/')) throw new Error('Invalid path.');
+ return request(`http://[::1]:9000${path}`, options);
}
function fetchHttps(path, options) {
if(!path.startsWith('/')) throw new Error('Invalid path.');
- return fetch(`https://localhost:9001${path}`, { redirect:'manual', ...options });
+ return request(`https://127.0.0.1:9001${path}`, options);
+}
+
+function fetchHttps6(path, options) {
+ if(!path.startsWith('/')) throw new Error('Invalid path.');
+ return request(`https://[::1]:9001${path}`, options);
}
function assertEnketoReceived(...expectedRequests) {
@@ -129,7 +212,7 @@ function assertBackendReceived(...expectedRequests) {
}
async function assertMockHttpReceived(port, expectedRequests) {
- const res = await fetch(`http://localhost:${port}/request-log`);
+ const res = await request(`http://localhost:${port}/request-log`);
assert.isTrue(res.ok);
assert.deepEqual(expectedRequests, await res.json());
}
@@ -143,6 +226,62 @@ function resetBackendMock() {
}
async function resetMock(port) {
- const res = await fetch(`http://localhost:${port}/reset`);
+ const res = await request(`http://localhost:${port}/reset`);
assert.isTrue(res.ok);
}
+
+// Similar to fetch() but:
+//
+// 1. do not follow redirects
+// 2. allow overriding of fetch's "forbidden" headers: https://developer.mozilla.org/en-US/docs/Glossary/Forbidden_header_name
+// 3. allow access to server SSL certificate
+function request(url, { body, ...options }={}) {
+ if(!options.headers) options.headers = {};
+ if(!options.headers.host) options.headers.host = 'odk-nginx.example.test';
+
+ return new Promise((resolve, reject) => {
+ try {
+ const req = getProtocolImplFrom(url).request(url, options, res => {
+ res.on('error', reject);
+
+ const body = new Readable({ _read: () => {} });
+ res.on('error', err => body.destroy(err));
+ res.on('data', data => body.push(data));
+ res.on('end', () => body.push(null));
+
+ const text = () => new Promise((resolve, reject) => {
+ const chunks = [];
+ body.on('error', reject);
+ body.on('data', data => chunks.push(data))
+ body.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
+ });
+
+ const status = res.statusCode;
+
+ resolve({
+ status,
+ ok: status >= 200 && status < 300,
+ statusText: res.statusText,
+ body,
+ text,
+ json: async () => JSON.parse(await text()),
+ headers: new Headers(res.headers),
+ });
+ });
+ req.on('error', reject);
+ if(body !== undefined) req.write(body);
+ req.end();
+ } catch(err) {
+ reject(err);
+ }
+ });
+}
+
+function getProtocolImplFrom(url) {
+ const { protocol } = new URL(url);
+ switch(protocol) {
+ case 'http:': return require('node:http');
+ case 'https:': return require('node:https');
+ default: throw new Error(`Unsupported protocol: ${protocol}`);
+ }
+}
From 801ab503c74eaa24b27a226622eceee9351d5db0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?H=C3=A9l=C3=A8ne=20Martin?=
Date: Mon, 9 Dec 2024 20:55:06 -0800
Subject: [PATCH 08/10] Upgrade dependencies for v2024.3. (#838)
* Use node 22.12.0 Docker images
* Upgrade Enketo
* Fix dependency vulnerabilities
* Use pyxform 3.0.0
---
.github/workflows/test-nginx.yml | 2 +-
client | 2 +-
docker-compose.yml | 2 +-
enketo.dockerfile | 2 +-
nginx.dockerfile | 2 +-
secrets.dockerfile | 2 +-
server | 2 +-
service.dockerfile | 2 +-
test/mock-http-server/package-lock.json | 26 ++++++++++++++-----------
9 files changed, 23 insertions(+), 19 deletions(-)
diff --git a/.github/workflows/test-nginx.yml b/.github/workflows/test-nginx.yml
index ee56a757..cd1349c8 100644
--- a/.github/workflows/test-nginx.yml
+++ b/.github/workflows/test-nginx.yml
@@ -16,7 +16,7 @@ jobs:
submodules: recursive
- uses: actions/setup-node@v4
with:
- node-version: 20.17.0
+ node-version: 22.12.0
- run: cd test && npm i
- run: cd test && ./run-tests.sh
diff --git a/client b/client
index 99b11a8c..3fb0c22b 160000
--- a/client
+++ b/client
@@ -1 +1 @@
-Subproject commit 99b11a8cc7d30dbc03376f97ab345dc0d18c2a98
+Subproject commit 3fb0c22b1cbdc3a6004963afcc3847a82c09307d
diff --git a/docker-compose.yml b/docker-compose.yml
index 1d70fdfc..97b7ac65 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -108,7 +108,7 @@ services:
options:
max-file: "30"
pyxform:
- image: 'ghcr.io/getodk/pyxform-http:v2.1.1'
+ image: 'ghcr.io/getodk/pyxform-http:v3.0.0'
restart: always
secrets:
volumes:
diff --git a/enketo.dockerfile b/enketo.dockerfile
index c4b03466..efd47403 100644
--- a/enketo.dockerfile
+++ b/enketo.dockerfile
@@ -1,4 +1,4 @@
-FROM ghcr.io/enketo/enketo:7.4.0
+FROM ghcr.io/enketo/enketo:7.5.0
ENV ENKETO_SRC_DIR=/srv/src/enketo/packages/enketo-express
WORKDIR ${ENKETO_SRC_DIR}
diff --git a/nginx.dockerfile b/nginx.dockerfile
index b2b94712..fb66d63f 100644
--- a/nginx.dockerfile
+++ b/nginx.dockerfile
@@ -1,4 +1,4 @@
-FROM node:20.17.0-slim AS intermediate
+FROM node:22.12.0-slim AS intermediate
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
diff --git a/secrets.dockerfile b/secrets.dockerfile
index aef60b73..0a53585a 100644
--- a/secrets.dockerfile
+++ b/secrets.dockerfile
@@ -1,3 +1,3 @@
-FROM node:20.17.0-slim
+FROM node:22.12.0-slim
COPY files/enketo/generate-secrets.sh ./
diff --git a/server b/server
index 63ca7881..b4754cf5 160000
--- a/server
+++ b/server
@@ -1 +1 @@
-Subproject commit 63ca7881f6e6eb0b5c9051bf64448d802720f100
+Subproject commit b4754cf52bfa64b1ca841bc9ccb64a38726398e8
diff --git a/service.dockerfile b/service.dockerfile
index 82d5848f..7b947419 100644
--- a/service.dockerfile
+++ b/service.dockerfile
@@ -1,4 +1,4 @@
-ARG node_version=20.17.0
+ARG node_version=22.12.0
diff --git a/test/mock-http-server/package-lock.json b/test/mock-http-server/package-lock.json
index 75660bbe..7f68446c 100644
--- a/test/mock-http-server/package-lock.json
+++ b/test/mock-http-server/package-lock.json
@@ -95,9 +95,9 @@
}
},
"node_modules/cookie": {
- "version": "0.6.0",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
- "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
+ "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
"engines": {
"node": ">= 0.6"
}
@@ -194,16 +194,16 @@
}
},
"node_modules/express": {
- "version": "4.21.0",
- "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz",
- "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==",
+ "version": "4.21.2",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
+ "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
"body-parser": "1.20.3",
"content-disposition": "0.5.4",
"content-type": "~1.0.4",
- "cookie": "0.6.0",
+ "cookie": "0.7.1",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
@@ -217,7 +217,7 @@
"methods": "~1.1.2",
"on-finished": "2.4.1",
"parseurl": "~1.3.3",
- "path-to-regexp": "0.1.10",
+ "path-to-regexp": "0.1.12",
"proxy-addr": "~2.0.7",
"qs": "6.13.0",
"range-parser": "~1.2.1",
@@ -232,6 +232,10 @@
},
"engines": {
"node": ">= 0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
"node_modules/finalhandler": {
@@ -485,9 +489,9 @@
}
},
"node_modules/path-to-regexp": {
- "version": "0.1.10",
- "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
- "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w=="
+ "version": "0.1.12",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
+ "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ=="
},
"node_modules/proxy-addr": {
"version": "2.0.7",
From 20878c7ffe3b92a2b9f35bb253e6043e5ddba67b Mon Sep 17 00:00:00 2001
From: Alex Anderson <191496+alxndrsn@users.noreply.github.com>
Date: Tue, 10 Dec 2024 09:26:31 +0300
Subject: [PATCH 09/10] test/nginx: remove misleading comment (#839)
This comment was merged as part of #809, but was not correct by the time the PR was merged.
---
test/test-nginx.js | 1 -
1 file changed, 1 deletion(-)
diff --git a/test/test-nginx.js b/test/test-nginx.js
index 0c38aa6c..a367d7f5 100644
--- a/test/test-nginx.js
+++ b/test/test-nginx.js
@@ -234,7 +234,6 @@ async function resetMock(port) {
//
// 1. do not follow redirects
// 2. allow overriding of fetch's "forbidden" headers: https://developer.mozilla.org/en-US/docs/Glossary/Forbidden_header_name
-// 3. allow access to server SSL certificate
function request(url, { body, ...options }={}) {
if(!options.headers) options.headers = {};
if(!options.headers.host) options.headers.host = 'odk-nginx.example.test';
From 0b373353cfc736b0052cb7bbd9f4b091e484fba1 Mon Sep 17 00:00:00 2001
From: Matthew White
Date: Tue, 17 Dec 2024 14:55:05 -0500
Subject: [PATCH 10/10] Update submodules for v2024.3 (#853)
---
client | 2 +-
server | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/client b/client
index 3fb0c22b..6ba4ef11 160000
--- a/client
+++ b/client
@@ -1 +1 @@
-Subproject commit 3fb0c22b1cbdc3a6004963afcc3847a82c09307d
+Subproject commit 6ba4ef1183d2081e1da6f9921806c03df8483f97
diff --git a/server b/server
index b4754cf5..92f1186a 160000
--- a/server
+++ b/server
@@ -1 +1 @@
-Subproject commit b4754cf52bfa64b1ca841bc9ccb64a38726398e8
+Subproject commit 92f1186a3257393ba46ea8ede3dc75803eb15c4d