Files
gh-christianlouis-dmarq/backend/app/templates/layouts/base.html
T
copilot-swe-agent[bot] 531dc968a8 feat: integrate Logto OIDC for user authentication
- Add Logto OIDC integration (app/core/logto.py): CookieStorage adapter,
  create/decode session token helpers, sync_logto_user upsert
- New auth endpoints (/api/v1/auth): sign-in, callback, sign-out, me
- AuthRedirectMiddleware: protects HTML pages, redirects to /setup when
  Logto is unconfigured, to /login otherwise
- Update require_admin_auth: accepts dmarq_session cookie JWT first,
  then API key, then Bearer JWT (fully backward compatible)
- Update User model: add logto_id, username, picture, created_at, updated_at;
  make hashed_password nullable for Logto-only users; is_superuser default=True
- New Alembic migration d4e5f6a7b8c9 for the above schema changes
- Add LOGTO_ENDPOINT / LOGTO_APP_ID / LOGTO_APP_SECRET / LOGTO_REDIRECT_URI
  settings with logto_configured property
- Create login.html (Sign in with Logto button) and setup.html (step-by-step
  configuration guide)
- Update base.html: user menu with avatar/name and sign-out via Alpine.js
  fetch to /api/v1/auth/me
- Update settings.html: remove localStorage adminApiKey; session cookie is
  sent automatically by browser; add 401 → /login redirect
- Update requirements.txt: replace fastapi-users additions with logto + aiohttp
- Add test_auth.py: 18 new tests covering session tokens, CookieStorage,
  sync_logto_user, /me, /sign-in (503), /sign-out cookie clearing
- Fix test_security_extra.py: pass Request mock to require_admin_auth;
  add new test_valid_session_cookie_returns_auth_context

Agent-Logs-Url: https://github.com/christianlouis/dmarq/sessions/b448f585-7646-40f8-ae2d-9986c361e3fd

Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
2026-03-30 10:09:50 +00:00

118 lines
5.4 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en" data-theme="dmarqlight" class="dark:data-theme-dmarqdark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}DMARQ - DMARC Monitoring{% endblock %}</title>
<!-- Fonts -->
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&family=Open+Sans:wght@400;500;600&display=swap" rel="stylesheet">
<!-- Tailwind CSS & DaisyUI via CDN -->
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdn.jsdelivr.net/npm/daisyui@4.12.24/dist/full.css" rel="stylesheet" type="text/css"/>
<!-- Alpine.js for interactivity -->
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
{% block head %}{% endblock %}
</head>
<body class="min-h-screen bg-base-100 font-body antialiased">
<!-- Updated Menu Bar -->
<header class="navbar bg-primary text-primary-content"
x-data="userMenu()" x-init="loadUser()">
<div class="flex-1">
<a href="/" class="btn btn-ghost normal-case text-xl">
<img src="/static/img/monogram_light.png" alt="DMARQ Logo" class="w-8 h-8 mr-2">
DMARQ
</a>
</div>
<div class="flex-none">
<ul class="menu menu-horizontal px-1">
<li><a href="/">Dashboard</a></li>
<li><a href="/domains">Domains</a></li>
<li><a href="/reports">Reports</a></li>
<li><a href="/upload">Upload</a></li>
<li><a href="/mail-sources">Mail Sources</a></li>
<li><a href="/settings">Settings</a></li>
</ul>
<!-- User menu -->
<div class="ml-2">
<template x-if="user">
<div class="dropdown dropdown-end">
<label tabindex="0" class="btn btn-ghost btn-circle avatar placeholder">
<div class="bg-primary-content text-primary rounded-full w-8">
<template x-if="user.picture">
<img :src="user.picture" :alt="user.full_name || user.email" class="rounded-full w-8 h-8 object-cover">
</template>
<template x-if="!user.picture">
<span class="text-sm font-semibold" x-text="(user.full_name || user.email || '?')[0].toUpperCase()"></span>
</template>
</div>
</label>
<ul tabindex="0" class="menu menu-sm dropdown-content mt-3 z-[1] p-2 shadow bg-base-100 text-base-content rounded-box w-56">
<li class="menu-title px-2 py-1">
<span class="text-xs font-semibold truncate" x-text="user.full_name || user.email"></span>
<span class="text-xs text-base-content/50 truncate" x-text="user.email" x-show="user.full_name"></span>
</li>
<li><a href="/settings">Settings</a></li>
<li>
<a href="/api/v1/auth/sign-out" class="text-error">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"/>
</svg>
Sign out
</a>
</li>
</ul>
</div>
</template>
<template x-if="!user">
<a href="/login" class="btn btn-ghost btn-sm">Sign in</a>
</template>
</div>
</div>
</header>
<!-- Updated Main Content Area with page-specific data initialization -->
<main class="p-4 md:p-6">
{% block content %}{% endblock %}
</main>
<!-- Scripts -->
{% block scripts %}{% endblock %}
<!-- User-menu Alpine component -->
<script>
function userMenu() {
return {
user: null,
async loadUser() {
try {
const res = await fetch('/api/v1/auth/me');
if (res.ok) {
this.user = await res.json();
}
} catch (_) {
// silently ignore user is simply not shown
}
},
};
}
</script>
<!-- Initialize theme from localStorage -->
<script>
document.addEventListener('DOMContentLoaded', function() {
const darkMode = localStorage.getItem('darkMode') === 'true';
if (darkMode) {
document.documentElement.classList.add('dark');
document.documentElement.setAttribute('data-theme', 'dmarqdark');
} else {
document.documentElement.classList.remove('dark');
document.documentElement.setAttribute('data-theme', 'dmarqlight');
}
});
</script>
</body>
</html>