feat(photos): community film strip uploads + admin moderation
Backend
- photos table + upload storage under DATA_DIR/uploads (magic-byte sniffing,
no multipart dep, SVG rejected, wx exclusive writes)
- POST/GET /api/photos, GET /api/photos/:id/file with nosniff + sandboxed CSP
- admin routes (ADMIN_EMAILS allowlist): list, delete one, clear all
- identity-keyed rate limits (login 20/15m, signup 5/h, upload 60/h)
- cookie gains Secure when the request is https (via trustProxy)
- /api/auth/me now 200 {user:null} instead of 401 when signed out
Frontend
- landing strip section: signed-in users upload straight from the reel,
guests get a /app?auth=1 link
- /admin page: grid of uploads with delete + clear all
- nginx: nosniff / X-Frame-Options / Referrer-Policy, forward
X-Forwarded-Proto so the API can mark cookies Secure behind TLS
Tests: docker/backend test/security.mjs (45 checks)
This commit is contained in:
@@ -7,10 +7,13 @@ import { ThemeProvider } from './theme/ThemeProvider';
|
||||
import { I18nProvider } from './i18n/I18nProvider';
|
||||
import { Landing } from './Landing';
|
||||
import { Workspace } from './App';
|
||||
import { Admin } from './Admin';
|
||||
|
||||
// Two routes, no router: the landing page and the workspace. nginx serves
|
||||
// index.html for both (SPA fallback), so this is just a pathname check.
|
||||
const isWorkspace = window.location.pathname.startsWith('/app');
|
||||
// Three routes, no router: the landing page, the workspace and the strip
|
||||
// moderation screen. nginx serves index.html for all of them (SPA fallback),
|
||||
// so this is just a pathname check.
|
||||
const path = window.location.pathname;
|
||||
const page = path.startsWith('/app') ? <Workspace /> : path.startsWith('/admin') ? <Admin /> : <Landing />;
|
||||
const root = document.getElementById('root');
|
||||
if (!root) throw new Error('#root missing');
|
||||
|
||||
@@ -18,6 +21,6 @@ if (!root) throw new Error('#root missing');
|
||||
// run the render pipeline twice on every mount.
|
||||
createRoot(root).render(
|
||||
<ThemeProvider>
|
||||
<I18nProvider>{isWorkspace ? <Workspace /> : <Landing />}</I18nProvider>
|
||||
<I18nProvider>{page}</I18nProvider>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user