Release 1.5.7
Publish Docker Image / build-and-push (push) Successful in 1m16s

This commit is contained in:
2026-07-25 17:58:00 +01:00
parent 47613e61a2
commit 04ddec50ad
3 changed files with 53 additions and 6 deletions
+6
View File
@@ -6,6 +6,12 @@ All notable changes to this project will be documented in this file.
- No unreleased changes recorded yet.
## 1.5.7 - 2026-07-25
### Fixed
- Upload removal requests now authenticate correctly when a deployed player parses a bodyless `DELETE` request as an empty object, which could cause 401s for newly uploaded images.
## 1.5.6 - 2026-07-25
### Changed
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "pulse-signage",
"version": "1.5.6",
"version": "1.5.7",
"private": false,
"description": "Pulse Signage application with MySQL and media storage",
"repository": {
+46 -5
View File
@@ -47,6 +47,19 @@ function canonicalize(value) {
return value === undefined ? undefined : value;
}
function normalizeRequestPath(pathValue) {
const value = String(pathValue || '').trim();
if (!value) {
return '';
}
try {
return decodeURIComponent(value);
} catch (_error) {
return value;
}
}
function stableJson(value) {
return JSON.stringify(canonicalize(value));
}
@@ -56,10 +69,38 @@ function hashPayload(value) {
return crypto.createHash('sha256').update(normalized).digest('hex');
}
function normalizeRequestAuthBody(req) {
if (!req) {
return null;
}
const body = req.body;
if (body === undefined || body === null) {
return null;
}
if (Buffer.isBuffer(body)) {
return body;
}
if (typeof body === 'object' && !Array.isArray(body)) {
const hasFields = Object.keys(body).length > 0;
if (!hasFields) {
const contentLength = Number(String(req.headers && req.headers['content-length'] || '').trim() || 0);
const contentType = String(req.headers && req.headers['content-type'] || '').trim();
if (!contentLength || !contentType) {
return null;
}
}
}
return body;
}
function getRequestPath(req) {
const explicitPath = String(req && req.path ? req.path : '').trim();
if (explicitPath) {
return explicitPath;
return normalizeRequestPath(explicitPath);
}
const rawUrl = String(req && req.url ? req.url : '').trim();
@@ -68,9 +109,9 @@ function getRequestPath(req) {
}
try {
return new URL(rawUrl, 'http://localhost').pathname;
return normalizeRequestPath(new URL(rawUrl, 'http://localhost').pathname);
} catch (_error) {
return rawUrl.split('?')[0] || '';
return normalizeRequestPath(rawUrl.split('?')[0] || '');
}
}
@@ -167,7 +208,7 @@ function createRequestAuthHeaders(options) {
}
const method = String(options && options.method || 'GET').trim().toUpperCase();
const pathname = String(options && options.pathname || '').trim();
const pathname = normalizeRequestPath(options && options.pathname || '');
const timestamp = String(options && options.timestamp || Date.now()).trim();
const bodyDigest = hashPayload(options && Object.prototype.hasOwnProperty.call(options, 'body') ? options.body : null);
const signature = signText(secret, `request\n${method}\n${pathname}\n${timestamp}\n${bodyDigest}`);
@@ -199,7 +240,7 @@ function verifyRequestAuth(req) {
return false;
}
const expectedSignature = signText(secret, `request\n${String(req.method || 'GET').trim().toUpperCase()}\n${getRequestPath(req)}\n${timestamp}\n${hashPayload(req.body)}`);
const expectedSignature = signText(secret, `request\n${String(req.method || 'GET').trim().toUpperCase()}\n${getRequestPath(req)}\n${timestamp}\n${hashPayload(normalizeRequestAuthBody(req))}`);
return timingSafeEqualHex(expectedSignature, signature);
}