[JDDEV-26] [JDDEV-30] [JDDEV-33] Feat: 기본 로그인/회원가입/구글 로그인 API 연결#59
Conversation
📝 WalkthroughWalkthroughThis PR establishes OAuth2 redirect handling and comprehensive email-based authentication. It adds a new auth library with API integration, token persistence, and JWT parsing; implements OAuth2 callback handling with conditional routing; enhances the login/signup screen with async flows and error management; and syncs authenticated user email across the sidebar component. ChangesOAuth2 and Email Authentication Integration
Sequence Diagram(s)sequenceDiagram
participant Browser
participant OAuthRedirectPage
participant OAuthRedirectClient
participant AuthLib
participant Router
Browser->>OAuthRedirectPage: Load redirect page
OAuthRedirectPage->>OAuthRedirectClient: Render with Suspense
OAuthRedirectClient->>OAuthRedirectClient: Parse query params
alt Has accessToken and refreshToken
OAuthRedirectClient->>AuthLib: saveAuthTokens()
AuthLib->>Browser: Store tokens in localStorage
OAuthRedirectClient->>Router: router.replace(ROUTES.APPLY)
Router->>Browser: Redirect to /apply
else Missing tokens
OAuthRedirectClient->>OAuthRedirectClient: Schedule redirect
OAuthRedirectClient->>Router: router.replace(ROUTES.LOGIN) after 2s
Router->>Browser: Redirect to /login
end
sequenceDiagram
participant User
participant EmailLoginScreen
participant AuthLib
participant Router
User->>EmailLoginScreen: Enter email and password
alt Login flow
EmailLoginScreen->>EmailLoginScreen: Validate inputs
EmailLoginScreen->>AuthLib: loginWithEmail(credentials)
AuthLib->>AuthLib: POST to /auth/login
AuthLib->>EmailLoginScreen: Return tokens or error
alt Success
EmailLoginScreen->>AuthLib: saveAuthTokens(tokens)
EmailLoginScreen->>Router: router.replace(ROUTES.APPLY)
else Error
EmailLoginScreen->>EmailLoginScreen: Display loginErrorMessage
end
else Signup flow
EmailLoginScreen->>EmailLoginScreen: Validate name, email, password
EmailLoginScreen->>AuthLib: sendEmailVerification(email)
AuthLib->>EmailLoginScreen: Success or error
EmailLoginScreen->>EmailLoginScreen: Switch to verification mode
User->>EmailLoginScreen: Enter verification code
EmailLoginScreen->>AuthLib: confirmEmailVerification(code)
EmailLoginScreen->>AuthLib: signupWithEmail(credentials)
alt Success
EmailLoginScreen->>AuthLib: saveAuthTokens(tokens)
EmailLoginScreen->>Router: router.replace(ROUTES.APPLY)
else Error
EmailLoginScreen->>EmailLoginScreen: Display verificationErrorMessage
end
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
jobdri/src/components/common/lnb/Lnb.tsx (1)
40-48: ⚡ Quick win
storage-only subscription ignores same-tab userEmail writes, but current auth routing likely avoids the stale-case
Lnb.tsxsubscribes only to thestorageevent (lines 40-48).userEmailis only written insaveAuthTokens(localStorage.setItem) andsaveAuthTokensis only called fromjobdri/src/components/login/EmailLoginScreen.tsx(/login) andjobdri/src/app/oauth2/redirect/OAuthRedirectClient.tsx(/oauth2/redirect).LayoutShellonly rendersLnbforLAYOUT_ROUTES(/and/credit), so during/loginand/oauth2/redirecttheuseSyncExternalStoresubscription isn’t mounted—when routing reaches/apply,Lnbmounts fresh viaMockApplicationHomePageClientand the initial snapshot should be current.
Recommend adding a same-tab notification (e.g., custom event) ifuserEmailcan change whileLnbremains mounted (future in-app email changes).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@jobdri/src/components/common/lnb/Lnb.tsx` around lines 40 - 48, subscribeToStoredEmail currently only listens to the window "storage" event so it misses same-tab updates to AUTH_STORAGE_KEYS.userEmail; update the sync subscription to also handle a custom same-tab event: add a listener in subscribeToStoredEmail (function subscribeToStoredEmail) for a custom event name (e.g., "userEmailChanged") that calls onStoreChange, and remove that listener in the cleanup alongside the existing window.removeEventListener("storage", ...). Then update saveAuthTokens (where localStorage.setItem(AUTH_STORAGE_KEYS.userEmail, ... ) is called) to dispatch the same custom event after writing to localStorage so Lnb (and other same-tab subscribers) receive immediate updates.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@jobdri/src/app/oauth2/redirect/OAuthRedirectClient.tsx`:
- Around line 11-35: The component OAuthRedirectClient must stop reading
accessToken/refreshToken from URL query (avoid
saveAuthTokens/getEmailFromAccessToken usage) and instead read an authorization
code (e.g., "code" and optional "state") and call a backend endpoint to exchange
that code server-side so the server issues HttpOnly cookies; update the
useEffect in OAuthRedirectClient to POST the code to an API (e.g.,
/auth/oauth/callback), handle success by router.replace(ROUTES.APPLY) and
failure by router.replace(ROUTES.LOGIN), and remove any client-side storage of
raw tokens; keep getEmailFromAccessToken only if used elsewhere but do not use
it here, and ensure the redirect replaces history (router.replace) so the code
param is not retained.
In `@jobdri/src/components/login/EmailLoginScreen.tsx`:
- Around line 236-241: The login validation currently blocks submission using
the signup password regex by checking passwordPattern.test(password); remove
that check from the EmailLoginScreen validation and replace it with a simple
presence (and/or minimal length) check (e.g., password.length > 0 or >= 6) so
existing users with legacy passwords are not rejected; update the conditional
that uses isLoginSubmitting, isLoginReady, emailPattern, passwordPattern (and
any variable named password) to only use emailPattern for format validation and
a simple non-empty/min-length check for password, keeping variable names
unchanged.
In `@jobdri/src/lib/auth.ts`:
- Around line 172-174: The code only writes AUTH_STORAGE_KEYS.userEmail when the
variable email is truthy, leaving a stale value if email becomes undefined; in
the function containing the snippet, add an else branch to clear the key (e.g.,
window.localStorage.removeItem(AUTH_STORAGE_KEYS.userEmail) or setItem with an
empty string) so that when email is falsy the stored userEmail is removed;
reference the existing AUTH_STORAGE_KEYS.userEmail, window.localStorage.setItem
call, and the email variable to locate and update the logic.
---
Nitpick comments:
In `@jobdri/src/components/common/lnb/Lnb.tsx`:
- Around line 40-48: subscribeToStoredEmail currently only listens to the window
"storage" event so it misses same-tab updates to AUTH_STORAGE_KEYS.userEmail;
update the sync subscription to also handle a custom same-tab event: add a
listener in subscribeToStoredEmail (function subscribeToStoredEmail) for a
custom event name (e.g., "userEmailChanged") that calls onStoreChange, and
remove that listener in the cleanup alongside the existing
window.removeEventListener("storage", ...). Then update saveAuthTokens (where
localStorage.setItem(AUTH_STORAGE_KEYS.userEmail, ... ) is called) to dispatch
the same custom event after writing to localStorage so Lnb (and other same-tab
subscribers) receive immediate updates.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 9e958f1a-1079-4007-acc1-465c2fcf9cae
📒 Files selected for processing (6)
jobdri/src/app/oauth2/redirect/OAuthRedirectClient.tsxjobdri/src/app/oauth2/redirect/page.tsxjobdri/src/components/common/lnb/Lnb.tsxjobdri/src/components/login/EmailLoginScreen.tsxjobdri/src/constants/routes.tsjobdri/src/lib/auth.ts
| const accessToken = searchParams.get("accessToken"); | ||
| const refreshToken = searchParams.get("refreshToken"); | ||
| const error = searchParams.get("error"); | ||
| const errorMessage = searchParams.get("message"); | ||
| const message = | ||
| accessToken && refreshToken | ||
| ? "Google 로그인 처리 중입니다." | ||
| : errorMessage || | ||
| (error ? "Google 로그인에 실패했습니다." : "로그인 정보를 확인할 수 없습니다."); | ||
|
|
||
| useEffect(() => { | ||
| if (accessToken && refreshToken) { | ||
| saveAuthTokens( | ||
| { accessToken, refreshToken }, | ||
| searchParams.get("email") || | ||
| getEmailFromAccessToken(accessToken) || | ||
| undefined, | ||
| ); | ||
| router.replace(ROUTES.APPLY); | ||
| return; | ||
| } | ||
|
|
||
| const timerId = window.setTimeout(() => { | ||
| router.replace(ROUTES.LOGIN); | ||
| }, 2000); |
There was a problem hiding this comment.
Avoid query-string token delivery in OAuth callback.
Reading accessToken/refreshToken from URL query on Line 11-12 exposes tokens to browser history and potential referrer leakage. This flow should move to a safer contract (e.g., backend-set HttpOnly cookies or one-time code exchange).
🛡️ Immediate client-side mitigation (partial)
useEffect(() => {
if (accessToken && refreshToken) {
+ window.history.replaceState(null, "", ROUTES.OAUTH_REDIRECT);
saveAuthTokens(
{ accessToken, refreshToken },
searchParams.get("email") ||
getEmailFromAccessToken(accessToken) ||
undefined,
);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@jobdri/src/app/oauth2/redirect/OAuthRedirectClient.tsx` around lines 11 - 35,
The component OAuthRedirectClient must stop reading accessToken/refreshToken
from URL query (avoid saveAuthTokens/getEmailFromAccessToken usage) and instead
read an authorization code (e.g., "code" and optional "state") and call a
backend endpoint to exchange that code server-side so the server issues HttpOnly
cookies; update the useEffect in OAuthRedirectClient to POST the code to an API
(e.g., /auth/oauth/callback), handle success by router.replace(ROUTES.APPLY) and
failure by router.replace(ROUTES.LOGIN), and remove any client-side storage of
raw tokens; keep getEmailFromAccessToken only if used elsewhere but do not use
it here, and ensure the redirect replaces history (router.replace) so the code
param is not retained.
| if ( | ||
| isLoginSubmitting || | ||
| !isLoginReady || | ||
| !emailPattern.test(email) || | ||
| !passwordPattern.test(password) | ||
| ) { |
There was a problem hiding this comment.
Do not apply signup password policy in login validation.
On Line 240, login is blocked unless the password matches the signup regex. This can reject legitimate existing users whose stored passwords don’t conform to the new client-side pattern.
✅ Suggested fix
if (
isLoginSubmitting ||
!isLoginReady ||
- !emailPattern.test(email) ||
- !passwordPattern.test(password)
+ !emailPattern.test(email)
) {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@jobdri/src/components/login/EmailLoginScreen.tsx` around lines 236 - 241, The
login validation currently blocks submission using the signup password regex by
checking passwordPattern.test(password); remove that check from the
EmailLoginScreen validation and replace it with a simple presence (and/or
minimal length) check (e.g., password.length > 0 or >= 6) so existing users with
legacy passwords are not rejected; update the conditional that uses
isLoginSubmitting, isLoginReady, emailPattern, passwordPattern (and any variable
named password) to only use emailPattern for format validation and a simple
non-empty/min-length check for password, keeping variable names unchanged.
| if (email) { | ||
| window.localStorage.setItem(AUTH_STORAGE_KEYS.userEmail, email); | ||
| } |
There was a problem hiding this comment.
Clear stale userEmail when email is unavailable.
On Line 172, userEmail is only written when email is truthy. If a later login flow cannot resolve email, the previous account email stays in storage and can be shown as the current user.
🔧 Suggested fix
if (email) {
window.localStorage.setItem(AUTH_STORAGE_KEYS.userEmail, email);
+ } else {
+ window.localStorage.removeItem(AUTH_STORAGE_KEYS.userEmail);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (email) { | |
| window.localStorage.setItem(AUTH_STORAGE_KEYS.userEmail, email); | |
| } | |
| if (email) { | |
| window.localStorage.setItem(AUTH_STORAGE_KEYS.userEmail, email); | |
| } else { | |
| window.localStorage.removeItem(AUTH_STORAGE_KEYS.userEmail); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@jobdri/src/lib/auth.ts` around lines 172 - 174, The code only writes
AUTH_STORAGE_KEYS.userEmail when the variable email is truthy, leaving a stale
value if email becomes undefined; in the function containing the snippet, add an
else branch to clear the key (e.g.,
window.localStorage.removeItem(AUTH_STORAGE_KEYS.userEmail) or setItem with an
empty string) so that when email is falsy the stored userEmail is removed;
reference the existing AUTH_STORAGE_KEYS.userEmail, window.localStorage.setItem
call, and the email variable to locate and update the logic.
🔗 관련 이슈
📝 개요
/apply로 이동하도록 처리했습니다.⌨️ 작업 상세 내용
/oauth2/redirect콜백 페이지 추가💡 코드 설명 및 참고사항
🔍 리뷰 요구사항 (Reviewers)