style(shared-sqs): fix indentation in tenant_store.go
This commit is contained in:
@@ -3,165 +3,165 @@
|
|||||||
package tenant
|
package tenant
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Tenant — модель тенанта shared-sqs.
|
// Tenant — модель тенанта shared-sqs.
|
||||||
// AccessKey используется как идентификатор в AWS Authorization header.
|
// AccessKey используется как идентификатор в AWS Authorization header.
|
||||||
type Tenant struct {
|
type Tenant struct {
|
||||||
ID string // уникальный идентификатор тенанта (t-<hex>)
|
ID string // уникальный идентификатор тенанта (t-<hex>)
|
||||||
Name string // человекочитаемое имя
|
Name string // человекочитаемое имя
|
||||||
AccessKey string // аналог AWS AccessKeyId (SSAK-<hex>)
|
AccessKey string // аналог AWS AccessKeyId (SSAK-<hex>)
|
||||||
SecretKey string // аналог AWS SecretAccessKey (64 hex chars)
|
SecretKey string // аналог AWS SecretAccessKey (64 hex chars)
|
||||||
MaxQueues int // лимит очередей (0 = безлимит)
|
MaxQueues int // лимит очередей (0 = безлимит)
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
Active bool
|
Active bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// TenantStore — потокобезопасное in-memory хранилище тенантов.
|
// TenantStore — потокобезопасное in-memory хранилище тенантов.
|
||||||
// Два индекса позволяют быстро искать как по ID (admin API), так и по AccessKey (auth middleware).
|
// Два индекса позволяют быстро искать как по ID (admin API), так и по AccessKey (auth middleware).
|
||||||
type TenantStore struct {
|
type TenantStore struct {
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
byID map[string]*Tenant
|
byID map[string]*Tenant
|
||||||
byAccessKey map[string]*Tenant
|
byAccessKey map[string]*Tenant
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTenantStore — создаёт пустое хранилище тенантов.
|
// NewTenantStore — создаёт пустое хранилище тенантов.
|
||||||
func NewTenantStore() *TenantStore {
|
func NewTenantStore() *TenantStore {
|
||||||
return &TenantStore{
|
return &TenantStore{
|
||||||
byID: make(map[string]*Tenant),
|
byID: make(map[string]*Tenant),
|
||||||
byAccessKey: make(map[string]*Tenant),
|
byAccessKey: make(map[string]*Tenant),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create — создаёт нового тенанта, генерирует ключи, сохраняет в оба индекса.
|
// Create — создаёт нового тенанта, генерирует ключи, сохраняет в оба индекса.
|
||||||
func (s *TenantStore) Create(name string, maxQueues int) (*Tenant, error) {
|
func (s *TenantStore) Create(name string, maxQueues int) (*Tenant, error) {
|
||||||
id, err := generateTenantID()
|
id, err := generateTenantID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("generate tenant id: %w", err)
|
return nil, fmt.Errorf("generate tenant id: %w", err)
|
||||||
}
|
}
|
||||||
accessKey, err := generateAccessKey()
|
accessKey, err := generateAccessKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("generate access key: %w", err)
|
return nil, fmt.Errorf("generate access key: %w", err)
|
||||||
}
|
}
|
||||||
secretKey, err := generateSecretKey()
|
secretKey, err := generateSecretKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("generate secret key: %w", err)
|
return nil, fmt.Errorf("generate secret key: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
t := &Tenant{
|
t := &Tenant{
|
||||||
ID: id,
|
ID: id,
|
||||||
Name: name,
|
Name: name,
|
||||||
AccessKey: accessKey,
|
AccessKey: accessKey,
|
||||||
SecretKey: secretKey,
|
SecretKey: secretKey,
|
||||||
MaxQueues: maxQueues,
|
MaxQueues: maxQueues,
|
||||||
CreatedAt: time.Now().UTC(),
|
CreatedAt: time.Now().UTC(),
|
||||||
Active: true,
|
Active: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
s.byID[t.ID] = t
|
s.byID[t.ID] = t
|
||||||
s.byAccessKey[t.AccessKey] = t
|
s.byAccessKey[t.AccessKey] = t
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
|
|
||||||
return t, nil
|
return t, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateFixed — создаёт тенанта с заранее известными credentials (для seed/demo).
|
// CreateFixed — создаёт тенанта с заранее известными credentials (для seed/demo).
|
||||||
// Используется только при инициализации демо-данных; не вызывается из user-facing API.
|
// Используется только при инициализации демо-данных; не вызывается из user-facing API.
|
||||||
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()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
if _, exists := s.byID[tenantID]; exists {
|
if _, exists := s.byID[tenantID]; exists {
|
||||||
return nil, fmt.Errorf("tenant with id %s already exists", tenantID)
|
return nil, fmt.Errorf("tenant with id %s already exists", tenantID)
|
||||||
}
|
}
|
||||||
if _, exists := s.byAccessKey[accessKey]; exists {
|
if _, exists := s.byAccessKey[accessKey]; exists {
|
||||||
return nil, fmt.Errorf("tenant with access key %s already exists", accessKey)
|
return nil, fmt.Errorf("tenant with access key %s already exists", accessKey)
|
||||||
}
|
}
|
||||||
t := &Tenant{
|
t := &Tenant{
|
||||||
ID: tenantID,
|
ID: tenantID,
|
||||||
Name: name,
|
Name: name,
|
||||||
AccessKey: accessKey,
|
AccessKey: accessKey,
|
||||||
SecretKey: secretKey,
|
SecretKey: secretKey,
|
||||||
MaxQueues: maxQueues,
|
MaxQueues: maxQueues,
|
||||||
CreatedAt: time.Now().UTC(),
|
CreatedAt: time.Now().UTC(),
|
||||||
Active: true,
|
Active: true,
|
||||||
}
|
}
|
||||||
s.byID[t.ID] = t
|
s.byID[t.ID] = t
|
||||||
s.byAccessKey[t.AccessKey] = t
|
s.byAccessKey[t.AccessKey] = t
|
||||||
return t, nil
|
return t, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetByAccessKey — поиск тенанта по AccessKeyId (используется в auth middleware).
|
// GetByAccessKey — поиск тенанта по AccessKeyId (используется в auth middleware).
|
||||||
func (s *TenantStore) GetByAccessKey(accessKey string) (*Tenant, bool) {
|
func (s *TenantStore) GetByAccessKey(accessKey string) (*Tenant, bool) {
|
||||||
s.mu.RLock()
|
s.mu.RLock()
|
||||||
t, ok := s.byAccessKey[accessKey]
|
t, ok := s.byAccessKey[accessKey]
|
||||||
s.mu.RUnlock()
|
s.mu.RUnlock()
|
||||||
return t, ok
|
return t, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetByID — поиск тенанта по ID (используется в admin API).
|
// GetByID — поиск тенанта по ID (используется в admin API).
|
||||||
func (s *TenantStore) GetByID(id string) (*Tenant, bool) {
|
func (s *TenantStore) GetByID(id string) (*Tenant, bool) {
|
||||||
s.mu.RLock()
|
s.mu.RLock()
|
||||||
t, ok := s.byID[id]
|
t, ok := s.byID[id]
|
||||||
s.mu.RUnlock()
|
s.mu.RUnlock()
|
||||||
return t, ok
|
return t, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete — удаляет тенанта из ОБОИХ индексов.
|
// Delete — удаляет тенанта из ОБОИХ индексов.
|
||||||
// Ловушка #2: если удалить только из одного индекса — orphaned данные и memory leak.
|
// Ловушка #2: если удалить только из одного индекса — orphaned данные и memory leak.
|
||||||
func (s *TenantStore) Delete(id string) bool {
|
func (s *TenantStore) Delete(id string) bool {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
t, ok := s.byID[id]
|
t, ok := s.byID[id]
|
||||||
if !ok {
|
if !ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
delete(s.byID, t.ID)
|
delete(s.byID, t.ID)
|
||||||
delete(s.byAccessKey, t.AccessKey)
|
delete(s.byAccessKey, t.AccessKey)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// List — список всех тенантов (для admin GET /tenants).
|
// List — список всех тенантов (для admin GET /tenants).
|
||||||
func (s *TenantStore) List() []*Tenant {
|
func (s *TenantStore) List() []*Tenant {
|
||||||
s.mu.RLock()
|
s.mu.RLock()
|
||||||
result := make([]*Tenant, 0, len(s.byID))
|
result := make([]*Tenant, 0, len(s.byID))
|
||||||
for _, t := range s.byID {
|
for _, t := range s.byID {
|
||||||
result = append(result, t)
|
result = append(result, t)
|
||||||
}
|
}
|
||||||
s.mu.RUnlock()
|
s.mu.RUnlock()
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// generateTenantID — генерирует уникальный ID тенанта в формате t-<12 hex bytes>.
|
// generateTenantID — генерирует уникальный ID тенанта в формате t-<12 hex bytes>.
|
||||||
func generateTenantID() (string, error) {
|
func generateTenantID() (string, error) {
|
||||||
b := make([]byte, 8)
|
b := make([]byte, 8)
|
||||||
if _, err := rand.Read(b); err != nil {
|
if _, err := rand.Read(b); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return "t-" + hex.EncodeToString(b), nil
|
return "t-" + hex.EncodeToString(b), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// generateAccessKey — генерирует AccessKey в формате SSAK-<12 hex bytes>.
|
// generateAccessKey — генерирует AccessKey в формате SSAK-<12 hex bytes>.
|
||||||
// SSAK = Shared SQS Access Key. Используем crypto/rand (ловушка #1: не math/rand).
|
// SSAK = Shared SQS Access Key. Используем crypto/rand (ловушка #1: не math/rand).
|
||||||
func generateAccessKey() (string, error) {
|
func generateAccessKey() (string, error) {
|
||||||
b := make([]byte, 12)
|
b := make([]byte, 12)
|
||||||
if _, err := rand.Read(b); err != nil {
|
if _, err := rand.Read(b); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return "SSAK-" + hex.EncodeToString(b), nil
|
return "SSAK-" + hex.EncodeToString(b), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// generateSecretKey — генерирует SecretKey как 64 hex символа (32 random bytes).
|
// generateSecretKey — генерирует SecretKey как 64 hex символа (32 random bytes).
|
||||||
// Используем crypto/rand (ловушка #1).
|
// Используем crypto/rand (ловушка #1).
|
||||||
func generateSecretKey() (string, error) {
|
func generateSecretKey() (string, error) {
|
||||||
b := make([]byte, 32)
|
b := make([]byte, 32)
|
||||||
if _, err := rand.Read(b); err != nil {
|
if _, err := rand.Read(b); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return hex.EncodeToString(b), nil
|
return hex.EncodeToString(b), nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user