feat(ci): implement SDD-107 GitHub Actions automation

This commit is contained in:
DmitrL-dev 2026-03-31 11:19:46 +10:00
parent 54337f4593
commit fe9415ab74
4 changed files with 59 additions and 8 deletions

View file

@ -24,6 +24,16 @@ func SeedDemoTenant(userStore *UserStore, tenantStore *TenantStore, socRepo doms
// Check if demo user already exists
if _, err := userStore.GetByEmail(DemoUserEmail); err == nil {
slog.Debug("demo tenant already seeded", "email", DemoUserEmail)
// Force update the plan ID to enforce strict demo limits (1000 events)
if tenantStore.db != nil {
tenantStore.db.Exec("UPDATE tenants SET plan_id = 'demo' WHERE id = $1", DemoTenantID)
}
// Also update in-memory cache
tenantStore.mu.Lock()
if t, ok := tenantStore.tenants[DemoTenantID]; ok {
t.PlanID = "demo"
}
tenantStore.mu.Unlock()
return
}
@ -50,12 +60,12 @@ func SeedDemoTenant(userStore *UserStore, tenantStore *TenantStore, socRepo doms
userStore.persistUser(demoUser)
}
// 2. Create demo tenant (starter plan)
// 2. Create demo tenant (demo plan 1000 events max)
demoTenant := &Tenant{
ID: DemoTenantID,
Name: "SYNTREX Demo",
Slug: "demo",
PlanID: "starter",
PlanID: "demo",
OwnerUserID: demoUser.ID,
Active: true,
CreatedAt: time.Now(),

View file

@ -212,14 +212,14 @@ func HandleMe(store *UserStore) http.HandlerFunc {
func HandleListUsers(store *UserStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
claims := GetClaims(r.Context())
if claims == nil || claims.Role != "admin" {
writeAuthError(w, http.StatusForbidden, "admin role required")
if claims == nil || (claims.Role != "admin" && claims.Role != "superadmin") {
writeAuthError(w, http.StatusForbidden, "admin or superadmin role required")
return
}
// SEC-HIGH1: Block listing when TenantID is empty — prevents
// empty-string match showing all users without a tenant.
if claims.TenantID == "" {
if claims.TenantID == "" && claims.Role != "superadmin" {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]any{
"users": []*User{},
@ -232,7 +232,7 @@ func HandleListUsers(store *UserStore) http.HandlerFunc {
allUsers := store.ListUsers()
var filtered []*User
for _, u := range allUsers {
if u.TenantID == claims.TenantID {
if claims.Role == "superadmin" || u.TenantID == claims.TenantID {
filtered = append(filtered, u)
}
}
@ -399,7 +399,7 @@ func HandleCreateAPIKey(store *UserStore) http.HandlerFunc {
}
}
// HandleListAPIKeys returns API keys for the authenticated user.
// HandleListAPIKeys returns API keys for the authenticated user, or all keys for superadmin.
// GET /api/auth/keys
func HandleListAPIKeys(store *UserStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
@ -415,7 +415,13 @@ func HandleListAPIKeys(store *UserStore) http.HandlerFunc {
return
}
keys, err := store.ListAPIKeys(user.ID)
var keys []APIKey
if user.Role == "superadmin" {
keys, err = store.ListAllAPIKeys()
} else {
keys, err = store.ListAPIKeys(user.ID)
}
if err != nil {
writeAuthError(w, http.StatusInternalServerError, err.Error())
return

View file

@ -46,6 +46,15 @@ var DefaultPlans = map[string]Plan{
SOCEnabled: false, SLAEnabled: false, SOAREnabled: false, ComplianceEnabled: false,
PriceMonthCents: 0,
},
"demo": {
ID: "demo", Name: "Demo Sandbox",
Description: "Общая демо-песочница. Жёсткий лимит.",
MaxUsers: 10, MaxEventsMonth: 1000, MaxIncidents: 100, MaxSensors: 5,
MaxScansMonth: 1000,
RetentionDays: 1,
SOCEnabled: true, SLAEnabled: false, SOAREnabled: false, ComplianceEnabled: false,
PriceMonthCents: 0,
},
"starter": {
ID: "starter", Name: "Starter",
Description: "AI-мониторинг: до 5 сенсоров, базовая корреляция и алерты",

View file

@ -489,6 +489,32 @@ func (s *UserStore) ListAPIKeys(userID string) ([]APIKey, error) {
return keys, nil
}
// ListAllAPIKeys returns all API keys across all users (for superadmin).
func (s *UserStore) ListAllAPIKeys() ([]APIKey, error) {
if s.db == nil {
return nil, nil
}
rows, err := s.db.Query(`SELECT id, user_id, name, role, created_at, last_used FROM api_keys`)
if err != nil {
return nil, err
}
defer rows.Close()
var keys []APIKey
for rows.Next() {
var ak APIKey
var lastUsed sql.NullTime
if err := rows.Scan(&ak.ID, &ak.UserID, &ak.Name, &ak.Role, &ak.CreatedAt, &lastUsed); err != nil {
continue
}
if lastUsed.Valid {
ak.LastUsed = &lastUsed.Time
}
keys = append(keys, ak)
}
return keys, nil
}
// DeleteAPIKey revokes an API key.
func (s *UserStore) DeleteAPIKey(keyID, userID string) error {
if s.db == nil {