414 lines
16 KiB
Go
414 lines
16 KiB
Go
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)
|
|
}
|
|
}
|