feat: 添加二步验证,添加二步验证相关

This commit is contained in:
sunxiaolong
2024-07-13 13:02:29 +08:00
parent a3356ec021
commit c72e8b5565
34 changed files with 375 additions and 61 deletions

View File

@@ -12,7 +12,7 @@ server:
steps:
- name: build new image
commands:
- docker compose -f ./manifest/docker/docker-compose.yml build --no-cache
- USE_PROXY=1 docker compose -f ./manifest/docker/docker-compose.yml build --no-cache
- name: clean old container
commands:
- docker container inspect kami_backend &> /dev/null && docker container rm -f kami_backend
@@ -50,7 +50,7 @@ server:
steps:
- name: build new image
commands:
- docker compose -f ./manifest/docker/docker-compose.yml build --no-cache
- USE_PROXY=0 docker compose -f ./manifest/docker/docker-compose.yml build --no-cache
- name: clean old container
commands:
- docker container inspect kami_backend &> /dev/null && docker container rm -f kami_backend

View File

@@ -22,4 +22,8 @@ type ISysUserV1 interface {
UserStatus(ctx context.Context, req *v1.UserStatusReq) (res *v1.UserStatusRes, err error)
UserDelete(ctx context.Context, req *v1.UserDeleteReq) (res *v1.UserDeleteRes, err error)
UserGetByIds(ctx context.Context, req *v1.UserGetByIdsReq) (res *v1.UserGetByIdsRes, err error)
TotpStatusGet(ctx context.Context, req *v1.TotpStatusGetReq) (res *v1.TotpStatusGetRes, err error)
TotpSet(ctx context.Context, req *v1.TotpSetReq) (res *v1.TotpSetRes, err error)
TotpImageGet(ctx context.Context, req *v1.TotpImageGetReq) (res *v1.TotpImageGetRes, err error)
TotpReset(ctx context.Context, req *v1.TotpResetReq) (res *v1.TotpResetRes, err error)
}

View File

@@ -90,6 +90,7 @@ type UserChangePwdReq struct {
g.Meta `path:"/user/changePwd" tags:"用户管理" method:"put" summary:"重置用户密码"`
OldPassword string `p:"oldPassword" v:"required#旧密码不能为空"`
NewPassword string `p:"newPassword" v:"required#新密码不能为空"`
TotpCode string `p:"totpCode"`
}
type UserChangePwdRes struct{}

48
api/sysUser/v1/totp.go Normal file
View File

@@ -0,0 +1,48 @@
package v1
import (
"github.com/gogf/gf/v2/frame/g"
)
type TotpStatusGetReq struct {
g.Meta `path:"/user/totp/status" tags:"TOTP二步验证" method:"get" summary:"确认当前用户是否开启totp"`
}
type TotpStatusGetRes struct {
Status bool `json:"status"`
Image string `json:"image"`
OtpSecret string `json:"otpSecret"`
OtpKey string `json:"otpKey"`
}
// TotpSetReq 设置二步验证
type TotpSetReq struct {
g.Meta `path:"/user/totp/set" tags:"TOTP二步验证" method:"post" summary:"设置二步验证"`
Code string `p:"code" v:"required#验证码不能为空"`
OtpKey string `p:"otpKey" v:"required#otpKey不能为空"`
OtpSecret string `p:"otpSecret" v:"required#otpSecret不能为空"`
Password string `p:"password" v:"required#密码不能为空"`
}
type TotpSetRes struct {
}
// TotpImageGetReq 查看两步验证图像
type TotpImageGetReq struct {
g.Meta `path:"/user/totp/image" tags:"TOTP二步验证" method:"get" summary:"查看两步验证图像"`
Code string `p:"code" v:"required#验证码不能为空"`
}
type TotpImageGetRes struct {
Image string `json:"image"`
}
// TotpResetReq 重置两步验证
type TotpResetReq struct {
g.Meta `path:"/user/totp/reset" tags:"TOTP二步验证" method:"post" summary:"重置两步验证"`
Code string `p:"code" v:"required#验证码不能为空"`
}
type TotpResetRes struct {
Image string `json:"image"`
}

View File

@@ -1 +0,0 @@
package v1

View File

