feat(auth): add login/logout, user identity, and soft auth gate

- api/logout.php: destroys session + clears cookie, redirects to /
- api/guest-session.php: sets guest flag, lets users explore without account
- layout.php: removes hard PHP redirect; authenticated users see email +
  "Logg ut" in topbar; guests see guest banner (sticky, dismissible) and
  auth gate modal (dismissible via localStorage) instead of redirect
- layout_footer.php: injects auth gate modal + JS for banner/modal dismiss
- layout_dashboard.php: adds username + "Logg ut" to dash-topbar
- index.php: adds "Utforsk uten konto" link under primary login CTA
- tools.css: .guest-banner, .auth-gate-*, .topbar-user, .dash-topbar__user

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-23 18:05:51 +02:00
parent b6212b8729
commit 33dc5406b2
7 changed files with 337 additions and 9 deletions
+36 -9
View File
@@ -2,11 +2,8 @@
declare(strict_types=1);
// Required vars: $toolName (string), $toolTitle (string), $toolKind (string), $toolBadge (string)
require_once __DIR__ . '/bootstrap.php';
if (!dbnToolsIsAuthenticated()) {
$return = urlencode($_SERVER['REQUEST_URI'] ?? '/');
header('Location: /?return=' . $return);
exit;
}
$layoutIsGuest = !dbnToolsIsAuthenticated();
$uiLang = dbnToolsCurrentLanguage();
$navItems = dbnToolsLaunchedTools($uiLang);
@@ -17,12 +14,23 @@ $toolKind = $toolMeta['sub'] ?? ($toolKind ?? '');
$toolBadge = $toolMeta['badge'] ?? ($toolBadge ?? '');
$langPath = strtok((string)($_SERVER['REQUEST_URI'] ?? '/'), '?') ?: '/';
// Credit balance for free-tier users
// Credit balance — only meaningful for authenticated free-tier users
$layoutFreeTierBalance = -1;
if (dbnToolsIsFreeTier()) {
if (!$layoutIsGuest && dbnToolsIsFreeTier()) {
require_once __DIR__ . '/FreeTier.php';
$layoutFreeTierBalance = FreeTier::balance((int)$_SESSION['dbn_tools_sso_uid']);
}
// User identity for topbar (null for guests)
$layoutAuthUser = $layoutIsGuest ? null : dbnToolsAuthenticatedUser();
$layoutUserDisplay = '';
if ($layoutAuthUser !== null) {
$email = (string)($layoutAuthUser['email'] ?? '');
// Show only local part (before @) to keep topbar compact
$layoutUserDisplay = strstr($email, '@', true) ?: $email;
}
$layoutReturnUrl = urlencode($_SERVER['REQUEST_URI'] ?? '/');
?>
<!doctype html>
<html lang="<?= htmlspecialchars($uiLang) ?>">
@@ -35,9 +43,9 @@ if (dbnToolsIsFreeTier()) {
<?php endif; ?>
<link rel="stylesheet" href="assets/css/tools.css">
</head>
<body data-authenticated="true" data-active-tool="<?= htmlspecialchars($toolName) ?>">
<body data-authenticated="<?= $layoutIsGuest ? 'false' : 'true' ?>" data-active-tool="<?= htmlspecialchars($toolName) ?>">
<script>
window.DBN_TOOLS_AUTHENTICATED = true;
window.DBN_TOOLS_AUTHENTICATED = <?= $layoutIsGuest ? 'false' : 'true' ?>;
window.DBN_TOOLS_LANG = <?= json_encode($uiLang, JSON_UNESCAPED_UNICODE) ?>;
<?php if ($layoutFreeTierBalance >= 0): ?>
window.DBN_FREE_TIER_BALANCE = <?= $layoutFreeTierBalance ?>;
@@ -49,6 +57,15 @@ window.DBN_FREE_TIER_BALANCE = <?= $layoutFreeTierBalance ?>;
<button onclick="this.parentElement.remove()" aria-label="Dismiss" class="syttende-mai-close">✕</button>
</div>
<?php endif; ?>
<?php if ($layoutIsGuest): ?>
<div id="guestBanner" class="guest-banner" role="alert" aria-live="polite">
<span>Du er ikke innlogget — verktøyene krever konto for å fungere.</span>
<a href="/?return=<?= $layoutReturnUrl ?>" class="guest-banner__login">Logg inn</a>
<button id="guestBannerClose" class="guest-banner__close" aria-label="Lukk">✕</button>
</div>
<?php endif; ?>
<main id="appShell" class="app-shell">
<header class="topbar">
<div>
@@ -67,9 +84,19 @@ window.DBN_FREE_TIER_BALANCE = <?= $layoutFreeTierBalance ?>;
<a href="<?= htmlspecialchars($langPath . '?lang=' . $langCode) ?>" class="<?= $langCode === $uiLang ? 'is-active' : '' ?>"><?= htmlspecialchars(dbnToolsLanguageLabel($langCode)) ?></a>
<?php endforeach; ?>
</nav>
<?php if ($layoutIsGuest): ?>
<a href="/?return=<?= $layoutReturnUrl ?>" class="secondary-button" style="text-decoration:none;">Logg inn</a>
<?php else: ?>
<a href="/dashboard/" class="secondary-button" style="text-decoration:none;">📚 Min korpus</a>
<span id="healthPill" class="status-pill"><?= htmlspecialchars(dbnToolsT('session_active', $uiLang)) ?></span>
<button id="healthButton" class="secondary-button" type="button"><?= htmlspecialchars(dbnToolsT('health', $uiLang)) ?></button>
<span class="topbar-user">
<?php if ($layoutUserDisplay !== ''): ?>
<span class="topbar-user__name" title="<?= htmlspecialchars($layoutAuthUser['email'] ?? '') ?>"><?= htmlspecialchars($layoutUserDisplay) ?></span>
<?php endif; ?>
<a href="/api/logout.php" class="topbar-user__logout">Logg ut</a>
</span>
<?php endif; ?>
</div>
</header>