6989a98d75
- Arkitektur: docs/auth/passwordless-architecture.md - Backend: iom/quixzoom-auth-service/ (FastAPI + Redis) - Webb: quixzoom-market-pages/se/login/ (QR-kod + polling) - App: iom/quixzoom-app/src/features/auth/ (push + deep links) Flöde: QR-kod → app-godkännande → webb-inloggad
47 lines
2.1 KiB
JavaScript
47 lines
2.1 KiB
JavaScript
import { HttpProxyMiddlewareError } from '../../errors.js';
|
|
const CR_OR_LF = /[\r\n]/;
|
|
/**
|
|
* HPM_ERR_INVALID_MULTIPART prefixed error code will be used in
|
|
* [status-code.ts]({@link ../../status-code.ts}) to return status code 400.
|
|
*/
|
|
export const HPM_ERR_INVALID_MULTIPART = 'HPM_ERR_INVALID_MULTIPART';
|
|
/**
|
|
* stringify FormData data
|
|
* @param contentType
|
|
* @param data
|
|
* @returns
|
|
*/
|
|
export function stringifyFormData(contentType, data) {
|
|
const boundary = getMultipartBoundary(contentType);
|
|
let str = '';
|
|
for (const [key, value] of Object.entries(data)) {
|
|
const normalizedKey = String(key);
|
|
const normalizedValue = String(value);
|
|
// Reject potentially dangerous sequences to prevent multipart header/body injection.
|
|
validateMultipartField(normalizedKey, normalizedValue, boundary);
|
|
str += `--${boundary}\r\nContent-Disposition: form-data; name="${escapeMultipartFieldName(normalizedKey)}"\r\n\r\n${normalizedValue}\r\n`;
|
|
}
|
|
return str;
|
|
}
|
|
function getMultipartBoundary(contentType) {
|
|
const boundaryMatch = /(?:^|;)\s*boundary=(?:"([^"]+)"|([^;]+))/i.exec(contentType);
|
|
// Keep backward-compatible behavior when boundary is omitted: fall back to legacy extraction.
|
|
const boundary = (boundaryMatch?.[1] ?? boundaryMatch?.[2] ?? contentType).trim();
|
|
if (!boundary || CR_OR_LF.test(boundary)) {
|
|
throw new HttpProxyMiddlewareError('[HPM] invalid multipart boundary detected.', `${HPM_ERR_INVALID_MULTIPART}_BOUNDARY`);
|
|
}
|
|
return boundary;
|
|
}
|
|
function validateMultipartField(fieldName, fieldValue, boundary) {
|
|
const boundaryDelimiter = `--${boundary}`;
|
|
if (CR_OR_LF.test(fieldName)) {
|
|
throw new HttpProxyMiddlewareError(`[HPM] invalid multipart field name "${fieldName}" detected.`, `${HPM_ERR_INVALID_MULTIPART}_FIELD_NAME`);
|
|
}
|
|
if (CR_OR_LF.test(fieldValue) || fieldValue.includes(boundaryDelimiter)) {
|
|
throw new HttpProxyMiddlewareError(`[HPM] invalid multipart field value for "${fieldName}" detected.`, `${HPM_ERR_INVALID_MULTIPART}_FIELD_VALUE`);
|
|
}
|
|
}
|
|
function escapeMultipartFieldName(fieldName) {
|
|
return fieldName.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
}
|