1<?php
2
3function znote_login_guard_config(): array {
4 global $config;
5 $cfg = (array)($config['login_guard'] ?? array());
6
7 return array(
8 'enabled' => !empty($cfg['enabled']),
9 'threshold' => max(1, (int)($cfg['threshold'] ?? 5)),
10 'window_seconds' => max(60, (int)($cfg['window_minutes'] ?? 15) * 60),
11 'lockout_seconds' => max(60, (int)($cfg['lockout_minutes'] ?? 15) * 60),
12 );
13}
14
15function znote_login_guard_ip(): string {
16 $ip = (string)(function_exists('getIP') ? getIP() : ($_SERVER['REMOTE_ADDR'] ?? ''));
17 return substr(trim($ip), 0, 45);
18}
19
20function znote_login_guard_record(string $ip, string $username, bool $success): void {
21 if (!function_exists('znote_table_exists') || !znote_table_exists('znote_login_attempts')) {
22 return;
23 }
24
25 $now = time();
26 db()->execute("
27 INSERT INTO `znote_login_attempts` (`ip`, `username`, `success`, `created_at`)
28 VALUES (?, ?, ?, ?);
29 ", [$ip, substr($username, 0, 32), $success ? 1 : 0, $now]);
30
31 if (random_int(1, 20) === 1) {
32 db()->execute("DELETE FROM `znote_login_attempts` WHERE `created_at` < ?;", [$now - 86400]);
33 }
34}
35
36function znote_login_guard_lockout_remaining(string $ip): int {
37 $cfg = znote_login_guard_config();
38 if (!$cfg['enabled'] || !function_exists('znote_table_exists') || !znote_table_exists('znote_login_attempts')) {
39 return 0;
40 }
41
42 $now = time();
43 $windowStart = $now - $cfg['window_seconds'];
44
45 $row = db()->fetchOne("
46 SELECT COUNT(*) AS `failures`, MAX(`created_at`) AS `last_failure`
47 FROM `znote_login_attempts`
48 WHERE `ip` = ? AND `success` = 0 AND `created_at` >= ?;
49 ", [$ip, $windowStart]);
50
51 if (!is_array($row) || (int)$row['failures'] < $cfg['threshold']) {
52 return 0;
53 }
54
55 $unlocksAt = (int)$row['last_failure'] + $cfg['lockout_seconds'];
56 return max(0, $unlocksAt - $now);
57}
58
59function znote_login_guard_is_locked(string $ip): bool {
60 return znote_login_guard_lockout_remaining($ip) > 0;
61}
62