add a description/howto (AUTH_GATE.md)

Harden auth gate by disabling PWA caching
This commit is contained in:
BlackSigKill 2026-02-09 20:46:43 +01:00
parent 7e6ff83192
commit 20fc0e8f8e
3 changed files with 89 additions and 8 deletions

55
AUTH_GATE.md Normal file
View file

@ -0,0 +1,55 @@
# Global Auth Gate
This document explains the optional server-side login gate and what it implies for your site.
## Overview
- When enabled, all HTML routes require login.
- Login uses Firebase Auth (Google or email) and exchanges a Firebase ID token for a server session.
- The session is stored in a signed cookie and checked on every request.
## Where it runs
- The gate runs only in `vite preview` (production-like server).
- The Vite dev server (`vite dev`) does not enable the gate.
- Static hosting cannot enforce the gate, because there is no server to verify tokens or set cookies.
## Flow
1. User requests `/` or any HTML route.
2. Server checks the `mono_session` cookie.
3. If missing, redirect to `/login`.
4. Login page signs in with Firebase and POSTs to `/api/auth/login`.
5. Server verifies the ID token and sets a session cookie.
6. User is redirected back to `/`.
## Configuration
- `AUTH_ENABLED=true` enables the gate (default is false).
- `AUTH_SECRET` is required when the gate is enabled. It signs the session cookie.
- `FIREBASE_PROJECT_ID` sets the Firebase project used to verify tokens.
- `FIREBASE_CONFIG` (JSON) injects config into the login page.
- `POCKETBASE_URL` hides the custom DB setting field.
- `SESSION_MAX_AGE` sets cookie lifetime in ms (default 7 days).
## Implications for the site
- Requires a server runtime. Pure static hosting will not force login.
- Unauthenticated requests to non-HTML assets return 401.
- `/login` and `/login.html` remain accessible to start the flow.
- Logging out clears the session and redirects to `/login`.
- Authenticated visits to `/login` redirect back to `/`.
## Enable (Docker)
1. `cp .env.example .env`
2. Set `AUTH_ENABLED=true` and `AUTH_SECRET=...`
3. Optionally set `FIREBASE_CONFIG` and `FIREBASE_PROJECT_ID`
4. `docker compose up -d`
5. Visit `http://localhost:3000`
## Enable (local preview)
1. `npm run build`
2. Set env vars in your shell or `.env`
3. `npm run preview`

View file

@ -208,6 +208,26 @@ function hideOfflineNotification() {
}
}
async function disablePwaForAuthGate() {
if (!('serviceWorker' in navigator)) return;
try {
const registrations = await navigator.serviceWorker.getRegistrations();
await Promise.all(registrations.map((registration) => registration.unregister()));
} catch (error) {
console.warn('Failed to unregister service workers:', error);
}
if ('caches' in window) {
try {
const cacheKeys = await caches.keys();
await Promise.all(cacheKeys.map((key) => caches.delete(key)));
} catch (error) {
console.warn('Failed to clear caches:', error);
}
}
}
document.addEventListener('DOMContentLoaded', async () => {
const api = new LosslessAPI(apiSettings);
@ -1419,14 +1439,18 @@ document.addEventListener('DOMContentLoaded', async () => {
});
// PWA Update Logic
const updateSW = registerSW({
onNeedRefresh() {
showUpdateNotification(() => updateSW(true));
},
onOfflineReady() {
console.log('App ready to work offline');
},
});
if (window.__AUTH_GATE__) {
disablePwaForAuthGate();
} else {
const updateSW = registerSW({
onNeedRefresh() {
showUpdateNotification(() => updateSW(true));
},
onOfflineReady() {
console.log('App ready to work offline');
},
});
}
document.getElementById('show-shortcuts-btn')?.addEventListener('click', () => {
showKeyboardShortcuts();

View file

@ -123,6 +123,7 @@ export default function authGatePlugin() {
}
if (loginHtml) {
res.setHeader('Content-Type', 'text/html');
res.setHeader('Cache-Control', 'no-store');
res.end(loginHtml);
} else {
res.statusCode = 404;
@ -182,6 +183,7 @@ export default function authGatePlugin() {
const ext = extname(url);
if ((!ext || ext === '.html') && indexHtml) {
res.setHeader('Content-Type', 'text/html');
res.setHeader('Cache-Control', 'no-store');
res.end(indexHtml);
return;
}