Enhance API functionality and improve JWT authentication

- 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.
This commit is contained in:
Lars Behrends
2025-12-31 10:08:49 +01:00
parent 1b053148f0
commit b728b0c72d
18 changed files with 858 additions and 27 deletions

View File

@@ -4,15 +4,30 @@ 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)
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();
}
@@ -96,6 +111,92 @@ class AuthService
}
}
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'])) {