@@ -10,6 +10,7 @@ type UserLoginReq struct {
Password string `p:"password" v:"required#密码不能为空"`
VerifyCode string `p:"verifyCode" v:"required#验证码不能为空"`
VerifyKey string `p:"verifyKey" v:"required#验证秘钥不能为空"`
TotpCode string `p:"totpCode"`
}
type UserLoginRes struct {

View File

@@ -2,6 +2,7 @@ package apple_card_info
import (
"context"
"kami/internal/model/entity"
v1 "kami/api/apple_card_info/v1"
"kami/api/commonApi"
@@ -15,14 +16,17 @@ import (
func (c *ControllerV1) CardHistoryInfoList(ctx context.Context, req *v1.CardHistoryInfoListReq) (res *v1.CardHistoryInfoListRes, err error) {
appleCardService := service.AppleAccount()
_, userId, err := service.SysUser().GetUserIdFromToken(ctx)
_, userInfo, err := service.SysUser().GetUserIdFromToken(ctx)
if err != nil {
err = errHandler.WrapError(ctx, gcode.CodeOperationFailed, err, "token解析失败")
return
}
if userInfo == nil {
userInfo = &entity.V1SysUser{}
}
total, data, err := appleCardService.GetWalletHistory(ctx, model.CardAppleAccountHistoryQueryInput{
CardHistoryInfoListReq: req,
UserId: userId,
UserId: userInfo.Id,
})
res = &v1.CardHistoryInfoListRes{
CommonPageRes: commonApi.CommonPageRes[v1.CardHistoryModel]{

View File

@@ -2,6 +2,7 @@ package apple_card_info
import (
"context"
"kami/internal/model/entity"
v1 "kami/api/apple_card_info/v1"
"kami/internal/errHandler"
@@ -20,10 +21,13 @@ func (c *ControllerV1) CardInfoBatchAddFromXlsx(ctx context.Context, req *v1.Car
err = errHandler.WrapError(ctx, errHandler.ErrFileBroken, err, "")
return
}
_, userId, err := service.SysUser().GetUserIdFromToken(ctx)
_, userInfo, err := service.SysUser().GetUserIdFromToken(ctx)
if err != nil {
return
}
if userInfo == nil {
userInfo = &entity.V1SysUser{}
}
xlsx, err := excelize.OpenReader(f)
if err != nil {
err = errHandler.WrapError(ctx, errHandler.ErrFileBroken, err, "")
@@ -65,7 +69,7 @@ func (c *ControllerV1) CardInfoBatchAddFromXlsx(ctx context.Context, req *v1.Car
Account: accountName,
Password: accountPass,
},
UploadUserId: userId,
UploadUserId: userInfo.Id,
}, nil)
if err != nil {
failedItem++

View File

@@ -2,6 +2,7 @@ package apple_card_info
import (
"context"
"kami/internal/model/entity"
v1 "kami/api/apple_card_info/v1"
"kami/internal/errHandler"
@@ -28,13 +29,16 @@ func (c *ControllerV1) CardInfoCreate(ctx context.Context, req *v1.CardInfoCreat
err = errHandler.WrapError(ctx, errHandler.ErrAppleAccountRepeatByUnLegalOperation, err, "")
return
}
_, userId, err := service.SysUser().GetUserIdFromToken(ctx)
_, userInfo, err := service.SysUser().GetUserIdFromToken(ctx)
if err != nil {
return
}
if userInfo == nil {
userInfo = &entity.V1SysUser{}
}
err = appleAccountService.Add(ctx, model.AppleAccountRecordInput{
AppleAccountRecord: req.AppleAccountRecord,
UploadUserId: userId,
UploadUserId: userInfo.Id,
}, nil)
return
}

View File

@@ -2,6 +2,7 @@ package apple_card_info
import (
"context"
"kami/internal/model/entity"
v1 "kami/api/apple_card_info/v1"
"kami/api/commonApi"
@@ -13,14 +14,17 @@ import (
func (c *ControllerV1) CardInfoList(ctx context.Context, req *v1.CardInfoListReq) (res *v1.CardInfoListRes, err error) {
appleAccountService := service.AppleAccount()
_, userId, err := service.SysUser().GetUserIdFromToken(ctx)
_, userInfo, err := service.SysUser().GetUserIdFromToken(ctx)
if err != nil {
return
}
if userInfo == nil {
userInfo = &entity.V1SysUser{}
}
total, list, err := appleAccountService.List(ctx, model.CardInfoParamsInput{
CommonPageReq: req.CommonPageReq,
Account: req.Account,
UploadUserId: userId,
UploadUserId: userInfo.Id,
})
res = &v1.CardInfoListRes{
CommonPageRes: commonApi.CommonPageRes[v1.AppleCardListRecord]{

View File

@@ -15,14 +15,14 @@ import (
func (c *ControllerV1) RechargeList(ctx context.Context, req *v1.RechargeListReq) (res *v1.RechargeListRes, err error) {
rechargeOrderService := service.RechargeHistory()
_, userId, err := service.SysUser().GetUserIdFromToken(ctx)
if err != nil {
_, userInfo, err := service.SysUser().GetUserIdFromToken(ctx)
if err != nil || userInfo == nil {
err = errHandler.WrapError(ctx, gcode.CodeNotAuthorized, err, "获取用户失败")
return
}
total, list, err := rechargeOrderService.List(ctx, model.AppleCardRechargeParamsInput{
RechargeListReq: *req,
UserId: userId,
UserId: userInfo.Id,
})
if err != nil {
err = errHandler.WrapError(ctx, gcode.CodeDbOperationError, err, "")

View File

@@ -2,6 +2,7 @@ package order
import (
"context"
"kami/internal/model/entity"
"math"
"github.com/gogf/gf/v2/errors/gcode"
@@ -14,13 +15,16 @@ import (
func (c *ControllerV1) OrderSummaryGetList(ctx context.Context, req *v1.OrderSummaryGetListReq) (res *v1.OrderSummaryGetListRes, err error) {
orderService := service.OrderSummary()
_, userId, err := service.SysUser().GetUserIdFromToken(ctx)
_, userInfo, err := service.SysUser().GetUserIdFromToken(ctx)
if err != nil {
return
}
if userInfo == nil {
userInfo = &entity.V1SysUser{}
}
total, list, err := orderService.GetList(ctx, model.OrderSummaryListInput{
OrderSummaryGetListReq: *req,
UserId: userId,
UserId: userInfo.Id,
})
if err != nil {
err = errHandler.WrapError(ctx, gcode.CodeDbOperationError, err, "获取订单汇总列表失败")

View File

@@ -0,0 +1,35 @@
package sysUser
import (
"context"
"kami/internal/errHandler"
"kami/internal/service"
"kami/utility/mfa"
"github.com/gogf/gf/v2/errors/gcode"
"kami/api/sysUser/v1"
)
func (c *ControllerV1) TotpImageGet(ctx context.Context, req *v1.TotpImageGetReq) (res *v1.TotpImageGetRes, err error) {
userService := service.SysUser()
needAuth, userInfo, err := userService.GetUserIdFromToken(ctx)
if err != nil || userInfo == nil || userInfo.Id == "" {
err = errHandler.WrapError(ctx, gcode.CodeInternalError, err, "获取用户信息失败")
return
}
if !needAuth {
return nil, errHandler.WrapError(ctx, gcode.CodeInternalError, err, "用户未登录")
}
if !mfa.ValidCode(req.Code, userInfo.OtpSecret) {
return nil, errHandler.WrapError(ctx, errHandler.ErrLoginUserTotpError, err, "验证码错误")
}
otp, err := mfa.GetOtp(userInfo.Id, userInfo.Username, userInfo.OtpKey, userInfo.OtpSecret)
if err != nil {
err = errHandler.WrapError(ctx, gcode.CodeInternalError, err, "获取otp失败")
return
}
res = &v1.TotpImageGetRes{
Image: otp.QrImage,
}
return
}

View File

@@ -0,0 +1,28 @@
package sysUser
import (
"context"
"kami/internal/errHandler"
"kami/internal/service"
"kami/utility/mfa"
"github.com/gogf/gf/v2/errors/gcode"
"kami/api/sysUser/v1"
)
func (c *ControllerV1) TotpReset(ctx context.Context, req *v1.TotpResetReq) (res *v1.TotpResetRes, err error) {
userService := service.SysUser()
needAuth, userInfo, err := userService.GetUserIdFromToken(ctx)
if err != nil || userInfo == nil || userInfo.Id == "" {
err = errHandler.WrapError(ctx, gcode.CodeInternalError, err, "获取用户信息失败")
return
}
if !needAuth {
return nil, errHandler.WrapError(ctx, gcode.CodeInternalError, err, "用户未登录")
}
if !mfa.ValidCode(req.Code, userInfo.OtpSecret) {
return nil, errHandler.WrapError(ctx, errHandler.ErrLoginUserTotpError, err, "验证码错误")
}
err = userService.ResetTotp(ctx, userInfo)
return
}

View File

@@ -0,0 +1,34 @@
package sysUser
import (
"context"
"kami/internal/errHandler"
"kami/internal/service"
"kami/utility/mfa"
"kami/utility/utils"
"github.com/gogf/gf/v2/errors/gcode"
"kami/api/sysUser/v1"
)
func (c *ControllerV1) TotpSet(ctx context.Context, req *v1.TotpSetReq) (res *v1.TotpSetRes, err error) {
userService := service.SysUser()
needAuth, userInfo, err := userService.GetUserIdFromToken(ctx)
if err != nil || userInfo == nil || userInfo.Id == "" {
err = errHandler.WrapError(ctx, gcode.CodeInternalError, err, "获取用户信息失败")
return
}
if !needAuth {
return nil, errHandler.WrapError(ctx, gcode.CodeInternalError, err, "用户未登录")
}
if utils.EncryptPassword(req.Password, userInfo.UserSalt) != userInfo.UserPassword {
err = errHandler.WrapError(ctx, errHandler.ErrLoginUserPasswordError, err)
return
}
if !mfa.ValidCode(req.Code, req.OtpSecret) {
err = errHandler.WrapError(ctx, errHandler.ErrLoginUserTotpError, err)
return
}
err = userService.SetTotp(ctx, userInfo, req.OtpKey, req.OtpSecret)
return
}

View File

@@ -0,0 +1,38 @@
package sysUser
import (
"context"
"kami/internal/errHandler"
"kami/internal/service"
"kami/utility/mfa"
"github.com/gogf/gf/v2/errors/gcode"
"kami/api/sysUser/v1"
)
func (c *ControllerV1) TotpStatusGet(ctx context.Context, req *v1.TotpStatusGetReq) (res *v1.TotpStatusGetRes, err error) {
res = &v1.TotpStatusGetRes{}
userService := service.SysUser()
needAuth, userInfo, err := userService.GetUserIdFromToken(ctx)
if err != nil || userInfo == nil || userInfo.Id == "" {
err = errHandler.WrapError(ctx, gcode.CodeInternalError, err, "获取用户信息失败")
return
}
if !needAuth {
return nil, errHandler.WrapError(ctx, gcode.CodeInternalError, err, "用户未登录")
}
if userInfo.OtpSecret == "" {
res.Status = false
otp, err2 := mfa.GetOtp(userInfo.Id, userInfo.Username, userInfo.OtpKey, userInfo.OtpSecret)
if err2 != nil {
err = err2
return
}
res.Image = otp.QrImage
res.OtpSecret = otp.Secret
res.OtpKey = otp.Key
} else {
res.Status = true
}
return
}

View File

@@ -2,8 +2,7 @@ package sysUser
import (
"context"
"github.com/gogf/gf/v2/os/glog"
"kami/utility/mfa"
v1 "kami/api/sysUser/v1"
"kami/internal/errHandler"
@@ -17,24 +16,26 @@ import (
func (c *ControllerV1) UserChangePwd(ctx context.Context, req *v1.UserChangePwdReq) (res *v1.UserChangePwdRes, err error) {
userService := service.SysUser()
needAuth, userId, err := userService.GetUserIdFromToken(ctx)
needAuth, userItem, err := userService.GetUserIdFromToken(ctx)
if !needAuth {
err = gerror.NewCode(gcode.CodeNotAuthorized, "当前用户权限不正确")
return
}
userItem, err := userService.GetUserById(ctx, userId)
if err != nil || userItem.Id == "" {
if err != nil || userItem == nil || userItem.Id == "" {
err = errHandler.WrapError(ctx, gcode.CodeInternalError, err, "获取用户信息失败")
return
}
glog.Info(ctx, userItem.UserPassword, "新密码", utils.EncryptPassword(req.OldPassword, userItem.UserSalt))
if userItem.UserPassword != utils.EncryptPassword(req.OldPassword, userItem.UserSalt) {
err = gerror.NewCode(gcode.CodeNotAuthorized, "旧密码错误")
return
}
if userItem.OtpSecret != "" && !mfa.ValidCode(req.TotpCode, userItem.OtpSecret) {
err = gerror.NewCode(gcode.CodeValidationFailed, "二次验证错误")
return
}
err = userService.ChangePwd(ctx, &model.UserChangePwdInput{
UserChangePwdReq: *req,
UserId: userId,
UserId: userItem.Id,
})
if err != nil {
err = errHandler.WrapError(ctx, gcode.CodeInternalError, err, "修改密码失败")

View File

@@ -11,16 +11,16 @@ import (
)
func (c *ControllerV1) SysPaymentGetOne(ctx context.Context, req *v1.SysPaymentGetOneReq) (res *v1.SysPaymentGetOneRes, err error) {
needAuth, userId, err := service.SysUser().GetUserIdFromToken(ctx)
needAuth, userInfo, err := service.SysUser().GetUserIdFromToken(ctx)
if err != nil {
err = errHandler.WrapError(ctx, gcode.CodeInternalError, err, "获取用户信息失败")
}
if needAuth {
if userId == "" {
if userInfo == nil || userInfo.Id == "" {
err = errHandler.WrapError(ctx, gcode.CodeNotAuthorized, err, "权限不足")
return
}
req.UserId = userId
req.UserId = userInfo.Id
}
sysUserPayment := service.SysUserPayment()
resData, err := sysUserPayment.GetDetailById(ctx, &model.SysUserPaymentGetOneInput{

View File

@@ -11,16 +11,16 @@ import (
)
func (c *ControllerV1) SysPaymentRecordsGet(ctx context.Context, req *v1.SysPaymentRecordsGetReq) (res *v1.SysPaymentRecordsGetRes, err error) {
needAuth, userId, err := service.SysUser().GetUserIdFromToken(ctx)
needAuth, userInfo, err := service.SysUser().GetUserIdFromToken(ctx)
if err != nil {
err = errHandler.WrapError(ctx, gcode.CodeInternalError, err, "获取用户信息失败")
}
if needAuth {
if userId == "" {
if userInfo == nil || userInfo.Id == "" {
err = errHandler.WrapError(ctx, gcode.CodeNotAuthorized, err, "权限不足")
return
}
req.UserId = userId
req.UserId = userInfo.Id
}
total, data, err := service.SysUserPayment().ListRecords(ctx, model.SysUserRecordListInput{
SysPaymentRecordsGetReq: *req,

View File

@@ -4,6 +4,7 @@ import (
"context"
"kami/internal/errHandler"
"kami/internal/model"
"kami/internal/model/entity"
"kami/internal/service"
"github.com/gogf/gf/v2/errors/gcode"
@@ -12,17 +13,20 @@ import (
func (c *ControllerV1) SysPaymentRecordsGetStatistics(ctx context.Context, req *v1.SysPaymentRecordsGetStatisticsReq) (res *v1.SysPaymentRecordsGetStatisticsRes, err error) {
currentUserId := ""
needAuth, userId, err := service.SysUser().GetUserIdFromToken(ctx)
needAuth, userInfo, err := service.SysUser().GetUserIdFromToken(ctx)
if err != nil {
err = errHandler.WrapError(ctx, gcode.CodeInternalError, err, "获取用户信息失败")
}
if needAuth {
if userId == "" {
if userInfo == nil || userInfo.Id == "" {
err = errHandler.WrapError(ctx, gcode.CodeNotAuthorized, err, "权限不足")
return
}
}
currentUserId = userId
if userInfo == nil {
userInfo = &entity.V1SysUser{}
}
currentUserId = userInfo.Id
data, err := service.SysUserPayment().GetDetailById(ctx, &model.SysUserPaymentGetOneInput{
UserId: currentUserId,
})

View File

@@ -2,9 +2,9 @@ package sys_user_login
import (
"context"
"github.com/gogf/gf/v2/errors/gcode"
"kami/internal/errHandler"
"kami/utility/mfa"
v1 "kami/api/sys_user_login/v1"
"kami/internal/model"
@@ -19,14 +19,18 @@ func (c *ControllerV1) UserLogin(ctx context.Context, req *v1.UserLoginReq) (res
err = errHandler.WrapError(ctx, gcode.CodeNotAuthorized, err, "验证码错误")
return
}
// userAgent := utils.GetUserAgent(ctx)
user, err := service.SysUser().GetAdminUserByUsernamePassword(ctx,
userService := service.SysUser()
user, err := userService.GetAdminUserByUsernamePassword(ctx,
&model.UserLoginInput{Username: req.Username, Password: req.Password},
)
if err != nil {
err = errHandler.WrapError(ctx, errHandler.ErrLoginPasswordOrUserError, err, "用户名或密码错误")
return
}
if user.OtpSecret != "" && !mfa.ValidCode(req.TotpCode, user.OtpSecret) {
err = errHandler.WrapError(ctx, gcode.CodeValidationFailed, err, "二步验证错误")
return
}
//if err != nil {
// // 保存登录失败的日志信息
// service.SysLoginLog().Invoke(gctx.New(), &model.LoginLogParams{

View File

@@ -27,6 +27,8 @@ type V1SysUserColumns struct {
IsAdmin string // 是否是管理员
UserSalt string //
UserStatus string // 用户状态
OtpKey string //
OtpSecret string //
CreatedAt string //
UpdatedAt string //
DeletedAt string //
@@ -41,6 +43,8 @@ var v1SysUserColumns = V1SysUserColumns{
IsAdmin: "is_admin",
UserSalt: "user_salt",
UserStatus: "user_status",
OtpKey: "otp_key",
OtpSecret: "otp_secret",
CreatedAt: "created_at",
UpdatedAt: "updated_at",
DeletedAt: "deleted_at",

View File

@@ -16,7 +16,13 @@ var (
HttpClientGetError = gcode.New(1005, "http请求失败", nil)
CornError = gcode.New(2001, "处理错误,请稍后重试", nil)
ErrLoginPasswordOrUserError = gcode.New(3001, "用户名或密码错误", nil)
ErrLoginPasswordOrUserError = gcode.New(3001, "用户名或密码错误", nil)
ErrLoginUserNotExist = gcode.New(3002, "用户不存在", nil)
ErrLoginUserDisabled = gcode.New(3003, "用户被禁用", nil)
ErrLoginUserNotActive = gcode.New(3004, "用户未激活", nil)
ErrLoginUserNotExistOrDisabled = gcode.New(3005, "用户不存在或已被禁用", nil)
ErrLoginUserPasswordError = gcode.New(3006, "密码错误", nil)
ErrLoginUserTotpError = gcode.New(3007, "二步验证错误", nil)
ErrOperationForbidden = gcode.New(4001, "操作被禁止", nil)
ErrTokenError = gcode.New(5001, "token错误", nil)

View File

@@ -7,12 +7,15 @@ import (
"golang.org/x/net/context"
)
func WrapError(ctx context.Context, code gcode.Code, err error, message string) error {
func WrapError(ctx context.Context, code gcode.Code, err error, message ...string) error {
if err != nil {
glog.Errorf(ctx, "%+v", err)
}
if message == "" {
message = code.Message()
newMsg := ""
if len(message) != 0 {
newMsg = message[0]
} else {
newMsg = code.Message()
}
return gerror.NewCode(code, message)
return gerror.NewCode(code, newMsg)
}

View File

@@ -525,7 +525,7 @@ func (s *sSysUser) ChangeUserStatus(ctx context.Context, input *model.UserStatus
return
}
// Delete 删除用户
// DeleteById 删除用户
func (s *sSysUser) DeleteById(ctx context.Context, id string) (err error) {
err = config.GetDatabaseV1().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
_, err = dao.V1SysUser.Ctx(ctx).DB(config.GetDatabaseV1()).TX(tx).Where(dao.V1SysUser.Columns().Id, id).Delete()
@@ -574,7 +574,7 @@ func (s *sSysUser) GetUsersAll(ctx context.Context) (users []*model.SysUserSimpl
return
}
func (s *sSysUser) GetUserIdFromToken(ctx context.Context) (needAuth bool, userId string, err error) {
func (s *sSysUser) GetUserIdFromToken(ctx context.Context) (needAuth bool, user *entity.V1SysUser, err error) {
tokenFrom := g.RequestFromCtx(ctx).Request.Header.Get("tokenFrom")
needAuth = false
if tokenFrom == "" {
@@ -591,16 +591,14 @@ func (s *sSysUser) GetUserIdFromToken(ctx context.Context) (needAuth bool, userI
return
}
userToken, err := token.ParseUserToken(ctx, tokenStr)
userId = ""
if err != nil {
return
}
userService := service.SysUser()
userSchema, err2 := userService.GetUserInfoById(ctx, userToken.UserID)
if err2 != nil || userSchema == nil || userSchema.Id == "" {
user, err2 := userService.GetUserInfoById(ctx, userToken.UserID, true)
if err2 != nil || user == nil {
err = errHandler.WrapError(ctx, errHandler.ErrTokenExpired, err, "token失效或用户不存在")
return
}
userId = userSchema.Id
return
}

View File

@@ -0,0 +1,47 @@
package sysUser
import (
"context"
"github.com/gogf/gf/v2/errors/gerror"
"kami/internal/dao"
"kami/internal/model/do"
"kami/internal/model/entity"
"kami/utility/config"
"kami/utility/mfa"
"kami/utility/utils"
)
// SetTotp 设置totp
func (s *sSysUser) SetTotp(ctx context.Context, data *entity.V1SysUser, key, secret string) (err error) {
if data == nil {
err = gerror.New("用户不存在")
return
}
otp, err := mfa.GetOtp(data.Id, data.Username, key, secret)
if err != nil {
return
}
_, err = dao.V1SysUser.Ctx(ctx).DB(config.GetDatabaseV1()).
Where(dao.V1SysUser.Columns().Id, data.Id).
Update(do.V1SysUser{
OtpKey: otp.Key,
OtpSecret: otp.Secret,
})
err = utils.HandleNoRowsError(err)
return
}
// ResetTotp 重置两步验证
func (s *sSysUser) ResetTotp(ctx context.Context, data *entity.V1SysUser) (err error) {
if data == nil {
err = gerror.New("用户不存在")
return
}
_, err = dao.V1SysUser.Ctx(ctx).DB(config.GetDatabaseV1()).
Where(dao.V1SysUser.Columns().Id, data.Id).
Update(do.V1SysUser{
OtpKey: "",
OtpSecret: "",
})
return
}

View File

@@ -19,6 +19,8 @@ type V1SysUser struct {
IsAdmin interface{} // 是否是管理员
UserSalt interface{} //
UserStatus interface{} // 用户状态
OtpKey interface{} //
OtpSecret interface{} //
CreatedAt *gtime.Time //
UpdatedAt *gtime.Time //
DeletedAt *gtime.Time //

View File

@@ -17,6 +17,8 @@ type V1SysUser struct {
IsAdmin int `json:"isAdmin" orm:"is_admin" description:"是否是管理员"`
UserSalt string `json:"userSalt" orm:"user_salt" description:""`
UserStatus int `json:"userStatus" orm:"user_status" description:"用户状态"`
OtpKey string `json:"otpKey" orm:"otp_key" description:""`
OtpSecret string `json:"otpSecret" orm:"otp_secret" description:""`
CreatedAt *gtime.Time `json:"createdAt" orm:"created_at" description:""`
UpdatedAt *gtime.Time `json:"updatedAt" orm:"updated_at" description:""`
DeletedAt *gtime.Time `json:"deletedAt" orm:"deleted_at" description:""`

View File

@@ -71,6 +71,8 @@ type LoginUserOutput struct {
Username string `json:"username" orm:"username" description:"账号"`
UserSalt string `json:"userSalt"`
UserPassword string `json:"userPassword"`
OtpSecret string `json:"otpSecret"`
OtpKey string `json:"otpKey"`
}
type UserLoginOutOutput struct {

View File

@@ -36,6 +36,7 @@ type (
GetPermissions(ctx context.Context, roleIds []uint) (userButtons []string, err error)
// List 用户列表
List(ctx context.Context, req *model.UserSearchInput) (total int, userList []*entity.V1SysUser, err error)
// ListWithPayment 查询用户数据,带钱包信息
ListWithPayment(ctx context.Context, req *model.UserSearchInput) (total int, userList []*model.UserSearchWithPaymentOutput, err error)
Add(ctx context.Context, req *model.UserAddInput) (err error)
Edit(ctx context.Context, req *model.UserEditInput) (err error)
@@ -49,15 +50,19 @@ type (
// ChangePwd ResetUserPwd 重置用户密码
ChangePwd(ctx context.Context, input *model.UserChangePwdInput) (err error)
ChangeUserStatus(ctx context.Context, input *model.UserStatusInput) (err error)
// Delete 删除用户
// DeleteById 删除用户
DeleteById(ctx context.Context, id string) (err error)
// Delete 删除用户
Delete(ctx context.Context, ids []string) (err error)
// GetUsers 通过用户ids查询多个用户信息
GetUsers(ctx context.Context, ids []int) (users []*model.SysUserSimpleOutput, err error)
// 获取所有用户
// GetUsersAll 获取所有用户
GetUsersAll(ctx context.Context) (users []*model.SysUserSimpleOutput, err error)
GetUserIdFromToken(ctx context.Context) (needAuth bool, userId string, err error)
GetUserIdFromToken(ctx context.Context) (needAuth bool, user *entity.V1SysUser, err error)
// SetTotp 设置totp
SetTotp(ctx context.Context, data *entity.V1SysUser, key, secret string) (err error)
// ResetTotp 重置两步验证
ResetTotp(ctx context.Context, data *entity.V1SysUser) (err error)
}
)

View File

@@ -77,7 +77,7 @@ secret:
token:
cacheKey: "gfToken"
timeOut: 10800
timeout: 10800
maxRefresh: 5400
multiLogin: true
encryptKey: "49c54195e750b04e74a8429b17896586"

View File

@@ -1,22 +1,36 @@
FROM golang:1.22 AS builder
ENV GO111MODULE=on GOPROXY=https://goproxy.cn,direct CGO_ENABLED=0 GOOS=linux GOARCH=amd64
ARG USE_PROXY
WORKDIR /build
COPY ./ /build/
RUN go build main.go
# 根据USE_PROXY参数设置环境变量
RUN if [ "$USE_PROXY" = "1" ]; then \
export GOPROXY=https://goproxy.cn,direct; \
# else \
# export GOPROXY=direct; \
fi \
&& export GO111MODULE=on \
&& export CGO_ENABLED=0 \
&& export GOOS=linux \
&& export GOARCH=amd64 \
&& go mod tidy && go build main.go
FROM alpine:latest
WORKDIR /app
ENV TZ Asia/Shanghai
# 定义参数
ARG USE_PROXY
#设置国内镜像源时区
RUN echo "https://mirrors.aliyun.com/alpine/v3.18/main/" > /etc/apk/repositories && \
echo "https://mirrors.aliyun.com/alpine/v3.18/community/" >> /etc/apk/repositories \
&& apk update && apk upgrade && apk add --no-cache tzdata && \
RUN if [ "$USE_PROXY" = "1" ]; then \
echo "https://mirrors.aliyun.com/alpine/v3.18/main/" > /etc/apk/repositories && \
echo "https://mirrors.aliyun.com/alpine/v3.18/community/" >> /etc/apk/repositories; \
fi && \
apk update && \
apk upgrade && \
apk add --no-cache tzdata && \
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
echo "Asia/Shanghai" > /etc/timezone

View File

@@ -3,6 +3,8 @@ services:
build:
context: ../../.
dockerfile: ./manifest/docker/Dockerfile
args:
- USE_PROXY=${USE_PROXY}
container_name: kami_backend
image: kami_backend:0.11
restart: always

View File

@@ -3,6 +3,9 @@ package mfa
import (
"bytes"
"encoding/base64"
"fmt"
"github.com/gogf/gf/util/grand"
"net/url"
"strconv"
"time"
@@ -15,19 +18,28 @@ const secretLength = 16
type Otp struct {
Secret string `json:"secret"`
QrImage string `json:"qrImage"`
Key string `json:"key"`
}
func GetOtp(userId, username string) (otp Otp, err error) {
secret := gotp.RandomSecret(secretLength)
func GetOtp(userId, username, key, secret string) (otp Otp, err error) {
if secret == "" {
secret = gotp.RandomSecret(secretLength)
key = grand.Letters(6)
}
otp.Secret = secret
totp := gotp.NewDefaultTOTP(secret)
uri := totp.ProvisioningUri(userId, "kami "+username)
uri := totp.ProvisioningUri(userId, fmt.Sprintf("卡销平台(供销端) %s %s", username, key))
uri, err = url.PathUnescape(uri)
if err != nil {
return
}
subImg, err := qrcode.Encode(uri, qrcode.Medium, 256)
dist := make([]byte, 3000)
base64.StdEncoding.Encode(dist, subImg)
index := bytes.IndexByte(dist, 0)
baseImage := dist[0:index]
otp.QrImage = "data:image/png;base64," + string(baseImage)
otp.Key = key
return
}