Add project files

This commit is contained in:
infosave2007
2025-11-07 13:34:06 +03:00
parent 3402b19f2c
commit a33af60f2d
41 changed files with 8128 additions and 0 deletions
+268
View File
@@ -0,0 +1,268 @@
<?php
/**
* JWT Authentication Helper
* Provides JWT token generation and validation for API authentication
*/
use Firebase\JWT\JWT as FirebaseJWT;
use Firebase\JWT\Key;
class JWT {
private static ?string $secretKey = null;
/**
* Get or generate JWT secret key
*/
private static function getSecretKey(): string {
if (self::$secretKey !== null) {
return self::$secretKey;
}
// Try to get from environment
$envKey = getenv('JWT_SECRET');
if ($envKey && strlen($envKey) >= 32) {
self::$secretKey = $envKey;
return self::$secretKey;
}
// Try to get from database settings
$pdo = DB::conn();
$stmt = $pdo->prepare('SELECT value FROM settings WHERE key = ?');
$stmt->execute(['jwt_secret']);
$result = $stmt->fetch();
if ($result && !empty($result['value'])) {
self::$secretKey = $result['value'];
return self::$secretKey;
}
// Generate new secret key and save it
$newKey = bin2hex(random_bytes(32));
$stmt = $pdo->prepare('INSERT INTO settings (key, value) VALUES (?, ?) ON DUPLICATE KEY UPDATE value = ?');
$stmt->execute(['jwt_secret', $newKey, $newKey]);
self::$secretKey = $newKey;
return self::$secretKey;
}
/**
* Generate JWT token for user
*
* @param int $userId User ID
* @param int $expiresIn Token lifetime in seconds (default: 30 days)
* @return string JWT token
*/
public static function generate(int $userId, int $expiresIn = 2592000): string {
$issuedAt = time();
$expire = $issuedAt + $expiresIn;
$payload = [
'iss' => 'amnezia-panel', // Issuer
'aud' => 'amnezia-api', // Audience
'iat' => $issuedAt, // Issued at
'exp' => $expire, // Expiration
'sub' => $userId, // Subject (user ID)
'jti' => bin2hex(random_bytes(16)) // JWT ID (unique token identifier)
];
return FirebaseJWT::encode($payload, self::getSecretKey(), 'HS256');
}
/**
* Validate and decode JWT token
*
* @param string $token JWT token
* @return object|null Decoded token payload or null if invalid
*/
public static function decode(string $token): ?object {
try {
$decoded = FirebaseJWT::decode($token, new Key(self::getSecretKey(), 'HS256'));
// Verify issuer and audience
if ($decoded->iss !== 'amnezia-panel' || $decoded->aud !== 'amnezia-api') {
return null;
}
return $decoded;
} catch (Exception $e) {
error_log('JWT decode error: ' . $e->getMessage());
return null;
}
}
/**
* Get user ID from JWT token
*
* @param string $token JWT token
* @return int|null User ID or null if invalid
*/
public static function getUserId(string $token): ?int {
$decoded = self::decode($token);
if ($decoded === null) {
return null;
}
return (int)$decoded->sub;
}
/**
* Verify JWT token and get user data
*
* @param string $token JWT token
* @return array|null User data or null if invalid
*/
public static function verify(string $token): ?array {
$userId = self::getUserId($token);
if ($userId === null) {
return null;
}
// Get user from database
$pdo = DB::conn();
$stmt = $pdo->prepare('SELECT id, name, email, role FROM users WHERE id = ?');
$stmt->execute([$userId]);
$user = $stmt->fetch();
return $user ?: null;
}
/**
* Extract token from Authorization header
*
* @return string|null Token or null if not found
*/
public static function getTokenFromHeader(): ?string {
// Try getallheaders() first (Apache/FPM)
if (function_exists('getallheaders')) {
$headers = getallheaders();
} else {
// Fallback for other environments (nginx, CLI)
$headers = [];
foreach ($_SERVER as $key => $value) {
if (strpos($key, 'HTTP_') === 0) {
$header = str_replace(' ', '-', ucwords(str_replace('_', ' ', strtolower(substr($key, 5)))));
$headers[$header] = $value;
}
}
}
// Check Authorization header
if (isset($headers['Authorization'])) {
$auth = $headers['Authorization'];
// Bearer token format: "Bearer {token}"
if (preg_match('/Bearer\s+(.+)/', $auth, $matches)) {
return $matches[1];
}
}
// Check X-API-Token header (alternative)
if (isset($headers['X-Api-Token'])) {
return $headers['X-Api-Token'];
}
// Also check direct $_SERVER access for Authorization
if (isset($_SERVER['HTTP_AUTHORIZATION'])) {
$auth = $_SERVER['HTTP_AUTHORIZATION'];
if (preg_match('/Bearer\s+(.+)/', $auth, $matches)) {
return $matches[1];
}
}
return null;
}
/**
* Middleware: Require JWT authentication for API endpoints
*
* @return array|null User data if authenticated, sends 401 response and returns null if not
*/
public static function requireAuth(): ?array {
$token = self::getTokenFromHeader();
if ($token === null) {
http_response_code(401);
header('Content-Type: application/json');
echo json_encode(['error' => 'Missing authentication token']);
return null;
}
$user = self::verify($token);
if ($user === null) {
http_response_code(401);
header('Content-Type: application/json');
echo json_encode(['error' => 'Invalid or expired token']);
return null;
}
return $user;
}
/**
* Create API token for user (saves to database)
*
* @param int $userId User ID
* @param string|null $name Token name/description
* @param int $expiresIn Token lifetime in seconds (default: 30 days)
* @return array Token data (id, token, expires_at)
*/
public static function createApiToken(int $userId, ?string $name = null, int $expiresIn = 2592000): array {
$token = self::generate($userId, $expiresIn);
$expiresAt = date('Y-m-d H:i:s', time() + $expiresIn);
$pdo = DB::conn();
$stmt = $pdo->prepare('
INSERT INTO api_tokens (user_id, token, name, expires_at)
VALUES (?, ?, ?, ?)
');
$stmt->execute([
$userId,
$token,
$name ?? 'API Token',
$expiresAt
]);
return [
'id' => (int)$pdo->lastInsertId(),
'token' => $token,
'name' => $name ?? 'API Token',
'expires_at' => $expiresAt
];
}
/**
* Revoke API token
*
* @param int $tokenId Token ID
* @param int $userId User ID (for ownership verification)
* @return bool Success
*/
public static function revokeApiToken(int $tokenId, int $userId): bool {
$pdo = DB::conn();
$stmt = $pdo->prepare('DELETE FROM api_tokens WHERE id = ? AND user_id = ?');
return $stmt->execute([$tokenId, $userId]);
}
/**
* Get all API tokens for user
*
* @param int $userId User ID
* @return array List of tokens
*/
public static function getUserTokens(int $userId): array {
$pdo = DB::conn();
$stmt = $pdo->prepare('
SELECT id, name, LEFT(token, 20) as token_preview, created_at, expires_at
FROM api_tokens
WHERE user_id = ? AND (expires_at IS NULL OR expires_at > NOW())
ORDER BY created_at DESC
');
$stmt->execute([$userId]);
return $stmt->fetchAll();
}
}