32 lines
3.4 KiB
TypeScript
32 lines
3.4 KiB
TypeScript
import { NextResponse, type NextRequest } from 'next/server';
|
|
import { authConfig, SESSION_COOKIE, validSession } from './lib/auth';
|
|
export function proxy(request: NextRequest) {
|
|
const path = request.nextUrl.pathname;
|
|
const publicBrandAssets = ['/favicon.ico','/apple-touch-icon.png','/brand/logo-light.svg','/brand/logo-dark.svg','/brand/icon-dark.svg','/studio/favicon.ico','/studio/apple-touch-icon.png','/studio/brand/logo-light.svg','/studio/brand/logo-dark.svg','/studio/brand/icon-dark.svg'];
|
|
if (['GET','HEAD'].includes(request.method) && publicBrandAssets.includes(path)) return NextResponse.next({headers:{'Referrer-Policy':'same-origin'}});
|
|
const responseHeaders = {'Cache-Control':'private, no-store', 'Referrer-Policy':'same-origin'};
|
|
// Public knowledge pages are read-only. Account handlers enforce their own origin and session checks.
|
|
const publicRead = ['GET','HEAD'].includes(request.method) && (path === '/' || ['/workspace','/graphic','/infinite_canvas','/studio','/studio/graphic','/studio/infinite_canvas','/studio/api'].includes(path) || path.startsWith('/studio/_next/') || path === '/protocol' || ['/llms.txt','/index.md','/agent','/agent/index.md','/api/agent-index','/api/guides'].includes(path) || path.startsWith('/guide/') || path.startsWith('/api/guides/'));
|
|
if (path === '/api/canvases' || path.startsWith('/api/canvases/') || path === '/write' || path === '/documents' || path.startsWith('/documents/') || path === '/api/documents' || path.startsWith('/api/documents/') || publicRead || path === '/account' || path.startsWith('/api/account/')) return NextResponse.next({headers:{...responseHeaders,'Referrer-Policy':'no-referrer'}});
|
|
if (path === '/brand-mark.svg' || path === '/login' || path === '/api/auth/login' || path === '/api/auth/logout' || path === '/api/health' || path === '/api/ready' || path.startsWith('/_next/static/')) return NextResponse.next({headers:responseHeaders});
|
|
let config;
|
|
try { config = authConfig(); } catch { return NextResponse.json({error:'登录服务配置不可用'}, {status:503,headers:responseHeaders}); }
|
|
if (!config) {
|
|
if (process.env.AUTH_DISABLED_FOR_TESTS === 'true' && process.env.AUTH_CONFIG_FILE === '') return NextResponse.next();
|
|
return NextResponse.json({error:'管理员登录服务尚未配置'}, {status:503,headers:responseHeaders});
|
|
}
|
|
const bearer = request.headers.get('authorization');
|
|
const token = bearer?.startsWith('Bearer ') ? bearer.slice(7) : request.cookies.get(SESSION_COOKIE)?.value;
|
|
if (validSession(token,config)) {
|
|
if (!['GET','HEAD','OPTIONS'].includes(request.method) && !bearer) {
|
|
const origin = request.headers.get('origin');
|
|
const host = request.headers.get('host');
|
|
try { if (!origin || new URL(origin).host !== host || request.headers.get('sec-fetch-site') === 'cross-site') return NextResponse.json({error:'请求来源不受信任'}, {status:403,headers:responseHeaders}); } catch { return NextResponse.json({error:'请求来源不受信任'}, {status:403,headers:responseHeaders}); }
|
|
}
|
|
return NextResponse.next({headers:responseHeaders});
|
|
}
|
|
if (path.startsWith('/api/') || path.startsWith('/studio/api/') || !['GET','HEAD'].includes(request.method)) return NextResponse.json({error:'请先使用动态码登录'}, {status:401,headers:responseHeaders});
|
|
const login = request.nextUrl.clone(); login.pathname = '/login'; login.search = ''; login.searchParams.set('next',path + request.nextUrl.search);
|
|
return NextResponse.redirect(login,{headers:responseHeaders});
|
|
}
|