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>
64 lines
2.2 KiB
PHP
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]);
|