package oauth import ( "crypto/rand" "encoding/hex" "strings" "github.com/veypi/vbase/internal/api/middleware" "github.com/veypi/vbase/internal/model" "github.com/veypi/vigo" ) // ClientRequest 客户端请求 type ClientRequest struct { Name string `json:"name" src:"json" desc:"应用名称"` Description string `json:"description" src:"json" desc:"应用描述"` RedirectURIs []string `json:"redirect_uris" src:"json" desc:"回调地址列表"` GrantTypes []string `json:"grant_types" src:"json" desc:"授权类型"` ResponseTypes []string `json:"response_types" src:"json" desc:"响应类型"` AllowedScopes []string `json:"allowed_scopes" src:"json" desc:"允许的权限范围"` TokenExpiry int `json:"token_expiry" src:"json" desc:"Token有效期(秒)"` RefreshExpiry int `json:"refresh_expiry" src:"json" desc:"RefreshToken有效期(秒)"` OrgID string `json:"org_id" src:"json" desc:"所属组织ID(可选)"` } // ClientResponse 客户端响应 type ClientResponse struct { ID string `json:"id"` Name string `json:"name"` Description string `json:"description"` ClientID string `json:"client_id"` ClientSecret string `json:"client_secret,omitempty"` RedirectURIs []string `json:"redirect_uris"` GrantTypes []string `json:"grant_types"` ResponseTypes []string `json:"response_types"` AllowedScopes []string `json:"allowed_scopes"` TokenExpiry int `json:"token_expiry"` RefreshExpiry int `json:"refresh_expiry"` OwnerID string `json:"owner_id"` OrgID string `json:"org_id,omitempty"` Status int `json:"status"` CreatedAt string `json:"created_at"` } // generateClientID 生成客户端ID func generateClientID() string { b := make([]byte, 16) rand.Read(b) return hex.EncodeToString(b) } // generateClientSecret 生成客户端密钥 func generateClientSecret() string { b := make([]byte, 32) rand.Read(b) return hex.EncodeToString(b) } // CreateClient 创建OAuth客户端 func CreateClient(x *vigo.X, req *ClientRequest) (*ClientResponse, error) { userID := middleware.CurrentUser(x) if userID == "" { return nil, vigo.ErrNotAuthorized } // 默认值 grantTypes := req.GrantTypes if len(grantTypes) == 0 { grantTypes = []string{model.GrantTypeAuthorizationCode, model.GrantTypeRefreshToken} } responseTypes := req.ResponseTypes if len(responseTypes) == 0 { responseTypes = []string{model.ResponseTypeCode} } allowedScopes := req.AllowedScopes if len(allowedScopes) == 0 { allowedScopes = []string{model.ScopeOpenID, model.ScopeProfile, model.ScopeEmail} } tokenExpiry := req.TokenExpiry if tokenExpiry == 0 { tokenExpiry = 3600 } refreshExpiry := req.RefreshExpiry if refreshExpiry == 0 { refreshExpiry = 2592000 // 30天 } client := &model.OAuthClient{ Name: req.Name, Description: req.Description, ClientID: generateClientID(), ClientSecret: generateClientSecret(), RedirectURIs: strings.Join(req.RedirectURIs, ","), GrantTypes: strings.Join(grantTypes, ","), ResponseTypes: strings.Join(responseTypes, ","), AllowedScopes: strings.Join(allowedScopes, ","), TokenExpiry: tokenExpiry, RefreshExpiry: refreshExpiry, OwnerID: userID, Status: 1, } if req.OrgID != "" { // 检查用户是否是组织成员 var member model.OrgMember if err := model.DB.Where("org_id = ? AND user_id = ? AND status = ?", req.OrgID, userID, model.MemberStatusActive).First(&member).Error; err != nil { return nil, vigo.ErrForbidden.WithString("you are not a member of this organization") } client.OrgID = req.OrgID } if err := model.DB.Create(client).Error; err != nil { return nil, vigo.ErrInternalServer.WithError(err) } return &ClientResponse{ ID: client.ID, Name: client.Name, Description: client.Description, ClientID: client.ClientID, ClientSecret: client.ClientSecret, RedirectURIs: req.RedirectURIs, GrantTypes: grantTypes, ResponseTypes: responseTypes, AllowedScopes: allowedScopes, TokenExpiry: client.TokenExpiry, RefreshExpiry: client.RefreshExpiry, OwnerID: client.OwnerID, OrgID: client.OrgID, Status: client.Status, CreatedAt: client.CreatedAt.Format("2006-01-02 15:04:05"), }, nil } // ListClientsRequest 客户端列表请求 type ListClientsRequest struct { Page int `json:"page" src:"query" default:"1" desc:"页码"` PageSize int `json:"page_size" src:"query" default:"10" desc:"每页数量"` OrgID string `json:"org_id" src:"query" desc:"组织ID筛选"` } // ListClientsResponse 客户端列表响应 type ListClientsResponse struct { Items []ClientResponse `json:"items"` Total int64 `json:"total"` Page int `json:"page"` PageSize int `json:"page_size"` TotalPages int `json:"total_pages"` } // ListClients 获取OAuth客户端列表 func ListClients(x *vigo.X, req *ListClientsRequest) (*ListClientsResponse, error) { userID := middleware.CurrentUser(x) if userID == "" { return nil, vigo.ErrNotAuthorized } if req.Page < 1 { req.Page = 1 } if req.PageSize < 1 || req.PageSize > 100 { req.PageSize = 10 } query := model.DB.Model(&model.OAuthClient{}).Where("owner_id = ? OR org_id IN (SELECT org_id FROM org_members WHERE user_id = ? AND status = ?)", userID, userID, model.MemberStatusActive) if req.OrgID != "" { query = query.Where("org_id = ?", req.OrgID) } var total int64 if err := query.Count(&total).Error; err != nil { return nil, vigo.ErrInternalServer.WithError(err) } var clients []model.OAuthClient offset := (req.Page - 1) * req.PageSize if err := query.Offset(offset).Limit(req.PageSize).Order("created_at DESC").Find(&clients).Error; err != nil { return nil, vigo.ErrInternalServer.WithError(err) } items := make([]ClientResponse, 0, len(clients)) for _, c := range clients { items = append(items, toClientResponse(&c, false)) } totalPages := int((total + int64(req.PageSize) - 1) / int64(req.PageSize)) return &ListClientsResponse{ Items: items, Total: total, Page: req.Page, PageSize: req.PageSize, TotalPages: totalPages, }, nil } // GetClientRequest 获取客户端请求 type GetClientRequest struct { ID string `json:"id" src:"path@client_id" desc:"客户端ID"` } // GetClient 获取客户端详情 func GetClient(x *vigo.X, req *GetClientRequest) (*ClientResponse, error) { userID := middleware.CurrentUser(x) if userID == "" { return nil, vigo.ErrNotAuthorized } var client model.OAuthClient if err := model.DB.Where("client_id = ?", req.ID).First(&client).Error; err != nil { return nil, vigo.ErrNotFound } // 检查权限 if client.OwnerID != userID { // 检查是否是组织成员 if client.OrgID != "" { var member model.OrgMember if err := model.DB.Where("org_id = ? AND user_id = ? AND status = ?", client.OrgID, userID, model.MemberStatusActive).First(&member).Error; err != nil { return nil, vigo.ErrForbidden } } else { return nil, vigo.ErrForbidden } } resp := toClientResponse(&client, false) return &resp, nil } // UpdateClientRequest 更新客户端请求 type UpdateClientRequest struct { ID string `json:"id" src:"path@client_id" desc:"客户端ID"` Name *string `json:"name" src:"json" desc:"应用名称"` Description *string `json:"description" src:"json" desc:"应用描述"` RedirectURIs []string `json:"redirect_uris" src:"json" desc:"回调地址列表"` AllowedScopes []string `json:"allowed_scopes" src:"json" desc:"允许的权限范围"` TokenExpiry *int `json:"token_expiry" src:"json" desc:"Token有效期"` RefreshExpiry *int `json:"refresh_expiry" src:"json" desc:"RefreshToken有效期"` Status *int `json:"status" src:"json" desc:"状态"` } // UpdateClient 更新OAuth客户端 func UpdateClient(x *vigo.X, req *UpdateClientRequest) (*ClientResponse, error) { userID := middleware.CurrentUser(x) if userID == "" { return nil, vigo.ErrNotAuthorized } var client model.OAuthClient if err := model.DB.Where("client_id = ?", req.ID).First(&client).Error; err != nil { return nil, vigo.ErrNotFound } // 检查权限 if client.OwnerID != userID { if client.OrgID != "" { var member model.OrgMember if err := model.DB.Where("org_id = ? AND user_id = ? AND status = ?", client.OrgID, userID, model.MemberStatusActive).First(&member).Error; err != nil { return nil, vigo.ErrForbidden } } else { return nil, vigo.ErrForbidden } } updates := make(map[string]interface{}) if req.Name != nil { updates["name"] = *req.Name } if req.Description != nil { updates["description"] = *req.Description } if req.RedirectURIs != nil { updates["redirect_uris"] = strings.Join(req.RedirectURIs, ",") } if req.AllowedScopes != nil { updates["allowed_scopes"] = strings.Join(req.AllowedScopes, ",") } if req.TokenExpiry != nil { updates["token_expiry"] = *req.TokenExpiry } if req.RefreshExpiry != nil { updates["refresh_expiry"] = *req.RefreshExpiry } if req.Status != nil { updates["status"] = *req.Status } if len(updates) > 0 { if err := model.DB.Model(&client).Updates(updates).Error; err != nil { return nil, vigo.ErrInternalServer.WithError(err) } } // 重新获取 model.DB.First(&client, "client_id = ?", req.ID) resp := toClientResponse(&client, false) return &resp, nil } // DeleteClientRequest 删除客户端请求 type DeleteClientRequest struct { ID string `json:"id" src:"path@client_id" desc:"客户端ID"` } // DeleteClient 删除OAuth客户端 func DeleteClient(x *vigo.X, req *DeleteClientRequest) error { userID := middleware.CurrentUser(x) if userID == "" { return vigo.ErrNotAuthorized } var client model.OAuthClient if err := model.DB.Where("client_id = ?", req.ID).First(&client).Error; err != nil { return vigo.ErrNotFound } // 检查权限 if client.OwnerID != userID { if client.OrgID != "" { var member model.OrgMember if err := model.DB.Where("org_id = ? AND user_id = ? AND status = ?", client.OrgID, userID, model.MemberStatusActive).First(&member).Error; err != nil { return vigo.ErrForbidden } } else { return vigo.ErrForbidden } } // 软删除 if err := model.DB.Delete(&client).Error; err != nil { return vigo.ErrInternalServer.WithError(err) } return nil } // RegenerateSecretRequest 重新生成密钥请求 type RegenerateSecretRequest struct { ID string `json:"id" src:"path@client_id" desc:"客户端ID"` } // RegenerateSecret 重新生成客户端密钥 func RegenerateSecret(x *vigo.X, req *RegenerateSecretRequest) (*ClientResponse, error) { userID := middleware.CurrentUser(x) if userID == "" { return nil, vigo.ErrNotAuthorized } var client model.OAuthClient if err := model.DB.Where("client_id = ?", req.ID).First(&client).Error; err != nil { return nil, vigo.ErrNotFound } // 检查权限 if client.OwnerID != userID { if client.OrgID != "" { var member model.OrgMember if err := model.DB.Where("org_id = ? AND user_id = ? AND status = ?", client.OrgID, userID, model.MemberStatusActive).First(&member).Error; err != nil { return nil, vigo.ErrForbidden } } else { return nil, vigo.ErrForbidden } } // 生成新密钥 newSecret := generateClientSecret() if err := model.DB.Model(&client).Update("client_secret", newSecret).Error; err != nil { return nil, vigo.ErrInternalServer.WithError(err) } resp := toClientResponse(&client, true) return &resp, nil } // helper functions func toClientResponse(c *model.OAuthClient, includeSecret bool) ClientResponse { resp := ClientResponse{ ID: c.ID, Name: c.Name, Description: c.Description, ClientID: c.ClientID, RedirectURIs: strings.Split(c.RedirectURIs, ","), GrantTypes: strings.Split(c.GrantTypes, ","), ResponseTypes: strings.Split(c.ResponseTypes, ","), AllowedScopes: strings.Split(c.AllowedScopes, ","), TokenExpiry: c.TokenExpiry, RefreshExpiry: c.RefreshExpiry, OwnerID: c.OwnerID, OrgID: c.OrgID, Status: c.Status, CreatedAt: c.CreatedAt.Format("2006-01-02 15:04:05"), } if includeSecret { resp.ClientSecret = c.ClientSecret } return resp }