2
0

heavily simplify auth

This commit is contained in:
Sky Johnson 2025-09-11 08:23:31 -05:00
parent dfae8871e1
commit 0c40ae5888
8 changed files with 72 additions and 979 deletions

View File

@ -1,26 +1,23 @@
<?php <?php
require_once __DIR__ . '/UserProviderInterface.php';
require_once __DIR__ . '/User.php'; require_once __DIR__ . '/User.php';
require_once __DIR__ . '/../session.php'; require_once __DIR__ . '/../session.php';
/** /**
* Auth handles user authentication with cookie support * Simplified Auth handles user authentication with external verification
*/ */
class Auth class Auth
{ {
private UserProviderInterface $provider;
private Session $session; private Session $session;
private ?User $user = null; private ?User $user = null;
private array $config; private array $config;
const SESSION_KEY = 'auth_user_id'; const SESSION_KEY = 'auth_user_data';
const REMEMBER_COOKIE = 'remember_token'; const REMEMBER_COOKIE = 'remember_token';
const REMEMBER_DURATION = 2592000; // 30 days in seconds const REMEMBER_DURATION = 2592000; // 30 days in seconds
public function __construct(UserProviderInterface $provider, Session $session, array $config = []) public function __construct(Session $session, array $config = [])
{ {
$this->provider = $provider;
$this->session = $session; $this->session = $session;
$this->config = array_merge([ $this->config = array_merge([
'cookie_name' => self::REMEMBER_COOKIE, 'cookie_name' => self::REMEMBER_COOKIE,
@ -36,54 +33,31 @@ class Auth
} }
/** /**
* Attempt to authenticate user with credentials * Login with externally verified user data
*/ */
public function attempt(array $credentials, bool $remember = false): bool public function login(array $userData, bool $remember = false): void
{ {
$identifier = $credentials['email'] ?? $credentials['username'] ?? null; $this->user = new User($userData);
$password = $credentials['password'] ?? null; $this->session->set(self::SESSION_KEY, $userData);
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(); $this->session->regenerate();
if ($remember) { if ($remember) {
$this->createRememberToken($user); $this->createRememberToken($userData);
} }
} }
/** /**
* Login using user ID * Login using user data directly
*/ */
public function loginById(int|string $id, bool $remember = false): bool public function loginUser(User $user, bool $remember = false): void
{ {
$user = $this->provider->findById($id); $this->user = $user;
$this->session->set(self::SESSION_KEY, $user->toArray());
$this->session->regenerate();
if (!$user) { if ($remember) {
return false; $this->createRememberToken($user->toArray());
} }
$this->login($user, $remember);
return true;
} }
/** /**
@ -91,10 +65,6 @@ class Auth
*/ */
public function logout(): void public function logout(): void
{ {
if ($this->user && $this->user->rememberToken) {
$this->provider->updateRememberToken($this->user, null);
}
$this->user = null; $this->user = null;
$this->session->remove(self::SESSION_KEY); $this->session->remove(self::SESSION_KEY);
$this->session->regenerate(); $this->session->regenerate();
@ -127,16 +97,17 @@ class Auth
} }
// Try to load from session // Try to load from session
$userId = $this->session->get(self::SESSION_KEY); $userData = $this->session->get(self::SESSION_KEY);
if ($userId) { if ($userData) {
$this->user = $this->provider->findById($userId); $this->user = new User($userData);
return $this->user; return $this->user;
} }
// Try to load from remember cookie // Try to load from remember cookie
$this->user = $this->getUserFromRememberCookie(); $userData = $this->getUserDataFromRememberCookie();
if ($this->user) { if ($userData) {
$this->session->set(self::SESSION_KEY, $this->user->getId()); $this->user = new User($userData);
$this->session->set(self::SESSION_KEY, $userData);
} }
return $this->user; return $this->user;
@ -151,39 +122,12 @@ class Auth
} }
/** /**
* Register a new user * Set user data after external verification
*/ */
public function register(array $data, bool $login = true): User public function setUserData(array $userData): void
{ {
// Hash password before storing $this->user = new User($userData);
if (isset($data['password'])) { $this->session->set(self::SESSION_KEY, $userData);
$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);
} }
/** /**
@ -207,25 +151,22 @@ class Auth
*/ */
private function initializeFromSession(): void private function initializeFromSession(): void
{ {
if (!$this->session->isStarted()) { $this->session->start();
$this->session->start();
}
$this->user(); $this->user();
} }
/** /**
* Create remember token for user * Create remember token for user
*/ */
private function createRememberToken(User $user): void private function createRememberToken(array $userData): void
{ {
$token = $this->generateRememberToken(); $token = $this->generateRememberToken();
$hashedToken = hash('sha256', $token); $hashedToken = hash('sha256', $token);
$userData['remember_token'] = $hashedToken;
$this->session->set('remember_user_data', $userData);
$this->provider->updateRememberToken($user, $hashedToken); $this->setRememberCookie($userData['id'] . '|' . $token);
$user->rememberToken = $hashedToken;
$this->setRememberCookie($user->getId() . '|' . $token);
} }
/** /**
@ -275,9 +216,9 @@ class Auth
} }
/** /**
* Get user from remember cookie * Get user data from remember cookie
*/ */
private function getUserFromRememberCookie(): ?User private function getUserDataFromRememberCookie(): ?array
{ {
$cookie = $_COOKIE[$this->config['cookie_name']] ?? null; $cookie = $_COOKIE[$this->config['cookie_name']] ?? null;
@ -288,32 +229,24 @@ class Auth
[$id, $token] = explode('|', $cookie, 2); [$id, $token] = explode('|', $cookie, 2);
$hashedToken = hash('sha256', $token); $hashedToken = hash('sha256', $token);
$user = $this->provider->findById($id); $userData = $this->session->get('remember_user_data');
if (!$user || $user->rememberToken !== $hashedToken) { if (!$userData || $userData['id'] != $id || ($userData['remember_token'] ?? null) !== $hashedToken) {
$this->clearRememberCookie(); $this->clearRememberCookie();
return null; return null;
} }
return $user; return $userData;
} }
/** /**
* Refresh user instance from provider * Clear user data and session
*/ */
public function refresh(): void public function clear(): void
{ {
if ($this->user) { $this->user = null;
$this->user = $this->provider->findById($this->user->getId()); $this->session->remove(self::SESSION_KEY);
} $this->session->remove('remember_user_data');
} $this->clearRememberCookie();
/**
* Set the current user
*/
public function setUser(User $user): void
{
$this->user = $user;
$this->session->set(self::SESSION_KEY, $user->getId());
} }
} }

View File

@ -66,9 +66,7 @@ class AuthMiddleware
public function optional(): callable public function optional(): callable
{ {
return function(Context $context, callable $next) { return function(Context $context, callable $next) {
if ($this->auth->check()) { if ($this->auth->check()) $context->set('user', $this->auth->user());
$context->set('user', $this->auth->user());
}
$next(); $next();
}; };
} }
@ -91,7 +89,7 @@ class AuthMiddleware
} }
$user = $this->auth->user(); $user = $this->auth->user();
$userRole = $user->getAttribute('role') ?? $user->role ?? null; $userRole = $user->role;
if (!in_array($userRole, $roles)) { if (!in_array($userRole, $roles)) {
if ($context->request->expectsJson()) { if ($context->request->expectsJson()) {
@ -174,7 +172,7 @@ class AuthMiddleware
?? $context->request->header('X-CSRF-TOKEN') ?? $context->request->header('X-CSRF-TOKEN')
?? $context->request->header('X-XSRF-TOKEN'); ?? $context->request->header('X-XSRF-TOKEN');
if (!$context->session->validateToken($token)) { if (!$context->session->validateCsrf($token)) {
if ($context->request->expectsJson()) { if ($context->request->expectsJson()) {
$context->json(['error' => 'CSRF token mismatch'], 419); $context->json(['error' => 'CSRF token mismatch'], 419);
return; return;
@ -195,12 +193,12 @@ class AuthMiddleware
return function(Context $context, callable $next) { return function(Context $context, callable $next) {
// Auth class already handles remember cookies in constructor // Auth class already handles remember cookies in constructor
// This middleware can be used to refresh the remember token if needed // This middleware can be used to refresh the remember token if needed
if ($this->auth->check()) { if ($this->auth->check()) {
$context->set('user', $this->auth->user()); $context->set('user', $this->auth->user());
} }
$next(); $next();
}; };
} }
} }

View File

@ -1,7 +1,7 @@
<?php <?php
/** /**
* User model represents an authenticated user * User model represents an authenticated user for auth functionality only
*/ */
class User class User
{ {
@ -10,19 +10,18 @@ class User
public string $email; public string $email;
public string $password; public string $password;
public ?string $rememberToken; public ?string $rememberToken;
public array $attributes = []; public string $role;
public ?string $createdAt; public ?string $lastLogin;
public ?string $updatedAt;
public function __construct(array $data = []) public function __construct(array $data = [])
{ {
foreach ($data as $key => $value) { $this->id = $data['id'] ?? 0;
if (property_exists($this, $key)) { $this->username = $data['username'] ?? '';
$this->$key = $value; $this->email = $data['email'] ?? '';
} else { $this->password = $data['password'] ?? '';
$this->attributes[$key] = $value; $this->rememberToken = $data['remember_token'] ?? $data['rememberToken'] ?? null;
} $this->role = $data['role'] ?? 'user';
} $this->lastLogin = $data['last_login'] ?? $data['lastLogin'] ?? null;
} }
/** /**
@ -41,22 +40,6 @@ class User
return $this->id; return $this->id;
} }
/**
* Get custom attribute
*/
public function getAttribute(string $key): mixed
{
return $this->attributes[$key] ?? null;
}
/**
* Set custom attribute
*/
public function setAttribute(string $key, mixed $value): void
{
$this->attributes[$key] = $value;
}
/** /**
* Convert to array * Convert to array
*/ */
@ -66,9 +49,11 @@ class User
'id' => $this->id, 'id' => $this->id,
'username' => $this->username, 'username' => $this->username,
'email' => $this->email, 'email' => $this->email,
'created_at' => $this->createdAt, 'password' => $this->password,
'updated_at' => $this->updatedAt, 'remember_token' => $this->rememberToken,
] + $this->attributes; 'role' => $this->role,
'last_login' => $this->lastLogin,
];
} }
/** /**
@ -76,8 +61,12 @@ class User
*/ */
public function toSafeArray(): array public function toSafeArray(): array
{ {
$data = $this->toArray(); return [
unset($data['password'], $data['remember_token']); 'id' => $this->id,
return $data; 'username' => $this->username,
'email' => $this->email,
'role' => $this->role,
'last_login' => $this->lastLogin,
];
} }
} }

