diff --git a/handlers/admin_handler.go b/handlers/admin_handler.go index 4fac5b9..9576ca8 100644 --- a/handlers/admin_handler.go +++ b/handlers/admin_handler.go @@ -1,6 +1,7 @@ package handlers import ( + "math" "net/http" "strconv" @@ -11,11 +12,12 @@ import ( ) type AdminHandler struct { - adminService *services.AdminService + adminService *services.AdminService + clientService *services.ClientService } -func NewAdminHandler(adminService *services.AdminService) *AdminHandler { - return &AdminHandler{adminService: adminService} +func NewAdminHandler(adminService *services.AdminService, clientService *services.ClientService) *AdminHandler { + return &AdminHandler{adminService: adminService, clientService: clientService} } func (h *AdminHandler) ShowAdminLogin(c *gin.Context) { @@ -63,20 +65,153 @@ func (h *AdminHandler) ListUsers(c *gin.Context) { }) } +// ListClients 显示客户端列表页面 func (h *AdminHandler) ListClients(c *gin.Context) { - page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) - pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10")) + page := 1 + pageSize := 10 - clients, total, err := h.adminService.ListClients(page, pageSize) + // 从查询参数获取分页信息 + if pageStr := c.Query("page"); pageStr != "" { + if p, err := strconv.Atoi(pageStr); err == nil && p > 0 { + page = p + } + } + if pageSizeStr := c.Query("page_size"); pageSizeStr != "" { + if ps, err := strconv.Atoi(pageSizeStr); err == nil && ps > 0 { + pageSize = ps + } + } + + // 获取客户端列表 + clients, total, err := h.clientService.GetClients(page, pageSize) if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + c.HTML(http.StatusInternalServerError, "error.html", gin.H{ + "error": err.Error(), + }) return } c.HTML(http.StatusOK, "admin_clients.html", gin.H{ "clients": clients, - "total": total, "page": page, "pageSize": pageSize, + "total": total, + "lastPage": int(math.Ceil(float64(total) / float64(pageSize))), }) } + +// ShowCreateUser 显示创建用户页面 +func (h *AdminHandler) ShowCreateUser(c *gin.Context) { + c.HTML(http.StatusOK, "admin_create_user.html", gin.H{ + "title": "创建用户", + }) +} + +// HandleCreateUser 处理创建用户请求 +func (h *AdminHandler) HandleCreateUser(c *gin.Context) { + username := c.PostForm("username") + password := c.PostForm("password") + email := c.PostForm("email") + + if username == "" || password == "" || email == "" { + c.HTML(http.StatusBadRequest, "admin_create_user.html", gin.H{ + "title": "创建用户", + "error": "用户名、密码和邮箱都不能为空", + }) + return + } + + _, err := h.adminService.CreateUser(username, password, email) + if err != nil { + c.HTML(http.StatusBadRequest, "admin_create_user.html", gin.H{ + "title": "创建用户", + "error": "创建用户失败:" + err.Error(), + }) + return + } + + c.Redirect(http.StatusFound, "/admin/users") +} + +// ShowEditUser 显示编辑用户页面 +func (h *AdminHandler) ShowEditUser(c *gin.Context) { + userID, err := strconv.ParseUint(c.Param("id"), 10, 32) + if err != nil { + c.HTML(http.StatusBadRequest, "error.html", gin.H{ + "error": "无效的用户ID", + }) + return + } + + user, err := h.adminService.GetUser(uint(userID)) + if err != nil { + c.HTML(http.StatusNotFound, "error.html", gin.H{ + "error": "用户不存在", + }) + return + } + + c.HTML(http.StatusOK, "admin_edit_user.html", gin.H{ + "title": "编辑用户", + "user": user, + }) +} + +// HandleEditUser 处理编辑用户请求 +func (h *AdminHandler) HandleEditUser(c *gin.Context) { + userID, err := strconv.ParseUint(c.Param("id"), 10, 32) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "无效的用户ID"}) + return + } + + username := c.PostForm("username") + email := c.PostForm("email") + isActiveStr := c.PostForm("is_active") + var isActive *bool + if isActiveStr != "" { + active := isActiveStr == "true" + isActive = &active + } + + _, err = h.adminService.UpdateUser(uint(userID), username, email, isActive) + if err != nil { + c.HTML(http.StatusBadRequest, "admin_edit_user.html", gin.H{ + "title": "编辑用户", + "error": "更新用户失败:" + err.Error(), + }) + return + } + + // 如果提供了新密码,则更新密码 + newPassword := c.PostForm("password") + if newPassword != "" { + err = h.adminService.UpdateUserPassword(uint(userID), newPassword) + if err != nil { + c.HTML(http.StatusBadRequest, "admin_edit_user.html", gin.H{ + "title": "编辑用户", + "error": "更新密码失败:" + err.Error(), + }) + return + } + } + + c.Redirect(http.StatusFound, "/admin/users") +} + +// HandleDeleteUser 处理删除用户请求 +func (h *AdminHandler) HandleDeleteUser(c *gin.Context) { + userID, err := strconv.ParseUint(c.Param("id"), 10, 32) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "无效的用户ID"}) + return + } + + err = h.adminService.DeleteUser(uint(userID)) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "删除用户失败:" + err.Error()}) + return + } + + c.Redirect(http.StatusFound, "/admin/users") +} diff --git a/handlers/client_handler.go b/handlers/client_handler.go new file mode 100644 index 0000000..ea06fcb --- /dev/null +++ b/handlers/client_handler.go @@ -0,0 +1,75 @@ +package handlers + +import ( + "net/http" + + "oidc-oauth2-server/services" + + "github.com/gin-gonic/gin" +) + +type ClientHandler struct { + clientService *services.ClientService +} + +func NewClientHandler(clientService *services.ClientService) *ClientHandler { + return &ClientHandler{clientService: clientService} +} + +// RegisterRoutes 注册路由 +func (h *ClientHandler) RegisterRoutes(router *gin.Engine) { + api := router.Group("/api") + { + api.POST("/clients", h.CreateClient) + api.PUT("/clients/:id", h.UpdateClient) + api.DELETE("/clients/:id", h.DeleteClient) + } +} + +// CreateClient 创建客户端 +func (h *ClientHandler) CreateClient(c *gin.Context) { + var req services.ClientRegistrationRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + client, err := h.clientService.RegisterClient(&req) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusCreated, client) +} + +// UpdateClient 更新客户端 +func (h *ClientHandler) UpdateClient(c *gin.Context) { + clientID := c.Param("id") + + var req services.ClientRegistrationRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + client, err := h.clientService.UpdateClient(clientID, &req) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, client) +} + +// DeleteClient 删除客户端 +func (h *ClientHandler) DeleteClient(c *gin.Context) { + clientID := c.Param("id") + + if err := h.clientService.DeleteClient(clientID); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.Status(http.StatusOK) +} diff --git a/main.go b/main.go index 1c10a4f..5e43504 100644 --- a/main.go +++ b/main.go @@ -56,6 +56,9 @@ func main() { "add": func(a, b int) int { return a + b }, + "multiply": func(a, b int) int { + return a * b + }, }) r.LoadHTMLGlob("templates/*") @@ -75,7 +78,8 @@ func main() { oidcHandler := handlers.NewOIDCHandler(config.GlobalConfig.OAuth.IssuerURL, oauthService, authService) registrationHandler := handlers.NewRegistrationHandler(clientService) tokenHandler := handlers.NewTokenHandler(tokenService) - adminHandler := handlers.NewAdminHandler(adminService) + adminHandler := handlers.NewAdminHandler(adminService, clientService) + clientHandler := handlers.NewClientHandler(clientService) // 认证路由 r.GET("/login", authHandler.ShowLogin) @@ -113,6 +117,16 @@ func main() { authorized.GET("/dashboard", adminHandler.Dashboard) authorized.GET("/users", adminHandler.ListUsers) authorized.GET("/clients", adminHandler.ListClients) + + // 用户管理路由 + authorized.GET("/users/create", adminHandler.ShowCreateUser) + authorized.POST("/users/create", adminHandler.HandleCreateUser) + authorized.GET("/users/:id/edit", adminHandler.ShowEditUser) + authorized.POST("/users/:id/edit", adminHandler.HandleEditUser) + authorized.POST("/users/:id/delete", adminHandler.HandleDeleteUser) + + // 客户端管理API路由 + clientHandler.RegisterRoutes(r) } } diff --git a/models/client.go b/models/client.go index f6cf75c..a768bab 100644 --- a/models/client.go +++ b/models/client.go @@ -4,13 +4,16 @@ import ( "time" "gorm.io/datatypes" - "gorm.io/gorm" ) +// Client 表示 OAuth2 客户端 type Client struct { - gorm.Model - ClientID string `gorm:"uniqueIndex;not null"` - ClientSecret string `gorm:"not null"` + ID uint `json:"id"` + ClientID string `json:"client_id" gorm:"unique"` + ClientSecret string `json:"client_secret,omitempty"` + Name string `json:"name"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` RedirectURIs datatypes.JSON `gorm:"type:json"` TokenEndpointAuthMethod string `gorm:"not null"` GrantTypes datatypes.JSON `gorm:"type:json"` @@ -25,8 +28,19 @@ type Client struct { SoftwareID string `gorm:"type:varchar(255)"` SoftwareVersion string `gorm:"type:varchar(255)"` IsActive bool `gorm:"default:true"` - CreatedAt time.Time - UpdatedAt time.Time +} + +// CreateClientRequest 创建客户端的请求结构 +type CreateClientRequest struct { + ClientID string `json:"client_id" binding:"required"` + ClientSecret string `json:"client_secret" binding:"required"` + Name string `json:"name" binding:"required"` +} + +// UpdateClientRequest 更新客户端的请求结构 +type UpdateClientRequest struct { + ClientID string `json:"client_id" binding:"required"` + Name string `json:"name" binding:"required"` } func (c *Client) TableName() string { diff --git a/services/admin.go b/services/admin.go index 13131b5..950626f 100644 --- a/services/admin.go +++ b/services/admin.go @@ -177,3 +177,72 @@ func (s *AdminService) ListClients(page, pageSize int) ([]models.Client, int64, err := s.db.Offset((page - 1) * pageSize).Limit(pageSize).Find(&clients).Error return clients, total, err } + +// CreateUser 创建新用户 +func (s *AdminService) CreateUser(username, password, email string) (*models.User, error) { + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + if err != nil { + return nil, err + } + + user := &models.User{ + Username: username, + Password: string(hashedPassword), + Email: email, + IsActive: true, + } + + if err := s.db.Create(user).Error; err != nil { + return nil, err + } + + return user, nil +} + +// UpdateUser 更新用户信息 +func (s *AdminService) UpdateUser(id uint, username, email string, isActive *bool) (*models.User, error) { + user := &models.User{} + if err := s.db.First(user, id).Error; err != nil { + return nil, err + } + + if username != "" { + user.Username = username + } + if email != "" { + user.Email = email + } + if isActive != nil { + user.IsActive = *isActive + } + + if err := s.db.Save(user).Error; err != nil { + return nil, err + } + + return user, nil +} + +// UpdateUserPassword 更新用户密码 +func (s *AdminService) UpdateUserPassword(id uint, password string) error { + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + if err != nil { + return err + } + + return s.db.Model(&models.User{}).Where("id = ?", id).Update("password", string(hashedPassword)).Error +} + +// DeleteUser 删除用户 +func (s *AdminService) DeleteUser(id uint) error { + return s.db.Delete(&models.User{}, id).Error +} + +// GetUser 获取单个用户信息 +func (s *AdminService) GetUser(id uint) (*models.User, error) { + user := &models.User{} + if err := s.db.First(user, id).Error; err != nil { + return nil, err + } + return user, nil +} diff --git a/services/client.go b/services/client.go index a8ff806..dbd0520 100644 --- a/services/client.go +++ b/services/client.go @@ -209,3 +209,58 @@ func generateSecureToken(length int) string { } return base64.RawURLEncoding.EncodeToString(b) } + +// GetClients 获取客户端列表(分页) +func (s *ClientService) GetClients(page, pageSize int) ([]ClientResponse, int64, error) { + var clients []models.Client + var total int64 + + // 获取总数 + if err := s.db.Model(&models.Client{}).Count(&total).Error; err != nil { + return nil, 0, err + } + + // 获取分页数据 + offset := (page - 1) * pageSize + if err := s.db.Offset(offset).Limit(pageSize).Find(&clients).Error; err != nil { + return nil, 0, err + } + + // 转换为响应格式 + responses := make([]ClientResponse, len(clients)) + for i, client := range clients { + // 解析 JSON 字段 + var redirectURIs, grantTypes, responseTypes, scopes, contacts []string + json.Unmarshal(client.RedirectURIs, &redirectURIs) + json.Unmarshal(client.GrantTypes, &grantTypes) + json.Unmarshal(client.ResponseTypes, &responseTypes) + json.Unmarshal(client.Scopes, &scopes) + json.Unmarshal(client.Contacts, &contacts) + + responses[i] = ClientResponse{ + ClientID: client.ClientID, + ClientSecret: client.ClientSecret, + ClientIDIssuedAt: client.CreatedAt, + RedirectURIs: redirectURIs, + TokenEndpointAuthMethod: client.TokenEndpointAuthMethod, + GrantTypes: grantTypes, + ResponseTypes: responseTypes, + ClientName: client.ClientName, + ClientURI: client.ClientURI, + LogoURI: client.LogoURI, + Scope: func() string { + if len(scopes) > 0 { + return scopes[0] + } + return "" + }(), + Contacts: contacts, + TosURI: client.TosURI, + PolicyURI: client.PolicyURI, + SoftwareID: client.SoftwareID, + SoftwareVersion: client.SoftwareVersion, + } + } + + return responses, total, nil +} diff --git a/templates/admin_clients.html b/templates/admin_clients.html index e7ba984..c2fd841 100644 --- a/templates/admin_clients.html +++ b/templates/admin_clients.html @@ -1,38 +1,281 @@ {{template "header" .}}
-

客户端管理

- - - - - - - - - - - {{range .clients}} - - - - - - - {{end}} - -
ID客户端ID名称创建时间
{{.ID}}{{.ClientID}}{{.Name}}{{.CreatedAt.Format "2006-01-02 15:04:05"}}
+
+

+ 客户端管理 +

+ +
+
+
+ + + + + + + + + + + + + {{range .clients}} + + + + + + + + + {{end}} + +
客户端ID客户端密钥名称重定向URI创建时间操作
{{.ClientID}}{{.ClientSecret}}{{.ClientName}} +
+ {{range $i, $uri := .RedirectURIs}}{{if $i}}, {{end}}{{$uri}}{{end}} +
+
{{.ClientIDIssuedAt.Format "2006-01-02 15:04:05"}} + {{$redirectURIsStr := ""}} + {{range $i, $uri := .RedirectURIs}} + {{if $i}} + {{$redirectURIsStr = printf "%s,%s" $redirectURIsStr $uri}} + {{else}} + {{$redirectURIsStr = $uri}} + {{end}} + {{end}} +
+ + +
+
+
+
-
+ + + + + + + + + + + {{template "footer" .}} \ No newline at end of file diff --git a/templates/admin_create_user.html b/templates/admin_create_user.html new file mode 100644 index 0000000..2372917 --- /dev/null +++ b/templates/admin_create_user.html @@ -0,0 +1,32 @@ + + + + 创建用户 - OIDC OAuth2 管理系统 + + + +
+

创建新用户

+ {{if .error}} +
{{.error}}
+ {{end}} +
+
+ + +
+
+ + +
+
+ + +
+ + 返回 +
+
+ + + \ No newline at end of file diff --git a/templates/admin_edit_user.html b/templates/admin_edit_user.html new file mode 100644 index 0000000..f095329 --- /dev/null +++ b/templates/admin_edit_user.html @@ -0,0 +1,36 @@ + + + + 编辑用户 - OIDC OAuth2 管理系统 + + + +
+

编辑用户

+ {{if .error}} +
{{.error}}
+ {{end}} +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ + 返回 +
+
+ + + \ No newline at end of file diff --git a/templates/admin_users.html b/templates/admin_users.html index 6d0010a..cc4cf19 100644 --- a/templates/admin_users.html +++ b/templates/admin_users.html @@ -1,38 +1,110 @@ -{{template "header" .}} -
-

用户管理

- - - - - - - - - - - {{range .users}} - - - - - - - {{end}} - -
ID用户名邮箱创建时间
{{.ID}}{{.Username}}{{.Email}}{{.CreatedAt.Format "2006-01-02 15:04:05"}}
- -
-{{template "footer" .}} \ No newline at end of file + +
+
+

用户管理

+ 创建新用户 +
+ + {{if .error}} +
{{.error}}
+ {{end}} + + + + + + + + + + + + + + {{range .users}} + + + + + + + + + {{end}} + +
ID用户名邮箱状态创建时间操作
{{.ID}}{{.Username}}{{.Email}} + {{if .IsActive}} + 激活 + {{else}} + 禁用 + {{end}} + {{.CreatedAt.Format "2006-01-02 15:04:05"}} + 编辑 + +
+ + + +
+ + + + + \ No newline at end of file diff --git a/templates/header.html b/templates/header.html index a250bc8..36fb0ac 100644 --- a/templates/header.html +++ b/templates/header.html @@ -4,6 +4,7 @@ OIDC OAuth2 管理系统 +