feat: enforce maximum password length of 64 characters in user registration and password update
Build & Push Docker Image / build (push) Successful in 4m19s
Build & Push Docker Image / build (push) Successful in 4m19s
This commit is contained in:
+26
-10
@@ -15,8 +15,10 @@ router.post('/callback/:uid', async (req, res) => {
|
||||
const { token } = req.query;
|
||||
const expectedToken = getAnalyticsToken(req.params.uid);
|
||||
|
||||
// Constant-time comparison to prevent timing attacks
|
||||
if (!token || token.length !== expectedToken.length ||
|
||||
// Constant-time comparison to prevent timing attacks.
|
||||
// Reject non-string tokens (e.g. ?token=a&token=b would yield an array and
|
||||
// crash Buffer.from).
|
||||
if (typeof token !== 'string' || token.length !== expectedToken.length ||
|
||||
!crypto.timingSafeEqual(Buffer.from(token), Buffer.from(expectedToken))) {
|
||||
return res.status(403).json({ error: 'Invalid token' });
|
||||
}
|
||||
@@ -216,15 +218,20 @@ router.get('/:id/export/:format', authenticateToken, async (req, res) => {
|
||||
const safeName = (entry.meeting_name || 'analytics').replace(/[^a-zA-Z0-9_-]/g, '_');
|
||||
|
||||
if (format === 'csv') {
|
||||
// Prefix-escape values that start with a formula trigger character so that
|
||||
// Excel/LibreOffice do not evaluate them as formulas (CSV injection).
|
||||
const escapeCsv = (val) => {
|
||||
if (val === null || val === undefined) return '';
|
||||
let s = String(val);
|
||||
if (/^[=+\-@\t\r]/.test(s)) s = "'" + s;
|
||||
if (s.includes(',') || s.includes('"') || s.includes('\n') || s.includes('\r')) {
|
||||
return '"' + s.replace(/"/g, '""') + '"';
|
||||
}
|
||||
return s;
|
||||
};
|
||||
const header = COLUMNS.map(c => c.header).join(',');
|
||||
const csvRows = rows.map(r =>
|
||||
COLUMNS.map(c => {
|
||||
const val = r[c.key];
|
||||
if (typeof val === 'string' && (val.includes(',') || val.includes('"') || val.includes('\n'))) {
|
||||
return '"' + val.replace(/"/g, '""') + '"';
|
||||
}
|
||||
return val;
|
||||
}).join(',')
|
||||
COLUMNS.map(c => escapeCsv(r[c.key])).join(',')
|
||||
);
|
||||
const csv = [header, ...csvRows].join('\n');
|
||||
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
|
||||
@@ -233,10 +240,19 @@ router.get('/:id/export/:format', authenticateToken, async (req, res) => {
|
||||
}
|
||||
|
||||
if (format === 'xlsx') {
|
||||
// Prefix-escape strings that would otherwise be evaluated as formulas.
|
||||
const sanitizeXlsx = (r) => {
|
||||
const out = {};
|
||||
for (const k of Object.keys(r)) {
|
||||
const v = r[k];
|
||||
out[k] = (typeof v === 'string' && /^[=+\-@\t\r]/.test(v)) ? "'" + v : v;
|
||||
}
|
||||
return out;
|
||||
};
|
||||
const workbook = new ExcelJS.Workbook();
|
||||
const sheet = workbook.addWorksheet('Analytics');
|
||||
sheet.columns = COLUMNS;
|
||||
rows.forEach(r => sheet.addRow(r));
|
||||
rows.forEach(r => sheet.addRow(sanitizeXlsx(r)));
|
||||
// Style header row
|
||||
sheet.getRow(1).font = { bold: true };
|
||||
sheet.getRow(1).fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFE0E0E0' } };
|
||||
|
||||
Reference in New Issue
Block a user