View File

@ -1,52 +0,0 @@
<?php
/**
* UserProviderInterface defines contract for user storage providers
*/
interface UserProviderInterface
{
/**
* Find user by ID
*/
public function findById(int|string $id): ?User;
/**
* Find user by email
*/
public function findByEmail(string $email): ?User;
/**
* Find user by username
*/
public function findByUsername(string $username): ?User;
/**
* Find user by credentials (email or username)
*/
public function findByCredentials(string $identifier): ?User;
/**
* Find user by remember token
*/
public function findByRememberToken(string $token): ?User;
/**
* Create a new user
*/
public function create(array $data): User;
/**
* Update user data
*/
public function update(User $user, array $data): bool;
/**
* Update remember token
*/
public function updateRememberToken(User $user, ?string $token): bool;
/**
* Verify user password
*/
public function verifyPassword(User $user, string $password): bool;
}

View File

@ -1,170 +0,0 @@
<?php
require_once __DIR__ . '/../UserProviderInterface.php';
require_once __DIR__ . '/../User.php';
/**
* JsonUserProvider stores users in a JSON file
*/
class JsonUserProvider implements UserProviderInterface
{
private string $filePath;
private array $users = [];
public function __construct(string $filePath)
{
$this->filePath = $filePath;
$this->loadUsers();
}
/**
* Load users from JSON file
*/
private function loadUsers(): void
{
if (!file_exists($this->filePath)) {
$this->users = [];
$this->saveUsers();
return;
}
$content = file_get_contents($this->filePath);
$data = json_decode($content, true) ?? [];
$this->users = [];
foreach ($data as $userData) {
$this->users[$userData['id']] = new User($userData);
}
}
/**
* Save users to JSON file
*/
private function saveUsers(): void
{
$data = [];
foreach ($this->users as $user) {
$userData = [
'id' => $user->id,
'username' => $user->username,
'email' => $user->email,
'password' => $user->password,
'remember_token' => $user->rememberToken,
'created_at' => $user->createdAt,
'updated_at' => $user->updatedAt,
] + $user->attributes;
$data[] = $userData;
}
file_put_contents($this->filePath, json_encode($data, JSON_PRETTY_PRINT));
}
/**
* Generate next user ID
*/
private function generateId(): int
{
if (empty($this->users)) {
return 1;
}
return max(array_keys($this->users)) + 1;
}
public function findById(int|string $id): ?User
{
return $this->users[$id] ?? null;
}
public function findByEmail(string $email): ?User
{
foreach ($this->users as $user) {
if ($user->email === $email) {
return $user;
}
}
return null;
}
public function findByUsername(string $username): ?User
{
foreach ($this->users as $user) {
if ($user->username === $username) {
return $user;
}
}
return null;
}
public function findByCredentials(string $identifier): ?User
{
// Check if identifier is email
if (filter_var($identifier, FILTER_VALIDATE_EMAIL)) {
return $this->findByEmail($identifier);
}
// Otherwise treat as username
return $this->findByUsername($identifier);
}
public function findByRememberToken(string $token): ?User
{
foreach ($this->users as $user) {
if ($user->rememberToken === $token) {
return $user;
}
}
return null;
}
public function create(array $data): User
{
$data['id'] = $data['id'] ?? $this->generateId();
$data['created_at'] = $data['created_at'] ?? date('Y-m-d H:i:s');
$data['updated_at'] = $data['updated_at'] ?? date('Y-m-d H:i:s');
$user = new User($data);
$this->users[$user->id] = $user;
$this->saveUsers();
return $user;
}
public function update(User $user, array $data): bool
{
if (!isset($this->users[$user->id])) {
return false;
}
foreach ($data as $key => $value) {
if (property_exists($user, $key)) {
$user->$key = $value;
} else {
$user->attributes[$key] = $value;
}
}
$user->updatedAt = date('Y-m-d H:i:s');
$this->users[$user->id] = $user;
$this->saveUsers();
return true;
}
public function updateRememberToken(User $user, ?string $token): bool
{
if (!isset($this->users[$user->id])) {
return false;
}
$user->rememberToken = $token;
$this->users[$user->id] = $user;
$this->saveUsers();
return true;
}
public function verifyPassword(User $user, string $password): bool
{
return password_verify($password, $user->password);
}
}

