ZUKU API — Authentication

계정 가입·로그인·세션·캡차·개발자 API 키 계약입니다. 본 문서는 backend/rs/src/router.rs의 실제 경로와 일치합니다.

Base URL

환경 Base URL
프로덕션 https://zuzunza.com/api/v1
로컬 http://localhost:3001/api/v1

목차

  1. 인증 모델
  2. 응답 봉투
  3. 엔드포인트 표
  4. Captcha
  5. Register / Signup
  6. Login
  7. Logout / Refresh
  8. Me (프로필)
  9. Sessions
  10. OAuth
  11. Developer API Keys
  12. curl · JS/TS 예제
  13. 오류 코드 요약

1. 인증 모델

ZUKU API는 두 가지 자격 증명을 사용합니다.

방식 헤더 용도
Bearer session Authorization: Bearer <access_token> 로그인 세션. 대부분의 쓰기 API·프로필·세션·개발자 키 관리에 필요
X-API-Key X-API-Key: sk_live_… 개발자 포털에서 발급한 키. sk_live_ 접두사 필수. Bearer가 없을 때 일부 서버-투-서버 경로(예: 콘텐츠 생성)에서 발급자를 해석

규칙 요약:

  • 세션 토큰은 POST /auth/login 또는 POST /auth/register 응답의 tokens.access_token / tokens.refresh_token입니다.
  • token_type은 항상 "Bearer"입니다.
  • 개발자 키 원문(key)은 발급 직후 한 번만 내려갑니다. 이후 GET /developer/keys에는 메타데이터만 있습니다.
  • 소셜·알림·DM·세션 관리 등 본 문서의 대부분 경로는 Bearer 세션만 허용합니다 (require_authenticated_user).

2. 응답 봉투

성공:

{
  "success": true,
  "data": { },
  "meta": {
    "request_id": "…",
    "timestamp": "2026-08-22T04:00:00Z",
    "version": "v1"
  }
}

실패:

{
  "success": false,
  "error": {
    "code": "UNAUTHORIZED",
    "message": "…",
    "details": [{ "field": "password", "message": "…" }]
  },
  "meta": { "request_id": "…", "timestamp": "…", "version": "v1" }
}

details는 검증 오류(VALIDATION_ERROR) 등에서만 포함됩니다.

일부 성공 응답은 본문 없이 204 No Content입니다(로그아웃, 세션 폐기, API 키 삭제).


3. 엔드포인트 표

Method Path Auth 설명
POST /auth/register 없음† 회원가입
POST /auth/signup 없음† /auth/register와 동일 핸들러(별칭)
POST /auth/login 없음† 로그인 · 세션 발급
POST /auth/logout Bearer 현재 access 세션 폐기 → 204
POST /auth/refresh 없음(본문 토큰) access 토큰 회전
GET /auth/me Bearer 현재 사용자
PATCH /auth/me Bearer 프로필 부분 수정
GET /auth/sessions Bearer 활성 세션 목록
DELETE /auth/sessions/{id} Bearer 세션 하나 폐기 → 204
POST /auth/oauth/{provider} 501 NOT_IMPLEMENTED
POST /captcha/challenge 없음 PoW 챌린지 발급
POST /captcha/verify 없음 토큰 검증(서버·디버그)
POST /developer/keys Bearer API 키 발급 → 201
GET /developer/keys Bearer 내 키 목록
DELETE /developer/keys/{id} Bearer 키 폐기 → 204

CAPTCHA_HMAC_SECRET이 설정된 환경에서는 register/login 본문에 유효한 zcaptcha_token이 필요합니다.


4. Captcha

캡차 프로토콜·환경 변수·풀이 흐름은 Captcha에 자세히 있습니다.

POST /captcha/challenge

  • 503 CAPTCHA_NOT_CONFIGUREDCAPTCHA_HMAC_SECRET 미설정
  • 성공 시 data 예:
{
  "algorithm": "SHA-256",
  "challenge": "…",
  "salt": "…",
  "signature": "…",
  "maxnumber": 100000
}

POST /captcha/verify

