feat(mobile): integrate backend authentication - #7
Draft
dohyeon16 wants to merge 3 commits into
Draft
Conversation
Connect the React Native app to the Phase 2 auth backend for email signup/login/refresh/logout and account management, while leaving the existing Google/Kakao/Naver social flows untouched. - Add typed API client (core/api): timeout, HTTP error normalization, Bearer header, secret-redacted dev logging, /api/v1 base URL from EXPO_PUBLIC_API_BASE_URL (Preview fallback). - Add auth session layer: refresh token in expo-secure-store, access token in memory, refresh rotation, single-flight refresh, one-shot 401 retry, family-revoke handling. Pure/testable (session.ts). - Wire Splash (restore session with social/local fallback), Login, Signup (auto-login on register), and add Account screen (view/edit name, delete account) + backend-aware logout in More. - Unit tests for error normalization and session rotation/single-flight/ 401/logout/delete. Preview integration smoke passed 13/13. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwR3pvvEE1hy9NNrmMjmCQ
Phase 2.6 non-device stabilization on the PR #7 integration branch. Static analysis, automated tests, bundle validation, and Preview API integration only — no device-dependent behavior changed. Session robustness (features/auth/state/session.ts): - initialize() now guards SecureStore read failures / corrupt tokens so a keychain error can't crash startup — falls back to unauthenticated. - applySession() rolls back the in-memory access token if the refresh token can't be persisted (no half-authenticated state). - clearLocalSession() treats store.clear() failures as best-effort so logout/delete always reach the unauthenticated state. API client (core/api/client.ts): - Inject fetch and drop DOM-global type coupling so the client is unit-testable under node:test. - joinUrl() collapses duplicate slashes when combining base + /api/v1 + path. Notifications (core/notifications): - Extract pure deriveNotifications() from the storage-coupled feed so the notification targets / dedupe / sort logic is unit-testable (behavior-preserving). Tests: +40 (session edge cases, API client, notification derivation); node:test suite now 60/60. Preview integration smoke 19/19. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwR3pvvEE1hy9NNrmMjmCQ
Phase 2.6 follow-up. Two non-device issues from the stabilization report,
fixed with static analysis + automated tests only.
Local dates (UTC/KST boundary):
- todayDateString/currentYearMonth used Date.toISOString() (UTC), which
returns the previous day/month during KST 00:00–08:59. Replace with pure
local-date helpers formatLocalDate/formatLocalYearMonth and add
parseLocalDate to avoid the new Date("YYYY-MM-DD") UTC-parse trap.
- Remove the same toISOString().slice() antipattern from payCalc weekKeyOf
(grouping key; behavior-neutral) and backup filename stamps.
- Timestamps (createdAt/updatedAt/addedAt/exportedAt) stay full ISO/UTC —
those are instants, not local calendar dates.
Backup restore atomicity:
- importAllData did multiRemove() then multiSet(), so a mid-restore failure
could wipe or partially restore data. Extract pure restoreBackupData()
(validate in memory -> snapshot -> set-new then clear-absent -> rollback
to snapshot on any failure). If rollback also fails, throw a typed
BackupRestoreError instead of reporting success. appLock and auth tokens
are never in BACKUP_KEYS, so they remain excluded.
Tests: +17 (9 local-date boundary, 8 restore failure-injection/rollback);
node:test suite now 77/77. tsc clean; iOS/Android/Web export pass; Preview
smoke 19/19. expo-doctor unchanged (2 pre-existing non-blocking warnings).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwR3pvvEE1hy9NNrmMjmCQ
This was referenced Aug 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
개요 (Phase 2.5)
Phase 2 백엔드 인증 API를 모바일 앱(React Native/Expo)에 연동합니다. 이메일
회원가입·로그인·자동 로그인·토큰 갱신·로그아웃·회원정보·회원탈퇴를 구현하고,
기존 Google/Kakao/Naver 소셜 로그인 흐름은 그대로 보존합니다. Phase 3 기능
(근무지/출퇴근/급여/증빙/리포트)은 포함하지 않습니다.
구현 범위
src/core/api/): 공통 base URL, JSON, 타임아웃,네트워크/HTTP 오류 정규화,
Authorization: Bearer, 개발 로그 secret redaction.refresh 엔드포인트에는 자동 재시도/interceptor를 걸지 않음(무한 루프 방지).
src/features/auth/state/session.ts, 순수·테스트 가능):initialize/register/login/refreshSession/logout/getCurrentUser/updateCurrentUser/
deleteCurrentUser/clearLocalSession.
Signup(가입 성공 시 자동 로그인), Account(회원정보 조회/이름 수정/회원탈퇴),
More(백엔드 사용자 표시 + 세션 인지 로그아웃).
사용한 실제 API 엔드포인트 (backend
770416e,/api/v1확인)/api/v1/auth/register/api/v1/auth/login/api/v1/auth/refresh/api/v1/auth/logout{ok}/api/v1/users/me/api/v1/users/me/api/v1/users/me응답
user:{id, email(nullable), name, primary_provider, created_at, updated_at}—내부 필드(
password_hash등) 미노출.SecureStore / 토큰 정책
expo-secure-store(iOS Keychain / Android Keystore)에만 저장./users/me→ 인증 전환.refresh rotation / single-flight
기존 소셜 로그인 보존
POST /auth/session/{provider}폴링,callback, DELETE 정리), Render callback URL, provider 환경변수, 포트 8081,
prompt=login(카카오) 등 변경 없음.인증 연동과 분리해 기존 로컬 Account 저장 방식을 그대로 유지했습니다.
(후속: 백엔드
POST /api/v1/auth/bridge/exchange로 브릿지 세션→토큰 교환이가능하므로, 소셜 로그인을 동일 토큰 세션으로 통합하는 것을 다음 단계로 남깁니다.)
테스트 결과
npx tsc --noEmit: 통과 (strict).npm test: 37/37 통과 (신규 인증 17 + 기존 20). 오류 정규화, 세션 복원,refresh rotation, 동시 401 single-flight, 재시도 후 401 세션 정리, refresh 재사용/폐기,
로그아웃 로컬 정리, DELETE 204 처리, 탈퇴 후 상태 초기화 커버.
expo config) + web 번들 export: 통과.Preview 통합 스모크 (Preview 전용, 임시 계정, 토큰 미출력)
https://workproof-backend-preview.onrender.com대상 13/13 PASS:register(201) → users/me → refresh(회전) → PATCH 이름 → logout → 폐기 refresh 재사용(401)
→ 재로그인 → DELETE(204) → 탈퇴 후 로그인 차단(401) → 탈퇴 후 access 차단(401).
임시 계정은 soft-delete 상태로 정리됨. Production에는 테스트 요청하지 않았습니다.
실기기 미검증 (별도 필요)
Issue #2를 닫지 않는 이유
Issue #2는 major enhancements의 네이티브/Development Build 검증 항목입니다. 본
작업은 그 검증을 대신 수행하지 않으며, 위 실기기 항목도 미검증이므로 Issue #2를
닫지 않습니다.
Merge 선행조건 / 순서
본 PR의 base는 PR #3 브랜치(
refactor/feature-based-structure)입니다. 확정된 순서:backend / Render / Neon 무변경 확인
.env실제 값 미변경,.env.example에 key 이름/예시만 추가.mobile/내부. main / PR feat: expand payroll, attendance, scheduling, and security features #1 / PR refactor: organize mobile source by feature #3 브랜치, Issue test: complete native validation for major enhancements #2 무변경.Phase 2.6 — Non-device stabilization (commit
bfc5a59)실기기 없이 검증 가능한 범위를 정적 분석·자동화 테스트·번들 검증·Preview API 통합으로 최대한 완성했습니다. 기기 의존 동작은 변경하지 않았습니다.
수정한 결함 (감사에서 발견, 실기기 불필요)
joinUrl로 base+/api/v1+path 결합 시 이중 슬래시 제거. fetch 주입 가능 구조로 바꿔 단위 테스트 가능하게(동작 동일).deriveNotifications()분리(동작 보존) → 알림 이동 대상/중복/정렬 자동 검증.추가한 자동화 테스트 (+40, 총 60/60 통과)
정적/번들 검증
tsc --noEmit통과 ·npm test60/60 ·expo configOK.expo-doctor16/18 — 2건은 기존부터 있던 비차단 경고(@expo/config-plugins 직접설치 권고, expo 54.0.35↔~54.0.36 패치 드리프트). 이번 변경으로 새 경고 없음. (지시대로expo install --fix/audit fix미실행.)Preview 통합 스모크 (Preview 전용, 임시계정, 토큰 미출력) — 19/19 PASS
health→register(201)→me→PATCH→중복(409)→오답(401)→미존재(401)→통합 실패 메시지→refresh(회전)→이전 refresh 재사용(401·family 폐기)→재로그인→logout→로그아웃 refresh 재사용(401)→재로그인→DELETE(204)→탈퇴 후 login/access/refresh 전부 401. 임시계정 soft-delete 정리. Production 무요청.
문서화한 발견사항 (이번엔 수정 안 함)
date.ts의todayDateString/currentYearMonth가 UTC(toISOString) 기반 — KST 자정~09시 경계 하루 오차 가능. 단, 저장·비교에 동일 함수를 써 내부 일관(크래시/데이터손실 아님)하고, 근태 기록 날짜 지정이 Issue test: complete native validation for major enhancements #2 device 검증 대상이라 이 auth 브랜치에서 변경하지 않고 남깁니다.실기기 미검증(BLOCKED — requires physical device/dev build)
Face ID 실제 프롬프트, Keychain/Keystore 실제 저장, GPS 센서, 로컬 알림 실수신, 파일 picker/공유 시트, 카메라/사진 권한, Safe Area 육안, Google/Kakao/Naver 실계정 로그인, custom scheme/deep-link 복귀. → PR #1→#3→#7 병합 정리 후 최종 1회 실기기 검증에서 확인. Issue #2 미close, PR #7 Draft 유지.
Phase 2.6 follow-up — 로컬 날짜 & 백업 복원 견고화 (commit
fa4867d)Phase 2.6 보고서의 비실기기 잔여 문제 2건을 정적 분석·자동화 테스트로만 해결.
1) date.ts UTC/KST 날짜 경계 오류
todayDateString/currentYearMonth가Date.toISOString()(UTC) 기반이라 KST 00:00~08:59에 전날/전월 반환 → 순수 로컬 헬퍼formatLocalDate/formatLocalYearMonth로 교체,new Date("YYYY-MM-DD")UTC 파싱 함정 회피용parseLocalDate추가.toISOString().slice()를 로컬 날짜로 사용) 제거:payCalc.weekKeyOf(주 그룹핑 키 — behavior-neutral), backup 파일명 스탬프(native/web).createdAt/updatedAt/addedAt/exportedAt)는 시점(instant)이라 ISO/UTC 그대로 유지 — 저장 형식YYYY-MM-DD불변, 기존 데이터 형식 변경 없음, timezone 라이브러리 미추가.2) importAllData 복원 비원자성
multiRemove()→multiSet()순서라 중간 실패 시 데이터 유실/부분복원 위험.restoreBackupData()추출: 메모리 검증 → 스냅샷 → 새 값 먼저 쓰기 → 백업에 없는 키 비우기 → 실패 시 스냅샷으로 롤백. 롤백까지 실패하면BackupRestoreError로 명확한 실패 반환(성공 처리 금지, reload 안 함).appLock·인증 토큰은BACKUP_KEYS에 없어 항상 제외(SecureStore refresh·메모리 access·backend 세션은 백업 대상 자체가 아님).테스트 (+17, 총 77/77)
검증
tsc --noEmit통과 ·npm test77/77 ·expo configOK · iOS/Android/Web export 통과 ·git diff --checkclean.expo-doctor16/18 — 기존 비차단 2건만(@expo/config-plugins 직접설치 권고: doctor도 peer 목적이면 무시 가능 안내 / expo 54.0.35↔~54.0.36 패치 드리프트, package.json 핀~54.0.2미변경). 이번 변경으로 새 경고·새 의존성 없음.expo install --fix/audit fix미실행.실기기 미검증 유지 (BLOCKED)
기기 timezone 변경 실검증, 실제 파일 picker/공유 시트 복원 동작 등은 여전히 실기기 항목 — 최종 병합 정리 후 1회 실기기 검증에서 확인. Issue #2 미close, PR #7 Draft 유지.
🤖 Generated with Claude Code