Tighten auth defaults and register form behavior
This commit is contained in:
@@ -19,8 +19,7 @@
|
||||
<label for="authPassword">Password</label>
|
||||
<input id="authPassword" type="password" maxlength="64" autocomplete="current-password" />
|
||||
</div>
|
||||
<p id="authPolicyHintLogin" class="auth-hint"></p>
|
||||
<button id="showRegisterButton" type="button">Need an account? Register</button>
|
||||
<button id="showRegisterButton" type="button">Register</button>
|
||||
</section>
|
||||
<section id="registerView" class="auth-panel hidden">
|
||||
<h2>Register</h2>
|
||||
@@ -32,12 +31,16 @@
|
||||
<label for="registerPassword">Password</label>
|
||||
<input id="registerPassword" type="password" maxlength="64" autocomplete="new-password" />
|
||||
</div>
|
||||
<div class="auth-row">
|
||||
<label for="registerPasswordConfirm">Confirm Password</label>
|
||||
<input id="registerPasswordConfirm" type="password" maxlength="64" autocomplete="new-password" />
|
||||
</div>
|
||||
<div class="auth-row">
|
||||
<label for="registerEmail">Email (optional)</label>
|
||||
<input id="registerEmail" type="email" maxlength="320" autocomplete="email" />
|
||||
</div>
|
||||
<p id="authPolicyHintRegister" class="auth-hint"></p>
|
||||
<button id="showLoginButton" type="button">Have an account? Log in</button>
|
||||
<button id="showLoginButton" type="button">Login</button>
|
||||
</section>
|
||||
<div class="controls" id="button-container">
|
||||
<button id="connectButton">Connect</button>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Maintainer-controlled web client version.
|
||||
// Format: YYYY.MM.DD Rn (example: 2026.02.20 R2)
|
||||
window.CHGRID_WEB_VERSION = "2026.02.25 R247";
|
||||
window.CHGRID_WEB_VERSION = "2026.02.25 R248";
|
||||
// Optional display timezone for timestamps. Falls back to America/Detroit if unset/invalid.
|
||||
window.CHGRID_TIME_ZONE = "America/Detroit";
|
||||
|
||||
@@ -95,9 +95,9 @@ type Dom = {
|
||||
registerView: HTMLElement;
|
||||
authUsername: HTMLInputElement;
|
||||
authPassword: HTMLInputElement;
|
||||
authPolicyHintLogin: HTMLParagraphElement;
|
||||
registerUsername: HTMLInputElement;
|
||||
registerPassword: HTMLInputElement;
|
||||
registerPasswordConfirm: HTMLInputElement;
|
||||
registerEmail: HTMLInputElement;
|
||||
authPolicyHintRegister: HTMLParagraphElement;
|
||||
showRegisterButton: HTMLButtonElement;
|
||||
@@ -128,9 +128,9 @@ const dom: Dom = {
|
||||
registerView: requiredById('registerView'),
|
||||
authUsername: requiredById('authUsername'),
|
||||
authPassword: requiredById('authPassword'),
|
||||
authPolicyHintLogin: requiredById('authPolicyHintLogin'),
|
||||
registerUsername: requiredById('registerUsername'),
|
||||
registerPassword: requiredById('registerPassword'),
|
||||
registerPasswordConfirm: requiredById('registerPasswordConfirm'),
|
||||
registerEmail: requiredById('registerEmail'),
|
||||
authPolicyHintRegister: requiredById('authPolicyHintRegister'),
|
||||
showRegisterButton: requiredById('showRegisterButton'),
|
||||
@@ -551,9 +551,7 @@ function applyAuthPolicy(policy: unknown): void {
|
||||
passwordMaxLength: passwordMax,
|
||||
};
|
||||
localStorage.setItem(AUTH_POLICY_STORAGE_KEY, JSON.stringify(authPolicy));
|
||||
const hint = `Username: ${usernameMin}-${usernameMax} chars (a-z, 0-9, _, -). Password: ${passwordMin}-${passwordMax} chars.`;
|
||||
dom.authPolicyHintLogin.textContent = hint;
|
||||
dom.authPolicyHintRegister.textContent = hint;
|
||||
dom.authPolicyHintRegister.textContent = `Username, ${usernameMin}-${usernameMax} characters. Password, ${passwordMin}-${passwordMax} characters.`;
|
||||
dom.authUsername.minLength = usernameMin;
|
||||
dom.authUsername.maxLength = usernameMax;
|
||||
dom.registerUsername.minLength = usernameMin;
|
||||
@@ -562,6 +560,8 @@ function applyAuthPolicy(policy: unknown): void {
|
||||
dom.authPassword.maxLength = passwordMax;
|
||||
dom.registerPassword.minLength = passwordMin;
|
||||
dom.registerPassword.maxLength = passwordMax;
|
||||
dom.registerPasswordConfirm.minLength = passwordMin;
|
||||
dom.registerPasswordConfirm.maxLength = passwordMax;
|
||||
updateConnectAvailability();
|
||||
}
|
||||
|
||||
@@ -597,7 +597,8 @@ function updateConnectAvailability(): void {
|
||||
sanitizeAuthUsername(dom.authUsername.value).length >= usernameMin && dom.authPassword.value.trim().length >= passwordMin;
|
||||
const hasRegisterCredentials =
|
||||
sanitizeAuthUsername(dom.registerUsername.value).length >= usernameMin &&
|
||||
dom.registerPassword.value.trim().length >= passwordMin;
|
||||
dom.registerPassword.value.trim().length >= passwordMin &&
|
||||
dom.registerPassword.value === dom.registerPasswordConfirm.value;
|
||||
const authReady = hasSessionToken || (authMode === 'login' ? hasLoginCredentials : hasRegisterCredentials);
|
||||
dom.connectButton.textContent = hasSessionToken ? 'Connect' : authMode === 'login' ? 'Log In & Connect' : 'Register & Connect';
|
||||
dom.connectButton.disabled = mediaSession.isConnecting() || !authReady;
|
||||
@@ -1423,7 +1424,7 @@ function buildAuthRequestPacket(): OutgoingMessage | null {
|
||||
const username = sanitizeAuthUsername(dom.registerUsername.value);
|
||||
const password = dom.registerPassword.value;
|
||||
const email = dom.registerEmail.value.trim();
|
||||
if (!username || !password) return null;
|
||||
if (!username || !password || password !== dom.registerPasswordConfirm.value) return null;
|
||||
return { type: 'auth_register', username, password, ...(email ? { email } : {}) };
|
||||
}
|
||||
const username = sanitizeAuthUsername(dom.authUsername.value);
|
||||
@@ -1462,6 +1463,7 @@ async function handleAuthResult(message: Extract<IncomingMessage, { type: 'auth_
|
||||
if (!message.ok) {
|
||||
dom.authPassword.value = '';
|
||||
dom.registerPassword.value = '';
|
||||
dom.registerPasswordConfirm.value = '';
|
||||
if (message.message.toLowerCase().includes('session')) {
|
||||
authSessionToken = '';
|
||||
settings.saveAuthSessionToken('');
|
||||
@@ -1493,6 +1495,7 @@ async function handleAuthResult(message: Extract<IncomingMessage, { type: 'auth_
|
||||
}
|
||||
dom.authPassword.value = '';
|
||||
dom.registerPassword.value = '';
|
||||
dom.registerPasswordConfirm.value = '';
|
||||
setConnectionStatus('Authenticated. Joining world...');
|
||||
}
|
||||
|
||||
@@ -2672,6 +2675,9 @@ function setupUiHandlers(): void {
|
||||
dom.registerPassword.addEventListener('input', () => {
|
||||
updateConnectAvailability();
|
||||
});
|
||||
dom.registerPasswordConfirm.addEventListener('input', () => {
|
||||
updateConnectAvailability();
|
||||
});
|
||||
dom.registerEmail.addEventListener('input', () => {
|
||||
updateConnectAvailability();
|
||||
});
|
||||
|
||||
@@ -5,9 +5,6 @@ REPO_ROOT="${1:-/home/bestmidi/chgrid}"
|
||||
PUBLISH_DIR="${2:-/home/bestmidi/public_html/chgrid}"
|
||||
BASE_PATH="${3:-/chgrid/}"
|
||||
SERVICE_NAME="${4:-chat-grid.service}"
|
||||
if [[ -z "${SERVICE_NAME// }" || "$SERVICE_NAME" == "-" || "$SERVICE_NAME" == ".service" ]]; then
|
||||
SERVICE_NAME="chat-grid.service"
|
||||
fi
|
||||
|
||||
"$REPO_ROOT/deploy/scripts/deploy_client.sh" "$REPO_ROOT" "$PUBLISH_DIR" "$BASE_PATH"
|
||||
sudo systemctl restart "$SERVICE_NAME"
|
||||
|
||||
@@ -12,7 +12,6 @@ import re
|
||||
import secrets
|
||||
import sqlite3
|
||||
import time
|
||||
import uuid
|
||||
|
||||
|
||||
SESSION_TTL_MS = 14 * 24 * 60 * 60 * 1000
|
||||
@@ -111,16 +110,15 @@ class AuthService:
|
||||
if role not in {"user", "admin"}:
|
||||
raise AuthError("role must be user or admin.")
|
||||
now_ms = self.now_ms()
|
||||
user_id = str(uuid.uuid4())
|
||||
password_hash = self._hash_password(password)
|
||||
try:
|
||||
self._conn.execute(
|
||||
"""
|
||||
INSERT INTO users (
|
||||
id, username, password_hash, email, role, status, last_nickname, created_at_ms, updated_at_ms, last_login_at_ms
|
||||
) VALUES (?, ?, ?, ?, ?, 'active', NULL, ?, ?, ?)
|
||||
username, password_hash, email, role, status, last_nickname, created_at_ms, updated_at_ms, last_login_at_ms
|
||||
) VALUES (?, ?, ?, ?, 'active', NULL, ?, ?, ?)
|
||||
""",
|
||||
(user_id, normalized_username, password_hash, normalized_email, role, now_ms, now_ms, now_ms),
|
||||
(normalized_username, password_hash, normalized_email, role, now_ms, now_ms, now_ms),
|
||||
)
|
||||
self._conn.commit()
|
||||
except sqlite3.IntegrityError as exc:
|
||||
@@ -154,6 +152,16 @@ class AuthService:
|
||||
if not self._verify_password(password, user_row["password_hash"]):
|
||||
raise AuthError("Invalid username or password.")
|
||||
user = self._row_to_user(user_row)
|
||||
if not user.last_nickname:
|
||||
self.set_last_nickname(user.id, user.username)
|
||||
user = AuthUser(
|
||||
id=user.id,
|
||||
username=user.username,
|
||||
role=user.role,
|
||||
status=user.status,
|
||||
email=user.email,
|
||||
last_nickname=user.username,
|
||||
)
|
||||
self._conn.execute(
|
||||
"UPDATE users SET last_login_at_ms = ?, updated_at_ms = ? WHERE id = ?",
|
||||
(self.now_ms(), self.now_ms(), user.id),
|
||||
@@ -196,13 +204,23 @@ class AuthService:
|
||||
)
|
||||
self._conn.commit()
|
||||
user = AuthUser(
|
||||
id=row["user_id"],
|
||||
id=str(row["user_id"]),
|
||||
username=row["username"],
|
||||
role=row["role"],
|
||||
status=row["status"],
|
||||
email=row["email"],
|
||||
last_nickname=row["last_nickname"],
|
||||
)
|
||||
if not user.last_nickname:
|
||||
self.set_last_nickname(user.id, user.username)
|
||||
user = AuthUser(
|
||||
id=user.id,
|
||||
username=user.username,
|
||||
role=user.role,
|
||||
status=user.status,
|
||||
email=user.email,
|
||||
last_nickname=user.username,
|
||||
)
|
||||
return AuthSession(session_id=row["session_id"], token=cleaned, user=user)
|
||||
|
||||
def revoke(self, token: str) -> None:
|
||||
@@ -243,7 +261,7 @@ class AuthService:
|
||||
self._conn.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id TEXT PRIMARY KEY,
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
password_hash TEXT NOT NULL,
|
||||
email TEXT UNIQUE,
|
||||
@@ -259,8 +277,8 @@ class AuthService:
|
||||
self._conn.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS sessions (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id TEXT NOT NULL,
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
token_hash TEXT NOT NULL UNIQUE,
|
||||
created_at_ms INTEGER NOT NULL,
|
||||
last_seen_at_ms INTEGER NOT NULL,
|
||||
@@ -288,14 +306,14 @@ class AuthService:
|
||||
token_hash = self._hash_token(token)
|
||||
now_ms = self.now_ms()
|
||||
expires_at_ms = now_ms + SESSION_TTL_MS
|
||||
session_id = str(uuid.uuid4())
|
||||
self._conn.execute(
|
||||
"""
|
||||
INSERT INTO sessions (id, user_id, token_hash, created_at_ms, last_seen_at_ms, expires_at_ms, revoked_at_ms, ip, user_agent)
|
||||
VALUES (?, ?, ?, ?, ?, ?, NULL, NULL, NULL)
|
||||
INSERT INTO sessions (user_id, token_hash, created_at_ms, last_seen_at_ms, expires_at_ms, revoked_at_ms, ip, user_agent)
|
||||
VALUES (?, ?, ?, ?, ?, NULL, NULL, NULL)
|
||||
""",
|
||||
(session_id, user.id, token_hash, now_ms, now_ms, expires_at_ms),
|
||||
(user.id, token_hash, now_ms, now_ms, expires_at_ms),
|
||||
)
|
||||
session_id = str(self._conn.execute("SELECT last_insert_rowid() AS id").fetchone()["id"])
|
||||
self._conn.commit()
|
||||
return AuthSession(session_id=session_id, token=token, user=user)
|
||||
|
||||
@@ -315,7 +333,7 @@ class AuthService:
|
||||
"""Convert a DB row into AuthUser."""
|
||||
|
||||
return AuthUser(
|
||||
id=row["id"],
|
||||
id=str(row["id"]),
|
||||
username=row["username"],
|
||||
role=row["role"],
|
||||
status=row["status"],
|
||||
|
||||
Reference in New Issue
Block a user