b14f4c1796
New licensed lake, picnic, dining, server, burger, and food photography. Rebuilt homepage gallery, logo, colors, copy, menu, specials, and exports. Added dashboard-configurable branding in [settings.php (line 18)](/Users/tyemeclifford/Documents/GH/fatbottomgrille/views/admin/settings.php:18). Added a public attribution page and full [image credits](/Users/tyemeclifford/Documents/GH/fatbottomgrille/public/assets/images/CREDITS.md). Migrated existing SQLite menu data and admin email to admin@lakesideorders.test. Verified 51 PHP files, JSON/JavaScript, storefront pages, database migration, admin settings, and menu PDF. All passed.
56 lines
1.8 KiB
PHP
56 lines
1.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace FatBottom\Services;
|
|
|
|
use FatBottom\Core\Config;
|
|
use FatBottom\Core\Database;
|
|
|
|
final class TwoFactorService
|
|
{
|
|
public function __construct(
|
|
private readonly Database $db,
|
|
private readonly Mailer $mailer,
|
|
private readonly Config $config
|
|
) {
|
|
}
|
|
|
|
public function issue(array $user, string $purpose = 'login'): ?string
|
|
{
|
|
$code = (string) random_int(100000, 999999);
|
|
$expiresAt = gmdate('Y-m-d H:i:s', time() + 600);
|
|
|
|
$this->db->execute(
|
|
'INSERT INTO login_codes (user_id, code_hash, purpose, expires_at) VALUES (?, ?, ?, ?)',
|
|
[(int) $user['id'], password_hash($code, PASSWORD_DEFAULT), $purpose, $expiresAt]
|
|
);
|
|
|
|
$name = htmlspecialchars((string) $user['full_name'], ENT_QUOTES, 'UTF-8');
|
|
$businessName = (string) $this->config->get('business.name', 'Lakeside Orders');
|
|
$this->mailer->send(
|
|
(string) $user['email'],
|
|
"Your {$businessName} verification code",
|
|
"<p>Hi {$name},</p><p>Your verification code is <strong>{$code}</strong>.</p><p>It expires in 10 minutes.</p>"
|
|
);
|
|
|
|
return (bool) $this->config->get('mailgun.enabled', false) ? null : $code;
|
|
}
|
|
|
|
public function verify(int $userId, string $code, string $purpose = 'login'): bool
|
|
{
|
|
$record = $this->db->fetch(
|
|
'SELECT * FROM login_codes
|
|
WHERE user_id = ? AND purpose = ? AND used_at IS NULL AND expires_at > CURRENT_TIMESTAMP
|
|
ORDER BY id DESC LIMIT 1',
|
|
[$userId, $purpose]
|
|
);
|
|
if ($record === null || !password_verify($code, (string) $record['code_hash'])) {
|
|
return false;
|
|
}
|
|
|
|
$this->db->execute('UPDATE login_codes SET used_at = CURRENT_TIMESTAMP WHERE id = ?', [(int) $record['id']]);
|
|
return true;
|
|
}
|
|
}
|