big restructure
This commit is contained in:
164
lib/Service/Auth/AuthService.php
Normal file
164
lib/Service/Auth/AuthService.php
Normal file
@@ -0,0 +1,164 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Service\Auth;
|
||||
|
||||
use MintyPHP\Auth;
|
||||
use MintyPHP\Router;
|
||||
use MintyPHP\Support\Flash;
|
||||
use MintyPHP\Service\User\UserService;
|
||||
use MintyPHP\Service\Access\PermissionService;
|
||||
use MintyPHP\Service\Auth\RememberMeService;
|
||||
use MintyPHP\Service\Auth\EmailVerificationService;
|
||||
use MintyPHP\Repository\User\UserRepository;
|
||||
|
||||
class AuthService
|
||||
{
|
||||
public static function login(string $email, string $password): array
|
||||
{
|
||||
$email = trim($email);
|
||||
|
||||
// Check if email is verified before attempting login
|
||||
$existingUser = UserRepository::findByEmail($email);
|
||||
if ($existingUser && empty($existingUser['email_verified_at'])) {
|
||||
return [
|
||||
'ok' => false,
|
||||
'message' => 'Please verify your email first',
|
||||
'flash_key' => 'login_not_verified',
|
||||
'needs_verification' => true,
|
||||
'email' => $email,
|
||||
];
|
||||
}
|
||||
|
||||
$user = Auth::login($email, $password);
|
||||
|
||||
if (!$user) {
|
||||
return [
|
||||
'ok' => false,
|
||||
'message' => 'Email/password not valid',
|
||||
'flash_key' => 'login_invalid',
|
||||
];
|
||||
}
|
||||
|
||||
if (!($_SESSION['user']['active'] ?? 1)) {
|
||||
Auth::logout();
|
||||
return [
|
||||
'ok' => false,
|
||||
'message' => 'Account is inactive',
|
||||
'flash_key' => 'login_inactive',
|
||||
];
|
||||
}
|
||||
|
||||
$userId = (int) ($_SESSION['user']['id'] ?? 0);
|
||||
if ($userId > 0) {
|
||||
PermissionService::getUserPermissions($userId, true);
|
||||
self::loadTenantDataIntoSession($userId);
|
||||
if (!empty($_SESSION['no_active_tenant'])) {
|
||||
Auth::logout();
|
||||
return [
|
||||
'ok' => false,
|
||||
'message' => 'No active tenant assigned',
|
||||
'flash_key' => 'login_no_active_tenant',
|
||||
];
|
||||
}
|
||||
UserRepository::updateLastLogin($userId);
|
||||
}
|
||||
|
||||
return ['ok' => true];
|
||||
}
|
||||
|
||||
public static function loginAndRedirect(
|
||||
string $email,
|
||||
string $password,
|
||||
string $successTarget = 'admin',
|
||||
string $failTarget = 'login',
|
||||
bool $remember = false
|
||||
): void {
|
||||
$result = self::login($email, $password);
|
||||
|
||||
if (!($result['ok'] ?? false)) {
|
||||
// Check if user needs to verify email
|
||||
if (!empty($result['needs_verification'])) {
|
||||
$_SESSION['email_verification_email'] = $result['email'] ?? $email;
|
||||
Flash::error(t('Please verify your email first'), 'verify-email', 'login_not_verified');
|
||||
Router::redirect('verify-email');
|
||||
}
|
||||
|
||||
$message = $result['message'] ?? 'Email/password not valid';
|
||||
$key = $result['flash_key'] ?? 'login_invalid';
|
||||
Flash::error($message, $failTarget, $key);
|
||||
Router::redirect($failTarget);
|
||||
}
|
||||
|
||||
if ($remember) {
|
||||
$userId = (int) ($_SESSION['user']['id'] ?? 0);
|
||||
if ($userId > 0) {
|
||||
RememberMeService::rememberUser($userId);
|
||||
}
|
||||
}
|
||||
Router::redirect($successTarget);
|
||||
}
|
||||
|
||||
public static function register(array $data): array
|
||||
{
|
||||
$email = trim((string) ($data['email'] ?? ''));
|
||||
$result = UserService::register($data);
|
||||
if (!($result['ok'] ?? false)) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Get the created user to send verification email
|
||||
$createdUser = UserRepository::findByEmail($email);
|
||||
if ($createdUser && isset($createdUser['id'])) {
|
||||
$userId = (int) $createdUser['id'];
|
||||
EmailVerificationService::sendVerification($userId);
|
||||
}
|
||||
|
||||
// Don't login - user needs to verify email first
|
||||
return ['ok' => true, 'email' => $email, 'needs_verification' => true];
|
||||
}
|
||||
|
||||
public static function logout(): void
|
||||
{
|
||||
RememberMeService::forgetCurrentUser();
|
||||
Auth::logout();
|
||||
}
|
||||
|
||||
public static function logoutAndRedirect(string $target = 'login'): void
|
||||
{
|
||||
RememberMeService::forgetCurrentUser();
|
||||
Auth::logout();
|
||||
Router::redirect($target);
|
||||
}
|
||||
|
||||
public static function loadTenantDataIntoSession(int $userId): void
|
||||
{
|
||||
if ($userId <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Load available tenants first
|
||||
$availableTenants = UserService::getAvailableTenants($userId);
|
||||
$_SESSION['available_tenants'] = $availableTenants;
|
||||
$_SESSION['available_departments_by_tenant'] = UserService::getAvailableDepartmentsByTenant($userId);
|
||||
|
||||
$hasTenantAdminAccess = PermissionService::userHas($userId, PermissionService::TENANTS_UPDATE)
|
||||
|| PermissionService::userHas($userId, PermissionService::SETTINGS_UPDATE);
|
||||
|
||||
if (!$availableTenants && !$hasTenantAdminAccess) {
|
||||
$_SESSION['no_active_tenant'] = true;
|
||||
unset($_SESSION['current_tenant']);
|
||||
return;
|
||||
}
|
||||
$_SESSION['no_active_tenant'] = false;
|
||||
|
||||
// Load current tenant (with fallback to first available)
|
||||
$currentTenant = UserService::getCurrentTenant($userId);
|
||||
if (!$currentTenant && !empty($availableTenants)) {
|
||||
// Fallback: use first available tenant
|
||||
$currentTenant = $availableTenants[0];
|
||||
}
|
||||
if ($currentTenant) {
|
||||
$_SESSION['current_tenant'] = $currentTenant;
|
||||
}
|
||||
}
|
||||
}
|
||||
146
lib/Service/Auth/EmailVerificationService.php
Normal file
146
lib/Service/Auth/EmailVerificationService.php
Normal file
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Service\Auth;
|
||||
|
||||
use MintyPHP\Repository\Auth\EmailVerificationRepository;
|
||||
use MintyPHP\Repository\User\UserRepository;
|
||||
use MintyPHP\I18n;
|
||||
use MintyPHP\Http\Request;
|
||||
|
||||
class EmailVerificationService
|
||||
{
|
||||
private const CODE_LENGTH = 6;
|
||||
private const EXPIRY_MINUTES = 30;
|
||||
private const MAX_ATTEMPTS = 5;
|
||||
|
||||
public static function sendVerification(int $userId, ?string $locale = null): array
|
||||
{
|
||||
$user = UserRepository::find($userId);
|
||||
if (!$user || !isset($user['id'])) {
|
||||
return ['ok' => false, 'error' => 'user_not_found'];
|
||||
}
|
||||
|
||||
$email = (string) ($user['email'] ?? '');
|
||||
if ($email === '') {
|
||||
return ['ok' => false, 'error' => 'email_required'];
|
||||
}
|
||||
|
||||
EmailVerificationRepository::invalidateForUser($userId);
|
||||
|
||||
$code = self::generateCode();
|
||||
$codeHash = password_hash($code, PASSWORD_DEFAULT);
|
||||
$expiresAt = gmdate('Y-m-d H:i:s', time() + (self::EXPIRY_MINUTES * 60));
|
||||
|
||||
$verificationId = EmailVerificationRepository::create($userId, $codeHash, $expiresAt);
|
||||
if (!$verificationId) {
|
||||
return ['ok' => false, 'error' => 'create_failed'];
|
||||
}
|
||||
|
||||
$locale = $locale ?: ($user['locale'] ?? null) ?: (I18n::$locale ?? I18n::$defaultLocale);
|
||||
$name = trim(($user['first_name'] ?? '') . ' ' . ($user['last_name'] ?? ''));
|
||||
$isGerman = strpos((string) $locale, 'de') === 0;
|
||||
$greeting = $isGerman ? 'Hallo' : 'Hello';
|
||||
if ($name !== '') {
|
||||
$greeting .= ' ' . $name;
|
||||
}
|
||||
$greeting .= ',';
|
||||
$verifyPath = Request::withLocale('verify-email', $locale);
|
||||
$verifyUrl = appUrl($verifyPath);
|
||||
$previousLocale = I18n::$locale ?? null;
|
||||
I18n::$locale = $locale;
|
||||
$subject = t('Email verification code');
|
||||
I18n::$locale = $previousLocale;
|
||||
|
||||
$vars = [
|
||||
'app_name' => appTitle(),
|
||||
'app_logo_url' => appLogoUrlAbsolute(128),
|
||||
'imprint_url' => appUrl(Request::withLocale('imprint', $locale)),
|
||||
'privacy_url' => appUrl(Request::withLocale('privacy', $locale)),
|
||||
'code' => $code,
|
||||
'expires_minutes' => self::EXPIRY_MINUTES,
|
||||
'verify_url' => $verifyUrl,
|
||||
'greeting' => $greeting,
|
||||
];
|
||||
MailService::sendTemplate('email_verification', $vars, $email, $subject, $locale);
|
||||
|
||||
return ['ok' => true];
|
||||
}
|
||||
|
||||
public static function verifyCode(string $email, string $code): array
|
||||
{
|
||||
$email = trim($email);
|
||||
$code = trim($code);
|
||||
if ($email === '' || $code === '') {
|
||||
return ['ok' => false, 'error' => 'invalid'];
|
||||
}
|
||||
|
||||
$user = UserRepository::findByEmail($email);
|
||||
if (!$user || !isset($user['id'])) {
|
||||
return ['ok' => false, 'error' => 'invalid'];
|
||||
}
|
||||
|
||||
$userId = (int) $user['id'];
|
||||
|
||||
// Check if already verified
|
||||
if (!empty($user['email_verified_at'])) {
|
||||
return ['ok' => false, 'error' => 'already_verified'];
|
||||
}
|
||||
|
||||
$verification = EmailVerificationRepository::findActiveByUserId($userId);
|
||||
if (!$verification || !isset($verification['id'])) {
|
||||
return ['ok' => false, 'error' => 'invalid'];
|
||||
}
|
||||
|
||||
$attempts = (int) ($verification['attempts'] ?? 0);
|
||||
if ($attempts >= self::MAX_ATTEMPTS) {
|
||||
return ['ok' => false, 'error' => 'too_many_attempts'];
|
||||
}
|
||||
|
||||
$hash = (string) ($verification['code_hash'] ?? '');
|
||||
if ($hash === '' || !password_verify($code, $hash)) {
|
||||
EmailVerificationRepository::incrementAttempts((int) $verification['id']);
|
||||
return ['ok' => false, 'error' => 'invalid'];
|
||||
}
|
||||
|
||||
// Mark verification as used
|
||||
EmailVerificationRepository::markUsed((int) $verification['id']);
|
||||
|
||||
// Mark user email as verified
|
||||
UserRepository::setEmailVerified($userId);
|
||||
|
||||
return ['ok' => true, 'user_id' => $userId];
|
||||
}
|
||||
|
||||
public static function resendVerification(string $email, ?string $locale = null): array
|
||||
{
|
||||
$email = trim($email);
|
||||
if ($email === '') {
|
||||
return ['ok' => false, 'error' => 'email_required'];
|
||||
}
|
||||
|
||||
$user = UserRepository::findByEmail($email);
|
||||
if (!$user || !isset($user['id'])) {
|
||||
// Don't reveal if user exists
|
||||
return ['ok' => true];
|
||||
}
|
||||
|
||||
// Check if already verified
|
||||
if (!empty($user['email_verified_at'])) {
|
||||
return ['ok' => false, 'error' => 'already_verified'];
|
||||
}
|
||||
|
||||
return self::sendVerification((int) $user['id'], $locale);
|
||||
}
|
||||
|
||||
public static function getExpiryMinutes(): int
|
||||
{
|
||||
return self::EXPIRY_MINUTES;
|
||||
}
|
||||
|
||||
private static function generateCode(): string
|
||||
{
|
||||
$max = (10 ** self::CODE_LENGTH) - 1;
|
||||
$code = (string) random_int(0, $max);
|
||||
return str_pad($code, self::CODE_LENGTH, '0', STR_PAD_LEFT);
|
||||
}
|
||||
}
|
||||
147
lib/Service/Auth/PasswordResetService.php
Normal file
147
lib/Service/Auth/PasswordResetService.php
Normal file
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Service\Auth;
|
||||
|
||||
use MintyPHP\Repository\Auth\PasswordResetRepository;
|
||||
use MintyPHP\Repository\User\UserRepository;
|
||||
use MintyPHP\I18n;
|
||||
use MintyPHP\Http\Request;
|
||||
use MintyPHP\Service\Auth\RememberMeService;
|
||||
|
||||
class PasswordResetService
|
||||
{
|
||||
private const CODE_LENGTH = 6;
|
||||
private const EXPIRY_MINUTES = 15;
|
||||
private const MAX_ATTEMPTS = 5;
|
||||
|
||||
public static function requestReset(string $email, ?string $locale = null): array
|
||||
{
|
||||
$email = trim($email);
|
||||
if ($email === '') {
|
||||
return ['ok' => false, 'error' => 'email_required'];
|
||||
}
|
||||
|
||||
$user = UserRepository::findByEmail($email);
|
||||
if (!$user || !isset($user['id'])) {
|
||||
return ['ok' => true];
|
||||
}
|
||||
|
||||
$userId = (int) $user['id'];
|
||||
PasswordResetRepository::invalidateForUser($userId);
|
||||
|
||||
$code = self::generateCode();
|
||||
$codeHash = password_hash($code, PASSWORD_DEFAULT);
|
||||
$expiresAt = gmdate('Y-m-d H:i:s', time() + (self::EXPIRY_MINUTES * 60));
|
||||
|
||||
$resetId = PasswordResetRepository::create($userId, $codeHash, $expiresAt);
|
||||
if (!$resetId) {
|
||||
return ['ok' => false, 'error' => 'create_failed'];
|
||||
}
|
||||
|
||||
$locale = $locale ?: ($user['locale'] ?? null) ?: (I18n::$locale ?? I18n::$defaultLocale);
|
||||
$name = trim(($user['first_name'] ?? '') . ' ' . ($user['last_name'] ?? ''));
|
||||
$isGerman = strpos((string) $locale, 'de') === 0;
|
||||
$greeting = $isGerman ? 'Hallo' : 'Hello';
|
||||
if ($name !== '') {
|
||||
$greeting .= ' ' . $name;
|
||||
}
|
||||
$greeting .= ',';
|
||||
$verifyPath = Request::withLocale('password/verify', $locale);
|
||||
$verifyUrl = appUrl($verifyPath);
|
||||
$previousLocale = I18n::$locale ?? null;
|
||||
I18n::$locale = $locale;
|
||||
$subject = t('Password reset code');
|
||||
I18n::$locale = $previousLocale;
|
||||
|
||||
$vars = [
|
||||
'app_name' => appTitle(),
|
||||
'app_logo_url' => appLogoUrlAbsolute(128),
|
||||
'imprint_url' => appUrl(Request::withLocale('imprint', $locale)),
|
||||
'privacy_url' => appUrl(Request::withLocale('privacy', $locale)),
|
||||
'code' => $code,
|
||||
'expires_minutes' => self::EXPIRY_MINUTES,
|
||||
'verify_url' => $verifyUrl,
|
||||
'greeting' => $greeting,
|
||||
];
|
||||
MailService::sendTemplate('reset_code', $vars, $email, $subject, $locale);
|
||||
|
||||
return ['ok' => true];
|
||||
}
|
||||
|
||||
public static function verifyCode(string $email, string $code): array
|
||||
{
|
||||
$email = trim($email);
|
||||
$code = trim($code);
|
||||
if ($email === '' || $code === '') {
|
||||
return ['ok' => false, 'error' => 'invalid'];
|
||||
}
|
||||
|
||||
$user = UserRepository::findByEmail($email);
|
||||
if (!$user || !isset($user['id'])) {
|
||||
return ['ok' => false, 'error' => 'invalid'];
|
||||
}
|
||||
|
||||
$reset = PasswordResetRepository::findActiveByUserId((int) $user['id']);
|
||||
if (!$reset || !isset($reset['id'])) {
|
||||
return ['ok' => false, 'error' => 'invalid'];
|
||||
}
|
||||
|
||||
$attempts = (int) ($reset['attempts'] ?? 0);
|
||||
if ($attempts >= self::MAX_ATTEMPTS) {
|
||||
return ['ok' => false, 'error' => 'too_many_attempts'];
|
||||
}
|
||||
|
||||
$hash = (string) ($reset['code_hash'] ?? '');
|
||||
if ($hash === '' || !password_verify($code, $hash)) {
|
||||
PasswordResetRepository::incrementAttempts((int) $reset['id']);
|
||||
return ['ok' => false, 'error' => 'invalid'];
|
||||
}
|
||||
|
||||
return ['ok' => true, 'reset_id' => (int) $reset['id'], 'user_id' => (int) $user['id']];
|
||||
}
|
||||
|
||||
public static function resetPassword(int $resetId, string $password, string $password2): array
|
||||
{
|
||||
$reset = PasswordResetRepository::findById($resetId);
|
||||
if (!$reset || !isset($reset['id'])) {
|
||||
return ['ok' => false, 'errors' => [t('Reset request not found')]];
|
||||
}
|
||||
|
||||
if (!empty($reset['used_at'])) {
|
||||
return ['ok' => false, 'errors' => [t('Reset request already used')]];
|
||||
}
|
||||
|
||||
$expiresAt = (string) ($reset['expires_at'] ?? '');
|
||||
if ($expiresAt !== '') {
|
||||
try {
|
||||
$expiry = new \DateTimeImmutable($expiresAt, new \DateTimeZone('UTC'));
|
||||
if ($expiry->getTimestamp() <= time()) {
|
||||
return ['ok' => false, 'errors' => [t('Reset request expired')]];
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return ['ok' => false, 'errors' => [t('Reset request expired')]];
|
||||
}
|
||||
}
|
||||
|
||||
$userId = (int) ($reset['user_id'] ?? 0);
|
||||
if ($userId <= 0) {
|
||||
return ['ok' => false, 'errors' => [t('Reset request not found')]];
|
||||
}
|
||||
|
||||
$result = UserService::resetPassword($userId, $password, $password2);
|
||||
if (!($result['ok'] ?? false)) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
PasswordResetRepository::markUsed($resetId);
|
||||
RememberMeService::forgetAllForUser($userId);
|
||||
return ['ok' => true];
|
||||
}
|
||||
|
||||
private static function generateCode(): string
|
||||
{
|
||||
$max = (10 ** self::CODE_LENGTH) - 1;
|
||||
$code = (string) random_int(0, $max);
|
||||
return str_pad($code, self::CODE_LENGTH, '0', STR_PAD_LEFT);
|
||||
}
|
||||
}
|
||||
165
lib/Service/Auth/RememberMeService.php
Normal file
165
lib/Service/Auth/RememberMeService.php
Normal file
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Service\Auth;
|
||||
|
||||
use MintyPHP\Session;
|
||||
use MintyPHP\Repository\Auth\RememberTokenRepository;
|
||||
use MintyPHP\Repository\User\UserRepository;
|
||||
use MintyPHP\I18n;
|
||||
use MintyPHP\Service\Access\PermissionService;
|
||||
use MintyPHP\Service\Auth\AuthService;
|
||||
|
||||
class RememberMeService
|
||||
{
|
||||
private const COOKIE_NAME = 'remember';
|
||||
private const LIFETIME_DAYS = 30;
|
||||
|
||||
public static function rememberUser(int $userId): void
|
||||
{
|
||||
$selector = bin2hex(random_bytes(12));
|
||||
$token = bin2hex(random_bytes(32));
|
||||
$tokenHash = hash('sha256', $token);
|
||||
$expiresAt = gmdate('Y-m-d H:i:s', time() + self::lifetimeSeconds());
|
||||
RememberTokenRepository::create($userId, $selector, $tokenHash, $expiresAt);
|
||||
self::setCookie($selector, $token);
|
||||
}
|
||||
|
||||
public static function autoLoginFromCookie(): bool
|
||||
{
|
||||
if (!empty($_SESSION['user']['id'])) {
|
||||
return false;
|
||||
}
|
||||
$value = $_COOKIE[self::cookieName()] ?? '';
|
||||
if ($value === '' || strpos($value, ':') === false) {
|
||||
return false;
|
||||
}
|
||||
[$selector, $token] = explode(':', $value, 2);
|
||||
$selector = trim($selector);
|
||||
$token = trim($token);
|
||||
if ($selector === '' || $token === '') {
|
||||
self::clearCookie();
|
||||
return false;
|
||||
}
|
||||
|
||||
$record = RememberTokenRepository::findBySelector($selector);
|
||||
if (!$record) {
|
||||
self::clearCookie();
|
||||
return false;
|
||||
}
|
||||
|
||||
$expiresAt = (string) ($record['expires_at'] ?? '');
|
||||
if ($expiresAt !== '' && strtotime($expiresAt . ' UTC') <= time()) {
|
||||
RememberTokenRepository::deleteById((int) $record['id']);
|
||||
self::clearCookie();
|
||||
return false;
|
||||
}
|
||||
|
||||
$hash = (string) ($record['token_hash'] ?? '');
|
||||
if ($hash === '' || !hash_equals($hash, hash('sha256', $token))) {
|
||||
RememberTokenRepository::deleteById((int) $record['id']);
|
||||
self::clearCookie();
|
||||
return false;
|
||||
}
|
||||
|
||||
$userId = (int) ($record['user_id'] ?? 0);
|
||||
if ($userId <= 0) {
|
||||
self::clearCookie();
|
||||
return false;
|
||||
}
|
||||
|
||||
$user = UserRepository::find($userId);
|
||||
if (!$user || empty($user['id']) || !($user['active'] ?? 1)) {
|
||||
RememberTokenRepository::deleteById((int) $record['id']);
|
||||
self::clearCookie();
|
||||
return false;
|
||||
}
|
||||
|
||||
Session::regenerate();
|
||||
$_SESSION['user'] = $user;
|
||||
if (!empty($user['locale'])) {
|
||||
I18n::$locale = (string) $user['locale'];
|
||||
}
|
||||
$userId = (int) ($user['id'] ?? 0);
|
||||
if ($userId > 0) {
|
||||
PermissionService::getUserPermissions($userId, true);
|
||||
AuthService::loadTenantDataIntoSession($userId);
|
||||
if (empty($_SESSION['no_active_tenant'])) {
|
||||
UserRepository::updateLastLogin($userId);
|
||||
}
|
||||
}
|
||||
|
||||
$newToken = bin2hex(random_bytes(32));
|
||||
$newHash = hash('sha256', $newToken);
|
||||
$newExpires = gmdate('Y-m-d H:i:s', time() + self::lifetimeSeconds());
|
||||
RememberTokenRepository::updateToken((int) $record['id'], $newHash, $newExpires);
|
||||
self::setCookie($selector, $newToken);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function forgetCurrentUser(): void
|
||||
{
|
||||
$value = $_COOKIE[self::cookieName()] ?? '';
|
||||
if ($value !== '' && strpos($value, ':') !== false) {
|
||||
[$selector] = explode(':', $value, 2);
|
||||
$selector = trim($selector);
|
||||
if ($selector !== '') {
|
||||
$record = RememberTokenRepository::findBySelector($selector);
|
||||
if ($record && isset($record['id'])) {
|
||||
RememberTokenRepository::deleteById((int) $record['id']);
|
||||
}
|
||||
}
|
||||
}
|
||||
self::clearCookie();
|
||||
}
|
||||
|
||||
public static function forgetAllForUser(int $userId): void
|
||||
{
|
||||
RememberTokenRepository::deleteByUserId($userId);
|
||||
}
|
||||
|
||||
public static function expireAllTokensByAdmin(): int
|
||||
{
|
||||
return RememberTokenRepository::expireAllByAdmin();
|
||||
}
|
||||
|
||||
private static function setCookie(string $selector, string $token): void
|
||||
{
|
||||
$value = $selector . ':' . $token;
|
||||
$expires = time() + self::lifetimeSeconds();
|
||||
$secure = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')
|
||||
|| (($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '') === 'https');
|
||||
|
||||
setcookie(self::cookieName(), $value, [
|
||||
'expires' => $expires,
|
||||
'path' => '/',
|
||||
'secure' => $secure,
|
||||
'httponly' => true,
|
||||
'samesite' => 'Lax',
|
||||
]);
|
||||
}
|
||||
|
||||
private static function clearCookie(): void
|
||||
{
|
||||
$secure = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')
|
||||
|| (($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '') === 'https');
|
||||
setcookie(self::cookieName(), '', [
|
||||
'expires' => time() - 3600,
|
||||
'path' => '/',
|
||||
'secure' => $secure,
|
||||
'httponly' => true,
|
||||
'samesite' => 'Lax',
|
||||
]);
|
||||
}
|
||||
|
||||
private static function lifetimeSeconds(): int
|
||||
{
|
||||
return self::LIFETIME_DAYS * 24 * 60 * 60;
|
||||
}
|
||||
|
||||
private static function cookieName(): string
|
||||
{
|
||||
$name = getenv('REMEMBER_COOKIE_NAME') ?: self::COOKIE_NAME;
|
||||
return trim($name) !== '' ? $name : self::COOKIE_NAME;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user