From 1c97b904af4150cde0fe3004a8482e10f9a91428 Mon Sep 17 00:00:00 2001 From: happybell80 Date: Sat, 23 Aug 2025 15:46:19 +0900 Subject: [PATCH] =?UTF-8?q?Gmail=20OAuth=20=ED=86=A0=ED=81=B0=20=EA=B0=B1?= =?UTF-8?q?=EC=8B=A0=20=EC=8B=9C=EC=8A=A4=ED=85=9C=20=ED=8A=B8=EB=9F=AC?= =?UTF-8?q?=EB=B8=94=EC=8A=88=ED=8C=85=20=EB=AC=B8=EC=84=9C=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 문제 상황 및 해결 과정 정리 - 테이블 구조, OAuth 설정, 자동 갱신 API 문서화 - 주요 이슈 및 해결 방법 기록 - 4시 데모 준비 상태 확인 --- ...il_OAuth_토큰_갱신_시스템_구축.md | 198 ++++++++++++++++++ ...appybell80_rb8001_레벨1_표시_문제.md | 161 ++++++++++++++ 2 files changed, 359 insertions(+) create mode 100644 troubleshooting/250823_happybell80_Gmail_OAuth_토큰_갱신_시스템_구축.md create mode 100644 troubleshooting/250823_happybell80_rb8001_레벨1_표시_문제.md diff --git a/troubleshooting/250823_happybell80_Gmail_OAuth_토큰_갱신_시스템_구축.md b/troubleshooting/250823_happybell80_Gmail_OAuth_토큰_갱신_시스템_구축.md new file mode 100644 index 0000000..fc40b12 --- /dev/null +++ b/troubleshooting/250823_happybell80_Gmail_OAuth_토큰_갱신_시스템_구축.md @@ -0,0 +1,198 @@ +# Gmail OAuth 토큰 갱신 시스템 구축 + +## 작성일: 2025-08-23 +## 작성자: happybell80 with Claude + +--- + +## 1. 문제 상황 + +### 초기 문제 +- Gmail 토큰이 1시간 후 만료되는데 자동 갱신 기능 없음 +- 4시 데모를 위해 긴급하게 토큰 갱신 시스템 필요 +- 여러 사용자(happybell80, cdctfm, 0914eagle)의 토큰 관리 필요 + +### 증상 +- 토큰 만료 후 Gmail 기능 사용 불가 +- 수동으로 재인증 필요 +- 사용자별 다른 OAuth 앱 사용 시 관리 어려움 + +--- + +## 2. 시스템 분석 + +### 2.1 테이블 구조 파악 +```sql +gmail_tokens 테이블: +- 구조 혼재: 신규 컬럼(access_token, refresh_token)과 기존 컬럼(token_data, oauth_config) +- 두 가지 형식 모두 지원 필요 +``` + +### 2.2 관련 서비스 +1. **auth-server**: OAuth 인증 및 토큰 저장 +2. **skill-email**: Gmail API 사용 +3. **frontend-customer**: 사용자 인터페이스 + +--- + +## 3. 해결 과정 + +### 3.1 Gmail 사용 권한 레벨 조정 +```typescript +// frontend-customer/src/components/skills-items-panel.tsx +const gmailItem: GmailItem = { + requiredLevel: 1, // 5 → 1로 변경 + capabilities: { + 1: '이메일 읽기', // 5 → 1 + 3: '이메일 보내기', // 7 → 3 + 5: '실시간 추적' // 11 → 5 + } +} +``` + +### 3.2 OAuth Redirect URI 추가 +Google Cloud Console에 추가: +- `https://auth.ro-being.com/auth/gmail/callback` +- `https://auth.ro-being.com/auth/gmail/passport/callback` + +### 3.3 Username to UUID 변환 로직 +```python +# auth-server/app/providers/gmail_passport.py +async def get_uuid_from_username(username: str, conn) -> str: + row = await conn.fetchrow(""" + SELECT id::text FROM users WHERE username = $1 + """, username) + if row: + return row['id'] + return username # fallback +``` + +### 3.4 토큰 자동 갱신 API 수정 +```python +# auth-server/app/api/gmail_refresh.py +@router.post("/refresh/{user_id}") +async def refresh_gmail_token(user_id: str): + # 1. 새 컬럼 구조와 기존 구조 모두 조회 + cur.execute(''' + SELECT access_token, refresh_token, expires_at, + token_data, oauth_config, metadata, expiry + FROM gmail_tokens + WHERE user_id = %s::uuid + ''', (user_id,)) + + # 2. 만료 확인 (5분 이상 남았으면 갱신 안함) + if remaining > 300: + return {"status": "valid", ...} + + # 3. Google OAuth API로 토큰 갱신 + response = requests.post('https://oauth2.googleapis.com/token', ...) + + # 4. 새 토큰으로 DB 업데이트 + cur.execute(''' + UPDATE gmail_tokens + SET access_token = %s, expires_at = %s, ... + ''') +``` + +### 3.5 OAuth Config 저장 +```python +# gmail_passport.py에 추가 +oauth_config = { + "client_id": CLIENT_ID, + "client_secret": CLIENT_SECRET, + "token_uri": "https://oauth2.googleapis.com/token", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "redirect_uris": [REDIRECT_URI] +} + +# INSERT 쿼리에 oauth_config 추가 +await conn.execute(""" + INSERT INTO gmail_tokens ( + ..., oauth_config, ... + ) VALUES (..., $8, ...) +""", ..., json.dumps(oauth_config), ...) +``` + +--- + +## 4. 주요 이슈 및 해결 + +### 4.1 테이블 컬럼 불일치 +**문제**: `token_data` NOT NULL 제약으로 INSERT 실패 +**해결**: 51123 서버에서 직접 컬럼 제약 제거 +```sql +ALTER TABLE gmail_tokens ALTER COLUMN token_data DROP NOT NULL; +``` + +### 4.2 UUID 타입 오류 +**문제**: `invalid UUID 'happybell80'` +**해결**: username을 UUID로 변환하는 함수 추가 + +### 4.3 Scopes 타입 오류 +**문제**: `expected str, got list` +**해결**: `json.dumps(scopes)`로 JSON 문자열 변환 + +### 4.4 DB 연결 설정 +**문제**: `host.docker.internal` vs 실제 IP +**해결**: Docker 환경에서는 `host.docker.internal` 사용 + +--- + +## 5. 최종 결과 + +### 시스템 구성 +1. **자동 갱신 API**: `/api/gmail/refresh/{user_id}` +2. **상태 확인 API**: `/api/gmail/check/{user_id}` +3. **OAuth Config 저장**: 사용자별 다른 OAuth 앱 지원 + +### 사용자별 상태 (4시 데모 준비) +| 사용자 | Token | 만료 시간 | OAuth App | +|--------|-------|-----------|-----------| +| happybell80 | ✅ | 16:28 | 1044056... | +| cdctfm | ✅ | 16:28 | 1044056... | +| 0914eagle | ✅ | 9시간+ | 3191622... | + +--- + +## 6. 교훈 + +### 테이블 설계 +- 마이그레이션 시 신구 컬럼 호환성 고려 +- NOT NULL 제약은 신중하게 설정 + +### OAuth 구현 +- Client ID/Secret은 사용자별로 다를 수 있음 +- Refresh Token 관리가 핵심 +- 토큰 갱신 로직은 5분 전부터 시작 + +### 디버깅 체인 +1. 프론트엔드 요청 확인 +2. Gateway 라우팅 확인 +3. Auth 서버 처리 확인 +4. DB 쿼리 및 데이터 확인 + +### 협업 +- 서버 접근 권한이 다를 때 역할 분담 중요 +- 로컬 개발자는 코드 수정, 서버 관리자는 DB/환경 설정 + +--- + +## 7. 참고 파일 + +- `auth-server/app/providers/gmail_passport.py` +- `auth-server/app/api/gmail_refresh.py` +- `frontend-customer/src/components/skills-items-panel.tsx` +- `skill-email/services/db_credentials_provider.py` + +--- + +## 8. 향후 개선사항 + +1. **자동 갱신 스케줄러**: 만료 5분 전 자동 실행 +2. **토큰 상태 모니터링**: 대시보드에서 실시간 확인 +3. **에러 알림**: 갱신 실패 시 Slack 알림 +4. **다중 OAuth 앱 관리 UI**: 사용자가 직접 OAuth 앱 선택 + +--- + +*작성 완료: 2025-08-23 15:45* \ No newline at end of file diff --git a/troubleshooting/250823_happybell80_rb8001_레벨1_표시_문제.md b/troubleshooting/250823_happybell80_rb8001_레벨1_표시_문제.md new file mode 100644 index 0000000..e7a946f --- /dev/null +++ b/troubleshooting/250823_happybell80_rb8001_레벨1_표시_문제.md @@ -0,0 +1,161 @@ +# rb8001 레벨 1 표시 문제 해결 + +## 작성일: 2025-08-23 +## 작성자: happybell80 with Claude + +--- + +## 1. 문제 상황 + +### 증상 +- DB에 rb8001의 레벨이 20으로 저장되어 있음 +- 프론트엔드에서는 계속 레벨 1로 표시됨 +- `/api/stats/rb8001` 요청시 기본값(레벨 1) 반환 + +### 영향 +- 사용자가 로빙의 실제 성장 상태를 확인할 수 없음 +- Gmail 아이템 등 레벨 기반 기능 사용 제한 + +--- + +## 2. 원인 분석 + +### 2.1 Gateway의 하드코딩 문제 +**위치**: `robeing-gateway/app/main.py:267` + +```python +# 문제 코드 +response = await http_client.get( + f"http://192.168.219.52:10508/api/stats/{robeing_id}" # 항상 rb10508_micro로! +) +``` + +**문제점**: +- 모든 stats 요청을 rb10508_micro(포트 10508)로 강제 라우팅 +- 사용자별 로빙 무시 + +### 2.2 프론트엔드 헤더 누락 +**위치**: `frontend-customer/src/services/robeing-api.ts:447` + +```typescript +// 문제 코드 +const response = await fetch(`${ROBEING_API_URL}/api/stats/${robeingId}`); +// X-User-Id 헤더 없음! +``` + +**문제점**: +- Gateway가 사용자를 식별할 수 없음 +- 기본값 반환 + +### 2.3 잘못된 엔드포인트 경로 +**실제 엔드포인트**: +- rb8001: `/stats` (api 없음) +- rb10508_micro: `/stats/{robeing_id}` (api 없음) + +**Gateway 요청**: +- `/api/stats/{robeing_id}` → 404 에러 + +### 2.4 서버 위치 오류 +**Gateway 설정**: +- rb8001: `http://localhost:8001` → 51123 서버에 없음 + +**실제 위치**: +- rb8001: `http://192.168.219.52:8001` (51124 서버) + +### 2.5 State Service 연결 실패 +**rb8001 환경변수**: +- `STATE_SERVICE_URL` 미설정 +- State Service에서 DB 스탯 로드 불가 +- 기본값 사용 (각 스탯 10, 총 50 → 레벨 1) + +--- + +## 3. 해결 과정 + +### 3.1 Gateway 사용자별 라우팅 구현 +```python +# robeing-gateway/app/main.py +@app.get("/api/stats/{robeing_id}") +async def get_stats( + robeing_id: str, + x_user_id: Optional[str] = Header(None) # 사용자 식별 추가 +): + # 사용자별 로빙 정보 조회 + robeing_info = await get_robeing_info(x_user_id) + + # 로빙별 올바른 서버로 라우팅 + if robeing_id == "rb8001": + target_url = f"http://192.168.219.52:8001/stats" + elif robeing_id == "rb10508_micro": + target_url = f"http://192.168.219.52:10508/stats/{robeing_id}" +``` + +### 3.2 프론트엔드 헤더 추가 +```typescript +// frontend-customer/src/services/robeing-api.ts +const headers: HeadersInit = { + 'X-User-Id': userId, + 'Authorization': `Bearer ${authToken}` +}; + +const response = await fetch(`${ROBEING_API_URL}/api/stats/${robeingId}`, { + headers +}); +``` + +### 3.3 응답 형식 정규화 +```python +# rb8001 응답 형식 변환 +if robeing_id == "rb8001" and "stats" in data: + return { + "robeing_id": robeing_id, + "level": data.get("level", 1), + "experience": stats_data.get("experience", 0), + # ... 표준 형식으로 변환 + } +``` + +--- + +## 4. 남은 문제 + +### rb8001의 State Service 미연결 +- `STATE_SERVICE_URL` 환경변수 없음 +- DB 스탯 로드 실패, 자체 계산 사용 +- 실제 DB 레벨(20)과 불일치 + +**해결 방안**: +1. docker-compose.yml에 STATE_SERVICE_URL 추가 +2. 또는 rb8001이 직접 DB 접근하도록 수정 + +--- + +## 5. 교훈 + +### 아키텍처 일관성 +- 모든 로빙이 동일한 API 경로 규칙을 따라야 함 +- `/api/stats` vs `/stats` 혼용 문제 + +### 헤더 전달 중요성 +- 프론트엔드는 항상 사용자 식별 헤더 포함 필요 +- X-User-Id, Authorization 등 + +### 환경변수 관리 +- 필수 서비스 URL은 반드시 설정 +- 누락시 fallback 처리 필요 + +### 디버깅 체인 +1. 프론트엔드 요청 확인 +2. Gateway 라우팅 확인 +3. 실제 서비스 엔드포인트 확인 +4. 서비스 내부 로직 확인 + +--- + +## 6. 참고 파일 + +- `robeing-gateway/app/main.py` +- `frontend-customer/src/services/robeing-api.ts` +- `rb8001/main.py` +- `rb8001/app/router/router.py` +- `rb8001/docker-compose.yml` \ No newline at end of file