Files
friessn 6c9cd67a08 Rebuild auth on Keycloak OIDC, fix rootless Ansible deploy
Replaces the single shared HTTP Basic service-account credential (which
caused a production outage from a username mismatch) with per-user login:
Keycloak (already running on this VM for gcnm, now also fronted on
auth.friessn.de with its own "homekeeper" realm) authenticates the user
once via the landing page, FastAPI verifies the OIDC id_token and mints
its own signed session JWT as a cookie, and both Shiny apps forward that
per-session token as a Bearer credential instead of a static shared one.
Authorization is a simple ALLOWED_USERS allowlist; the old auth.users
table and bcrypt seeding are gone entirely.

Also carries forward the in-progress rootless Podman/Quadlet migration
(gitea, homekeeper, podman roles) and fixes a pre-existing bug where
each role's handlers were malformed inside tasks/main.yml instead of
their own handlers/main.yml, which broke ansible-playbook entirely.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FbiCdckkTX2HyAkyi1R39d
2026-07-13 06:31:36 +00:00

140 lines
3.2 KiB
HTML
Raw Permalink 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="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Homekeeper</title>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background: #f8f9fa;
min-height: 100vh;
display: flex;
flex-direction: column;
}
header {
background: #fff;
border-bottom: 1px solid #dee2e6;
padding: 1rem 1.5rem;
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
}
header h1 {
font-size: 1.25rem;
font-weight: 600;
color: #212529;
}
#auth-area {
font-size: 0.875rem;
color: #6c757d;
white-space: nowrap;
}
#auth-area a {
color: #0d6efd;
text-decoration: none;
}
main {
flex: 1;
padding: 2rem 1.5rem;
max-width: 600px;
width: 100%;
margin: 0 auto;
}
h2 {
font-size: 0.875rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
color: #6c757d;
margin-bottom: 1rem;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: 1rem;
}
.tile {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 0.625rem;
background: #fff;
border: 1px solid #dee2e6;
border-radius: 0.75rem;
padding: 1.5rem 1rem;
text-decoration: none;
color: #212529;
cursor: pointer;
transition: box-shadow 0.15s, transform 0.1s;
-webkit-tap-highlight-color: transparent;
}
.tile:hover {
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
transform: translateY(-1px);
}
.tile:active {
transform: scale(0.97);
}
.tile-icon {
font-size: 2rem;
line-height: 1;
}
.tile-label {
font-size: 0.9375rem;
font-weight: 500;
text-align: center;
}
</style>
</head>
<body>
<header>
<h1>Homekeeper</h1>
<span id="auth-area"><a href="/api/v1/auth/login">Anmelden</a></span>
</header>
<main>
<h2>Apps</h2>
<div class="grid">
<a class="tile" href="/beekeeper/">
<span class="tile-icon">🐝</span>
<span class="tile-label">Beekeeper</span>
</a>
<a class="tile" href="/listkeeper/">
<span class="tile-icon">📋</span>
<span class="tile-label">Listkeeper</span>
</a>
</div>
</main>
<script>
fetch('/api/v1/auth/me', { credentials: 'same-origin' })
.then(function (res) { return res.ok ? res.json() : null; })
.then(function (data) {
if (!data) return;
var area = document.getElementById('auth-area');
area.textContent = 'Angemeldet als ' + data.username + ' ';
var logout = document.createElement('a');
logout.href = '/api/v1/auth/logout';
logout.textContent = 'Abmelden';
area.appendChild(logout);
})
.catch(function () {});
</script>
</body>
</html>