package store import ( "strings" "time" "evobgp/internal/authkey" "github.com/google/uuid" ) func (m *Memory) ListAPIKeys(tenantID string) ([]*APIKey, error) { m.mu.RLock() defer m.mu.RUnlock() var out []*APIKey for _, rec := range m.apiKeys { if rec.TenantID == tenantID { out = append(out, apiKeyCopy(&rec.APIKey)) } } return out, nil } func (m *Memory) GetAPIKey(tenantID, id string) (*APIKey, error) { m.mu.RLock() defer m.mu.RUnlock() rec, ok := m.apiKeys[id] if !ok || rec.TenantID != tenantID { return nil, ErrNotFound } return apiKeyCopy(&rec.APIKey), nil } func (m *Memory) CreateAPIKey(tenantID string, in *APIKeyCreate) (*APIKeyWithSecret, error) { if in == nil || strings.TrimSpace(in.Name) == "" || !ValidAPIKeyRole(in.Role) { return nil, ErrInvalidInput } tok, err := authkey.GenerateToken() if err != nil { return nil, err } now := time.Now().UTC() m.mu.Lock() defer m.mu.Unlock() if _, ok := m.tenants[tenantID]; !ok { return nil, ErrTenantScope } id := uuid.NewString() k := &apiKeyRec{ APIKey: APIKey{ ID: id, TenantID: tenantID, Name: strings.TrimSpace(in.Name), Role: strings.ToLower(strings.TrimSpace(in.Role)), Prefix: authkey.Prefix(tok), CreatedAt: now, UpdatedAt: now, ExpiresAt: in.ExpiresAt, }, TokenHash: authkey.HashToken(tok), } m.apiKeys[id] = k return &APIKeyWithSecret{APIKey: *apiKeyCopy(&k.APIKey), Token: tok}, nil } func (m *Memory) UpdateAPIKey(tenantID, id string, patch *APIKeyPatch) (*APIKey, error) { if patch == nil { return nil, ErrInvalidInput } m.mu.Lock() defer m.mu.Unlock() rec, ok := m.apiKeys[id] if !ok || rec.TenantID != tenantID { return nil, ErrNotFound } if rec.RevokedAt != nil { return nil, ErrInvalidInput } if patch.Name != nil { n := strings.TrimSpace(*patch.Name) if n == "" { return nil, ErrInvalidInput } rec.Name = n } if patch.Role != nil { if !ValidAPIKeyRole(*patch.Role) { return nil, ErrInvalidInput } rec.Role = strings.ToLower(strings.TrimSpace(*patch.Role)) } if patch.ClearExpiresAt { rec.ExpiresAt = nil } else if patch.ExpiresAt != nil { rec.ExpiresAt = patch.ExpiresAt } rec.UpdatedAt = time.Now().UTC() return apiKeyCopy(&rec.APIKey), nil } func (m *Memory) RevokeAPIKey(tenantID, id string) error { m.mu.Lock() defer m.mu.Unlock() rec, ok := m.apiKeys[id] if !ok || rec.TenantID != tenantID { return ErrNotFound } now := time.Now().UTC() rec.RevokedAt = &now rec.UpdatedAt = now return nil } func (m *Memory) RotateAPIKey(tenantID, id string) (*APIKeyWithSecret, error) { m.mu.Lock() defer m.mu.Unlock() rec, ok := m.apiKeys[id] if !ok || rec.TenantID != tenantID { return nil, ErrNotFound } if rec.RevokedAt != nil { return nil, ErrInvalidInput } tok, err := authkey.GenerateToken() if err != nil { return nil, err } now := time.Now().UTC() rec.TokenHash = authkey.HashToken(tok) rec.Prefix = authkey.Prefix(tok) rec.UpdatedAt = now return &APIKeyWithSecret{APIKey: *apiKeyCopy(&rec.APIKey), Token: tok}, nil } func (m *Memory) ListActiveAPIKeyHashes() ([]APIKeyAuthRow, error) { m.mu.RLock() defer m.mu.RUnlock() now := time.Now().UTC() var out []APIKeyAuthRow for _, rec := range m.apiKeys { if rec.RevokedAt != nil { continue } if rec.ExpiresAt != nil && !rec.ExpiresAt.After(now) { continue } out = append(out, APIKeyAuthRow{ ID: rec.ID, TenantID: rec.TenantID, Role: rec.Role, TokenHash: append([]byte(nil), rec.TokenHash...), }) } return out, nil } func (m *Memory) TouchAPIKeyLastUsed(id string) error { m.mu.Lock() defer m.mu.Unlock() rec, ok := m.apiKeys[id] if !ok { return ErrNotFound } now := time.Now().UTC() rec.LastUsedAt = &now return nil } func apiKeyCopy(k *APIKey) *APIKey { cp := *k if k.ExpiresAt != nil { t := *k.ExpiresAt cp.ExpiresAt = &t } if k.RevokedAt != nil { t := *k.RevokedAt cp.RevokedAt = &t } if k.LastUsedAt != nil { t := *k.LastUsedAt cp.LastUsedAt = &t } return &cp }