fix : 403 check Unauthorized

This commit is contained in:
马登山
2026-02-02 15:43:58 +08:00
parent eced64dbf8
commit 2684db6e7e
4 changed files with 84 additions and 50 deletions
+1 -1
View File
@@ -3,7 +3,7 @@
"singleQuote": false,
"jsxSingleQuote": false,
"trailingComma": "all",
"printWidth": 90,
"printWidth": 120,
"tabWidth": 2,
"arrowParens": "always",
"bracketSpacing": true,
+42 -27
View File
@@ -17,7 +17,10 @@ interface S3ContextValue {
isReady: boolean
}
const S3Context = createContext<S3ContextValue>({ client: null, isReady: false })
const S3Context = createContext<S3ContextValue>({
client: null,
isReady: false,
})
export function S3Provider({ children }: { children: React.ReactNode }) {
const { credentials, isAuthenticated, logout } = useAuth()
@@ -54,42 +57,54 @@ export function S3Provider({ children }: { children: React.ReactNode }) {
/* eslint-disable @typescript-eslint/no-explicit-any -- AWS SDK middleware types are complex */
client.middlewareStack.add(
((next: any) =>
async (args: any) => {
try {
const response = (await next(args)) as S3Response
((next: any) => async (args: any) => {
try {
const response = (await next(args)) as S3Response
if (response.response?.body && typeof response.response.body === "string") {
const body = response.response.body.trim()
if (body.match(/^<\?xml[^>]*\?><[^>]*><\/[^>]*>$/)) {
const tagName = body.match(/<([^>]*)><\/\1>/)?.[1]
if (tagName) {
const propertyName = tagName.replace(/(?:^|_)([a-z])/g, (_, letter: string) =>
letter.toUpperCase()
)
return {
response: response.response,
[propertyName]: null,
}
if (response.response?.body && typeof response.response.body === "string") {
const body = response.response.body.trim()
if (body.match(/^<\?xml[^>]*\?><[^>]*><\/[^>]*>$/)) {
const tagName = body.match(/<([^>]*)><\/\1>/)?.[1]
if (tagName) {
const propertyName = tagName.replace(/(?:^|_)([a-z])/g, (_, letter: string) => letter.toUpperCase())
return {
response: response.response,
[propertyName]: null,
}
}
}
}
return response
} catch (error: unknown) {
const err = error as { $metadata?: { httpStatusCode?: number }; Code?: string }
if (err?.$metadata?.httpStatusCode === 401) {
return response
} catch (error: unknown) {
const err = error as {
$metadata?: { httpStatusCode?: number }
Code?: string
name?: string
message?: string
}
if (err?.$metadata?.httpStatusCode === 401) {
logout()
window.location.href = getLoginRoute()
return { response: { statusCode: 401, headers: {} } }
}
if (err?.$metadata?.httpStatusCode === 403) {
const codeText = (err?.Code || err?.name || "").toLowerCase()
const isUnauthorizedAccess = codeText === "unauthorizedaccess"
const isInvalidAccessKey = codeText === "invalidaccesskeyid"
if (isUnauthorizedAccess || isInvalidAccessKey) {
logout()
window.location.href = getLoginRoute()
return { response: { statusCode: 401, headers: {} } }
}
if (err?.Code) {
throw new Error(err.Code)
}
throw error
}
}) as any,
{ step: "deserialize", name: "handleXmlResponse" }
if (err?.Code) {
throw new Error(err.Code)
}
throw error
}
}) as any,
{ step: "deserialize", name: "handleXmlResponse" },
)
/* eslint-enable @typescript-eslint/no-explicit-any */
+40 -18
View File
@@ -32,11 +32,7 @@ export class ApiClient {
this.errorHandler = handler
}
async request(
url: string,
options: RequestOptions = {},
parseJson: boolean = true
) {
async request(url: string, options: RequestOptions = {}, parseJson: boolean = true) {
url = this.config?.baseUrl ? joinURL(this.config?.baseUrl, url) : url
options.headers = { ...this.config?.headers, ...options.headers }
if (
@@ -59,7 +55,10 @@ export class ApiClient {
logger.log("[request] url:", url)
logger.log("[request] options:", options)
const response = await this.$api.fetch(url, options as RequestInit & { body?: BodyInit | null; aws?: Record<string, unknown> })
const response = await this.$api.fetch(
url,
options as RequestInit & { body?: BodyInit | null; aws?: Record<string, unknown> },
)
logger.log("[request] response:", response)
@@ -70,8 +69,39 @@ export class ApiClient {
return
}
if (response.status === 403) {
if (this.errorHandler) {
await this.errorHandler.handle403()
try {
const cloned = response.clone()
let codeText = ""
try {
const data = (await cloned.json()) as Record<string, unknown>
let code: string | undefined = (data?.code as string | undefined) || (data?.Code as string | undefined)
if (data && typeof data === "object" && "error" in data) {
const errObj = (data as { error?: unknown }).error
if (errObj && typeof errObj === "object") {
const e = errObj as { code?: unknown }
if (typeof e.code === "string") code = code ?? e.code
}
}
codeText = typeof code === "string" ? code : ""
} catch {
codeText = ""
}
const normalizedCode = codeText.toLowerCase()
const isUnauthorizedAccess = normalizedCode === "unauthorizedaccess"
const isInvalidAccessKey = normalizedCode === "invalidaccesskeyid"
if (this.errorHandler) {
if (isUnauthorizedAccess || isInvalidAccessKey) {
await this.errorHandler.handle401()
} else {
await this.errorHandler.handle403()
}
}
} catch {
if (this.errorHandler) {
await this.errorHandler.handle403()
}
}
return
}
@@ -81,11 +111,7 @@ export class ApiClient {
throw new Error(errorMsg)
}
if (
response.status === 204 ||
response.headers.get("content-length") === "0" ||
!response.body
) {
if (response.status === 204 || response.headers.get("content-length") === "0" || !response.body) {
return null
}
@@ -101,11 +127,7 @@ export class ApiClient {
}
async *streamRequest(url: string, options: RequestOptions = {}) {
const response = (await this.request(
url,
{ method: "GET", ...options },
false
)) as Response | null
const response = (await this.request(url, { method: "GET", ...options }, false)) as Response | null
if (!response?.body) {
throw new Error("No response body")
+1 -4
View File
@@ -1,7 +1,4 @@
import type {
ApiErrorHandlerCallback,
IApiErrorHandler,
} from "@/types/api"
import type { ApiErrorHandlerCallback, IApiErrorHandler } from "@/types/api"
export class ApiErrorHandler implements IApiErrorHandler {
private onUnauthorized?: ApiErrorHandlerCallback