Files
user-system/sdk/go/user-management/auth.go
long-agent 765a50b7d4 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
- 响应格式协议中间件
- 导出无界查询修复
2026-04-03 17:38:31 +08:00

247 lines
6.0 KiB
Go

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
}