mirror of
https://github.com/Nezumi-2711/9router.git
synced 2026-09-22 20:00:47 +00:00
fix(auth): avoid stale redirects after auth changes
Use full-page navigation after login/logout so the dashboard reloads with the fresh auth cookie, and mark login/logout responses no-store. Fixes #2100 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
committed by
decolua
co-authored by
Cursor
parent
ab5ec52f28
commit
6e9c7bf448
@@ -1,7 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect, useRef } from "react";
|
import { useState, useEffect, useRef } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
|
||||||
import { Card, Button, Toggle, Input } from "@/shared/components";
|
import { Card, Button, Toggle, Input } from "@/shared/components";
|
||||||
import Modal, { ConfirmModal } from "@/shared/components/Modal";
|
import Modal, { ConfirmModal } from "@/shared/components/Modal";
|
||||||
import LanguageSwitcher from "@/shared/components/LanguageSwitcher";
|
import LanguageSwitcher from "@/shared/components/LanguageSwitcher";
|
||||||
@@ -21,7 +20,6 @@ function getLocaleFromCookie() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function ProfilePage() {
|
export default function ProfilePage() {
|
||||||
const router = useRouter();
|
|
||||||
const { theme, setTheme, isDark } = useTheme();
|
const { theme, setTheme, isDark } = useTheme();
|
||||||
const [locale, setLocale] = useState("en");
|
const [locale, setLocale] = useState("en");
|
||||||
const [langOpen, setLangOpen] = useState(false);
|
const [langOpen, setLangOpen] = useState(false);
|
||||||
@@ -569,8 +567,7 @@ export default function ProfilePage() {
|
|||||||
try {
|
try {
|
||||||
const res = await fetch("/api/auth/logout", { method: "POST" });
|
const res = await fetch("/api/auth/logout", { method: "POST" });
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
router.push("/login");
|
window.location.assign("/login");
|
||||||
router.refresh();
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to logout:", err);
|
console.error("Failed to logout:", err);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { checkLock, recordFail, recordSuccess, getClientIp } from "@/lib/auth/lo
|
|||||||
import { isLocalRequest } from "@/dashboardGuard";
|
import { isLocalRequest } from "@/dashboardGuard";
|
||||||
|
|
||||||
const RESET_HINT = "Forgot password? Reset to default via 9Router CLI → Settings → Reset Password to Default.";
|
const RESET_HINT = "Forgot password? Reset to default via 9Router CLI → Settings → Reset Password to Default.";
|
||||||
|
const NO_STORE_HEADERS = { "Cache-Control": "no-store" };
|
||||||
|
|
||||||
function isTunnelRequest(request, settings) {
|
function isTunnelRequest(request, settings) {
|
||||||
const host = (request.headers.get("host") || "").split(":")[0].toLowerCase();
|
const host = (request.headers.get("host") || "").split(":")[0].toLowerCase();
|
||||||
@@ -61,7 +62,7 @@ export async function POST(request) {
|
|||||||
const mustChangePassword =
|
const mustChangePassword =
|
||||||
!storedHash && !process.env.INITIAL_PASSWORD && !isLocalRequest(request);
|
!storedHash && !process.env.INITIAL_PASSWORD && !isLocalRequest(request);
|
||||||
|
|
||||||
return NextResponse.json({ success: true, mustChangePassword });
|
return NextResponse.json({ success: true, mustChangePassword }, { headers: NO_STORE_HEADERS });
|
||||||
}
|
}
|
||||||
|
|
||||||
const { remainingBeforeLock } = recordFail(ip);
|
const { remainingBeforeLock } = recordFail(ip);
|
||||||
|
|||||||
@@ -8,5 +8,5 @@ export async function POST() {
|
|||||||
cookieStore.delete("oidc_state");
|
cookieStore.delete("oidc_state");
|
||||||
cookieStore.delete("oidc_nonce");
|
cookieStore.delete("oidc_nonce");
|
||||||
cookieStore.delete("oidc_code_verifier");
|
cookieStore.delete("oidc_code_verifier");
|
||||||
return NextResponse.json({ success: true });
|
return NextResponse.json({ success: true }, { headers: { "Cache-Control": "no-store" } });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { Card, Button, Input } from "@/shared/components";
|
import { Card, Button, Input } from "@/shared/components";
|
||||||
import { useRouter } from "next/navigation";
|
|
||||||
|
|
||||||
export default function LoginPage() {
|
export default function LoginPage() {
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
@@ -16,7 +15,6 @@ export default function LoginPage() {
|
|||||||
const [oidcLoginLabel, setOidcLoginLabel] = useState("Sign in with OIDC");
|
const [oidcLoginLabel, setOidcLoginLabel] = useState("Sign in with OIDC");
|
||||||
const [mustChange, setMustChange] = useState(false);
|
const [mustChange, setMustChange] = useState(false);
|
||||||
const [newPassword, setNewPassword] = useState("");
|
const [newPassword, setNewPassword] = useState("");
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
// Countdown for rate-limit
|
// Countdown for rate-limit
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -40,8 +38,7 @@ export default function LoginPage() {
|
|||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (data.requireLogin === false) {
|
if (data.requireLogin === false) {
|
||||||
router.push("/dashboard");
|
window.location.assign("/dashboard");
|
||||||
router.refresh();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setHasPassword(!!data.hasPassword);
|
setHasPassword(!!data.hasPassword);
|
||||||
@@ -58,7 +55,7 @@ export default function LoginPage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
checkAuth();
|
checkAuth();
|
||||||
}, [router]);
|
}, []);
|
||||||
|
|
||||||
const handleLogin = async (e) => {
|
const handleLogin = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -79,8 +76,7 @@ export default function LoginPage() {
|
|||||||
setMustChange(true);
|
setMustChange(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
router.push("/dashboard");
|
window.location.assign("/dashboard");
|
||||||
router.refresh();
|
|
||||||
} else {
|
} else {
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
setError(data.error || "Invalid password");
|
setError(data.error || "Invalid password");
|
||||||
@@ -106,8 +102,7 @@ export default function LoginPage() {
|
|||||||
body: JSON.stringify({ currentPassword: password, newPassword }),
|
body: JSON.stringify({ currentPassword: password, newPassword }),
|
||||||
});
|
});
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
router.push("/dashboard");
|
window.location.assign("/dashboard");
|
||||||
router.refresh();
|
|
||||||
} else {
|
} else {
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
setError(data.error || "Failed to set password");
|
setError(data.error || "Failed to set password");
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import { usePathname, useRouter } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import ProviderIcon from "@/shared/components/ProviderIcon";
|
import ProviderIcon from "@/shared/components/ProviderIcon";
|
||||||
@@ -180,7 +180,6 @@ const getPageInfo = (pathname) => {
|
|||||||
|
|
||||||
export default function Header({ onMenuClick, showMenuButton = true }) {
|
export default function Header({ onMenuClick, showMenuButton = true }) {
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const router = useRouter();
|
|
||||||
const [displayName, setDisplayName] = useState("");
|
const [displayName, setDisplayName] = useState("");
|
||||||
const [loginMethod, setLoginMethod] = useState("");
|
const [loginMethod, setLoginMethod] = useState("");
|
||||||
const [donateOpen, setDonateOpen] = useState(false);
|
const [donateOpen, setDonateOpen] = useState(false);
|
||||||
@@ -219,8 +218,7 @@ export default function Header({ onMenuClick, showMenuButton = true }) {
|
|||||||
try {
|
try {
|
||||||
const res = await fetch("/api/auth/logout", { method: "POST" });
|
const res = await fetch("/api/auth/logout", { method: "POST" });
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
router.push("/login");
|
window.location.assign("/login");
|
||||||
router.refresh();
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to logout:", err);
|
console.error("Failed to logout:", err);
|
||||||
|
|||||||
Reference in New Issue
Block a user