Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
112 lines
3.0 KiB
PHP
112 lines
3.0 KiB
PHP
<?php
|
|
require_once __DIR__ . '/db.php';
|
|
|
|
$secure = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')
|
|
|| (($_SERVER['SERVER_PORT'] ?? null) == 443);
|
|
session_set_cookie_params([
|
|
'lifetime' => 0,
|
|
'path' => '/',
|
|
'httponly' => true,
|
|
'secure' => $secure,
|
|
'samesite' => 'Lax',
|
|
]);
|
|
session_name('OMSORG_FELIX_APP');
|
|
session_start();
|
|
|
|
function is_logged_in(): bool {
|
|
if (empty($_SESSION['felix_logged_in']) || empty($_SESSION['felix_username'])) {
|
|
return false;
|
|
}
|
|
$stmt = db()->prepare('SELECT active FROM users WHERE username = ?');
|
|
$stmt->execute([$_SESSION['felix_username']]);
|
|
$row = $stmt->fetch();
|
|
return $row && (int)$row['active'] === 1;
|
|
}
|
|
|
|
function require_login(): void {
|
|
if (!is_logged_in()) {
|
|
header('Location: ../index.php');
|
|
exit;
|
|
}
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST)) {
|
|
verify_csrf();
|
|
}
|
|
}
|
|
|
|
function csrf_token(): string {
|
|
if (empty($_SESSION['csrf_token'])) {
|
|
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
|
}
|
|
return $_SESSION['csrf_token'];
|
|
}
|
|
|
|
function csrf_field(): string {
|
|
return '<input type="hidden" name="csrf_token" value="' . csrf_token() . '">';
|
|
}
|
|
|
|
function verify_csrf(): void {
|
|
$token = $_POST['csrf_token'] ?? '';
|
|
if (!hash_equals(csrf_token(), $token)) {
|
|
http_response_code(403);
|
|
exit('Ungültige Anfrage.');
|
|
}
|
|
}
|
|
|
|
function require_admin(): void {
|
|
require_login();
|
|
if (!is_admin()) {
|
|
header('Location: ../pages/dashboard.php');
|
|
exit;
|
|
}
|
|
}
|
|
|
|
function is_admin(): bool {
|
|
return isset($_SESSION['felix_role']) && $_SESSION['felix_role'] === 'admin';
|
|
}
|
|
|
|
function current_user(): array|false {
|
|
static $cache = null;
|
|
static $cachedFor = null;
|
|
$username = $_SESSION['felix_username'] ?? '';
|
|
if ($username === '') return false;
|
|
if ($cache !== null && $cachedFor === $username) return $cache;
|
|
$stmt = db()->prepare('SELECT * FROM users WHERE username = ?');
|
|
$stmt->execute([$username]);
|
|
$cache = $stmt->fetch();
|
|
$cachedFor = $username;
|
|
return $cache;
|
|
}
|
|
|
|
function current_username(): string {
|
|
return $_SESSION['felix_username'] ?? '';
|
|
}
|
|
|
|
function current_name(): string {
|
|
return $_SESSION['felix_name'] ?? '';
|
|
}
|
|
|
|
function e(string $s): string {
|
|
return htmlspecialchars($s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
|
}
|
|
|
|
// --- Login rate limiting (IP-based, persisted in DB) ---
|
|
|
|
function login_recent_fails(string $ip, int $windowSec = 600): int {
|
|
$cutoff = date('c', time() - $windowSec);
|
|
$stmt = db()->prepare(
|
|
'SELECT COUNT(*) FROM login_attempts WHERE ip = ? AND attempted_at >= ?'
|
|
);
|
|
$stmt->execute([$ip, $cutoff]);
|
|
return (int) $stmt->fetchColumn();
|
|
}
|
|
|
|
function login_record_fail(string $ip, string $username): void {
|
|
db()->prepare(
|
|
'INSERT INTO login_attempts (ip, username, attempted_at) VALUES (?, ?, ?)'
|
|
)->execute([$ip, $username, date('c')]);
|
|
}
|
|
|
|
function login_clear_fails(string $ip): void {
|
|
db()->prepare('DELETE FROM login_attempts WHERE ip = ?')->execute([$ip]);
|
|
}
|