Skip to contentSkip to content

HTTP API Authentication and Errors

Use this page when a third-party client or the web frontend calls translation, history, resource, quota, or admin HTTP endpoints. It explains how sessions are created, how the token is carried and verified, how permissions are layered, and which status codes and error structures failures return. This guide covers the developer HTTP API authentication and error contract only; the login and session UI is documented in Login, language, and session, workspace and translation operations in Upload, configure, and translate, and the request/response models of translation, streaming, batch, and history endpoints in Translation endpoints and the other http-api/ pages.

Endpoint scope

  • This guide documents session creation and verification from a developer perspective: the /auth/* session endpoints, the X-Session-Token request header, FastAPI dependencies such as require_auth / require_admin, and verify_translation_auth at the translation entry.
  • The status-code matrix covers the static behavior of every router group; the error-response section distinguishes the middleware envelope, route-layer strings, and the 422 validation shape.
  • The session token is an opaque random string persisted in memory plus sessions.json; it is not a JWT and carries no decodable user information.
  • This page records no real account, token, password, nonce, API key, or private absolute path. Rate-limit counts, timeout minutes, and similar values come from source constants and do not represent a running deployment's actual configuration.

Session authentication flow

Initial setup and login

  1. When no account exists, GET /auth/status returns {"need_setup": true, "registration_enabled": ...}; the client calls POST /auth/setup to create the first admin and receives a token on success.
  2. With existing accounts, POST /auth/login submits JSON {"username": "…", "password": "…"}; success returns success, token, user, and must_change_password. Invalid credentials still return HTTP 200 with success: false.
  3. When the admin enables registration, POST /auth/register creates a normal user and returns a token; otherwise it returns 403.
  4. After login/registration, the frontend stores the token in browser localStorage.session_token and every protected request carries the X-Session-Token request header.
  5. POST /auth/logout terminates the current session; POST /auth/change-password requires a token and verifies the old password. GET /auth/check returns {"valid": true, "user": {...}} or {"valid": false}, and the web frontend clears the local token and returns to the login page accordingly.
flowchart LR
    C["Client"] -->|"GET /auth/status"| ST{"need_setup?"}
    ST -->|"yes"| SP["POST /auth/setup creates the first admin"]
    ST -->|"no"| L["POST /auth/login or /auth/register"]
    SP -->|"returns token"| T["Session token"]
    L -->|"returns token"| T
    T -->|"subsequent requests"| H["X-Session-Token request header"]
    H --> D{"require_auth verifies token and account activity"}
    D -->|"missing/invalid/expired/inactive"| E401["401 detail.error.code"]
    D -->|"pass"| R["Route business logic"]
    R -->|"non-admin hits admin endpoint"| E403["403 ADMIN_REQUIRED"]
    R -->|"missing feature or resource permission"| E403P["403 matching PERMISSION_DENIED"]
    R -->|"concurrency or daily quota exceeded"| E429["429 CONCURRENT_LIMIT_EXCEEDED or DAILY_QUOTA_EXCEEDED"]

Token lifecycle

  • Generation: SessionService.create_session() uses secrets.token_urlsafe(32) to produce a 32-byte URL-safe random token; the token is opaque and contains no username or role.
  • Expiry: SessionService defaults to session_timeout_minutes=60 with sliding expiry based on last_activity; every verify_token / update_activity refreshes it, and 60 minutes without activity invalidates it.
  • Persistence: at startup the server creates SessionService(..., enable_persistence=True), atomically writing active sessions to manga_translator/server/data/sessions.json; only active, non-expired sessions are loaded after restart.
  • Invalidation: /auth/logout terminates a session; a disabled account is rejected by require_auth with USER_INACTIVE; a cleanup service periodically removes expired sessions.

Authentication dependencies and permissions

The dependencies below are defined in manga_translator/server/core/middleware.py and manga_translator/server/routes/translation_auth.py. require_auth reads the token from the X-Session-Token header and returns a session object; require_admin additionally requires role == 'admin'.

Dependency/functionSourceFailure statusError code
require_authX-Session-Token request header401NO_TOKEN / INVALID_TOKEN / USER_INACTIVE
require_adminreuses require_auth403ADMIN_REQUIRED
check_translator_permissionsession + translator parameter403TRANSLATOR_PERMISSION_DENIED
check_parameter_permissionsession + parameter dictionaryno errorsilently filters unauthorized parameters
check_concurrent_limitcalled from business logic429CONCURRENT_LIMIT_EXCEEDED
check_daily_quotacalled from business logic429DAILY_QUOTA_EXCEEDED
verify_translation_authreads the request header directly401 / 403session codes + TRANSLATOR/OCR/COLORIZER/RENDERER_PERMISSION_DENIED

At the translation entry, verify_translation_auth first verifies the token, then applies default values for parameters disabled by the user/group configuration, and finally checks translator, OCR, colorizer, and renderer permissions. Concurrency and daily-quota checks run in the route layer inside track_task_start / track_task_end, and failures roll back the concurrent counter.

Public endpoints and exceptions

The following endpoints do not require X-Session-Token; business-data requests inside those pages still authenticate separately.

Endpoint categoryStatic behavior and boundary
Pages, static, locale, API infoGET /, GET /admin, GET /api, GET /favicon.ico, /static/*, and the conditionally mounted /locales/* when the desktop locale directory exists
Before a session exists/auth/login, /auth/status, /auth/setup, /auth/register (registration still honors the admin switch and rate limits)
Legacy password gateGET /user/access, POST /user/login; see the subsection below
Download tickets`GET
Public/compatibility metadata/config, /config/defaults, /config/options, /fonts, /translators, /languages, /workflows, /translator-config/{translator}, /user/access, /i18n/*, /announcement; with a token the responses are filtered per user
Internal instance registrationPOST /register is verified with the X-Nonce header (see Dependencies and conflicts), not X-Session-Token

Legacy password gate

GET /user/access returns require_password; POST /user/login verifies a single password supplied as the form field password. When no password is required it succeeds directly; otherwise it rate-limits per IP (10 attempts in 10 minutes) and returns 429 with Retry-After when exceeded. It does not issue an X-Session-Token; the frontend records success in sessionStorage.user_logged_in. This is not the main login flow of the current startup path.

Download tickets

History downloads first request a short-lived ticket from an authenticated endpoint, then download via GET|HEAD /api/history/downloads/t/{ticket}. The default ticket TTL is 5 minutes and the token is generated with secrets.token_urlsafe(32); invalid, expired, or deleted files return 404. The ticket endpoint does not read the session header, so the ticket itself is sensitive and must not be written into logs or documentation.

Error response structure

Actual responses have three shapes; a client should read detail first and then branch on its type:

  1. Middleware and translation auth: they raise HTTPException(status_code=..., detail={"error": {...}}), which the FastAPI default handler wraps as-is into the detail.error envelope.
  2. Route layer: most 400/403/404/409/500 responses use a plain string detail.
  3. Global validation: 422 returns a detail array plus the raw request body string.

core/middleware.py also defines the create_error_response(code, message, details, status_code) helper that can produce the {"error": {...}} shape directly; current routes do not call it, so real errors follow the HTTPException shapes above.

json
{
  "detail": {
    "error": {
      "code": "TRANSLATOR_PERMISSION_DENIED",
      "message": "您没有权限使用翻译器 '<translator>'",
      "details": {
        "translator": "<translator>",
        "allowed_translators": ["*"]
      }
    }
  }
}
json
{
  "detail": [
    { "loc": ["body", "password"], "msg": "String should have at least 6 characters", "type": "string_too_short" }
  ],
  "body": "<raw request body string>"
}
json
{ "detail": "会话不存在" }
flowchart LR
    REQ["Request"] --> V{"Request body validation failed?"}
    V -->|"yes"| V422["422 detail array + body"]
    V -->|"no"| B{"Business or auth failure?"}
    B -->|"middleware or translation auth"| ENV["detail.error envelope code/message/details"]
    B -->|"route layer rejects directly"| STR["detail is a string"]
    B -->|"no"| OK["200 or 201/204 success response"]

Error codes are stable program identifiers such as NO_TOKEN, ADMIN_REQUIRED, or DAILY_QUOTA_EXCEEDED; message is user-facing and may change between versions. Clients should branch on code, not on message.

Rate limits and quotas

Endpoint/checkWindow and limit (source constants)Response
POST /auth/loginIP 15 attempts / 10 minutes; username 8 attempts / 10 minutes429 + Retry-After
POST /auth/registerIP 5 attempts / 10 minutes429 + Retry-After
POST /user/login (legacy)IP 10 attempts / 10 minutes429 + Retry-After
Concurrent taskseffective concurrency limit of user/group429 CONCURRENT_LIMIT_EXCEEDED
Daily quotaeffective daily quota of user/group429 DAILY_QUOTA_EXCEEDED

SlidingWindowRateLimiter implements a sliding window; Retry-After is in seconds. When concurrency or quota checks fail, the route layer rolls back the concurrent counter it already incremented. A 429 does not mean invalid credentials, and the client should not clear the session token.

API constraints

  • At the translation entry, verify_translation_auth runs first (permission filtering and disabled-parameter defaults), then the route layer counts concurrency/quota; 401/403 are returned before counting and 429 is returned during counting.
  • Parameter permissions are silently filtered rather than rejected: check_parameter_permission keeps only the parameters the user may change, and hiding a control in the frontend cannot replace the server-side check.
  • CORS is configured as allow_origins=["*"], allow_credentials=True, with all methods and headers; this is source configuration and does not prove the browser will allow every origin/credential combination.
  • FastAPI default docs are not disabled: a running instance also serves /openapi.json, /docs, /docs/oauth2-redirect, and /redoc.
  • The internal POST /register (instance registration) uses X-Nonce (secrets.token_hex(16), generated at startup) instead of X-Session-Token; the two mechanisms must not be mixed, and documentation and logs must never contain a real nonce.
  • Download tickets are 5-minute short-lived credentials; they do not require the session header, so their exposure window is bounded but they remain sensitive.

Developer Guide

Option matrix

Web session UI strings

The table below lists login/session/error-related strings actually called by the web main script and verified in both desktop locales. The login.html form texts ("用户名", "密码", "登录", "注册", "首次使用,请创建管理员账户", etc.) are hardcoded Chinese without i18n keys and must not be recorded as localized text.

UI call keyEnglish actual valueSimplified Chinese actual value
Manga TranslatorManga Translator漫画翻译器
adminmissing, caller fallbackmissing, caller fallback
web_session_tokenSession Token会话令牌
web_active_sessionsActive Sessions活跃会话
web_permission_deniedPermission Denied权限不足
web_quota_exceededQuota Exceeded配额已用完
web_errorError错误
web_daily_quotaDaily Quota每日配额
web_used_todayUsed today今日已使用
API Keys (.env)API Keys (.env)API密钥 (.env)
Start TranslationStart Translation开始翻译

t(key, defaultText) in static/script.js returns the default value or the key itself when the locale is not loaded or the key is missing, so parts of the UI still show hardcoded Chinese under the English locale.

Status code matrix

The table below covers the statically verified triggers of every status code; except for explicit overrides, the default success status is 200.

StatusStatic trigger scopeSource
200Ordinary successful JSON/HTML/stream/file/delete responses; /auth/login, /auth/register, /auth/logout, and /auth/change-password also return 200 with success: false on business failures such as wrong password or wrong old passwordFastAPI default; routes/auth.py
201Successful creation via POST /sessions/, POST /api/admin/users/, POST /api/admin/groups/sessions.py:61, users.py:79, groups.py:87
204Successful DELETE /api/admin/users/{username}users.py:378
400Request fields, initial setup/registration validation, no batch images, invalid import, or resource/admin inputs; some history/ticket requests tooauth.py:362, translation.py:449, history.py:582, config_management.py:227
401Missing session header, invalid/expired token, activity refresh failure, disabled account, or invalid internal /register noncecore/middleware.py:119, translation_auth.py:253, main.py:317
403Non-admin, missing feature/resource/history permission, or registration disabled by the admincore/middleware.py:198, :246, translation_auth.py:345, auth.py:460
404favicon/file/user/group/session/history/download-ticket/preset object not foundmain.py:288, history.py:136, users.py:236, config_management.py:182
409Creating an admin preset whose name already existsconfig_management.py:227
422Global RequestValidationError handler returns detail plus a request body stringmain.py:255:273
429Login or registration rate limit (with Retry-After), legacy password-gate rate limit, concurrent-task limit, or daily quota exceededauth.py:52, web.py:89, core/middleware.py:326, :365
499Batch translation task force-cancelled or detected as cancelledtranslation.py:421, :518
500Uninitialized services, translation/import/export, persistence, resource, and admin service failures, handled or unhandledauth.py:135, translation.py:527, resources.py:111, logs.py:249
File/pathActual role on this pageManual-edit and compatibility note
manga_translator/server/data/sessions.jsonActive-session persistence (atomic writes)Never read or display a real token; format is version plus a sessions list
manga_translator/server/data/accounts.jsonAccounts, roles, permissions, and password verificationNever display real accounts or passwords
manga_translator/server/data/audit.logLogin/logout/password/registration/task auditMust be sanitized before sharing
manga_translator/server/data/server_config.jsonAdmin settings: registration switch, user_access, API key policyNever display real configuration content
.envServer API key loading/env and /env/effective never return plaintext server keys
manga_translator/server/static/login.htmlSession entry pageForm texts are hardcoded Chinese without i18n keys

Mermaid data-flow limits

The diagrams describe session establishment, token verification, and error-classification paths; they do not claim every run makes a network request, nor that /auth/check, rate limits, or quotas trigger in every deployment. Deployment-specific behavior may vary.

Code locations

LayerFileWhat was checked
Service initializationmanga_translator/server/main.pySessionService 60 minutes, persistence, CORS, 422 handler, router registration, internal /register nonce
Middlewaremanga_translator/server/core/middleware.pyrequire_auth/require_admin, feature permissions, concurrency/quota, error envelope, create_error_response
Session servicesmanga_translator/server/core/session_service.py, session_security_service.pytoken_urlsafe(32), sliding expiry, persistence, session ownership and access audit
Auth routesmanga_translator/server/routes/auth.pylogin/setup/register/logout/change-password/check/status and rate limits
Translation authmanga_translator/server/routes/translation_auth.pyverify_translation_auth, disabled-parameter defaults, feature permissions, task counting
Route status codesmanga_translator/server/routes/translation.py, history.py, web.py, users.py, groups.py, config_management.py, sessions.py200/201/204/400/404/409/429/499/500, download tickets
UI/i18nmanga_translator/server/static/script.js, login.html, static/js/i18n.js, desktop_qt_ui/locales/en_US.json, zh_CN.jsonkey mapping, hardcoded texts, localStorage.session_token