mirror of
https://github.com/syntrex-lab/gomcp.git
synced 2026-04-25 04:16:22 +02:00
feat(ci): implement SDD-107 GitHub Actions automation
This commit is contained in:
parent
54337f4593
commit
fe9415ab74
4 changed files with 59 additions and 8 deletions
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 сенсоров, базовая корреляция и алерты",
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue