Add Google OAuth admin login; fix header comment; both auth methods active

This commit is contained in:
2026-05-29 15:13:38 +00:00
parent 53f9f3e4da
commit e344e52d70
4 changed files with 167 additions and 3 deletions
+109
View File
@@ -0,0 +1,109 @@
<?php
/**
* Google OAuth callback — exchange code for token, verify admin, create session
*/
require_once __DIR__ . '/../../../includes/auth.php';
function googleOAuthError(string $msg): void {
$_SESSION['admin_login_error'] = $msg;
header('Location: /admin/login.php');
exit;
}
// Verify state to prevent CSRF
if (empty($_GET['state']) || $_GET['state'] !== ($_SESSION['google_oauth_state'] ?? '')) {
googleOAuthError('Invalid OAuth state. Please try again.');
}
unset($_SESSION['google_oauth_state']);
if (!empty($_GET['error'])) {
googleOAuthError('Google sign-in was cancelled or denied.');
}
if (empty($_GET['code'])) {
googleOAuthError('No authorization code received from Google.');
}
// Exchange code for access token
$tokenResp = json_decode(file_get_contents('https://oauth2.googleapis.com/token', false, stream_context_create([
'http' => [
'method' => 'POST',
'header' => 'Content-Type: application/x-www-form-urlencoded',
'content' => http_build_query([
'code' => $_GET['code'],
'client_id' => GOOGLE_CLIENT_ID,
'client_secret' => GOOGLE_CLIENT_SECRET,
'redirect_uri' => GOOGLE_REDIRECT_URI,
'grant_type' => 'authorization_code',
]),
'timeout' => 15,
],
])), true);
if (empty($tokenResp['access_token'])) {
error_log('[Google OAuth] token exchange failed: ' . json_encode($tokenResp));
googleOAuthError('Failed to complete Google sign-in. Please try again.');
}
// Fetch Google user info
$userInfo = json_decode(file_get_contents('https://www.googleapis.com/oauth2/v3/userinfo', false, stream_context_create([
'http' => [
'method' => 'GET',
'header' => 'Authorization: Bearer ' . $tokenResp['access_token'],
'timeout' => 10,
],
])), true);
if (empty($userInfo['email'])) {
googleOAuthError('Could not retrieve email from Google. Please try again.');
}
$googleEmail = strtolower($userInfo['email']);
// Look up admin by email
$admin = db()->fetch(
"SELECT * FROM admin_users WHERE email = :email",
['email' => $googleEmail]
);
if (!$admin) {
error_log('[Google OAuth] login attempt by non-admin: ' . $googleEmail);
googleOAuthError('No admin account found for ' . htmlspecialchars($googleEmail) . '. Contact the site administrator.');
}
// Store google_id on first Google login
if (empty($admin['google_id'])) {
try {
db()->query(
"UPDATE admin_users SET google_id = :gid, last_login = NOW() WHERE user_id = :id",
['gid' => $userInfo['sub'], 'id' => $admin['user_id']]
);
} catch (Exception $e) {
// google_id column may not exist yet — update last_login only
db()->query(
"UPDATE admin_users SET last_login = NOW() WHERE user_id = :id",
['id' => $admin['user_id']]
);
}
} else {
db()->query(
"UPDATE admin_users SET last_login = NOW() WHERE user_id = :id",
['id' => $admin['user_id']]
);
}
// Create session
$_SESSION['admin'] = [
'user_id' => $admin['user_id'],
'email' => $admin['email'],
'name' => $admin['name'],
'is_master' => (bool)$admin['is_master'],
'permissions' => json_decode($admin['permissions'] ?? '[]', true),
'auth_method' => 'google',
];
session_regenerate_id(true);
$redirect = $_SESSION['admin_redirect'] ?? '/admin/';
unset($_SESSION['admin_redirect']);
header('Location: ' . $redirect);
exit;
+30
View File
@@ -0,0 +1,30 @@
<?php
/**
* Redirect to Google OAuth — admin login entry point
*/
require_once __DIR__ . '/../../../includes/auth.php';
if (AdminAuth::isLoggedIn()) {
header('Location: /admin/');
exit;
}
if (!defined('GOOGLE_CLIENT_ID') || !GOOGLE_CLIENT_ID) {
die('Google OAuth is not configured. Set GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET in config.');
}
$state = bin2hex(random_bytes(16));
$_SESSION['google_oauth_state'] = $state;
$params = http_build_query([
'client_id' => GOOGLE_CLIENT_ID,
'redirect_uri' => GOOGLE_REDIRECT_URI,
'response_type' => 'code',
'scope' => 'openid email profile',
'state' => $state,
'access_type' => 'online',
'prompt' => 'select_account',
]);
header('Location: https://accounts.google.com/o/oauth2/v2/auth?' . $params);
exit;
+2 -2
View File
@@ -1,7 +1,7 @@
<?php
/**
* Tom's Java Jive - Admin Header
* Authentication temporarily disabled
* Requires AdminAuth::require() — redirects to login.php
*/
require_once __DIR__ . '/../../includes/auth.php';
@@ -105,10 +105,10 @@ $currentPage = basename($_SERVER['PHP_SELF'], '.php');
<a href="/admin/campaigns.php" class="nav-item <?= $currentPage === 'campaigns' ? 'active' : '' ?>">
<i class="fas fa-envelope"></i> Email Campaigns
</a>
<a href="/admin/email-log.php" class="nav-item <?= $currentPage === 'email-log' ? 'active' : '' ?>"><i class="fas fa-envelope-open-text"></i> Email Log</a>
</div>
<div class="nav-group">
<span class="nav-group-title">Settings</span>
<a href="/admin/settings.php" class="nav-item <?= $currentPage === 'settings' ? 'active' : '' ?>">
<i class="fas fa-cog"></i> Store Settings
</a>
+26 -1
View File
@@ -9,7 +9,11 @@ if (AdminAuth::isLoggedIn()) {
exit;
}
$error = '';
$googleEnabled = defined('GOOGLE_CLIENT_ID') && GOOGLE_CLIENT_ID;
$error = $_SESSION['admin_login_error'] ?? '';
unset($_SESSION['admin_login_error']);
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$email = trim($_POST['email'] ?? '');
$password = trim($_POST['password'] ?? '');
@@ -41,6 +45,12 @@ input{width:100%;background:#111;border:1.5px solid #2a2a2a;border-radius:8px;pa
input:focus{border-color:#FF5E1A}
.btn{width:100%;padding:14px;border:none;border-radius:8px;background:#FF5E1A;color:#fff;font-family:Inter,sans-serif;font-weight:600;font-size:15px;cursor:pointer;transition:background .2s;margin-top:4px}
.btn:hover{background:#e54d0f}
.btn-google{display:flex;align-items:center;justify-content:center;gap:10px;width:100%;padding:13px;border:1.5px solid #333;border-radius:8px;background:#111;color:#e0e0e0;font-family:Inter,sans-serif;font-weight:500;font-size:15px;cursor:pointer;text-decoration:none;transition:border-color .2s,background .2s}
.btn-google:hover{border-color:#555;background:#1a1a1a}
.btn-google svg{width:20px;height:20px;flex-shrink:0}
.divider{display:flex;align-items:center;gap:12px;margin:22px 0}
.divider::before,.divider::after{content:'';flex:1;height:1px;background:#2a2a2a}
.divider span{font-size:12px;color:#444}
.error{background:rgba(220,38,38,.1);border:1px solid rgba(220,38,38,.3);color:#f87171;padding:12px 15px;border-radius:8px;font-size:14px;margin-bottom:20px;display:flex;align-items:center;gap:8px}
.back{display:block;text-align:center;margin-top:20px;color:#555;font-size:13px;text-decoration:none;transition:color .2s}
.back:hover{color:#FF5E1A}
@@ -52,9 +62,24 @@ input:focus{border-color:#FF5E1A}
<h1>☕ Tom's Java Jive</h1>
<p>Admin Panel</p>
</div>
<?php if ($error): ?>
<div class="error">⚠ <?= htmlspecialchars($error) ?></div>
<?php endif; ?>
<?php if ($googleEnabled): ?>
<a href="/admin/auth/google/login.php" class="btn-google">
<svg viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
<path fill="#4285F4" d="M46.98 24.55c0-1.57-.15-3.09-.38-4.55H24v8.51h12.94c-.58 2.96-2.26 5.48-4.78 7.18l7.73 6c4.51-4.18 7.09-10.36 7.09-17.14z"/>
<path fill="#34A853" d="M24 48c6.48 0 11.93-2.13 15.89-5.81l-7.73-6c-2.15 1.45-4.92 2.3-8.16 2.3-6.26 0-11.57-4.22-13.47-9.91l-7.98 6.19C6.51 42.62 14.62 48 24 48z"/>
<path fill="#FBBC05" d="M10.53 28.59c-.48-1.45-.76-2.99-.76-4.59s.27-3.14.76-4.59l-7.98-6.19C.92 16.46 0 20.12 0 24c0 3.88.92 7.54 2.56 10.78l7.97-6.19z"/>
<path fill="#EA4335" d="M24 9.5c3.54 0 6.71 1.22 9.21 3.6l6.85-6.85C35.9 2.38 30.47 0 24 0 14.62 0 6.51 5.38 2.56 13.22l7.98 6.19C12.43 13.72 17.74 9.5 24 9.5z"/>
</svg>
Sign in with Google
</a>
<div class="divider"><span>or sign in with email</span></div>
<?php endif; ?>
<form method="POST">
<label>Email Address</label>
<input type="email" name="email" placeholder="admin@tomsjavajive.com" required autocomplete="email">