Skip to content

feat: add token management and Bearer auth for LLM API gateway - #195

Open
BeiBaiNian wants to merge 1 commit into
apache:masterfrom
BeiBaiNian:feat-token-1.3
Open

feat: add token management and Bearer auth for LLM API gateway#195
BeiBaiNian wants to merge 1 commit into
apache:masterfrom
BeiBaiNian:feat-token-1.3

Conversation

@BeiBaiNian

Copy link
Copy Markdown
Contributor

Why

The /v1/chat/completions gateway endpoint has no authentication at all: anyone who knows the address can burn upstream LLM quota through the configured channels. Following the mentor's three-tier priority (working features → user experience → security), this PR delivers the milestone 1.3 core: API token management with Bearer authentication, plus rate limiting, a model allowlist and expiration times. Secret keys are stored as plaintext per (the project is locally deployed; encryption is deferred), and that does not block this PR.

Changes

Backend:

  • object/token.go (new): Token model and CRUD; sk--prefixed 48-char random secret key generation; masked keys in list responses; validateToken checks (non-empty name/displayName, RFC3339 expire time, at most 200 allowed models with at most 100 chars each, status enum).
  • controllers/token.go (new): five management APIs (get-tokens paginated list / get-token / add-token / update-token / delete-token) with tenant isolation (non-admins can only operate on their own tokens).
  • object/rate_limit.go (new): per-token per-minute window rate limiting; entries guarded by sync.Mutex (fixes the lost count++ under concurrency of the first draft); stale entry cleanup throttled by a timestamp (at most one full sweep per minute).
  • controllers/proxy.go: authentication chain wired in (Bearer plaintext comparison → status → expiration fail-closed → model allowlist → rate limit); a malformed expiration time no longer silently passes (defense in depth: write-time validation plus read-time rejection); checkTokenAccess extracted as a pure function so the checks are unit-testable without a database; authenticateToken returns bool only (removes the dead-code return value reserved for 1.4); outdated comment updated.
  • object/channel.go: stale TODO comments cleaned up.
  • object/ormer.go, routers/router.go: token table registration (auto-created by Sync2) and five /api routes.
  • run/database_cloud_test.go: added the skipCi build tag, consistent with the other nine test files in the repo.

Frontend:

  • TokenListPage.js (new): pagination, loading state, status tag, copy button, edit/delete actions.
  • TokenEditPage.js (new): create/edit form; the full secret key is shown once right after creation; the date picker emits RFC3339.
  • TokenBackend.js (new): API layer; App.js gains the menu entry.
  • Locale files: token strings; properly localized in zh/en, backfilled with English in de/es/fr/id/ja/ko/ru (fixes the Chinese values that were mistakenly filled in earlier, restoring the English fallback baseline for those languages).

Docs:

  • README.md: new LLM API Gateway overview section.
  • docs/llm-api-gateway.md (new): full usage guide (channel configuration, token issuance, curl and OpenAI SDK examples, access control semantics, current limitations).

Tests:

  • object/token_test.go (new): all validateToken branches, secret key generation, masking.
  • object/rate_limit_test.go (new): window reset, 51 concurrent callers (verifies the mutex fix), stale cleanup, limit<=0.
  • controllers/proxy_test.go: new TestCheckTokenAccess (disabled / expired / malformed fail-closed / model allowlist).

Verification

Automated:

  • go build ./... passes.
  • go test -race ./controllers/ passes (including the new TestCheckTokenAccess).
  • go test -race on the seven new object tests is all green; the 51-concurrent rate limit test shows no data race and exact counting.

End-to-end (local port 17000, a DeepSeek channel of type custom with a real upstream key):

  • Valid token with an allowed model → real upstream reply returned.
  • Model outside the allowlist → 403 model not allowed for this token (allowlist enforced).
  • Wrong key → 401 (authentication enforced).

Manual UI checklist (must be done by a human):

  • Create a token → the full sk- key is shown once → copy it; the list shows only the masked prefix.
  • Call /v1/chat/completions with curl (with a real channel configured).
  • Rate limit: RateLimit=1, two requests back to back → the second gets 429, recovered after a minute.
  • Expiration: set to expire in 1 minute → 403 after expiry.
  • Disabled: Status=disabled → 403.
  • Edit/delete: editing without touching the key keeps it working; after deletion the old key gets 401.
  • Walk through the whole flow with the Chinese UI.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant