diff --git a/.claude/agents/mail-backend-dev.md b/.claude/agents/mail-backend-dev.md new file mode 100644 index 00000000..3c9e0bb4 --- /dev/null +++ b/.claude/agents/mail-backend-dev.md @@ -0,0 +1,80 @@ +# mail-backend-dev + +## 핵심 역할 +zioinfo-mail 웹메일 시스템의 FastAPI 백엔드를 구현한다. 기존 Postfix(SMTP) + Dovecot(IMAP)와 연동하여 메일 읽기·쓰기·검색·폴더 관리 API를 제공한다. + +## 구현 범위 + +### API 엔드포인트 +``` +POST /auth/login → IMAP 인증 → JWT 발급 +POST /auth/logout → 세션 종료 + +GET /mail/folders → 폴더 목록 (INBOX, Sent, Drafts, Trash, Spam) +GET /mail/messages → 메일 목록 (폴더, 페이지, 검색) +GET /mail/messages/{uid} → 메일 상세 + 첨부파일 목록 +GET /mail/attachments/{uid}/{part} → 첨부파일 다운로드 + +POST /mail/send → 메일 발송 (SMTP) +POST /mail/draft → 임시저장 +PUT /mail/messages/{uid}/read → 읽음 처리 +PUT /mail/messages/{uid}/move → 폴더 이동 +DELETE /mail/messages/{uid} → 삭제 (Trash 이동) +DELETE /mail/messages/{uid}/force → 영구 삭제 + +GET /mail/search?q= → 전문 검색 (IMAP SEARCH) +``` + +### 기술 스택 +```python +# 핵심 의존성 +aioimaplib==0.9.2 # async IMAP4 클라이언트 +aiosmtplib==3.0.1 # async SMTP 클라이언트 +python-jose==3.3.0 # JWT +email-parser # 메일 파싱 +python-multipart # 첨부파일 업로드 +``` + +### IMAP 연결 설정 +```python +IMAP_HOST = "localhost" +IMAP_PORT = 993 # SSL +SMTP_HOST = "localhost" +SMTP_PORT = 587 # STARTTLS +SMTP_USER = "{user}@zioinfo.co.kr" +``` + +### 인증 방식 +- 사용자가 입력한 `user@zioinfo.co.kr` + 비밀번호로 IMAP 로그인 +- 성공 시 JWT 발급 (IMAP 자격증명을 암호화하여 토큰에 포함) +- 이후 모든 요청은 JWT에서 IMAP 자격증명 복호화하여 사용 + +### 메일 파싱 +- `email.parser` 표준 라이브러리 사용 +- HTML/텍스트 멀티파트 처리 +- 첨부파일: Content-Disposition 파싱, 인라인 이미지 CID 처리 +- 한글 인코딩: `chardet` + `email.header.decode_header` + +## 파일 구조 +``` +workspace/zioinfo-mail/backend/ +├── main.py # FastAPI 앱 (포트 8026) +├── auth.py # IMAP 인증 + JWT +├── imap_client.py # IMAP 연결 풀 + 메일 조회 +├── smtp_client.py # SMTP 메일 발송 +├── mail_parser.py # 메일 파싱 유틸 +├── models.py # Pydantic 스키마 +└── requirements.txt +``` + +## 보안 원칙 +1. IMAP 비밀번호는 JWT 페이로드에 AES 암호화 저장 +2. 첨부파일 경로 순회 방지 (`..` 차단) +3. HTML 메일 내 스크립트 태그 sanitize +4. CORS: 허용 origin을 `mail.zioinfo.co.kr`로 제한 + +## 팀 통신 프로토콜 +- **수신**: orchestrator로부터 구현 시작 신호 +- **발신**: mail-frontend-dev에게 API 스펙 (`_workspace/api-spec.md`) +- **발신**: mail-infra-setup에게 서비스 포트, systemd 파일 요청 +- **보고**: 완료 후 orchestrator에게 엔드포인트 목록 전달 diff --git a/.claude/agents/mail-frontend-dev.md b/.claude/agents/mail-frontend-dev.md new file mode 100644 index 00000000..8f7ee3ae --- /dev/null +++ b/.claude/agents/mail-frontend-dev.md @@ -0,0 +1,90 @@ +# mail-frontend-dev + +## 핵심 역할 +zioinfo-mail 웹메일 시스템의 React 18 SPA 프론트엔드를 구현한다. 깔끔한 3-패널 메일 클라이언트 UI (폴더 트리 + 메일 목록 + 메일 본문)를 구축한다. + +## UI 레이아웃 + +``` +┌──────────────────────────────────────────────────────────┐ +│ 🔵 zioinfo MAIL [검색창] [작성] [로그아웃] │ ← Header +├───────────┬──────────────────┬──────────────────────────┤ +│ 폴더트리 │ 메일 목록 │ 메일 본문 │ +│ │ │ │ +│ 📥 받은 │ ─ 보낸사람 ─ │ 제목: ... │ +│ 메함 │ 제목 미리보기 │ 보낸사람: ... │ +│ 📤 보낸 │ 날짜 · 크기 │ 받는사람: ... │ +│ 메함 │ │ ───────────────── │ +│ 📝 임시 │ [읽음][삭제] │ 본문 내용 │ +│ 보관함 │ [이동][스팸] │ │ +│ 🗑️ 휴지통 │ │ [첨부파일 목록] │ +│ ⚠️ 스팸 │ │ │ +└───────────┴──────────────────┴──────────────────────────┘ +``` + +## 화면 구성 + +### 주요 컴포넌트 +``` +src/ +├── App.tsx +├── pages/ +│ ├── Login.tsx # 로그인 (user@zioinfo.co.kr) +│ └── Mail.tsx # 메인 메일 클라이언트 +├── components/ +│ ├── FolderTree.tsx # 좌측 폴더 목록 + 안읽음 수 +│ ├── MailList.tsx # 중앙 메일 목록 + 페이지네이션 +│ ├── MailView.tsx # 우측 메일 본문 + 첨부파일 +│ ├── Compose.tsx # 작성/답장/전달 (모달) +│ └── SearchBar.tsx # 전문 검색 +├── api/ +│ └── mailApi.ts # axios 기반 API 클라이언트 +├── store/ +│ └── mailStore.ts # Zustand 상태 관리 +└── styles/ + └── mail.css # 메일 클라이언트 스타일 +``` + +### 핵심 기능 +- **3-패널 레이아웃**: 폴더/목록/본문 분할 뷰 +- **메일 작성**: To/CC/BCC, 에디터(기본 textarea), 첨부파일 드래그앤드롭 +- **답장/전달**: 인용 포함 자동 구성 +- **HTML 메일**: DOMPurify로 sanitize 후 iframe 렌더링 +- **페이지네이션**: 폴더당 50건 기본 +- **실시간 새 메일**: 30초 폴링 + +### 기술 스택 +```json +{ + "react": "^18", + "typescript": "^5", + "vite": "^5", + "axios": "^1", + "zustand": "^4", + "dompurify": "^3", + "date-fns": "^3" +} +``` + +### 디자인 원칙 +- 색상: 지오정보기술 브랜드 (#003366 딥블루, #00A0C8 스카이블루) +- 폰트: Pretendard (기존 시스템과 통일) +- 반응형: 모바일에서 2-패널 전환 (폴더 숨김) +- 다크모드 불필요 (라이트 전용) + +## 파일 구조 +``` +workspace/zioinfo-mail/frontend/ +├── index.html +├── package.json +├── vite.config.ts # outDir: '../dist' +├── tsconfig.json +└── src/ + └── ... +``` + +## 팀 통신 프로토콜 +- **수신**: mail-backend-dev로부터 `_workspace/api-spec.md` (API 스펙) +- **수신**: orchestrator로부터 구현 시작 신호 +- **발신**: orchestrator에게 빌드 완료 + dist 경로 보고 +- **협업**: API 스펙 불명확 시 mail-backend-dev에게 SendMessage로 질의 diff --git a/.claude/agents/mail-infra-setup.md b/.claude/agents/mail-infra-setup.md new file mode 100644 index 00000000..ea6e5aed --- /dev/null +++ b/.claude/agents/mail-infra-setup.md @@ -0,0 +1,114 @@ +# mail-infra-setup + +## 핵심 역할 +zioinfo-mail 웹메일 시스템의 서버 인프라를 구성한다. nginx 설정, systemd 서비스 등록, Postfix/Dovecot 연동 검증, Gitea 저장소 생성, 배포 파이프라인 연결을 담당한다. + +## 인프라 구성 목표 + +### 서비스 구조 +``` +Client → nginx:8025 (HTTPS) → FastAPI:8026 (backend API) + → /var/www/mail/ (React SPA) +``` + +### nginx 설정 (`/etc/nginx/sites-available/zioinfo-mail`) +```nginx +server { + listen 8025 ssl; + server_name mail.zioinfo.co.kr; + ssl_certificate /etc/letsencrypt/live/zioinfo.co.kr/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/zioinfo.co.kr/privkey.pem; + root /var/www/mail; + index index.html; + + location / { + try_files $uri $uri/ /index.html; + add_header Cache-Control no-cache; + } + location /api/ { + proxy_pass http://127.0.0.1:8026; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_read_timeout 60s; + } + location ~* \.(js|css|png|ico|woff2)$ { + expires 7d; + add_header Cache-Control "public, immutable"; + } +} +``` + +### systemd 서비스 (`/etc/systemd/system/zioinfo-mail.service`) +```ini +[Unit] +Description=ZioInfo Webmail Backend +After=network.target postfix.service dovecot.service + +[Service] +User=root +WorkingDirectory=/opt/mail/backend +ExecStart=/opt/mail/venv/bin/uvicorn main:app --host 127.0.0.1 --port 8026 --workers 2 +Restart=on-failure +RestartSec=5 +StandardOutput=append:/var/log/zioinfo/mail.log +StandardError=append:/var/log/zioinfo/mail.log + +[Install] +WantedBy=multi-user.target +``` + +## 구현 작업 목록 + +1. **Postfix/Dovecot 연동 검증** + - IMAP localhost:993 접속 테스트 (ythong@zioinfo.co.kr) + - SMTP localhost:587 발송 테스트 + +2. **서버 디렉토리 생성** + ```bash + mkdir -p /opt/mail/backend /opt/mail/venv /var/www/mail /var/log/zioinfo + ``` + +3. **Python venv + 패키지 설치** + ```bash + python3 -m venv /opt/mail/venv + /opt/mail/venv/bin/pip install -r requirements.txt + ``` + +4. **nginx 설정 등록 + 포트 오픈** + ```bash + ln -sf /etc/nginx/sites-available/zioinfo-mail /etc/nginx/sites-enabled/ + nginx -t && systemctl reload nginx + ufw allow 8025/tcp + ``` + +5. **systemd 등록 + 시작** + ```bash + systemctl daemon-reload + systemctl enable zioinfo-mail + systemctl start zioinfo-mail + ``` + +6. **Gitea 저장소 생성** (`zio/zioinfo-mail`) + - Gitea API: `POST /api/v1/user/repos` + +7. **deploy_server.py에 zioinfo-mail 배포 함수 추가** + - repo: `zioinfo-mail` + - 단계: git pull → npm build → copy dist → pip install → restart + +## 검증 체크리스트 +- [ ] `curl -f http://localhost:8026/health` → 200 +- [ ] `curl -f http://localhost:8025/` → 200 (nginx) +- [ ] IMAP 로그인 성공 (ythong@zioinfo.co.kr) +- [ ] SMTP 발송 성공 +- [ ] `systemctl is-active zioinfo-mail` → active + +## 접속 정보 +- 서버: 101.79.17.164 (root, paramiko) +- Gitea: `base64(zio:Zio@Admin2026!)` +- IMAP: localhost:993 (SSL) +- SMTP: localhost:587 (STARTTLS) + +## 팀 통신 프로토콜 +- **수신**: orchestrator로부터 "인프라 준비 시작" + backend/frontend 완료 신호 +- **발신**: orchestrator에게 포트/경로 확정 정보 전달 +- **발신**: deploy-server.py 업데이트 완료 보고 diff --git a/.claude/skills/zioinfo-mail-orchestrator/SKILL.md b/.claude/skills/zioinfo-mail-orchestrator/SKILL.md new file mode 100644 index 00000000..1eb91c06 --- /dev/null +++ b/.claude/skills/zioinfo-mail-orchestrator/SKILL.md @@ -0,0 +1,247 @@ +--- +name: zioinfo-mail-orchestrator +description: > + zioinfo-mail 웹메일 시스템 구축 오케스트레이터. + 지오정보기술 SMTP 서버(Postfix + Dovecot)를 활용한 React+FastAPI 웹메일 클라이언트를 + workspace/zioinfo-mail/ 에 구축하고 서버(mail.zioinfo.co.kr:8025)에 배포한다. + FastAPI IMAP/SMTP 프록시 백엔드, React 3-패널 메일 UI 프론트엔드, nginx+systemd 인프라를 + 에이전트 팀으로 병렬 구현한다. + 다음 상황에서 반드시 사용: + (1) 'webmail', '웹메일', 'zioinfo-mail', '메일 시스템 구축'; + (2) '메일 클라이언트', '이메일 UI', 'IMAP 연동', 'SMTP 연동'; + (3) mail.zioinfo.co.kr 관련 작업; + (4) 다시 실행, 업데이트, 수정, 보완. +--- + +# zioinfo-mail 웹메일 시스템 오케스트레이터 + +**실행 모드:** 하이브리드 +- Phase 1 (인프라 검증): 서브 에이전트 (mail-infra-setup) +- Phase 2 (Backend + Frontend 구현): **병렬 서브 에이전트** +- Phase 3 (통합 배포): 에이전트 팀 (3명 협업) + +--- + +## 시스템 개요 + +``` +사용자 브라우저 + ↓ HTTPS:8025 +nginx (/var/www/mail) → React SPA + ↓ /api/ +FastAPI (127.0.0.1:8026) + ↓ IMAP:993 (SSL) ↓ SMTP:587 (STARTTLS) + Dovecot Postfix + (읽기) (발송) +``` + +**기존 인프라:** +| 항목 | 값 | +|------|-----| +| Postfix | active, `mail.zioinfo.co.kr` | +| Dovecot | active, IMAP/POP3, maildir:~/Maildir | +| TLS cert | `/etc/ssl/guardia/server.crt` | +| 계정 | ythong / info / admin @zioinfo.co.kr | +| 웹메일 URL | `https://mail.zioinfo.co.kr:8025` | + +--- + +## Phase 0: 컨텍스트 확인 + +``` +workspace/zioinfo-mail/ 존재 여부: +- 없음 → 초기 구현 (Phase 1부터 전체) +- 있음 + backend/만 요청 → mail-backend-dev만 재실행 +- 있음 + frontend/만 요청 → mail-frontend-dev만 재실행 +- 있음 + 배포 요청 → Phase 3만 실행 +``` + +--- + +## Phase 1: 인프라 사전 검증 (서브 에이전트) + +**mail-infra-setup** 에이전트 실행 (읽기 전용 검증): + +```python +# 검증 항목 +1. IMAP localhost:993 접속 테스트 +2. SMTP localhost:587 접속 테스트 +3. 포트 8025/8026 사용 가능 여부 +4. /opt/mail/, /var/www/mail/ 생성 가능 여부 +5. Gitea zio/zioinfo-mail 저장소 존재 여부 +``` + +결과를 `_workspace/infra-check.json`에 저장. +IMAP/SMTP 접속 실패 시 → Postfix/Dovecot 설정 점검 후 재시도. + +--- + +## Phase 2: Backend + Frontend 병렬 구현 (서브 에이전트) + +**mail-backend-dev** + **mail-frontend-dev** 동시 실행: + +### mail-backend-dev 작업 목록 + +``` +workspace/zioinfo-mail/backend/ +├── main.py ← FastAPI 앱 (포트 8026) +├── auth.py ← IMAP 로그인 → JWT 발급 +├── imap_client.py ← aioimaplib 연결 풀 +├── smtp_client.py ← aiosmtplib 발송 +├── mail_parser.py ← 메일 파싱 (한글, 첨부파일) +├── models.py ← Pydantic 스키마 +└── requirements.txt +``` + +**핵심 구현 포인트:** +- JWT 페이로드에 IMAP 자격증명 AES 암호화 포함 +- IMAP 연결 풀: 사용자당 1개 재사용 +- 한글 제목/본문 인코딩: `chardet` + `email.header.decode_header` +- 첨부파일: `/tmp/mail_attach_{uid}/` 임시 저장 후 스트리밍 + +### mail-frontend-dev 작업 목록 + +``` +workspace/zioinfo-mail/frontend/ +├── package.json ← react, typescript, vite, axios, zustand, dompurify +├── vite.config.ts ← outDir: '../dist', proxy: /api → :8026 +├── src/ +│ ├── App.tsx +│ ├── pages/Login.tsx ← 로그인 +│ ├── pages/Mail.tsx ← 3-패널 메인 +│ ├── components/ +│ │ ├── FolderTree.tsx ← 좌측 폴더 트리 +│ │ ├── MailList.tsx ← 중앙 목록 +│ │ ├── MailView.tsx ← 우측 본문 +│ │ └── Compose.tsx ← 작성 모달 +│ ├── api/mailApi.ts ← axios 클라이언트 +│ └── store/mailStore.ts ← zustand +``` + +**Backend API 스펙** (`_workspace/api-spec.md` 참조): +- Backend 완료 후 API 스펙 파일 생성 → Frontend에서 읽어 구현 + +--- + +## Phase 3: 통합 배포 (에이전트 팀) + +3명 팀 구성: mail-backend-dev + mail-frontend-dev + mail-infra-setup + +### 3-1. Frontend 빌드 +```bash +cd workspace/zioinfo-mail/frontend && npm run build +# dist/ → workspace/zioinfo-mail/dist/ +``` + +### 3-2. 서버 업로드 (mail-infra-setup 담당) +```bash +# Backend: paramiko sftp → /opt/mail/backend/ +# Frontend dist: bundle → /var/www/mail/ +# Python venv: pip install requirements.txt +``` + +### 3-3. systemd + nginx 설정 +```bash +# /etc/systemd/system/zioinfo-mail.service 작성 +# /etc/nginx/sites-available/zioinfo-mail 작성 +# systemctl enable + start zioinfo-mail +# nginx -t && systemctl reload nginx +# ufw allow 8025/tcp +``` + +### 3-4. Gitea repo 생성 + push +```bash +# Gitea API로 zio/zioinfo-mail repo 생성 +# repos/zioinfo-mail/ 로컬 git init +# bundle → server → push +``` + +### 3-5. deploy_server.py에 zioinfo-mail 추가 +```python +# /opt/zioinfo/deploy_server.py에 zioinfo-mail 배포 함수 추가 +elif repo == "zioinfo-mail": + steps = [ + ("git pull", [...]), + ("npm build", [...]), + ("copy dist", ["bash", "-c", "cp -r {SRC}/dist/. /var/www/mail/"]), + ("pip install", [...]), + ("restart", ["systemctl", "restart", "zioinfo-mail"]), + ("health check", [...]), + ] +``` + +--- + +## Phase 4: 검증 + +```bash +# 1. 서비스 상태 +systemctl is-active zioinfo-mail +curl -f http://localhost:8026/health + +# 2. IMAP 로그인 테스트 +curl -X POST http://localhost:8026/auth/login \ + -d '{"username":"ythong@zioinfo.co.kr","password":"1q2w3e!Q"}' + +# 3. 메일 목록 조회 +curl http://localhost:8026/mail/messages \ + -H "Authorization: Bearer {token}" + +# 4. nginx 응답 +curl -f http://localhost:8025/ +``` + +--- + +## 데이터 흐름 + +``` +_workspace/ +├── infra-check.json ← Phase 1 결과 +├── api-spec.md ← Backend → Frontend 전달 +└── deploy-result.json ← Phase 3 결과 +``` + +--- + +## 에러 핸들링 + +| 에러 | 원인 | 처리 | +|------|------|------| +| IMAP 연결 실패 | Dovecot SSL 설정 | `/etc/ssl/guardia/server.crt` 확인 | +| SMTP 인증 실패 | SASL 설정 | `postconf smtpd_sasl_*` 확인 | +| npm build 실패 | node_modules 없음 | `npm ci` 재시도 | +| 포트 충돌 | 8026 이미 사용 | 8027로 변경 | +| HTML 메일 XSS | DOMPurify 미적용 | iframe sandbox 사용 | + +--- + +## 테스트 시나리오 + +**정상 흐름:** +1. `https://mail.zioinfo.co.kr:8025` 접속 +2. `ythong@zioinfo.co.kr` / `1q2w3e!Q` 로그인 +3. 받은메함 목록 표시 +4. 메일 클릭 → 본문 조회 +5. 작성 → To: `info@zioinfo.co.kr` → 발송 +6. 발신 계정 받은메함에서 수신 확인 + +**에러 흐름:** +- 잘못된 비밀번호 → 401 응답 + "인증 실패" 메시지 + +--- + +## should-trigger + +- "웹메일 만들어줘" +- "zioinfo-mail 구축" +- "메일 클라이언트 개발" +- "mail.zioinfo.co.kr 배포" +- "IMAP 연동 웹메일" +- "다시 실행", "수정", "보완" + +## should-NOT-trigger + +- "GUARDiA에서 메일 알림 보내줘" → guardia-orchestrator (ITSM 알림) +- "메일 서버 설정해줘" → Postfix/Dovecot 직접 설정 (인프라 작업) +- "홈페이지 문의 메일 연동" → homepage-cms-orchestrator diff --git a/.claude/skills/zioinfo-mail-orchestrator/references/backend-template.py b/.claude/skills/zioinfo-mail-orchestrator/references/backend-template.py new file mode 100644 index 00000000..6adb7829 --- /dev/null +++ b/.claude/skills/zioinfo-mail-orchestrator/references/backend-template.py @@ -0,0 +1,144 @@ +""" +zioinfo-mail FastAPI 백엔드 템플릿 +mail-backend-dev 에이전트가 이 파일을 기반으로 확장 구현한다. +""" +from fastapi import FastAPI, Depends, HTTPException, status +from fastapi.middleware.cors import CORSMiddleware +from fastapi.responses import StreamingResponse +from pydantic import BaseModel +from typing import Optional +import asyncio, aioimaplib, aiosmtplib +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText +from email.mime.base import MIMEBase +from email import encoders +import email.header, chardet, ssl +from jose import jwt +from cryptography.fernet import Fernet +import os, json, base64, hashlib +from datetime import datetime, timedelta + +app = FastAPI(title="zioinfo-mail API", version="1.0.0") +app.add_middleware(CORSMiddleware, + allow_origins=["https://mail.zioinfo.co.kr", "http://localhost:5173"], + allow_credentials=True, allow_methods=["*"], allow_headers=["*"]) + +# ── 설정 ──────────────────────────────────────────────────── +IMAP_HOST = "localhost"; IMAP_PORT = 993 +SMTP_HOST = "localhost"; SMTP_PORT = 587 +JWT_SECRET = os.getenv("MAIL_JWT_SECRET", "change-me-in-production") +JWT_EXPIRE_HOURS = 8 +# IMAP 자격증명 암호화 키 (32바이트 → Fernet) +FERNET_KEY = os.getenv("MAIL_FERNET_KEY", + base64.urlsafe_b64encode(hashlib.sha256(JWT_SECRET.encode()).digest())) +fernet = Fernet(FERNET_KEY) + +# ── 모델 ──────────────────────────────────────────────────── +class LoginRequest(BaseModel): + username: str # user@zioinfo.co.kr + password: str + +class SendRequest(BaseModel): + to: str; cc: Optional[str] = None; bcc: Optional[str] = None + subject: str; body: str; html: bool = False + reply_to_uid: Optional[str] = None + +# ── 인증 ──────────────────────────────────────────────────── +async def verify_imap(username: str, password: str) -> bool: + """IMAP 로그인으로 자격증명 검증""" + try: + ssl_ctx = ssl.create_default_context() + ssl_ctx.check_hostname = False + ssl_ctx.verify_mode = ssl.CERT_NONE + imap = aioimaplib.IMAP4_SSL(IMAP_HOST, IMAP_PORT, ssl_context=ssl_ctx) + await imap.wait_hello_from_server() + res, _ = await imap.login(username, password) + await imap.logout() + return res == "OK" + except Exception: + return False + +def create_token(username: str, password: str) -> str: + enc_pw = fernet.encrypt(password.encode()).decode() + payload = { + "sub": username, + "pw": enc_pw, + "exp": datetime.utcnow() + timedelta(hours=JWT_EXPIRE_HOURS) + } + return jwt.encode(payload, JWT_SECRET, algorithm="HS256") + +def get_credentials(token: str = Depends(lambda: None)) -> tuple[str, str]: + from fastapi import Header + # FastAPI security dependency - 실제 구현에서 Bearer 헤더에서 추출 + try: + payload = jwt.decode(token, JWT_SECRET, algorithms=["HS256"]) + username = payload["sub"] + password = fernet.decrypt(payload["pw"].encode()).decode() + return username, password + except Exception: + raise HTTPException(status.HTTP_401_UNAUTHORIZED) + +# ── 엔드포인트 ─────────────────────────────────────────────── +@app.get("/health") +async def health(): + return {"status": "ok", "service": "zioinfo-mail"} + +@app.post("/auth/login") +async def login(req: LoginRequest): + if not await verify_imap(req.username, req.password): + raise HTTPException(status.HTTP_401_UNAUTHORIZED, "인증 실패") + token = create_token(req.username, req.password) + return {"access_token": token, "token_type": "bearer", "username": req.username} + +# ── 메일 파싱 유틸 ─────────────────────────────────────────── +def decode_header_str(raw: str) -> str: + """RFC2047 인코딩된 헤더 디코딩 (한글 포함)""" + parts = email.header.decode_header(raw or "") + result = [] + for part, charset in parts: + if isinstance(part, bytes): + charset = charset or chardet.detect(part).get('encoding', 'utf-8') or 'utf-8' + result.append(part.decode(charset, errors='replace')) + else: + result.append(part) + return "".join(result) + +def parse_message(msg) -> dict: + """email.message.Message → dict""" + body_text = body_html = "" + attachments = [] + if msg.is_multipart(): + for part in msg.walk(): + ct = part.get_content_type() + cd = str(part.get('Content-Disposition', '')) + if ct == 'text/plain' and 'attachment' not in cd: + body_text = _decode_payload(part) + elif ct == 'text/html' and 'attachment' not in cd: + body_html = _decode_payload(part) + elif 'attachment' in cd or part.get_filename(): + attachments.append({ + "filename": decode_header_str(part.get_filename() or "unnamed"), + "content_type": ct, + "size": len(part.get_payload(decode=True) or b""), + }) + else: + ct = msg.get_content_type() + if ct == 'text/html': + body_html = _decode_payload(msg) + else: + body_text = _decode_payload(msg) + return { + "subject": decode_header_str(msg.get("Subject", "")), + "from": decode_header_str(msg.get("From", "")), + "to": decode_header_str(msg.get("To", "")), + "cc": decode_header_str(msg.get("Cc", "")), + "date": msg.get("Date", ""), + "body_text": body_text, + "body_html": body_html, + "attachments": attachments, + } + +def _decode_payload(part) -> str: + raw = part.get_payload(decode=True) or b"" + charset = part.get_content_charset() or chardet.detect(raw).get('encoding', 'utf-8') or 'utf-8' + return raw.decode(charset, errors='replace') diff --git a/CLAUDE.md b/CLAUDE.md index 4d25b59a..8cdd9b9d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -279,6 +279,23 @@ GUARDiA ITSM (허브, :9001/:8443) --- +## 하네스: zioinfo-mail 웹메일 시스템 + +**목표:** 지오정보기술 Postfix/Dovecot SMTP 서버를 활용한 React+FastAPI 웹메일 클라이언트 구축. `mail.zioinfo.co.kr:8025`, IMAP/SMTP 연동, 3-패널 메일 UI. + +**트리거:** 웹메일, zioinfo-mail, 메일 클라이언트, IMAP/SMTP 연동 요청 시 `zioinfo-mail-orchestrator` 스킬을 사용하라. + +**에이전트:** mail-backend-dev (FastAPI IMAP/SMTP 프록시), mail-frontend-dev (React SPA), mail-infra-setup (nginx/systemd/배포) + +**인프라:** Postfix(25/587) + Dovecot(143/993) 기운영 중 | 계정: ythong/info/admin @zioinfo.co.kr + +**변경 이력:** +| 날짜 | 변경 내용 | 대상 | 사유 | +|------|----------|------|------| +| 2026-06-01 | 초기 하네스 구성 | 전체 | zioinfo SMTP 기반 웹메일 구축 | + +--- + ## 하네스: 5개 시스템 배포 동기화 **목표:** guardia-itsm·zioinfo-web·guardia-manager·guardia-messenger·guardia-docs 5개 시스템의 workspace↔repos↔Gitea↔서버 4-way 동기화 상태를 검증하고 이슈를 자동 수정한다.