2
0
Web/auth/Auth.php
2025-09-10 21:57:44 -05:00

319 lines
7.9 KiB
PHP

<?php
require_once __DIR__ . '/UserProviderInterface.php';
require_once __DIR__ . '/User.php';
require_once __DIR__ . '/../session.php';
/**
* Auth handles user authentication with cookie support
*/
class Auth
{
private UserProviderInterface $provider;
private Session $session;
private ?User $user = null;
private array $config;
const SESSION_KEY = 'auth_user_id';
const REMEMBER_COOKIE = 'remember_token';
const REMEMBER_DURATION = 2592000; // 30 days in seconds
public function __construct(UserProviderInterface $provider, Session $session, array $config = [])
{
$this->provider = $provider;
$this->session = $session;
$this->config = array_merge([
'cookie_name' => self::REMEMBER_COOKIE,
'cookie_lifetime' => self::REMEMBER_DURATION,
'cookie_path' => '/',
'cookie_domain' => '',
'cookie_secure' => false,
'cookie_httponly' => true,
'cookie_samesite' => 'Lax'
], $config);
$this->initializeFromSession();
}
/**
* Attempt to authenticate user with credentials
*/
public function attempt(array $credentials, bool $remember = false): bool
{
$identifier = $credentials['email'] ?? $credentials['username'] ?? null;
$password = $credentials['password'] ?? null;
if (!$identifier || !$password) {
return false;
}
$user = $this->provider->findByCredentials($identifier);
if (!$user || !$this->provider->verifyPassword($user, $password)) {
return false;
}
$this->login($user, $remember);
return true;
}
/**
* Login a user instance
*/
public function login(User $user, bool $remember = false): void
{
$this->user = $user;
$this->session->set(self::SESSION_KEY, $user->getId());
$this->session->regenerate();
if ($remember) {
$this->createRememberToken($user);
}
}
/**
* Login using user ID
*/
public function loginById(int|string $id, bool $remember = false): bool
{
$user = $this->provider->findById($id);
if (!$user) {
return false;
}
$this->login($user, $remember);
return true;
}
/**
* Logout the current user
*/
public function logout(): void
{
if ($this->user && $this->user->rememberToken) {
$this->provider->updateRememberToken($this->user, null);
}
$this->user = null;
$this->session->remove(self::SESSION_KEY);
$this->session->regenerate();
$this->clearRememberCookie();
}
/**
* Check if user is authenticated
*/
public function check(): bool
{
return $this->user() !== null;
}
/**
* Check if user is guest (not authenticated)
*/
public function guest(): bool
{
return !$this->check();
}
/**
* Get the currently authenticated user
*/
public function user(): ?User
{
if ($this->user) {
return $this->user;
}
// Try to load from session
$userId = $this->session->get(self::SESSION_KEY);
if ($userId) {
$this->user = $this->provider->findById($userId);
return $this->user;
}
// Try to load from remember cookie
$this->user = $this->getUserFromRememberCookie();
if ($this->user) {
$this->session->set(self::SESSION_KEY, $this->user->getId());
}
return $this->user;
}
/**
* Get user ID
*/
public function id(): int|string|null
{
return $this->user()?->getId();
}
/**
* Register a new user
*/
public function register(array $data, bool $login = true): User
{
// Hash password before storing
if (isset($data['password'])) {
$data['password'] = $this->hashPassword($data['password']);
}
$user = $this->provider->create($data);
if ($login) {
$this->login($user);
}
return $user;
}
/**
* Validate user credentials without logging in
*/
public function validate(array $credentials): bool
{
$identifier = $credentials['email'] ?? $credentials['username'] ?? null;
$password = $credentials['password'] ?? null;
if (!$identifier || !$password) {
return false;
}
$user = $this->provider->findByCredentials($identifier);
return $user && $this->provider->verifyPassword($user, $password);
}
/**
* Hash a password
*/
public function hashPassword(string $password): string
{
return password_hash($password, PASSWORD_BCRYPT, ['cost' => 10]);
}
/**
* Verify a password against hash
*/
public function verifyPassword(string $password, string $hash): bool
{
return password_verify($password, $hash);
}
/**
* Initialize user from session
*/
private function initializeFromSession(): void
{
if (!$this->session->isStarted()) {
$this->session->start();
}
$this->user();
}
/**
* Create remember token for user
*/
private function createRememberToken(User $user): void
{
$token = $this->generateRememberToken();
$hashedToken = hash('sha256', $token);
$this->provider->updateRememberToken($user, $hashedToken);
$user->rememberToken = $hashedToken;
$this->setRememberCookie($user->getId() . '|' . $token);
}
/**
* Generate a random remember token
*/
private function generateRememberToken(): string
{
return bin2hex(random_bytes(32));
}
/**
* Set remember cookie
*/
private function setRememberCookie(string $value): void
{
setcookie(
$this->config['cookie_name'],
$value,
[
'expires' => time() + $this->config['cookie_lifetime'],
'path' => $this->config['cookie_path'],
'domain' => $this->config['cookie_domain'],
'secure' => $this->config['cookie_secure'],
'httponly' => $this->config['cookie_httponly'],
'samesite' => $this->config['cookie_samesite']
]
);
}
/**
* Clear remember cookie
*/
private function clearRememberCookie(): void
{
setcookie(
$this->config['cookie_name'],
'',
[
'expires' => time() - 3600,
'path' => $this->config['cookie_path'],
'domain' => $this->config['cookie_domain'],
'secure' => $this->config['cookie_secure'],
'httponly' => $this->config['cookie_httponly'],
'samesite' => $this->config['cookie_samesite']
]
);
}
/**
* Get user from remember cookie
*/
private function getUserFromRememberCookie(): ?User
{
$cookie = $_COOKIE[$this->config['cookie_name']] ?? null;
if (!$cookie || !str_contains($cookie, '|')) {
return null;
}
[$id, $token] = explode('|', $cookie, 2);
$hashedToken = hash('sha256', $token);
$user = $this->provider->findById($id);
if (!$user || $user->rememberToken !== $hashedToken) {
$this->clearRememberCookie();
return null;
}
return $user;
}
/**
* Refresh user instance from provider
*/
public function refresh(): void
{
if ($this->user) {
$this->user = $this->provider->findById($this->user->getId());
}
}
/**
* Set the current user
*/
public function setUser(User $user): void
{
$this->user = $user;
$this->session->set(self::SESSION_KEY, $user->getId());
}
}