ZUKU API — Captcha
자체 호스팅 Proof-of-Work 캡차입니다. 외부 위젯(reCAPTCHA 등) 없이 CAPTCHA_HMAC_SECRET 기반 HMAC으로 챌린지를 발급·검증합니다.
가입·로그인 게이트와 디버그 검증 API를 포함합니다. 구현: backend/rs/src/captcha.rs, router.rs.
Base URL: https://zuzunza.com/api/v1 (로컬 http://localhost:3001/api/v1)
목차
1. 개요 · 환경 변수
| 변수 | 기본 | 설명 |
|---|---|---|
CAPTCHA_HMAC_SECRET |
(빈 문자열) | 필수(프로덕션). 비어 있으면 challenge/verify는 503 fail-closed |
CAPTCHA_MAX_NUMBER |
100000 |
PoW 탐색 상한(0..=maxnumber) |
CAPTCHA_TTL_SECS |
300 |
salt에 심는 만료(초) |
CAPTCHA_DEV_BYPASS |
off | 1이면 로컬 호스트 + 고정 바이패스 토큰 허용 |
프로토콜(레거시 servgate와 호환):
- 서버가
salt,challenge=hex(SHA-256(salt‖number)),signature=hex(HMAC-SHA256(secret, challenge))발급 - 클라이언트가
0..=maxnumber에서challenge와 일치하는number를 탐색 base64(JSON{algorithm,challenge,number,salt,signature})토큰을 제출
내부 필터/샌드박스와 무관합니다. 캡차는 인증 남용 완화용입니다.
2. Challenge
POST /captcha/challenge
인증 불필요. 본문 없음.
성공 200 — data:
{
"algorithm": "SHA-256",
"challenge": "a1b2…",
"salt": "zzcp_<hex>.<expiry_unix>",
"signature": "…",
"maxnumber": 100000
}
| HTTP | code | 조건 |
|---|---|---|
| 503 | CAPTCHA_NOT_CONFIGURED |
CAPTCHA_HMAC_SECRET 미설정 |
| 405 | — | GET 등 비허용 메서드 |
3. Verify
POST /captcha/verify
서버-투-서버·디버그용. 가입/로그인 게이트는 본문 필드 zcaptcha_token으로 동일 검증기를 씁니다.
요청
{
"token": "<solved-base64-token>",
"dev_host": "localhost"
}
dev_host는 CAPTCHA_DEV_BYPASS=1일 때만 의미가 있습니다.
성공 200 — data: { "success": true }
| HTTP | code | 조건 |
|---|---|---|
| 503 | CAPTCHA_NOT_CONFIGURED |
시크릿 없음 |
| 403 | CAPTCHA_FAILED |
서명·만료·number·재사용 실패 |
| 400 | BAD_REQUEST |
JSON 파싱 실패 등 |
4. Auth 게이트 (zcaptcha_token)
POST /auth/register · POST /auth/signup · POST /auth/login 공통:
| 서버 상태 | 동작 |
|---|---|
| 시크릿 없음 | 게이트 비활성 — 가입/로그인 막지 않음 |
| 시크릿 있음 | 본문 zcaptcha_token 필수. 실패 시 403 CAPTCHA_FAILED |
CAPTCHA_DEV_BYPASS=1 + 허용 토큰/호스트 |
개발 우회 |
개발 바이패스 토큰(고정): __zuzunza_captcha_localhost_bypass__
(프로덕션에서 CAPTCHA_DEV_BYPASS를 켜지 마세요.)
자세한 가입/로그인 필드는 Authentication.
5. 클라이언트 풀이 흐름
POST /captcha/challenge
│
▼
worker: for n in 0..=maxnumber
if sha256(salt + n) == challenge → found
│
▼
token = btoa(JSON.stringify({ algorithm, challenge, number, salt, signature }))
│
▼
POST /auth/register { …, zcaptcha_token: token }
권장:
- UI 스레드를 막지 않도록 Web Worker에서 탐색
- 실패 시 새 challenge를 다시 받고 재시도(만료·replay)
X-Request-ID로 문의 추적용 상관관계 유지
6. curl · JS/TS 예제
curl — challenge
curl -sS -X POST "https://zuzunza.com/api/v1/captcha/challenge" \
-H "Accept-Language: ko-KR" \
-H "X-Request-Id: $(uuidgen)"
curl — verify
curl -sS -X POST "https://zuzunza.com/api/v1/captcha/verify" \
-H "Content-Type: application/json" \
-d '{"token":"'"$TOKEN"'"}'
JS/TS — challenge → register
const API = "https://zuzunza.com/api/v1";
type Challenge = {
algorithm: string;
challenge: string;
salt: string;
signature: string;
maxnumber: number;
};
async function fetchChallenge(): Promise<Challenge> {
const res = await fetch(`${API}/captcha/challenge`, { method: "POST" });
const env = await res.json();
if (!env.success) throw new Error(`${env.error.code}: ${env.error.message}`);
return env.data as Challenge;
}
/** 데모용 동기 풀이 — 프로덕션은 Worker로 이전하세요. */
function solvePoW(c: Challenge): string {
// 실제 구현은 SHA-256(salt + String(n)) hex === c.challenge 일 때까지 n 증가
throw new Error("implement PoW solver (see captcha.rs protocol)");
}
async function registerWithCaptcha(input: {
email: string;
password: string;
handle: string;
}) {
const challenge = await fetchChallenge();
const zcaptcha_token = solvePoW(challenge);
const res = await fetch(`${API}/auth/register`, {
method: "POST",
headers: { "Content-Type": "application/json", "Accept-Language": "ko-KR" },
body: JSON.stringify({ ...input, password_confirm: input.password, zcaptcha_token }),
});
const env = await res.json();
if (!env.success) throw new Error(`${env.error.code}: ${env.error.message}`);
return env.data;
}
더 많은 end-to-end 시나리오는 Examples.
7. 오류 코드
| code | HTTP | 의미 |
|---|---|---|
CAPTCHA_NOT_CONFIGURED |
503 | HMAC 시크릿 미설정(challenge/verify) |
CAPTCHA_FAILED |
403 | 토큰 검증 실패 또는 게이트 거절 |
BAD_REQUEST |
400 | 본문 형식 오류 |
8. 관련 문서
- Authentication — register/login 필드
- Errors — 공통 봉투
- Examples — 통합 시나리오
- 사이트: /docs/api/captcha