feat(api): enhance whoami endpoint with realtime stats and extended key info

Add realtime statistics (requests, tokens, QPS, rate limiting) to whoami
response for both master and key authentication types. Extend key response
with additional fields including master name, model limits, quota tracking,
and usage statistics.

- Inject StatsService into AuthHandler for realtime stats retrieval
- Add WhoamiRealtimeView struct for realtime statistics
- Include admin permissions field in admin response
- Add comprehensive key metadata (quotas, model limits, usage stats)
- Add test for expired key returning 401 Unauthorized
This commit is contained in:
2026-01-06 09:15:49 +08:00
parent f99e4a15ab
commit 1ee6bea413
3 changed files with 188 additions and 32 deletions

View File

@@ -40,7 +40,8 @@ func newAuthHandler(t *testing.T) (*AuthHandler, *gorm.DB, *miniredis.Miniredis)
rdb := redis.NewClient(&redis.Options{Addr: mr.Addr()})
masterService := service.NewMasterService(db)
handler := NewAuthHandler(db, rdb, adminService, masterService)
statsService := service.NewStatsService(rdb)
handler := NewAuthHandler(db, rdb, adminService, masterService, statsService)
return handler, db, mr
}
@@ -152,3 +153,56 @@ func TestAuthHandler_Whoami_KeyResponseIncludesIPRules(t *testing.T) {
t.Fatalf("expected expires_at=%d, got %v", expiresAt, resp["expires_at"])
}
}
func TestAuthHandler_Whoami_ExpiredKey_Returns401(t *testing.T) {
handler, db, mr := newAuthHandler(t)
token := "sk-live-expired"
hash := tokenhash.HashToken(token)
// Set expiration to 1 hour ago
expiresAt := time.Now().Add(-time.Hour).Unix()
mr.HSet("auth:token:"+hash, "master_id", "1")
mr.HSet("auth:token:"+hash, "issued_at_epoch", "1")
mr.HSet("auth:token:"+hash, "status", "active")
mr.HSet("auth:token:"+hash, "group", "default")
mr.HSet("auth:token:"+hash, "expires_at", fmt.Sprintf("%d", expiresAt))
mr.HSet("auth:master:1", "epoch", "1")
mr.HSet("auth:master:1", "status", "active")
// Create key in DB
key := &model.Key{
MasterID: 1,
TokenHash: hash,
Group: "default",
Scopes: "chat:write",
DefaultNamespace: "default",
Namespaces: "default",
Status: "active",
IssuedAtEpoch: 1,
IssuedBy: "master",
}
if err := db.Create(key).Error; err != nil {
t.Fatalf("create key: %v", err)
}
r := gin.New()
r.GET("/auth/whoami", handler.Whoami)
req := httptest.NewRequest(http.MethodGet, "/auth/whoami", nil)
req.Header.Set("Authorization", "Bearer "+token)
rr := httptest.NewRecorder()
r.ServeHTTP(rr, req)
if rr.Code != http.StatusUnauthorized {
t.Fatalf("expected 401 for expired key, got %d body=%s", rr.Code, rr.Body.String())
}
var resp map[string]any
if err := json.Unmarshal(rr.Body.Bytes(), &resp); err != nil {
t.Fatalf("decode response: %v", err)
}
if resp["error"] != "token has expired" {
t.Fatalf("expected 'token has expired' error, got %v", resp["error"])
}
}