P2.1: BYOC Admin API — произвольные креды при создании тенанта

POST /admin/tenants теперь принимает опциональные access_key/secret_key. Если
задано любое из полей — оба обязательны (мин. длина 8/16). С произвольными
кредами tenantID генерируется случайно (CreateFixedAutoID). Без кредов —
поведение прежнее (store.Create).
This commit is contained in:
Repinoid
2026-10-06 18:17:24 +03:00
parent b7f79b388f
commit 8c39bc5346
2 changed files with 32 additions and 1 deletions
+21 -1
View File
@@ -275,6 +275,10 @@ func (h *Handler) jwtMiddleware(next http.Handler) http.Handler {
type createTenantRequest struct { type createTenantRequest struct {
Name string `json:"name"` Name string `json:"name"`
MaxQueues int `json:"max_queues"` MaxQueues int `json:"max_queues"`
// BYOC: необязательные произвольные креды. Если задано одно поле —
// оба обязательны.
AccessKey string `json:"access_key,omitempty"`
SecretKey string `json:"secret_key,omitempty"`
} }
// tenantCreateResponse — ответ с secret_key (показывается ТОЛЬКО при создании) // 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"}) json.NewEncoder(w).Encode(map[string]string{"error": "name is required"})
return 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 { if err != nil {
log.Errorf("admin: failed to create tenant: %v", err) log.Errorf("admin: failed to create tenant: %v", err)
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
+11
View File
@@ -103,6 +103,17 @@ func (s *TenantStore) Create(name string, maxQueues int) (*Tenant, error) {
// CreateFixed — создаёт тенанта с заранее известными credentials (для seed/demo). // CreateFixed — создаёт тенанта с заранее известными credentials (для seed/demo).
// Используется только при инициализации демо-данных; не вызывается из user-facing API. // Используется только при инициализации демо-данных; не вызывается из 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) { func (s *TenantStore) CreateFixed(name string, maxQueues int, tenantID, accessKey, secretKey string) (*Tenant, error) {
s.mu.Lock() s.mu.Lock()
if _, exists := s.byID[tenantID]; exists { if _, exists := s.byID[tenantID]; exists {