package main import ( "encoding/json" "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" "log" "net/http" "os" "path/filepath" "time" "user-management/auth" "user-management/system" ) func main() { // 初始化認證服務 dbPath := os.Getenv("DB_PATH") if dbPath == "" { dbPath = "/data/admin.db" } authService, err := auth.NewAuthService(dbPath) if err != nil { log.Fatal(err) } // 獲取 socket 路徑 socketPath := os.Getenv("SOCKET_PATH") if socketPath == "" { socketPath = "/var/run/usermgmt.sock" } // 創建系統客戶端 client := system.NewSystemClient(socketPath) r := gin.Default() // CORS 中間件 // Apply the CORS middleware r.Use(cors.New(cors.Config{ AllowOrigins: []string{"*"}, // Allow only frontend origin AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, AllowHeaders: []string{"Content-Type", "Authorization"}, ExposeHeaders: []string{"Content-Length"}, AllowCredentials: true, MaxAge: 12 * time.Hour, })) r.Static("/static", "./markdowns") // Dynamic endpoint to serve markdown content r.GET("/api/markdowns/:filename", func(c *gin.Context) { filename := c.Param("filename") filePath := filepath.Join("markdowns", filename) // Read the markdown file content, err := os.ReadFile(filePath) if err != nil { // Handle file not found or read error c.JSON(http.StatusNotFound, gin.H{"error": "File not found"}) return } // Serve the file content c.Data(http.StatusOK, "text/markdown; charset=utf-8", content) }) // 管理員認證 API r.POST("/api/admin/login", func(c *gin.Context) { var req struct { Username string `json:"username"` Password string `json:"password"` } if err := c.BindJSON(&req); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } admin, err := authService.ValidateAdmin(req.Username, req.Password) if err != nil { c.JSON(401, gin.H{"error": "Invalid credentials"}) return } token, err := auth.GenerateToken(admin.Username) if err != nil { c.JSON(500, gin.H{"error": "Failed to generate token"}) return } c.JSON(200, gin.H{ "token": token, "admin": admin, }) }) // 管理員 CRUD API adminAPI := r.Group("/api/admin") adminAPI.GET("/admins", func(c *gin.Context) { admins, err := authService.GetAdmins() if err != nil { c.JSON(500, gin.H{"error": err.Error()}) return } c.JSON(200, admins) }) adminAPI.POST("/admins", func(c *gin.Context) { var req struct { Username string `json:"username"` Password string `json:"password"` } if err := c.BindJSON(&req); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } err := authService.CreateAdmin(req.Username, req.Password) if err != nil { c.JSON(500, gin.H{"error": err.Error()}) return } c.JSON(200, gin.H{"message": "Admin created successfully"}) }) adminAPI.Use(auth.JWTAuthMiddleware()) { adminAPI.PUT("/admins/:id/password", func(c *gin.Context) { id := c.Param("id") var req struct { Password string `json:"password"` } if err := c.BindJSON(&req); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } err := authService.UpdateAdminPassword(id, req.Password) if err != nil { c.JSON(500, gin.H{"error": err.Error()}) return } c.JSON(200, gin.H{"message": "Password updated successfully"}) }) adminAPI.PUT("/admins/:id/active", func(c *gin.Context) { id := c.Param("id") var req struct { IsActive bool `json:"is_active"` } if err := c.BindJSON(&req); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } err := authService.UpdateAdminActivate(id, req.IsActive) if err != nil { c.JSON(500, gin.H{"error": err.Error()}) return } c.JSON(200, gin.H{"message": "Admin active status updated successfully"}) }) adminAPI.DELETE("/admins/:id", func(c *gin.Context) { id := c.Param("id") err := authService.DeleteAdmin(id) if err != nil { c.JSON(500, gin.H{"error": err.Error()}) return } c.JSON(200, gin.H{"message": "Admin deleted successfully"}) }) } sambaConfigAPI := r.Group("/api/samba/config") sambaConfigAPI.Use(auth.JWTAuthMiddleware()) { sambaConfigAPI.GET("/global_setting", func(c *gin.Context) { settings, err := client.GetSambaGlobalSetting() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } // Unmarshal the JSON string into a map var settingsMap map[string]interface{} if err := json.Unmarshal([]byte(settings), &settingsMap); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to parse settings JSON"}) return } c.JSON(http.StatusOK, settingsMap["global"]) }) sambaConfigAPI.GET("/section_setting", func(c *gin.Context) { settings, err := client.GetSambaSectionSetting() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } // Unmarshal the JSON string into a map var settingsMap map[string]interface{} if err := json.Unmarshal([]byte(settings), &settingsMap); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to parse settings JSON"}) return } c.JSON(http.StatusOK, settingsMap["sections"]) }) sambaConfigAPI.POST("/update_section_setting", func(c *gin.Context) { // Read the request body var reqBody struct { SectionSettings string `json:"section_settings"` // Expecting a JSON string } if err := c.BindJSON(&reqBody); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"}) return } // Call the UpdateSambaSectionSetting function err := client.UpdateSambaSectionSetting(reqBody.SectionSettings) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "Section settings updated successfully"}) }) sambaConfigAPI.POST("/update_global_setting", func(c *gin.Context) { // Read the request body var reqBody struct { GlobalSettings string `json:"global_settings"` // Expecting a JSON string } if err := c.BindJSON(&reqBody); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"}) return } // Call the UpdateSambaGlobalSetting function err := client.UpdateSambaGlobalSetting(reqBody.GlobalSettings) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "Global settings updated successfully"}) }) sambaConfigAPI.GET("/status", func(c *gin.Context) { _, err := client.GetSambaServiceInfo() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, "{\"timestamp\": \"2025-01-26T20:21:50.949817+0800\", \"version\": \"4.19.5-Ubuntu\", \"smb_conf\": \"/etc/samba/smb.conf\", \"sessions\": {\"4116888046\": {\"session_id\": \"4116888046\", \"server_id\": {\"pid\": \"1805949\", \"task_id\": \"0\", \"vnn\": \"4294967295\", \"unique_id\": \"469723148854225877\"}, \"uid\": 1001, \"gid\": 1001, \"username\": \"jing\", \"groupname\": \"jing\", \"remote_machine\": \"192.168.0.1\", \"hostname\": \"ipv4:192.168.0.1:60343\", \"session_dialect\": \"SMB3_11\", \"encryption\": {\"cipher\": \"-\", \"degree\": \"none\"}, \"signing\": {\"cipher\": \"AES-128-GMAC\", \"degree\": \"partial\"}}}, \"tcons\": {\"2344016500\": {\"service\": \"share_data\", \"server_id\": {\"pid\": \"1805949\", \"task_id\": \"0\", \"vnn\": \"4294967295\", \"unique_id\": \"469723148854225877\"}, \"tcon_id\": \"2344016500\", \"session_id\": \"4116888046\", \"machine\": \"192.168.0.1\", \"connected_at\": \"2025-01-26T17:11:07.474663+08:00\", \"encryption\": {\"cipher\": \"-\", \"degree\": \"none\"}, \"signing\": {\"cipher\": \"-\", \"degree\": \"none\"}}, \"3876116464\": {\"service\": \"public_data\", \"server_id\": {\"pid\": \"1805949\", \"task_id\": \"0\", \"vnn\": \"4294967295\", \"unique_id\": \"469723148854225877\"}, \"tcon_id\": \"3876116464\", \"session_id\": \"4116888046\", \"machine\": \"192.168.0.1\", \"connected_at\": \"2025-01-26T17:11:07.477214+08:00\", \"encryption\": {\"cipher\": \"-\", \"degree\": \"none\"}, \"signing\": {\"cipher\": \"-\", \"degree\": \"none\"}}}, \"open_files\": {\"/samba/share_data/.\": {\"service_path\": \"/samba/share_data\", \"filename\": \".\", \"fileid\": {\"devid\": 2050, \"inode\": 43646978, \"extid\": 0}, \"num_pending_deletes\": 0, \"opens\": {\"1805949/39\": {\"server_id\": {\"pid\": \"1805949\", \"task_id\": \"0\", \"vnn\": \"4294967295\", \"unique_id\": \"469723148854225877\"}, \"uid\": 65534, \"share_file_id\": \"39\", \"sharemode\": {\"hex\": \"0x00000007\", \"READ\": true, \"WRITE\": true, \"DELETE\": true, \"text\": \"RWD\"}, \"access_mask\": {\"hex\": \"0x00100081\", \"READ_DATA\": true, \"WRITE_DATA\": false, \"APPEND_DATA\": false, \"READ_EA\": false, \"WRITE_EA\": false, \"EXECUTE\": false, \"READ_ATTRIBUTES\": true, \"WRITE_ATTRIBUTES\": false, \"DELETE_CHILD\": false, \"DELETE\": false, \"READ_CONTROL\": false, \"WRITE_DAC\": false, \"SYNCHRONIZE\": true, \"ACCESS_SYSTEM_SECURITY\": false, \"text\": \"R\"}, \"caching\": {\"READ\": false, \"WRITE\": false, \"HANDLE\": false, \"hex\": \"0x00000000\", \"text\": \"\"}, \"oplock\": {}, \"lease\": {}, \"opened_at\": \"2025-01-26T17:11:08.520410+08:00\"}, \"1805949/27\": {\"server_id\": {\"pid\": \"1805949\", \"task_id\": \"0\", \"vnn\": \"4294967295\", \"unique_id\": \"469723148854225877\"}, \"uid\": 65534, \"share_file_id\": \"27\", \"sharemode\": {\"hex\": \"0x00000007\", \"READ\": true, \"WRITE\": true, \"DELETE\": true, \"text\": \"RWD\"}, \"access_mask\": {\"hex\": \"0x00100081\", \"READ_DATA\": true, \"WRITE_DATA\": false, \"APPEND_DATA\": false, \"READ_EA\": false, \"WRITE_EA\": false, \"EXECUTE\": false, \"READ_ATTRIBUTES\": true, \"WRITE_ATTRIBUTES\": false, \"DELETE_CHILD\": false, \"DELETE\": false, \"READ_CONTROL\": false, \"WRITE_DAC\": false, \"SYNCHRONIZE\": true, \"ACCESS_SYSTEM_SECURITY\": false, \"text\": \"R\"}, \"caching\": {\"READ\": false, \"WRITE\": false, \"HANDLE\": false, \"hex\": \"0x00000000\", \"text\": \"\"}, \"oplock\": {}, \"lease\": {}, \"opened_at\": \"2025-01-26T17:11:08.458851+08:00\"}, \"1805949/13\": {\"server_id\": {\"pid\": \"1805949\", \"task_id\": \"0\", \"vnn\": \"4294967295\", \"unique_id\": \"469723148854225877\"}, \"uid\": 65534, \"share_file_id\": \"13\", \"sharemode\": {\"hex\": \"0x00000007\", \"READ\": true, \"WRITE\": true, \"DELETE\": true, \"text\": \"RWD\"}, \"access_mask\": {\"hex\": \"0x00100081\", \"READ_DATA\": true, \"WRITE_DATA\": false, \"APPEND_DATA\": false, \"READ_EA\": false, \"WRITE_EA\": false, \"EXECUTE\": false, \"READ_ATTRIBUTES\": true, \"WRITE_ATTRIBUTES\": false, \"DELETE_CHILD\": false, \"DELETE\": false, \"READ_CONTROL\": false, \"WRITE_DAC\": false, \"SYNCHRONIZE\": true, \"ACCESS_SYSTEM_SECURITY\": false, \"text\": \"R\"}, \"caching\": {\"READ\": false, \"WRITE\": false, \"HANDLE\": false, \"hex\": \"0x00000000\", \"text\": \"\"}, \"oplock\": {}, \"lease\": {}, \"opened_at\": \"2025-01-26T17:11:08.445320+08:00\"}}}, \"/samba/lab_data/.\": {\"service_path\": \"/samba/lab_data\", \"filename\": \".\", \"fileid\": {\"devid\": 2050, \"inode\": 43654374, \"extid\": 0}, \"num_pending_deletes\": 0, \"opens\": {\"1805949/3\": {\"server_id\": {\"pid\": \"1805949\", \"task_id\": \"0\", \"vnn\": \"4294967295\", \"unique_id\": \"469723148854225877\"}, \"uid\": 65534, \"share_file_id\": \"3\", \"sharemode\": {\"hex\": \"0x00000000\", \"READ\": false, \"WRITE\": false, \"DELETE\": false, \"text\": \"\"}, \"access_mask\": {\"hex\": \"0x00100080\", \"READ_DATA\": false, \"WRITE_DATA\": false, \"APPEND_DATA\": false, \"READ_EA\": false, \"WRITE_EA\": false, \"EXECUTE\": false, \"READ_ATTRIBUTES\": true, \"WRITE_ATTRIBUTES\": false, \"DELETE_CHILD\": false, \"DELETE\": false, \"READ_CONTROL\": false, \"WRITE_DAC\": false, \"SYNCHRONIZE\": true, \"ACCESS_SYSTEM_SECURITY\": false, \"text\": \"\"}, \"caching\": {\"READ\": false, \"WRITE\": false, \"HANDLE\": false, \"hex\": \"0x00000000\", \"text\": \"\"}, \"oplock\": {}, \"lease\": {}, \"opened_at\": \"2025-01-26T17:11:07.479460+08:00\"}}}}}\n") }) sambaConfigAPI.POST("/restart_samba", func(c *gin.Context) { err := client.RestartSambaService() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "Success restart samba service"}) }) } // 用戶管理 API r.GET("/api/users", func(c *gin.Context) { users, err := client.GetUsers() if err != nil { c.JSON(500, gin.H{"error": err.Error()}) return } c.JSON(200, users) }) r.POST("/api/users", func(c *gin.Context) { var req struct { Username string `json:"username"` Password string `json:"password"` Group string `json:"group"` IsAdmin bool `json:"is_admin"` Shell string `json:"shell"` } if err := c.BindJSON(&req); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } err := client.CreateUser(req.Username, req.Password, req.Group, req.IsAdmin, req.Shell) if err != nil { c.JSON(500, gin.H{"error": err.Error()}) return } c.JSON(200, gin.H{"message": "User created successfully"}) }) r.PUT("/api/users/:username", func(c *gin.Context) { username := c.Param("username") var req struct { Password string `json:"password"` } if err := c.BindJSON(&req); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } err := client.ModifyUserPasswd(username, req.Password) if err != nil { c.JSON(500, gin.H{"error": err.Error()}) return } c.JSON(200, gin.H{"message": "User modified successfully"}) }) r.DELETE("/api/users/:username", func(c *gin.Context) { username := c.Param("username") err := client.DeleteUser(username) if err != nil { c.JSON(500, gin.H{"error": err.Error()}) return } c.JSON(200, gin.H{"message": "User deleted successfully"}) }) r.GET("/api/groups", func(c *gin.Context) { groups, err := client.GetGroups() if err != nil { c.JSON(500, gin.H{"error": err.Error()}) return } c.JSON(200, groups) }) r.GET("/api/groups/:username", func(c *gin.Context) { username := c.Param("username") groups, err := client.GetUserGroups(username) if err != nil { c.JSON(500, gin.H{"error": err.Error()}) return } c.JSON(200, groups) }) // Add user to multiple groups endpoint r.POST("/api/groups/:username", func(c *gin.Context) { username := c.Param("username") var req struct { Groups []string `json:"groups"` } if err := c.BindJSON(&req); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } // Add user to each group err := client.AddUserToGroups(username, req.Groups) if err != nil { c.JSON(500, gin.H{"error": err.Error()}) return } c.JSON(200, gin.H{"message": "User added to groups successfully"}) }) // Remove user from a group endpoint r.DELETE("/api/groups/:username", func(c *gin.Context) { username := c.Param("username") var req struct { Group string `json:"group"` } if err := c.BindJSON(&req); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } err := client.RemoveUserFromGroup(username, req.Group) if err != nil { c.JSON(500, gin.H{"error": err.Error()}) return } c.JSON(200, gin.H{"message": "Group removed successfully"}) }) // Samba 連接 API r.GET("/api/samba-connections", func(c *gin.Context) { connections, err := client.GetSambaConnections() if err != nil { c.JSON(500, gin.H{"error": err.Error()}) return } c.JSON(200, connections) }) // 啟動服務器 port := os.Getenv("PORT") if port == "" { port = "8080" } log.Printf("Server starting on port %s", port) if err := r.Run(":" + port); err != nil { log.Fatal(err) } }