Files
site_aegisone/service/inc/auth.php
T
angel 72b6879f4b v1.7.0: refactor max_bot to flat structure, add VCF+UserModel+NLP history context, portal pages and proxy fixes
- Refactored max_bot from nested packages to flat module structure
- Q2: Extended BotUser model (patronymic, email, org, address, vcf_raw, contact_hash, phone_verified, email_verified, last_interaction, total_conversations, total_tickets)
- Q2: VCF parser (FN, N, TEL, EMAIL, ORG, ADR), upsert on re-contact, NLP history context (_get_user_history_context -> YandexGPT)
- Q1: Broadcast preview modal with 10s confirmation timer
- Q3: CSS var(--white)->var(--bg-card), var(--text)->var(--text-primary)
- Q4: bot_settings showNotification(), editable max_bot_id
- Q5: Webhook secret passthrough via X-Max-Bot-Api-Secret
- Masking sensitive keys, dialog_cleared handler, migrate via _add_column_if_not_exists()
- Rate limit (asyncio.sleep 0.5 per 10), dead code removed, conv.intent context in contact.py
- Portal pages: bot_consent, bot_kb (edit), bot_settings, bot_test, bot_tickets, portal_settings
- Tests: 21/21 passing, added test_yandex_gpt.py, test_email_sender.py
- Deploy: deploy_full.sh, schema.sql, seed_knowledge_base.sql
2026-05-29 02:30:30 +03:00

140 lines
4.3 KiB
PHP

<?php
require_once __DIR__ . '/../../config.php';
require_once __DIR__ . '/db.php';
/**
* ---------- SESSION ----------
*/
function auth_session_start(): void {
if (session_status() === PHP_SESSION_NONE) {
session_set_cookie_params([
'lifetime' => SERVICE_SESSION_TTL,
'path' => '/service/',
'secure' => !empty($_SERVER['HTTPS']),
'httponly' => true,
'samesite' => 'Strict',
]);
session_name('AEGIS_SRV');
session_start();
}
}
/**
* ---------- LOGIN ----------
*/
define('BRUTE_FORCE_WINDOW', 180);
define('BRUTE_FORCE_MAX_ATTEMPTS', 3);
function auth_brute_force_check(string $login): bool {
$pdo = getDB();
$stmt = $pdo->prepare("SELECT COUNT(*) FROM login_attempts WHERE (login = ? OR ip_address = ?) AND attempt_time > NOW() - CAST(? || ' seconds' AS INTERVAL)");
$stmt->execute([$login, $_SERVER['REMOTE_ADDR'] ?? '', BRUTE_FORCE_WINDOW]);
return $stmt->fetchColumn() >= BRUTE_FORCE_MAX_ATTEMPTS;
}
function auth_record_attempt(string $login): void {
$pdo = getDB();
$stmt = $pdo->prepare("INSERT INTO login_attempts (ip_address, login) VALUES (?, ?)");
$stmt->execute([$_SERVER['REMOTE_ADDR'] ?? '', $login]);
}
function auth_clear_attempts(string $login): void {
$pdo = getDB();
$stmt = $pdo->prepare("DELETE FROM login_attempts WHERE login = ? OR ip_address = ?");
$stmt->execute([$login, $_SERVER['REMOTE_ADDR'] ?? '']);
}
function auth_login(string $login, string $password): array {
$pdo = getDB();
if (auth_brute_force_check($login)) {
return ['success' => false, 'error' => 'Превышен лимит попыток. Подождите 3 минуты.'];
}
$stmt = $pdo->prepare("SELECT id, login, password_hash, role, full_name, is_active FROM users WHERE login = ? LIMIT 1");
$stmt->execute([$login]);
$user = $stmt->fetch();
if (!$user || !password_verify($password, $user['password_hash'])) {
auth_record_attempt($login);
return ['success' => false, 'error' => 'Неверный логин или пароль.'];
}
if (!$user['is_active']) {
return ['success' => false, 'error' => 'Учётная запись заблокирована.'];
}
auth_clear_attempts($login);
$_SESSION['user_id'] = (int)$user['id'];
$_SESSION['user_login'] = $user['login'];
$_SESSION['user_role'] = $user['role'];
$_SESSION['user_name'] = $user['full_name'];
$_SESSION['auth_time'] = time();
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
auth_log($user['id'], 'login', 'Вход в систему');
return ['success' => true];
}
/**
* ---------- LOGOUT ----------
*/
function auth_logout(): void {
if (!empty($_SESSION['user_id'])) {
auth_log($_SESSION['user_id'], 'logout', 'Выход из системы');
}
$_SESSION = [];
if (ini_get('session.use_cookies')) {
setcookie(session_name(), '', time() - 42000, '/service/');
}
session_destroy();
}
/**
* ---------- GUARD ----------
*/
function auth_require(): array {
auth_session_start();
$allowed_roles = func_get_args();
if (empty($_SESSION['user_id'])) {
header('Location: /service/index.php?redirect=' . urlencode($_SERVER['REQUEST_URI']));
exit;
}
if (!empty($allowed_roles) && !in_array($_SESSION['user_role'], $allowed_roles, true)) {
http_response_code(403);
require __DIR__ . '/../pages/shared/403.php';
exit;
}
return $_SESSION;
}
function auth_has_role(string $role): bool {
return isset($_SESSION['user_role']) && $_SESSION['user_role'] === $role;
}
/**
* ---------- CSRF ----------
*/
function auth_csrf_token(): string {
return $_SESSION['csrf_token'] ?? '';
}
function auth_csrf_verify(string $token): bool {
return hash_equals($_SESSION['csrf_token'] ?? '', $token);
}
/**
* ---------- AUDIT LOG ----------
*/
function auth_log(?int $userId, string $action, string $details = ''): void {
try {
$pdo = getDB();
$stmt = $pdo->prepare("INSERT INTO audit_log (user_id, action, details, ip_address) VALUES (?, ?, ?, ?)");
$stmt->execute([$userId, $action, $details, $_SERVER['REMOTE_ADDR'] ?? '']);
} catch (\Throwable $e) {
// silent
}
}