[FEAT] OAuth 로그인 인가 코드 전송 후 회원가입/로그인 성공 분기 작업#177
Conversation
- redirect , signup
- 임시 확인 용도
- OAuth 일원화
- 로그인 성공 -> accessToken SessionStorage 관리 - 회원 가입 필요 -> temporary Token SessionStorage 관리
- Login , Register 부분 우선 추가
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. WalkthroughRefactors authentication and signup to use a shared Axios instance ( Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant KakaoCallback
participant apiInstance
participant Backend
participant SessionStorage
participant LocalStorage
User->>KakaoCallback: OAuth callback (with code)
KakaoCallback->>apiInstance: POST /auth/kakao/login (code)
apiInstance->>Backend: forward request
alt LOGIN_SUCCESS
Backend-->>apiInstance: { status: "LOGIN_SUCCESS", accessToken }
apiInstance-->>KakaoCallback: response
KakaoCallback->>SessionStorage: store accessToken
KakaoCallback->>User: navigate to "/"
else REGISTRATION_REQUIRED
Backend-->>apiInstance: { status: "REGISTRATION_REQUIRED", temporaryToken }
apiInstance-->>KakaoCallback: response
KakaoCallback->>LocalStorage: store temporaryToken
KakaoCallback->>User: navigate to "/signup"
else Error
Backend-->>apiInstance: ErrorResponse
apiInstance-->>KakaoCallback: AxiosError
KakaoCallback->>User: throw/display Error with server message
end
sequenceDiagram
actor User
participant SignupForm
participant LocalStorage
participant apiInstance
participant Backend
participant SessionStorage
User->>SignupForm: submit form
SignupForm->>LocalStorage: read temporaryToken
alt token missing
LocalStorage-->>SignupForm: null
SignupForm->>User: show error toast (abort)
else token present
LocalStorage-->>SignupForm: temporaryToken
SignupForm->>apiInstance: POST /auth/register (formData + Bearer tempToken)
apiInstance->>Backend: forward request
alt REGISTER_SUCCESS
Backend-->>apiInstance: { status: "REGISTER_SUCCESS", accessToken, refreshToken }
apiInstance-->>SignupForm: response
SignupForm->>SessionStorage: store accessToken
SignupForm->>User: toast -> onAutoClose -> navigate "/"
else Error
Backend-->>apiInstance: ErrorResponse
apiInstance-->>SignupForm: AxiosError
SignupForm->>User: show mapped error toast
end
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Summary of ChangesHello @aaaaaattt, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! 이 PR은 OAuth 로그인(특히 카카오) 기능을 통합하고, 새로운 사용자(관리자)를 위한 회원가입 워크플로우를 구축하여 애플리케이션의 인증 시스템을 확장합니다. 사용자 경험을 개선하고, API 통신을 표준화하며, 타입 안전성을 높이는 데 중점을 두었습니다. Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
| } catch (e) { | ||
| const error = e as AxiosError<ErrorResponse>; | ||
| return new Error(error.response?.data.message); | ||
| } |
There was a problem hiding this comment.
catch 블록의 에러 처리가 올바르지 않습니다. useEffect 내부의 비동기 함수에서 new Error()를 반환해도 아무런 효과가 없으며, 에러 발생 시 사용자는 로딩 화면에 계속 머물게 됩니다. 에러를 사용자에게 알리고 로그인 페이지 등으로 리디렉션해야 합니다.
} catch (e) {
const error = e as AxiosError<ErrorResponse>;
// TODO: 사용자에게 에러 메시지 표시 (e.g., toast)
console.error(error.response?.data.message ?? '로그인 중 오류가 발생했습니다.');
navigate('/login');
}
| case 'LOGIN_SUCCESS': | ||
| localStorage.setItem('accessToken', res.data.accessToken); | ||
| navigate('/'); | ||
| break; |
There was a problem hiding this comment.
LoginSuccessResponse에 refreshToken이 포함되어 있지만, 저장되지 않고 있습니다. 애플리케이션이 리프레시 토큰을 사용하여 세션을 유지하도록 설계되었다면, refreshToken도 localStorage나 안전한 쿠키 등에 저장해야 합니다.
| case 'LOGIN_SUCCESS': | |
| localStorage.setItem('accessToken', res.data.accessToken); | |
| navigate('/'); | |
| break; | |
| case 'LOGIN_SUCCESS': | |
| localStorage.setItem('accessToken', res.data.accessToken); | |
| localStorage.setItem('refreshToken', res.data.refreshToken); | |
| navigate('/'); | |
| break; |
|
|
||
| switch (res.data.status) { | ||
| case 'LOGIN_SUCCESS': | ||
| localStorage.setItem('accessToken', res.data.accessToken); |
| }; | ||
|
|
||
| export const apiInstance = initInstance({ | ||
| baseURL: import.meta.env.VITE_API_BASE_URL_LOCAL, |
src/pages/admin/Signup/api/signup.ts
Outdated
| headers: { Authorization: `Bearer ${tempToken}` }, | ||
| }, | ||
| ); | ||
| sessionStorage.setItem('accessToken', response.data.accessToken); |
There was a problem hiding this comment.
| toast.success('회원가입 완료!', { | ||
| style: { backgroundColor: theme.colors.primary, color: 'white' }, | ||
| duration: 1000, | ||
| }); | ||
| setTimeout(() => { | ||
| navigate(`/`); | ||
| }, 1000); |
There was a problem hiding this comment.
토스트 메시지를 보여준 후 setTimeout을 사용해 페이지를 이동시키는 방식은 불안정할 수 있습니다. 예를 들어 토스트 표시 시간이 변경되면 setTimeout의 시간도 함께 수정해야 합니다. 사용하시는 sonner 라이브러리는 토스트가 사라질 때 호출되는 onAutoClose나 onDismiss 같은 콜백 함수를 제공합니다. 이를 사용하면 더 견고한 코드를 작성할 수 있습니다.
toast.success('회원가입 완료!', {
style: { backgroundColor: theme.colors.primary, color: 'white' },
duration: 1000,
onAutoClose: () => navigate('/'),
});
| } catch (e: any) { | ||
| toast.error(e.message, { | ||
| duration: 1000, | ||
| style: { | ||
| backgroundColor: 'white', | ||
| color: theme.colors.error, | ||
| }, | ||
| }); | ||
| } |
There was a problem hiding this comment.
catch 블록에서 에러 타입을 any로 지정하는 것은 타입스크립트에서 권장되지 않습니다. unknown으로 타입을 지정한 후, 타입 가드를 통해 타입을 좁혀서 사용하는 것이 더 안전합니다. postSignupForm 함수는 Error 객체를 throw하므로, instanceof Error를 사용하여 확인할 수 있습니다.
} catch (e: unknown) {
if (e instanceof Error) {
toast.error(e.message, {
duration: 1000,
style: {
backgroundColor: 'white',
color: theme.colors.error,
},
});
}
}
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/pages/admin/Signup/components/SignupForm/index.tsx (1)
42-50: Replaceanywith proper error typing.Using
anydefeats TypeScript's type safety and causes the pipeline to fail. SincepostSignupFormthrowsErrorobjects, type the catch parameter asunknownand narrow toError.Apply this diff:
- } catch (e: any) { - toast.error(e.message, { + } catch (e: unknown) { + const errorMessage = e instanceof Error ? e.message : '알 수 없는 오류가 발생했습니다.'; + toast.error(errorMessage, { duration: 1000, style: { backgroundColor: 'white',
♻️ Duplicate comments (4)
src/pages/admin/Login/api/initInstance.ts (1)
19-19: Hardcoded baseURL limits environment portability.Using
VITE_API_BASE_URL_LOCALprevents deployment to staging or production. Consider using a single environment variable (e.g.,VITE_API_BASE_URL) and defining it per environment in.env.development,.env.production, etc.src/pages/admin/Login/KakaoCallback.tsx (2)
40-40: MissingrefreshTokenstorage prevents session renewal.The server returns
refreshTokenin bothLOGIN_SUCCESSand during signup (RegisterSuccessResponse), but onlyaccessTokenis stored. Without the refresh token, the app cannot renew expired access tokens, forcing users to re-authenticate.Store the
refreshTokenalongsideaccessToken:case 'LOGIN_SUCCESS': localStorage.setItem('accessToken', res.data.accessToken); + localStorage.setItem('refreshToken', res.data.refreshToken); navigate('/');And in
src/pages/admin/Signup/api/signup.ts:sessionStorage.setItem('accessToken', response.data.accessToken); + sessionStorage.setItem('refreshToken', response.data.refreshToken);Also applies to: 44-44
48-51: Critical: Error handling returns Error instead of handling it.Returning
new Error(...)in auseEffectcallback has no effect. The user remains stuck on the loading spinner indefinitely when login fails.Handle the error by showing user feedback and navigating away:
} catch (e) { const error = e as AxiosError<ErrorResponse>; - return new Error(error.response?.data.message); + // TODO: Display error message via toast + console.error(error.response?.data.message ?? '로그인 중 오류가 발생했습니다.'); + navigate('/login'); }src/pages/admin/Signup/api/signup.ts (1)
21-21: MissingrefreshTokenstorage prevents session renewal.The
RegisterSuccessResponseincludes arefreshToken, but only theaccessTokenis stored. This prevents the application from refreshing expired tokens, forcing users to re-authenticate.Apply this diff:
sessionStorage.setItem('accessToken', response.data.accessToken); + sessionStorage.setItem('refreshToken', response.data.refreshToken);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
src/pages/admin/Login/KakaoCallback.tsx(1 hunks)src/pages/admin/Login/api/initInstance.ts(1 hunks)src/pages/admin/Signup/api/signup.ts(1 hunks)src/pages/admin/Signup/components/SignupForm/index.tsx(1 hunks)src/pages/admin/Signup/type/error.ts(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
src/pages/admin/Signup/components/SignupForm/index.tsx (1)
src/pages/admin/Signup/api/signup.ts (1)
postSignupForm(12-39)
src/pages/admin/Signup/api/signup.ts (3)
src/pages/admin/Signup/type/signup.ts (1)
SignupFormInputs(1-7)src/pages/admin/Login/api/initInstance.ts (1)
apiInstance(18-20)src/pages/admin/Signup/type/error.ts (1)
ErrorResponse(1-5)
src/pages/admin/Login/KakaoCallback.tsx (2)
src/pages/admin/Login/api/initInstance.ts (1)
apiInstance(18-20)src/pages/admin/Signup/type/error.ts (1)
ErrorResponse(1-5)
🪛 GitHub Actions: Dongarium FE CI/CD
src/pages/admin/Signup/components/SignupForm/index.tsx
[error] 42-42: ESLint: Unexpected any. Specify a different type. @typescript-eslint/no-explicit-any
🪛 GitHub Check: lint
src/pages/admin/Signup/components/SignupForm/index.tsx
[failure] 42-42:
Unexpected any. Specify a different type
🔇 Additional comments (8)
src/pages/admin/Signup/type/error.ts (1)
1-5: LGTM!Clean and well-structured error type definition that standardizes server error responses across the authentication flow.
src/pages/admin/Login/api/initInstance.ts (1)
4-16: LGTM!The instance creation pattern with configurable defaults is well-structured and provides good flexibility for header merging and timeout configuration.
src/pages/admin/Signup/components/SignupForm/index.tsx (1)
26-31: LGTM!Good defensive check for the temporary token with clear user feedback via toast on missing token.
src/pages/admin/Login/KakaoCallback.tsx (2)
8-19: LGTM!Clean discriminated union types for modeling the login response states.
27-30: LGTM!Appropriate early exit when authorization code is missing.
src/pages/admin/Signup/api/signup.ts (3)
6-10: LGTM!Well-defined response type with proper discriminated union via the
statusfield.
14-20: LGTM!Correct use of Bearer token in Authorization header for authenticated signup request.
27-36: LGTM!Comprehensive error handling with user-friendly messages mapped from HTTP status codes.
|
|
||
| switch (res.data.status) { | ||
| case 'LOGIN_SUCCESS': | ||
| localStorage.setItem('accessToken', res.data.accessToken); |
There was a problem hiding this comment.
Critical: Inconsistent token storage breaks authentication.
Line 40 stores accessToken in localStorage, but src/pages/admin/Signup/api/signup.ts (Line 21) uses sessionStorage. This inconsistency will cause authentication failures when tokens are retrieved from the wrong storage location.
Standardize on one storage mechanism. If the app should persist across browser sessions, use localStorage consistently:
// In src/pages/admin/Signup/api/signup.ts, Line 21
- sessionStorage.setItem('accessToken', response.data.accessToken);
+ localStorage.setItem('accessToken', response.data.accessToken);Or if sessions should end when the browser closes, use sessionStorage consistently:
- localStorage.setItem('accessToken', res.data.accessToken);
+ sessionStorage.setItem('accessToken', res.data.accessToken);Also applies to: 21-21
🤖 Prompt for AI Agents
In src/pages/admin/Login/KakaoCallback.tsx around line 40, the code stores
accessToken in localStorage while src/pages/admin/Signup/api/signup.ts (line 21)
uses sessionStorage; change the storage call here from
localStorage.setItem('accessToken', ...) to
sessionStorage.setItem('accessToken', ...) (or alternatively change the signup
file if you decide to standardize on localStorage) and then search the repo for
other accessToken/session token reads/writes and make them consistent to the
chosen storage mechanism.
| } catch (e: unknown) { | ||
| if (axios.isAxiosError(e)) { | ||
| const error = e as AxiosError<ErrorResponse>; | ||
| const status = error.response?.status; | ||
| const detailMsg = error.response?.data.detail; | ||
| switch (status) { | ||
| case 400: | ||
| throw new Error(`입력 오류: ${detailMsg}`); | ||
| case 401: | ||
| throw new Error(`권한 오류: ${detailMsg}`); | ||
| case 409: | ||
| throw new Error(`중복 오류: ${detailMsg}`); | ||
| default: | ||
| throw new Error(`알 수 없는 오류: ${e.message}`); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Non-Axios errors are not propagated.
The error handling only catches Axios errors. Network failures, timeouts, or other non-Axios errors will fall through silently without notifying the caller.
Apply this diff to ensure all errors are properly propagated:
}
+ throw new Error('알 수 없는 오류가 발생했습니다.');
}
}
};🤖 Prompt for AI Agents
In src/pages/admin/Signup/api/signup.ts around lines 22 to 38, the catch block
only handles Axios errors and silently swallows non-Axios exceptions; update the
catch to rethrow non-Axios errors so callers are notified: after the
axios.isAxiosError branch, add a fallback that throws the original error (or
converts unknown to an Error via new Error(String(e))) so network failures,
timeouts, or other exceptions are propagated; keep existing Axios-specific
switch logic intact.
There was a problem hiding this comment.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/pages/admin/Signup/components/SignupForm/index.tsx(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/pages/admin/Signup/components/SignupForm/index.tsx (2)
src/pages/admin/Signup/api/signup.ts (1)
postSignupForm(12-39)src/styles/theme.ts (1)
theme(3-10)
🔇 Additional comments (2)
src/pages/admin/Signup/components/SignupForm/index.tsx (2)
38-38: Good improvement using onAutoClose.This change correctly addresses the previous review feedback by replacing
setTimeoutwith theonAutoClosecallback, making the navigation directly tied to the toast lifecycle.
40-49: Safer error handling implemented correctly.This change correctly addresses the previous review feedback by using
unknowntype with aninstanceof Errorguard. The implementation aligns well with the API layer, which consistently throwsErrorobjects as shown in thepostSignupFormimplementation.
| const temporaryToken = localStorage.getItem('temporaryToken'); | ||
|
|
||
| if (!temporaryToken) { | ||
| toast.error('회원가입을 위한 토큰이 존재하지 않습니다.'); | ||
| return; | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Remove temporaryToken after successful signup.
The guard logic correctly prevents submission when the token is missing. However, after successful signup (when postSignupForm completes and stores the accessToken in sessionStorage), the temporaryToken should be removed from localStorage to prevent reuse and maintain clean state.
Add cleanup after line 34:
try {
await postSignupForm(signupFormValue, temporaryToken);
+ localStorage.removeItem('temporaryToken');
toast.success('회원가입 완료!', {Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/pages/admin/Signup/components/SignupForm/index.tsx around lines 26-31
(and after line 34), the temporaryToken is read from localStorage but never
removed after a successful signup; update the success path after postSignupForm
completes (where you store accessToken in sessionStorage) to call
localStorage.removeItem('temporaryToken') so the token is cleaned up only on
successful signup, leaving failure/error paths unchanged.
- accessToken : localStorage - temporaryToken : sessionStorage
|
헉 아직 허스키 안되시나요?? 제가 말씀드린 파일은 존재하나요? (바쁘시면 나중에 확인하셔도 돼요) |
|
.husky 파일이랑 pre-commit 파일 확인했는데 작동을 안하더라고요 사용하는 기준 shell이나운영체제 차이가 있을 수도 있을 것 같네요(저는 zsh 사용중입니다) |
…ogin [FEAT] OAuth 로그인 인가 코드 전송 후 회원가입/로그인 성공 분기 작업
…ogin [FEAT] OAuth 로그인 인가 코드 전송 후 회원가입/로그인 성공 분기 작업
#️⃣연관된 이슈
📝작업 내용
스크린샷 (선택)
💬리뷰 요구사항(선택)
Summary by CodeRabbit
New Features
Improvements