fix: security hardening across platform

- Disable open /api/auth/register endpoint (gateway)
- Require gateway session auth on Immich and Karakeep hooks proxies
- Replace SHA-256 with bcrypt in fitness service (auth + seed)
- Remove hardcoded Telegram user IDs from fitness seed
- Add Secure flag to session cookie
- Add domain allowlist and content-type validation to image proxy
- Strengthen .gitignore (env variants, runtime data, test artifacts)
This commit is contained in:
Yusuf Suleman
2026-03-29 08:25:50 -05:00
parent d1801540ae
commit 6bd23e7e8b
8 changed files with 111 additions and 27 deletions

View File

@@ -8,8 +8,26 @@ const karakeepUrl = env.KARAKEEP_URL || '';
const karakeepApiKey = env.KARAKEEP_API_KEY || '';
export const handle: Handle = async ({ event, resolve }) => {
async function isAuthenticated(request: Request): Promise<boolean> {
const cookie = request.headers.get('cookie') || '';
if (!cookie.includes('platform_session=')) return false;
try {
const res = await fetch(`${gatewayUrl}/api/auth/me`, { headers: { cookie } });
if (!res.ok) return false;
const data = await res.json();
return data.authenticated === true;
} catch {
return false;
}
}
// ── Immich API proxy (shared across all pages) ──
if (event.url.pathname.startsWith('/api/immich/') && immichUrl && immichApiKey) {
if (!await isAuthenticated(event.request)) {
return new Response(JSON.stringify({ error: 'Unauthorized' }), {
status: 401, headers: { 'Content-Type': 'application/json' }
});
}
const immichPath = event.url.pathname.replace('/api/immich', '/api');
// Thumbnail/original image proxy — cache-friendly binary response
@@ -78,6 +96,11 @@ export const handle: Handle = async ({ event, resolve }) => {
// ── Karakeep API proxy (server-to-server) ──
if (event.url.pathname.startsWith('/api/karakeep/') && karakeepUrl && karakeepApiKey) {
if (!await isAuthenticated(event.request)) {
return new Response(JSON.stringify({ error: 'Unauthorized' }), {
status: 401, headers: { 'Content-Type': 'application/json' }
});
}
const action = event.url.pathname.split('/').pop(); // 'save' or 'delete'
try {
const body = await event.request.json();
@@ -125,6 +148,9 @@ export const handle: Handle = async ({ event, resolve }) => {
// Legacy trips-specific Immich thumbnail (keep for backward compat)
if (event.url.pathname.startsWith('/api/trips/immich/thumb/') && immichUrl && immichApiKey) {
if (!await isAuthenticated(event.request)) {
return new Response('', { status: 401 });
}
const assetId = event.url.pathname.split('/').pop();
try {
const response = await fetch(`${immichUrl}/api/assets/${assetId}/thumbnail`, {