From 31fcaa2c1c60005815bc6318c86e17add614c000 Mon Sep 17 00:00:00 2001 From: hided62 Date: Sat, 8 Aug 2026 12:51:45 +0000 Subject: [PATCH] fix(gateway): accept Kakao already-registered response --- app/gateway-api/src/auth/kakaoClient.ts | 14 ++++++- app/gateway-api/src/router.ts | 2 +- app/gateway-api/test/authFlow.test.ts | 4 +- app/gateway-api/test/kakaoClient.test.ts | 47 ++++++++++++++++++++++++ 4 files changed, 63 insertions(+), 4 deletions(-) create mode 100644 app/gateway-api/test/kakaoClient.test.ts diff --git a/app/gateway-api/src/auth/kakaoClient.ts b/app/gateway-api/src/auth/kakaoClient.ts index 0c448a7d..171071d0 100644 --- a/app/gateway-api/src/auth/kakaoClient.ts +++ b/app/gateway-api/src/auth/kakaoClient.ts @@ -25,6 +25,11 @@ export interface KakaoUserInfo { kakaoAccount: KakaoAccountInfo; } +export interface KakaoSignupResult { + id?: string; + alreadyRegistered: boolean; +} + const buildForm = (params: Record): URLSearchParams => { const form = new URLSearchParams(); for (const [key, value] of Object.entries(params)) { @@ -106,19 +111,24 @@ export class KakaoOAuthClient { return parseToken(payload); } - async signup(accessToken: string): Promise<{ id?: string; msg?: string }> { + async signup(accessToken: string): Promise { const response = await fetch(new URL('/v1/user/signup', this.apiHost), { headers: { Authorization: `Bearer ${accessToken}`, }, }); const payload = (await response.json()) as Record; + if (!response.ok && payload.code === -102 && payload.msg === 'already registered') { + return { + alreadyRegistered: true, + }; + } if (!response.ok) { throw new Error(`Kakao signup error: ${JSON.stringify(payload)}`); } return { id: payload.id ? String(payload.id) : undefined, - msg: payload.msg ? String(payload.msg) : undefined, + alreadyRegistered: false, }; } diff --git a/app/gateway-api/src/router.ts b/app/gateway-api/src/router.ts index 281ce283..2aa18b19 100644 --- a/app/gateway-api/src/router.ts +++ b/app/gateway-api/src/router.ts @@ -253,7 +253,7 @@ export const appRouter = router({ const tokenIssuedAt = new Date(); const signupResult = await ctx.kakaoClient.signup(token.accessToken); - const alreadyRegisteredWithKakao = !signupResult.id && signupResult.msg === 'already registered'; + const alreadyRegisteredWithKakao = !signupResult.id && signupResult.alreadyRegistered; if (!signupResult.id && !alreadyRegisteredWithKakao) { throw new TRPCError({ code: 'BAD_REQUEST', diff --git a/app/gateway-api/test/authFlow.test.ts b/app/gateway-api/test/authFlow.test.ts index 0bcd70dd..b0cbc2aa 100644 --- a/app/gateway-api/test/authFlow.test.ts +++ b/app/gateway-api/test/authFlow.test.ts @@ -71,7 +71,9 @@ const buildCaller = ( }; }, signup: async () => - options.kakaoSignupAlreadyRegistered ? { msg: 'already registered' as const } : { id: kakaoProfile.id }, + options.kakaoSignupAlreadyRegistered + ? { alreadyRegistered: true } + : { id: kakaoProfile.id, alreadyRegistered: false }, getMe: async () => ({ id: kakaoProfile.id, kakaoAccount: { diff --git a/app/gateway-api/test/kakaoClient.test.ts b/app/gateway-api/test/kakaoClient.test.ts new file mode 100644 index 00000000..12b9b9d0 --- /dev/null +++ b/app/gateway-api/test/kakaoClient.test.ts @@ -0,0 +1,47 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; + +import { KakaoOAuthClient } from '../src/auth/kakaoClient.js'; + +const createClient = (): KakaoOAuthClient => + new KakaoOAuthClient({ + restKey: 'rest-key', + redirectUri: 'https://gateway.example.test/oauth/callback', + apiHost: 'https://kapi.example.test', + }); + +describe('Kakao OAuth HTTP transport', () => { + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('normalizes Kakao -102 already registered errors into an account recovery result', async () => { + const fetchMock = vi.spyOn(globalThis, 'fetch').mockResolvedValueOnce( + new Response(JSON.stringify({ msg: 'already registered', code: -102 }), { + status: 400, + headers: { 'Content-Type': 'application/json' }, + }) + ); + + await expect(createClient().signup('access-token')).resolves.toEqual({ + alreadyRegistered: true, + }); + expect(fetchMock).toHaveBeenCalledWith(new URL('https://kapi.example.test/v1/user/signup'), { + headers: { + Authorization: 'Bearer access-token', + }, + }); + }); + + it('continues to reject unrelated Kakao signup errors', async () => { + vi.spyOn(globalThis, 'fetch').mockResolvedValueOnce( + new Response(JSON.stringify({ msg: 'invalid request', code: -201 }), { + status: 400, + headers: { 'Content-Type': 'application/json' }, + }) + ); + + await expect(createClient().signup('access-token')).rejects.toThrow( + 'Kakao signup error: {"msg":"invalid request","code":-201}' + ); + }); +});