Add multi-user support: Authelia OIDC, SQLite, Garage S3

- Auth via Authelia OIDC (public client + PKCE) with signed session cookies
- SQLite DB for users, rides, and insights (SQLAlchemy)
- Garage S3 for GPX file storage (boto3)
- All API endpoints scoped to authenticated user
- Dockerfile + docker-compose.yml with Garage service
- Frontend auth check: redirects to /auth/login on 401, shows user badge

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-08-06 22:12:23 -04:00
parent 65a1bb010d
commit c6d9a3b8b6
12 changed files with 853 additions and 134 deletions
+21 -1
View File
@@ -33,6 +33,10 @@
#sidebar-header { padding: 16px; border-bottom: 1px solid var(--border); }
#sidebar-header h1 { font-size: 15px; font-weight: 600; color: var(--text); }
#sidebar-header p { color: var(--muted); font-size: 11px; margin-top: 2px; }
#user-row { display: none; align-items: center; justify-content: space-between; margin-top: 4px; }
#user-name { font-size: 11px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#logout-link { font-size: 11px; color: var(--muted); text-decoration: none; flex-shrink: 0; margin-left: 6px; }
#logout-link:hover { color: var(--accent); }
#ride-list { flex: 1; overflow-y: auto; }
#ride-list::-webkit-scrollbar { width: 4px; }
#ride-list::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
@@ -147,6 +151,10 @@
<div id="sidebar-header">
<h1>bikeslop</h1>
<p id="ride-count">Loading...</p>
<div id="user-row">
<span id="user-name"></span>
<a id="logout-link" href="/auth/logout">Sign out</a>
</div>
<div class="rhr-row">
<label for="rhr-input">Resting HR</label>
<input id="rhr-input" type="number" min="30" max="100" value="60">
@@ -804,7 +812,19 @@ document.getElementById('unit-toggle').addEventListener('click', () => {
if (currentPoints && currentPoints.length) renderCharts(currentPoints);
});
loadRides();
async function initAuth() {
const res = await fetch('/api/me');
if (res.status === 401) {
window.location.href = '/auth/login';
return;
}
const me = await res.json();
const userRow = document.getElementById('user-row');
userRow.style.display = 'flex';
document.getElementById('user-name').textContent = me.display_name || me.email || '';
loadRides();
}
initAuth();
</script>
</body>
</html>