# Gmail 토큰 NULL로 인한 일일 브리핑 실패 해결 가이드 ## 작성일: 2025-08-25 ## 작성자: 서버 관리자 ## 상태: 해결 진행중 ## 영향: 매일 9시 브리핑 이메일 수집 실패 (사용자 3명) --- ## 1. 문제 상황 ### 1.1 증상 - **발생 시간**: 매일 오전 9시 (KST) - **영향 사용자**: - happybell80 (goeun2dc@gmail.com) - 0914eagle (0914eagle@gmail.com) - cdctfm (cdctfm@gmail.com) - **현상**: 이메일 요약 없이 뉴스만 포함된 불완전한 브리핑 전송 ### 1.2 근본 원인 ```sql -- 현재 DB 상태: 모든 사용자 token_data가 NULL SELECT username, has_token, is_equipped FROM gmail_tokens; 결과: happybell80 | f (NULL) | t (장착됨) -- 모순 상태 0914eagle | f (NULL) | t (장착됨) -- 모순 상태 cdctfm | f (NULL) | t (장착됨) -- 모순 상태 ``` **문제**: is_equipped=true이지만 실제 token_data=NULL --- ## 2. 시스템 플로우 분석 ### 2.1 일일 브리핑 플로우 ```mermaid sequenceDiagram participant Cron as Gateway Cron
(매일 9시) participant RB as rb8001
(51124:8001) participant Email as skill-email
(51124:8501) participant DB as PostgreSQL
(main_db) participant Slack as Slack API Cron->>RB: POST /api/cron/daily-summary RB->>Email: GET /messages?user_id={slack_id} Email->>DB: SELECT token_data FROM gmail_tokens DB-->>Email: token_data: NULL ❌ Email-->>RB: 500 Internal Server Error RB->>Slack: 불완전한 브리핑 전송
(이메일 없이 뉴스만) ``` ### 2.2 Gmail OAuth 플로우 (재인증 필요) ```mermaid sequenceDiagram participant User as 사용자 participant Front as 프론트엔드 participant Auth as auth-server
(51123:9000) participant Google as Google OAuth participant DB as PostgreSQL User->>Front: Gmail 연결 클릭 Front->>Auth: GET /api/gmail/auth?user_id={uuid} Auth->>Auth: OAuth URL 생성 Auth-->>Front: Redirect to Google Front->>Google: 사용자 리다이렉트 User->>Google: 권한 승인 Google->>Auth: Callback with code Auth->>Google: Exchange code for tokens Google-->>Auth: access_token, refresh_token Auth->>DB: UPDATE gmail_tokens
SET token_data = {...} Auth-->>Front: 인증 완료 ``` --- ## 3. 해결 방안 ### 3.1 단기 해결책 (즉시 적용) #### 방법 1: 관리자 대신 인증 (추천) ```bash # 1. 테스트 계정으로 OAuth 인증 URL 생성 curl -X GET "http://localhost:9000/api/gmail/auth?user_id=1e16e9d5-59f3-54da-a661-8abeabff4230" # 2. 반환된 URL을 브라우저에서 열어 Google 로그인 # 3. 권한 승인 후 콜백 확인 # 4. 토큰 저장 확인 export PGPASSWORD=robeings psql -h localhost -U robeings -d main_db -c \ "SELECT user_id, token_data IS NOT NULL FROM gmail_tokens WHERE user_id = '1e16e9d5-59f3-54da-a661-8abeabff4230';" # 5. 브리핑 수동 테스트 curl -X POST http://192.168.219.52:8001/api/cron/daily-summary \ -H "Content-Type: application/json" \ -H "Authorization: Bearer cron-secret-2024" ``` #### 방법 2: 임시 토큰 주입 (비추천, 테스트용) ```sql -- 주의: 실제 유효한 토큰이 필요함 UPDATE gmail_tokens SET token_data = '{ "access_token": "실제_액세스_토큰", "refresh_token": "실제_리프레시_토큰", "token_type": "Bearer", "expires_in": 3599 }'::jsonb, expiry = NOW() + INTERVAL '1 hour' WHERE user_id = '1e16e9d5-59f3-54da-a661-8abeabff4230'; ``` ### 3.2 중기 해결책 (사용자 재인증 유도) #### 프론트엔드에 알림 추가 ```javascript // 로그인 시 Gmail 토큰 체크 useEffect(() => { checkGmailToken().then(hasToken => { if (!hasToken) { showNotification({ title: "Gmail 재인증 필요", message: "일일 브리핑을 위해 Gmail을 다시 연결해주세요", action: "연결하기", onClick: () => window.location.href = '/settings/gmail' }); } }); }, []); ``` #### Slack 메시지로 재인증 안내 ```python # rb8001의 dm_skill.py에 추가 async def notify_reauth_needed(user_slack_id: str): await slack_client.chat_postMessage( channel=user_slack_id, text="🔔 Gmail 재인증이 필요합니다", blocks=[{ "type": "section", "text": { "type": "mrkdwn", "text": "일일 브리핑을 위해 Gmail을 다시 연결해주세요:\nhttps://ro-being.com/settings/gmail" } }] ) ``` ### 3.3 장기 해결책 (자동 복구) #### 토큰 자동 갱신 구현 ```python # skill-email 서비스에 추가 async def auto_refresh_token(user_id: str): """만료된 토큰 자동 갱신""" try: # 1. refresh_token으로 새 access_token 획득 response = await httpx.post( f"http://auth-server:9000/api/gmail/refresh/{user_id}" ) if response.status_code == 200: logger.info(f"Token refreshed for {user_id}") return True elif response.status_code == 401: # refresh_token도 만료 → 재인증 필요 await notify_reauth_needed(user_id) return False except Exception as e: logger.error(f"Token refresh failed: {e}") return False ``` --- ## 4. 실행 단계별 가이드 ### Step 1: 현재 상태 확인 (5분) ```bash # 1. 토큰 상태 확인 export PGPASSWORD=robeings psql -h localhost -U robeings -d main_db -c \ "SELECT u.username, gt.token_data IS NOT NULL as has_token, gt.is_equipped, gt.metadata->>'email' as email FROM users u LEFT JOIN gmail_tokens gt ON u.id = gt.user_id WHERE u.username IN ('happybell80', '0914eagle', 'cdctfm');" # 2. 크론잡 확인 docker exec robeing-gateway crontab -l | grep daily # 3. 최근 실패 로그 확인 ssh admin@192.168.219.52 "docker logs rb8001 --tail 100 | grep -E 'daily-summary|gmail|500'" 2>/dev/null || echo "직접 확인 필요" ``` ### Step 2: 테스트 계정 인증 (15분) ```bash # 1. OAuth URL 생성 (happybell80 계정) curl -s "http://localhost:9000/api/gmail/auth?user_id=1e16e9d5-59f3-54da-a661-8abeabff4230" | jq -r '.auth_url' # 2. 브라우저에서 URL 열고 Google 계정으로 로그인 # 3. 권한 승인 (이메일 읽기, 쓰기, 수정) # 4. 인증 성공 확인 psql -h localhost -U robeings -d main_db -c \ "SELECT token_data->'access_token' IS NOT NULL as success FROM gmail_tokens WHERE user_id = '1e16e9d5-59f3-54da-a661-8abeabff4230';" ``` ### Step 3: 브리핑 테스트 (10분) ```bash # 1. 수동 브리핑 실행 curl -X POST http://192.168.219.52:8001/api/cron/daily-summary \ -H "Content-Type: application/json" \ -H "Authorization: Bearer cron-secret-2024" # 2. 실시간 로그 모니터링 docker logs robeing-gateway -f 2>&1 | grep -E "daily|gmail|email" # 3. Slack DM 확인 # happybell80 사용자의 Slack DM에 브리핑이 도착했는지 확인 ``` ### Step 4: 다른 사용자 처리 (20분) ```bash # 0914eagle 계정 인증 curl -s "http://localhost:9000/api/gmail/auth?user_id=b6ea2ee0-a15a-5cf4-93a9-a9ca20d4c4a0" | jq -r '.auth_url' # cdctfm 계정 인증 curl -s "http://localhost:9000/api/gmail/auth?user_id=69ae4ea9-a15f-5110-9f5d-6568e380fcfb" | jq -r '.auth_url' ``` ### Step 5: 모니터링 설정 (10분) ```bash # 매일 8시 50분 토큰 상태 체크 크론 추가 cat << 'EOF' > /tmp/check_gmail_tokens.sh #!/bin/bash PGPASSWORD=robeings psql -h localhost -U robeings -d main_db -c \ "SELECT username, token_data IS NOT NULL as has_token FROM users u JOIN gmail_tokens gt ON u.id = gt.user_id;" | \ mail -s "Gmail Token Status" admin@ro-being.com EOF chmod +x /tmp/check_gmail_tokens.sh # 크론잡 등록 (선택사항) echo "50 8 * * * /tmp/check_gmail_tokens.sh" | crontab -l | crontab - ``` --- ## 5. 검증 체크리스트 - [ ] 모든 사용자의 token_data NOT NULL 확인 - [ ] 수동 브리핑 실행 시 이메일 포함 확인 - [ ] Slack DM으로 완전한 브리핑 수신 확인 - [ ] 내일 9시 자동 실행 모니터링 예약 --- ## 6. 관련 파일 및 참고 문서 ### 핵심 파일 - **auth-server**: `/home/admin/auth-server/app/providers/gmail_passport.py` - **게이트웨이 크론**: `/home/admin/robeing-gateway/app/crontab.py` - **rb8001 브리핑**: `rb8001/app/skills/dm_skill.py:384` (51124 서버) - **skill-email**: `skill-email/main.py` (51124 서버) ### 참고 문서 - [일일 브리핑 시퀀스 다이어그램](/home/admin/DOCS/300_architecture/sequences/daily_briefing_sequences.md) - [Gmail 토큰 자동 갱신](/home/admin/DOCS/troubleshooting/250821_gmail_token_auto_refresh.md) - [rb8001 크론 실패 분석](/home/admin/DOCS/troubleshooting/250824_rb8001_daily_summary_cron_failure.md) --- ## 7. 주의사항 ### ⚠️ 보안 관련 - 실제 토큰을 로그에 출력하지 말 것 - refresh_token은 절대 노출하지 말 것 - 테스트 후 불필요한 토큰은 삭제 ### ⚠️ 운영 관련 - 프로덕션 사용자의 토큰 직접 수정 지양 - 사용자에게 재인증 안내 우선 - 크론 실행 시간(9시) 피해서 테스트 --- ## 8. 문제 재발 방지 ### 단기 조치 1. 토큰 만료 알림 시스템 구축 2. 관리자 대시보드에 토큰 상태 표시 ### 장기 조치 1. refresh_token 자동 갱신 로직 구현 2. 토큰 만료 7일 전 사용자 알림 3. 프론트엔드 Gmail 연결 상태 표시 개선 --- ## 예상 소요 시간: 1시간 - Step 1-2: 20분 (기본 인증) - Step 3: 10분 (테스트) - Step 4: 20분 (추가 사용자) - Step 5: 10분 (모니터링)