요청:

{ "token": "<solved-token>", "dev_host": "localhost" }
  • 503 CAPTCHA_NOT_CONFIGURED
  • 403 CAPTCHA_FAILED — 토큰 무효
  • 성공: { "success": true } (봉투의 data 안)

register / login 게이트 (zcaptcha_token)

서버 설정 동작
CAPTCHA_HMAC_SECRET 없음 게이트 비활성(통과). 가입/로그인을 막지 않음
CAPTCHA_HMAC_SECRET 있음 본문 zcaptcha_token 필수. 실패 시 403 CAPTCHA_FAILED
CAPTCHA_DEV_BYPASS=1 + 허용 토큰/호스트 개발 우회 가능

5. Register / Signup

POST /auth/registerPOST /auth/signup

요청 본문

필드 필수 규칙
email @ 포함
password 최소 8자
password_confirm password와 일치
handle 비어 있으면 안 됨
display_name 선택 없으면 handle을 표시 이름으로 사용
zcaptcha_token 조건부 시크릿 설정 시 필수

성공 201

data.user + data.tokens (access_token, refresh_token, token_type, expires_in)

주요 오류

HTTP code 상황
409 ALREADY_AUTHENTICATED 유효한 Bearer로 이미 로그인한 채 가입 시도
409 EMAIL_EXISTS 이메일 중복
409 LEGACY_ACCOUNT_EXISTS 주전자닷컴 레거시에 동일 email/handle 존재 → 로그인으로 안내
409 HANDLE_EXISTS handle 중복(또는 DB 중복)
422 VALIDATION_ERROR 비밀번호·이메일·handle 검증 실패
403 CAPTCHA_FAILED 캡차 게이트 실패

6. Login

POST /auth/login

요청 본문

필드 필수 설명
identifier 또는 email ✅ (둘 중 하나) identifier 우선. 이메일·아이디·닉네임
password
device_id 선택 세션 메타
zcaptcha_token 조건부 캡차 시크릿 설정 시

서버는 (1) zuku bcrypt 계정 → (2) 레거시 계정 연동 순으로 시도합니다. 레거시 성공 시 zuku 행으로 링크 후 bcrypt 재해시합니다.

성공 200

data.user + data.tokens

주요 오류

HTTP code
401 INVALID_CREDENTIALS
403 ACCOUNT_SUSPENDED
403 CAPTCHA_FAILED

7. Logout / Refresh

POST /auth/logout

  • 헤더: Authorization: Bearer <access_token>
  • 성공: 204 (본문 없음)
  • 헤더 없음/형식 오류: 401 UNAUTHORIZED

POST /auth/refresh

{ "refresh_token": "…" }
  • 성공: data.tokens (새 access_token, 기존 refresh_token 유지, expires_in ≈ 7일)
  • 실패: 401 INVALID_REFRESH_TOKEN

8. Me (프로필)

GET /auth/me

Bearer 필수. data.user 반환.

공개 사용자 필드 예: id(usr_…), email, display_name, handle, role, avatar_url, is_verified, created_at, profile_completion_status, bio, website_url, location, cover_url, banner_tone

PATCH /auth/me

전달된 필드만 갱신. 허용 필드: display_name, bio, website_url, location, banner_tone, cover_url, avatar_url

banner_tonecyan | swipe | jump | slate 중 하나.


9. Sessions

중복 로그인 시 강제 전체 로그아웃 대신, 사용자가 낯선 세션만 끊을 수 있습니다.

GET /auth/sessions

data.sessions[]: id, device_label, created_at, last_seen_at, is_current

토큰 원문은 절대 내려주지 않습니다.

DELETE /auth/sessions/{id}

  • {id}는 숫자 세션 id
  • 본인 소유만 폐기. 없거나 타인 → 404
  • 성공: 204

10. OAuth

POST /auth/oauth/{provider}

항상 501 Not Implemented, code NOT_IMPLEMENTED.

스테이징에서 provider client secret 미발급으로 스코프 아웃된 상태입니다.


11. Developer API Keys

모두 Bearer 필수.

