mirror of
https://github.com/myronblair/tomtomgames-app
synced 2026-06-30 17:49:57 -05:00
210 lines
9.5 KiB
PHP
210 lines
9.5 KiB
PHP
<?php
|
||
require_once __DIR__ . '/db.php';
|
||
require_once __DIR__ . '/mailer.php';
|
||
|
||
function isLoggedIn(): bool {
|
||
return isset($_SESSION['user_id']) && !empty($_SESSION['user_id']);
|
||
}
|
||
|
||
|
||
function logoutUser(): void {
|
||
// Clear all session data
|
||
$_SESSION = [];
|
||
// Destroy session cookie
|
||
if (isset($_COOKIE[session_name()])) {
|
||
setcookie(session_name(), '', [
|
||
'expires' => time() - 3600,
|
||
'path' => '/',
|
||
'secure' => true,
|
||
'httponly' => true,
|
||
'samesite' => 'Lax',
|
||
]);
|
||
}
|
||
session_destroy();
|
||
}
|
||
|
||
function requireLogin(): void {
|
||
if (!isLoggedIn()) { header('Location: /'); exit; }
|
||
}
|
||
|
||
function requireAdmin(): void {
|
||
requireLogin();
|
||
if (empty($_SESSION['is_admin'])) {
|
||
header('Location: /admin/login.php'); exit;
|
||
}
|
||
}
|
||
|
||
function currentUser(): ?array {
|
||
if (!isLoggedIn()) return null;
|
||
$stmt = db()->prepare("SELECT * FROM users WHERE id = ?");
|
||
$stmt->execute([$_SESSION['user_id']]);
|
||
return $stmt->fetch() ?: null;
|
||
}
|
||
|
||
function loginUser(string $username, string $password): array {
|
||
$stmt = db()->prepare("SELECT * FROM users WHERE username = ? AND status = 'active'");
|
||
$stmt->execute([strtolower(trim($username))]);
|
||
$user = $stmt->fetch();
|
||
|
||
if (!$user || !password_verify($password, $user['password'])) {
|
||
logActivity('LOGIN_FAILED', null, null, 'auth', 0, 'Failed login: '.$username, '', 'security', '', '', 'warning');
|
||
return ['success' => false, 'error' => 'Invalid username or password.'];
|
||
}
|
||
|
||
if (!$user['email_verified'] && !$user['is_admin']) {
|
||
return ['success'=>false,'error'=>'Please verify your email address before logging in.','unverified'=>true,'email'=>$user['email']];
|
||
}
|
||
|
||
if ($user['is_admin'] && !$user['email_verified']) {
|
||
db()->prepare("UPDATE users SET email_verified=1 WHERE id=?")->execute([$user['id']]);
|
||
$user['email_verified'] = 1;
|
||
}
|
||
|
||
session_regenerate_id(true);
|
||
$_SESSION['user_id'] = $user['id'];
|
||
$_SESSION['username'] = $user['username'];
|
||
$_SESSION['alias'] = $user['alias'];
|
||
$_SESSION['is_admin'] = $user['is_admin'];
|
||
|
||
db()->prepare("UPDATE users SET last_login=NOW() WHERE id=?")->execute([$user['id']]);
|
||
logActivity('LOGIN_SUCCESS', (int)$user['id'], null, 'auth', (int)$user['id'], 'Login OK', '', 'auth', '', '', 'info');
|
||
|
||
return ['success' => true, 'user' => $user];
|
||
}
|
||
|
||
function initiateRegistration(string $username, string $password, string $alias, string $email, string $referralCode = ''): array {
|
||
$username = strtolower(trim($username));
|
||
$alias = trim($alias);
|
||
$email = strtolower(trim($email));
|
||
|
||
if (strlen($username) < 3 || strlen($username) > 50)
|
||
return ['success'=>false,'error'=>'Username must be 3–50 characters.'];
|
||
if (!preg_match('/^[a-z0-9_]+$/', $username))
|
||
return ['success'=>false,'error'=>'Username may only contain letters, numbers, and underscores.'];
|
||
if (strlen($password) < 6)
|
||
return ['success'=>false,'error'=>'Password must be at least 6 characters.'];
|
||
if (empty($alias))
|
||
return ['success'=>false,'error'=>'Alias is required.'];
|
||
if (empty($email) || !filter_var($email, FILTER_VALIDATE_EMAIL))
|
||
return ['success'=>false,'error'=>'A valid email address is required.'];
|
||
|
||
$s = db()->prepare("SELECT id FROM users WHERE username=?");
|
||
$s->execute([$username]);
|
||
if ($s->fetch()) return ['success'=>false,'error'=>'Username already taken.'];
|
||
|
||
$s = db()->prepare("SELECT id FROM users WHERE email=?");
|
||
$s->execute([$email]);
|
||
if ($s->fetch()) return ['success'=>false,'error'=>'An account with that email already exists.'];
|
||
|
||
$s = db()->prepare("SELECT id FROM pending_registrations WHERE username=? AND expires_at > NOW()");
|
||
$s->execute([$username]);
|
||
if ($s->fetch()) return ['success'=>false,'error'=>'Username already reserved. Try again in 24 hours.'];
|
||
|
||
$s = db()->prepare("SELECT id, token FROM pending_registrations WHERE email=? AND expires_at > NOW()");
|
||
$s->execute([$email]);
|
||
$existing = $s->fetch();
|
||
if ($existing) {
|
||
sendVerificationEmail($email, $alias, $existing['token']);
|
||
return ['success'=>true,'resent'=>true,'email'=>$email];
|
||
}
|
||
|
||
db()->prepare("DELETE FROM pending_registrations WHERE email=? OR (username=? AND expires_at <= NOW())")->execute([$email, $username]);
|
||
|
||
$referrerId = null;
|
||
if ($referralCode) {
|
||
$rs = db()->prepare("SELECT id FROM users WHERE referral_code=? AND status='active'");
|
||
$rs->execute([strtoupper(trim($referralCode))]);
|
||
$ru = $rs->fetch();
|
||
if ($ru) $referrerId = (int)$ru['id'];
|
||
}
|
||
|
||
$token = bin2hex(random_bytes(32));
|
||
$hash = password_hash($password, PASSWORD_BCRYPT, ['cost'=>8]);
|
||
$expiresAt = date('Y-m-d H:i:s', time() + VERIFY_TTL);
|
||
|
||
$stmt = db()->prepare("INSERT INTO pending_registrations (username,password,alias,email,token,referred_by,expires_at) VALUES (?,?,?,?,?,?,?)");
|
||
$stmt->execute([$username,$hash,$alias,$email,$token,$referrerId,$expiresAt]);
|
||
|
||
$sent = sendVerificationEmail($email, $alias, $token);
|
||
if (!$sent) {
|
||
return ['success'=>true,'email'=>$email,'mail_warning'=>true];
|
||
}
|
||
return ['success'=>true,'email'=>$email];
|
||
}
|
||
|
||
function verifyEmailToken(string $token): array {
|
||
$token = trim($token);
|
||
if (empty($token)) return ['success'=>false,'error'=>'Invalid verification link.'];
|
||
|
||
$stmt = db()->prepare("SELECT * FROM pending_registrations WHERE token=? AND expires_at > NOW()");
|
||
$stmt->execute([$token]);
|
||
$pending = $stmt->fetch();
|
||
if (!$pending) return ['success'=>false,'error'=>'Verification link is invalid or has expired.'];
|
||
|
||
db()->beginTransaction();
|
||
try {
|
||
$ins = db()->prepare("INSERT INTO users (username,password,alias,email,email_verified,status) VALUES (?,?,?,?,1,'active')");
|
||
$ins->execute([$pending['username'],$pending['password'],$pending['alias'],$pending['email']]);
|
||
$userId = db()->lastInsertId();
|
||
|
||
$code = strtoupper(substr(md5($userId.uniqid()),0,8));
|
||
db()->prepare("UPDATE users SET referral_code=? WHERE id=?")->execute([$code,$userId]);
|
||
|
||
if (!empty($pending['referred_by'])) {
|
||
try {
|
||
db()->prepare("INSERT IGNORE INTO referrals (referrer_id,referred_id,status) VALUES (?,?,'pending')")->execute([$pending['referred_by'],$userId]);
|
||
db()->prepare("UPDATE users SET referred_by=? WHERE id=?")->execute([$pending['referred_by'],$userId]);
|
||
} catch(Exception $e) {}
|
||
}
|
||
|
||
db()->prepare("DELETE FROM pending_registrations WHERE token=?")->execute([$token]);
|
||
db()->commit();
|
||
} catch(Exception $e) {
|
||
db()->rollBack();
|
||
return ['success'=>false,'error'=>'Account creation failed. Please try again.'];
|
||
}
|
||
|
||
return ['success'=>true,'user_id'=>$userId,'username'=>$pending['username'],'alias'=>$pending['alias']];
|
||
}
|
||
|
||
// ── Activity Logger ────────────────────────────────────────
|
||
function logActivity(string $action, ?int $userId=null, ?int $adminId=null, string $entityType='', int $entityId=0, string $detail='', string $ip='', string $category='general', string $oldValue='', string $newValue='', string $severity='info'): void {
|
||
try {
|
||
if (rand(1,100) <= 3) {
|
||
db()->exec("DELETE FROM activity_log WHERE created_at < DATE_SUB(NOW(), INTERVAL 90 DAY)");
|
||
}
|
||
$ip = $ip ?: ($_SERVER['REMOTE_ADDR'] ?? '');
|
||
$userAgent = substr($_SERVER['HTTP_USER_AGENT'] ?? '', 0, 300);
|
||
$page = substr($_SERVER['REQUEST_URI'] ?? '', 0, 200);
|
||
$sessionId = session_id() ?: '';
|
||
db()->prepare("INSERT INTO activity_log (user_id,admin_id,action,category,entity_type,entity_id,detail,old_value,new_value,ip,user_agent,page,session_id,severity) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)")
|
||
->execute([$userId,$adminId,substr($action,0,120),$category,$entityType,$entityId?:null,substr($detail,0,2000),substr($oldValue,0,2000),substr($newValue,0,2000),$ip,$userAgent,$page,$sessionId,$severity]);
|
||
} catch(Exception $e) {}
|
||
}
|
||
|
||
function logPlayerAction(string $action, int $userId, string $detail='', string $category='player', string $severity='info'): void {
|
||
logActivity($action,$userId,null,'user',$userId,$detail,'',$category,'','',$severity);
|
||
}
|
||
function logAdminAction(string $action, int $adminId, string $entityType='', int $entityId=0, string $detail='', string $old='', string $new='', string $severity='info'): void {
|
||
logActivity($action,null,$adminId,$entityType,$entityId,$detail,'','admin',$old,$new,$severity);
|
||
}
|
||
function logSecurityEvent(string $action, ?int $userId=null, string $detail='', string $severity='warning'): void {
|
||
logActivity($action,$userId,null,'security',0,$detail,'','security','','',$severity);
|
||
}
|
||
|
||
function getAppVersion(): string {
|
||
try {
|
||
$v = db()->query("SELECT version FROM app_version ORDER BY id DESC LIMIT 1")->fetchColumn();
|
||
return $v ?: '1.0.2';
|
||
} catch(Exception $e) { return '1.0.2'; }
|
||
}
|
||
|
||
// ── CSRF ───────────────────────────────────────────────────
|
||
function getCsrfToken(): string {
|
||
if (empty($_SESSION['csrf_token'])) $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||
return $_SESSION['csrf_token'];
|
||
}
|
||
function validateCsrfToken(string $token): bool {
|
||
return !empty($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], $token);
|
||
}
|