129 lines
3.8 KiB
PHP
129 lines
3.8 KiB
PHP
<?php
|
|
|
|
class Auth {
|
|
private const JWT_ALGO = 'HS256';
|
|
private const JWT_EXPIRY = 3600; // 1 hour
|
|
|
|
private string $usersFile;
|
|
private string $jwtSecret;
|
|
|
|
public function __construct(string $usersFile = null) {
|
|
$this->usersFile = $usersFile ?? __DIR__ . '/../config/users.json';
|
|
|
|
// Load JWT secret from environment variable
|
|
$this->jwtSecret = $_ENV['JWT_SECRET'] ?? getenv('JWT_SECRET');
|
|
if (empty($this->jwtSecret)) {
|
|
throw new Exception('JWT_SECRET environment variable is not configured');
|
|
}
|
|
}
|
|
|
|
public function login(string $username, string $password): array {
|
|
$users = $this->loadUsers();
|
|
|
|
foreach ($users['users'] as $user) {
|
|
if ($user['username'] === $username) {
|
|
if (password_verify($password, $user['password'])) {
|
|
$token = $this->generateToken($user);
|
|
return [
|
|
'success' => true,
|
|
'token' => $token,
|
|
'user' => [
|
|
'username' => $user['username'],
|
|
'role' => $user['role']
|
|
]
|
|
];
|
|
}
|
|
return ['success' => false, 'error' => 'Invalid password'];
|
|
}
|
|
}
|
|
|
|
return ['success' => false, 'error' => 'User not found'];
|
|
}
|
|
|
|
public function verifyToken(string $token): ?array {
|
|
$parts = explode('.', $token);
|
|
|
|
if (count($parts) !== 3) {
|
|
return null;
|
|
}
|
|
|
|
list($header, $payload, $signature) = $parts;
|
|
|
|
// Verify signature
|
|
$expectedSignature = base64_encode(
|
|
hash_hmac('sha256', "$header.$payload", $this->jwtSecret, true)
|
|
);
|
|
|
|
if (!hash_equals($expectedSignature, $signature)) {
|
|
return null;
|
|
}
|
|
|
|
// Decode payload
|
|
$payloadData = json_decode(base64_decode($payload), true);
|
|
|
|
// Check expiry
|
|
if (isset($payloadData['exp']) && $payloadData['exp'] < time()) {
|
|
return null;
|
|
}
|
|
|
|
return $payloadData;
|
|
}
|
|
|
|
public function requireAuth(string $token): array {
|
|
$payload = $this->verifyToken($token);
|
|
|
|
if ($payload === null) {
|
|
http_response_code(401);
|
|
echo json_encode(['error' => 'Unauthorized']);
|
|
exit;
|
|
}
|
|
|
|
return $payload;
|
|
}
|
|
|
|
public function requireAdmin(string $token): array {
|
|
$payload = $this->requireAuth($token);
|
|
|
|
if ($payload['role'] !== 'admin') {
|
|
http_response_code(403);
|
|
echo json_encode(['error' => 'Forbidden']);
|
|
exit;
|
|
}
|
|
|
|
return $payload;
|
|
}
|
|
|
|
private function generateToken(array $user): string {
|
|
$header = base64_encode(json_encode([
|
|
'alg' => self::JWT_ALGO,
|
|
'typ' => 'JWT'
|
|
]));
|
|
|
|
$payload = base64_encode(json_encode([
|
|
'username' => $user['username'],
|
|
'role' => $user['role'],
|
|
'iat' => time(),
|
|
'exp' => time() + self::JWT_EXPIRY
|
|
]));
|
|
|
|
$signature = base64_encode(
|
|
hash_hmac('sha256', "$header.$payload", $this->jwtSecret, true)
|
|
);
|
|
|
|
return "$header.$payload.$signature";
|
|
}
|
|
|
|
private function loadUsers(): array {
|
|
if (!file_exists($this->usersFile)) {
|
|
return ['users' => []];
|
|
}
|
|
|
|
$content = file_get_contents($this->usersFile);
|
|
return json_decode($content, true) ?? ['users' => []];
|
|
}
|
|
|
|
public function hashPassword(string $password): string {
|
|
return password_hash($password, PASSWORD_BCRYPT);
|
|
}
|
|
}
|