v1.0.0.0
StocketBase
- <?php
-
- declare(strict_types=1);
-
- require_once __DIR__ . '/database.php';
- require_once __DIR__ . '/language.php';
-
- final class UserAuth
- {
- private static ?array $current = null;
-
- public static function boot(): void
- {
- if (session_status() !== PHP_SESSION_ACTIVE) {
- session_set_cookie_params([
- 'lifetime' => 0,
- 'path' => '/',
- 'secure' => Config::get('SESSION_SECURE', '1') === '1',
- 'httponly' => true,
- 'samesite' => 'Lax',
- ]);
- session_start();
- }
- }
-
- public static function registrationEnabled(): bool
- {
- require_once __DIR__ . '/site-front.php';
-
- return SiteFront::settings()['registration_enabled'] === '1';
- }
-
- public static function register(string $email, string $password, string $displayName): array
- {
- if (!self::registrationEnabled()) {
- return [false, Language::get('auth_registration_closed', 'Registration is currently closed.')];
- }
-
- $email = trim(strtolower($email));
-
- if ($email === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
- return [false, Language::get('auth_invalid_email', 'Please provide a valid email address.')];
- }
-
- if (strlen($password) < 8) {
- return [false, Language::get('auth_password_too_short', 'Password must be at least 8 characters.')];
- }
-
- $db = Database::users();
-
- $existingStatement = $db->prepare('SELECT id FROM user_accounts WHERE email = :email LIMIT 1');
- $existingStatement->execute(['email' => $email]);
- if ($existingStatement->fetch()) {
- return [false, Language::get('auth_email_taken', 'An account with this email already exists.')];
- }
-
- $insert = $db->prepare(
- 'INSERT INTO user_accounts (email, password_hash, display_name, status) VALUES (:email, :hash, :display_name, :status)'
- );
- $passwordHash = password_hash($password, PASSWORD_DEFAULT);
- $insert->execute([
- 'email' => $email,
- 'hash' => $passwordHash,
- 'display_name' => mb_substr($displayName !== '' ? $displayName : explode('@', $email)[0], 0, 100),
- 'status' => 'active',
- ]);
-
- $newId = (int) $db->lastInsertId();
- self::startSession($newId, $passwordHash);
-
- return [true, null];
- }
-
- public static function attemptLogin(string $email, string $password): bool
- {
- $email = trim(strtolower($email));
- $db = Database::users();
-
- $statement = $db->prepare('SELECT * FROM user_accounts WHERE email = :email AND status = :status LIMIT 1');
- $statement->execute(['email' => $email, 'status' => 'active']);
- $account = $statement->fetch();
-
- if (!$account || !password_verify($password, $account['password_hash'])) {
- return false;
- }
-
- self::startSession((int) $account['id'], (string) $account['password_hash']);
-
- $update = $db->prepare('UPDATE user_accounts SET last_login_at = NOW() WHERE id = :id');
- $update->execute(['id' => $account['id']]);
-
- return true;
- }
-
- private static function startSession(int $userId, string $passwordHash): void
- {
- session_regenerate_id(true);
- $_SESSION['user_account_id'] = $userId;
- $_SESSION['user_pw_fp'] = hash('sha256', $passwordHash);
- self::$current = null;
- }
-
- public static function refreshPasswordFingerprint(int $userId): void
- {
- $statement = Database::users()->prepare('SELECT password_hash FROM user_accounts WHERE id = :id LIMIT 1');
- $statement->execute(['id' => $userId]);
- $_SESSION['user_pw_fp'] = hash('sha256', (string) $statement->fetchColumn());
- self::$current = null;
- }
-
- public static function verifyPassword(int $userId, string $password): bool
- {
- $statement = Database::users()->prepare('SELECT password_hash FROM user_accounts WHERE id = :id LIMIT 1');
- $statement->execute(['id' => $userId]);
-
- return password_verify($password, (string) $statement->fetchColumn());
- }
-
- public static function logout(): void
- {
- unset($_SESSION['user_account_id'], $_SESSION['user_pw_fp']);
- self::$current = null;
- if (session_status() === PHP_SESSION_ACTIVE && !headers_sent()) {
- session_regenerate_id(true);
- }
- }
-
- public static function check(): bool
- {
- return self::user() !== null;
- }
-
- public static function user(): ?array
- {
- if (!isset($_SESSION['user_account_id'])) {
- return null;
- }
-
- if (self::$current !== null) {
- return self::$current;
- }
-
- $statement = Database::users()->prepare('SELECT * FROM user_accounts WHERE id = :id LIMIT 1');
- $statement->execute(['id' => $_SESSION['user_account_id']]);
- $account = $statement->fetch();
-
- $fingerprintMatches = isset($_SESSION['user_pw_fp'])
- && $account
- && hash_equals((string) $_SESSION['user_pw_fp'], hash('sha256', (string) $account['password_hash']));
-
- if (!$account || $account['status'] !== 'active' || !$fingerprintMatches) {
- self::logout();
- return null;
- }
-
- self::$current = $account;
- return self::$current;
- }
-
- public static function hasActiveSubscription(int $userAccountId): bool
- {
- $statement = Database::users()->prepare(
- "SELECT id FROM subscriptions WHERE user_account_id = :id AND status IN ('active', 'trialing') AND (current_period_end IS NULL OR current_period_end > NOW()) LIMIT 1"
- );
- $statement->execute(['id' => $userAccountId]);
-
- return (bool) $statement->fetch();
- }
-
- public static function requireLogin(): void
- {
- if (!self::check()) {
- header('Location: user-login.php');
- exit;
- }
- }
- }
-