- 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
6.1 KiB
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 관련 서비스
- auth-server: OAuth 인증 및 토큰 저장
- skill-email: Gmail API 사용
- 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/callbackhttps://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. 최종 결과
시스템 구성
- 자동 갱신 API:
/api/gmail/refresh/{user_id} - 상태 확인 API:
/api/gmail/check/{user_id} - 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분 전부터 시작
디버깅 체인
- 프론트엔드 요청 확인
- Gateway 라우팅 확인
- Auth 서버 처리 확인
- DB 쿼리 및 데이터 확인
협업
- 서버 접근 권한이 다를 때 역할 분담 중요
- 로컬 개발자는 코드 수정, 서버 관리자는 DB/환경 설정
7. 참고 파일
auth-server/app/providers/gmail_passport.pyauth-server/app/api/gmail_refresh.pyfrontend-customer/src/components/skills-items-panel.tsxskill-email/services/db_credentials_provider.py
8. 향후 개선사항
- 자동 갱신 스케줄러: 만료 5분 전 자동 실행
- 토큰 상태 모니터링: 대시보드에서 실시간 확인
- 에러 알림: 갱신 실패 시 Slack 알림
- 다중 OAuth 앱 관리 UI: 사용자가 직접 OAuth 앱 선택
작성 완료: 2025-08-23 15:45