security: fix all Medium findings from FLAWS_REPORT audit
- Auth: TOTP replay protection with counter tracking, constant-time backup code comparison, atomic lockout increment, per-token logout - Invoices/PDFs: net-based VAT calculation, dangerous URL scheme stripping in cleanQuillHtml, orders-pdf error handling - Orders: reject item changes on status transition, cascading delete cleanup, take:1 with orderBy - Projects: atomic rename collision handling, MIME/extension validation, empty customer name rejection - Attendance: Czech public holiday awareness in frontend fund calculation, leave_hours 0 handling, invalid date NaN guard, bounded per-month queries in workfund - Users/Admin: profile audit logging + password validation, session revocation guard, session ID validation, dashboard DB aggregation, soft-deleted record protection in scope templates - Frontend: FormField label linkage, Pagination ARIA, error handling in OrderConfirmationModal, 401 propagation, GPS emoji hidden from screen readers, table sort state fix, geolocation race/abort cleanup, Leaflet popup DOM safety, Vehicles toggleActive minimal body, CompanySettings ref mutation fix, OfferDetail unlock abort, AttendanceBalances combined fetches - Utils: env validation, Puppeteer concurrency mutex, invoice alert cron cleanup on shutdown, body limit alignment, TOTP error logging, trustProxy from env, symlink rejection, rate cache Map usage Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -81,7 +81,41 @@ export const config = {
|
||||
origins: (process.env.CORS_ORIGINS || "").split(",").filter(Boolean),
|
||||
},
|
||||
|
||||
trustProxy: (process.env.TRUST_PROXY || "")
|
||||
.split(",")
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean),
|
||||
|
||||
security: {
|
||||
bcryptCost: 12,
|
||||
},
|
||||
} as const;
|
||||
|
||||
const HEX64_RE = /^[0-9a-fA-F]{64}$/;
|
||||
if (!HEX64_RE.test(config.jwt.secret)) {
|
||||
throw new Error("JWT_SECRET must be a 64-character hex string");
|
||||
}
|
||||
if (!HEX64_RE.test(config.totp.encryptionKey)) {
|
||||
throw new Error("TOTP_ENCRYPTION_KEY must be a 64-character hex string");
|
||||
}
|
||||
if (Number.isNaN(config.port) || config.port < 1 || config.port > 65535) {
|
||||
throw new Error("PORT must be a valid TCP port (1-65535)");
|
||||
}
|
||||
if (
|
||||
Number.isNaN(config.jwt.accessTokenExpiry) ||
|
||||
config.jwt.accessTokenExpiry <= 0
|
||||
) {
|
||||
throw new Error("ACCESS_TOKEN_EXPIRY must be a positive integer");
|
||||
}
|
||||
if (
|
||||
Number.isNaN(config.jwt.refreshTokenSessionExpiry) ||
|
||||
config.jwt.refreshTokenSessionExpiry <= 0
|
||||
) {
|
||||
throw new Error("REFRESH_TOKEN_SESSION_EXPIRY must be a positive integer");
|
||||
}
|
||||
if (
|
||||
Number.isNaN(config.jwt.refreshTokenRememberExpiry) ||
|
||||
config.jwt.refreshTokenRememberExpiry <= 0
|
||||
) {
|
||||
throw new Error("REFRESH_TOKEN_REMEMBER_EXPIRY must be a positive integer");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user