mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-07-17 01:07:31 +02:00
ea457f27da
Skip the Secure flag on the session cookie when the request comes from a loopback address (localhost, 127.0.0.1, ::1). The Docker image sets NODE_ENV=production which always enabled Secure, but browsers silently drop Secure cookies on plain HTTP — breaking the admin panel for self-hosted users accessing http://localhost:3000. Fixes #129
109 lines
3.2 KiB
TypeScript
109 lines
3.2 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server';
|
|
import {
|
|
clearAdminSessionToken,
|
|
createAdminSessionToken,
|
|
hasAdminSessionToken,
|
|
} from '@/lib/server/adminSessionStore';
|
|
|
|
const COOKIE_NAME = 'sb_admin_session';
|
|
const COOKIE_MAX_AGE = 60 * 60 * 8;
|
|
const NO_STORE_HEADERS = {
|
|
'Cache-Control': 'no-store, max-age=0',
|
|
Pragma: 'no-cache',
|
|
};
|
|
|
|
function cookieOptions(req: NextRequest) {
|
|
const host = req.headers.get('host') ?? '';
|
|
const isLoopback = /^(localhost|127\.0\.0\.1|\[::1\])(:\d+)?$/.test(host);
|
|
return {
|
|
httpOnly: true,
|
|
sameSite: 'strict' as const,
|
|
secure: process.env.NODE_ENV === 'production' && !isLoopback,
|
|
path: '/',
|
|
maxAge: COOKIE_MAX_AGE,
|
|
};
|
|
}
|
|
|
|
async function verifyAdminKey(adminKey: string): Promise<{ ok: true } | { ok: false; detail: string }> {
|
|
const backendUrl = process.env.BACKEND_URL ?? 'http://127.0.0.1:8000';
|
|
const verifyAgainstBackend = async (): Promise<
|
|
{ ok: true } | { ok: false; detail: string }
|
|
> => {
|
|
try {
|
|
const res = await fetch(`${backendUrl}/api/settings/privacy-profile`, {
|
|
method: 'GET',
|
|
headers: { 'X-Admin-Key': adminKey },
|
|
cache: 'no-store',
|
|
});
|
|
if (res.ok) return { ok: true };
|
|
const data = await res.json().catch(() => ({}));
|
|
return {
|
|
ok: false,
|
|
detail: String(data?.detail || data?.message || 'Unable to verify admin key'),
|
|
};
|
|
} catch {
|
|
return {
|
|
ok: false,
|
|
detail: 'Unable to verify admin key against backend',
|
|
};
|
|
}
|
|
};
|
|
|
|
const configuredAdmin = String(process.env.ADMIN_KEY || '').trim();
|
|
if (configuredAdmin) {
|
|
if (adminKey !== configuredAdmin) {
|
|
return { ok: false, detail: 'Invalid admin key' };
|
|
}
|
|
return verifyAgainstBackend();
|
|
}
|
|
|
|
return verifyAgainstBackend();
|
|
}
|
|
|
|
export async function POST(req: NextRequest) {
|
|
const body = await req.json().catch(() => ({}));
|
|
const adminKey = String(body?.adminKey || '').trim();
|
|
if (!adminKey) {
|
|
return NextResponse.json(
|
|
{ ok: false, detail: 'Missing admin key' },
|
|
{ status: 400, headers: NO_STORE_HEADERS },
|
|
);
|
|
}
|
|
const verification = await verifyAdminKey(adminKey);
|
|
if (!verification.ok) {
|
|
return NextResponse.json(
|
|
{ ok: false, detail: verification.detail },
|
|
{ status: 403, headers: NO_STORE_HEADERS },
|
|
);
|
|
}
|
|
const existingToken = req.cookies.get(COOKIE_NAME)?.value || '';
|
|
if (existingToken) {
|
|
clearAdminSessionToken(existingToken);
|
|
}
|
|
const sessionToken = createAdminSessionToken(adminKey, COOKIE_MAX_AGE);
|
|
const res = NextResponse.json({ ok: true }, { headers: NO_STORE_HEADERS });
|
|
res.cookies.set(COOKIE_NAME, sessionToken, cookieOptions(req));
|
|
return res;
|
|
}
|
|
|
|
export async function DELETE(req: NextRequest) {
|
|
const existingToken = req.cookies.get(COOKIE_NAME)?.value || '';
|
|
if (existingToken) {
|
|
clearAdminSessionToken(existingToken);
|
|
}
|
|
const res = NextResponse.json({ ok: true }, { headers: NO_STORE_HEADERS });
|
|
res.cookies.set(COOKIE_NAME, '', {
|
|
...cookieOptions(req),
|
|
maxAge: 0,
|
|
});
|
|
return res;
|
|
}
|
|
|
|
export async function GET(req: NextRequest) {
|
|
const token = req.cookies.get(COOKIE_NAME)?.value || '';
|
|
return NextResponse.json(
|
|
{ ok: true, hasSession: hasAdminSessionToken(token) },
|
|
{ headers: NO_STORE_HEADERS },
|
|
);
|
|
}
|