feat: 添加二步验证,添加二步验证相关
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
48
api/sysUser/v1/totp.go
Normal 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"`
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
package v1
|
||||
@@ -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 {
|
||||
|
||||
@@ -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]{
|
||||
|
||||
@@ -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++
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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]{
|
||||
|
||||
@@ -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, "")
|
||||
|
||||
@@ -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, "获取订单汇总列表失败")
|
||||
|
||||
35
internal/controller/sysUser/sysUser_v1_totp_image_get.go
Normal file
35
internal/controller/sysUser/sysUser_v1_totp_image_get.go
Normal 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
|
||||
}
|
||||
28
internal/controller/sysUser/sysUser_v1_totp_reset.go
Normal file
28
internal/controller/sysUser/sysUser_v1_totp_reset.go
Normal 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
|
||||
}
|
||||
34
internal/controller/sysUser/sysUser_v1_totp_set.go
Normal file
34
internal/controller/sysUser/sysUser_v1_totp_set.go
Normal 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
|
||||
}
|
||||
38
internal/controller/sysUser/sysUser_v1_totp_status_get.go
Normal file
38
internal/controller/sysUser/sysUser_v1_totp_status_get.go
Normal 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
|
||||
}
|
||||
@@ -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, "修改密码失败")
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
47
internal/logic/sys_user/totp.go
Normal file
47
internal/logic/sys_user/totp.go
Normal 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
|
||||
}
|
||||
@@ -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 //
|
||||
|
||||
@@ -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:""`
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ secret:
|
||||
|
||||
token:
|
||||
cacheKey: "gfToken"
|
||||
timeOut: 10800
|
||||
timeout: 10800
|
||||
maxRefresh: 5400
|
||||
multiLogin: true
|
||||
encryptKey: "49c54195e750b04e74a8429b17896586"
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user