From 8c39bc53463ee09df026fd6a01f48eebc5b4a7d8 Mon Sep 17 00:00:00 2001 From: Repinoid Date: Tue, 6 Oct 2026 18:17:24 +0300 Subject: [PATCH] =?UTF-8?q?P2.1:=20BYOC=20Admin=20API=20=E2=80=94=20=D0=BF?= =?UTF-8?q?=D1=80=D0=BE=D0=B8=D0=B7=D0=B2=D0=BE=D0=BB=D1=8C=D0=BD=D1=8B?= =?UTF-8?q?=D0=B5=20=D0=BA=D1=80=D0=B5=D0=B4=D1=8B=20=D0=BF=D1=80=D0=B8=20?= =?UTF-8?q?=D1=81=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD=D0=B8=D0=B8=20=D1=82=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=D0=BD=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit POST /admin/tenants теперь принимает опциональные access_key/secret_key. Если задано любое из полей — оба обязательны (мин. длина 8/16). С произвольными кредами tenantID генерируется случайно (CreateFixedAutoID). Без кредов — поведение прежнее (store.Create). --- app/admin/admin.go | 22 +++++++++++++++++++++- app/tenant/tenant_store.go | 11 +++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/app/admin/admin.go b/app/admin/admin.go index 61b9f36..0c1eb21 100644 --- a/app/admin/admin.go +++ b/app/admin/admin.go @@ -275,6 +275,10 @@ func (h *Handler) jwtMiddleware(next http.Handler) http.Handler { type createTenantRequest struct { Name string `json:"name"` MaxQueues int `json:"max_queues"` + // BYOC: необязательные произвольные креды. Если задано одно поле — + // оба обязательны. + AccessKey string `json:"access_key,omitempty"` + SecretKey string `json:"secret_key,omitempty"` } // tenantCreateResponse — ответ с secret_key (показывается ТОЛЬКО при создании) @@ -317,7 +321,23 @@ func (h *Handler) createTenant(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(map[string]string{"error": "name is required"}) return } - t, err := h.store.Create(req.Name, req.MaxQueues) + + // BYOC: либо оба поля кредов заданы, либо ни одного. + var t *tenant.Tenant + var err error + if req.AccessKey != "" || req.SecretKey != "" { + if req.AccessKey == "" || req.SecretKey == "" { + jsonErr(w, http.StatusBadRequest, "both access_key and secret_key are required when specifying custom credentials") + return + } + if len(req.AccessKey) < 8 || len(req.SecretKey) < 16 { + jsonErr(w, http.StatusBadRequest, "access_key min 8 chars, secret_key min 16 chars") + return + } + t, err = h.store.CreateFixedAutoID(req.Name, req.MaxQueues, req.AccessKey, req.SecretKey) + } else { + t, err = h.store.Create(req.Name, req.MaxQueues) + } if err != nil { log.Errorf("admin: failed to create tenant: %v", err) w.Header().Set("Content-Type", "application/json") diff --git a/app/tenant/tenant_store.go b/app/tenant/tenant_store.go index e8f3d9b..70a921b 100644 --- a/app/tenant/tenant_store.go +++ b/app/tenant/tenant_store.go @@ -103,6 +103,17 @@ func (s *TenantStore) Create(name string, maxQueues int) (*Tenant, error) { // CreateFixed — создаёт тенанта с заранее известными credentials (для seed/demo). // Используется только при инициализации демо-данных; не вызывается из user-facing API. +// CreateFixedAutoID — создаёт тенанта с заранее известными креды (BYOC), +// но СЛУЧАЙНЫМ tenantID (генерируется через crypto/rand). Используется Admin API +// при задании произвольных access_key/secret_key без явного tenantID. +func (s *TenantStore) CreateFixedAutoID(name string, maxQueues int, accessKey, secretKey string) (*Tenant, error) { + id, err := generateTenantID() + if err != nil { + return nil, fmt.Errorf("generate tenant id: %w", err) + } + return s.CreateFixed(name, maxQueues, id, accessKey, secretKey) +} + func (s *TenantStore) CreateFixed(name string, maxQueues int, tenantID, accessKey, secretKey string) (*Tenant, error) { s.mu.Lock() if _, exists := s.byID[tenantID]; exists {