531dc968a8
- 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>
118 lines
5.4 KiB
HTML
118 lines
5.4 KiB
HTML
<!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> |