// Copyright (C) 2024 veypi // 2025-03-04 16:08:06 // Distributed under terms of the MIT license. package oauth import ( "github.com/veypi/vbase/auth" "github.com/veypi/vbase/cfg" "github.com/veypi/vbase/libs/crypto" "github.com/veypi/vbase/models" "github.com/veypi/vigo" ) type ListClientsRequest struct { Page int `json:"page" src:"query" default:"1"` PageSize int `json:"page_size" src:"query" default:"20"` } type ListClientsResponse struct { Items []models.OAuthClient `json:"items"` Total int64 `json:"total"` Page int `json:"page"` PageSize int `json:"page_size"` TotalPages int `json:"total_pages"` } func listClients(x *vigo.X, req *ListClientsRequest) (*ListClientsResponse, error) { db := cfg.DB().Model(&models.OAuthClient{}) var total int64 if err := db.Count(&total).Error; err != nil { return nil, vigo.ErrInternalServer.WithError(err) } var clients []models.OAuthClient offset := (req.Page - 1) * req.PageSize if err := db.Order("created_at DESC").Offset(offset).Limit(req.PageSize).Find(&clients).Error; err != nil { return nil, vigo.ErrInternalServer.WithError(err) } totalPages := int(total) / req.PageSize if int(total)%req.PageSize > 0 { totalPages++ } return &ListClientsResponse{ Items: clients, Total: total, Page: req.Page, PageSize: req.PageSize, TotalPages: totalPages, }, nil } type CreateClientRequest struct { Name string `json:"name" src:"json" desc:"客户端名称"` Description string `json:"description" src:"json" desc:"描述"` RedirectURIs []string `json:"redirect_uris" src:"json" desc:"允许的重定向URI"` AllowedScopes string `json:"allowed_scopes" src:"json" desc:"允许的授权范围"` } type CreateClientResponse struct { models.OAuthClient ClientSecret string `json:"client_secret"` } func createClient(x *vigo.X, req *CreateClientRequest) (*CreateClientResponse, error) { ownerID := auth.VBaseAuth.UserID(x) if ownerID == "" { return nil, vigo.ErrUnauthorized } clientID := crypto.GenerateClientID() clientSecret := crypto.GenerateClientSecret() // TODO: 序列化 RedirectURIs client := &models.OAuthClient{ ClientID: clientID, ClientSecret: clientSecret, Name: req.Name, Description: req.Description, AllowedScopes: req.AllowedScopes, OwnerID: ownerID, Status: models.OAuthClientStatusActive, } if err := cfg.DB().Create(client).Error; err != nil { return nil, vigo.ErrInternalServer.WithError(err) } return &CreateClientResponse{ OAuthClient: *client, ClientSecret: clientSecret, }, nil } type GetClientRequest struct { ClientID string `src:"path@client_id" desc:"客户端ID"` } func getClient(x *vigo.X, req *GetClientRequest) (*models.OAuthClient, error) { var client models.OAuthClient if err := cfg.DB().First(&client, "client_id = ?", req.ClientID).Error; err != nil { return nil, vigo.ErrNotFound } return &client, nil } type UpdateClientRequest struct { ClientID string `src:"path@client_id" desc:"客户端ID"` Name *string `json:"name,omitempty" src:"json" desc:"客户端名称"` Description *string `json:"description,omitempty" src:"json" desc:"描述"` AllowedScopes *string `json:"allowed_scopes,omitempty" src:"json" desc:"允许的授权范围"` Status *int `json:"status,omitempty" src:"json" desc:"状态"` } func updateClient(x *vigo.X, req *UpdateClientRequest) (*models.OAuthClient, error) { var client models.OAuthClient if err := cfg.DB().First(&client, "client_id = ?", req.ClientID).Error; err != nil { return nil, vigo.ErrNotFound } // 检查权限:只有所有者或管理员可以修改 currentUserID := auth.VBaseAuth.UserID(x) if currentUserID == "" { return nil, vigo.ErrUnauthorized } // 检查是否是所有者 isOwner := client.OwnerID == currentUserID // 检查是否是管理员(拥有 *:* 权限) isAdmin := auth.VBaseAuth.Check(x.Context(), currentUserID, "*:*", auth.LevelAdmin) if !isOwner && !isAdmin { return nil, vigo.ErrForbidden.WithString("not the owner of this client") } updates := make(map[string]any) if req.Name != nil { updates["name"] = *req.Name } if req.Description != nil { updates["description"] = *req.Description } if req.AllowedScopes != nil { updates["allowed_scopes"] = *req.AllowedScopes } if req.Status != nil { updates["status"] = *req.Status } if err := cfg.DB().Model(&client).Updates(updates).Error; err != nil { return nil, vigo.ErrInternalServer.WithError(err) } return &client, nil } type DeleteClientRequest struct { ClientID string `src:"path@client_id" desc:"客户端ID"` } func deleteClient(x *vigo.X, req *DeleteClientRequest) error { var client models.OAuthClient if err := cfg.DB().First(&client, "client_id = ?", req.ClientID).Error; err != nil { return vigo.ErrNotFound } // 检查权限:只有所有者或管理员可以删除 currentUserID := auth.VBaseAuth.UserID(x) if currentUserID == "" { return vigo.ErrUnauthorized } // 检查是否是所有者 isOwner := client.OwnerID == currentUserID // 检查是否是管理员(拥有 *:* 权限) isAdmin := auth.VBaseAuth.Check(x.Context(), currentUserID, "*:*", auth.LevelAdmin) if !isOwner && !isAdmin { return vigo.ErrForbidden.WithString("not the owner of this client") } if err := cfg.DB().Delete(&client).Error; err != nil { return vigo.ErrInternalServer.WithError(err) } return nil }