View File

@ -1,205 +0,0 @@
<?php
require_once __DIR__ . '/../UserProviderInterface.php';
require_once __DIR__ . '/../User.php';
/**
* MysqlUserProvider stores users in MySQL database
*/
class MysqlUserProvider implements UserProviderInterface
{
private PDO $db;
private string $table;
public function __construct(array $config, string $table = 'users')
{
$this->table = $table;
$dsn = sprintf(
'mysql:host=%s;port=%s;dbname=%s;charset=%s',
$config['host'] ?? 'localhost',
$config['port'] ?? 3306,
$config['database'],
$config['charset'] ?? 'utf8mb4'
);
try {
$this->db = new PDO(
$dsn,
$config['username'],
$config['password'],
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4"
]
);
$this->createTable();
} catch (PDOException $e) {
throw new Exception("Failed to connect to MySQL database: " . $e->getMessage());
}
}
/**
* Create users table if it doesn't exist
*/
private function createTable(): void
{
$sql = "CREATE TABLE IF NOT EXISTS {$this->table} (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(255) UNIQUE,
email VARCHAR(255) UNIQUE,
password VARCHAR(255) NOT NULL,
remember_token VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
attributes JSON,
INDEX idx_email (email),
INDEX idx_username (username),
INDEX idx_remember_token (remember_token)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci";
$this->db->exec($sql);
}
/**
* Map database row to User object
*/
private function mapToUser(array $row): User
{
$attributes = isset($row['attributes']) ? json_decode($row['attributes'], true) ?? [] : [];
return new User([
'id' => $row['id'],
'username' => $row['username'],
'email' => $row['email'],
'password' => $row['password'],
'rememberToken' => $row['remember_token'],
'createdAt' => $row['created_at'],
'updatedAt' => $row['updated_at'],
'attributes' => $attributes
]);
}
public function findById(int|string $id): ?User
{
$stmt = $this->db->prepare("SELECT * FROM {$this->table} WHERE id = :id");
$stmt->execute(['id' => $id]);
$row = $stmt->fetch();
return $row ? $this->mapToUser($row) : null;
}
public function findByEmail(string $email): ?User
{
$stmt = $this->db->prepare("SELECT * FROM {$this->table} WHERE email = :email");
$stmt->execute(['email' => $email]);
$row = $stmt->fetch();
return $row ? $this->mapToUser($row) : null;
}
public function findByUsername(string $username): ?User
{
$stmt = $this->db->prepare("SELECT * FROM {$this->table} WHERE username = :username");
$stmt->execute(['username' => $username]);
$row = $stmt->fetch();
return $row ? $this->mapToUser($row) : null;
}
public function findByCredentials(string $identifier): ?User
{
$stmt = $this->db->prepare(
"SELECT * FROM {$this->table} WHERE email = :identifier OR username = :identifier"
);
$stmt->execute(['identifier' => $identifier]);
$row = $stmt->fetch();
return $row ? $this->mapToUser($row) : null;
}
public function findByRememberToken(string $token): ?User
{
$stmt = $this->db->prepare("SELECT * FROM {$this->table} WHERE remember_token = :token");
$stmt->execute(['token' => $token]);
$row = $stmt->fetch();
return $row ? $this->mapToUser($row) : null;
}
public function create(array $data): User
{
$attributes = $data['attributes'] ?? [];
unset($data['attributes']);
$stmt = $this->db->prepare(
"INSERT INTO {$this->table} (username, email, password, remember_token, attributes)
VALUES (:username, :email, :password, :remember_token, :attributes)"
);
$stmt->execute([
'username' => $data['username'] ?? null,
'email' => $data['email'] ?? null,
'password' => $data['password'],
'remember_token' => $data['remember_token'] ?? null,
'attributes' => json_encode($attributes)
]);
$data['id'] = $this->db->lastInsertId();
$data['attributes'] = $attributes;
// Fetch created/updated timestamps
$user = $this->findById($data['id']);
return $user;
}
public function update(User $user, array $data): bool
{
$attributes = array_merge($user->attributes, $data['attributes'] ?? []);
unset($data['attributes']);
$fields = [];
$params = ['id' => $user->id];
foreach (['username', 'email', 'password'] as $field) {
if (isset($data[$field])) {
$fields[] = "$field = :$field";
$params[$field] = $data[$field];
$user->$field = $data[$field];
}
}
if (!empty($attributes)) {
$fields[] = "attributes = :attributes";
$params['attributes'] = json_encode($attributes);
$user->attributes = $attributes;
}
if (empty($fields)) {
return true;
}
$sql = "UPDATE {$this->table} SET " . implode(', ', $fields) . " WHERE id = :id";
$stmt = $this->db->prepare($sql);
return $stmt->execute($params);
}
public function updateRememberToken(User $user, ?string $token): bool
{
$stmt = $this->db->prepare(
"UPDATE {$this->table} SET remember_token = :token WHERE id = :id"
);
return $stmt->execute([
'id' => $user->id,
'token' => $token
]);
}
public function verifyPassword(User $user, string $password): bool
{
return password_verify($password, $user->password);
}
}

