Sync current dev state
This commit is contained in:
@@ -750,4 +750,211 @@ class VAuth
|
||||
'token' => substr($token, 0, 8) . '...' // Log partial token for debugging
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate JWT token for API authentication
|
||||
* @param array $user User data
|
||||
* @param int|null $expiryTime Optional custom expiry time in seconds
|
||||
* @return string JWT token
|
||||
*/
|
||||
public function generateJWTToken($user, $expiryTime = null)
|
||||
{
|
||||
try {
|
||||
$expiryTime = $expiryTime ?? (24 * 60 * 60); // Default 24 hours
|
||||
|
||||
$header = json_encode([
|
||||
'typ' => 'JWT',
|
||||
'alg' => 'HS256'
|
||||
]);
|
||||
|
||||
$payload = json_encode([
|
||||
'user_id' => $user['user_id'],
|
||||
'username' => $user['username'],
|
||||
'email' => $user['email'],
|
||||
'role' => $user['role'] ?? 'member',
|
||||
'iat' => time(),
|
||||
'exp' => time() + $expiryTime
|
||||
]);
|
||||
|
||||
$headerEncoded = $this->base64UrlEncode($header);
|
||||
$payloadEncoded = $this->base64UrlEncode($payload);
|
||||
|
||||
$jwtSecret = getenv('JWT_SECRET') ?: (defined('JWT_SECRET') ? JWT_SECRET : 'change_this_jwt_secret');
|
||||
$signature = hash_hmac('sha256', $headerEncoded . '.' . $payloadEncoded, $jwtSecret, true);
|
||||
$signatureEncoded = $this->base64UrlEncode($signature);
|
||||
|
||||
return $headerEncoded . '.' . $payloadEncoded . '.' . $signatureEncoded;
|
||||
|
||||
} catch (Exception $e) {
|
||||
$this->logger->error('JWT generation failed', [
|
||||
'error' => $e->getMessage(),
|
||||
'user_id' => $user['user_id'] ?? 'unknown'
|
||||
]);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate JWT token
|
||||
* @param string $token JWT token
|
||||
* @return array|null User data or null if invalid
|
||||
*/
|
||||
public function validateJWTToken($token)
|
||||
{
|
||||
try {
|
||||
$parts = explode('.', $token);
|
||||
if (count($parts) !== 3) {
|
||||
$this->logger->logSecurityEvent('Invalid JWT format', ['token' => substr($token, 0, 20) . '...']);
|
||||
return null;
|
||||
}
|
||||
|
||||
list($headerEncoded, $payloadEncoded, $signatureProvided) = $parts;
|
||||
|
||||
// Verify signature
|
||||
$jwtSecret = getenv('JWT_SECRET') ?: (defined('JWT_SECRET') ? JWT_SECRET : 'change_this_jwt_secret');
|
||||
$expectedSignature = hash_hmac('sha256', $headerEncoded . '.' . $payloadEncoded, $jwtSecret, true);
|
||||
$expectedSignatureEncoded = $this->base64UrlEncode($expectedSignature);
|
||||
|
||||
if (!hash_equals($expectedSignatureEncoded, $signatureProvided)) {
|
||||
$this->logger->logSecurityEvent('JWT signature verification failed', []);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Decode payload
|
||||
$payload = json_decode($this->base64UrlDecode($payloadEncoded), true);
|
||||
|
||||
if (!$payload || !isset($payload['user_id'])) {
|
||||
$this->logger->logSecurityEvent('Invalid JWT payload', []);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check expiration
|
||||
if (isset($payload['exp']) && $payload['exp'] < time()) {
|
||||
$this->logger->logSecurityEvent('JWT token expired', ['user_id' => $payload['user_id']]);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Verify user exists and is active
|
||||
$sql = "SELECT `user_id`, `username`, `email`, `role`, `status`
|
||||
FROM `db_users`
|
||||
WHERE `user_id` = ? AND `status` = 'active'";
|
||||
$result = $this->db->dbConnection()->Execute($sql, [$payload['user_id']]);
|
||||
|
||||
if (!$result || $result->EOF) {
|
||||
$this->logger->logSecurityEvent('JWT user not found or inactive', ['user_id' => $payload['user_id']]);
|
||||
return null;
|
||||
}
|
||||
|
||||
$user = $result->fields;
|
||||
|
||||
return [
|
||||
'user_id' => $user['user_id'],
|
||||
'username' => $user['username'],
|
||||
'email' => $user['email'],
|
||||
'role' => $user['role']
|
||||
];
|
||||
|
||||
} catch (Exception $e) {
|
||||
$this->logger->error('JWT validation error', [
|
||||
'error' => $e->getMessage(),
|
||||
'token' => substr($token, 0, 20) . '...'
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Login with JWT token return (for API clients)
|
||||
* @param string $identifier Username or email
|
||||
* @param string $password Password
|
||||
* @param int|null $expiryTime Optional token expiry time
|
||||
* @return array Result with token and user data
|
||||
*/
|
||||
public function loginWithToken($identifier, $password, $expiryTime = null)
|
||||
{
|
||||
try {
|
||||
// Use regular login to validate credentials
|
||||
$loginResult = $this->login($identifier, $password, false);
|
||||
|
||||
if (!$loginResult['success']) {
|
||||
return $loginResult;
|
||||
}
|
||||
|
||||
// Generate JWT token
|
||||
$token = $this->generateJWTToken($loginResult['user'], $expiryTime);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => 'Login successful',
|
||||
'token' => $token,
|
||||
'token_type' => 'Bearer',
|
||||
'expires_in' => $expiryTime ?? (24 * 60 * 60),
|
||||
'user' => $loginResult['user']
|
||||
];
|
||||
|
||||
} catch (Exception $e) {
|
||||
$this->logger->error('Token login error', [
|
||||
'error' => $e->getMessage(),
|
||||
'identifier' => $identifier ?? 'unknown'
|
||||
]);
|
||||
return ['success' => false, 'message' => 'An error occurred during login'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate request via Bearer token (for API requests)
|
||||
* @param string|null $authHeader Authorization header value
|
||||
* @return array|null User data or null if not authenticated
|
||||
*/
|
||||
public function authenticateBearer($authHeader = null)
|
||||
{
|
||||
try {
|
||||
// Get Authorization header if not provided
|
||||
if ($authHeader === null) {
|
||||
$authHeader = $_SERVER['HTTP_AUTHORIZATION'] ?? $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ?? '';
|
||||
|
||||
// Apache mod_rewrite workaround
|
||||
if (empty($authHeader) && function_exists('apache_request_headers')) {
|
||||
$headers = apache_request_headers();
|
||||
$authHeader = $headers['Authorization'] ?? $headers['authorization'] ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($authHeader)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Extract Bearer token
|
||||
if (strpos($authHeader, 'Bearer ') === 0) {
|
||||
$token = substr($authHeader, 7);
|
||||
return $this->validateJWTToken($token);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
} catch (Exception $e) {
|
||||
$this->logger->error('Bearer authentication error', ['error' => $e->getMessage()]);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Base64 URL-safe encoding
|
||||
* @param string $data Data to encode
|
||||
* @return string Encoded data
|
||||
*/
|
||||
private function base64UrlEncode($data)
|
||||
{
|
||||
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
|
||||
}
|
||||
|
||||
/**
|
||||
* Base64 URL-safe decoding
|
||||
* @param string $data Data to decode
|
||||
* @return string Decoded data
|
||||
*/
|
||||
private function base64UrlDecode($data)
|
||||
{
|
||||
return base64_decode(strtr($data, '-_', '+/'));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user