- Integrated Google TTS languages from a separate module for better maintainability. - Updated local device voice fetching to support both macOS and Windows, improving cross-platform compatibility. - Enhanced dashboard route protection by adding dynamic settings for login requirements and tunnel access. - Introduced UI elements for managing security settings related to API key requirements and dashboard access via tunnel. - Added default TTS response example in the media provider page for better user guidance. - Updated constants to reflect changes in TTS provider configurations. This commit improves the overall user experience and security of the TTS features.
120 lines
3.5 KiB
JavaScript
120 lines
3.5 KiB
JavaScript
import { NextResponse } from "next/server";
|
|
import { jwtVerify } from "jose";
|
|
|
|
const SECRET = new TextEncoder().encode(
|
|
process.env.JWT_SECRET || "9router-default-secret-change-me"
|
|
);
|
|
|
|
// Always require JWT token regardless of requireLogin setting
|
|
const ALWAYS_PROTECTED = [
|
|
"/api/shutdown",
|
|
"/api/settings/database",
|
|
];
|
|
|
|
// Require auth, but allow through if requireLogin is disabled
|
|
const PROTECTED_API_PATHS = [
|
|
"/api/settings",
|
|
"/api/keys",
|
|
"/api/providers/client",
|
|
"/api/provider-nodes/validate",
|
|
];
|
|
|
|
function isLocalRequest(request) {
|
|
const host = request.headers.get("host") || "";
|
|
const hostname = host.split(":")[0];
|
|
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1";
|
|
}
|
|
|
|
async function hasValidToken(request) {
|
|
const token = request.cookies.get("auth_token")?.value;
|
|
if (!token) return false;
|
|
try {
|
|
await jwtVerify(token, SECRET);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async function isAuthenticated(request) {
|
|
if (await hasValidToken(request)) return true;
|
|
// Allow if requireLogin is disabled
|
|
const origin = request.nextUrl.origin;
|
|
try {
|
|
const res = await fetch(`${origin}/api/settings/require-login`);
|
|
const data = await res.json();
|
|
if (data.requireLogin === false) return true;
|
|
} catch {
|
|
// On error, require login
|
|
}
|
|
return false;
|
|
}
|
|
|
|
export async function proxy(request) {
|
|
const { pathname } = request.nextUrl;
|
|
|
|
// Always protected - allow localhost or valid JWT only
|
|
if (ALWAYS_PROTECTED.some((p) => pathname.startsWith(p))) {
|
|
if (isLocalRequest(request) || await hasValidToken(request))
|
|
return NextResponse.next();
|
|
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
}
|
|
|
|
// Protect sensitive API endpoints (bypass if localhost or requireLogin = false)
|
|
if (PROTECTED_API_PATHS.some((p) => pathname.startsWith(p))) {
|
|
if (pathname === "/api/settings/require-login") return NextResponse.next();
|
|
if (isLocalRequest(request) || await isAuthenticated(request))
|
|
return NextResponse.next();
|
|
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
}
|
|
|
|
|
|
|
|
// Protect all dashboard routes
|
|
if (pathname.startsWith("/dashboard")) {
|
|
const origin = request.nextUrl.origin;
|
|
let requireLogin = true;
|
|
let tunnelDashboardAccess = false;
|
|
|
|
try {
|
|
const res = await fetch(`${origin}/api/settings/require-login`);
|
|
const data = await res.json();
|
|
requireLogin = data.requireLogin !== false;
|
|
tunnelDashboardAccess = data.tunnelDashboardAccess === true;
|
|
} catch {
|
|
// On error, keep defaults (require login, block tunnel)
|
|
}
|
|
|
|
// Block tunnel access if disabled (checked before token to enforce the setting)
|
|
if (!isLocalRequest(request) && !tunnelDashboardAccess) {
|
|
return NextResponse.redirect(new URL("/login", request.url));
|
|
}
|
|
|
|
// If login not required, allow through
|
|
if (!requireLogin) return NextResponse.next();
|
|
|
|
// Verify JWT token
|
|
const token = request.cookies.get("auth_token")?.value;
|
|
if (token) {
|
|
try {
|
|
await jwtVerify(token, SECRET);
|
|
return NextResponse.next();
|
|
} catch {
|
|
return NextResponse.redirect(new URL("/login", request.url));
|
|
}
|
|
}
|
|
|
|
return NextResponse.redirect(new URL("/login", request.url));
|
|
}
|
|
|
|
// Redirect / to /dashboard if logged in, or /dashboard if it's the root
|
|
if (pathname === "/") {
|
|
return NextResponse.redirect(new URL("/dashboard", request.url));
|
|
}
|
|
|
|
return NextResponse.next();
|
|
}
|
|
|
|
export const config = {
|
|
matcher: ["/", "/dashboard/:path*"],
|
|
};
|