1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
|
<?php
require_once __DIR__ . '/../partials/session.php';
require_once __DIR__ . '/../partials/proxy_helpers.php';
// Post-login destination: honour a local ?ref= path (no open redirects),
// otherwise land on the unified admin panel.
$ref = $_GET['ref'] ?? '';
$dest = (is_string($ref) && $ref !== '' && $ref[0] === '/' && substr($ref, 0, 2) !== '//') ? $ref : '/admin.php';
if (!empty($_SESSION['admin'])) {
header('Location: ' . $dest);
exit;
}
if (empty($_SESSION['csrf'])) {
$_SESSION['csrf'] = bin2hex(random_bytes(32));
}
/**
* Load admin credentials from outside the document root.
* Override path with ADMIN_AUTH_FILE in the php-fpm environment.
*
* File returns: ['algo' => 'password_hash'|'sha256', 'hash' => string, 'salt' => string?]
*/
function admin_auth_config(): ?array {
$path = getenv('ADMIN_AUTH_FILE');
if (!is_string($path) || $path === '') {
$path = '/var/lib/sillylaird/admin_auth.php';
}
if (!is_file($path) || !is_readable($path)) {
return null;
}
$cfg = include $path;
return is_array($cfg) ? $cfg : null;
}
function admin_password_ok(string $provided): bool {
$cfg = admin_auth_config();
if ($cfg === null) {
return false;
}
$hash = (string)($cfg['hash'] ?? '');
if ($hash === '') {
return false;
}
$algo = strtolower((string)($cfg['algo'] ?? 'password_hash'));
if ($algo === 'password_hash' || str_starts_with($hash, '$2y$') || str_starts_with($hash, '$2a$') || str_starts_with($hash, '$argon')) {
return password_verify($provided, $hash);
}
// Legacy: sha256(password + salt)
$salt = (string)($cfg['salt'] ?? '');
$computed = hash('sha256', $provided . $salt);
return hash_equals($hash, $computed);
}
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!proxy_rate_limit('admin_login', 5, 300)) {
http_response_code(429);
$error = 'Too many attempts. Try again in a few minutes.';
} else {
$token = $_POST['csrf'] ?? '';
if (!is_string($token) || !hash_equals($_SESSION['csrf'], $token)) {
$error = 'Bad request.';
} else {
$provided = $_POST['password'] ?? '';
if (is_string($provided) && admin_password_ok($provided)) {
session_regenerate_id(true);
$_SESSION['admin'] = true;
unset($_SESSION['csrf']);
header('Location: ' . $dest);
exit;
}
$error = 'Wrong password.';
usleep(random_int(150000, 400000));
}
}
}
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Admin Login — SillyLaird</title>
<link rel="icon" href="https://www.sillylaird.ca/assets/img/lain.png" />
<link rel="stylesheet" href="/assets/css/fonts.css" />
<link rel="stylesheet" href="/assets/css/site.css?v=1.5" />
<link rel="stylesheet" href="/assets/css/skeleton.css" />
</head>
<body>
<a class="skip-link" href="#main">Skip to content</a>
<?php include $_SERVER['DOCUMENT_ROOT'] . '/partials/header.php'; ?>
<main id="main" class="wrap stack">
<section aria-labelledby="login-title">
<h1 id="login-title">Admin Login</h1>
<?php if ($error): ?>
<p style="color: var(--accent); font-size: var(--fs-sm);"><?= htmlspecialchars($error, ENT_QUOTES, 'UTF-8') ?></p>
<?php endif; ?>
<form method="post" action="" style="max-width: 360px;">
<input type="hidden" name="csrf" value="<?= htmlspecialchars($_SESSION['csrf'], ENT_QUOTES, 'UTF-8') ?>" />
<div class="field-group">
<label for="password">Password</label>
<input type="password" id="password" name="password" autofocus autocomplete="current-password" />
</div>
<button type="submit">Enter</button>
</form>
</section>
</main>
<?php include $_SERVER['DOCUMENT_ROOT'] . '/partials/footer.php'; ?>
</body>
</html>
|