# 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_token 테이블:
- 구조 혼재: 신규 컬럼(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 user 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_token 
        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_token 
        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_token (
        ..., oauth_config, ...
    ) VALUES (..., $8, ...)
""", ..., json.dumps(oauth_config), ...)
```

---

## 4. 주요 이슈 및 해결

### 4.1 테이블 컬럼 불일치
**문제**: `token_data` NOT NULL 제약으로 INSERT 실패
**해결**: 51123 서버에서 직접 컬럼 제약 제거
```sql
ALTER TABLE gmail_token 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_token 
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_token 
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*