Files
LiveServer-M1/src/middleware.ts
joylessorchid 3846e3e00d feat: LiveServer-M1 v1 — educational video conferencing platform
Full MVP implementation:
- Next.js 16 + React 19 + TypeScript + Tailwind CSS v4
- LiveKit integration (rooms, tokens, webhooks, moderation)
- better-auth (email/password, sessions, roles)
- Prisma 7 + PostgreSQL (9 models, 3 enums)
- 15 API routes (auth, rooms, lobby, chat, files, moderation, hand-raise)
- 7 pages (landing, auth, dashboard, join, video room)
- SSE-based waiting room with host approval flow
- Security: PIN rate limiting, session fingerprint bans, chat/files auth
- Python AI Agent (Deepgram STT + OpenAI summarization)
- Docker Compose (local + production with Traefik + Let's Encrypt)
- Interactive setup script (setup.sh)
- Dev protection middleware (DEV_ACCESS_KEY, ALLOWED_IPS)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-22 13:57:53 +03:00

72 lines
2.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { NextRequest, NextResponse } from "next/server";
/**
* Защита для локальной разработки.
* Если задана env ALLOWED_IPS — пропускает только эти IP.
* Если задана env DEV_ACCESS_KEY — требует ?key=... или cookie dev_access_key.
* В проде (DOMAIN задан) — middleware пропускает всё.
*/
export function middleware(req: NextRequest) {
// В проде — пропускаем
if (process.env.DOMAIN) {
return NextResponse.next();
}
const devAccessKey = process.env.DEV_ACCESS_KEY;
const allowedIps = process.env.ALLOWED_IPS; // "192.168.1.10,192.168.1.11"
// Проверка IP whitelist
if (allowedIps) {
const clientIp =
req.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ||
req.headers.get("x-real-ip") ||
"unknown";
const allowed = allowedIps.split(",").map((ip) => ip.trim());
// localhost всегда разрешён
if (
!allowed.includes(clientIp) &&
clientIp !== "127.0.0.1" &&
clientIp !== "::1" &&
clientIp !== "unknown"
) {
return new NextResponse("Forbidden", { status: 403 });
}
}
// Проверка access key
if (devAccessKey) {
const url = new URL(req.url);
const keyParam = url.searchParams.get("key");
const keyCookie = req.cookies.get("dev_access_key")?.value;
// Если ключ в query — ставим cookie и редиректим без key
if (keyParam === devAccessKey) {
url.searchParams.delete("key");
const res = NextResponse.redirect(url);
res.cookies.set("dev_access_key", devAccessKey, {
httpOnly: true,
maxAge: 60 * 60 * 24 * 7, // 7 дней
path: "/",
});
return res;
}
// Если нет валидной cookie — блокируем
if (keyCookie !== devAccessKey) {
return new NextResponse(
"Access denied. Add ?key=YOUR_DEV_ACCESS_KEY to URL.",
{ status: 403 }
);
}
}
return NextResponse.next();
}
export const config = {
// Защищаем всё кроме статики и API вебхуков
matcher: ["/((?!_next/static|_next/image|favicon.ico|api/livekit/webhook).*)"],
};