mirror of
https://github.com/syntrex-lab/gomcp.git
synced 2026-06-29 15:49:38 +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
|
// Check if demo user already exists
|
||||||
if _, err := userStore.GetByEmail(DemoUserEmail); err == nil {
|
if _, err := userStore.GetByEmail(DemoUserEmail); err == nil {
|
||||||
slog.Debug("demo tenant already seeded", "email", DemoUserEmail)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -50,12 +60,12 @@ func SeedDemoTenant(userStore *UserStore, tenantStore *TenantStore, socRepo doms
|
||||||
userStore.persistUser(demoUser)
|
userStore.persistUser(demoUser)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Create demo tenant (starter plan)
|
// 2. Create demo tenant (demo plan 1000 events max)
|
||||||
demoTenant := &Tenant{
|
demoTenant := &Tenant{
|
||||||
ID: DemoTenantID,
|
ID: DemoTenantID,
|
||||||
Name: "SYNTREX Demo",
|
Name: "SYNTREX Demo",
|
||||||
Slug: "demo",
|
Slug: "demo",
|
||||||
PlanID: "starter",
|
PlanID: "demo",
|
||||||
OwnerUserID: demoUser.ID,
|
OwnerUserID: demoUser.ID,
|
||||||
Active: true,
|
Active: true,
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
|
|
|
||||||
|
|
@ -212,14 +212,14 @@ func HandleMe(store *UserStore) http.HandlerFunc {
|
||||||
func HandleListUsers(store *UserStore) http.HandlerFunc {
|
func HandleListUsers(store *UserStore) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
claims := GetClaims(r.Context())
|
claims := GetClaims(r.Context())
|
||||||
if claims == nil || claims.Role != "admin" {
|
if claims == nil || (claims.Role != "admin" && claims.Role != "superadmin") {
|
||||||
writeAuthError(w, http.StatusForbidden, "admin role required")
|
writeAuthError(w, http.StatusForbidden, "admin or superadmin role required")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// SEC-HIGH1: Block listing when TenantID is empty — prevents
|
// SEC-HIGH1: Block listing when TenantID is empty — prevents
|
||||||
// empty-string match showing all users without a tenant.
|
// 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")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
json.NewEncoder(w).Encode(map[string]any{
|
json.NewEncoder(w).Encode(map[string]any{
|
||||||
"users": []*User{},
|
"users": []*User{},
|
||||||
|
|
@ -232,7 +232,7 @@ func HandleListUsers(store *UserStore) http.HandlerFunc {
|
||||||
allUsers := store.ListUsers()
|
allUsers := store.ListUsers()
|
||||||
var filtered []*User
|
var filtered []*User
|
||||||
for _, u := range allUsers {
|
for _, u := range allUsers {
|
||||||
if u.TenantID == claims.TenantID {
|
if claims.Role == "superadmin" || u.TenantID == claims.TenantID {
|
||||||
filtered = append(filtered, u)
|
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
|
// GET /api/auth/keys
|
||||||
func HandleListAPIKeys(store *UserStore) http.HandlerFunc {
|
func HandleListAPIKeys(store *UserStore) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
@ -415,7 +415,13 @@ func HandleListAPIKeys(store *UserStore) http.HandlerFunc {
|
||||||
return
|
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 {
|
if err != nil {
|
||||||
writeAuthError(w, http.StatusInternalServerError, err.Error())
|
writeAuthError(w, http.StatusInternalServerError, err.Error())
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,15 @@ var DefaultPlans = map[string]Plan{
|
||||||
SOCEnabled: false, SLAEnabled: false, SOAREnabled: false, ComplianceEnabled: false,
|
SOCEnabled: false, SLAEnabled: false, SOAREnabled: false, ComplianceEnabled: false,
|
||||||
PriceMonthCents: 0,
|
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": {
|
"starter": {
|
||||||
ID: "starter", Name: "Starter",
|
ID: "starter", Name: "Starter",
|
||||||
Description: "AI-мониторинг: до 5 сенсоров, базовая корреляция и алерты",
|
Description: "AI-мониторинг: до 5 сенсоров, базовая корреляция и алерты",
|
||||||
|
|
|
||||||
|
|
@ -489,6 +489,32 @@ func (s *UserStore) ListAPIKeys(userID string) ([]APIKey, error) {
|
||||||
return keys, nil
|
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.
|
// DeleteAPIKey revokes an API key.
|
||||||
func (s *UserStore) DeleteAPIKey(keyID, userID string) error {
|
func (s *UserStore) DeleteAPIKey(keyID, userID string) error {
|
||||||
if s.db == nil {
|
if s.db == nil {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue