# Gmail OAuth 토큰 갱신 시스템 구축 ## 작성일: 2025-08-23 (수정: 2025-08-28) ## 작성자: 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 **해결**: 환경변수 기반으로 동적 설정 ```python database_url = os.getenv("DATABASE_URL", "postgresql://robeings:robeings@192.168.219.45:5432/main_db") ``` ### 4.5 Gmail 토큰 테이블 구조 개선 **문제**: `access_token` 컬럼 누락으로 "column does not exist" 에러 **해결**: 필요한 컬럼들 추가 ```sql ALTER TABLE gmail_tokens ADD COLUMN IF NOT EXISTS access_token TEXT, ADD COLUMN IF NOT EXISTS refresh_token TEXT, ADD COLUMN IF NOT EXISTS token_type VARCHAR DEFAULT 'Bearer', ADD COLUMN IF NOT EXISTS expires_at FLOAT; -- 기존 token_data에서 새 컬럼으로 데이터 마이그레이션 UPDATE gmail_tokens SET access_token = token_data->>'access_token', refresh_token = token_data->>'refresh_token' WHERE token_data IS NOT NULL; ``` --- ## 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*