View File

@ -1,190 +0,0 @@
<?php
require_once __DIR__ . '/../UserProviderInterface.php';
require_once __DIR__ . '/../User.php';
/**
* SqliteUserProvider stores users in SQLite database
*/
class SqliteUserProvider implements UserProviderInterface
{
private PDO $db;
private string $table;
public function __construct(string $databasePath, string $table = 'users')
{
$this->table = $table;
try {
$this->db = new PDO("sqlite:$databasePath");
$this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->createTable();
} catch (PDOException $e) {
throw new Exception("Failed to connect to SQLite database: " . $e->getMessage());
}
}
/**
* Create users table if it doesn't exist
*/
private function createTable(): void
{
$sql = "CREATE TABLE IF NOT EXISTS {$this->table} (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username VARCHAR(255) UNIQUE,
email VARCHAR(255) UNIQUE,
password VARCHAR(255) NOT NULL,
remember_token VARCHAR(255),
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
attributes TEXT
)";
$this->db->exec($sql);
}
/**
* Map database row to User object
*/
private function mapToUser(array $row): User
{
$attributes = isset($row['attributes']) ? json_decode($row['attributes'], true) ?? [] : [];
return new User([
'id' => $row['id'],
'username' => $row['username'],
'email' => $row['email'],
'password' => $row['password'],
'rememberToken' => $row['remember_token'],
'createdAt' => $row['created_at'],
'updatedAt' => $row['updated_at'],
'attributes' => $attributes
]);
}
public function findById(int|string $id): ?User
{
$stmt = $this->db->prepare("SELECT * FROM {$this->table} WHERE id = :id");
$stmt->execute(['id' => $id]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
return $row ? $this->mapToUser($row) : null;
}
public function findByEmail(string $email): ?User
{
$stmt = $this->db->prepare("SELECT * FROM {$this->table} WHERE email = :email");
$stmt->execute(['email' => $email]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
return $row ? $this->mapToUser($row) : null;
}
public function findByUsername(string $username): ?User
{
$stmt = $this->db->prepare("SELECT * FROM {$this->table} WHERE username = :username");
$stmt->execute(['username' => $username]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
return $row ? $this->mapToUser($row) : null;
}
public function findByCredentials(string $identifier): ?User
{
$stmt = $this->db->prepare(
"SELECT * FROM {$this->table} WHERE email = :identifier OR username = :identifier"
);
$stmt->execute(['identifier' => $identifier]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
return $row ? $this->mapToUser($row) : null;
}
public function findByRememberToken(string $token): ?User
{
$stmt = $this->db->prepare("SELECT * FROM {$this->table} WHERE remember_token = :token");
$stmt->execute(['token' => $token]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
return $row ? $this->mapToUser($row) : null;
}
public function create(array $data): User
{
$attributes = $data['attributes'] ?? [];
unset($data['attributes']);
$stmt = $this->db->prepare(
"INSERT INTO {$this->table} (username, email, password, remember_token, attributes, created_at, updated_at)
VALUES (:username, :email, :password, :remember_token, :attributes, :created_at, :updated_at)"
);
$stmt->execute([
'username' => $data['username'] ?? null,
'email' => $data['email'] ?? null,
'password' => $data['password'],
'remember_token' => $data['remember_token'] ?? null,
'attributes' => json_encode($attributes),
'created_at' => $data['created_at'] ?? date('Y-m-d H:i:s'),
'updated_at' => $data['updated_at'] ?? date('Y-m-d H:i:s')
]);
$data['id'] = $this->db->lastInsertId();
$data['attributes'] = $attributes;
return new User($data);
}
public function update(User $user, array $data): bool
{
$attributes = array_merge($user->attributes, $data['attributes'] ?? []);
unset($data['attributes']);
$fields = [];
$params = ['id' => $user->id];
foreach (['username', 'email', 'password'] as $field) {
if (isset($data[$field])) {
$fields[] = "$field = :$field";
$params[$field] = $data[$field];
$user->$field = $data[$field];
}
}
if (!empty($attributes)) {
$fields[] = "attributes = :attributes";
$params['attributes'] = json_encode($attributes);
$user->attributes = $attributes;
}
if (empty($fields)) {
return true;
}
$fields[] = "updated_at = :updated_at";
$params['updated_at'] = date('Y-m-d H:i:s');
$sql = "UPDATE {$this->table} SET " . implode(', ', $fields) . " WHERE id = :id";
$stmt = $this->db->prepare($sql);
return $stmt->execute($params);
}
public function updateRememberToken(User $user, ?string $token): bool
{
$stmt = $this->db->prepare(
"UPDATE {$this->table} SET remember_token = :token, updated_at = :updated_at WHERE id = :id"
);
return $stmt->execute([
'id' => $user->id,
'token' => $token,
'updated_at' => date('Y-m-d H:i:s')
]);
}
public function verifyPassword(User $user, string $password): bool
{
return password_verify($password, $user->password);
}
}

View File

@ -1,210 +0,0 @@
<?php
require_once __DIR__ . '/web.php';
require_once __DIR__ . '/auth/Auth.php';
require_once __DIR__ . '/auth/AuthMiddleware.php';
require_once __DIR__ . '/auth/providers/JsonUserProvider.php';
require_once __DIR__ . '/auth/providers/SqliteUserProvider.php';
require_once __DIR__ . '/auth/providers/MysqlUserProvider.php';
// Choose your storage provider
// Option 1: JSON file storage
$userProvider = new JsonUserProvider(__DIR__ . '/storage/users.json');
// Option 2: SQLite database
// $userProvider = new SqliteUserProvider(__DIR__ . '/storage/database.sqlite');
// Option 3: MySQL database
// $userProvider = new MysqlUserProvider([
// 'host' => 'localhost',
// 'database' => 'myapp',
// 'username' => 'root',
// 'password' => 'password'
// ]);
// Create web application
$app = new Web(debug: true);
// Initialize auth system
$auth = new Auth($userProvider, new Session());
$authMiddleware = new AuthMiddleware($auth);
// Make auth available in all routes
$app->use(function($context, $next) use ($auth) {
$context->set('auth', $auth);
$next();
});
// Public routes
$app->get('/', function($context) {
$user = $context->get('auth')->user();
$context->html("<h1>Welcome " . ($user ? $user->username : 'Guest') . "</h1>");
});
// Registration page
$app->get('/register', function($context) {
$csrfToken = $context->session->token();
$context->html(<<<HTML
<form method="POST" action="/register">
<input type="hidden" name="_token" value="{$csrfToken}">
<input type="text" name="username" placeholder="Username" required><br>
<input type="email" name="email" placeholder="Email" required><br>
<input type="password" name="password" placeholder="Password" required><br>
<input type="password" name="password_confirmation" placeholder="Confirm Password" required><br>
<button type="submit">Register</button>
</form>
HTML);
});
// Registration handler
$app->post('/register', function($context) use ($auth) {
// Validate input
$validator = $context->validate($context->request->all(), [
'username' => 'required|alphaNum|min:3|max:20',
'email' => 'required|email',
'password' => 'required|min:6|confirmed'
]);
// Check if username/email already exists
if ($auth->provider->findByUsername($context->request->input('username'))) {
$context->error(400, 'Username already taken');
return;
}
if ($auth->provider->findByEmail($context->request->input('email'))) {
$context->error(400, 'Email already registered');
return;
}
// Register user
$user = $auth->register([
'username' => $context->request->input('username'),
'email' => $context->request->input('email'),
'password' => $context->request->input('password')
]);
$context->redirect('/dashboard');
})->use($authMiddleware->verifyCsrf());
// Login page
$app->get('/login', function($context) {
$csrfToken = $context->session->token();
$context->html(<<<HTML
<form method="POST" action="/login">
<input type="hidden" name="_token" value="{$csrfToken}">
<input type="text" name="username" placeholder="Username or Email" required><br>
<input type="password" name="password" placeholder="Password" required><br>
<label>
<input type="checkbox" name="remember"> Remember me
</label><br>
<button type="submit">Login</button>
</form>
HTML);
})->use($authMiddleware->requireGuest());
// Login handler
$app->post('/login', function($context) use ($auth) {
$credentials = [
'username' => $context->request->input('username'),
'password' => $context->request->input('password')
];
$remember = $context->request->input('remember') === 'on';
if ($auth->attempt($credentials, $remember)) {
$context->redirect('/dashboard');
} else {
$context->error(401, 'Invalid credentials');
}
})->use($authMiddleware->verifyCsrf())
->use($authMiddleware->requireGuest());
// Logout
$app->post('/logout', function($context) use ($auth) {
$auth->logout();
$context->redirect('/');
})->use($authMiddleware->verifyCsrf());
// Protected routes
$app->group('/dashboard', function($app) use ($authMiddleware) {
// Apply auth middleware to all routes in this group
$app->use($authMiddleware->requireAuth());
$app->get('', function($context) {
$user = $context->get('user');
$csrfToken = $context->session->token();
$context->html(<<<HTML
<h1>Dashboard</h1>
<p>Welcome, {$user->username}!</p>
<p>Email: {$user->email}</p>
<form method="POST" action="/logout" style="display:inline">
<input type="hidden" name="_token" value="{$csrfToken}">
<button type="submit">Logout</button>
</form>
HTML);
});
$app->get('/profile', function($context) {
$user = $context->get('user');
$context->json($user->toSafeArray());
});
$app->post('/update-profile', function($context) use ($auth) {
$user = $context->get('user');
$validator = $context->validate($context->request->all(), [
'email' => 'email',
'username' => 'alphaNum|min:3|max:20'
]);
$data = $context->request->only(['email', 'username']);
if (!empty($data)) {
$auth->provider->update($user, $data);
$auth->refresh();
}
$context->json(['success' => true, 'user' => $auth->user()->toSafeArray()]);
})->use($authMiddleware->verifyCsrf());
});
// Admin only routes
$app->group('/admin', function($app) use ($authMiddleware) {
// Require admin role
$app->use($authMiddleware->requireRole('admin'));
$app->get('', function($context) {
$context->html('<h1>Admin Panel</h1>');
});
});
// API routes with rate limiting
$app->group('/api', function($app) use ($authMiddleware, $auth) {
// Optional auth for API
$app->use($authMiddleware->optional());
// Rate limiting: 60 requests per minute
$app->use($authMiddleware->rateLimit(60, 1));
$app->post('/login', function($context) use ($auth) {
$credentials = $context->request->only(['username', 'password']);
if ($auth->validate($credentials)) {
$auth->attempt($credentials);
return ['success' => true, 'user' => $auth->user()->toSafeArray()];
}
return $context->json(['error' => 'Invalid credentials'], 401);
});
$app->get('/me', function($context) use ($auth) {
if ($auth->guest()) {
return $context->json(['error' => 'Unauthenticated'], 401);
}
return $auth->user()->toSafeArray();
});
});
// Run the application
$app->run();