sync: 对齐小程序 v1.0.2 生产代码并脱敏
This commit is contained in:
@@ -7,7 +7,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
@@ -39,10 +39,20 @@ import (
|
||||
|
||||
const appSideCaptchaCachePrefix = "app:side:captcha:"
|
||||
const appSmsCachePrefix = "app:sms:"
|
||||
const appSmsCooldownPrefix = "app:sms:cooldown:"
|
||||
const appSmsDailyPhonePrefix = "app:sms:daily:phone:"
|
||||
const appSmsDailyIPPrefix = "app:sms:daily:ip:"
|
||||
const appWechatBindTicketCachePrefix = "app:wechat:bind:"
|
||||
const appWechatMiniAppSecretPlaceholder = "CHANGE_ME_WECHAT_MINI_APP_SECRET"
|
||||
const appWechatBindTicketExpire = 10 * time.Minute
|
||||
|
||||
const (
|
||||
appSmsCodeTTL = 5 * time.Minute
|
||||
appSmsCooldownTTL = 60 * time.Second
|
||||
appSmsDailyLimit = 10
|
||||
appSmsDailyExpire = 24 * time.Hour
|
||||
)
|
||||
|
||||
var appSlideCaptcha slide.Captcha
|
||||
|
||||
type appWechatCode2SessionRes struct {
|
||||
@@ -188,16 +198,32 @@ func (s *sAppAuth) SendSms(ctx context.Context, param *model.AppSendSmsParam) (r
|
||||
param.TenantId = strings.TrimSpace(param.TenantId)
|
||||
param.Phone = strings.TrimSpace(param.Phone)
|
||||
param.Scene = strings.TrimSpace(param.Scene)
|
||||
if err = s.validateAppSmsScene(ctx, param); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = s.reserveAppSmsCooldown(ctx, param.TenantId, param.Phone); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = s.checkAppSmsRateLimit(ctx, param.TenantId, param.Phone); err != nil {
|
||||
_ = releaseAppSmsCooldown(ctx, param.TenantId, param.Phone)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
code := fmt.Sprintf("%04d", rand.Intn(10000))
|
||||
code, err := appRandomDigits(6)
|
||||
if err != nil {
|
||||
_ = releaseAppSmsCooldown(ctx, param.TenantId, param.Phone)
|
||||
return nil, gerror.Wrap(err, "生成短信验证码失败")
|
||||
}
|
||||
cacheKey := appSmsCacheKey(param.TenantId, param.Phone, param.Scene)
|
||||
if err = mcache.Set(ctx, cacheKey, code, 5*time.Minute); err != nil {
|
||||
if err = setAppSmsCode(ctx, cacheKey, code); err != nil {
|
||||
_ = releaseAppSmsCooldown(ctx, param.TenantId, param.Phone)
|
||||
return nil, gerror.New("短信验证码发送失败")
|
||||
}
|
||||
|
||||
sentByHTTP, err := sendAppPhoneCode(ctx, param.Phone, code)
|
||||
sentByHTTP, err := sendAppPhoneCode(ctx, param.Phone, code, appSmsServiceScene(param.Scene))
|
||||
if err != nil {
|
||||
_ = mcache.Remove(ctx, cacheKey)
|
||||
_ = deleteAppSmsCode(ctx, cacheKey)
|
||||
_ = releaseAppSmsCooldown(ctx, param.TenantId, param.Phone)
|
||||
g.Log().Errorf(ctx, "APP短信验证码发送失败 scene=%s phone=%s err=%+v", param.Scene, param.Phone, err)
|
||||
return nil, gerror.New("短信验证码发送失败,请稍后重试")
|
||||
}
|
||||
@@ -209,19 +235,110 @@ func (s *sAppAuth) SendSms(ctx context.Context, param *model.AppSendSmsParam) (r
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func sendAppPhoneCode(ctx context.Context, phone string, code string) (sentByHTTP bool, err error) {
|
||||
func (s *sAppAuth) validateAppSmsScene(ctx context.Context, param *model.AppSendSmsParam) error {
|
||||
if err := service.AppAuth().CheckSideCaptcha(ctx, &model.AppCheckSideCaptchaParam{
|
||||
CaptchaKey: strings.TrimSpace(param.CaptchaKey),
|
||||
Point: strings.TrimSpace(param.CaptchaPoint),
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
users, err := s.findAppLoginUsersByAccount(ctx, param.TenantId, param.Phone)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch param.Scene {
|
||||
case "register":
|
||||
if len(users) > 0 {
|
||||
return gerror.New("手机号已注册")
|
||||
}
|
||||
case "login", "forgotPassword", "wechatBindPhone":
|
||||
if len(users) == 0 {
|
||||
return gerror.New("手机号未注册")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *sAppAuth) checkAppSmsRateLimit(ctx context.Context, tenantId string, phone string) error {
|
||||
tenantId = strings.TrimSpace(tenantId)
|
||||
if tenantId == "" {
|
||||
tenantId = consts.DefaultSystemTenantCode
|
||||
}
|
||||
clientIP := strings.TrimSpace(utility.GetClientIp(ctx))
|
||||
for _, item := range []struct {
|
||||
key string
|
||||
name string
|
||||
skip bool
|
||||
}{
|
||||
{appSmsDailyPhonePrefix + strings.TrimSpace(tenantId) + ":" + phone, "手机号", false},
|
||||
{appSmsDailyIPPrefix + clientIP, "当前网络", clientIP == ""},
|
||||
} {
|
||||
if item.skip {
|
||||
continue
|
||||
}
|
||||
value, err := g.Redis().Do(ctx, "INCR", item.key)
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "短信服务暂不可用")
|
||||
}
|
||||
if value.Int() == 1 {
|
||||
_, _ = g.Redis().Do(ctx, "EXPIRE", item.key, int(appSmsDailyExpire.Seconds()))
|
||||
}
|
||||
if value.Int() > appSmsDailyLimit {
|
||||
return gerror.Newf("%s今日短信发送次数已达上限", item.name)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *sAppAuth) reserveAppSmsCooldown(ctx context.Context, tenantId string, phone string) error {
|
||||
key := appSmsCooldownCacheKey(tenantId, phone)
|
||||
value, err := g.Redis().Do(ctx, "SET", key, "1", "NX", "EX", int(appSmsCooldownTTL.Seconds()))
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "短信服务暂不可用")
|
||||
}
|
||||
if value.IsNil() || strings.EqualFold(value.String(), "") {
|
||||
return gerror.New("短信验证码发送过于频繁,请稍后再试")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func releaseAppSmsCooldown(ctx context.Context, tenantId string, phone string) error {
|
||||
_, err := g.Redis().Do(ctx, "DEL", appSmsCooldownCacheKey(tenantId, phone))
|
||||
return err
|
||||
}
|
||||
|
||||
func appSmsCooldownCacheKey(tenantId string, phone string) string {
|
||||
if tenantId == "" {
|
||||
tenantId = consts.DefaultSystemTenantCode
|
||||
}
|
||||
return fmt.Sprintf("%s%s:%s", appSmsCooldownPrefix, tenantId, phone)
|
||||
}
|
||||
|
||||
func appSmsServiceScene(scene string) string {
|
||||
switch scene {
|
||||
case "wechatBindPhone":
|
||||
return "bind_phone"
|
||||
default:
|
||||
return scene
|
||||
}
|
||||
}
|
||||
|
||||
func sendAppPhoneCode(ctx context.Context, phone string, code string, scene string) (sentByHTTP bool, err error) {
|
||||
var cfg appCodeHttpSenderConfig
|
||||
if v, cfgErr := g.Cfg().Get(ctx, "register.sms"); cfgErr == nil && !v.IsNil() {
|
||||
_ = v.Scan(&cfg)
|
||||
}
|
||||
if strings.TrimSpace(cfg.Url) != "" {
|
||||
return true, callAppCodeHTTP(ctx, cfg, phone, code)
|
||||
return true, callAppCodeHTTP(ctx, cfg, phone, code, scene)
|
||||
}
|
||||
if !strings.EqualFold(strings.TrimSpace(g.Cfg().MustGet(ctx, "system.gfmode", "prod").String()), "dev") {
|
||||
return false, gerror.New("短信服务未配置")
|
||||
}
|
||||
g.Log().Infof(ctx, "APP短信验证码已生成 phone=%s code=%s", phone, code)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func callAppCodeHTTP(ctx context.Context, cfg appCodeHttpSenderConfig, contact string, code string) error {
|
||||
func callAppCodeHTTP(ctx context.Context, cfg appCodeHttpSenderConfig, contact string, code string, scene string) error {
|
||||
method := strings.ToUpper(strings.TrimSpace(cfg.Method))
|
||||
if method == "" {
|
||||
method = http.MethodPost
|
||||
@@ -231,7 +348,7 @@ func callAppCodeHTTP(ctx context.Context, cfg appCodeHttpSenderConfig, contact s
|
||||
successStatus = http.StatusOK
|
||||
}
|
||||
|
||||
body := renderAppCodeTemplate(cfg.BodyTemplate, contact, code)
|
||||
body := renderAppCodeTemplate(cfg.BodyTemplate, contact, code, scene)
|
||||
if strings.TrimSpace(body) == "" {
|
||||
bodyBytes, _ := json.Marshal(g.Map{
|
||||
"contact": contact,
|
||||
@@ -280,7 +397,7 @@ func callAppCodeHTTP(ctx context.Context, cfg appCodeHttpSenderConfig, contact s
|
||||
return nil
|
||||
}
|
||||
|
||||
func renderAppCodeTemplate(template string, contact string, code string) string {
|
||||
func renderAppCodeTemplate(template string, contact string, code string, scene string) string {
|
||||
template = strings.TrimSpace(template)
|
||||
if template == "" {
|
||||
return ""
|
||||
@@ -295,6 +412,9 @@ func renderAppCodeTemplate(template string, contact string, code string) string
|
||||
"{phone}", contact,
|
||||
"{{phone}}", contact,
|
||||
"${phone}", contact,
|
||||
"{scene}", scene,
|
||||
"{{scene}}", scene,
|
||||
"${scene}", scene,
|
||||
"{code}", code,
|
||||
"{{code}}", code,
|
||||
"${code}", code,
|
||||
@@ -309,23 +429,37 @@ func appSmsCacheKey(tenantId string, phone string, scene string) string {
|
||||
return fmt.Sprintf("%s%s:%s:%s", appSmsCachePrefix, tenantId, scene, phone)
|
||||
}
|
||||
|
||||
func setAppSmsCode(ctx context.Context, key string, code string) error {
|
||||
_, err := g.Redis().Do(ctx, "SETEX", key, int(appSmsCodeTTL.Seconds()), code)
|
||||
return err
|
||||
}
|
||||
|
||||
func deleteAppSmsCode(ctx context.Context, key string) error {
|
||||
_, err := g.Redis().Do(ctx, "DEL", key)
|
||||
return err
|
||||
}
|
||||
|
||||
func verifyAppSmsCode(ctx context.Context, tenantId string, phone string, scene string, code string) error {
|
||||
value, err := mcache.Get(ctx, appSmsCacheKey(tenantId, phone, scene))
|
||||
value, err := g.Redis().Get(ctx, appSmsCacheKey(tenantId, phone, scene))
|
||||
if err != nil || value == nil {
|
||||
return gerror.NewCode(consts.CodeCaptchaError, "短信验证码已失效")
|
||||
}
|
||||
if fmt.Sprintf("%v", value) != code {
|
||||
if strings.TrimSpace(value.String()) != strings.TrimSpace(code) {
|
||||
return gerror.NewCode(consts.CodeCaptchaError, "短信验证码错误")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func verifyOptionalAppSmsCode(ctx context.Context, tenantId string, phone string, scene string, code string) error {
|
||||
code = strings.TrimSpace(code)
|
||||
if code == "" {
|
||||
return nil
|
||||
func appRandomDigits(length int) (string, error) {
|
||||
if length <= 0 {
|
||||
length = 6
|
||||
}
|
||||
return verifyAppSmsCode(ctx, tenantId, phone, scene, code)
|
||||
limit := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(length)), nil)
|
||||
value, err := cryptorand.Int(cryptorand.Reader, limit)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("%0*d", length, value.Int64()), nil
|
||||
}
|
||||
|
||||
func (s *sAppAuth) PhoneRegister(ctx context.Context, param *model.AppPhoneRegisterParam) (res *model.AppAuthTokenModel, err error) {
|
||||
@@ -335,7 +469,7 @@ func (s *sAppAuth) PhoneRegister(ctx context.Context, param *model.AppPhoneRegis
|
||||
param.TenantId = strings.TrimSpace(param.TenantId)
|
||||
param.Phone = strings.TrimSpace(param.Phone)
|
||||
param.SmsCode = strings.TrimSpace(param.SmsCode)
|
||||
if err = verifyOptionalAppSmsCode(ctx, param.TenantId, param.Phone, "register", param.SmsCode); err != nil {
|
||||
if err = verifyAppSmsCode(ctx, param.TenantId, param.Phone, "register", param.SmsCode); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if param.TenantId == "" {
|
||||
@@ -406,7 +540,7 @@ func (s *sAppAuth) PhoneRegister(ctx context.Context, param *model.AppPhoneRegis
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
_ = mcache.Remove(ctx, appSmsCacheKey(param.TenantId, param.Phone, "register"))
|
||||
_ = deleteAppSmsCode(ctx, appSmsCacheKey(param.TenantId, param.Phone, "register"))
|
||||
|
||||
return s.PhoneLogin(ctx, &model.AppPhoneLoginParam{
|
||||
TenantId: param.TenantId,
|
||||
@@ -522,6 +656,43 @@ func (s *sAppAuth) PhoneLogin(ctx context.Context, param *model.AppPhoneLoginPar
|
||||
return s.buildAppAuthToken(ctx, user)
|
||||
}
|
||||
|
||||
func (s *sAppAuth) PhoneSmsLogin(ctx context.Context, param *model.AppPhoneSmsLoginParam) (res *model.AppAuthTokenModel, err error) {
|
||||
if param == nil {
|
||||
return nil, gerror.New("请求参数不能为空")
|
||||
}
|
||||
param.TenantId = strings.TrimSpace(param.TenantId)
|
||||
param.Phone = strings.TrimSpace(param.Phone)
|
||||
param.SmsCode = strings.TrimSpace(param.SmsCode)
|
||||
if err = verifyAppSmsCode(ctx, param.TenantId, param.Phone, "login", param.SmsCode); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
users, err := s.findAppWechatBindUsersByPhone(ctx, param.TenantId, param.Phone)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(users) == 0 {
|
||||
return nil, gerror.New("手机号未注册")
|
||||
}
|
||||
if len(users) > 1 && param.SelectedUserId <= 0 {
|
||||
return &model.AppAuthTokenModel{NeedSelect: true, Candidates: appLoginCandidateModels(ctx, users)}, nil
|
||||
}
|
||||
user := users[0]
|
||||
if param.SelectedUserId > 0 {
|
||||
user = nil
|
||||
for _, item := range users {
|
||||
if item != nil && item.UserId == param.SelectedUserId {
|
||||
user = item
|
||||
break
|
||||
}
|
||||
}
|
||||
if user == nil {
|
||||
return nil, gerror.New("选择的账号不属于当前手机号")
|
||||
}
|
||||
}
|
||||
_ = deleteAppSmsCode(ctx, appSmsCacheKey(param.TenantId, param.Phone, "login"))
|
||||
return s.buildAppAuthToken(ctx, user)
|
||||
}
|
||||
|
||||
func (s *sAppAuth) WechatLogin(ctx context.Context, param *model.AppWechatLoginParam) (res *model.AppWechatLoginModel, err error) {
|
||||
if param == nil {
|
||||
return nil, gerror.New("请求参数不能为空")
|
||||
@@ -619,6 +790,7 @@ func (s *sAppAuth) WechatBindPhone(ctx context.Context, param *model.AppWechatBi
|
||||
if err = s.saveWechatOpenId(ctx, user, ticketData.OpenId, ticketData.UnionId); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_ = deleteAppSmsCode(ctx, appSmsCacheKey(tenantId, phone, "wechatBindPhone"))
|
||||
_ = mcache.Remove(ctx, appWechatBindTicketKey(param.BindTicket))
|
||||
|
||||
token, err := s.buildAppAuthToken(ctx, user)
|
||||
@@ -819,20 +991,12 @@ func appEnsureWechatBindPhoneVerified(ctx context.Context, param *model.AppWecha
|
||||
if ticketData != nil && strings.TrimSpace(ticketData.VerifiedPhone) == strings.TrimSpace(phone) {
|
||||
return nil
|
||||
}
|
||||
captchaKey := strings.TrimSpace(param.CaptchaKey)
|
||||
captchaPoint := strings.TrimSpace(param.CaptchaPoint)
|
||||
if captchaKey == "" || captchaPoint == "" {
|
||||
return gerror.New("请先完成图形验证")
|
||||
}
|
||||
if err := service.AppAuth().CheckSideCaptcha(ctx, &model.AppCheckSideCaptchaParam{
|
||||
CaptchaKey: captchaKey,
|
||||
Point: captchaPoint,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if ticketData == nil || strings.TrimSpace(ticketData.OpenId) == "" || strings.TrimSpace(phone) == "" {
|
||||
return gerror.New("微信绑定参数不完整")
|
||||
}
|
||||
if err := verifyAppSmsCode(ctx, ticketData.TenantId, phone, "wechatBindPhone", param.SmsCode); err != nil {
|
||||
return err
|
||||
}
|
||||
ticketData.VerifiedPhone = strings.TrimSpace(phone)
|
||||
if err := mcache.Set(ctx, appWechatBindTicketKey(param.BindTicket), ticketData, appWechatBindTicketExpire); err != nil {
|
||||
return gerror.Wrap(err, "保存手机号验证状态失败")
|
||||
@@ -1075,7 +1239,7 @@ func (s *sAppAuth) ForgotPassword(ctx context.Context, param *model.AppForgotPas
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = mcache.Remove(ctx, appSmsCacheKey(param.TenantId, param.Phone, "forgotPassword"))
|
||||
_ = deleteAppSmsCode(ctx, appSmsCacheKey(param.TenantId, param.Phone, "forgotPassword"))
|
||||
|
||||
salt := utility.RandomString(5)
|
||||
password := appPasswordEncryptForStore(param.Password, salt)
|
||||
|
||||
Reference in New Issue
Block a user