security: fix critical/high auth, idor, races and persistence

This commit is contained in:
Naeel
2026-04-10 19:41:59 +03:00
parent e9a26f7975
commit a5e9bfb15c
11 changed files with 868 additions and 505 deletions
+20 -4
View File
@@ -14,6 +14,7 @@ import (
"shared-sqs/app/auth"
"shared-sqs/app/models"
"shared-sqs/app/persistence"
"shared-sqs/app/tenant"
"github.com/google/uuid"
@@ -36,9 +37,9 @@ func findQueue(tenantAccessKey, queueName string) (string, *models.Queue) {
// Handler — admin API handler, holds TenantStore и admin token
type Handler struct {
store *tenant.TenantStore
adminToken string
nubesEndpoint string // URL nubes API для валидации JWT (напр. https://deck-api-test.ngcloud.ru/api/v1)
store *tenant.TenantStore
adminToken string
nubesEndpoint string // URL nubes API для валидации JWT (напр. https://deck-api-test.ngcloud.ru/api/v1)
}
// NewHandler — создаёт admin handler
@@ -188,12 +189,18 @@ func (h *Handler) jwtMiddleware(next http.Handler) http.Handler {
}
// Проверяем что тенант существует (был создан при /ui/api/auth)
_, ok := h.store.GetBySub(claims.Sub)
jwtTenant, ok := h.store.GetBySub(claims.Sub)
if !ok {
jsonErr(w, http.StatusForbidden, "tenant not found — authenticate first via POST /ui/api/auth")
return
}
// IDOR защита: для /ui/api/tenants/{id}/... разрешаем доступ только к своему tenant ID.
if pathTenantID, exists := mux.Vars(r)["id"]; exists && pathTenantID != "" && pathTenantID != jwtTenant.ID {
jsonErr(w, http.StatusForbidden, "forbidden tenant access")
return
}
next.ServeHTTP(w, r)
})
}
@@ -315,13 +322,18 @@ func (h *Handler) deleteTenant(w http.ResponseWriter, r *http.Request) {
}
// Удаляем все очереди тенанта из SyncQueues
prefix := t.AccessKey + ":"
deletedQueueKeys := make([]string, 0)
models.SyncQueues.Lock()
for key := range models.SyncQueues.Queues {
if strings.HasPrefix(key, prefix) {
deletedQueueKeys = append(deletedQueueKeys, key)
delete(models.SyncQueues.Queues, key)
}
}
models.SyncQueues.Unlock()
for _, queueKey := range deletedQueueKeys {
persistence.DeleteQueue(queueKey)
}
h.store.Delete(id)
w.WriteHeader(http.StatusNoContent)
@@ -427,6 +439,7 @@ func (h *Handler) createTenantQueue(w http.ResponseWriter, r *http.Request) {
Messages: []models.SqsMessage{},
Duplicates: make(map[string]time.Time),
}
persistence.SaveQueue(key, models.SyncQueues.Queues[key])
models.SyncQueues.Unlock()
log.Infof("admin: created queue %s for tenant %s", req.Name, t.ID)
w.Header().Set("Content-Type", "application/json")
@@ -453,6 +466,7 @@ func (h *Handler) deleteTenantQueue(w http.ResponseWriter, r *http.Request) {
}
delete(models.SyncQueues.Queues, key)
models.SyncQueues.Unlock()
persistence.DeleteQueue(key)
log.Infof("admin: deleted queue %s for tenant %s", queueName, t.ID)
w.WriteHeader(http.StatusNoContent)
}
@@ -552,6 +566,7 @@ func (h *Handler) sendMessageToQueue(w http.ResponseWriter, r *http.Request) {
}
models.SyncQueues.Lock()
models.SyncQueues.Queues[key].Messages = append(models.SyncQueues.Queues[key].Messages, msg)
persistence.SaveQueue(key, models.SyncQueues.Queues[key])
models.SyncQueues.Unlock()
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
@@ -575,6 +590,7 @@ func (h *Handler) purgeQueue(w http.ResponseWriter, r *http.Request) {
}
models.SyncQueues.Lock()
models.SyncQueues.Queues[key].Messages = models.SyncQueues.Queues[key].Messages[:0]
persistence.SaveQueue(key, models.SyncQueues.Queues[key])
models.SyncQueues.Unlock()
log.Infof("admin: purged queue %s for tenant %s", queueName, t.ID)
w.WriteHeader(http.StatusNoContent)