// Code scaffolded by goctl. Safe to edit. // goctl 1.10.1 package pub import ( "bytes" "context" "encoding/json" "io" "net/http" "strings" "time" "perms-system-server/internal/config" "perms-system-server/internal/middleware" "perms-system-server/internal/response" "perms-system-server/internal/svc" "perms-system-server/internal/types" "github.com/zeromicro/go-zero/core/logx" ) type LoginByCapLogic struct { logx.Logger ctx context.Context svcCtx *svc.ServiceContext } func NewLoginByCapLogic(ctx context.Context, svcCtx *svc.ServiceContext) *LoginByCapLogic { return &LoginByCapLogic{ Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx, } } func (l *LoginByCapLogic) LoginByCap(req *types.LoginByCapReq) (*types.LoginResp, error) { if err := verifyCapToken(l.ctx, l.svcCtx.Config.Capjs, req.CapToken, l.Logger); err != nil { return nil, err } clientIP := middleware.GetClientIP(l.ctx) result, err := ValidateProductLogin(l.ctx, l.svcCtx, req.Username, req.Password, req.ProductCode, clientIP) if err != nil { if le, ok := err.(*LoginError); ok { switch le.Code { case 400: return nil, response.ErrBadRequest(le.Message) case 401: return nil, response.ErrUnauthorized(le.Message) case 403: return nil, response.ErrForbidden(le.Message) case 429: return nil, response.NewCodeError(429, le.Message) } } return nil, err } ud := result.UserDetails return &types.LoginResp{ AccessToken: result.AccessToken, RefreshToken: result.RefreshToken, Expires: time.Now().Unix() + l.svcCtx.Config.Auth.AccessExpire, UserInfo: types.UserInfo{ UserId: ud.UserId, Username: ud.Username, Nickname: ud.Nickname, Avatar: ud.Avatar, Email: ud.Email, Phone: ud.Phone, IsSuperAdmin: ud.IsSuperAdminRaw, MustChangePassword: ud.MustChangePwdRaw, MemberType: ud.MemberType, Perms: ud.Perms, }, }, nil } // verifyCapToken 向 cap.js 服务端发送 siteverify 请求,校验人机验证令牌。 // 供产品端和管理端的 cap.js 登录逻辑共用。 func verifyCapToken(ctx context.Context, cfg config.CapjsConf, capToken string, logger logx.Logger) error { if cfg.Enable != 1 || cfg.EndpointURL == "" { return response.ErrBadRequest("当前未启用人机验证,请使用图片验证码登录") } if strings.TrimSpace(capToken) == "" { return response.ErrBadRequest("人机验证不能为空") } body, _ := json.Marshal(map[string]string{ "secret": cfg.Secret, "response": capToken, }) verifyURL := cfg.EndpointURL + "/" + cfg.Key + "/siteverify" req, err := http.NewRequestWithContext(ctx, http.MethodPost, verifyURL, bytes.NewBuffer(body)) if err != nil { logger.Errorf("verifyCapToken: create request failed: %v", err) return response.NewCodeError(500, "人机验证校验失败") } req.Header.Set("Content-Type", "application/json") client := &http.Client{Timeout: 10 * time.Second} res, err := client.Do(req) if err != nil { logger.Errorf("verifyCapToken: send request failed: %v", err) return response.NewCodeError(500, "人机验证校验失败") } defer res.Body.Close() raw, err := io.ReadAll(res.Body) if err != nil { logger.Errorf("verifyCapToken: read response failed: %v", err) return response.NewCodeError(500, "人机验证校验失败") } var result struct { Success bool `json:"success"` } if err := json.Unmarshal(raw, &result); err != nil { logger.Errorf("verifyCapToken: unmarshal failed: %v, body: %s", err, raw) return response.NewCodeError(500, "人机验证校验失败") } if !result.Success { return response.ErrBadRequest("人机验证失败,请重试") } return nil }