DOCS/troubleshooting/250823_happybell80_Gmail_OAuth_토큰_갱신_시스템_구축.md
happybell80 97e0888ce0 Fix more incorrect table names in documentation
- users → user in SQL contexts (94 occurrences)
- robeings → robeing in SQL contexts
- user_preferences → user_preference (14 files)
- slack_workspaces → slack_workspace in SQL contexts (17 files)

All table names now correctly match PostgreSQL schema
2025-09-26 00:52:15 +09:00

6.1 KiB

Gmail OAuth 토큰 갱신 시스템 구축

작성일: 2025-08-23 (수정: 2025-08-28)

작성자: happybell80 with Claude

상태: 토큰 갱신 구현됨


1. 문제 상황

초기 문제

  • Gmail 토큰이 1시간 후 만료되는데 자동 갱신 기능 없음
  • 4시 데모를 위해 긴급하게 토큰 갱신 시스템 필요
  • 여러 사용자(happybell80, cdctfm, 0914eagle)의 토큰 관리 필요

증상

  • 토큰 만료 후 Gmail 기능 사용 불가
  • 수동으로 재인증 필요
  • 사용자별 다른 OAuth 앱 사용 시 관리 어려움

2. 시스템 분석

2.1 테이블 구조 파악

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 사용 권한 레벨 조정

// 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 변환 로직

# 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 수정

# 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 저장

# 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 서버에서 직접 컬럼 제약 제거

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 해결: 환경변수 기반으로 동적 설정

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" 에러 해결: 필요한 컬럼들 추가

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