SOGOMS v1.0.5 - Auto-génération login_data et version UI
- Génération automatique de login_data dans auth.yaml après scan DB - Tables avec filter:owner incluses dans login_data pour login enrichi - Affichage version SOGOMS dans l'interface admin (login + header) - Documentation mise à jour (DOCTECH.md, README.md, TODO.md) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,7 @@ type AdminServer struct {
|
||||
adminCfg *admin.AdminConfig
|
||||
registry *config.Registry
|
||||
sessions *SessionStore
|
||||
version string
|
||||
rateLimiter *RateLimiter
|
||||
perms *admin.PermissionChecker
|
||||
audit *admin.AuditLogger
|
||||
@@ -585,6 +586,14 @@ func (s *AdminServer) HandleAppScanDB(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Mettre à jour login_data dans auth.yaml
|
||||
if err := UpdateLoginData(appID); err != nil {
|
||||
log.Printf("[admin] update login_data error: %v", err)
|
||||
// On ne bloque pas, le scan a réussi
|
||||
} else {
|
||||
log.Printf("[admin] login_data updated for app: %s", appID)
|
||||
}
|
||||
|
||||
// Recharger le registry local
|
||||
if err := s.registry.Load(); err != nil {
|
||||
log.Printf("[admin] reload registry error: %v", err)
|
||||
@@ -614,6 +623,9 @@ func (s *AdminServer) HandleAppScanDB(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *AdminServer) render(w http.ResponseWriter, name string, data map[string]any) {
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
|
||||
// Ajouter la version à toutes les pages
|
||||
data["SogVersion"] = s.version
|
||||
|
||||
tmpl := s.getTemplates()
|
||||
if err := tmpl.ExecuteTemplate(w, name, data); err != nil {
|
||||
log.Printf("[admin] template error: %v", err)
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"sogoms.com/internal/admin"
|
||||
"sogoms.com/internal/config"
|
||||
"sogoms.com/internal/protocol"
|
||||
"sogoms.com/internal/version"
|
||||
)
|
||||
|
||||
//go:embed templates/*.html templates/partials/*.html
|
||||
@@ -99,6 +100,7 @@ func main() {
|
||||
adminCfg: adminCfg,
|
||||
registry: registry,
|
||||
sessions: sessions,
|
||||
version: version.Version,
|
||||
rateLimiter: rateLimiter,
|
||||
perms: perms,
|
||||
audit: audit,
|
||||
|
||||
@@ -417,6 +417,138 @@ func hasUserID(tableData map[string]any) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// UpdateLoginData met à jour le bloc login_data dans auth.yaml
|
||||
// en se basant sur le schema généré (tables avec filter: owner).
|
||||
func UpdateLoginData(appID string) error {
|
||||
// 1. Lire le schema.yaml
|
||||
schemaPath := filepath.Join("/config", "apps", appID, "schema.yaml")
|
||||
schemaData, err := os.ReadFile(schemaPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read schema: %w", err)
|
||||
}
|
||||
|
||||
var schema map[string]any
|
||||
if err := yaml.Unmarshal(schemaData, &schema); err != nil {
|
||||
return fmt.Errorf("parse schema: %w", err)
|
||||
}
|
||||
|
||||
// 2. Identifier les tables avec user_id (filter: owner)
|
||||
tablesRaw, ok := schema["tables"].(map[string]any)
|
||||
if !ok {
|
||||
return nil // Pas de tables, rien à faire
|
||||
}
|
||||
|
||||
loginData := make(map[string]string)
|
||||
tableNames := make([]string, 0, len(tablesRaw))
|
||||
for name := range tablesRaw {
|
||||
tableNames = append(tableNames, name)
|
||||
}
|
||||
sort.Strings(tableNames)
|
||||
|
||||
for _, tableName := range tableNames {
|
||||
tableRaw := tablesRaw[tableName]
|
||||
table, ok := tableRaw.(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// Vérifier si la table a une colonne avec filter: owner
|
||||
columns, ok := table["columns"].(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
hasOwnerFilter := false
|
||||
for _, colRaw := range columns {
|
||||
col, ok := colRaw.(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if filter, ok := col["filter"].(string); ok && filter == "owner" {
|
||||
hasOwnerFilter = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !hasOwnerFilter {
|
||||
continue
|
||||
}
|
||||
|
||||
// Collecter les noms de colonnes (sauf user_id)
|
||||
colNames := make([]string, 0, len(columns))
|
||||
hasPosition := false
|
||||
for colName := range columns {
|
||||
if colName == "user_id" {
|
||||
continue // On n'inclut pas user_id dans le SELECT
|
||||
}
|
||||
colNames = append(colNames, colName)
|
||||
if colName == "position" {
|
||||
hasPosition = true
|
||||
}
|
||||
}
|
||||
sort.Strings(colNames)
|
||||
|
||||
// Mettre id en premier si présent
|
||||
for i, name := range colNames {
|
||||
if name == "id" {
|
||||
colNames = append([]string{"id"}, append(colNames[:i], colNames[i+1:]...)...)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Construire la requête
|
||||
query := fmt.Sprintf("SELECT %s\nFROM %s WHERE user_id = ?",
|
||||
strings.Join(colNames, ", "), tableName)
|
||||
|
||||
// Ajouter ORDER BY si position existe
|
||||
if hasPosition {
|
||||
query += " ORDER BY position"
|
||||
}
|
||||
|
||||
loginData[tableName] = query
|
||||
}
|
||||
|
||||
if len(loginData) == 0 {
|
||||
return nil // Pas de tables owner, rien à générer
|
||||
}
|
||||
|
||||
// 3. Lire auth.yaml existant
|
||||
authPath := filepath.Join("/config", "apps", appID, "queries", "auth.yaml")
|
||||
var existingData map[string]any
|
||||
|
||||
if data, err := os.ReadFile(authPath); err == nil {
|
||||
if err := yaml.Unmarshal(data, &existingData); err != nil {
|
||||
existingData = make(map[string]any)
|
||||
}
|
||||
} else {
|
||||
existingData = make(map[string]any)
|
||||
}
|
||||
|
||||
// 4. Mettre à jour seulement login_data
|
||||
existingData["login_data"] = loginData
|
||||
|
||||
// 5. Réécrire le fichier avec commentaire
|
||||
queriesDir := filepath.Dir(authPath)
|
||||
if err := os.MkdirAll(queriesDir, 0755); err != nil {
|
||||
return fmt.Errorf("create queries dir: %w", err)
|
||||
}
|
||||
|
||||
yamlData, err := yaml.Marshal(existingData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal auth.yaml: %w", err)
|
||||
}
|
||||
|
||||
// Ajouter un header
|
||||
header := "# Requêtes d'authentification\n# login_data généré automatiquement depuis schema.yaml\n\n"
|
||||
finalData := []byte(header + string(yamlData))
|
||||
|
||||
if err := os.WriteFile(authPath, finalData, 0644); err != nil {
|
||||
return fmt.Errorf("write auth.yaml: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReloadGateway demande à sogoctl de recharger sogoway.
|
||||
func (sp *ServicePool) ReloadGateway() error {
|
||||
conn, err := net.DialTimeout("unix", "/run/sogoctl.sock", 2*time.Second)
|
||||
|
||||
@@ -63,6 +63,9 @@
|
||||
|
||||
<button type="submit">Se connecter</button>
|
||||
</form>
|
||||
<footer style="text-align: center; margin-top: 1rem; font-size: 0.8rem; color: var(--pico-muted-color);">
|
||||
v{{.SogVersion}}
|
||||
</footer>
|
||||
</article>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<header class="container">
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/admin/" class="logo"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#1095c1" d="M0,3v8H11V0H3A3,3,0,0,0,0,3Z"/><path fill="#1095c1" d="M21,0H13V11H24V3A3,3,0,0,0,21,0Z"/><path fill="#1095c1" d="M0,21a3,3,0,0,0,3,3h8V13H0Z"/><path fill="#1095c1" d="M13,24h8a3,3,0,0,0,3-3V13H13Z"/></svg>SOGO<span>MS</span></a></li>
|
||||
<li><a href="/admin/" class="logo"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#1095c1" d="M0,3v8H11V0H3A3,3,0,0,0,0,3Z"/><path fill="#1095c1" d="M21,0H13V11H24V3A3,3,0,0,0,21,0Z"/><path fill="#1095c1" d="M0,21a3,3,0,0,0,3,3h8V13H0Z"/><path fill="#1095c1" d="M13,24h8a3,3,0,0,0,3-3V13H13Z"/></svg>SOGO<span>MS</span> <small style="font-weight:normal;color:var(--pico-muted-color)">v{{.SogVersion}}</small></a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
{{if .User}}
|
||||
|
||||
Reference in New Issue
Block a user