odysseus-dev/odysseus · error · Error
Login failed
Error message
Login failed
What it means
Fallback error from doLogin() when POST /api/auth/login returns non-2xx. The server's data.detail is preferred (e.g. 'invalid credentials'); this literal appears only when the body lacks detail. The thrown error is caught by the outer try and rendered in the login form.
Source
Thrown at static/login.html:499
fetch('/api/auth/features', { credentials: 'same-origin' }).then(r => r.json()),
fetch('/api/auth/settings', { credentials: 'same-origin' }).then(r => r.json()),
]).then(([sess, feat, sett]) => {
sessionStorage.setItem('ody-prefetch-sessions', JSON.stringify(sess));
sessionStorage.setItem('ody-prefetch-features', JSON.stringify(feat));
sessionStorage.setItem('ody-prefetch-settings', JSON.stringify(sett));
}).catch(() => {}).finally(() => { window.location.replace('/'); });
}
async function doLogin(totpCode) {
const loginBody = { username, password, remember };
if (totpCode) loginBody.totp_code = totpCode;
const res = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'same-origin',
body: JSON.stringify(loginBody)
});
const data = await res.json();
if (!res.ok) throw new Error(data.detail || 'Login failed');
return data;
}
try {
let data = await doLogin();
// 2FA required — show TOTP input
if (data.requires_totp) {
submitBtn.disabled = false;
// Only add TOTP input once
if (document.getElementById('totp-input')) return;
form._totpMode = true;
const totpWrap = document.createElement('div');
totpWrap.style.cssText = 'margin-top:12px;';
totpWrap.innerHTML = '<label for="totp-input" style="font-size:0.85em;opacity:0.7;display:block;margin-bottom:4px;">2FA Code</label><input type="text" id="totp-input" placeholder="Enter 6-digit code" aria-label="Two-factor authentication code" autocomplete="one-time-code" inputmode="numeric" maxlength="8" style="width:100%;padding:10px 12px;background:var(--bg);color:var(--fg);border:1px solid var(--border);border-radius:8px;font-size:14px;box-sizing:border-box;text-align:center;letter-spacing:4px;">';
const formEl = submitBtn.parentElement;
formEl.insertBefore(totpWrap, submitBtn);
const totpInput = document.getElementById('totp-input');
totpInput.focus();
submitBtn.textContent = 'Verify';View on GitHub (pinned to f9235ebbf1)
Solutions
- Verify username and password; the specific server reason usually replaces this message
- If the generic message persists, inspect the raw response body/status in devtools (proxy or 5xx issue)
- Confirm the account exists and is not locked; reset password if needed
- Ensure cookies/JS are enabled so the login POST and session work
Defensive patterns
Strategy: try-catch
Try / catch
try { const data = await doLogin(); if (data.requires_totp) showTotpStep(); } catch (err) { errEl.textContent = err.message || 'Login failed'; submitBtn.disabled = false; } Prevention
- Read the server-provided detail text for the true failure cause
- Inspect the raw response when only the generic message shows (likely proxy/5xx)
- Handle requires_totp before treating the call as failed
When it happens
Trigger: Wrong username/password (backend returns detail like 'Invalid credentials'), account locked, or a non-JSON response (gateway error) leaving data.detail undefined.
Common situations: Typo in credentials; password changed elsewhere; expired/locked account; API fronted by a proxy that returns HTML errors.
Related errors
- Account creation failed
- detail || ('HTTP ' + res.status)
- d.error || 'Clear failed'
- Failed to save note
- Failed to trigger task (${res.status})
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/986de777e537449c.
Report an issue: GitHub.