2023-07-22 20:51:46 +08:00
|
|
|
|
package api
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
2023-11-07 21:05:21 +08:00
|
|
|
|
"context"
|
2023-07-22 20:51:46 +08:00
|
|
|
|
"fmt"
|
|
|
|
|
|
"mayfly-go/internal/auth/api/form"
|
2023-08-31 21:49:20 +08:00
|
|
|
|
"mayfly-go/internal/auth/config"
|
2024-11-20 22:43:53 +08:00
|
|
|
|
"mayfly-go/internal/auth/imsg"
|
2024-12-08 13:04:23 +08:00
|
|
|
|
"mayfly-go/internal/auth/pkg/captcha"
|
|
|
|
|
|
"mayfly-go/internal/auth/pkg/otp"
|
2025-04-23 20:36:32 +08:00
|
|
|
|
"mayfly-go/internal/pkg/utils"
|
2023-07-22 20:51:46 +08:00
|
|
|
|
sysapp "mayfly-go/internal/sys/application"
|
|
|
|
|
|
sysentity "mayfly-go/internal/sys/domain/entity"
|
|
|
|
|
|
"mayfly-go/pkg/biz"
|
|
|
|
|
|
"mayfly-go/pkg/cache"
|
2023-10-26 17:15:49 +08:00
|
|
|
|
"mayfly-go/pkg/errorx"
|
2024-04-28 23:45:57 +08:00
|
|
|
|
"mayfly-go/pkg/model"
|
2023-07-22 20:51:46 +08:00
|
|
|
|
"mayfly-go/pkg/req"
|
2023-10-12 12:14:56 +08:00
|
|
|
|
"mayfly-go/pkg/utils/collx"
|
2023-07-22 20:51:46 +08:00
|
|
|
|
"mayfly-go/pkg/utils/cryptox"
|
2023-09-12 20:54:07 +08:00
|
|
|
|
"mayfly-go/pkg/ws"
|
2023-07-22 20:51:46 +08:00
|
|
|
|
"time"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
type AccountLogin struct {
|
2024-12-16 23:29:18 +08:00
|
|
|
|
accountApp sysapp.Account `inject:"T"`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (a *AccountLogin) ReqConfs() *req.Confs {
|
|
|
|
|
|
reqs := [...]*req.Conf{
|
|
|
|
|
|
// 用户账号密码登录
|
|
|
|
|
|
req.NewPost("/login", a.Login).Log(req.NewLogSaveI(imsg.LogAccountLogin)).DontNeedToken(),
|
|
|
|
|
|
|
|
|
|
|
|
req.NewGet("/refreshToken", a.RefreshToken).DontNeedToken(),
|
|
|
|
|
|
|
|
|
|
|
|
// 用户退出登录
|
|
|
|
|
|
req.NewPost("/logout", a.Logout),
|
|
|
|
|
|
|
|
|
|
|
|
// 用户otp双因素校验
|
|
|
|
|
|
req.NewPost("/otp-verify", a.OtpVerify).DontNeedToken(),
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return req.NewConfs("/auth/accounts", reqs[:]...)
|
2023-07-22 20:51:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** 用户账号密码登录 **/
|
|
|
|
|
|
|
|
|
|
|
|
// @router /auth/accounts/login [post]
|
|
|
|
|
|
func (a *AccountLogin) Login(rc *req.Ctx) {
|
2025-05-24 16:22:54 +08:00
|
|
|
|
loginForm := req.BindJsonAndValid[*form.LoginForm](rc)
|
2024-11-20 22:43:53 +08:00
|
|
|
|
ctx := rc.MetaCtx
|
2023-07-22 20:51:46 +08:00
|
|
|
|
|
2023-08-31 21:49:20 +08:00
|
|
|
|
accountLoginSecurity := config.GetAccountLoginSecurity()
|
2023-07-22 20:51:46 +08:00
|
|
|
|
// 判断是否有开启登录验证码校验
|
|
|
|
|
|
if accountLoginSecurity.UseCaptcha {
|
|
|
|
|
|
// 校验验证码
|
2024-11-20 22:43:53 +08:00
|
|
|
|
biz.IsTrueI(ctx, captcha.Verify(loginForm.Cid, loginForm.Captcha), imsg.ErrCaptchaErr)
|
2023-07-22 20:51:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
username := loginForm.Username
|
|
|
|
|
|
|
|
|
|
|
|
clientIp := getIpAndRegion(rc)
|
2023-10-12 12:14:56 +08:00
|
|
|
|
rc.ReqParam = collx.Kvs("username", username, "ip", clientIp)
|
2023-07-22 20:51:46 +08:00
|
|
|
|
|
2025-04-23 20:36:32 +08:00
|
|
|
|
originPwd, err := utils.DefaultRsaDecrypt(loginForm.Password, true)
|
2024-11-20 22:43:53 +08:00
|
|
|
|
biz.ErrIsNilAppendErr(err, "decryption password error: %s")
|
2023-07-22 20:51:46 +08:00
|
|
|
|
|
|
|
|
|
|
account := &sysentity.Account{Username: username}
|
2024-12-16 23:29:18 +08:00
|
|
|
|
err = a.accountApp.GetByCond(model.NewModelCond(account).Columns("Id", "Name", "Username", "Password", "Status", "LastLoginTime", "LastLoginIp", "OtpSecret"))
|
2023-07-22 20:51:46 +08:00
|
|
|
|
|
|
|
|
|
|
failCountKey := fmt.Sprintf("account:login:failcount:%s", username)
|
|
|
|
|
|
nowFailCount := cache.GetInt(failCountKey)
|
|
|
|
|
|
loginFailCount := accountLoginSecurity.LoginFailCount
|
|
|
|
|
|
loginFailMin := accountLoginSecurity.LoginFailMin
|
2024-11-20 22:43:53 +08:00
|
|
|
|
biz.IsTrueI(ctx, nowFailCount < loginFailCount, imsg.ErrLoginRestrict, "failCount", loginFailCount, "min", loginFailMin)
|
2023-07-22 20:51:46 +08:00
|
|
|
|
|
|
|
|
|
|
if err != nil || !cryptox.CheckPwdHash(originPwd, account.Password) {
|
|
|
|
|
|
nowFailCount++
|
2025-04-23 20:36:32 +08:00
|
|
|
|
cache.Set(failCountKey, nowFailCount, time.Minute*time.Duration(loginFailMin))
|
2024-11-20 22:43:53 +08:00
|
|
|
|
panic(errorx.NewBizI(ctx, imsg.ErrLoginFail, "failCount", nowFailCount))
|
2023-07-22 20:51:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 校验密码强度(新用户第一次登录密码与账号名一致)
|
2024-11-20 22:43:53 +08:00
|
|
|
|
// biz.IsTrueBy(utils.CheckAccountPasswordLever(originPwd), errorx.NewBizCode(401, "您的密码安全等级较低,请修改后重新登录"))
|
|
|
|
|
|
rc.ResData = LastLoginCheck(ctx, account, accountLoginSecurity, clientIp)
|
2023-07-22 20:51:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type OtpVerifyInfo struct {
|
2024-05-13 19:55:43 +08:00
|
|
|
|
AccountId uint64
|
|
|
|
|
|
Username string
|
|
|
|
|
|
OptStatus int
|
|
|
|
|
|
AccessToken string
|
|
|
|
|
|
RefreshToken string
|
|
|
|
|
|
OtpSecret string
|
2023-07-22 20:51:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// OTP双因素校验
|
|
|
|
|
|
func (a *AccountLogin) OtpVerify(rc *req.Ctx) {
|
2025-05-24 16:22:54 +08:00
|
|
|
|
otpVerify := req.BindJsonAndValid[*form.OtpVerfiy](rc)
|
2024-11-20 22:43:53 +08:00
|
|
|
|
ctx := rc.MetaCtx
|
2023-07-22 20:51:46 +08:00
|
|
|
|
|
|
|
|
|
|
tokenKey := fmt.Sprintf("otp:token:%s", otpVerify.OtpToken)
|
|
|
|
|
|
otpInfo := new(OtpVerifyInfo)
|
2024-05-13 19:55:43 +08:00
|
|
|
|
ok := cache.Get(tokenKey, otpInfo)
|
2024-11-20 22:43:53 +08:00
|
|
|
|
biz.IsTrueI(ctx, ok, imsg.ErrOtpTokenInvalid)
|
2023-07-22 20:51:46 +08:00
|
|
|
|
|
|
|
|
|
|
failCountKey := fmt.Sprintf("account:otp:failcount:%d", otpInfo.AccountId)
|
|
|
|
|
|
failCount := cache.GetInt(failCountKey)
|
2024-11-20 22:43:53 +08:00
|
|
|
|
biz.IsTrueI(ctx, failCount < 5, imsg.ErrOtpCheckRestrict)
|
2023-07-22 20:51:46 +08:00
|
|
|
|
|
|
|
|
|
|
otpStatus := otpInfo.OptStatus
|
|
|
|
|
|
accessToken := otpInfo.AccessToken
|
|
|
|
|
|
accountId := otpInfo.AccountId
|
|
|
|
|
|
otpSecret := otpInfo.OtpSecret
|
|
|
|
|
|
|
|
|
|
|
|
if !otp.Validate(otpVerify.Code, otpSecret) {
|
2025-04-23 20:36:32 +08:00
|
|
|
|
cache.Set(failCountKey, failCount+1, time.Minute*time.Duration(10))
|
2024-11-20 22:43:53 +08:00
|
|
|
|
panic(errorx.NewBizI(ctx, imsg.ErrOtpCheckFail))
|
2023-07-22 20:51:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果是未注册状态,则更新account表的otpSecret信息
|
|
|
|
|
|
if otpStatus == OtpStatusNoReg {
|
|
|
|
|
|
update := &sysentity.Account{OtpSecret: otpSecret}
|
|
|
|
|
|
update.Id = accountId
|
2024-01-05 08:55:34 +08:00
|
|
|
|
biz.ErrIsNil(update.OtpSecretEncrypt())
|
2024-12-16 23:29:18 +08:00
|
|
|
|
biz.ErrIsNil(a.accountApp.Update(context.Background(), update))
|
2023-07-22 20:51:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
la := &sysentity.Account{Username: otpInfo.Username}
|
|
|
|
|
|
la.Id = accountId
|
2024-11-20 22:43:53 +08:00
|
|
|
|
go saveLogin(ctx, la, getIpAndRegion(rc))
|
2023-07-22 20:51:46 +08:00
|
|
|
|
|
|
|
|
|
|
cache.Del(tokenKey)
|
2024-05-13 19:55:43 +08:00
|
|
|
|
rc.ResData = collx.Kvs("token", accessToken, "refresh_token", otpInfo.RefreshToken)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (a *AccountLogin) RefreshToken(rc *req.Ctx) {
|
|
|
|
|
|
refreshToken := rc.Query("refresh_token")
|
2024-11-20 22:43:53 +08:00
|
|
|
|
biz.NotEmpty(refreshToken, "refresh_token cannot be empty")
|
2024-05-13 19:55:43 +08:00
|
|
|
|
|
|
|
|
|
|
accountId, username, err := req.ParseToken(refreshToken)
|
2024-05-16 17:26:32 +08:00
|
|
|
|
biz.IsTrueBy(err == nil, errorx.PermissionErr)
|
|
|
|
|
|
|
2024-05-13 19:55:43 +08:00
|
|
|
|
token, refreshToken, err := req.CreateToken(accountId, username)
|
|
|
|
|
|
biz.ErrIsNil(err)
|
|
|
|
|
|
rc.ResData = collx.Kvs("token", token, "refresh_token", refreshToken)
|
2023-07-22 20:51:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (a *AccountLogin) Logout(rc *req.Ctx) {
|
2023-11-07 21:05:21 +08:00
|
|
|
|
la := rc.GetLoginAccount()
|
|
|
|
|
|
req.GetPermissionCodeRegistery().Remove(la.Id)
|
|
|
|
|
|
ws.CloseClient(ws.UserId(la.Id))
|
2023-07-22 20:51:46 +08:00
|
|
|
|
}
|