Publier Globinours 1.0.0-rc.3
This commit is contained in:
parent
ea8c24d622
commit
9a2b4068da
325 changed files with 38230 additions and 20 deletions
144
app/Services/TrustedDeviceService.php
Normal file
144
app/Services/TrustedDeviceService.php
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class TrustedDeviceService
|
||||
{
|
||||
private const COOKIE = 'globinours_device';
|
||||
public static function create(int $userId, string $name = ''): void
|
||||
{
|
||||
$selector = bin2hex(random_bytes(9));
|
||||
$token = random_bytes(32);
|
||||
$expires = time() + 60 * 60 * 24 * 90;
|
||||
$name = trim($name) ?: self::deviceLabel();
|
||||
DB::pdo()
|
||||
->prepare(
|
||||
'INSERT INTO trusted_devices(user_id,selector,token_hash,device_name,user_agent,ip_address,expires_at) VALUES(?,?,?,?,?,?,?)',
|
||||
)
|
||||
->execute([
|
||||
$userId,
|
||||
$selector,
|
||||
hash('sha256', $token),
|
||||
mb_substr($name, 0, 100),
|
||||
mb_substr((string) ($_SERVER['HTTP_USER_AGENT'] ?? ''), 0, 500) ?: null,
|
||||
$_SERVER['REMOTE_ADDR'] ?? null,
|
||||
date('Y-m-d H:i:s', $expires),
|
||||
]);
|
||||
self::cookie($selector . '.' . bin2hex($token), $expires);
|
||||
}
|
||||
public static function authenticate(): ?int
|
||||
{
|
||||
$raw = (string) ($_COOKIE[self::COOKIE] ?? '');
|
||||
if (!preg_match('/^([a-f0-9]{18})\.([a-f0-9]{64})$/', $raw, $m)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
$s = DB::pdo()->prepare(
|
||||
"SELECT td.*,u.active FROM trusted_devices td JOIN users u ON u.id=td.user_id WHERE td.selector=? AND td.revoked_at IS NULL AND datetime(td.expires_at)>datetime('now') AND (td.last_used_at IS NULL OR datetime(td.last_used_at)>datetime('now','-30 days'))",
|
||||
);
|
||||
$s->execute([$m[1]]);
|
||||
$row = $s->fetch(PDO::FETCH_ASSOC);
|
||||
if (
|
||||
!$row ||
|
||||
!hash_equals((string) $row['token_hash'], hash('sha256', hex2bin($m[2]) ?: '')) ||
|
||||
(int) $row['active'] !== 1
|
||||
) {
|
||||
self::forget();
|
||||
return null;
|
||||
}
|
||||
$new = random_bytes(32);
|
||||
DB::pdo()
|
||||
->prepare(
|
||||
"UPDATE trusted_devices SET token_hash=?,last_used_at=datetime('now'),ip_address=? WHERE id=?",
|
||||
)
|
||||
->execute([hash('sha256', $new), $_SERVER['REMOTE_ADDR'] ?? null, $row['id']]);
|
||||
self::cookie($m[1] . '.' . bin2hex($new), strtotime((string) $row['expires_at']));
|
||||
return (int) $row['user_id'];
|
||||
} catch (Throwable) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
public static function currentSelector(): ?string
|
||||
{
|
||||
$raw = (string) ($_COOKIE[self::COOKIE] ?? '');
|
||||
return preg_match('/^([a-f0-9]{18})\./', $raw, $m) ? $m[1] : null;
|
||||
}
|
||||
public static function revokeCurrent(): void
|
||||
{
|
||||
$selector = self::currentSelector();
|
||||
if ($selector) {
|
||||
try {
|
||||
DB::pdo()
|
||||
->prepare("UPDATE trusted_devices SET revoked_at=datetime('now') WHERE selector=?")
|
||||
->execute([$selector]);
|
||||
} catch (Throwable) {
|
||||
}
|
||||
}
|
||||
self::forget();
|
||||
}
|
||||
public static function devices(?int $userId = null): array
|
||||
{
|
||||
$userId ??= Auth::id();
|
||||
$s = DB::pdo()->prepare(
|
||||
'SELECT * FROM trusted_devices WHERE user_id=? ORDER BY revoked_at IS NULL DESC,last_used_at DESC',
|
||||
);
|
||||
$s->execute([$userId]);
|
||||
return $s->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
public static function revoke(int $id, int $userId, bool $admin = false): void
|
||||
{
|
||||
$sql =
|
||||
'UPDATE trusted_devices SET revoked_at=datetime(\'now\') WHERE id=:id' .
|
||||
($admin ? '' : ' AND user_id=:uid');
|
||||
$params = [':id' => $id];
|
||||
if (!$admin) {
|
||||
$params[':uid'] = $userId;
|
||||
}
|
||||
DB::pdo()->prepare($sql)->execute($params);
|
||||
if (
|
||||
self::currentSelector() &&
|
||||
self::currentSelector() ===
|
||||
(string) DB::pdo()
|
||||
->query('SELECT selector FROM trusted_devices WHERE id=' . (int) $id)
|
||||
->fetchColumn()
|
||||
) {
|
||||
self::forget();
|
||||
}
|
||||
}
|
||||
private static function deviceLabel(): string
|
||||
{
|
||||
$ua = strtolower((string) ($_SERVER['HTTP_USER_AGENT'] ?? ''));
|
||||
if (str_contains($ua, 'globinoursandroid')) {
|
||||
return 'Application Android';
|
||||
}
|
||||
if (str_contains($ua, 'android')) {
|
||||
return 'Smartphone ou tablette Android';
|
||||
}
|
||||
if (str_contains($ua, 'iphone') || str_contains($ua, 'ipad')) {
|
||||
return 'iPhone ou iPad';
|
||||
}
|
||||
return 'Navigateur web';
|
||||
}
|
||||
private static function cookie(string $value, int $expires): void
|
||||
{
|
||||
setcookie(self::COOKIE, $value, [
|
||||
'expires' => $expires,
|
||||
'path' => '/',
|
||||
'secure' => Auth::isSecureRequest(),
|
||||
'httponly' => true,
|
||||
'samesite' => 'Strict',
|
||||
]);
|
||||
$_COOKIE[self::COOKIE] = $value;
|
||||
}
|
||||
private static function forget(): void
|
||||
{
|
||||
setcookie(self::COOKIE, '', [
|
||||
'expires' => time() - 3600,
|
||||
'path' => '/',
|
||||
'secure' => Auth::isSecureRequest(),
|
||||
'httponly' => true,
|
||||
'samesite' => 'Strict',
|
||||
]);
|
||||
unset($_COOKIE[self::COOKIE]);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue