feat(api): implement API key management and authentication enhancements
CI / changes (push) Successful in 7s
CI / commitlint (push) Has been skipped
CI / openapi (push) Successful in 25s
CI / web (push) Successful in 28s
CI / go (push) Failing after 24s
CI / bird2 (push) Has been skipped
CI / release (push) Has been skipped

- Added endpoints for managing API keys, including creation, retrieval, updating, and revocation.
- Introduced a new Auth session endpoint to retrieve current tenant and role information.
- Updated the authentication middleware to support API key-based authentication and track last used timestamps.
- Enhanced documentation to reflect new API key functionalities and usage guidelines.
- Improved logging for demo authentication scenarios.
This commit is contained in:
Denozordec
2026-05-21 11:26:17 +07:00
parent 880d77810a
commit 6329a4df27
28 changed files with 1682 additions and 39 deletions
+13 -21
View File
@@ -15,6 +15,7 @@ type Auth struct {
TenantID string
Role string // viewer, editor, operator, node
Token string
APIKeyID string // non-empty for DB-managed keys
}
func authFromContext(ctx context.Context) (Auth, bool) {
@@ -26,6 +27,7 @@ type apiKeyRecord struct {
token string
tenantID string
role string
keyID string // set for DB-managed keys (last_used_at)
}
func parseAPIKeysSpec(spec string) []apiKeyRecord {
@@ -54,20 +56,6 @@ func parseAPIKeysSpec(spec string) []apiKeyRecord {
func (s *Server) authMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if s.insecureDev {
h := r.Header.Get("Authorization")
const p = "Bearer "
if strings.HasPrefix(h, p) {
tok := strings.TrimSpace(strings.TrimPrefix(h, p))
if tok == "dev" {
if a, ok := s.devAuth(); ok {
r = r.WithContext(context.WithValue(r.Context(), authCtxKey, a))
next.ServeHTTP(w, r)
return
}
}
}
}
h := r.Header.Get("Authorization")
const p = "Bearer "
if !strings.HasPrefix(h, p) {
@@ -75,18 +63,22 @@ func (s *Server) authMiddleware(next http.Handler) http.Handler {
return
}
raw := strings.TrimSpace(strings.TrimPrefix(h, p))
var matched *apiKeyRecord
for i := range s.apiKeys {
if s.apiKeys[i].token == raw {
matched = &s.apiKeys[i]
break
if raw == "dev" {
if a, ok := s.devAuth(); ok {
r = r.WithContext(context.WithValue(r.Context(), authCtxKey, a))
next.ServeHTTP(w, r)
return
}
}
if matched == nil {
matched, ok := s.keyResolver.Lookup(raw)
if !ok {
writeProblem(w, http.StatusUnauthorized, "Unauthorized", "unknown api key")
return
}
a := Auth{TenantID: matched.tenantID, Role: matched.role, Token: raw}
a := Auth{TenantID: matched.tenantID, Role: matched.role, Token: raw, APIKeyID: matched.keyID}
if matched.keyID != "" {
go func(id string) { _ = s.store.TouchAPIKeyLastUsed(id) }(matched.keyID)
}
r = r.WithContext(context.WithValue(r.Context(), authCtxKey, a))
next.ServeHTTP(w, r)
})