POST /developer/keys

{ "name": "CI bot" }
  • name: 1~50자
  • 201: data.api_key(메타) + data.key(평문, 한 번만)

GET /developer/keys

data.api_keys 배열

DELETE /developer/keys/{id}

성공 204. 없거나 타인 → 404 API_KEY_NOT_FOUND

발급 키는 X-API-Key: sk_live_… 형태로 일부 API에서 Bearer 대신 사용할 수 있습니다.


12. curl · JS/TS 예제

아래 예는 로컬 Base를 사용합니다. 프로덕션은 https://zuzunza.com/api/v1로 바꾸면 됩니다.

Register

curl -sS -X POST "http://localhost:3001/api/v1/auth/register" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "[email protected]",
    "password": "password1",
    "password_confirm": "password1",
    "handle": "demo_user",
    "zcaptcha_token": "OPTIONAL_WHEN_CAPTCHA_SET"
  }'
const base = "http://localhost:3001/api/v1";

const registerRes = await fetch(`${base}/auth/register`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    email: "[email protected]",
    password: "password1",
    password_confirm: "password1",
    handle: "demo_user",
    // CAPTCHA_HMAC_SECRET 설정 시:
    // zcaptcha_token: solvedToken,
  }),
});
const registerJson = await registerRes.json();
const { access_token, refresh_token } = registerJson.data.tokens;

Login

curl -sS -X POST "http://localhost:3001/api/v1/auth/login" \
  -H "Content-Type: application/json" \
  -d '{
    "identifier": "[email protected]",
    "password": "password1"
  }'
const loginRes = await fetch(`${base}/auth/login`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    identifier: "[email protected]",
    password: "password1",
  }),
});
const loginJson = await loginRes.json();
const accessToken = loginJson.data.tokens.access_token;
const refreshToken = loginJson.data.tokens.refresh_token;

Refresh

curl -sS -X POST "http://localhost:3001/api/v1/auth/refresh" \
  -H "Content-Type: application/json" \
  -d "{\"refresh_token\":\"$REFRESH\"}"
const refreshRes = await fetch(`${base}/auth/refresh`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ refresh_token: refreshToken }),
});
const refreshed = await refreshRes.json();
const newAccess = refreshed.data.tokens.access_token;

Me

curl -sS "http://localhost:3001/api/v1/auth/me" \
  -H "Authorization: Bearer $ACCESS"
const meRes = await fetch(`${base}/auth/me`, {
  headers: { Authorization: `Bearer ${accessToken}` },
});
const me = await meRes.json();
console.log(me.data.user.handle);

Create API key

curl -sS -X POST "http://localhost:3001/api/v1/developer/keys" \
  -H "Authorization: Bearer $ACCESS" \
  -H "Content-Type: application/json" \
  -d '{"name":"my-integration"}'
const keyRes = await fetch(`${base}/developer/keys`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${accessToken}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ name: "my-integration" }),
});
const keyJson = await keyRes.json();
// 평문 키는 이 응답에서만 보관할 것
const rawKey = keyJson.data.key;

13. 오류 코드 요약

code 전형적 HTTP 의미
ALREADY_AUTHENTICATED 409 로그인 상태에서 가입
EMAIL_EXISTS 409 이메일 중복
LEGACY_ACCOUNT_EXISTS 409 레거시 계정 존재 → 로그인
HANDLE_EXISTS 409 handle 중복
VALIDATION_ERROR 422 필드 검증
INVALID_CREDENTIALS 401 로그인 실패
ACCOUNT_SUSPENDED 403 정지 계정
UNAUTHORIZED 401 Bearer 없음/무효
INVALID_REFRESH_TOKEN 401 refresh 무효
CAPTCHA_NOT_CONFIGURED 503 캡차 시크릿 없음(challenge/verify)
CAPTCHA_FAILED 403 캡차 실패
NOT_IMPLEMENTED 501 OAuth
API_KEY_NOT_FOUND 404 키 삭제 대상 없음
NOT_FOUND 404 세션 등

*ZUKU API · Authentication · router.rs 기준*