Files
AriWeb-Suite/modules/arimail/docs/USAGE-API.md
T

92 lines
9.0 KiB
Markdown

# 메일 사용량 정보 제공 API (1.0.9)
웹메이커(WebHosting_AI_agent) 같은 외부 관리 시스템이 AriMail 의 **디스크 사용량·메일 수·발송량**을 도메인·계정 단위로 읽어 통합 관리할 수 있게 하는 읽기 전용 API 입니다. 구현은 `server/usage.mjs`.
## 1. 켜기 — 키 만들기
1. 전체 설정 화면(기본 도메인의 전용 포트 `https://<PUBLIC_HOST>:8791/?token=…`, 또는 관리자 페이지 › 메일 서비스 설정 `/setup`)의 **사용량 API** 카드에서 **키 만들기**를 누릅니다.
2. 키(`au_…`, 32바이트 난수)는 **그때 한 번만** 보입니다. 웹메이커의 설정에 복사해 두세요. 설정 화면에는 이후 앞뒤 일부만 보입니다.
3. (권장) 같은 카드의 **접속 허용 IP** 에 웹메이커 서버의 IP 또는 CIDR 을 적고 저장합니다(쉼표 구분, 예 `203.0.113.10, 10.0.0.0/8`). 비우면 키만 맞으면 어디서나 허용합니다.
4. 키를 바꾸려면 **키 다시 만들기**(기존 키는 즉시 무효), 끄려면 **키 삭제**. 키는 기본 도메인의 설정 파일(`data/config.json` 의 `USAGE_API_KEY`)에만 저장되고 서버 하나에 하나입니다(모든 도메인을 한 키로 봄).
API 로도 할 수 있습니다(전용 포트의 토큰 또는 관리자 세션): `POST /api/setup/usage-key {"action":"generate"}` → `{ ok, key, endpoint }`, `{"action":"revoke"}` → `{ ok, enabled:false }`. 허용 IP 는 `PUT /api/setup/config {"values":{"USAGE_API_ALLOW":"…"}}`.
## 2. 부르기
```
GET https://<기본 도메인 호스트>/api/usage 모든 도메인 요약
GET https://<기본 도메인 호스트>/api/usage/<도메인> 도메인 하나 상세(계정 목록·일별 추이 포함)
헤더: Authorization: Bearer <키> (또는 X-Usage-Key: <키>)
쿼리: days=30 일별 추이·계정별 기간 합계의 기간(1~366, 기본 30)
accounts=1 계정 목록 포함(요약은 기본 0, 상세는 기본 1)
series=1 일별 추이 포함(요약은 기본 0, 상세는 기본 1)
```
어느 등록 호스트로 불러도 같습니다(예 `https://webmail.ariweb.net/api/usage`). 경로 접두사 아래의 도메인(`site.example.com/mail`)은 `https://site.example.com/mail/api/usage`.
```bash
curl -s -H 'Authorization: Bearer au_…' 'https://mail.arihakdang.com/api/usage?days=30' | jq .totals
curl -s -H 'Authorization: Bearer au_…' 'https://mail.arihakdang.com/api/usage/ariweb.net?days=7'
```
## 3. 응답
전체 요약(`/api/usage`):
```json
{ "ok": true, "version": "1.0.9", "generatedAt": "…", "primary": "arihakdang.com",
"totals": { "domains": 2, "accounts": 5, "storageBytes": 12345678, "largeFileBytes": 0, "messages": 420, "sentMonth": 31, "receivedMonth": 88 },
"domains": [ { …도메인 보고서(아래, accountList·daily 없음)… } ] }
```
도메인 보고서(`/api/usage/<도메인>` 또는 `domains[]` 의 항목):
| 필드 | 뜻 |
|---|---|
| `domain`, `primary`, `enabled`, `hosts`, `authMode`(local/www/webmaker), `mailbox`, `inbound`, `outbound`, `timezone` | 도메인 정보. `enabled:false` 는 중지한 도메인 |
| `accounts` | `{ local, external, total, limit, percent, externalSource }` — 게이트웨이 계정 수, 외부(웹메이커) 계정 수, 합계, 시스템 제한(0 = 없음) |
| `storage` | `{ bytes, limit, percent, largeFiles:{count, bytes, sent} }` — 모든 메일함의 사용량(MIME 기준 원본 크기 합), 시스템 제한, 대용량 첨부파일(별도 저장) |
| `messages` | `{ total, inbound, outbound, drafts, failed }` — 저장된 메일 수(휴지통 포함, 영구 삭제 제외) |
| `traffic` | `{ day, week, month }` 각각 `{ sent, received, total, sentBytes, receivedBytes, start, end }` — 통계 시간대(`STATS_TIMEZONE`, 기본 Asia/Seoul) 기준 오늘·이번 주(월~일)·이달. `monthlyLimit`, `monthlyPercent` 는 월간 통수 제한 대비 |
| `period` | `{ days, start, end }` — 아래 `daily`·계정별 기간 합계의 범위(`daily` 또는 `accountList` 를 포함할 때만 있음) |
| `daily[]` | `{ date:"YYYY-MM-DD", sent, received, sentBytes, receivedBytes }` 오래된 날부터 `days` 개 |
| `accountList[]` | `{ login, name, mailbox, role, source, status, messages, bytes, limit, percent, lastLoginAt, sent, received, sentBytes, receivedBytes }` — 계정(메일함)마다. `source` 는 local(게이트웨이 계정) / www(연결된 웹 서비스 관리자) / webmaker(웹메이커 계정) / orphan(계정은 지웠지만 메일이 남은 메일함). `sent`·`received`·`*Bytes` 는 `period` 안의 합계, `bytes`·`limit` 은 현재 사용량과 적용 중인 최대 크기(0 = 없음) |
| `limits` | `{ system, admin }` — 설치 시 시스템 제한(`LIMIT_*`)과 관리자 페이지의 제한 값(안내용) |
"발신" 은 발송이 끝난 메일(`state='sent'`, 발송 시각 기준), "수신" 은 받은 시각 기준입니다. 바이트는 저장된 원본(.eml) 크기라 MIME 변환으로 늘어난 실제 크기입니다. 메일 본문·주소록·비밀번호·API 키는 어떤 응답에도 들어가지 않습니다.
오류: `usage_api_disabled`(404, 키를 만들지 않음) · `unauthenticated`(401) · `forbidden_ip`(403) · `rate_limited`(429) · `domain_not_found`(404) · `method_not_allowed`(405). 모든 응답은 `Cache-Control: no-store`. 전체 요약에서 어느 도메인의 조회가 실패하면 그 항목만 `{ domain, error:'server_error' }` 로 표시되고 합계에서 빠집니다. `accounts.externalSource.error` 에는 웹메이커 파일을 읽지 못한 사유(파일 경로 포함)가 그대로 들어갑니다.
## 4. 보안 대책 — 웹메이커의 해당 기능에서만 접근
| 장치 | 내용 |
|---|---|
| 비밀 키 | 32바이트 난수(`au_…`). 전체 설정 화면에서만 만들고 한 번만 표시, 비교는 상수 시간. 키가 없으면 API 자체가 404 |
| 접속 IP 제한 | `USAGE_API_ALLOW`(IP/CIDR 목록)에 웹메이커 서버만 적어 두면 키가 유출돼도 다른 곳에서는 403. 접속 IP 는 클라이언트가 넣을 수 있는 헤더가 아니라 nginx 가 보증하는 값 — `X-Real-IP`(1.0.9 템플릿) 또는 `X-Forwarded-For` 의 **마지막** 주소(nginx 가 덧붙인 실제 접속 주소) — 로 정합니다. Cloudflare 프록시 뒤라면 실제 접속 주소가 Cloudflare 이므로 환경 변수 `USAGE_TRUST_CF=1` 을 두어 `CF-Connecting-IP` 를 믿게 하거나(원본 서버 접근을 Cloudflare IP 로 제한한 경우만) IP 제한 대신 키만 쓰세요. 저장 때 항목 형식을 검사해 오타가 있으면 422 로 거부합니다 |
| 시도 제한 | 키가 틀린 요청 IP 마다 15분 10회(그 뒤 429), 성공 요청도 IP 마다 1분 120회 |
| 읽기 전용·최소 정보 | GET 만, 수(통·바이트)·제한·계정 이름만. 쓰기 동작 없음. 세션 쿠키와 무관(쿠키로는 못 부름) |
| 기록 | 성공 호출은 감사 기록(`audit_log`, actor `usage-api`), 거부(틀린 키·허용 밖 IP·빈도 초과)는 서버 로그에 IP·사유와 함께 남음 |
| 키를 만들 수 있는 사람 | 전체 설정 화면(전용 포트 토큰) 또는 기본 도메인 관리자 세션. 기본 도메인 관리자는 모든 도메인의 계정 목록·사용량을 볼 수 있는 키를 만들 수 있으므로 운영자만 두세요 |
| 전송 | HTTPS(nginx) 로만 노출. 키 교체·삭제는 즉시 적용(재시작 없음) |
웹메이커 쪽에는 키를 비밀 보관소(`lib/vault.js`, 예: 종류 `arimail-usage`)에 두고 서버 간 호출에만 쓰세요(브라우저에 내려보내지 않음).
## 5. 웹메이커에서 쓰는 예(Node, `lib/connectors/arimail.js` 에 더할 수 있는 모양)
```js
// 설정: mail.url(= AriMail 공개 주소), 보관소 'arimail-usage' 에 키
async function usage(domain = '', { days = 30 } = {}) {
const base = String(config.get('mail.url') || '').replace(/\/+$/, '');
const key = vault.get('arimail-usage'); if (!base || !key) throw new Error('AriMail 사용량 API 주소/키 없음');
const r = await fetch(`${base}/api/usage${domain ? '/' + encodeURIComponent(domain) : ''}?days=${days}`, { headers: { Authorization: 'Bearer ' + key }, signal: AbortSignal.timeout(20000) });
const d = await r.json().catch(() => ({}));
if (!r.ok) throw new Error(`AriMail 사용량 API ${r.status} ${d.error || ''}`);
return d; // 전체: d.totals / d.domains[] 도메인 하나: d.storage / d.traffic / d.accountList …
}
// 예: 사이트(site.ariweb.net)의 디스크 사용량과 이달 발송량
const u = await usage('site.ariweb.net', { days: 30 });
console.log(u.storage.bytes, u.traffic.month.sent, u.accountList.map((a) => [a.mailbox, a.bytes, a.sent]));
```
웹메이커의 관리자 콘솔 › 메일 시스템 화면에서 도메인마다 `storage.bytes`(디스크), `traffic.month.sent`(이달 발송), `accounts.total`(계정 수)을 보여 주고, 사용자(사이트 관리자) 화면에는 `accountList` 에서 그 사용자의 `mailbox` 행만 골라 보여 주면 됩니다. 1분에 120회 제한이 있으므로 화면마다 호출하지 말고 1~5분 캐시를 두는 편이 좋습니다.