This commit is contained in:
Purple
2026-01-18 01:53:16 +00:00
parent 35bcbb2d34
commit a413354655

View File

@@ -1,9 +1,11 @@
<?php
/**
* Geofeed Manager Login Page
* Simplified for Cloudflare Access authentication
*/
require_once __DIR__ . '/config.php';
require_once __DIR__ . '/includes/auth.php';
$error = '';
$success = '';
@@ -11,36 +13,50 @@ $success = '';
// Handle logout
if (isset($_GET['logout'])) {
logoutUser();
header('Location: login.php');
header('Location: login.php?logged_out=1');
exit;
}
// Already authenticated? Redirect to main page
if (isAuthenticated()) {
header('Location: index.php');
exit;
// Check for logout success message
if (isset($_GET['logged_out'])) {
$success = 'You have been logged out successfully.';
}
// Handle login form submission
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = trim($_POST['username'] ?? '');
$password = $_POST['password'] ?? '';
// Check for unauthorized error
if (isset($_GET['error']) && $_GET['error'] === 'unauthorized') {
$error = 'You do not have permission to access that page.';
}
// Get Cloudflare Access user info
$cfUser = getCurrentUser();
// Handle session login (when user clicks "Continue" button)
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'login') {
// Validate CSRF
if (!validateCSRFToken($_POST['csrf_token'] ?? '')) {
$error = 'Invalid security token. Please try again.';
} elseif (empty($username) || empty($password)) {
$error = 'Please enter both username and password.';
} elseif (authenticateUser($username, $password)) {
} elseif ($cfUser) {
// Start session with Cloudflare Access user
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
$_SESSION['authenticated'] = true;
$_SESSION['user'] = $cfUser['email'];
$_SESSION['login_time'] = time();
header('Location: index.php');
exit;
} else {
$error = 'Invalid username or password.';
// Add small delay to prevent brute force
usleep(500000);
$error = 'Unable to authenticate. Please ensure you are signed in via Cloudflare Access.';
}
}
// If already authenticated via session, redirect to main page
if (isAuthenticated()) {
header('Location: index.php');
exit;
}
$csrfToken = generateCSRFToken();
?>
<!DOCTYPE html>
@@ -88,6 +104,8 @@ $csrfToken = generateCSRFToken();
--error-bg: rgba(220, 53, 69, 0.1);
--success: #28a745;
--success-bg: rgba(40, 167, 69, 0.1);
--warning: #ffc107;
--warning-bg: rgba(255, 193, 7, 0.1);
--transition: all 0.2s ease;
@@ -119,6 +137,7 @@ $csrfToken = generateCSRFToken();
--error-bg: rgba(220, 53, 69, 0.2);
--success-bg: rgba(40, 167, 69, 0.2);
--warning-bg: rgba(255, 193, 7, 0.2);
}
}
@@ -204,46 +223,77 @@ $csrfToken = generateCSRFToken();
opacity: 0.9;
}
.login-form {
.login-body {
padding: 32px;
}
.form-group {
margin-bottom: 20px;
.user-info {
text-align: center;
margin-bottom: 24px;
}
.form-label {
display: block;
font-size: 13px;
.user-avatar {
width: 64px;
height: 64px;
background: var(--purple-lighter);
color: var(--purple-primary);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
font-weight: 600;
color: var(--text-secondary);
margin-bottom: 8px;
text-transform: uppercase;
letter-spacing: 0.03em;
margin: 0 auto 16px;
}
.form-input {
width: 100%;
padding: 14px 16px;
.user-email {
font-size: 16px;
font-family: inherit;
background: var(--bg-tertiary);
border: 2px solid var(--border);
border-radius: var(--radius-md);
font-weight: 500;
color: var(--text-primary);
transition: var(--transition);
outline: none;
margin-bottom: 4px;
}
.form-input:focus {
border-color: var(--purple-primary);
box-shadow: 0 0 0 4px rgba(107, 45, 123, 0.1);
.user-role {
font-size: 13px;
color: var(--text-secondary);
}
.form-input::placeholder {
.user-role .badge {
display: inline-block;
padding: 4px 10px;
border-radius: 20px;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.badge-admin {
background: var(--purple-lighter);
color: var(--purple-primary);
}
.badge-staff {
background: rgba(59, 130, 246, 0.15);
color: #3b82f6;
}
.auth-method {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
margin-top: 12px;
font-size: 12px;
color: var(--text-tertiary);
}
.auth-method svg {
width: 16px;
height: 16px;
color: #f48120;
}
.btn {
display: inline-flex;
align-items: center;
@@ -260,6 +310,7 @@ $csrfToken = generateCSRFToken();
-webkit-touch-callout: none;
-webkit-user-select: none;
user-select: none;
gap: 10px;
}
.btn:active {
@@ -305,6 +356,18 @@ $csrfToken = generateCSRFToken();
border: 1px solid rgba(40, 167, 69, 0.3);
}
.alert-warning {
background: var(--warning-bg);
color: #856404;
border: 1px solid rgba(255, 193, 7, 0.3);
}
@media (prefers-color-scheme: dark) {
.alert-warning {
color: #ffc107;
}
}
.alert-icon {
flex-shrink: 0;
}
@@ -325,28 +388,22 @@ $csrfToken = generateCSRFToken();
text-decoration: underline;
}
/* Loading spinner */
.spinner {
display: none;
width: 18px;
height: 18px;
border: 2px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top-color: white;
animation: spin 0.8s linear infinite;
margin-right: 8px;
.no-user-message {
text-align: center;
padding: 20px;
background: var(--bg-tertiary);
border-radius: var(--radius-md);
}
.btn.loading .spinner {
display: inline-block;
.no-user-message h3 {
font-size: 16px;
margin-bottom: 8px;
color: var(--text-primary);
}
.btn.loading .btn-text {
opacity: 0.7;
}
@keyframes spin {
to { transform: rotate(360deg); }
.no-user-message p {
font-size: 14px;
color: var(--text-secondary);
}
/* Shake animation for errors */
@@ -377,9 +434,7 @@ $csrfToken = generateCSRFToken();
<p class="login-subtitle">Sign in to manage your geofeed entries</p>
</div>
<form class="login-form" method="POST" action="login.php" id="loginForm">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrfToken) ?>">
<div class="login-body">
<?php if ($error): ?>
<div class="alert alert-error shake">
<svg class="alert-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
@@ -391,44 +446,75 @@ $csrfToken = generateCSRFToken();
</div>
<?php endif; ?>
<div class="form-group">
<label class="form-label" for="username">Username</label>
<input type="text" id="username" name="username" class="form-input" placeholder="Enter your username" required autocomplete="username" autofocus>
<?php if ($success): ?>
<div class="alert alert-success">
<svg class="alert-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/>
<polyline points="22 4 12 14.01 9 11.01"/>
</svg>
<span><?= htmlspecialchars($success) ?></span>
</div>
<?php endif; ?>
<div class="form-group">
<label class="form-label" for="password">Password</label>
<input type="password" id="password" name="password" class="form-input" placeholder="Enter your password" required autocomplete="current-password">
</div>
<?php if ($cfUser): ?>
<!-- Cloudflare Access user detected -->
<div class="user-info">
<?php
$email = $cfUser['email'];
$namePart = explode('@', $email)[0];
$initials = strtoupper(substr($namePart, 0, 2));
$role = $cfUser['role'];
?>
<div class="user-avatar"><?= htmlspecialchars($initials) ?></div>
<div class="user-email"><?= htmlspecialchars($email) ?></div>
<div class="user-role">
<span class="badge <?= $role === 'admin' ? 'badge-admin' : 'badge-staff' ?>">
<?= $role === 'admin' ? 'Administrator' : 'Staff' ?>
</span>
</div>
<div class="auth-method">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M16.5 7.5h-9v9h9v-9z"/>
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"/>
</svg>
Authenticated via Cloudflare Access
</div>
</div>
<button type="submit" class="btn btn-primary" id="submitBtn">
<span class="spinner"></span>
<span class="btn-text">Sign In</span>
</button>
</form>
<form method="POST" action="login.php">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrfToken) ?>">
<input type="hidden" name="action" value="login">
<button type="submit" class="btn btn-primary">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4"/>
<polyline points="10 17 15 12 10 7"/>
<line x1="15" y1="12" x2="3" y2="12"/>
</svg>
Continue to Dashboard
</button>
</form>
<?php else: ?>
<!-- No Cloudflare Access user detected -->
<div class="alert alert-warning">
<svg class="alert-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/>
<line x1="12" y1="9" x2="12" y2="13"/>
<line x1="12" y1="17" x2="12.01" y2="17"/>
</svg>
<span>Cloudflare Access authentication required</span>
</div>
<div class="no-user-message">
<h3>Authentication Required</h3>
<p>Please sign in through Cloudflare Access to continue. If you're seeing this message after authenticating, please contact your administrator.</p>
</div>
<?php endif; ?>
</div>
<div class="login-footer">
<p>Powered by <a href="https://purplecomputing.com" target="_blank" rel="noopener">Purple Computing</a></p>
</div>
</div>
</div>
<script>
// Add loading state on form submit
document.getElementById('loginForm').addEventListener('submit', function(e) {
const btn = document.getElementById('submitBtn');
btn.classList.add('loading');
btn.disabled = true;
});
// Focus first empty field
const usernameField = document.getElementById('username');
const passwordField = document.getElementById('password');
if (usernameField.value) {
passwordField.focus();
} else {
usernameField.focus();
}
</script>
</body>
</html>