diff --git a/backend/src/middleware/errorHandler.js b/backend/src/middleware/errorHandler.js index 67227ba..ba10080 100644 --- a/backend/src/middleware/errorHandler.js +++ b/backend/src/middleware/errorHandler.js @@ -214,6 +214,16 @@ function formatErrorResponse(err, req) { return response; } +/** + * Attach request ID to error response + */ +export function attachRequestIdToError(err, req) { + if (!err.requestId) { + err.requestId = req.id || 'unknown'; + } + return err; +} + /** * Centralized error handling middleware */ @@ -223,6 +233,9 @@ export function errorHandler(err, req, res, next) { return next(err); } + // Attach request ID to error + attachRequestIdToError(err, req); + // Default error values let statusCode = err.statusCode || 500; let code = err.code || ErrorCodes.INTERNAL_ERROR; @@ -327,4 +340,5 @@ export default { createValidationError, createStellarError, generateRequestId, + attachRequestIdToError, }; diff --git a/backend/src/middleware/securityHeaders.js b/backend/src/middleware/securityHeaders.js index 7cf966e..acb45a5 100644 --- a/backend/src/middleware/securityHeaders.js +++ b/backend/src/middleware/securityHeaders.js @@ -28,7 +28,11 @@ export function helmetMiddleware() { ].filter(Boolean), styleSrc: ["'self'", "'unsafe-inline'"], imgSrc: ["'self'", "data:", "https:"], - connectSrc: ["'self'", "https://horizon.stellar.org", "https://horizon-testnet.stellar.org"], + connectSrc: [ + "'self'", + "https://horizon.stellar.org", + "https://horizon-testnet.stellar.org", + ], fontSrc: ["'self'", "https:", "data:"], objectSrc: ["'none'"], mediaSrc: ["'self'"], diff --git a/backend/src/middleware/validate.js b/backend/src/middleware/validate.js index c1b0354..aab185d 100644 --- a/backend/src/middleware/validate.js +++ b/backend/src/middleware/validate.js @@ -83,7 +83,24 @@ export const rules = { .isFloat({ gt: 0 }) .withMessage('Amount must be a positive number') .isLength({ max: 20 }) - .withMessage('Amount too long'), + .withMessage('Amount too long') + .custom((amount) => { + // Normalize to fixed-decimal string with max 7 decimal places + const parsed = parseFloat(amount); + if (isNaN(parsed)) throw new Error('Amount must be a valid number'); + + const normalized = parsed.toFixed(7); + const decimalPlaces = normalized.split('.')[1].replace(/0+$/, '').length; + + if (decimalPlaces > 7) { + throw new Error('Amount cannot have more than 7 decimal places'); + } + return true; + }) + .customSanitizer((amount) => { + // Normalize to fixed-decimal string with max 7 decimal places + return parseFloat(amount).toFixed(7); + }), body('assetCode') .optional() .trim() @@ -209,7 +226,24 @@ export const rules = { .isFloat({ gt: 0 }) .withMessage('Amount must be a positive number') .isLength({ max: 20 }) - .withMessage('Amount too long'), + .withMessage('Amount too long') + .custom((amount) => { + // Normalize to fixed-decimal string with max 7 decimal places + const parsed = parseFloat(amount); + if (isNaN(parsed)) throw new Error('Amount must be a valid number'); + + const normalized = parsed.toFixed(7); + const decimalPlaces = normalized.split('.')[1].replace(/0+$/, '').length; + + if (decimalPlaces > 7) { + throw new Error('Amount cannot have more than 7 decimal places'); + } + return true; + }) + .customSanitizer((amount) => { + // Normalize to fixed-decimal string with max 7 decimal places + return parseFloat(amount).toFixed(7); + }), body('assetCode') .optional() .trim()