forked from fa/breadcrumb-the-shire
baseline
This commit is contained in:
157
lib/Service/RememberMeService.php
Normal file
157
lib/Service/RememberMeService.php
Normal file
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Service;
|
||||
|
||||
use MintyPHP\Session;
|
||||
use MintyPHP\Repository\RememberTokenRepository;
|
||||
use MintyPHP\Repository\UserRepository;
|
||||
use MintyPHP\I18n;
|
||||
use MintyPHP\Service\PermissionService;
|
||||
use MintyPHP\Service\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);
|
||||
}
|
||||
|
||||
$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);
|
||||
}
|
||||
|
||||
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