mirror of
git://f0xx.org/ac/ac-ms-identity
synced 2026-07-30 03:38:12 +03:00
initial
This commit is contained in:
101
src/AuthRegistration.php
Normal file
101
src/AuthRegistration.php
Normal file
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/** Email self-registration (verify link). TOTP enrollment follows login — task 3.x. */
|
||||
final class AuthRegistration {
|
||||
private const TOKEN_TTL_S = 86400;
|
||||
|
||||
private function __construct() {
|
||||
}
|
||||
|
||||
public static function ensureSchema(): void {
|
||||
AuthEmailSchema::ensure(Database::pdo());
|
||||
}
|
||||
|
||||
/** @return array{ok:bool,error?:string} */
|
||||
public static function register(string $email, string $password, string $username = ''): array {
|
||||
self::ensureSchema();
|
||||
if (AuthAttempts::isRateLimited($username !== '' ? $username : $email)) {
|
||||
return ['ok' => false, 'error' => 'rate_limited'];
|
||||
}
|
||||
$emailNorm = self::normalizeEmail($email);
|
||||
if ($emailNorm === '' || !filter_var($emailNorm, FILTER_VALIDATE_EMAIL)) {
|
||||
AuthAttempts::record('register_fail', $username ?: $email);
|
||||
return ['ok' => false, 'error' => 'invalid_email'];
|
||||
}
|
||||
if (strlen($password) < 10) {
|
||||
AuthAttempts::record('register_fail', $username ?: $email);
|
||||
return ['ok' => false, 'error' => 'weak_password'];
|
||||
}
|
||||
$username = trim($username);
|
||||
if ($username === '') {
|
||||
$username = strstr($emailNorm, '@', true) ?: $emailNorm;
|
||||
}
|
||||
$pdo = Database::pdo();
|
||||
$st = $pdo->prepare('SELECT id FROM users WHERE email_normalized = ? OR username = ? LIMIT 1');
|
||||
$st->execute([$emailNorm, $username]);
|
||||
if ($st->fetchColumn()) {
|
||||
AuthAttempts::record('register_fail', $username);
|
||||
return ['ok' => false, 'error' => 'already_registered'];
|
||||
}
|
||||
$hash = password_hash($password, PASSWORD_DEFAULT);
|
||||
$pdo->prepare(
|
||||
'INSERT INTO users (username, password_hash, role, email_normalized, status) VALUES (?, ?, ?, ?, ?)'
|
||||
)->execute([
|
||||
$username,
|
||||
$hash,
|
||||
'viewer',
|
||||
$emailNorm,
|
||||
'pending',
|
||||
]);
|
||||
$userId = (int) $pdo->lastInsertId();
|
||||
$token = bin2hex(random_bytes(32));
|
||||
$tokenHash = hash('sha256', $token);
|
||||
$expires = date('Y-m-d H:i:s', time() + self::TOKEN_TTL_S);
|
||||
$pdo->prepare(
|
||||
'INSERT INTO auth_tokens (user_id, purpose, token_hash, email_normalized, expires_at, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?)'
|
||||
)->execute([$userId, 'verify_email', $tokenHash, $emailNorm, $expires, date('Y-m-d H:i:s')]);
|
||||
$base = rtrim((string) cfg('base_path', ''), '/');
|
||||
$verifyUrl = 'https://apps.f0xx.org' . $base . '/verify-email?token=' . urlencode($token);
|
||||
if (!AuthMailer::sendVerifyEmail($emailNorm, $verifyUrl)) {
|
||||
error_log('AuthRegistration: verify mail failed for ' . $emailNorm);
|
||||
}
|
||||
AuthAttempts::record('register_ok', $username);
|
||||
return ['ok' => true];
|
||||
}
|
||||
|
||||
/** @return array{ok:bool,error?:string} */
|
||||
public static function verifyEmailToken(string $token): array {
|
||||
self::ensureSchema();
|
||||
$token = trim($token);
|
||||
if ($token === '') {
|
||||
return ['ok' => false, 'error' => 'missing_token'];
|
||||
}
|
||||
$hash = hash('sha256', $token);
|
||||
$pdo = Database::pdo();
|
||||
$st = $pdo->prepare(
|
||||
"SELECT * FROM auth_tokens WHERE token_hash = ? AND purpose = 'verify_email' AND used_at IS NULL LIMIT 1"
|
||||
);
|
||||
$st->execute([$hash]);
|
||||
$row = $st->fetch(PDO::FETCH_ASSOC);
|
||||
if (!$row) {
|
||||
return ['ok' => false, 'error' => 'invalid_token'];
|
||||
}
|
||||
$exp = strtotime((string) ($row['expires_at'] ?? ''));
|
||||
if ($exp !== false && $exp < time()) {
|
||||
return ['ok' => false, 'error' => 'expired_token'];
|
||||
}
|
||||
$userId = (int) ($row['user_id'] ?? 0);
|
||||
$now = date('Y-m-d H:i:s');
|
||||
$pdo->prepare('UPDATE auth_tokens SET used_at = ? WHERE id = ?')->execute([$now, (int) $row['id']]);
|
||||
$pdo->prepare(
|
||||
"UPDATE users SET email_verified_at = ?, status = 'active' WHERE id = ?"
|
||||
)->execute([$now, $userId]);
|
||||
return ['ok' => true];
|
||||
}
|
||||
|
||||
private static function normalizeEmail(string $email): string {
|
||||
return strtolower(trim($email));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user