fix(cms): stable bottom bar and self-save no longer trips the 409 lock
CI Pipeline with Test Management / 🧪 Run Tests & Generate Reports (push) Waiting to run
CI Pipeline with Test Management / 🐳 Docker Integration Tests (push) Blocked by required conditions
CI Pipeline with Test Management / 🏗️ Build Docker Image (push) Blocked by required conditions
CI Pipeline with Test Management / 📊 Generate Test Summary (push) Blocked by required conditions
Test Reporting & Gherkin Analysis / 🧪 Run Tests & Generate Reports (push) Waiting to run
Test Reporting & Gherkin Analysis / 📊 Analyze Test Coverage (push) Blocked by required conditions
Test Reporting & Gherkin Analysis / 🔄 Sync with Linear (push) Blocked by required conditions
Test Reporting & Gherkin Analysis / ⚡ Performance Monitoring (push) Blocked by required conditions
CI Pipeline with Test Management / 🧪 Run Tests & Generate Reports (push) Waiting to run
CI Pipeline with Test Management / 🐳 Docker Integration Tests (push) Blocked by required conditions
CI Pipeline with Test Management / 🏗️ Build Docker Image (push) Blocked by required conditions
CI Pipeline with Test Management / 📊 Generate Test Summary (push) Blocked by required conditions
Test Reporting & Gherkin Analysis / 🧪 Run Tests & Generate Reports (push) Waiting to run
Test Reporting & Gherkin Analysis / 📊 Analyze Test Coverage (push) Blocked by required conditions
Test Reporting & Gherkin Analysis / 🔄 Sync with Linear (push) Blocked by required conditions
Test Reporting & Gherkin Analysis / ⚡ Performance Monitoring (push) Blocked by required conditions
MITHOME-68: the optimistic-lock fingerprint was frozen at page load, so the user's own second save 409'd. /save now returns the hash of the written content and the client refreshes CONTENT_HASH on success — 409 only fires for genuine external changes (deploy, other tab, restore). Also: successful logins no longer consume the auth failure budget (only failed attempts do). MITHOME-69: bottom bar items no longer shift while saving/publishing — the status message occupies a constant flex slot (visibility instead of display), the publish button locks its width while running and restores its env-specific label, auto margins removed. Layout guard test added. Test markers are now run-unique so a crashed run can never poison the next one's expectations.
This commit is contained in:
+13
-7
@@ -134,12 +134,6 @@ const server = http.createServer(async (req, res) => {
|
||||
// Public: login form endpoint. Shares the auth rate-limit budget with failed
|
||||
// Basic attempts so the form cannot be brute-forced either.
|
||||
if (req.method === 'POST' && u.pathname === '/login') {
|
||||
if (exceedsRateLimit(`auth:${clientAddress}`, AUTH_MAX_ATTEMPTS)) {
|
||||
writeAudit('login_failed', { clientAddress, result: 'rate_limited' });
|
||||
res.writeHead(429, { 'Content-Type': 'application/json', 'Retry-After': String(RATE_LIMIT_WINDOW_MS / 1000) });
|
||||
res.end(JSON.stringify({ ok: false, error: 'Túl sok belépési kísérlet — próbáld újra később.' }));
|
||||
return;
|
||||
}
|
||||
let body = '';
|
||||
let bodyTooLarge = false;
|
||||
req.on('data', c => {
|
||||
@@ -155,12 +149,20 @@ const server = http.createServer(async (req, res) => {
|
||||
pass = String(parsed.pass || '');
|
||||
} catch { /* empty credentials fail validation below */ }
|
||||
if (!bodyTooLarge && validateLogin(user, pass, CMS_USER, CMS_PASS)) {
|
||||
// WHY: successful logins must not consume the failure budget — tests and
|
||||
// multi-tab users log in repeatedly and would lock themselves out.
|
||||
const isSecure = req.headers['x-forwarded-proto'] === 'https';
|
||||
writeAudit('login_success', { clientAddress });
|
||||
res.writeHead(200, { 'Content-Type': 'application/json', 'Set-Cookie': createSessionCookie(isSecure) });
|
||||
res.end(JSON.stringify({ ok: true }));
|
||||
return;
|
||||
}
|
||||
if (exceedsRateLimit(`auth:${clientAddress}`, AUTH_MAX_ATTEMPTS)) {
|
||||
writeAudit('login_failed', { clientAddress, result: 'rate_limited' });
|
||||
res.writeHead(429, { 'Content-Type': 'application/json', 'Retry-After': String(RATE_LIMIT_WINDOW_MS / 1000) });
|
||||
res.end(JSON.stringify({ ok: false, error: 'Túl sok belépési kísérlet — próbáld újra később.' }));
|
||||
return;
|
||||
}
|
||||
writeAudit('login_failed', { clientAddress, result: bodyTooLarge ? 'request_too_large' : 'invalid_credentials' });
|
||||
res.writeHead(401, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ ok: false, error: 'Hibás felhasználónév vagy jelszó.' }));
|
||||
@@ -263,9 +265,13 @@ const server = http.createServer(async (req, res) => {
|
||||
return;
|
||||
}
|
||||
const backupFile = backupAndWriteAtomically(FILES[activeFile], data, BACKUP_DIR);
|
||||
// Return the hash of the written content so the editor tab can refresh
|
||||
// its fingerprint — otherwise the user's OWN next save would trip the
|
||||
// optimistic-lock 409 (MITHOME-68).
|
||||
const newHash = crypto.createHash('sha256').update(JSON.stringify(data, null, 2).trim()).digest('hex');
|
||||
writeAudit('content_saved', { clientAddress, user: CMS_USER, file: activeFile, result: 'ok' });
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ ok: true, backup: path.relative(__dirname, backupFile) }));
|
||||
res.end(JSON.stringify({ ok: true, backup: path.relative(__dirname, backupFile), contentHash: newHash }));
|
||||
} catch (e) {
|
||||
writeAudit('content_saved', { clientAddress, user: CMS_USER, file: activeFile, result: 'error' });
|
||||
res.writeHead(500, { 'Content-Type': 'application/json' });
|
||||
|
||||
Reference in New Issue
Block a user