feat(api): API keys + Redis rate limiting + usage stats
Add an optional API-key layer to the public read-only API. Keys grant higher per-minute rate limits and attribute usage; anonymous callers are still allowed at a lower IP-based budget. - migration 0008_api_key: api_key table (sha256 hash only, plaintext shown once) - apikey pkg: key generation + hashing - ratelimit pkg: Redis fixed-window limiter + per-key usage counters; fails open - public API middleware: X-API-Key / Bearer auth, X-RateLimit-* headers, 429+Retry-After - admin: issue/list/revoke keys + usage view (API + UI tab) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/baicai2026-baicai/goods/api/internal/adminstore"
|
||||
"github.com/baicai2026-baicai/goods/api/internal/auth"
|
||||
"github.com/baicai2026-baicai/goods/api/internal/gtin"
|
||||
"github.com/baicai2026-baicai/goods/api/internal/ratelimit"
|
||||
)
|
||||
|
||||
// Handler holds the admin dependencies.
|
||||
@@ -25,6 +26,7 @@ type Handler struct {
|
||||
basePath string
|
||||
spa fs.FS
|
||||
submitLimit *rateLimiter
|
||||
usage *ratelimit.Limiter
|
||||
}
|
||||
|
||||
// New constructs an admin Handler. basePath is e.g. "/ping" (no trailing slash).
|
||||
@@ -39,6 +41,13 @@ func New(store *adminstore.Store, authn *auth.Authenticator, basePath string, sp
|
||||
}
|
||||
}
|
||||
|
||||
// WithUsage attaches a Redis-backed limiter used to read per-key usage counters
|
||||
// for the API-key management view. Optional; without it usage shows as zero.
|
||||
func (h *Handler) WithUsage(l *ratelimit.Limiter) *Handler {
|
||||
h.usage = l
|
||||
return h
|
||||
}
|
||||
|
||||
// Router builds the HTTP handler.
|
||||
func (h *Handler) Router() http.Handler {
|
||||
r := chi.NewRouter()
|
||||
@@ -73,6 +82,10 @@ func (h *Handler) Router() http.Handler {
|
||||
r.Get("/api/submissions/{id}", h.GetSubmission)
|
||||
r.Post("/api/submissions/{id}/approve", h.ApproveSubmission)
|
||||
r.Post("/api/submissions/{id}/reject", h.RejectSubmission)
|
||||
|
||||
r.Get("/api/keys", h.ListAPIKeys)
|
||||
r.Post("/api/keys", h.CreateAPIKey)
|
||||
r.Delete("/api/keys/{id}", h.RevokeAPIKey)
|
||||
})
|
||||
|
||||
r.Handle("/*", http.HandlerFunc(h.serveSPA))
|
||||
|
||||
Reference in New Issue
Block a user