diff --git a/frontend/components/auth/login-form.tsx b/frontend/components/auth/login-form.tsx index c29be39a..c89fd715 100644 --- a/frontend/components/auth/login-form.tsx +++ b/frontend/components/auth/login-form.tsx @@ -1,8 +1,8 @@ "use client" -import {useEffect, useMemo, useRef, useState} from "react" +import {useEffect, useRef, useState} from "react" import {useMutation, useQuery} from "@tanstack/react-query" -import {useRouter, useSearchParams} from "next/navigation" +import {useSearchParams} from "next/navigation" import {EyeIcon, EyeOffIcon} from "lucide-react" import {toast} from "sonner" import Link from "next/link" @@ -20,21 +20,6 @@ import services from "@/lib/services" import type {LoginRequest} from "@/lib/services/auth/types" import {safeRedirectTarget} from "@/lib/utils" -function getRedirectTarget(searchParams: ReturnType) { - const callbackUrl = searchParams.get("callbackUrl") - const storedRedirect = - typeof window === "undefined" - ? null - : sessionStorage.getItem("redirect_after_login") - const target = callbackUrl || storedRedirect || "/home" - - if (storedRedirect && typeof window !== "undefined") { - sessionStorage.removeItem("redirect_after_login") - } - - return safeRedirectTarget(target) -} - function persistRedirectTarget(searchParams: ReturnType) { const callbackUrl = searchParams.get("callbackUrl") if (callbackUrl && typeof window !== "undefined") { @@ -49,7 +34,6 @@ function configBool(value: string | undefined, fallback: boolean) { } export function LoginForm({ onOTPStateChange }: { onOTPStateChange?: (show: boolean) => void }) { - const router = useRouter() const searchParams = useSearchParams() const { setUser } = useAuth() const [username, setUsername] = useState("") @@ -89,11 +73,6 @@ export function LoginForm({ onOTPStateChange }: { onOTPStateChange?: (show: bool enabled: configBool(publicConfigQuery.data?.oidc_login_enabled, true), }) - const redirectTarget = useMemo( - () => getRedirectTarget(searchParams), - [searchParams], - ) - const capEnabled = configBool(publicConfigQuery.data?.cap_login_enabled, false) const capAutoSolve = configBool(publicConfigQuery.data?.cap_auto_solve, true) @@ -110,7 +89,6 @@ export function LoginForm({ onOTPStateChange }: { onOTPStateChange?: (show: bool }, onSuccess: (user) => { setUser(user) - router.replace(redirectTarget) toast.success("登录成功") }, onError: (error: Error) => { diff --git a/frontend/components/auth/login-page.tsx b/frontend/components/auth/login-page.tsx index 8ee38dc3..52a4ba14 100644 --- a/frontend/components/auth/login-page.tsx +++ b/frontend/components/auth/login-page.tsx @@ -27,21 +27,17 @@ import {safeRedirectTarget} from "@/lib/utils" export function LoginPage() { const router = useRouter() const searchParams = useSearchParams() - const { user, setUser } = useAuth() + const { user, loading, setUser } = useAuth() const [showOTP, setShowOTP] = useState(false) /* 处理OAuth回调 */ - const [isProcessingCallback, setIsProcessingCallback] = useState(() => { - const state = searchParams.get('state') - const code = searchParams.get('code') - return !!(state && code) - }) - const [isCheckingSession, setIsCheckingSession] = useState(() => !searchParams.get('state') || !searchParams.get('code')) + const isOAuthCallback = !!(searchParams.get('state') && searchParams.get('code')) + const [isProcessingCallback, setIsProcessingCallback] = useState(isOAuthCallback) + const isCheckingSession = !isOAuthCallback && loading const [loginSuccess, setLoginSuccess] = useState(false) const redirectedRef = useRef(false) const callbackProcessedRef = useRef(false) - const wasUserPresentRef = useRef(!!user) const resolveRedirectTarget = useCallback(() => { const callbackUrl = searchParams.get('callbackUrl') @@ -66,63 +62,15 @@ export function LoginPage() { const state = searchParams.get('state') const code = searchParams.get('code') - if (state && code) { - setIsCheckingSession(false) + if ((state && code) || loading || !user) { return } - if (user) { - if (wasUserPresentRef.current) { - if (!redirectedRef.current) { - redirectedRef.current = true - router.replace(resolveRedirectTargetRef.current()) - setIsCheckingSession(false) - } - } - return + if (!redirectedRef.current) { + redirectedRef.current = true + router.replace(resolveRedirectTargetRef.current()) } - - let cancelled = false - - const checkExistingSession = async () => { - setIsCheckingSession(true) - - try { - const response = await fetch('/api/v1/oauth/user-info', { - credentials: 'include', - cache: 'no-store', - }) - - if (cancelled) return - - if (response.ok) { - const payload = await response.json() - if (payload?.data) { - setUser(payload.data) - } - if (!redirectedRef.current) { - redirectedRef.current = true - router.replace(resolveRedirectTargetRef.current()) - } - return - } - } catch (error) { - if (!cancelled) { - console.error('Session probe error:', error) - } - } finally { - if (!cancelled) { - setIsCheckingSession(false) - } - } - } - - checkExistingSession() - - return () => { - cancelled = true - } - }, [router, searchParams, setUser, user]) + }, [loading, router, searchParams, user]) /* 回调逻辑 */ useEffect(() => { diff --git a/frontend/lib/services/core/api-client.ts b/frontend/lib/services/core/api-client.ts index 8616c725..384d49d6 100644 --- a/frontend/lib/services/core/api-client.ts +++ b/frontend/lib/services/core/api-client.ts @@ -8,6 +8,7 @@ import { NotFoundError, ServerError, TimeoutError, + UnauthorizedError, ValidationError, } from './errors'; import {ApiError, ApiResponse} from './types'; @@ -80,13 +81,15 @@ apiClient.interceptors.request.use( * @param currentPath - 当前路径,用于登录成功后重定向回来 */ function initiateLogin(currentPath: string): Promise { - if (!currentPath.startsWith('/login') && !currentPath.startsWith('/callback')) { - if (typeof window !== 'undefined') { - sessionStorage.setItem('redirect_after_login', currentPath); - const loginUrl = new URL('/login', window.location.origin); - loginUrl.searchParams.set('callbackUrl', currentPath); - window.location.href = loginUrl.toString(); - } + if (currentPath.startsWith('/login') || currentPath.startsWith('/callback')) { + return Promise.reject(new UnauthorizedError()); + } + + if (typeof window !== 'undefined') { + sessionStorage.setItem('redirect_after_login', currentPath); + const loginUrl = new URL('/login', window.location.origin); + loginUrl.searchParams.set('callbackUrl', currentPath); + window.location.href = loginUrl.toString(); } return new Promise(() => { }); @@ -259,9 +262,10 @@ function createRequestMethod( const promise = apiClient[method](url, data, config); pendingRequests.set(requestKey, promise as Promise>); - promise.finally(() => { - pendingRequests.delete(requestKey); - }); + promise.then( + () => pendingRequests.delete(requestKey), + () => pendingRequests.delete(requestKey), + ); return promise; }; @@ -277,9 +281,10 @@ function createRequestMethod( const promise = apiClient[method](url, config); pendingRequests.set(requestKey, promise as Promise>); - promise.finally(() => { - pendingRequests.delete(requestKey); - }); + promise.then( + () => pendingRequests.delete(requestKey), + () => pendingRequests.delete(requestKey), + ); return promise; }; diff --git a/frontend/proxy.ts b/frontend/proxy.ts index f692e4cd..0518b953 100644 --- a/frontend/proxy.ts +++ b/frontend/proxy.ts @@ -103,7 +103,7 @@ export function proxy(request: NextRequest) { } /* 页面请求:公共路由放行 */ - const publicRoutes = ['/', '/login', '/register', '/callback', '/privacy', '/terms'] + const publicRoutes = ['/', '/login', '/register', '/callback', '/privacy', '/terms', '/icon'] const publicPrefixes = ['/docs/', '/epay/'] if (publicRoutes.includes(pathname) || publicPrefixes.some(p => pathname.startsWith(p))) {