odysseus-dev/odysseus · error · HTTPException
Username already taken
Error message
Username already taken
What it means
Raised as HTTP 409 by the POST /signup handler when auth_manager.create_user() returns falsy. The auth manager refuses to create a user whose (lowercased) username already exists in its user store, so the signup is rejected as a conflict. This is a business-rule conflict, not a server failure.
Source
Thrown at routes/auth_routes.py:135
@router.post("/signup")
async def signup(body: SignupRequest, request: Request):
"""Create a new user account. Only works if signup is enabled by admin."""
if not _signup_limiter.check(request.client.host):
raise HTTPException(429, "Too many requests — try again later")
if not auth_manager.is_configured:
raise HTTPException(400, "Run setup first")
if not auth_manager.signup_enabled:
raise HTTPException(403, "Registration is disabled. Ask an admin for an account.")
if len(body.password) < PASSWORD_MIN_LENGTH:
raise HTTPException(400, f"Password must be at least {PASSWORD_MIN_LENGTH} characters")
if len(body.username.strip()) < 1:
raise HTTPException(400, "Username is required")
if body.username.lower() in RESERVED_USERNAMES:
raise HTTPException(403, "Username is reserved")
ok = await asyncio.to_thread(auth_manager.create_user, body.username, body.password, is_admin=False)
if not ok:
raise HTTPException(409, "Username already taken")
return {"ok": True, "message": "Account created"}
@router.post("/login")
async def login(body: LoginRequest, request: Request, response: Response):
if not _login_limiter.check(request.client.host):
raise HTTPException(429, "Too many requests — try again later")
# Verify password first
username = body.username.strip().lower()
if not await asyncio.to_thread(auth_manager.verify_password, username, body.password):
raise HTTPException(401, "Invalid credentials")
# Check 2FA if enabled
if auth_manager.totp_enabled(username):
if not body.totp_code:
# Password OK but need TOTP — tell client to show code input
return {"ok": False, "requires_totp": True, "username": username}
if not auth_manager.totp_verify(username, body.totp_code):
raise HTTPException(401, "Invalid 2FA code")
# All checks passed — create session (password already verified above)View on GitHub (pinned to f9235ebbf1)
Solutions
- Choose a different username and retry the signup request.
- If you already own the account, use POST /login instead of signing up again.
- If this was a double-submit, disable the submit button while the request is in flight.
- If you are an admin and must reuse the name, delete or rename the existing user via the admin /users endpoints first.
Example fix
// before
await fetch('/signup', {method:'POST', body: JSON.stringify({username:'alice', password:'...'})});
// after — handle the 409 conflict in the UI
const res = await fetch('/signup', {...});
if (res.status === 409) {
setError('That username is taken — pick another.');
return;
} Defensive patterns
Strategy: validation
Validate before calling
async function signup(username, password) {
// cheap pre-check: a taken name can be detected by attempting login? No —
// simplest: just handle 409. But avoid double-submit:
if (signup.inFlight) return;
signup.inFlight = true;
try {
const res = await fetch('/signup', {method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({username, password})});
if (res.status === 409) throw new Error('username-taken');
return res.json();
} finally { signup.inFlight = false; }
} Try / catch
try { await signup(u, p); } catch (e) { if (e.message === 'username-taken') showFieldError('username', 'This name is taken'); else throw e; } Prevention
- Disable the submit button while the signup request is in flight to prevent duplicate creates.
- Suggest alternative usernames in the UI when a 409 comes back.
- Treat 409 on retry-after-timeout as 'may have succeeded' — check via login before re-submitting.
When it happens
Trigger: Calling POST /signup with a username (case-insensitive) that already exists, after passing the earlier guards: setup configured, signup enabled, password >= PASSWORD_MIN_LENGTH, non-empty username, username not in RESERVED_USERNAMES.
Common situations: Double-submitting a signup form, retrying after a network timeout when the first request actually succeeded, picking a common username like 'admin' or 'test' that another user already claimed, or automated test suites re-running against a persistent user database.
Related errors
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/3e49fddfbb4dbda6.
Report an issue: GitHub.