147 lines
4.4 KiB
PHP
147 lines
4.4 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Auth;
|
|
|
|
use App\Services\Database\Connection;
|
|
use Firebase\JWT\JWT;
|
|
use Firebase\JWT\Key;
|
|
use PDO;
|
|
use Exception;
|
|
use Throwable;
|
|
|
|
class AuthService
|
|
{
|
|
private PDO $pdo;
|
|
private array $jwtConfig;
|
|
|
|
public function __construct(Connection $connection)
|
|
{
|
|
$this->pdo = $connection->getPdo();
|
|
$aiConfig = require __DIR__ . '/../../../config/ai.php';
|
|
$this->jwtConfig = $aiConfig['jwt'];
|
|
}
|
|
|
|
/**
|
|
* Register a new user.
|
|
*/
|
|
public function register(string $name, string $email, string $password): array
|
|
{
|
|
// Check for duplicates
|
|
$stmt = $this->pdo->prepare("SELECT id FROM users WHERE email = ?");
|
|
$stmt->execute([$email]);
|
|
if ($stmt->fetch()) {
|
|
throw new Exception("Email already registered.");
|
|
}
|
|
|
|
$passwordHash = password_hash($password, PASSWORD_BCRYPT);
|
|
|
|
$this->pdo->beginTransaction();
|
|
try {
|
|
$stmt = $this->pdo->prepare("INSERT INTO users (name, email, password_hash, status) VALUES (?, ?, ?, 'active')");
|
|
$stmt->execute([$name, $email, $passwordHash]);
|
|
$userId = (int)$this->pdo->lastInsertId();
|
|
|
|
// Count users to assign role: first user gets Admin, others get Member
|
|
$stmt = $this->pdo->query("SELECT COUNT(*) FROM users");
|
|
$count = (int)$stmt->fetchColumn();
|
|
|
|
$roleCode = $count === 1 ? 'admin' : 'member';
|
|
$stmt = $this->pdo->prepare("SELECT id FROM roles WHERE code = ?");
|
|
$stmt->execute([$roleCode]);
|
|
$roleId = $stmt->fetchColumn();
|
|
|
|
if ($roleId) {
|
|
$stmt = $this->pdo->prepare("INSERT INTO user_roles (user_id, role_id) VALUES (?, ?)");
|
|
$stmt->execute([$userId, $roleId]);
|
|
}
|
|
|
|
$this->pdo->commit();
|
|
|
|
return [
|
|
'id' => $userId,
|
|
'name' => $name,
|
|
'email' => $email,
|
|
'status' => 'active'
|
|
];
|
|
} catch (Throwable $e) {
|
|
$this->pdo->rollBack();
|
|
throw new Exception("Registration failed: " . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Authenticate a user by email and password.
|
|
*/
|
|
public function login(string $email, string $password): array
|
|
{
|
|
$stmt = $this->pdo->prepare("SELECT id, name, email, password_hash, status FROM users WHERE email = ? AND deleted_at IS NULL");
|
|
$stmt->execute([$email]);
|
|
$user = $stmt->fetch();
|
|
|
|
if (!$user || !password_verify($password, $user['password_hash'])) {
|
|
throw new Exception("Invalid email or password.");
|
|
}
|
|
|
|
if ($user['status'] !== 'active') {
|
|
throw new Exception("User account is inactive.");
|
|
}
|
|
|
|
unset($user['password_hash']);
|
|
return $user;
|
|
}
|
|
|
|
/**
|
|
* Get active user by ID.
|
|
*/
|
|
public function getUserById(int $id): ?array
|
|
{
|
|
$stmt = $this->pdo->prepare("SELECT id, name, email, status FROM users WHERE id = ? AND deleted_at IS NULL");
|
|
$stmt->execute([$id]);
|
|
$user = $stmt->fetch();
|
|
return $user ?: null;
|
|
}
|
|
|
|
/**
|
|
* Generate JWT for APIs.
|
|
*/
|
|
public function generateJwt(array $user): string
|
|
{
|
|
$issuedAt = time();
|
|
$expire = $issuedAt + $this->jwtConfig['expires_in'];
|
|
|
|
$payload = [
|
|
'iss' => $_ENV['APP_URL'] ?? 'https://scoutiq.intaleqapp.com',
|
|
'aud' => $_ENV['APP_URL'] ?? 'https://scoutiq.intaleqapp.com',
|
|
'iat' => $issuedAt,
|
|
'exp' => $expire,
|
|
'sub' => $user['id'],
|
|
'user' => [
|
|
'id' => $user['id'],
|
|
'name' => $user['name'],
|
|
'email' => $user['email']
|
|
]
|
|
];
|
|
|
|
return JWT::encode($payload, $this->jwtConfig['secret'], $this->jwtConfig['algorithm']);
|
|
}
|
|
|
|
/**
|
|
* Decode and verify JWT.
|
|
*/
|
|
public function verifyJwt(string $token): ?array
|
|
{
|
|
try {
|
|
$decoded = JWT::decode($token, new Key($this->jwtConfig['secret'], $this->jwtConfig['algorithm']));
|
|
$payload = (array)$decoded;
|
|
|
|
if (isset($payload['user'])) {
|
|
return (array)$payload['user'];
|
|
}
|
|
|
|
return $this->getUserById((int)$payload['sub']);
|
|
} catch (Throwable $e) {
|
|
return null;
|
|
}
|
|
}
|
|
}
|