Files
fitness/backend/AuthenticationHandler.php
T
2026-10-04 00:19:45 +03:00

196 lines
5.5 KiB
PHP

<?php
/**
* Authentication & Security Handler
* HMAC-based request validation for API endpoints
*/
class AuthenticationHandler {
private $db;
private const SIGNATURE_ALGORITHM = 'sha256';
private const TIMESTAMP_TOLERANCE = 300; // 5 minutes in seconds
public function __construct() {
$this->db = Database::getInstance();
}
/**
* Validate HMAC signature of incoming request
*
* @param string $api_key The API key from request header
* @param string $signature The HMAC signature from request header
* @param string $payload The raw request body
* @param string $timestamp The request timestamp
* @return array ['valid' => bool, 'user_id' => int|null, 'error' => string|null]
*/
public function validateHmacSignature($api_key, $signature, $payload, $timestamp) {
// Validate timestamp to prevent replay attacks
if (!$this->isValidTimestamp($timestamp)) {
return [
'valid' => false,
'user_id' => null,
'error' => 'Request timestamp is invalid or expired'
];
}
// Get user by API key
$user = $this->getUserByApiKey($api_key);
if (!$user) {
// Log suspicious activity
$this->logSecurityEvent('INVALID_API_KEY', $api_key);
return [
'valid' => false,
'user_id' => null,
'error' => 'Invalid API key'
];
}
// Generate expected signature
$expectedSignature = $this->generateSignature(
$payload,
$user['api_secret'],
$timestamp,
$api_key
);
// Compare signatures using timing-safe comparison
if (!hash_equals($expectedSignature, $signature)) {
// Log failed authentication attempt
$this->logSecurityEvent('INVALID_SIGNATURE', $api_key, $user['id']);
return [
'valid' => false,
'user_id' => null,
'error' => 'Invalid signature'
];
}
// Check if user is active
if (!$user['is_active']) {
return [
'valid' => false,
'user_id' => null,
'error' => 'User account is inactive'
];
}
return [
'valid' => true,
'user_id' => $user['id'],
'error' => null
];
}
/**
* Generate HMAC signature
*
* Signature format: HMAC-SHA256(timestamp|payload, api_secret)
*/
private function generateSignature($payload, $api_secret, $timestamp, $api_key) {
$data = $timestamp . '|' . $api_key . '|' . $payload;
return hash_hmac(self::SIGNATURE_ALGORITHM, $data, $api_secret);
}
/**
* Verify timestamp is within acceptable range
*/
private function isValidTimestamp($timestamp) {
$current_time = time();
$request_time = (int)$timestamp;
$time_diff = abs($current_time - $request_time);
return $time_diff <= self::TIMESTAMP_TOLERANCE;
}
/**
* Get user by API key
*/
private function getUserByApiKey($api_key) {
$stmt = $this->db->prepare('
SELECT id, api_secret, is_active, uuid
FROM users
WHERE api_key = ?
LIMIT 1
');
$stmt->bind_param('s', $api_key);
$stmt->execute();
$result = $stmt->get_result();
if ($result->num_rows === 0) {
return null;
}
return $result->fetch_assoc();
}
/**
* Log security events for audit trail
*/
private function logSecurityEvent($event_type, $api_key, $user_id = null) {
$ip_address = $this->getClientIpAddress();
$user_agent = $_SERVER['HTTP_USER_AGENT'] ?? 'Unknown';
$stmt = $this->db->prepare('
INSERT INTO api_logs (user_id, endpoint, method, status_code, ip_address, user_agent, error_message, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, NOW())
');
$endpoint = $event_type;
$method = $_SERVER['REQUEST_METHOD'];
$status_code = 401;
$error_msg = $event_type;
$stmt->bind_param(
'issssss',
$user_id,
$endpoint,
$method,
$status_code,
$ip_address,
$user_agent,
$error_msg
);
$stmt->execute();
$stmt->close();
}
/**
* Get client IP address (handles proxies)
*/
private function getClientIpAddress() {
if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
return $_SERVER['HTTP_CLIENT_IP'];
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
return trim($ips[0]);
} else {
return $_SERVER['REMOTE_ADDR'] ?? 'Unknown';
}
}
/**
* Generate API key and secret for new user
*/
public static function generateApiCredentials() {
return [
'api_key' => bin2hex(random_bytes(32)),
'api_secret' => bin2hex(random_bytes(32))
];
}
/**
* Hash password using bcrypt
*/
public static function hashPassword($password) {
return password_hash($password, PASSWORD_BCRYPT, ['cost' => 12]);
}
/**
* Verify password
*/
public static function verifyPassword($password, $hash) {
return password_verify($password, $hash);
}
}
?>