From 01a6d0f00a6ba8aaa0de8c238faf91d35d7b121f Mon Sep 17 00:00:00 2001 From: LordSchmackes Date: Fri, 22 May 2026 08:22:54 +0200 Subject: [PATCH] refactor(phase-2): extract auth CSS to stylesheet, harden session API, fix Firebase v10 error codes - Move 374-line inline style block from login.php into assets/style.css (Phase 2 section) - Add auth-page, auth-card, auth-tabs, auth-form, profile-shell and related classes to the main stylesheet as the single source of truth for all styles - Fix Firebase v10 compat SDK error code: auth/invalid-credential now handled alongside legacy auth/wrong-password and auth/user-not-found codes - Harden api/session.php: add Content-Type JSON header, email format validation, and a full security documentation comment explaining the token trust model - Add FOUC comment to partials/head.php clarifying dark-only design intent --- api/session.php | 58 ++++++--- assets/style.css | 278 ++++++++++++++++++++++++++++++++++++++++++ login.php | 305 ++-------------------------------------------- partials/head.php | 4 +- 4 files changed, 331 insertions(+), 314 deletions(-) diff --git a/api/session.php b/api/session.php index 9aecee2..fac5235 100644 --- a/api/session.php +++ b/api/session.php @@ -1,5 +1,22 @@ $uid, - 'email' => $email, - 'token' => $token - ]; - echo json_encode(['status' => 'success', 'message' => 'Logged in']); - } else { + $uid = trim($_POST['uid'] ?? ''); + $email = trim($_POST['email'] ?? ''); + $token = trim($_POST['token'] ?? ''); + + // Validate required fields – reject obviously malformed requests early + if ($uid === '' || $email === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) { http_response_code(400); - echo json_encode(['status' => 'error', 'message' => 'Missing UID']); + echo json_encode(['status' => 'error', 'message' => 'Invalid or missing uid/email']); + exit; } + + $_SESSION['fc_user'] = [ + 'uid' => $uid, + 'email' => $email, + 'token' => $token, // Stored for potential future server-side verification + ]; + echo json_encode(['status' => 'success', 'message' => 'Logged in']); + } elseif ($action === 'logout') { $_SESSION = []; - if (ini_get("session.use_cookies")) { + if (ini_get('session.use_cookies')) { $params = session_get_cookie_params(); - setcookie(session_name(), '', time() - 42000, - $params["path"], $params["domain"], - $params["secure"], $params["httponly"] + setcookie( + session_name(), '', time() - 42000, + $params['path'], $params['domain'], + $params['secure'], $params['httponly'] ); } session_destroy(); echo json_encode(['status' => 'success', 'message' => 'Logged out']); + } else { http_response_code(400); echo json_encode(['status' => 'error', 'message' => 'Invalid action']); diff --git a/assets/style.css b/assets/style.css index b6172b3..facdb34 100644 --- a/assets/style.css +++ b/assets/style.css @@ -1741,6 +1741,284 @@ body.nav-open { overflow: hidden; } ║ PHASE 2: AUTHENTICATION, FAVORITES & NEWSLETTER STYLES ║ ╚══════════════════════════════════════════════════════════════╝ */ +/* ── Auth Page Layout ── */ +.auth-page { + padding: clamp(6rem, 10vh, 10rem) var(--grid-margin) clamp(4rem, 8vh, 6rem); + min-height: 80vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +/* ── Auth Card ── */ +.auth-card { + width: 100%; + max-width: 500px; + background: var(--surface-2); + border: 1px solid var(--stroke); + box-shadow: var(--shadow); + border-radius: var(--radius); + padding: clamp(2rem, 5vw, 3.5rem); + position: relative; + overflow: hidden; + transition: transform 0.4s var(--ease-out-expo), opacity 0.4s ease; +} + +/* ── Auth Tabs (Login / Register toggle) ── */ +.auth-tabs { + display: flex; + justify-content: center; + gap: 2rem; + margin-bottom: 2.5rem; + border-bottom: 1px solid var(--stroke); + padding-bottom: 0.8rem; +} +.auth-tab { + font-family: var(--font-serif-display); + font-size: 1.5rem; + color: var(--color-text-muted); + background: none; + border: none; + cursor: pointer; + padding: 0; + position: relative; + transition: color 0.3s ease; +} +.auth-tab.active { + color: var(--ink); +} +.auth-tab::after { + content: ''; + position: absolute; + bottom: -0.9rem; + left: 0; + width: 100%; + height: 2px; + background-color: var(--color-accent-gold); + transform: scaleX(0); + transform-origin: center; + transition: transform 0.4s var(--ease-out-expo); +} +.auth-tab.active::after { + transform: scaleX(1); +} + +/* ── Auth Forms ── */ +.auth-form { + display: none; + flex-direction: column; + gap: 1.5rem; +} +.auth-form.active { + display: flex; + animation: authFadeIn 0.5s var(--ease-out-expo) both; +} +@keyframes authFadeIn { + from { opacity: 0; transform: translateY(10px); } + to { opacity: 1; transform: translateY(0); } +} + +/* ── Auth Form Fields ── */ +.auth-field { + display: flex; + flex-direction: column; + gap: 0.5rem; +} +.auth-field label { + font-family: var(--font-sans-clean); + font-size: 0.8rem; + text-transform: uppercase; + letter-spacing: 0.08em; + font-weight: 700; + color: var(--muted); +} +.auth-field input, +.auth-field select { + padding: 1rem 1.25rem; + border-radius: 10px; + border: 1px solid var(--stroke); + background: var(--surface); + color: var(--ink); + font-family: var(--font-sans-clean); + font-size: 0.95rem; + transition: border-color 0.3s ease, box-shadow 0.3s ease; + outline: none; +} +.auth-field input:focus, +.auth-field select:focus { + border-color: var(--color-accent-gold); + box-shadow: 0 0 0 3px var(--accent-soft); +} + +/* ── Auth Submit Button Row ── */ +.auth-btn-row { + margin-top: 1rem; +} +.auth-btn-row button { + width: 100%; + justify-content: center; +} + +/* ── Auth Form Switch Link ── */ +.auth-switch-text { + text-align: center; + font-size: 0.9rem; + color: var(--muted); + margin-top: 1.5rem; +} +.auth-switch-text button { + background: none; + border: none; + color: var(--ink); + font-weight: 700; + cursor: pointer; +} + +/* ── Auth Alert (Error Message) ── */ +.auth-alert { + background: #fdf2f2; + border: 1px solid #fbd5d5; + color: #9b1c1c; + padding: 1rem; + border-radius: 8px; + font-size: 0.9rem; + margin-bottom: 1.5rem; + display: none; + animation: authFadeIn 0.3s ease; +} +[data-theme="dark"] .auth-alert { + background: #2b1515; + border-color: #5a1818; + color: #ff9b9b; +} + +/* ── Loading Spinner ── */ +.spinner { + width: 20px; + height: 20px; + border: 2px solid rgba(255, 255, 255, 0.3); + border-radius: 50%; + border-top-color: #fff; + animation: spin 0.8s linear infinite; + display: none; +} +@keyframes spin { + to { transform: rotate(360deg); } +} +.btn-reveal:disabled { + opacity: 0.7; + cursor: not-allowed; +} +.btn-reveal:disabled .spinner { + display: inline-block; +} +.btn-reveal:disabled .btn-reveal-text { + display: none; +} + +/* ── Profile & Dashboard Shell ── */ +.profile-shell { + width: 100%; + max-width: 1100px; + display: grid; + grid-template-columns: 1fr; + gap: 3rem; +} +@media (min-width: 850px) { + .profile-shell { + grid-template-columns: 320px 1fr; + } +} + +/* ── Profile Sidebar ── */ +.profile-sidebar { + background: var(--surface-2); + border: 1px solid var(--stroke); + border-radius: var(--radius); + padding: 2.5rem; + display: flex; + flex-direction: column; + gap: 2rem; + height: fit-content; + box-shadow: var(--shadow); +} +.profile-avatar { + width: 80px; + height: 80px; + border-radius: 50%; + background: var(--accent-soft); + color: var(--color-accent-gold); + display: flex; + align-items: center; + justify-content: center; + font-family: var(--font-serif-display); + font-size: 2.5rem; + border: 1px solid rgba(195, 166, 119, 0.3); +} +.profile-meta h2 { + font-size: 1.75rem; + margin-bottom: 0.25rem; + font-family: var(--font-serif-display); +} +.profile-meta p { + font-size: 0.9rem; + color: var(--muted); +} +.profile-details { + border-top: 1px solid var(--stroke); + padding-top: 1.5rem; + display: flex; + flex-direction: column; + gap: 1.2rem; +} +.profile-stat-box { + display: flex; + flex-direction: column; + gap: 0.3rem; +} +.profile-stat-label { + font-size: 0.72rem; + text-transform: uppercase; + letter-spacing: 0.08em; + font-weight: 700; + color: var(--muted); +} +.profile-stat-val { + font-size: 1rem; + font-weight: 600; + color: var(--ink); +} + +/* ── Profile Main Content Area ── */ +.profile-main { + display: flex; + flex-direction: column; + gap: 2rem; +} +.profile-favorites-title { + font-family: var(--font-serif-display); + font-size: 2rem; + border-bottom: 1px solid var(--stroke); + padding-bottom: 0.8rem; + margin-bottom: 1rem; +} + +/* ── Favorites Grid ── */ +.favorites-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: var(--grid-gap); +} +.favorites-empty { + text-align: center; + padding: 4rem 2rem; + background: var(--surface-2); + border: 1px dashed var(--stroke); + border-radius: var(--radius); + color: var(--muted); +} + /* ── Premium Heart Buttons ── */ .card__image { position: relative; diff --git a/login.php b/login.php index 5fddef4..e979cd5 100644 --- a/login.php +++ b/login.php @@ -1,4 +1,4 @@ - -
@@ -536,8 +245,14 @@ function handleLogin(e) { .catch(error => { btn.disabled = false; let errMsg = error.message; - if (error.code === 'auth/wrong-password' || error.code === 'auth/user-not-found') { - errMsg = "Invalid email or password."; + // Firebase v10 compat SDK consolidates wrong-password + user-not-found + // into auth/invalid-credential. Handle all three for backwards compatibility. + if ( + error.code === 'auth/invalid-credential' || + error.code === 'auth/wrong-password' || + error.code === 'auth/user-not-found' + ) { + errMsg = 'Invalid email or password.'; } showAlert(errMsg); }); diff --git a/partials/head.php b/partials/head.php index 68f9746..0202167 100644 --- a/partials/head.php +++ b/partials/head.php @@ -70,7 +70,9 @@ $description = $description ?? 'Seasonal recipes, tested tips, and approachable - +