fix:Redirect loop in console rustfs/rustfs#1941

This commit is contained in:
马登山
2026-02-26 15:39:22 +08:00
parent 9e0e8e7082
commit 839cc4f39c
7 changed files with 49 additions and 35 deletions
+2 -2
View File
@@ -19,7 +19,7 @@ export function DashboardAuthGuard({ children }: { children: React.ReactNode })
useEffect(() => {
if (isReady && !isAuthenticated) {
router.replace("/auth/login?unauthorized=true")
router.replace("/auth/login/?unauthorized=true")
}
}, [isAuthenticated, isReady, router])
@@ -34,7 +34,7 @@ export function DashboardAuthGuard({ children }: { children: React.ReactNode })
if (!isReady || !isAuthenticated || isAdmin) return
if (isLoading || !hasFetchedPolicy) return
if (!canAccessPath(pathname)) {
router.replace("/403")
router.replace("/403/")
}
}, [isReady, isAuthenticated, isAdmin, isLoading, userPolicy, hasFetchedPolicy, canAccessPath, pathname, router])
+16 -4
View File
@@ -1,6 +1,7 @@
"use client"
import { createContext, useContext, useEffect, useState } from "react"
import { usePathname, useRouter } from "next/navigation"
import { ApiClient } from "@/lib/api-client"
import { AwsClient } from "@/lib/aws4fetch"
import { ApiErrorHandler } from "@/lib/api-error-handler"
@@ -19,6 +20,8 @@ export function ApiProvider({ children }: { children: React.ReactNode }) {
const { credentials, isAuthenticated, logout } = useAuth()
const [apiClient, setApiClient] = useState<ApiClient | null>(null)
const [isReady, setIsReady] = useState(false)
const router = useRouter()
const pathname = usePathname()
useEffect(() => {
if (!isAuthenticated || !credentials?.AccessKeyId) {
@@ -51,10 +54,19 @@ export function ApiProvider({ children }: { children: React.ReactNode }) {
const errorHandler = new ApiErrorHandler({
onUnauthorized: async () => {
logout()
window.location.href = getLoginRoute()
router.replace("/auth/login/")
},
onForbidden: async () => {
window.location.href = buildRoute("/403")
onForbidden: async (url) => {
// If the forbidden error happens on the accountinfo endpoint, it means the session is invalid
// or the user has no permissions at all. In this case, we should log out.
if (url?.includes("/is-admin") || url?.includes("/accountinfo") || url?.includes("/version")) {
logout()
router.replace("/auth/login/")
return
}
if (pathname === "/403/") return
router.replace("/403/")
},
})
@@ -71,7 +83,7 @@ export function ApiProvider({ children }: { children: React.ReactNode }) {
return () => {
cancelled = true
}
}, [isAuthenticated, credentials?.AccessKeyId, credentials?.SecretAccessKey, credentials?.SessionToken, logout])
}, [isAuthenticated, credentials?.AccessKeyId, credentials?.SecretAccessKey, credentials?.SessionToken, logout, pathname, router])
return <ApiContext.Provider value={{ api: apiClient, isReady }}>{children}</ApiContext.Provider>
}
+7 -7
View File
@@ -32,7 +32,7 @@ interface AuthContextValue {
const AuthContext = createContext<AuthContextValue | null>(null)
function isValidCredentials(credentials: Credentials): boolean {
function isValidCredentials(credentials: Credentials | undefined): boolean {
if (
!credentials?.AccessKeyId ||
!credentials?.SecretAccessKey ||
@@ -46,8 +46,8 @@ function isValidCredentials(credentials: Credentials): boolean {
}
export function AuthProvider({ children }: { children: ReactNode }) {
const [store, setStore] = useLocalStorage<Credentials>("auth.credentials", {})
const [isAdminStore, setIsAdminStore] = useLocalStorage<boolean>("auth.isAdmin", false)
const [store, setStore] = useLocalStorage<Credentials | undefined>("auth.credentials", undefined)
const [isAdminStore, setIsAdminStore] = useLocalStorage<boolean | undefined>("auth.isAdmin", undefined)
const [permanentStore, setPermanentStore] = useLocalStorage<Credentials | undefined>("auth.permanent", undefined)
const setCredentials = useCallback(
@@ -77,7 +77,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
)
const getIsAdmin = useCallback(() => {
return isAdminStore
return !!isAdminStore
}, [isAdminStore])
const login = useCallback(
@@ -125,9 +125,9 @@ export function AuthProvider({ children }: { children: ReactNode }) {
)
const logout = useCallback(() => {
setStore({})
setStore(undefined)
setPermanentStore(undefined)
setIsAdminStore(false)
setIsAdminStore(undefined)
}, [setStore, setPermanentStore, setIsAdminStore])
const logoutAndRedirect = useCallback(() => {
@@ -149,7 +149,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
credentials,
permanentCredentials: permanentStore,
isAuthenticated,
isAdmin: isAdminStore,
isAdmin: !!isAdminStore,
}),
[
login,
+5 -3
View File
@@ -1,6 +1,7 @@
"use client"
import { createContext, useContext, useEffect, useState } from "react"
import { useRouter } from "next/navigation"
import { S3Client } from "@aws-sdk/client-s3"
import { useAuth } from "@/contexts/auth-context"
import { configManager } from "@/lib/config"
@@ -40,6 +41,7 @@ export function S3Provider({ children }: { children: React.ReactNode }) {
const { credentials, isAuthenticated, logout } = useAuth()
const [s3Client, setS3Client] = useState<S3Client | null>(null)
const [isReady, setIsReady] = useState(false)
const router = useRouter()
useEffect(() => {
if (!isAuthenticated || !credentials?.AccessKeyId) {
@@ -123,7 +125,7 @@ export function S3Provider({ children }: { children: React.ReactNode }) {
}
if (err?.$metadata?.httpStatusCode === 401) {
logout()
window.location.href = getLoginRoute()
router.replace("/auth/login/")
return { response: { statusCode: 401, headers: {} } }
}
if (err?.$metadata?.httpStatusCode === 403) {
@@ -132,7 +134,7 @@ export function S3Provider({ children }: { children: React.ReactNode }) {
const isInvalidAccessKey = codeText === "invalidaccesskeyid"
if (isUnauthorizedAccess || isInvalidAccessKey) {
logout()
window.location.href = getLoginRoute()
router.replace("/auth/login/")
return { response: { statusCode: 401, headers: {} } }
}
}
@@ -153,7 +155,7 @@ export function S3Provider({ children }: { children: React.ReactNode }) {
return () => {
cancelled = true
}
}, [isAuthenticated, credentials?.AccessKeyId, credentials?.SecretAccessKey, credentials?.SessionToken, logout])
}, [isAuthenticated, credentials?.AccessKeyId, credentials?.SecretAccessKey, credentials?.SessionToken, logout, router])
return <S3Context.Provider value={{ client: s3Client, isReady }}>{children}</S3Context.Provider>
}
+4 -4
View File
@@ -73,7 +73,7 @@ export class ApiClient {
if (response.status === 401) {
if (this.errorHandler) {
await this.errorHandler.handle401()
await this.errorHandler.handle401(url)
}
return
}
@@ -102,14 +102,14 @@ export class ApiClient {
if (this.errorHandler) {
if (isUnauthorizedAccess || isInvalidAccessKey) {
await this.errorHandler.handle401()
await this.errorHandler.handle401(url)
} else {
await this.errorHandler.handle403()
await this.errorHandler.handle403(url)
}
}
} catch {
if (this.errorHandler) {
await this.errorHandler.handle403()
await this.errorHandler.handle403(url)
}
}
return
+10 -10
View File
@@ -15,37 +15,37 @@ export class ApiErrorHandler implements IApiErrorHandler {
this.onServerError = callbacks?.onServerError
}
async handle401(): Promise<void> {
async handle401(url?: string): Promise<void> {
if (this.onUnauthorized) {
await this.onUnauthorized()
await this.onUnauthorized(url)
}
}
async handle403(): Promise<void> {
async handle403(url?: string): Promise<void> {
if (this.onForbidden) {
await this.onForbidden()
await this.onForbidden(url)
}
}
async handleServerError(): Promise<void> {
async handleServerError(url?: string): Promise<void> {
if (this.onServerError) {
await this.onServerError()
await this.onServerError(url)
}
}
async handleByStatus(status: number): Promise<void> {
async handleByStatus(status: number, url?: string): Promise<void> {
switch (status) {
case 401:
await this.handle401()
await this.handle401(url)
break
case 403:
await this.handle403()
await this.handle403(url)
break
case 500:
case 502:
case 503:
case 504:
await this.handleServerError()
await this.handleServerError(url)
break
}
}
+5 -5
View File
@@ -1,8 +1,8 @@
export type ApiErrorHandlerCallback = () => void | Promise<void>
export type ApiErrorHandlerCallback = (url?: string) => void | Promise<void>
export interface IApiErrorHandler {
handle401(): Promise<void>
handle403(): Promise<void>
handleServerError(): Promise<void>
handleByStatus(status: number): Promise<void>
handle401(url?: string): Promise<void>
handle403(url?: string): Promise<void>
handleServerError(url?: string): Promise<void>
handleByStatus(status: number, url?: string): Promise<void>
}