Files
reflector-dashboard/auth/login.php
T
bjorneandClaude Fable 5.1 25b481e2ae Public web-only release: SvxPortal front-end + auth backend, install templates and INSTALL.md
Stripped from the internal repo: per-box deploy trees, Go services (nodelist/astmeta/proxy/svxdr),
deploy/sync scripts and all secrets. Config templates for nginx, PHP-FPM, Redis and tmpfiles
live in install/ with CHANGEME placeholders.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-26 23:07:05 +02:00

64 lines
2.2 KiB
PHP

<?php
declare(strict_types=1);
require __DIR__ . '/lib.php';
$j = json_in();
$user = trim((string)($j['user'] ?? ''));
$pass = (string)($j['pass'] ?? '');
if ($user === '' || $pass === '') json_out(['ok'=>false,'error'=>'missing'], 400);
// Rate limit: 5 attempts per 5 minutes per IP
$ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
rate_limit("login:{$ip}", 5, 300);
$uuid = find_acct_by_login($user);
if (!$uuid) json_out(['ok'=>false,'error'=>'invalid_login'], 401);
$acct = acct_get($uuid);
if (!$acct) json_out(['ok'=>false,'error'=>'invalid_login'], 401);
// Check global callsign ban (blocks the callsign on the portal)
if (is_call_banned((string)($acct['callsign'] ?? ''))) {
json_out(['ok'=>false, 'error'=>'Your callsign has been banned.'], 403);
}
// Check banned
if ((int)($acct['banned'] ?? 0) === 1) {
$reason = $acct['ban_reason'] ?? '';
$msg = 'Your account has been banned.';
if ($reason !== '') $msg .= ' Reason: ' . $reason;
json_out(['ok'=>false, 'error'=>$msg], 403);
}
// Check locked
if ((int)($acct['locked'] ?? 0) === 1) {
$reason = $acct['lock_reason'] ?? '';
$msg = 'Your account is locked.';
if ($reason !== '') $msg .= ' Reason: ' . $reason;
json_out(['ok'=>false, 'error'=>$msg], 403);
}
// Check email verified
if (!isset($acct['email_verified']) || (int)$acct['email_verified'] !== 1) {
json_out(['ok'=>false,'code'=>'unverified','error'=>'Your email address has not been verified. Check your inbox for the verification link.'], 403);
}
// Check approved: accounts with an acct_flags field need the normal-bit (4) to get in.
// Accounts from before the flags system have no field and pass as before.
$flags = ($acct['acct_flags'] ?? '') === '' ? null : ((int)$acct['acct_flags'] & 0x3F);
if ($flags !== null && ($flags & 4) === 0) {
json_out(['ok'=>false,'code'=>'unapproved','error'=>'Your account is registered and waiting for an admin to approve it before you can log in.'], 403);
}
$hash = (string)($acct['password_hash'] ?? '');
if ($hash === '' || !password_verify($pass, $hash)) {
json_out(['ok'=>false,'error'=>'invalid_login'], 401);
}
rate_limit_clear("login:{$ip}");
set_session_user($uuid);
redis()->hSet(acct_key($uuid), 'last_login_at', (string)time());
json_out(['ok'=>true]);