veza/veza-backend-api/internal/middleware/api_key_scope_test.go
senke b47fa21331 feat(v0.12.8): documentation & API publique — rate limiting, scopes, OpenAPI
- API key rate limiting middleware (1000 reads/h, 200 writes/h par clé)
  — tracking séparé read/write, par API key ID (pas par IP)
  — headers X-RateLimit-Limit/Remaining/Reset sur chaque réponse
- API key scope enforcement middleware (read → GET, write → POST/PUT/DELETE)
  — admin scope permet tout, CSRF skip pour API key auth
- OpenAPI spec: ajout securityDefinition ApiKeyAuth (X-API-Key header)
- Swagger annotations: ajout ApiKeyAuth dans cmd/api/main.go
- Wiring dans router.go: middlewares appliqués sur tout le groupe /api/v1
- Tests: 10 tests (5 rate limiter + 5 scope enforcement), tous PASS

Backend existant déjà en place (pré-v0.12.8):
- Swagger UI (gin-swagger + frontend SwaggerUIDoc component)
- API key CRUD (create/list/delete + X-API-Key auth dans AuthMiddleware)
- Developer Dashboard frontend (API keys, webhooks, playground)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 18:44:09 +01:00

149 lines
3.3 KiB
Go

package middleware
import (
"net/http"
"net/http/httptest"
"testing"
"veza-backend-api/internal/models"
"veza-backend-api/internal/services"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/lib/pq"
"go.uber.org/zap"
)
func TestRequireAPIKeyScope_PassthroughWithoutAPIKey(t *testing.T) {
svc := services.NewAPIKeyService(nil, zap.NewNop())
router := gin.New()
router.Use(RequireAPIKeyScope(svc))
router.GET("/test", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"ok": true})
})
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/test", nil)
router.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("expected 200, got %d", w.Code)
}
}
func TestRequireAPIKeyScope_AllowsReadScopeOnGET(t *testing.T) {
svc := services.NewAPIKeyService(nil, zap.NewNop())
apiKey := &models.APIKey{
ID: uuid.New(),
UserID: uuid.New(),
Name: "test",
Scopes: pq.StringArray{"read"},
}
router := gin.New()
router.Use(func(c *gin.Context) {
c.Set("api_key", apiKey)
c.Next()
})
router.Use(RequireAPIKeyScope(svc))
router.GET("/test", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"ok": true})
})
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/test", nil)
router.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("expected 200, got %d", w.Code)
}
}
func TestRequireAPIKeyScope_DeniesWriteWithReadOnlyScope(t *testing.T) {
svc := services.NewAPIKeyService(nil, zap.NewNop())
apiKey := &models.APIKey{
ID: uuid.New(),
UserID: uuid.New(),
Name: "test",
Scopes: pq.StringArray{"read"},
}
router := gin.New()
router.Use(func(c *gin.Context) {
c.Set("api_key", apiKey)
c.Next()
})
router.Use(RequireAPIKeyScope(svc))
router.POST("/test", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"ok": true})
})
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/test", nil)
router.ServeHTTP(w, req)
if w.Code != http.StatusForbidden {
t.Errorf("expected 403, got %d", w.Code)
}
}
func TestRequireAPIKeyScope_AllowsWriteWithWriteScope(t *testing.T) {
svc := services.NewAPIKeyService(nil, zap.NewNop())
apiKey := &models.APIKey{
ID: uuid.New(),
UserID: uuid.New(),
Name: "test",
Scopes: pq.StringArray{"read", "write"},
}
router := gin.New()
router.Use(func(c *gin.Context) {
c.Set("api_key", apiKey)
c.Next()
})
router.Use(RequireAPIKeyScope(svc))
router.POST("/test", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"ok": true})
})
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/test", nil)
router.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("expected 200, got %d", w.Code)
}
}
func TestRequireAPIKeyScope_AdminScopeAllowsAll(t *testing.T) {
svc := services.NewAPIKeyService(nil, zap.NewNop())
apiKey := &models.APIKey{
ID: uuid.New(),
UserID: uuid.New(),
Name: "test",
Scopes: pq.StringArray{"admin"},
}
router := gin.New()
router.Use(func(c *gin.Context) {
c.Set("api_key", apiKey)
c.Next()
})
router.Use(RequireAPIKeyScope(svc))
router.DELETE("/test", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"ok": true})
})
w := httptest.NewRecorder()
req, _ := http.NewRequest("DELETE", "/test", nil)
router.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("expected 200, got %d (admin should have write access)", w.Code)
}
}