- Created complete documentation in docs/ directory - Added PROJECT_OVERVIEW.md with feature highlights and getting started guide - Added ARCHITECTURE.md with system design and technical details - Added SECURITY.md with comprehensive security implementation guide - Added DEVELOPMENT.md with development workflows and best practices - Added DEPLOYMENT.md with production deployment instructions - Added API.md with complete REST API documentation - Added CONTRIBUTING.md with contribution guidelines - Added CHANGELOG.md with version history and migration notes - Reorganized all documentation files into docs/ directory for better organization - Updated README.md with proper documentation links and quick navigation - Enhanced project structure with professional documentation standards
753 lines
29 KiB
PHP
753 lines
29 KiB
PHP
<?php
|
|
/*******************************************************************************************************************
|
|
| Software Name : EasyStream
|
|
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
|
|
| Software Author : (c) Sami Ahmed
|
|
|*******************************************************************************************************************
|
|
|
|
|
|*******************************************************************************************************************
|
|
| This source file is subject to the EasyStream Proprietary License Agreement.
|
|
|
|
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|
|
|*******************************************************************************************************************
|
|
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|
|
|*******************************************************************************************************************/
|
|
|
|
defined('_ISVALID') or header('Location: /error');
|
|
|
|
/**
|
|
* Enhanced Authentication Class with Secure Session Management
|
|
*/
|
|
class VAuth
|
|
{
|
|
private static $instance = null;
|
|
private $db;
|
|
private $logger;
|
|
private $security;
|
|
private $redis;
|
|
|
|
// Session configuration
|
|
private $sessionTimeout = 3600; // 1 hour default
|
|
private $rememberMeTimeout = 2592000; // 30 days
|
|
private $maxLoginAttempts = 5;
|
|
private $lockoutDuration = 900; // 15 minutes
|
|
|
|
public static function getInstance()
|
|
{
|
|
if (self::$instance === null) {
|
|
self::$instance = new self();
|
|
}
|
|
return self::$instance;
|
|
}
|
|
|
|
private function __construct()
|
|
{
|
|
global $class_database;
|
|
|
|
$this->db = $class_database ?: new VDatabase();
|
|
$this->logger = VLogger::getInstance();
|
|
$this->security = VSecurity::getInstance();
|
|
$this->redis = $this->getRedisConnection();
|
|
|
|
// Configure secure session settings
|
|
$this->configureSession();
|
|
}
|
|
|
|
/**
|
|
* Configure secure session settings
|
|
*/
|
|
private function configureSession()
|
|
{
|
|
if (session_status() === PHP_SESSION_NONE) {
|
|
// Secure session configuration
|
|
ini_set('session.cookie_httponly', 1);
|
|
ini_set('session.cookie_secure', isset($_SERVER['HTTPS']) ? 1 : 0);
|
|
ini_set('session.cookie_samesite', 'Strict');
|
|
ini_set('session.use_strict_mode', 1);
|
|
ini_set('session.cookie_lifetime', 0); // Session cookies only
|
|
|
|
// Use Redis for session storage if available
|
|
if ($this->redis) {
|
|
ini_set('session.save_handler', 'redis');
|
|
$redisHost = getenv('REDIS_HOST') ?: 'redis';
|
|
$redisPort = getenv('REDIS_PORT') ?: 6379;
|
|
$redisDb = getenv('REDIS_DB') ?: 0;
|
|
ini_set('session.save_path', "tcp://{$redisHost}:{$redisPort}?database={$redisDb}");
|
|
}
|
|
|
|
session_start();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get Redis connection
|
|
* @return Redis|null
|
|
*/
|
|
private function getRedisConnection()
|
|
{
|
|
try {
|
|
if (!class_exists('Redis')) {
|
|
return null;
|
|
}
|
|
|
|
$redis = new Redis();
|
|
$host = getenv('REDIS_HOST') ?: 'redis';
|
|
$port = (int)(getenv('REDIS_PORT') ?: 6379);
|
|
$db = (int)(getenv('REDIS_DB') ?: 0);
|
|
|
|
if (!$redis->connect($host, $port, 2)) {
|
|
return null;
|
|
}
|
|
|
|
if ($db > 0) {
|
|
$redis->select($db);
|
|
}
|
|
|
|
return $redis;
|
|
|
|
} catch (Exception $e) {
|
|
$this->logger->warning('Redis connection failed for auth', ['error' => $e->getMessage()]);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Register a new user with email verification
|
|
* @param array $userData User registration data
|
|
* @return array Result with success status and message
|
|
*/
|
|
public function register($userData)
|
|
{
|
|
try {
|
|
// Validate required fields
|
|
$requiredFields = ['username', 'email', 'password'];
|
|
foreach ($requiredFields as $field) {
|
|
if (empty($userData[$field])) {
|
|
return ['success' => false, 'message' => "Field '{$field}' is required"];
|
|
}
|
|
}
|
|
|
|
// Sanitize and validate input
|
|
$username = VSecurity::validateInput($userData['username'], 'alphanum', null, ['min_length' => 3, 'max_length' => 50]);
|
|
$email = VSecurity::validateInput($userData['email'], 'email');
|
|
$password = $userData['password']; // Don't sanitize password
|
|
|
|
if (!$username) {
|
|
return ['success' => false, 'message' => 'Invalid username. Use 3-50 alphanumeric characters only.'];
|
|
}
|
|
|
|
if (!$email) {
|
|
return ['success' => false, 'message' => 'Invalid email address'];
|
|
}
|
|
|
|
if (strlen($password) < 8) {
|
|
return ['success' => false, 'message' => 'Password must be at least 8 characters long'];
|
|
}
|
|
|
|
// Check password strength
|
|
if (!$this->isPasswordStrong($password)) {
|
|
return ['success' => false, 'message' => 'Password must contain at least one uppercase letter, one lowercase letter, one number, and one special character'];
|
|
}
|
|
|
|
// Check if username or email already exists
|
|
if ($this->userExists($username, $email)) {
|
|
$this->logger->logSecurityEvent('Registration attempt with existing credentials', [
|
|
'username' => $username,
|
|
'email' => $email
|
|
]);
|
|
return ['success' => false, 'message' => 'Username or email already exists'];
|
|
}
|
|
|
|
// Generate verification token
|
|
$verificationToken = bin2hex(random_bytes(32));
|
|
|
|
// Hash password
|
|
$passwordHash = password_hash($password, PASSWORD_DEFAULT);
|
|
|
|
// Insert user into database
|
|
$sql = "INSERT INTO `db_users` (`username`, `email`, `password_hash`, `role`, `status`, `email_verified`, `verification_token`, `created_at`)
|
|
VALUES (?, ?, ?, 'member', 'active', 0, ?, NOW())";
|
|
|
|
$result = $this->db->dbConnection()->Execute($sql, [$username, $email, $passwordHash, $verificationToken]);
|
|
|
|
if (!$result) {
|
|
$this->logger->error('User registration failed', [
|
|
'username' => $username,
|
|
'email' => $email,
|
|
'error' => $this->db->dbConnection()->ErrorMsg()
|
|
]);
|
|
return ['success' => false, 'message' => 'Registration failed. Please try again.'];
|
|
}
|
|
|
|
$userId = $this->db->dbConnection()->Insert_ID();
|
|
|
|
// Send verification email
|
|
$this->sendVerificationEmail($email, $username, $verificationToken);
|
|
|
|
$this->logger->info('User registered successfully', [
|
|
'user_id' => $userId,
|
|
'username' => $username,
|
|
'email' => $email
|
|
]);
|
|
|
|
return [
|
|
'success' => true,
|
|
'message' => 'Registration successful! Please check your email to verify your account.',
|
|
'user_id' => $userId
|
|
];
|
|
|
|
} catch (Exception $e) {
|
|
$this->logger->error('Registration error', [
|
|
'error' => $e->getMessage(),
|
|
'trace' => $e->getTraceAsString()
|
|
]);
|
|
return ['success' => false, 'message' => 'An error occurred during registration'];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Verify email address
|
|
* @param string $token Verification token
|
|
* @return array Result with success status and message
|
|
*/
|
|
public function verifyEmail($token)
|
|
{
|
|
try {
|
|
$token = VSecurity::validateInput($token, 'alphanum');
|
|
if (!$token) {
|
|
return ['success' => false, 'message' => 'Invalid verification token'];
|
|
}
|
|
|
|
$sql = "SELECT `user_id`, `username`, `email` FROM `db_users` WHERE `verification_token` = ? AND `email_verified` = 0";
|
|
$result = $this->db->dbConnection()->Execute($sql, [$token]);
|
|
|
|
if (!$result || $result->EOF) {
|
|
$this->logger->logSecurityEvent('Invalid email verification attempt', ['token' => $token]);
|
|
return ['success' => false, 'message' => 'Invalid or expired verification token'];
|
|
}
|
|
|
|
$userData = $result->fields;
|
|
|
|
// Update user as verified
|
|
$updateSql = "UPDATE `db_users` SET `email_verified` = 1, `verification_token` = NULL, `updated_at` = NOW() WHERE `user_id` = ?";
|
|
$updateResult = $this->db->dbConnection()->Execute($updateSql, [$userData['user_id']]);
|
|
|
|
if (!$updateResult) {
|
|
return ['success' => false, 'message' => 'Verification failed. Please try again.'];
|
|
}
|
|
|
|
$this->logger->info('Email verified successfully', [
|
|
'user_id' => $userData['user_id'],
|
|
'username' => $userData['username'],
|
|
'email' => $userData['email']
|
|
]);
|
|
|
|
return [
|
|
'success' => true,
|
|
'message' => 'Email verified successfully! You can now log in.',
|
|
'user_id' => $userData['user_id']
|
|
];
|
|
|
|
} catch (Exception $e) {
|
|
$this->logger->error('Email verification error', [
|
|
'error' => $e->getMessage(),
|
|
'token' => $token ?? 'unknown'
|
|
]);
|
|
return ['success' => false, 'message' => 'An error occurred during verification'];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Authenticate user login
|
|
* @param string $identifier Username or email
|
|
* @param string $password Password
|
|
* @param bool $rememberMe Remember me option
|
|
* @return array Result with success status and user data
|
|
*/
|
|
public function login($identifier, $password, $rememberMe = false)
|
|
{
|
|
try {
|
|
// Rate limiting for login attempts
|
|
$clientIP = $this->getClientIP();
|
|
$rateLimitKey = "login_attempts_{$clientIP}";
|
|
|
|
if (!VSecurity::checkRateLimit($rateLimitKey, $this->maxLoginAttempts, $this->lockoutDuration, 'login')) {
|
|
$this->logger->logSecurityEvent('Login rate limit exceeded', [
|
|
'ip' => $clientIP,
|
|
'identifier' => $identifier
|
|
]);
|
|
return ['success' => false, 'message' => 'Too many login attempts. Please try again later.'];
|
|
}
|
|
|
|
// Validate input
|
|
$identifier = VSecurity::validateInput($identifier, 'string', null, ['max_length' => 255]);
|
|
if (!$identifier || !$password) {
|
|
return ['success' => false, 'message' => 'Username/email and password are required'];
|
|
}
|
|
|
|
// Find user by username or email
|
|
$user = $this->findUserByIdentifier($identifier);
|
|
if (!$user) {
|
|
$this->logger->logSecurityEvent('Login attempt with non-existent user', [
|
|
'identifier' => $identifier,
|
|
'ip' => $clientIP
|
|
]);
|
|
return ['success' => false, 'message' => 'Invalid credentials'];
|
|
}
|
|
|
|
// Check if account is active
|
|
if ($user['status'] !== 'active') {
|
|
$this->logger->logSecurityEvent('Login attempt with inactive account', [
|
|
'user_id' => $user['user_id'],
|
|
'username' => $user['username'],
|
|
'status' => $user['status']
|
|
]);
|
|
return ['success' => false, 'message' => 'Account is not active'];
|
|
}
|
|
|
|
// Verify password
|
|
if (!password_verify($password, $user['password_hash'])) {
|
|
$this->logger->logSecurityEvent('Failed login attempt', [
|
|
'user_id' => $user['user_id'],
|
|
'username' => $user['username'],
|
|
'ip' => $clientIP
|
|
]);
|
|
return ['success' => false, 'message' => 'Invalid credentials'];
|
|
}
|
|
|
|
// Check if email is verified (optional based on configuration)
|
|
global $cfg;
|
|
$requireEmailVerification = $cfg['require_email_verification'] ?? true;
|
|
if ($requireEmailVerification && !$user['email_verified']) {
|
|
return ['success' => false, 'message' => 'Please verify your email address before logging in'];
|
|
}
|
|
|
|
// Create session
|
|
$sessionResult = $this->createSession($user, $rememberMe);
|
|
if (!$sessionResult['success']) {
|
|
return $sessionResult;
|
|
}
|
|
|
|
// Update last login
|
|
$this->updateLastLogin($user['user_id']);
|
|
|
|
$this->logger->info('User logged in successfully', [
|
|
'user_id' => $user['user_id'],
|
|
'username' => $user['username'],
|
|
'ip' => $clientIP,
|
|
'remember_me' => $rememberMe
|
|
]);
|
|
|
|
return [
|
|
'success' => true,
|
|
'message' => 'Login successful',
|
|
'user' => [
|
|
'user_id' => $user['user_id'],
|
|
'username' => $user['username'],
|
|
'email' => $user['email'],
|
|
'role' => $user['role'],
|
|
'email_verified' => $user['email_verified']
|
|
]
|
|
];
|
|
|
|
} catch (Exception $e) {
|
|
$this->logger->error('Login error', [
|
|
'error' => $e->getMessage(),
|
|
'identifier' => $identifier ?? 'unknown'
|
|
]);
|
|
return ['success' => false, 'message' => 'An error occurred during login'];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create secure session
|
|
* @param array $user User data
|
|
* @param bool $rememberMe Remember me option
|
|
* @return array Result with success status
|
|
*/
|
|
private function createSession($user, $rememberMe = false)
|
|
{
|
|
try {
|
|
// Regenerate session ID to prevent session fixation
|
|
session_regenerate_id(true);
|
|
|
|
// Set session data
|
|
$_SESSION['USER_ID'] = $user['user_id'];
|
|
$_SESSION['USERNAME'] = $user['username'];
|
|
$_SESSION['EMAIL'] = $user['email'];
|
|
$_SESSION['ROLE'] = $user['role'];
|
|
$_SESSION['LOGIN_TIME'] = time();
|
|
$_SESSION['LAST_ACTIVITY'] = time();
|
|
$_SESSION['IP_ADDRESS'] = $this->getClientIP();
|
|
$_SESSION['USER_AGENT'] = $_SERVER['HTTP_USER_AGENT'] ?? '';
|
|
$_SESSION['SESSION_TOKEN'] = bin2hex(random_bytes(32));
|
|
|
|
// Store session in database
|
|
$sessionId = session_id();
|
|
$expiresAt = date('Y-m-d H:i:s', time() + ($rememberMe ? $this->rememberMeTimeout : $this->sessionTimeout));
|
|
|
|
$sql = "INSERT INTO `db_sessions` (`session_id`, `user_id`, `ip_address`, `user_agent`, `created_at`, `expires_at`, `remember_me`)
|
|
VALUES (?, ?, ?, ?, NOW(), ?, ?)
|
|
ON DUPLICATE KEY UPDATE
|
|
`ip_address` = VALUES(`ip_address`),
|
|
`user_agent` = VALUES(`user_agent`),
|
|
`expires_at` = VALUES(`expires_at`),
|
|
`remember_me` = VALUES(`remember_me`)";
|
|
|
|
$result = $this->db->dbConnection()->Execute($sql, [
|
|
$sessionId,
|
|
$user['user_id'],
|
|
$_SESSION['IP_ADDRESS'],
|
|
$_SESSION['USER_AGENT'],
|
|
$expiresAt,
|
|
$rememberMe ? 1 : 0
|
|
]);
|
|
|
|
if (!$result) {
|
|
$this->logger->error('Session creation failed', [
|
|
'user_id' => $user['user_id'],
|
|
'error' => $this->db->dbConnection()->ErrorMsg()
|
|
]);
|
|
return ['success' => false, 'message' => 'Session creation failed'];
|
|
}
|
|
|
|
// Set remember me cookie if requested
|
|
if ($rememberMe) {
|
|
$rememberToken = bin2hex(random_bytes(32));
|
|
$cookieExpiry = time() + $this->rememberMeTimeout;
|
|
|
|
setcookie('remember_token', $rememberToken, $cookieExpiry, '/', '', isset($_SERVER['HTTPS']), true);
|
|
|
|
// Store remember token in database
|
|
$tokenSql = "UPDATE `db_users` SET `remember_token` = ?, `remember_expires` = ? WHERE `user_id` = ?";
|
|
$this->db->dbConnection()->Execute($tokenSql, [
|
|
password_hash($rememberToken, PASSWORD_DEFAULT),
|
|
date('Y-m-d H:i:s', $cookieExpiry),
|
|
$user['user_id']
|
|
]);
|
|
}
|
|
|
|
return ['success' => true];
|
|
|
|
} catch (Exception $e) {
|
|
$this->logger->error('Session creation error', [
|
|
'error' => $e->getMessage(),
|
|
'user_id' => $user['user_id'] ?? 'unknown'
|
|
]);
|
|
return ['success' => false, 'message' => 'Session creation failed'];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Logout user and destroy session
|
|
* @return array Result with success status
|
|
*/
|
|
public function logout()
|
|
{
|
|
try {
|
|
$userId = $_SESSION['USER_ID'] ?? null;
|
|
$sessionId = session_id();
|
|
|
|
// Remove session from database
|
|
if ($sessionId) {
|
|
$sql = "DELETE FROM `db_sessions` WHERE `session_id` = ?";
|
|
$this->db->dbConnection()->Execute($sql, [$sessionId]);
|
|
}
|
|
|
|
// Clear remember me cookie and token
|
|
if (isset($_COOKIE['remember_token'])) {
|
|
setcookie('remember_token', '', time() - 3600, '/', '', isset($_SERVER['HTTPS']), true);
|
|
|
|
if ($userId) {
|
|
$tokenSql = "UPDATE `db_users` SET `remember_token` = NULL, `remember_expires` = NULL WHERE `user_id` = ?";
|
|
$this->db->dbConnection()->Execute($tokenSql, [$userId]);
|
|
}
|
|
}
|
|
|
|
// Destroy session
|
|
session_unset();
|
|
session_destroy();
|
|
|
|
$this->logger->info('User logged out successfully', [
|
|
'user_id' => $userId,
|
|
'session_id' => $sessionId
|
|
]);
|
|
|
|
return ['success' => true, 'message' => 'Logged out successfully'];
|
|
|
|
} catch (Exception $e) {
|
|
$this->logger->error('Logout error', [
|
|
'error' => $e->getMessage(),
|
|
'user_id' => $_SESSION['USER_ID'] ?? 'unknown'
|
|
]);
|
|
return ['success' => false, 'message' => 'An error occurred during logout'];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if user is authenticated
|
|
* @return bool True if authenticated
|
|
*/
|
|
public function isAuthenticated()
|
|
{
|
|
if (!isset($_SESSION['USER_ID']) || !isset($_SESSION['SESSION_TOKEN'])) {
|
|
return false;
|
|
}
|
|
|
|
// Check session timeout
|
|
$lastActivity = $_SESSION['LAST_ACTIVITY'] ?? 0;
|
|
if (time() - $lastActivity > $this->sessionTimeout) {
|
|
$this->logout();
|
|
return false;
|
|
}
|
|
|
|
// Update last activity
|
|
$_SESSION['LAST_ACTIVITY'] = time();
|
|
|
|
// Verify session in database
|
|
$sessionId = session_id();
|
|
$sql = "SELECT `user_id` FROM `db_sessions` WHERE `session_id` = ? AND `expires_at` > NOW()";
|
|
$result = $this->db->dbConnection()->Execute($sql, [$sessionId]);
|
|
|
|
if (!$result || $result->EOF) {
|
|
$this->logout();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Get current authenticated user
|
|
* @return array|null User data or null if not authenticated
|
|
*/
|
|
public function getCurrentUser()
|
|
{
|
|
if (!$this->isAuthenticated()) {
|
|
return null;
|
|
}
|
|
|
|
return [
|
|
'user_id' => $_SESSION['USER_ID'],
|
|
'username' => $_SESSION['USERNAME'],
|
|
'email' => $_SESSION['EMAIL'],
|
|
'role' => $_SESSION['ROLE']
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Request password reset
|
|
* @param string $email Email address
|
|
* @return array Result with success status and message
|
|
*/
|
|
public function requestPasswordReset($email)
|
|
{
|
|
try {
|
|
$email = VSecurity::validateInput($email, 'email');
|
|
if (!$email) {
|
|
return ['success' => false, 'message' => 'Invalid email address'];
|
|
}
|
|
|
|
// Rate limiting for password reset requests
|
|
$rateLimitKey = "password_reset_{$email}";
|
|
if (!VSecurity::checkRateLimit($rateLimitKey, 3, 3600, 'password_reset')) {
|
|
return ['success' => false, 'message' => 'Too many password reset requests. Please try again later.'];
|
|
}
|
|
|
|
// Find user by email
|
|
$sql = "SELECT `user_id`, `username`, `email` FROM `db_users` WHERE `email` = ? AND `status` = 'active'";
|
|
$result = $this->db->dbConnection()->Execute($sql, [$email]);
|
|
|
|
if (!$result || $result->EOF) {
|
|
// Don't reveal if email exists or not
|
|
return ['success' => true, 'message' => 'If the email exists, a password reset link has been sent.'];
|
|
}
|
|
|
|
$user = $result->fields;
|
|
|
|
// Generate reset token
|
|
$resetToken = bin2hex(random_bytes(32));
|
|
$expiresAt = date('Y-m-d H:i:s', time() + 3600); // 1 hour expiry
|
|
|
|
// Store reset token
|
|
$tokenSql = "UPDATE `db_users` SET `reset_token` = ?, `reset_expires` = ? WHERE `user_id` = ?";
|
|
$tokenResult = $this->db->dbConnection()->Execute($tokenSql, [$resetToken, $expiresAt, $user['user_id']]);
|
|
|
|
if (!$tokenResult) {
|
|
return ['success' => false, 'message' => 'Failed to generate reset token'];
|
|
}
|
|
|
|
// Send reset email
|
|
$this->sendPasswordResetEmail($user['email'], $user['username'], $resetToken);
|
|
|
|
$this->logger->info('Password reset requested', [
|
|
'user_id' => $user['user_id'],
|
|
'email' => $email
|
|
]);
|
|
|
|
return ['success' => true, 'message' => 'If the email exists, a password reset link has been sent.'];
|
|
|
|
} catch (Exception $e) {
|
|
$this->logger->error('Password reset request error', [
|
|
'error' => $e->getMessage(),
|
|
'email' => $email ?? 'unknown'
|
|
]);
|
|
return ['success' => false, 'message' => 'An error occurred while processing your request'];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reset password with token
|
|
* @param string $token Reset token
|
|
* @param string $newPassword New password
|
|
* @return array Result with success status and message
|
|
*/
|
|
public function resetPassword($token, $newPassword)
|
|
{
|
|
try {
|
|
$token = VSecurity::validateInput($token, 'alphanum');
|
|
if (!$token) {
|
|
return ['success' => false, 'message' => 'Invalid reset token'];
|
|
}
|
|
|
|
if (strlen($newPassword) < 8) {
|
|
return ['success' => false, 'message' => 'Password must be at least 8 characters long'];
|
|
}
|
|
|
|
if (!$this->isPasswordStrong($newPassword)) {
|
|
return ['success' => false, 'message' => 'Password must contain at least one uppercase letter, one lowercase letter, one number, and one special character'];
|
|
}
|
|
|
|
// Find user by reset token
|
|
$sql = "SELECT `user_id`, `username`, `email` FROM `db_users`
|
|
WHERE `reset_token` = ? AND `reset_expires` > NOW() AND `status` = 'active'";
|
|
$result = $this->db->dbConnection()->Execute($sql, [$token]);
|
|
|
|
if (!$result || $result->EOF) {
|
|
$this->logger->logSecurityEvent('Invalid password reset attempt', ['token' => $token]);
|
|
return ['success' => false, 'message' => 'Invalid or expired reset token'];
|
|
}
|
|
|
|
$user = $result->fields;
|
|
|
|
// Hash new password
|
|
$passwordHash = password_hash($newPassword, PASSWORD_DEFAULT);
|
|
|
|
// Update password and clear reset token
|
|
$updateSql = "UPDATE `db_users` SET
|
|
`password_hash` = ?,
|
|
`reset_token` = NULL,
|
|
`reset_expires` = NULL,
|
|
`updated_at` = NOW()
|
|
WHERE `user_id` = ?";
|
|
|
|
$updateResult = $this->db->dbConnection()->Execute($updateSql, [$passwordHash, $user['user_id']]);
|
|
|
|
if (!$updateResult) {
|
|
return ['success' => false, 'message' => 'Failed to update password'];
|
|
}
|
|
|
|
// Invalidate all existing sessions for this user
|
|
$this->invalidateUserSessions($user['user_id']);
|
|
|
|
$this->logger->info('Password reset successfully', [
|
|
'user_id' => $user['user_id'],
|
|
'username' => $user['username']
|
|
]);
|
|
|
|
return ['success' => true, 'message' => 'Password reset successfully. Please log in with your new password.'];
|
|
|
|
} catch (Exception $e) {
|
|
$this->logger->error('Password reset error', [
|
|
'error' => $e->getMessage(),
|
|
'token' => $token ?? 'unknown'
|
|
]);
|
|
return ['success' => false, 'message' => 'An error occurred while resetting your password'];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper methods
|
|
*/
|
|
|
|
private function userExists($username, $email)
|
|
{
|
|
$sql = "SELECT COUNT(*) as count FROM `db_users` WHERE `username` = ? OR `email` = ?";
|
|
$result = $this->db->dbConnection()->Execute($sql, [$username, $email]);
|
|
return $result && !$result->EOF && $result->fields['count'] > 0;
|
|
}
|
|
|
|
private function isPasswordStrong($password)
|
|
{
|
|
return preg_match('/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]/', $password);
|
|
}
|
|
|
|
private function findUserByIdentifier($identifier)
|
|
{
|
|
$sql = "SELECT * FROM `db_users` WHERE (`username` = ? OR `email` = ?) AND `status` != 'deleted'";
|
|
$result = $this->db->dbConnection()->Execute($sql, [$identifier, $identifier]);
|
|
return ($result && !$result->EOF) ? $result->fields : null;
|
|
}
|
|
|
|
private function updateLastLogin($userId)
|
|
{
|
|
$sql = "UPDATE `db_users` SET `last_login` = NOW() WHERE `user_id` = ?";
|
|
$this->db->dbConnection()->Execute($sql, [$userId]);
|
|
}
|
|
|
|
private function invalidateUserSessions($userId)
|
|
{
|
|
$sql = "DELETE FROM `db_sessions` WHERE `user_id` = ?";
|
|
$this->db->dbConnection()->Execute($sql, [$userId]);
|
|
}
|
|
|
|
private function getClientIP()
|
|
{
|
|
$ipKeys = [
|
|
'HTTP_CF_CONNECTING_IP',
|
|
'HTTP_X_FORWARDED_FOR',
|
|
'HTTP_X_FORWARDED',
|
|
'HTTP_X_CLUSTER_CLIENT_IP',
|
|
'HTTP_FORWARDED_FOR',
|
|
'HTTP_FORWARDED',
|
|
'REMOTE_ADDR'
|
|
];
|
|
|
|
foreach ($ipKeys as $key) {
|
|
if (array_key_exists($key, $_SERVER) === true) {
|
|
$ip = $_SERVER[$key];
|
|
if (strpos($ip, ',') !== false) {
|
|
$ip = explode(',', $ip)[0];
|
|
}
|
|
$ip = trim($ip);
|
|
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
|
|
return $ip;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $_SERVER['REMOTE_ADDR'] ?? 'unknown';
|
|
}
|
|
|
|
private function sendVerificationEmail($email, $username, $token)
|
|
{
|
|
// Implementation depends on your email system
|
|
// This is a placeholder for the email sending logic
|
|
$this->logger->info('Verification email sent', [
|
|
'email' => $email,
|
|
'username' => $username,
|
|
'token' => substr($token, 0, 8) . '...' // Log partial token for debugging
|
|
]);
|
|
}
|
|
|
|
private function sendPasswordResetEmail($email, $username, $token)
|
|
{
|
|
// Implementation depends on your email system
|
|
// This is a placeholder for the email sending logic
|
|
$this->logger->info('Password reset email sent', [
|
|
'email' => $email,
|
|
'username' => $username,
|
|
'token' => substr($token, 0, 8) . '...' // Log partial token for debugging
|
|
]);
|
|
}
|
|
} |