@@ -22,6 +22,8 @@ const requestBodySchema = z.object({
2222 selection : selectionSchema ,
2323} ) ;
2424
25+ type AdminVerificationStatus = "authorized" | "unauthorized" | "forbidden" | "error" ;
26+
2527const decodeTokenUserId = ( accessToken : string ) : string | null => {
2628 try {
2729 const payloadSegment = accessToken . split ( "." ) [ 1 ] ;
@@ -43,7 +45,57 @@ const decodeTokenUserId = (accessToken: string): string | null => {
4345 }
4446} ;
4547
46- const verifyAdminRole = async ( accessToken : string ) : Promise < boolean > => {
48+ const parseRoleValue = ( rawValue : unknown ) : string => {
49+ if ( typeof rawValue !== "string" ) {
50+ return "" ;
51+ }
52+
53+ return rawValue . trim ( ) . toUpperCase ( ) ;
54+ } ;
55+
56+ const hasAdminRole = ( rawValue : unknown ) : boolean => {
57+ const normalizedRole = parseRoleValue ( rawValue ) ;
58+ return normalizedRole === UserRole . ADMIN || normalizedRole === "ROLE_ADMIN" ;
59+ } ;
60+
61+ const getRoleFromMyResponse = ( data : unknown ) : unknown => {
62+ if ( ! data || typeof data !== "object" ) {
63+ return "" ;
64+ }
65+
66+ const root = data as Record < string , unknown > ;
67+ if ( typeof root . role === "string" ) {
68+ return root . role ;
69+ }
70+
71+ const nestedData = root . data ;
72+ if ( nestedData && typeof nestedData === "object" ) {
73+ const nested = nestedData as Record < string , unknown > ;
74+ if ( typeof nested . role === "string" ) {
75+ return nested . role ;
76+ }
77+ }
78+
79+ return "" ;
80+ } ;
81+
82+ const decodeTokenRole = ( accessToken : string ) : unknown => {
83+ try {
84+ const payloadSegment = accessToken . split ( "." ) [ 1 ] ;
85+ if ( ! payloadSegment ) {
86+ return "" ;
87+ }
88+
89+ const normalized = payloadSegment . replace ( / - / g, "+" ) . replace ( / _ / g, "/" ) ;
90+ const padded = normalized . padEnd ( Math . ceil ( normalized . length / 4 ) * 4 , "=" ) ;
91+ const payload = JSON . parse ( Buffer . from ( padded , "base64" ) . toString ( "utf8" ) ) as { role ?: string } ;
92+ return payload . role ?? "" ;
93+ } catch {
94+ return "" ;
95+ }
96+ } ;
97+
98+ const verifyAdminRole = async ( accessToken : string ) : Promise < AdminVerificationStatus > => {
4799 const apiServerUrl = process . env . NEXT_PUBLIC_API_SERVER_URL ?. trim ( ) ;
48100 if ( ! apiServerUrl ) {
49101 throw new Error ( "NEXT_PUBLIC_API_SERVER_URL is not configured." ) ;
@@ -57,14 +109,28 @@ const verifyAdminRole = async (accessToken: string): Promise<boolean> => {
57109 cache : "no-store" ,
58110 } ) ;
59111
112+ if ( response . status === 401 ) {
113+ return "unauthorized" ;
114+ }
115+
60116 if ( ! response . ok ) {
61- return false ;
117+ return "forbidden" ;
118+ }
119+
120+ const data = ( await response . json ( ) . catch ( ( ) => null ) ) as unknown ;
121+ const responseRole = getRoleFromMyResponse ( data ) ;
122+ if ( hasAdminRole ( responseRole ) ) {
123+ return "authorized" ;
124+ }
125+
126+ // Fallback: /my 인증(200)은 통과했는데 응답 role 스키마가 다른 경우를 대비.
127+ if ( hasAdminRole ( decodeTokenRole ( accessToken ) ) ) {
128+ return "authorized" ;
62129 }
63130
64- const data = ( await response . json ( ) ) as { role ?: string } ;
65- return data . role === UserRole . ADMIN ;
131+ return "forbidden" ;
66132 } catch {
67- return false ;
133+ return "error" ;
68134 }
69135} ;
70136
@@ -96,14 +162,25 @@ async function POST(request: NextRequest) {
96162 return NextResponse . json ( { message : "요청 본문을 읽을 수 없습니다." } , { status : 400 } ) ;
97163 }
98164
99- let isAdmin = false ;
165+ let verificationStatus : AdminVerificationStatus = "forbidden" ;
100166 try {
101- isAdmin = await verifyAdminRole ( accessToken ) ;
167+ verificationStatus = await verifyAdminRole ( accessToken ) ;
102168 } catch {
103169 return NextResponse . json ( { message : "서버 인증 설정 오류입니다." } , { status : 500 } ) ;
104170 }
105171
106- if ( ! isAdmin ) {
172+ if ( verificationStatus === "unauthorized" ) {
173+ return NextResponse . json ( { message : "로그인 세션이 만료되었습니다. 다시 로그인해주세요." } , { status : 401 } ) ;
174+ }
175+
176+ if ( verificationStatus === "error" ) {
177+ return NextResponse . json (
178+ { message : "관리자 권한 확인에 실패했습니다. 잠시 후 다시 시도해주세요." } ,
179+ { status : 503 } ,
180+ ) ;
181+ }
182+
183+ if ( verificationStatus !== "authorized" ) {
107184 return NextResponse . json ( { message : "관리자 권한이 필요합니다." } , { status : 403 } ) ;
108185 }
109186
0 commit comments