mirror of
https://github.com/decolua/9router.git
synced 2026-05-08 12:01:28 +00:00
- Improved dashboard access control by blocking tunnel/Tailscale access when disabled.
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "9router-app",
|
"name": "9router-app",
|
||||||
"version": "0.3.87",
|
"version": "0.3.88",
|
||||||
"description": "9Router web dashboard",
|
"description": "9Router web dashboard",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -121,8 +121,8 @@ export default function APIPageClient({ machineId }) {
|
|||||||
// Ping once to verify reachable
|
// Ping once to verify reachable
|
||||||
const healthUrl = `${tPublicUrl || tUrl}/api/health`;
|
const healthUrl = `${tPublicUrl || tUrl}/api/health`;
|
||||||
try {
|
try {
|
||||||
const ping = await fetch(healthUrl, { mode: "no-cors", cache: "no-store" });
|
const ping = await fetch(healthUrl, { cache: "no-store" });
|
||||||
if (ping.ok || ping.type === "opaque") {
|
if (ping.ok) {
|
||||||
setTunnelEnabled(true);
|
setTunnelEnabled(true);
|
||||||
} else {
|
} else {
|
||||||
pingTunnelHealth(tPublicUrl || tUrl);
|
pingTunnelHealth(tPublicUrl || tUrl);
|
||||||
@@ -769,7 +769,7 @@ export default function APIPageClient({ machineId }) {
|
|||||||
/>
|
/>
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<p className="font-medium text-sm">Allow dashboard access via tunnel</p>
|
<p className="font-medium text-sm">Allow dashboard access via tunnel</p>
|
||||||
<Tooltip text="When enabled, the dashboard can be accessed through your tunnel or Tailscale URL without requiring login. Only enable if you trust everyone who can reach your tunnel URL." />
|
<Tooltip text="When enabled, the dashboard can be accessed through your tunnel or Tailscale URL (login still required). When disabled, dashboard access via tunnel/Tailscale is completely blocked." />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -8,11 +8,23 @@ const SECRET = new TextEncoder().encode(
|
|||||||
process.env.JWT_SECRET || "9router-default-secret-change-me"
|
process.env.JWT_SECRET || "9router-default-secret-change-me"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
function isTunnelRequest(request, settings) {
|
||||||
|
const host = (request.headers.get("host") || "").split(":")[0].toLowerCase();
|
||||||
|
const tunnelHost = settings.tunnelUrl ? new URL(settings.tunnelUrl).hostname.toLowerCase() : "";
|
||||||
|
const tailscaleHost = settings.tailscaleUrl ? new URL(settings.tailscaleUrl).hostname.toLowerCase() : "";
|
||||||
|
return (tunnelHost && host === tunnelHost) || (tailscaleHost && host === tailscaleHost);
|
||||||
|
}
|
||||||
|
|
||||||
export async function POST(request) {
|
export async function POST(request) {
|
||||||
try {
|
try {
|
||||||
const { password } = await request.json();
|
const { password } = await request.json();
|
||||||
const settings = await getSettings();
|
const settings = await getSettings();
|
||||||
|
|
||||||
|
// Block login via tunnel/tailscale if dashboard access is disabled
|
||||||
|
if (isTunnelRequest(request, settings) && settings.tunnelDashboardAccess !== true) {
|
||||||
|
return NextResponse.json({ error: "Dashboard access via tunnel is disabled" }, { status: 403 });
|
||||||
|
}
|
||||||
|
|
||||||
// Default password is '123456' if not set
|
// Default password is '123456' if not set
|
||||||
const storedHash = settings.password;
|
const storedHash = settings.password;
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ export async function GET() {
|
|||||||
const settings = await getSettings();
|
const settings = await getSettings();
|
||||||
const requireLogin = settings.requireLogin !== false;
|
const requireLogin = settings.requireLogin !== false;
|
||||||
const tunnelDashboardAccess = settings.tunnelDashboardAccess === true;
|
const tunnelDashboardAccess = settings.tunnelDashboardAccess === true;
|
||||||
return NextResponse.json({ requireLogin, tunnelDashboardAccess });
|
const tunnelUrl = settings.tunnelUrl || "";
|
||||||
|
const tailscaleUrl = settings.tailscaleUrl || "";
|
||||||
|
return NextResponse.json({ requireLogin, tunnelDashboardAccess, tunnelUrl, tailscaleUrl });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return NextResponse.json({ requireLogin: true }, { status: 200 });
|
return NextResponse.json({ requireLogin: true }, { status: 200 });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,15 +81,20 @@ export async function proxy(request) {
|
|||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
requireLogin = data.requireLogin !== false;
|
requireLogin = data.requireLogin !== false;
|
||||||
tunnelDashboardAccess = data.tunnelDashboardAccess === true;
|
tunnelDashboardAccess = data.tunnelDashboardAccess === true;
|
||||||
|
|
||||||
|
// Block tunnel/tailscale access if disabled (redirect to login)
|
||||||
|
if (!tunnelDashboardAccess) {
|
||||||
|
const host = (request.headers.get("host") || "").split(":")[0].toLowerCase();
|
||||||
|
const tunnelHost = data.tunnelUrl ? new URL(data.tunnelUrl).hostname.toLowerCase() : "";
|
||||||
|
const tailscaleHost = data.tailscaleUrl ? new URL(data.tailscaleUrl).hostname.toLowerCase() : "";
|
||||||
|
if ((tunnelHost && host === tunnelHost) || (tailscaleHost && host === tailscaleHost)) {
|
||||||
|
return NextResponse.redirect(new URL("/login", request.url));
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// On error, keep defaults (require login, block tunnel)
|
// 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 login not required, allow through
|
||||||
if (!requireLogin) return NextResponse.next();
|
if (!requireLogin) return NextResponse.next();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user