From 2684db6e7ecbb9b36fc389f45d587d4a35e0b693 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A9=AC=E7=99=BB=E5=B1=B1?= Date: Mon, 2 Feb 2026 15:43:58 +0800 Subject: [PATCH] fix : 403 check Unauthorized --- .prettierrc | 2 +- contexts/s3-context.tsx | 69 ++++++++++++++++++++++++---------------- lib/api-client.ts | 58 ++++++++++++++++++++++----------- lib/api-error-handler.ts | 5 +-- 4 files changed, 84 insertions(+), 50 deletions(-) diff --git a/.prettierrc b/.prettierrc index 44fd7e5..924753f 100644 --- a/.prettierrc +++ b/.prettierrc @@ -3,7 +3,7 @@ "singleQuote": false, "jsxSingleQuote": false, "trailingComma": "all", - "printWidth": 90, + "printWidth": 120, "tabWidth": 2, "arrowParens": "always", "bracketSpacing": true, diff --git a/contexts/s3-context.tsx b/contexts/s3-context.tsx index 135c2ed..a42d8f1 100644 --- a/contexts/s3-context.tsx +++ b/contexts/s3-context.tsx @@ -17,7 +17,10 @@ interface S3ContextValue { isReady: boolean } -const S3Context = createContext({ client: null, isReady: false }) +const S3Context = createContext({ + 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 */ diff --git a/lib/api-client.ts b/lib/api-client.ts index 3b0d68a..50e9a5c 100644 --- a/lib/api-client.ts +++ b/lib/api-client.ts @@ -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 }) + const response = await this.$api.fetch( + url, + options as RequestInit & { body?: BodyInit | null; aws?: Record }, + ) 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 + 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") diff --git a/lib/api-error-handler.ts b/lib/api-error-handler.ts index f49e1da..a7a1753 100644 --- a/lib/api-error-handler.ts +++ b/lib/api-error-handler.ts @@ -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