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:
@@ -1,31 +1,41 @@
|
||||
import { Browser } from "puppeteer";
|
||||
|
||||
let browser: Browser | null = null;
|
||||
let launching: Promise<Browser> | null = null;
|
||||
|
||||
async function getBrowser(): Promise<Browser> {
|
||||
if (browser && browser.connected) return browser;
|
||||
|
||||
// Try puppeteer (bundles Chromium), fall back to puppeteer-core (system Chromium)
|
||||
try {
|
||||
const puppeteer = await import("puppeteer");
|
||||
browser = await puppeteer.default.launch({
|
||||
headless: true,
|
||||
args: ["--no-sandbox", "--disable-setuid-sandbox", "--disable-gpu"],
|
||||
});
|
||||
} catch {
|
||||
const core = await import("puppeteer-core");
|
||||
const executablePath =
|
||||
process.env.CHROMIUM_PATH ||
|
||||
"/usr/bin/chromium-browser" ||
|
||||
"/usr/bin/chromium";
|
||||
browser = await core.default.launch({
|
||||
headless: true,
|
||||
executablePath,
|
||||
args: ["--no-sandbox", "--disable-setuid-sandbox", "--disable-gpu"],
|
||||
});
|
||||
}
|
||||
if (launching) return launching;
|
||||
|
||||
return browser;
|
||||
launching = (async () => {
|
||||
// Try puppeteer (bundles Chromium), fall back to puppeteer-core (system Chromium)
|
||||
try {
|
||||
const puppeteer = await import("puppeteer");
|
||||
browser = await puppeteer.default.launch({
|
||||
headless: true,
|
||||
args: ["--no-sandbox", "--disable-setuid-sandbox", "--disable-gpu"],
|
||||
});
|
||||
} catch {
|
||||
const core = await import("puppeteer-core");
|
||||
const executablePath =
|
||||
process.env.CHROMIUM_PATH ||
|
||||
"/usr/bin/chromium-browser" ||
|
||||
"/usr/bin/chromium";
|
||||
browser = await core.default.launch({
|
||||
headless: true,
|
||||
executablePath,
|
||||
args: ["--no-sandbox", "--disable-setuid-sandbox", "--disable-gpu"],
|
||||
});
|
||||
}
|
||||
return browser!;
|
||||
})();
|
||||
|
||||
try {
|
||||
return await launching;
|
||||
} finally {
|
||||
launching = null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function htmlToPdf(html: string): Promise<Buffer> {
|
||||
|
||||
@@ -2,7 +2,10 @@ import * as OTPAuthLib from "otpauth";
|
||||
import { decrypt } from "./encryption";
|
||||
|
||||
export const OTPAuth = {
|
||||
verify(encryptedSecret: string, code: string): boolean {
|
||||
verify(
|
||||
encryptedSecret: string,
|
||||
code: string,
|
||||
): { valid: boolean; counter: number | null } {
|
||||
try {
|
||||
const secret = decrypt(encryptedSecret);
|
||||
const totp = new OTPAuthLib.TOTP({
|
||||
@@ -12,9 +15,14 @@ export const OTPAuth = {
|
||||
period: 30,
|
||||
});
|
||||
const delta = totp.validate({ token: code, window: 1 });
|
||||
return delta !== null;
|
||||
} catch {
|
||||
return false;
|
||||
if (delta === null) {
|
||||
return { valid: false, counter: null };
|
||||
}
|
||||
const currentCounter = Math.floor(Date.now() / 1000 / 30);
|
||||
return { valid: true, counter: currentCounter + delta };
|
||||
} catch (err) {
|
||||
console.error("TOTP verification error:", err);
|
||||
return { valid: false, counter: null };
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user