fix: 生产安全修复 + Go SDK + CAS SSO框架
安全修复: - CRITICAL: SSO重定向URL注入漏洞 - 修复redirect_uri白名单验证 - HIGH: SSO ClientSecret未验证 - 使用crypto/subtle.ConstantTimeCompare验证 - HIGH: 邮件验证码熵值过低(3字节) - 提升到6字节(48位熵) - HIGH: 短信验证码熵值过低(4字节) - 提升到6字节 - HIGH: Goroutine使用已取消上下文 - auth_email.go使用独立context+超时 - HIGH: SQL LIKE查询注入风险 - permission/role仓库使用escapeLikePattern 新功能: - Go SDK: sdk/go/user-management/ 完整SDK实现 - CAS SSO框架: internal/auth/cas.go CAS协议支持 其他: - L1Cache实例问题修复 - AuthMiddleware共享l1Cache - 设备指纹XSS防护 - 内存存储替代localStorage - 响应格式协议中间件 - 导出无界查询修复
This commit is contained in:
246
sdk/go/user-management/auth.go
Normal file
246
sdk/go/user-management/auth.go
Normal file
@@ -0,0 +1,246 @@
|
||||
package userManagement
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// LoginRequest 登录请求
|
||||
type LoginRequest struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
DeviceID string `json:"device_id,omitempty"`
|
||||
DeviceName string `json:"device_name,omitempty"`
|
||||
RememberMe bool `json:"remember_me"`
|
||||
}
|
||||
|
||||
// LoginResponse 登录响应
|
||||
type LoginResponse struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
RefreshToken string `json:"refresh_token,omitempty"`
|
||||
ExpiresIn int64 `json:"expires_in"`
|
||||
TokenType string `json:"token_type"`
|
||||
User *User `json:"user,omitempty"`
|
||||
}
|
||||
|
||||
// RegisterRequest 注册请求
|
||||
type RegisterRequest struct {
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
Phone string `json:"phone,omitempty"`
|
||||
Nickname string `json:"nickname,omitempty"`
|
||||
}
|
||||
|
||||
// RefreshTokenRequest 刷新令牌请求
|
||||
type RefreshTokenRequest struct {
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
}
|
||||
|
||||
// CapabilitiesResponse 能力响应
|
||||
type CapabilitiesResponse struct {
|
||||
LoginMethods []string `json:"login_methods"`
|
||||
SocialProviders []string `json:"social_providers,omitempty"`
|
||||
CaptchaRequired bool `json:"captcha_required"`
|
||||
SocialBindRequired bool `json:"social_bind_required,omitempty"`
|
||||
}
|
||||
|
||||
// TwoFactorVerifyRequest 两因素验证请求
|
||||
type TwoFactorVerifyRequest struct {
|
||||
Code string `json:"code"`
|
||||
DeviceID string `json:"device_id,omitempty"`
|
||||
TrustDevice bool `json:"trust_device,omitempty"`
|
||||
}
|
||||
|
||||
// PasswordResetRequest 密码重置请求
|
||||
type PasswordResetRequest struct {
|
||||
Token string `json:"token"`
|
||||
NewPassword string `json:"new_password"`
|
||||
}
|
||||
|
||||
// Login 执行登录
|
||||
func (c *Client) Login(ctx context.Context, req *LoginRequest) (*LoginResponse, error) {
|
||||
resp, err := c.doRequest(ctx, "POST", "/api/v1/auth/login", req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result LoginResponse
|
||||
if err := c.parseResponse(resp, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 自动设置 access token
|
||||
if result.AccessToken != "" {
|
||||
c.SetAccessToken(result.AccessToken)
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// Register 注册用户
|
||||
func (c *Client) Register(ctx context.Context, req *RegisterRequest) (*User, error) {
|
||||
resp, err := c.doRequest(ctx, "POST", "/api/v1/auth/register", req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result User
|
||||
if err := c.parseResponse(resp, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// GetCapabilities 获取登录能力
|
||||
func (c *Client) GetCapabilities(ctx context.Context) (*CapabilitiesResponse, error) {
|
||||
resp, err := c.doRequest(ctx, "GET", "/api/v1/auth/capabilities", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result CapabilitiesResponse
|
||||
if err := c.parseResponse(resp, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// RefreshToken 刷新令牌
|
||||
func (c *Client) RefreshToken(ctx context.Context, req *RefreshTokenRequest) (*LoginResponse, error) {
|
||||
resp, err := c.doRequest(ctx, "POST", "/api/v1/auth/refresh", req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result LoginResponse
|
||||
if err := c.parseResponse(resp, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if result.AccessToken != "" {
|
||||
c.SetAccessToken(result.AccessToken)
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// VerifyTwoFactor 验证两因素验证码
|
||||
func (c *Client) VerifyTwoFactor(ctx context.Context, req *TwoFactorVerifyRequest) error {
|
||||
resp, err := c.doRequest(ctx, "POST", "/api/v1/auth/2fa/verify", req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.parseResponse(resp, nil)
|
||||
}
|
||||
|
||||
// Logout 登出
|
||||
func (c *Client) Logout(ctx context.Context) error {
|
||||
resp, err := c.doRequest(ctx, "POST", "/api/v1/auth/logout", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.accessToken = ""
|
||||
return c.parseResponse(resp, nil)
|
||||
}
|
||||
|
||||
// RequestPasswordReset 请求密码重置
|
||||
func (c *Client) RequestPasswordReset(ctx context.Context, email string) error {
|
||||
resp, err := c.doRequest(ctx, "POST", "/api/v1/auth/password/reset", map[string]string{"email": email})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.parseResponse(resp, nil)
|
||||
}
|
||||
|
||||
// ResetPassword 重置密码
|
||||
func (c *Client) ResetPassword(ctx context.Context, req *PasswordResetRequest) error {
|
||||
resp, err := c.doRequest(ctx, "POST", "/api/v1/auth/password/reset/confirm", req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.parseResponse(resp, nil)
|
||||
}
|
||||
|
||||
// SendVerifyCode 发送验证码
|
||||
func (c *Client) SendVerifyCode(ctx context.Context, phone string) error {
|
||||
resp, err := c.doRequest(ctx, "POST", "/api/v1/auth/phone/send-code", map[string]string{"phone": phone})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.parseResponse(resp, nil)
|
||||
}
|
||||
|
||||
// LoginWithPhone 手机号登录
|
||||
func (c *Client) LoginWithPhone(ctx context.Context, phone, code string) (*LoginResponse, error) {
|
||||
req := map[string]string{
|
||||
"phone": phone,
|
||||
"code": code,
|
||||
}
|
||||
resp, err := c.doRequest(ctx, "POST", "/api/v1/auth/login/phone", req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result LoginResponse
|
||||
if err := c.parseResponse(resp, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if result.AccessToken != "" {
|
||||
c.SetAccessToken(result.AccessToken)
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// OAuthURL 获取 OAuth 授权 URL
|
||||
func (c *Client) OAuthURL(provider string, redirectURI, state string) (string, error) {
|
||||
params := map[string]string{
|
||||
"provider": provider,
|
||||
"redirect_uri": redirectURI,
|
||||
}
|
||||
if state != "" {
|
||||
params["state"] = state
|
||||
}
|
||||
|
||||
query := ""
|
||||
for k, v := range params {
|
||||
if query != "" {
|
||||
query += "&"
|
||||
}
|
||||
query += k + "=" + v
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s/api/v1/auth/oauth/authorize?%s", c.baseURL, query), nil
|
||||
}
|
||||
|
||||
// HandleOAuthCallback 处理 OAuth 回调
|
||||
func (c *Client) HandleOAuthCallback(ctx context.Context, provider, code string) (*LoginResponse, error) {
|
||||
req := map[string]string{
|
||||
"provider": provider,
|
||||
"code": code,
|
||||
}
|
||||
resp, err := c.doRequest(ctx, "POST", "/api/v1/auth/oauth/callback", req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result LoginResponse
|
||||
if err := c.parseResponse(resp, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if result.AccessToken != "" {
|
||||
c.SetAccessToken(result.AccessToken)
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
144
sdk/go/user-management/client.go
Normal file
144
sdk/go/user-management/client.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package userManagement
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Client API 客户端
|
||||
type Client struct {
|
||||
baseURL string
|
||||
httpClient *http.Client
|
||||
accessToken string
|
||||
apiKey string
|
||||
}
|
||||
|
||||
// ClientOption 配置选项
|
||||
type ClientOption func(*Client)
|
||||
|
||||
// WithAPIToken 设置 API Token(用于简单认证)
|
||||
func WithAPIToken(token string) ClientOption {
|
||||
return func(c *Client) {
|
||||
c.apiKey = token
|
||||
}
|
||||
}
|
||||
|
||||
// WithAccessToken 设置 Access Token(用于已认证请求)
|
||||
func WithAccessToken(token string) ClientOption {
|
||||
return func(c *Client) {
|
||||
c.accessToken = token
|
||||
}
|
||||
}
|
||||
|
||||
// WithHTTPClient 设置自定义 HTTP 客户端
|
||||
func WithHTTPClient(httpClient *http.Client) ClientOption {
|
||||
return func(c *Client) {
|
||||
c.httpClient = httpClient
|
||||
}
|
||||
}
|
||||
|
||||
// NewClient 创建新的 API 客户端
|
||||
func NewClient(baseURL string, opts ...ClientOption) *Client {
|
||||
c := &Client{
|
||||
baseURL: baseURL,
|
||||
httpClient: &http.Client{
|
||||
Timeout: 30 * time.Second,
|
||||
},
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(c)
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// APIResponse 标准 API 响应
|
||||
type APIResponse struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data json.RawMessage `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// ErrorResponse 错误响应
|
||||
type ErrorResponse struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
func (c *Client) doRequest(ctx context.Context, method, path string, body interface{}) (*http.Response, error) {
|
||||
u, err := url.JoinPath(c.baseURL, path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to join URL: %w", err)
|
||||
}
|
||||
|
||||
var reqBody io.Reader
|
||||
if body != nil {
|
||||
jsonData, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal request body: %w", err)
|
||||
}
|
||||
reqBody = bytes.NewReader(jsonData)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, method, u, reqBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Accept", "application/json")
|
||||
|
||||
if c.accessToken != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+c.accessToken)
|
||||
} else if c.apiKey != "" {
|
||||
req.Header.Set("X-API-Key", c.apiKey)
|
||||
}
|
||||
|
||||
return c.httpClient.Do(req)
|
||||
}
|
||||
|
||||
func (c *Client) parseResponse(resp *http.Response, result interface{}) error {
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read response body: %w", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
var errResp ErrorResponse
|
||||
if err := json.Unmarshal(body, &errResp); err == nil {
|
||||
return fmt.Errorf("API error %d: %s", resp.StatusCode, errResp.Message)
|
||||
}
|
||||
return fmt.Errorf("API error %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
if result == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var apiResp APIResponse
|
||||
if err := json.Unmarshal(body, &apiResp); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal response: %w", err)
|
||||
}
|
||||
|
||||
if apiResp.Data != nil {
|
||||
if err := json.Unmarshal(apiResp.Data, result); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal data: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetAccessToken 设置访问令牌
|
||||
func (c *Client) SetAccessToken(token string) {
|
||||
c.accessToken = token
|
||||
}
|
||||
138
sdk/go/user-management/device.go
Normal file
138
sdk/go/user-management/device.go
Normal file
@@ -0,0 +1,138 @@
|
||||
package userManagement
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ListDevicesParams 设备列表查询参数
|
||||
type ListDevicesParams struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
UserID int64 `json:"user_id,omitempty"`
|
||||
IsActive *bool `json:"is_active,omitempty"`
|
||||
IsTrusted *bool `json:"is_trusted,omitempty"`
|
||||
}
|
||||
|
||||
// GetMyDevices 获取当前用户的设备列表
|
||||
func (c *Client) GetMyDevices(ctx context.Context) ([]*Device, error) {
|
||||
resp, err := c.doRequest(ctx, "GET", "/api/v1/devices/me", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result []*Device
|
||||
if err := c.parseResponse(resp, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetTrustedDevices 获取信任设备列表
|
||||
func (c *Client) GetTrustedDevices(ctx context.Context) ([]*Device, error) {
|
||||
resp, err := c.doRequest(ctx, "GET", "/api/v1/devices/me/trusted", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result []*Device
|
||||
if err := c.parseResponse(resp, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetDevice 获取设备详情
|
||||
func (c *Client) GetDevice(ctx context.Context, id int64) (*Device, error) {
|
||||
resp, err := c.doRequest(ctx, "GET", fmt.Sprintf("/api/v1/devices/%d", id), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result Device
|
||||
if err := c.parseResponse(resp, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// ListDevices 获取设备列表(管理员用)
|
||||
func (c *Client) ListDevices(ctx context.Context, params *ListDevicesParams) (*PaginatedResponse, error) {
|
||||
if params.Page <= 0 {
|
||||
params.Page = 1
|
||||
}
|
||||
if params.PageSize <= 0 {
|
||||
params.PageSize = 20
|
||||
}
|
||||
|
||||
path := fmt.Sprintf("/api/v1/admin/devices?page=%d&page_size=%d", params.Page, params.PageSize)
|
||||
if params.UserID > 0 {
|
||||
path += fmt.Sprintf("&user_id=%d", params.UserID)
|
||||
}
|
||||
|
||||
resp, err := c.doRequest(ctx, "GET", path, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result PaginatedResponse
|
||||
if err := c.parseResponse(resp, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// TrustDevice 信任设备
|
||||
func (c *Client) TrustDevice(ctx context.Context, deviceID int64) error {
|
||||
resp, err := c.doRequest(ctx, "POST", fmt.Sprintf("/api/v1/devices/%d/trust", deviceID), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.parseResponse(resp, nil)
|
||||
}
|
||||
|
||||
// UntrustDevice 取消设备信任
|
||||
func (c *Client) UntrustDevice(ctx context.Context, deviceID int64) error {
|
||||
resp, err := c.doRequest(ctx, "DELETE", fmt.Sprintf("/api/v1/devices/%d/trust", deviceID), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.parseResponse(resp, nil)
|
||||
}
|
||||
|
||||
// TrustDeviceByDeviceID 通过 device_id 信任设备
|
||||
func (c *Client) TrustDeviceByDeviceID(ctx context.Context, deviceID string) error {
|
||||
resp, err := c.doRequest(ctx, "POST", fmt.Sprintf("/api/v1/devices/by-device-id/%s/trust", deviceID), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.parseResponse(resp, nil)
|
||||
}
|
||||
|
||||
// RevokeDevice 撤销设备
|
||||
func (c *Client) RevokeDevice(ctx context.Context, deviceID int64) error {
|
||||
resp, err := c.doRequest(ctx, "DELETE", fmt.Sprintf("/api/v1/devices/%d", deviceID), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.parseResponse(resp, nil)
|
||||
}
|
||||
|
||||
// LogoutOtherDevices 登出其他设备
|
||||
func (c *Client) LogoutOtherDevices(ctx context.Context, currentDeviceID string) error {
|
||||
req := map[string]string{"current_device_id": currentDeviceID}
|
||||
resp, err := c.doRequest(ctx, "POST", "/api/v1/devices/me/logout-others", req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.parseResponse(resp, nil)
|
||||
}
|
||||
135
sdk/go/user-management/example_test.go
Normal file
135
sdk/go/user-management/example_test.go
Normal file
@@ -0,0 +1,135 @@
|
||||
package userManagement
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
// Example_basic_usage 基础使用示例
|
||||
func Example_basic_usage() {
|
||||
// 创建客户端
|
||||
client := NewClient("https://api.example.com")
|
||||
|
||||
// 登录
|
||||
loginResp, err := client.Login(context.Background(), &LoginRequest{
|
||||
Username: "admin",
|
||||
Password: "password123",
|
||||
DeviceName: "Go SDK Test",
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("Login failed: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Logged in as %s, token: %s...\n", loginResp.User.Username, loginResp.AccessToken[:20])
|
||||
}
|
||||
|
||||
// Example_user_management 用户管理示例
|
||||
func Example_user_management() {
|
||||
client := NewClient("https://api.example.com", WithAPIToken("your-api-token"))
|
||||
|
||||
// 获取当前用户
|
||||
user, err := client.GetCurrentUser(context.Background())
|
||||
if err != nil {
|
||||
log.Fatalf("GetCurrentUser failed: %v", err)
|
||||
}
|
||||
fmt.Printf("Current user: %s (%s)\n", user.Username, user.Email)
|
||||
|
||||
// 创建新用户
|
||||
newUser, err := client.CreateUser(context.Background(), &CreateUserRequest{
|
||||
Username: "newuser",
|
||||
Email: "newuser@example.com",
|
||||
Password: "SecurePass123!",
|
||||
Status: UserStatusActive,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("CreateUser failed: %v", err)
|
||||
}
|
||||
fmt.Printf("Created user: %s (ID: %d)\n", newUser.Username, newUser.ID)
|
||||
|
||||
// 更新用户
|
||||
updatedUser, err := client.UpdateUser(context.Background(), newUser.ID, &UpdateUserRequest{
|
||||
Nickname: "New Nickname",
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("UpdateUser failed: %v", err)
|
||||
}
|
||||
fmt.Printf("Updated nickname: %s\n", updatedUser.Nickname)
|
||||
|
||||
// 删除用户
|
||||
if err := client.DeleteUser(context.Background(), newUser.ID); err != nil {
|
||||
log.Fatalf("DeleteUser failed: %v", err)
|
||||
}
|
||||
fmt.Printf("User %d deleted\n", newUser.ID)
|
||||
}
|
||||
|
||||
// Example_device_management 设备管理示例
|
||||
func Example_device_management() {
|
||||
client := NewClient("https://api.example.com", WithAccessToken("access-token"))
|
||||
|
||||
// 获取我的设备
|
||||
devices, err := client.GetMyDevices(context.Background())
|
||||
if err != nil {
|
||||
log.Fatalf("GetMyDevices failed: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("My devices (%d):\n", len(devices))
|
||||
for _, d := range devices {
|
||||
trustStatus := "untrusted"
|
||||
if d.IsTrusted {
|
||||
trustStatus = "trusted"
|
||||
}
|
||||
fmt.Printf(" - %s (%s) [%s]\n", d.DeviceName, d.DeviceType, trustStatus)
|
||||
}
|
||||
|
||||
// 获取信任设备
|
||||
trusted, err := client.GetTrustedDevices(context.Background())
|
||||
if err != nil {
|
||||
log.Fatalf("GetTrustedDevices failed: %v", err)
|
||||
}
|
||||
fmt.Printf("Trusted devices: %d\n", len(trusted))
|
||||
}
|
||||
|
||||
// Example_role_management 角色管理示例
|
||||
func Example_role_management() {
|
||||
client := NewClient("https://api.example.com", WithAccessToken("access-token"))
|
||||
|
||||
// 获取角色列表
|
||||
roles, err := client.ListRoles(context.Background(), &ListRolesParams{
|
||||
Page: 1,
|
||||
PageSize: 20,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("ListRoles failed: %v", err)
|
||||
}
|
||||
fmt.Printf("Total roles: %d\n", roles.Total)
|
||||
|
||||
// 获取权限树
|
||||
permissions, err := client.ListPermissions(context.Background())
|
||||
if err != nil {
|
||||
log.Fatalf("ListPermissions failed: %v", err)
|
||||
}
|
||||
fmt.Printf("Total permissions: %d\n", len(permissions))
|
||||
}
|
||||
|
||||
// Example_totp TOTP 两因素认证示例
|
||||
func Example_totp() {
|
||||
client := NewClient("https://api.example.com", WithAccessToken("access-token"))
|
||||
|
||||
// 启用 TOTP
|
||||
setup, err := client.EnableTOTP(context.Background())
|
||||
if err != nil {
|
||||
log.Fatalf("EnableTOTP failed: %v", err)
|
||||
}
|
||||
fmt.Printf("TOTP Secret: %s\n", setup.Secret)
|
||||
fmt.Printf("QR Code URL: %s\n", setup.QRCodeURL)
|
||||
fmt.Printf("Recovery Codes: %v\n", setup.RecoveryCodes)
|
||||
|
||||
// 用户手动验证 TOTP 后才能正式启用
|
||||
// 这里用示例 code 验证
|
||||
if err := client.VerifyTOTP(context.Background(), "123456"); err != nil {
|
||||
fmt.Printf("TOTP verification: %v\n", err)
|
||||
} else {
|
||||
fmt.Println("TOTP verified successfully")
|
||||
}
|
||||
}
|
||||
3
sdk/go/user-management/go.mod
Normal file
3
sdk/go/user-management/go.mod
Normal file
@@ -0,0 +1,3 @@
|
||||
module github.com/user-management-system/sdk/go
|
||||
|
||||
go 1.21
|
||||
135
sdk/go/user-management/log.go
Normal file
135
sdk/go/user-management/log.go
Normal file
@@ -0,0 +1,135 @@
|
||||
package userManagement
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ListLoginLogsParams 登录日志查询参数
|
||||
type ListLoginLogsParams struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
UserID int64 `json:"user_id,omitempty"`
|
||||
Status int `json:"status,omitempty"`
|
||||
StartAt *time.Time `json:"start_at,omitempty"`
|
||||
EndAt *time.Time `json:"end_at,omitempty"`
|
||||
}
|
||||
|
||||
// ListOperationLogsParams 操作日志查询参数
|
||||
type ListOperationLogsParams struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
UserID int64 `json:"user_id,omitempty"`
|
||||
Action string `json:"action,omitempty"`
|
||||
Resource string `json:"resource,omitempty"`
|
||||
StartAt *time.Time `json:"start_at,omitempty"`
|
||||
EndAt *time.Time `json:"end_at,omitempty"`
|
||||
}
|
||||
|
||||
// GetLoginLogs 获取登录日志列表
|
||||
func (c *Client) GetLoginLogs(ctx context.Context, params *ListLoginLogsParams) (*PaginatedResponse, error) {
|
||||
if params.Page <= 0 {
|
||||
params.Page = 1
|
||||
}
|
||||
if params.PageSize <= 0 {
|
||||
params.PageSize = 20
|
||||
}
|
||||
|
||||
path := fmt.Sprintf("/api/v1/logs/login?page=%d&page_size=%d", params.Page, params.PageSize)
|
||||
if params.UserID > 0 {
|
||||
path += fmt.Sprintf("&user_id=%d", params.UserID)
|
||||
}
|
||||
if params.Status > 0 {
|
||||
path += fmt.Sprintf("&status=%d", params.Status)
|
||||
}
|
||||
|
||||
resp, err := c.doRequest(ctx, "GET", path, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result PaginatedResponse
|
||||
if err := c.parseResponse(resp, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// GetOperationLogs 获取操作日志列表
|
||||
func (c *Client) GetOperationLogs(ctx context.Context, params *ListOperationLogsParams) (*PaginatedResponse, error) {
|
||||
if params.Page <= 0 {
|
||||
params.Page = 1
|
||||
}
|
||||
if params.PageSize <= 0 {
|
||||
params.PageSize = 20
|
||||
}
|
||||
|
||||
path := fmt.Sprintf("/api/v1/logs/operation?page=%d&page_size=%d", params.Page, params.PageSize)
|
||||
if params.UserID > 0 {
|
||||
path += fmt.Sprintf("&user_id=%d", params.UserID)
|
||||
}
|
||||
if params.Action != "" {
|
||||
path += "&action=" + params.Action
|
||||
}
|
||||
if params.Resource != "" {
|
||||
path += "&resource=" + params.Resource
|
||||
}
|
||||
|
||||
resp, err := c.doRequest(ctx, "GET", path, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result PaginatedResponse
|
||||
if err := c.parseResponse(resp, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// ExportLoginLogsRequest 导出登录日志请求
|
||||
type ExportLoginLogsRequest struct {
|
||||
Format string `json:"format"` // "xlsx" or "csv"
|
||||
UserID int64 `json:"user_id,omitempty"`
|
||||
Status int `json:"status,omitempty"`
|
||||
StartAt *time.Time `json:"start_at,omitempty"`
|
||||
EndAt *time.Time `json:"end_at,omitempty"`
|
||||
Fields string `json:"fields,omitempty"`
|
||||
}
|
||||
|
||||
// ExportLoginLogs 导出登录日志(返回下载 URL)
|
||||
func (c *Client) ExportLoginLogs(ctx context.Context, req *ExportLoginLogsRequest) (string, error) {
|
||||
resp, err := c.doRequest(ctx, "GET", "/api/v1/logs/login/export", req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var result map[string]string
|
||||
if err := c.parseResponse(resp, &result); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if url, ok := result["download_url"]; ok {
|
||||
return url, nil
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// GetStats 获取统计信息
|
||||
func (c *Client) GetStats(ctx context.Context) (*Stats, error) {
|
||||
resp, err := c.doRequest(ctx, "GET", "/api/v1/stats/dashboard", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result Stats
|
||||
if err := c.parseResponse(resp, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
157
sdk/go/user-management/role.go
Normal file
157
sdk/go/user-management/role.go
Normal file
@@ -0,0 +1,157 @@
|
||||
package userManagement
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// CreateRoleRequest 创建角色请求
|
||||
type CreateRoleRequest struct {
|
||||
Name string `json:"name"`
|
||||
Code string `json:"code"`
|
||||
Description string `json:"description,omitempty"`
|
||||
PermissionIDs []int64 `json:"permission_ids,omitempty"`
|
||||
Status RoleStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// UpdateRoleRequest 更新角色请求
|
||||
type UpdateRoleRequest struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
PermissionIDs []int64 `json:"permission_ids,omitempty"`
|
||||
Status RoleStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// ListRolesParams 角色列表查询参数
|
||||
type ListRolesParams struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
Keyword string `json:"keyword,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// GetRole 获取角色详情
|
||||
func (c *Client) GetRole(ctx context.Context, id int64) (*Role, error) {
|
||||
resp, err := c.doRequest(ctx, "GET", fmt.Sprintf("/api/v1/roles/%d", id), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result Role
|
||||
if err := c.parseResponse(resp, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// ListRoles 获取角色列表
|
||||
func (c *Client) ListRoles(ctx context.Context, params *ListRolesParams) (*PaginatedResponse, error) {
|
||||
if params.Page <= 0 {
|
||||
params.Page = 1
|
||||
}
|
||||
if params.PageSize <= 0 {
|
||||
params.PageSize = 20
|
||||
}
|
||||
|
||||
path := fmt.Sprintf("/api/v1/roles?page=%d&page_size=%d", params.Page, params.PageSize)
|
||||
if params.Keyword != "" {
|
||||
path += "&keyword=" + params.Keyword
|
||||
}
|
||||
if params.Status != "" {
|
||||
path += "&status=" + params.Status
|
||||
}
|
||||
|
||||
resp, err := c.doRequest(ctx, "GET", path, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result PaginatedResponse
|
||||
if err := c.parseResponse(resp, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// CreateRole 创建角色
|
||||
func (c *Client) CreateRole(ctx context.Context, req *CreateRoleRequest) (*Role, error) {
|
||||
resp, err := c.doRequest(ctx, "POST", "/api/v1/roles", req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result Role
|
||||
if err := c.parseResponse(resp, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// UpdateRole 更新角色
|
||||
func (c *Client) UpdateRole(ctx context.Context, id int64, req *UpdateRoleRequest) (*Role, error) {
|
||||
resp, err := c.doRequest(ctx, "PUT", fmt.Sprintf("/api/v1/roles/%d", id), req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result Role
|
||||
if err := c.parseResponse(resp, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// DeleteRole 删除角色
|
||||
func (c *Client) DeleteRole(ctx context.Context, id int64) error {
|
||||
resp, err := c.doRequest(ctx, "DELETE", fmt.Sprintf("/api/v1/roles/%d", id), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.parseResponse(resp, nil)
|
||||
}
|
||||
|
||||
// AssignPermissions 分配权限给角色
|
||||
func (c *Client) AssignPermissions(ctx context.Context, roleID int64, permissionIDs []int64) error {
|
||||
req := map[string][]int64{"permission_ids": permissionIDs}
|
||||
resp, err := c.doRequest(ctx, "POST", fmt.Sprintf("/api/v1/roles/%d/permissions", roleID), req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.parseResponse(resp, nil)
|
||||
}
|
||||
|
||||
// GetRolePermissions 获取角色权限
|
||||
func (c *Client) GetRolePermissions(ctx context.Context, roleID int64) ([]*Permission, error) {
|
||||
resp, err := c.doRequest(ctx, "GET", fmt.Sprintf("/api/v1/roles/%d/permissions", roleID), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result []*Permission
|
||||
if err := c.parseResponse(resp, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ListPermissions 获取权限列表(树形)
|
||||
func (c *Client) ListPermissions(ctx context.Context) ([]*Permission, error) {
|
||||
resp, err := c.doRequest(ctx, "GET", "/api/v1/permissions", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result []*Permission
|
||||
if err := c.parseResponse(resp, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
171
sdk/go/user-management/types.go
Normal file
171
sdk/go/user-management/types.go
Normal file
@@ -0,0 +1,171 @@
|
||||
package userManagement
|
||||
|
||||
import "time"
|
||||
|
||||
// User 用户
|
||||
type User struct {
|
||||
ID int64 `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phone,omitempty"`
|
||||
Nickname string `json:"nickname,omitempty"`
|
||||
Avatar string `json:"avatar,omitempty"`
|
||||
Status UserStatus `json:"status"`
|
||||
RoleIDs []int64 `json:"role_ids,omitempty"`
|
||||
Roles []*Role `json:"roles,omitempty"`
|
||||
IsSuperAdmin bool `json:"is_super_admin"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
LastLoginAt *time.Time `json:"last_login_at,omitempty"`
|
||||
}
|
||||
|
||||
// UserStatus 用户状态
|
||||
type UserStatus string
|
||||
|
||||
const (
|
||||
UserStatusActive UserStatus = "active"
|
||||
UserStatusInactive UserStatus = "inactive"
|
||||
UserStatusBanned UserStatus = "banned"
|
||||
)
|
||||
|
||||
// Role 角色
|
||||
type Role struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Code string `json:"code"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Status RoleStatus `json:"status"`
|
||||
Permissions []*Permission `json:"permissions,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// RoleStatus 角色状态
|
||||
type RoleStatus string
|
||||
|
||||
const (
|
||||
RoleStatusActive RoleStatus = "active"
|
||||
RoleStatusInactive RoleStatus = "inactive"
|
||||
)
|
||||
|
||||
// Permission 权限
|
||||
type Permission struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Code string `json:"code"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Type PermissionType `json:"type"`
|
||||
ParentID *int64 `json:"parent_id,omitempty"`
|
||||
Children []*Permission `json:"children,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
// PermissionType 权限类型
|
||||
type PermissionType string
|
||||
|
||||
const (
|
||||
PermissionTypeMenu PermissionType = "menu"
|
||||
PermissionTypeAction PermissionType = "action"
|
||||
PermissionTypeAPI PermissionType = "api"
|
||||
)
|
||||
|
||||
// Device 设备
|
||||
type Device struct {
|
||||
ID int64 `json:"id"`
|
||||
DeviceID string `json:"device_id"`
|
||||
DeviceName string `json:"device_name"`
|
||||
DeviceType DeviceType `json:"device_type"`
|
||||
DeviceOS string `json:"device_os"`
|
||||
DeviceBrowser string `json:"device_browser"`
|
||||
IP string `json:"ip"`
|
||||
Location string `json:"location,omitempty"`
|
||||
IsTrusted bool `json:"is_trusted"`
|
||||
IsActive bool `json:"is_active"`
|
||||
LastActiveAt time.Time `json:"last_active_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UserID int64 `json:"user_id"`
|
||||
}
|
||||
|
||||
// DeviceType 设备类型
|
||||
type DeviceType string
|
||||
|
||||
const (
|
||||
DeviceTypeDesktop DeviceType = "desktop"
|
||||
DeviceTypeMobile DeviceType = "mobile"
|
||||
DeviceTypeTablet DeviceType = "tablet"
|
||||
)
|
||||
|
||||
// LoginLog 登录日志
|
||||
type LoginLog struct {
|
||||
ID int64 `json:"id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
Username string `json:"username"`
|
||||
IP string `json:"ip"`
|
||||
Location string `json:"location,omitempty"`
|
||||
DeviceID string `json:"device_id"`
|
||||
DeviceName string `json:"device_name"`
|
||||
Status LoginStatus `json:"status"`
|
||||
FailReason string `json:"fail_reason,omitempty"`
|
||||
LoginMethod string `json:"login_method"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
// LoginStatus 登录状态
|
||||
type LoginStatus int
|
||||
|
||||
const (
|
||||
LoginStatusFailed LoginStatus = 0
|
||||
LoginStatusSuccess LoginStatus = 1
|
||||
)
|
||||
|
||||
// OperationLog 操作日志
|
||||
type OperationLog struct {
|
||||
ID int64 `json:"id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
Username string `json:"username"`
|
||||
Action string `json:"action"`
|
||||
Resource string `json:"resource"`
|
||||
ResourceID *int64 `json:"resource_id,omitempty"`
|
||||
Details string `json:"details,omitempty"`
|
||||
IP string `json:"ip"`
|
||||
Status int `json:"status"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
// Webhook Webhook 配置
|
||||
type Webhook struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
Events []string `json:"events"`
|
||||
Secret string `json:"secret,omitempty"`
|
||||
IsActive bool `json:"is_active"`
|
||||
RetryCount int `json:"retry_count"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// Stats 统计信息
|
||||
type Stats struct {
|
||||
TotalUsers int64 `json:"total_users"`
|
||||
ActiveUsers int64 `json:"active_users"`
|
||||
TotalDevices int64 `json:"total_devices"`
|
||||
ActiveDevices int64 `json:"active_devices"`
|
||||
TodayLogins int64 `json:"today_logins"`
|
||||
TodayFailLogins int64 `json:"today_fail_logins"`
|
||||
}
|
||||
|
||||
// Pagination 分页参数
|
||||
type Pagination struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
}
|
||||
|
||||
// PaginatedResponse 分页响应
|
||||
type PaginatedResponse struct {
|
||||
Items interface{} `json:"items"`
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
Pages int `json:"pages"`
|
||||
}
|
||||
247
sdk/go/user-management/user.go
Normal file
247
sdk/go/user-management/user.go
Normal file
@@ -0,0 +1,247 @@
|
||||
package userManagement
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// CreateUserRequest 创建用户请求
|
||||
type CreateUserRequest struct {
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
Phone string `json:"phone,omitempty"`
|
||||
Nickname string `json:"nickname,omitempty"`
|
||||
RoleIDs []int64 `json:"role_ids,omitempty"`
|
||||
Status UserStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// UpdateUserRequest 更新用户请求
|
||||
type UpdateUserRequest struct {
|
||||
Email string `json:"email,omitempty"`
|
||||
Phone string `json:"phone,omitempty"`
|
||||
Nickname string `json:"nickname,omitempty"`
|
||||
Avatar string `json:"avatar,omitempty"`
|
||||
Status UserStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// AssignRolesRequest 分配角色请求
|
||||
type AssignRolesRequest struct {
|
||||
RoleIDs []int64 `json:"role_ids"`
|
||||
}
|
||||
|
||||
// ListUsersParams 用户列表查询参数
|
||||
type ListUsersParams struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
Keyword string `json:"keyword,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// GetCurrentUser 获取当前登录用户
|
||||
func (c *Client) GetCurrentUser(ctx context.Context) (*User, error) {
|
||||
resp, err := c.doRequest(ctx, "GET", "/api/v1/users/me", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result User
|
||||
if err := c.parseResponse(resp, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// GetUser 获取用户详情
|
||||
func (c *Client) GetUser(ctx context.Context, id int64) (*User, error) {
|
||||
resp, err := c.doRequest(ctx, "GET", fmt.Sprintf("/api/v1/users/%d", id), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result User
|
||||
if err := c.parseResponse(resp, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// ListUsers 获取用户列表
|
||||
func (c *Client) ListUsers(ctx context.Context, params *ListUsersParams) (*PaginatedResponse, error) {
|
||||
if params.Page <= 0 {
|
||||
params.Page = 1
|
||||
}
|
||||
if params.PageSize <= 0 {
|
||||
params.PageSize = 20
|
||||
}
|
||||
|
||||
path := fmt.Sprintf("/api/v1/users?page=%d&page_size=%d", params.Page, params.PageSize)
|
||||
if params.Keyword != "" {
|
||||
path += "&keyword=" + params.Keyword
|
||||
}
|
||||
if params.Status != "" {
|
||||
path += "&status=" + params.Status
|
||||
}
|
||||
|
||||
resp, err := c.doRequest(ctx, "GET", path, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result PaginatedResponse
|
||||
if err := c.parseResponse(resp, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// CreateUser 创建用户
|
||||
func (c *Client) CreateUser(ctx context.Context, req *CreateUserRequest) (*User, error) {
|
||||
resp, err := c.doRequest(ctx, "POST", "/api/v1/users", req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result User
|
||||
if err := c.parseResponse(resp, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// UpdateUser 更新用户
|
||||
func (c *Client) UpdateUser(ctx context.Context, id int64, req *UpdateUserRequest) (*User, error) {
|
||||
resp, err := c.doRequest(ctx, "PUT", fmt.Sprintf("/api/v1/users/%d", id), req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result User
|
||||
if err := c.parseResponse(resp, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// DeleteUser 删除用户
|
||||
func (c *Client) DeleteUser(ctx context.Context, id int64) error {
|
||||
resp, err := c.doRequest(ctx, "DELETE", fmt.Sprintf("/api/v1/users/%d", id), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.parseResponse(resp, nil)
|
||||
}
|
||||
|
||||
// AssignRoles 分配角色
|
||||
func (c *Client) AssignRoles(ctx context.Context, userID int64, req *AssignRolesRequest) error {
|
||||
resp, err := c.doRequest(ctx, "POST", fmt.Sprintf("/api/v1/users/%d/roles", userID), req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.parseResponse(resp, nil)
|
||||
}
|
||||
|
||||
// GetUserRoles 获取用户角色
|
||||
func (c *Client) GetUserRoles(ctx context.Context, userID int64) ([]*Role, error) {
|
||||
resp, err := c.doRequest(ctx, "GET", fmt.Sprintf("/api/v1/users/%d/roles", userID), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result []*Role
|
||||
if err := c.parseResponse(resp, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// UpdatePassword 更新密码
|
||||
func (c *Client) UpdatePassword(ctx context.Context, oldPassword, newPassword string) error {
|
||||
req := map[string]string{
|
||||
"old_password": oldPassword,
|
||||
"new_password": newPassword,
|
||||
}
|
||||
resp, err := c.doRequest(ctx, "POST", "/api/v1/users/me/password", req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.parseResponse(resp, nil)
|
||||
}
|
||||
|
||||
// BindEmail 绑定邮箱
|
||||
func (c *Client) BindEmail(ctx context.Context, email string) error {
|
||||
req := map[string]string{"email": email}
|
||||
resp, err := c.doRequest(ctx, "POST", "/api/v1/users/me/email", req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.parseResponse(resp, nil)
|
||||
}
|
||||
|
||||
// BindPhone 绑定手机
|
||||
func (c *Client) BindPhone(ctx context.Context, phone, code string) error {
|
||||
req := map[string]string{
|
||||
"phone": phone,
|
||||
"code": code,
|
||||
}
|
||||
resp, err := c.doRequest(ctx, "POST", "/api/v1/users/me/phone", req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.parseResponse(resp, nil)
|
||||
}
|
||||
|
||||
// EnableTOTP 启用 TOTP
|
||||
func (c *Client) EnableTOTP(ctx context.Context) (*TOTPSetupResponse, error) {
|
||||
resp, err := c.doRequest(ctx, "POST", "/api/v1/users/me/2fa/totp/setup", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result TOTPSetupResponse
|
||||
if err := c.parseResponse(resp, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// TOTPSetupResponse TOTP 设置响应
|
||||
type TOTPSetupResponse struct {
|
||||
Secret string `json:"secret"`
|
||||
QRCodeURL string `json:"qr_code_url"`
|
||||
RecoveryCodes []string `json:"recovery_codes,omitempty"`
|
||||
}
|
||||
|
||||
// VerifyTOTP 验证 TOTP
|
||||
func (c *Client) VerifyTOTP(ctx context.Context, code string) error {
|
||||
req := map[string]string{"code": code}
|
||||
resp, err := c.doRequest(ctx, "POST", "/api/v1/users/me/2fa/totp/verify", req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.parseResponse(resp, nil)
|
||||
}
|
||||
|
||||
// DisableTOTP 禁用 TOTP
|
||||
func (c *Client) DisableTOTP(ctx context.Context, code string) error {
|
||||
req := map[string]string{"code": code}
|
||||
resp, err := c.doRequest(ctx, "POST", "/api/v1/users/me/2fa/totp/disable", req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.parseResponse(resp, nil)
|
||||
}
|
||||
Reference in New Issue
Block a user