Release 1.5.7
This commit is contained in:
@@ -6,6 +6,12 @@ All notable changes to this project will be documented in this file.
|
|||||||
|
|
||||||
- No unreleased changes recorded yet.
|
- 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
|
## 1.5.6 - 2026-07-25
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "pulse-signage",
|
"name": "pulse-signage",
|
||||||
"version": "1.5.6",
|
"version": "1.5.7",
|
||||||
"private": false,
|
"private": false,
|
||||||
"description": "Pulse Signage application with MySQL and media storage",
|
"description": "Pulse Signage application with MySQL and media storage",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|||||||
+46
-5
@@ -47,6 +47,19 @@ function canonicalize(value) {
|
|||||||
return value === undefined ? undefined : 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) {
|
function stableJson(value) {
|
||||||
return JSON.stringify(canonicalize(value));
|
return JSON.stringify(canonicalize(value));
|
||||||
}
|
}
|
||||||
@@ -56,10 +69,38 @@ function hashPayload(value) {
|
|||||||
return crypto.createHash('sha256').update(normalized).digest('hex');
|
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) {
|
function getRequestPath(req) {
|
||||||
const explicitPath = String(req && req.path ? req.path : '').trim();
|
const explicitPath = String(req && req.path ? req.path : '').trim();
|
||||||
if (explicitPath) {
|
if (explicitPath) {
|
||||||
return explicitPath;
|
return normalizeRequestPath(explicitPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
const rawUrl = String(req && req.url ? req.url : '').trim();
|
const rawUrl = String(req && req.url ? req.url : '').trim();
|
||||||
@@ -68,9 +109,9 @@ function getRequestPath(req) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return new URL(rawUrl, 'http://localhost').pathname;
|
return normalizeRequestPath(new URL(rawUrl, 'http://localhost').pathname);
|
||||||
} catch (_error) {
|
} 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 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 timestamp = String(options && options.timestamp || Date.now()).trim();
|
||||||
const bodyDigest = hashPayload(options && Object.prototype.hasOwnProperty.call(options, 'body') ? options.body : null);
|
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}`);
|
const signature = signText(secret, `request\n${method}\n${pathname}\n${timestamp}\n${bodyDigest}`);
|
||||||
@@ -199,7 +240,7 @@ function verifyRequestAuth(req) {
|
|||||||
return false;
|
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);
|
return timingSafeEqualHex(expectedSignature, signature);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user