- Implemented.

Rebuilt embed-example.html with inline, full-screen, and popup examples.
Fixed host-page/frame scrolling and contained reply navigation.
New logins now transactionally replace prior sessions; displaced clients 
return to login.
Added per-user session/IP locking with automatic SQLite migration.
Added modular Spam Control dashboard for locks, honeypot, and CAPTCHA 
settings.
Moved CAPTCHA controls out of Plans & Platform.
This commit is contained in:
Ty Clifford
2026-06-09 18:54:25 -04:00
parent 5ca55bf7c1
commit 36c488ca1e
12 changed files with 599 additions and 169 deletions
+2 -9
View File
@@ -64,15 +64,7 @@ function loginUser(): never {
jsonResponse(['error' => 'Invalid username or password'], 401);
}
if (getConfigVal('chat.session_lock_ip', true) || getConfigVal('chat.session_lock_cookie', true)) {
$cutoff = time() - ((int)getConfigVal('security.session_timeout_hours', 24) * 3600);
$existingStmt = $db->prepare("SELECT id,ip FROM sessions WHERE user_id=? AND last_active>?");
$existingStmt->execute([$user['id'], $cutoff]);
$existing = $existingStmt->fetch();
if ($existing && getConfigVal('chat.session_lock_ip', true) && $existing['ip'] !== clientIP()) {
jsonResponse(['error' => 'Already logged in from another location'], 403);
}
}
assertUserSessionLoginAllowed($db, (int)$user['id']);
if (!empty($user['two_factor_enabled']) && featureValue($user, 'email_2fa', false)) {
if (empty($user['email_verified_at']) || empty($user['email'])) {
@@ -109,6 +101,7 @@ function verifyTwoFactor(): never {
if ($row) $db->prepare("UPDATE login_challenges SET attempts=attempts+1 WHERE id=?")->execute([$challenge]);
jsonResponse(['error' => 'Invalid or expired login code'], 401);
}
assertUserSessionLoginAllowed($db, (int)$row['user_id']);
$db->prepare("DELETE FROM login_challenges WHERE id=?")->execute([$challenge]);
createUserSession($db, (int)$row['user_id']);
trackEvent('login_2fa', '/api/auth.php', (int)$row['user_id']);