Files
aj-portfolio/public/admin/login.php
2025-12-23 13:18:58 +02:00

171 lines
7.3 KiB
PHP

<?php
declare(strict_types=1);
require __DIR__ . '/../../includes/bootstrap.php';
// already logged in? go dashboard
if (admin_is_logged_in()) {
header('Location: ' . url_path('/public/admin/dashboard.php'));
exit;
}
$err = '';
$remember = false;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$csrf = (string)($_POST['csrf'] ?? '');
$user = trim((string)($_POST['username'] ?? ''));
$pass = (string)($_POST['password'] ?? '');
$remember = isset($_POST['remember']) && (string)$_POST['remember'] !== '';
if (!csrf_check($csrf)) {
$err = 'Invalid session. Refresh and try again.';
} elseif ($user === '' || strlen($user) > 50) {
$err = 'Invalid username.';
} elseif ($pass === '') {
$err = 'Password is required.';
} else {
// Prepared statement => SQLi safe
$st = pdo()->prepare("SELECT id, username, pass_hash FROM admin_users WHERE username = ? LIMIT 1");
$st->execute([$user]);
$a = $st->fetch(PDO::FETCH_ASSOC);
if (!$a || empty($a['pass_hash']) || !password_verify($pass, (string)$a['pass_hash'])) {
$err = 'Wrong username or password.';
} else {
$algo = defined('PASSWORD_ARGON2ID') ? PASSWORD_ARGON2ID : PASSWORD_DEFAULT;
$opts = ['memory_cost' => 1 << 17, 'time_cost' => 4, 'threads' => 2];
if (password_needs_rehash((string)$a['pass_hash'], $algo, $opts)) {
try {
$newHash = password_hash($pass, $algo, $opts);
$up = pdo()->prepare("UPDATE admin_users SET pass_hash=? WHERE id=?");
$up->execute([$newHash, (int)$a['id']]);
} catch (Throwable $e) {
// best-effort; continue login
}
}
admin_login($a, $remember);
header('Location: ' . url_path('/public/admin/dashboard.php'));
exit;
}
}
}
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Admin Login</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdn.jsdelivr.net/npm/remixicon@4.6.0/fonts/remixicon.css" rel="stylesheet">
<style>
.bg-aurora{position:fixed;inset:0;background:
radial-gradient(1000px 500px at 20% 20%, rgba(139,92,246,.30), transparent 60%),
radial-gradient(900px 600px at 80% 30%, rgba(217,70,239,.22), transparent 60%),
radial-gradient(900px 700px at 55% 90%, rgba(167,139,250,.18), transparent 60%);z-index:-2;}
.bg-grid{position:fixed;inset:0;background-image:
linear-gradient(rgba(255,255,255,.05) 1px, transparent 1px),
linear-gradient(90deg, rgba(255,255,255,.05) 1px, transparent 1px);
background-size:48px 48px;opacity:.12;mask-image:radial-gradient(circle at 50% 20%, #000 0%, transparent 65%);z-index:-1;}
.glass{background:rgba(16,14,26,.62);border:1px solid rgba(255,255,255,.10);
backdrop-filter:blur(18px) saturate(150%);box-shadow:0 12px 50px rgba(0,0,0,.45);}
.btn-glow{box-shadow:0 0 0 1px rgba(167,139,250,.25), 0 18px 40px rgba(139,92,246,.25);}
</style>
</head>
<body class="min-h-screen bg-black text-white flex items-center justify-center px-4">
<div class="bg-aurora"></div>
<div class="bg-grid"></div>
<div class="w-full max-w-md">
<div class="mb-5 flex items-center justify-between">
<div class="flex items-center gap-3">
<div class="h-12 w-12 grid place-items-center rounded-2xl bg-gradient-to-br from-violet-500 to-fuchsia-500 font-black text-black">GM</div>
<div>
<div class="text-2xl font-extrabold bg-gradient-to-r from-violet-300 via-fuchsia-400 to-violet-400 bg-clip-text text-transparent">
Welcome back
</div>
<div class="text-sm text-white/60">Login to manage your portfolio</div>
</div>
</div>
<div class="hidden sm:flex items-center gap-2 rounded-full border border-white/10 bg-white/5 px-3 py-2 text-xs text-white/80">
<i class="ri-shield-keyhole-line text-fuchsia-300"></i> Secure
</div>
</div>
<div class="glass rounded-3xl p-6 relative overflow-hidden">
<div class="pointer-events-none absolute inset-0 rounded-3xl ring-1 ring-white/10"></div>
<?php if ($err): ?>
<div class="mb-4 rounded-2xl border border-red-400/20 bg-red-500/10 px-4 py-3 text-sm">
<div class="flex items-start gap-2">
<i class="ri-error-warning-line mt-0.5 text-red-300"></i>
<div><?= htmlspecialchars($err) ?></div>
</div>
</div>
<?php endif; ?>
<form method="post" autocomplete="off" class="space-y-4">
<input type="hidden" name="csrf" value="<?= htmlspecialchars(csrf_token()) ?>">
<div>
<label class="mb-2 block text-sm text-white/70">Username</label>
<div class="relative">
<input name="username" maxlength="50" required
value="<?= htmlspecialchars((string)($_POST['username'] ?? '')) ?>"
placeholder="admin"
class="w-full rounded-2xl border border-white/10 bg-white/5 px-4 py-3 pr-11 text-white placeholder:text-white/35 outline-none
focus:border-violet-400/50 focus:ring-4 focus:ring-violet-500/20">
<i class="ri-user-3-line absolute right-4 top-1/2 -translate-y-1/2 text-white/45"></i>
</div>
</div>
<div>
<label class="mb-2 block text-sm text-white/70">Password</label>
<div class="relative">
<input id="pw" type="password" name="password" required placeholder="Your password"
class="w-full rounded-2xl border border-white/10 bg-white/5 px-4 py-3 pr-20 text-white placeholder:text-white/35 outline-none
focus:border-violet-400/50 focus:ring-4 focus:ring-violet-500/20">
<button type="button" id="togglePw"
class="absolute right-3 top-1/2 -translate-y-1/2 rounded-xl border border-white/10 bg-white/5 px-3 py-2 text-xs text-white/80
hover:bg-white/10 active:scale-95 transition">
<span id="eyeIcon"><i class="ri-eye-line"></i></span>
</button>
</div>
</div>
<div class="flex items-center justify-between text-sm text-white/80 pt-1">
<label class="inline-flex items-center gap-2 cursor-pointer select-none">
<input type="checkbox" name="remember" value="1" <?= $remember ? 'checked' : '' ?>
class="h-4 w-4 rounded border-white/20 bg-white/5 text-violet-400 focus:ring-violet-500/40">
<span>Remember me for 120 days</span>
</label>
<span class="text-white/50 text-xs">Secure cookie</span>
</div>
<button class="w-full rounded-2xl bg-gradient-to-br from-violet-500 to-fuchsia-500 py-3 font-extrabold text-black btn-glow
hover:brightness-110 active:scale-[0.99] transition">
<i class="ri-login-circle-line"></i> Login
</button>
</form>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script>
$(function(){
$('#togglePw').on('click', function(){
const $pw = $('#pw');
const isPwd = $pw.attr('type') === 'password';
$pw.attr('type', isPwd ? 'text' : 'password');
$('#eyeIcon').html(isPwd ? '<i class="ri-eye-off-line"></i>' : '<i class="ri-eye-line"></i>');
});
});
</script>
</body>
</html>