fix(auth): centralize password and username validation constants (#4120)

Added PASSWORD_MIN_LENGTH and RESERVED_USERNAMES to src/constants.py as the
single source of truth. Previously PASSWORD_MIN_LENGTH was hardcoded as 8 in
four route handlers and all three JS validation paths; RESERVED_USERNAMES was
an inline frozenset duplicated in core/auth.py, routes/assistant_routes.py,
routes/research_routes.py, and src/task_scheduler.py.

Added GET /api/auth/policy (unauthenticated) so the frontend reads the real
values from the server instead of hardcoding them in JS.

Added missing empty-username guard to /setup and admin POST /users. Both
returned a misleading 500/409 on whitespace-only input. /signup already had the
check; this makes all three consistent.
This commit is contained in:
Karl Jussila
2026-06-16 02:52:15 -05:00
committed by GitHub
parent 2b519bf355
commit ee72d71872
12 changed files with 327 additions and 30 deletions
+18 -5
View File
@@ -328,6 +328,7 @@
let mode = 'login'; // 'login' | 'signup' | 'setup'
let signupAllowed = false;
let policy = { password_min_length: 8, reserved_usernames: [] };
const rememberToggle = document.getElementById('rememberToggle');
@@ -360,10 +361,12 @@
}
}
// Check auth status
// Check auth status and fetch policy in parallel, but don't block the
// authenticated redirect on the policy response.
const policyPromise = fetch('/api/auth/policy', { credentials: 'same-origin' }).catch(() => null);
try {
const res = await fetch('/api/auth/status', { credentials: 'same-origin' });
const data = await res.json();
const statusRes = await fetch('/api/auth/status', { credentials: 'same-origin' });
const data = await statusRes.json();
if (data.authenticated) {
window.location.replace('/');
return;
@@ -374,6 +377,10 @@
} else {
setMode('login');
}
const policyRes = await policyPromise;
if (policyRes && policyRes.ok) {
policy = await policyRes.json();
}
} catch (e) {
setMode('login');
}
@@ -426,8 +433,14 @@
submitBtn.disabled = false;
return;
}
if (password.length < 8) {
errEl.textContent = 'Password must be at least 8 characters';
if (password.length < policy.password_min_length) {
errEl.textContent = `Password must be at least ${policy.password_min_length} characters`;
errEl.style.display = 'block';
submitBtn.disabled = false;
return;
}
if (policy.reserved_usernames.includes(username.toLowerCase())) {
errEl.textContent = 'This username is reserved';
errEl.style.display = 'block';
submitBtn.disabled = false;
return;