mirror of
https://github.com/ceratic/MediaCollectorLibary.git
synced 2026-05-13 23:56:46 +02:00
- Added JWT authentication support in AuthService and JwtService. - Implemented token generation and refresh mechanisms. - Updated ApiAuthMiddleware to handle authentication for protected routes. - Created ApiController and BaseApiController for standardized API responses. - Developed MediaController for managing media items with pagination and search capabilities. - Introduced DocsController for serving API documentation via Swagger UI. - Added routes for API documentation and media management. - Improved error handling and response formatting across API endpoints. - Updated composer.json to include necessary JWT and Swagger UI dependencies.
223 lines
5.9 KiB
PHP
223 lines
5.9 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\User;
|
|
use PDO;
|
|
use Firebase\JWT\JWT;
|
|
use Firebase\JWT\Key;
|
|
use Firebase\JWT\ExpiredException;
|
|
use DomainException;
|
|
use UnexpectedValueException;
|
|
|
|
class AuthService
|
|
{
|
|
private PDO $pdo;
|
|
private ?array $user = null;
|
|
private JwtService $jwtService;
|
|
private string $jwtSecret;
|
|
private string $jwtAlgo = 'HS256';
|
|
|
|
public function __construct(PDO $pdo, JwtService $jwtService = null)
|
|
{
|
|
$this->pdo = $pdo;
|
|
$this->jwtService = $jwtService ?? new JwtService([
|
|
'secret' => getenv('JWT_SECRET') ?: 'your-secret-key-change-this-in-production',
|
|
'algo' => $this->jwtAlgo,
|
|
'expiration' => 3600,
|
|
'leeway' => 60
|
|
]);
|
|
|
|
$this->checkSession();
|
|
}
|
|
|
|
public function checkSession(): void
|
|
{
|
|
if (isset($_SESSION['user_id'])) {
|
|
$user = User::findByUsername($this->pdo, $_SESSION['username']);
|
|
|
|
if (!$user || !$user['is_active']) {
|
|
$this->logout();
|
|
} else {
|
|
$this->user = $user;
|
|
}
|
|
}
|
|
}
|
|
|
|
public function login(string $username, string $password, string $ip = null): bool
|
|
{
|
|
$user = User::findByUsername($this->pdo, $username);
|
|
|
|
if (!$user || !$user['is_active']) {
|
|
return false;
|
|
}
|
|
|
|
// Verify password directly using the hash from database
|
|
if (!password_verify($password, $user['password'])) {
|
|
return false;
|
|
}
|
|
|
|
// Set session
|
|
$_SESSION['user_id'] = $user['id'];
|
|
$_SESSION['username'] = $user['username'];
|
|
$_SESSION['role'] = $user['role'];
|
|
|
|
// Update last login
|
|
$this->updateUserLastLogin($user['id'], $ip);
|
|
|
|
$this->user = $user;
|
|
return true;
|
|
}
|
|
|
|
public function logout(): void
|
|
{
|
|
unset($_SESSION['user_id']);
|
|
unset($_SESSION['username']);
|
|
unset($_SESSION['role']);
|
|
$this->user = null;
|
|
}
|
|
|
|
public function getCurrentUser(): ?array
|
|
{
|
|
return $this->user;
|
|
}
|
|
|
|
public function isLoggedIn(): bool
|
|
{
|
|
return $this->user !== null;
|
|
}
|
|
|
|
public function isAdmin(): bool
|
|
{
|
|
return $this->isLoggedIn() && $this->user['role'] === 'admin';
|
|
}
|
|
|
|
public function requireLogin(): void
|
|
{
|
|
if (!$this->isLoggedIn()) {
|
|
header('Location: /login');
|
|
exit;
|
|
}
|
|
}
|
|
|
|
public function requireAdmin(): void
|
|
{
|
|
$this->requireLogin();
|
|
|
|
if (!$this->isAdmin()) {
|
|
http_response_code(403);
|
|
echo 'Access denied. Admin privileges required.';
|
|
exit;
|
|
}
|
|
}
|
|
|
|
public function generateToken(array $user): array
|
|
{
|
|
$now = time();
|
|
$payload = [
|
|
'sub' => $user['id'],
|
|
'username' => $user['username'],
|
|
'role' => $user['role'] ?? 'user',
|
|
'iat' => $now,
|
|
'exp' => $now + 3600, // 1 hour expiration
|
|
'jti' => bin2hex(random_bytes(16))
|
|
];
|
|
|
|
$token = $this->jwtService->encode($payload);
|
|
$refreshToken = $this->generateRefreshToken($user['id']);
|
|
|
|
return [
|
|
'token' => $token,
|
|
'refresh_token' => $refreshToken,
|
|
'expires_in' => 3600,
|
|
'token_type' => 'Bearer'
|
|
];
|
|
}
|
|
|
|
public function refreshToken(string $refreshToken): ?array
|
|
{
|
|
// Verify refresh token (you might want to store and validate this in your database)
|
|
$stmt = $this->pdo->prepare("SELECT user_id FROM refresh_tokens WHERE token = :token AND expires_at > NOW()");
|
|
$stmt->execute(['token' => $refreshToken]);
|
|
$tokenData = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
if (!$tokenData) {
|
|
return null;
|
|
}
|
|
|
|
$user = (new User($this->pdo))->find($tokenData['user_id']);
|
|
if (!$user) {
|
|
return null;
|
|
}
|
|
|
|
// Generate new tokens
|
|
return $this->generateToken($user);
|
|
}
|
|
|
|
private function generateRefreshToken(int $userId): string
|
|
{
|
|
$token = bin2hex(random_bytes(32));
|
|
$expiresAt = date('Y-m-d H:i:s', time() + (30 * 24 * 3600)); // 30 days
|
|
|
|
$stmt = $this->pdo->prepare("
|
|
INSERT INTO refresh_tokens (user_id, token, expires_at, created_at)
|
|
VALUES (:user_id, :token, :expires_at, NOW())
|
|
ON DUPLICATE KEY UPDATE
|
|
token = VALUES(token),
|
|
expires_at = VALUES(expires_at),
|
|
created_at = NOW()
|
|
");
|
|
|
|
$stmt->execute([
|
|
'user_id' => $userId,
|
|
'token' => $token,
|
|
'expires_at' => $expiresAt
|
|
]);
|
|
|
|
return $token;
|
|
}
|
|
|
|
public function validateToken(string $token): ?array
|
|
{
|
|
try {
|
|
$payload = $this->jwtService->decode($token);
|
|
if (!$payload) {
|
|
return null;
|
|
}
|
|
|
|
// Optionally check if user still exists and is active
|
|
$user = (new User($this->pdo))->find($payload['sub']);
|
|
if (!$user || !$user['is_active']) {
|
|
return null;
|
|
}
|
|
|
|
return $payload;
|
|
} catch (\Exception $e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public function generateCSRFToken(): string
|
|
{
|
|
if (!isset($_SESSION['csrf_token'])) {
|
|
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
|
}
|
|
return $_SESSION['csrf_token'];
|
|
}
|
|
|
|
public function verifyCSRFToken(string $token): bool
|
|
{
|
|
return isset($_SESSION['csrf_token']) && $_SESSION['csrf_token'] === $token;
|
|
}
|
|
|
|
private function updateUserLastLogin(int $userId, string $ip = null): void
|
|
{
|
|
$stmt = $this->pdo->prepare("UPDATE users SET last_login_at = :last_login_at, login_ip = :login_ip WHERE id = :id");
|
|
$stmt->execute([
|
|
'id' => $userId,
|
|
'last_login_at' => date('Y-m-d H:i:s'),
|
|
'login_ip' => $ip
|
|
]);
|
|
}
|
|
}
|