mirror of
https://github.com/THU-MAIC/OpenMAIC.git
synced 2026-10-02 01:15:18 +08:00
When ACCESS_CODE is set, verification tokens (timestamp.HMAC) never expired because neither verifier checked the timestamp, and POST /api/access-code/verify had no attempt throttling. - Enforce a 7-day token lifetime in a shared, Edge-safe module used by both the Node verifier and the middleware Web-Crypto verifier, and reject non-canonical signatures in both. - Rate-limit verification only when the client identity is trusted (TRUST_PROXY_HEADERS=true): a per-client sliding window (10 failures / 60s) whose attempt is reserved atomically at check time, returning 429 with Retry-After. Without a trusted proxy the app cannot attribute requests to a client, so no shared throttle is applied — a long random ACCESS_CODE is the protection, and a warning is logged when it is short. - Store bounded, copied identity keys so a large forwarding header cannot retain memory. - Document the behavior in .env.example, README, and configuration docs. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
51 lines
1.9 KiB
TypeScript
51 lines
1.9 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server';
|
|
|
|
import { isAgentRuntimeConfigured, isProWorkbenchEnabled } from '@/lib/config/feature-flags';
|
|
import { verifyAccessTokenEdge } from '@/lib/server/access-token-edge';
|
|
|
|
export async function middleware(request: NextRequest) {
|
|
const { pathname } = request.nextUrl;
|
|
|
|
// Return an actual server-side 404 when either half of the workbench is off.
|
|
// Edge middleware cannot reliably inspect server-only deployment variables,
|
|
// so it enforces the public gate and leaves the complete runtime/database
|
|
// check to Node. A Node-hosted middleware uses the same gate as startup.
|
|
const canInspectServerRuntime = process.env.NEXT_RUNTIME !== 'edge';
|
|
const workbenchEnabled =
|
|
isProWorkbenchEnabled() && (!canInspectServerRuntime || isAgentRuntimeConfigured());
|
|
if (!workbenchEnabled && (pathname === '/workbench' || pathname.startsWith('/workbench/'))) {
|
|
return new NextResponse('Not found', { status: 404 });
|
|
}
|
|
|
|
const accessCode = process.env.ACCESS_CODE;
|
|
if (!accessCode) {
|
|
return NextResponse.next();
|
|
}
|
|
|
|
// Whitelist: access-code endpoints, health check
|
|
if (pathname.startsWith('/api/access-code/') || pathname === '/api/health') {
|
|
return NextResponse.next();
|
|
}
|
|
|
|
// Check cookie — validate HMAC signature, not just existence
|
|
const cookie = request.cookies.get('openmaic_access');
|
|
if (cookie?.value && (await verifyAccessTokenEdge(cookie.value, accessCode))) {
|
|
return NextResponse.next();
|
|
}
|
|
|
|
// API requests without valid cookie → 401
|
|
if (pathname.startsWith('/api/')) {
|
|
return NextResponse.json(
|
|
{ success: false, errorCode: 'INVALID_REQUEST', error: 'Access code required' },
|
|
{ status: 401 },
|
|
);
|
|
}
|
|
|
|
// Page requests → let through, frontend shows modal
|
|
return NextResponse.next();
|
|
}
|
|
|
|
export const config = {
|
|
matcher: ['/((?!_next/static|_next/image|favicon.ico|logos/).*)'],
|
|
};
|