mirror of
				https://github.com/TeaOSLab/EdgeAdmin.git
				synced 2025-11-04 13:10:26 +08:00 
			
		
		
		
	用户增加OTP认证设置
This commit is contained in:
		@@ -27,14 +27,14 @@ func (this *UpdateAction) RunGet(params struct {
 | 
				
			|||||||
		this.ErrorPage(err)
 | 
							this.ErrorPage(err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	admin := adminResp.Admin
 | 
						var admin = adminResp.Admin
 | 
				
			||||||
	if admin == nil {
 | 
						if admin == nil {
 | 
				
			||||||
		this.NotFound("admin", params.AdminId)
 | 
							this.NotFound("admin", params.AdminId)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// OTP认证
 | 
						// OTP认证
 | 
				
			||||||
	otpLoginIsOn := false
 | 
						var otpLoginIsOn = false
 | 
				
			||||||
	if admin.OtpLogin != nil {
 | 
						if admin.OtpLogin != nil {
 | 
				
			||||||
		otpLoginIsOn = admin.OtpLogin.IsOn
 | 
							otpLoginIsOn = admin.OtpLogin.IsOn
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -45,7 +45,7 @@ func (this *UpdateAction) RunGet(params struct {
 | 
				
			|||||||
		this.ErrorPage(err)
 | 
							this.ErrorPage(err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	countAccessKeys := countAccessKeyResp.Count
 | 
						var countAccessKeys = countAccessKeyResp.Count
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	this.Data["admin"] = maps.Map{
 | 
						this.Data["admin"] = maps.Map{
 | 
				
			||||||
		"id":              admin.Id,
 | 
							"id":              admin.Id,
 | 
				
			||||||
@@ -59,7 +59,7 @@ func (this *UpdateAction) RunGet(params struct {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 权限
 | 
						// 权限
 | 
				
			||||||
	moduleMaps := configloaders.AllModuleMaps()
 | 
						var moduleMaps = configloaders.AllModuleMaps()
 | 
				
			||||||
	for _, m := range moduleMaps {
 | 
						for _, m := range moduleMaps {
 | 
				
			||||||
		code := m.GetString("code")
 | 
							code := m.GetString("code")
 | 
				
			||||||
		isChecked := false
 | 
							isChecked := false
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,6 +5,8 @@ import (
 | 
				
			|||||||
	"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
 | 
						"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
 | 
				
			||||||
	"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
 | 
						"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
 | 
				
			||||||
	"github.com/iwind/TeaGo/actions"
 | 
						"github.com/iwind/TeaGo/actions"
 | 
				
			||||||
 | 
						"github.com/iwind/TeaGo/maps"
 | 
				
			||||||
 | 
						"github.com/xlzd/gotp"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type CreatePopupAction struct {
 | 
					type CreatePopupAction struct {
 | 
				
			||||||
@@ -30,6 +32,9 @@ func (this *CreatePopupAction) RunPost(params struct {
 | 
				
			|||||||
	Remark    string
 | 
						Remark    string
 | 
				
			||||||
	ClusterId int64
 | 
						ClusterId int64
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// OTP
 | 
				
			||||||
 | 
						OtpOn bool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Must *actions.Must
 | 
						Must *actions.Must
 | 
				
			||||||
	CSRF *actionutils.CSRF
 | 
						CSRF *actionutils.CSRF
 | 
				
			||||||
}) {
 | 
					}) {
 | 
				
			||||||
@@ -91,7 +96,28 @@ func (this *CreatePopupAction) RunPost(params struct {
 | 
				
			|||||||
		this.ErrorPage(err)
 | 
							this.ErrorPage(err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	defer this.CreateLogInfo("创建用户 %d", createResp.UserId)
 | 
					
 | 
				
			||||||
 | 
						var userId = createResp.UserId
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						defer this.CreateLogInfo("创建用户 %d", userId)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// OTP
 | 
				
			||||||
 | 
						if params.OtpOn {
 | 
				
			||||||
 | 
							_, err = this.RPC().LoginRPC().UpdateLogin(this.AdminContext(), &pb.UpdateLoginRequest{Login: &pb.Login{
 | 
				
			||||||
 | 
								Id:   0,
 | 
				
			||||||
 | 
								Type: "otp",
 | 
				
			||||||
 | 
								ParamsJSON: maps.Map{
 | 
				
			||||||
 | 
									"secret": gotp.RandomSecret(16), // TODO 改成可以设置secret长度
 | 
				
			||||||
 | 
								}.AsJSON(),
 | 
				
			||||||
 | 
								IsOn:    true,
 | 
				
			||||||
 | 
								AdminId: 0,
 | 
				
			||||||
 | 
								UserId:  userId,
 | 
				
			||||||
 | 
							}})
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								this.ErrorPage(err)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	this.Success()
 | 
						this.Success()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,6 +20,7 @@ func init() {
 | 
				
			|||||||
			Post("/delete", new(DeleteAction)).
 | 
								Post("/delete", new(DeleteAction)).
 | 
				
			||||||
			GetPost("/features", new(FeaturesAction)).
 | 
								GetPost("/features", new(FeaturesAction)).
 | 
				
			||||||
			GetPost("/verifyPopup", new(VerifyPopupAction)).
 | 
								GetPost("/verifyPopup", new(VerifyPopupAction)).
 | 
				
			||||||
 | 
								Get("/otpQrcode", new(OtpQrcodeAction)).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// AccessKeys
 | 
								// AccessKeys
 | 
				
			||||||
			Prefix("/users/accessKeys").
 | 
								Prefix("/users/accessKeys").
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										75
									
								
								internal/web/actions/default/users/otpQrcode.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								internal/web/actions/default/users/otpQrcode.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,75 @@
 | 
				
			|||||||
 | 
					package users
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
 | 
				
			||||||
 | 
						"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
 | 
				
			||||||
 | 
						"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
 | 
				
			||||||
 | 
						"github.com/iwind/TeaGo/maps"
 | 
				
			||||||
 | 
						"github.com/skip2/go-qrcode"
 | 
				
			||||||
 | 
						"github.com/xlzd/gotp"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type OtpQrcodeAction struct {
 | 
				
			||||||
 | 
						actionutils.ParentAction
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (this *OtpQrcodeAction) Init() {
 | 
				
			||||||
 | 
						this.Nav("", "", "")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (this *OtpQrcodeAction) RunGet(params struct {
 | 
				
			||||||
 | 
						UserId int64
 | 
				
			||||||
 | 
					}) {
 | 
				
			||||||
 | 
						loginResp, err := this.RPC().LoginRPC().FindEnabledLogin(this.AdminContext(), &pb.FindEnabledLoginRequest{
 | 
				
			||||||
 | 
							UserId: params.UserId,
 | 
				
			||||||
 | 
							Type:   "otp",
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							this.ErrorPage(err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						login := loginResp.Login
 | 
				
			||||||
 | 
						if login == nil || !login.IsOn {
 | 
				
			||||||
 | 
							this.NotFound("userLogin", params.UserId)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						loginParams := maps.Map{}
 | 
				
			||||||
 | 
						err = json.Unmarshal(login.ParamsJSON, &loginParams)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							this.ErrorPage(err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						secret := loginParams.GetString("secret")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 当前用户信息
 | 
				
			||||||
 | 
						userResp, err := this.RPC().UserRPC().FindEnabledUser(this.AdminContext(), &pb.FindEnabledUserRequest{UserId: params.UserId})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							this.ErrorPage(err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						var user = userResp.User
 | 
				
			||||||
 | 
						if user == nil {
 | 
				
			||||||
 | 
							this.NotFound("user", params.UserId)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						uiConfig, err := configloaders.LoadAdminUIConfig()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							this.ErrorPage(err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						var productName = uiConfig.ProductName
 | 
				
			||||||
 | 
						if len(productName) == 0 {
 | 
				
			||||||
 | 
							productName = "GoEdge用户"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						var url = gotp.NewDefaultTOTP(secret).ProvisioningUri(user.Username, productName)
 | 
				
			||||||
 | 
						data, err := qrcode.Encode(url, qrcode.Medium, 256)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							this.ErrorPage(err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						this.AddHeader("Content-Type", "image/png")
 | 
				
			||||||
 | 
						this.Write(data)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -6,6 +6,7 @@ import (
 | 
				
			|||||||
	"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
 | 
						"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
 | 
				
			||||||
	"github.com/iwind/TeaGo/actions"
 | 
						"github.com/iwind/TeaGo/actions"
 | 
				
			||||||
	"github.com/iwind/TeaGo/maps"
 | 
						"github.com/iwind/TeaGo/maps"
 | 
				
			||||||
 | 
						"github.com/xlzd/gotp"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type UpdateAction struct {
 | 
					type UpdateAction struct {
 | 
				
			||||||
@@ -30,7 +31,7 @@ func (this *UpdateAction) RunGet(params struct {
 | 
				
			|||||||
		this.ErrorPage(err)
 | 
							this.ErrorPage(err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	user := userResp.User
 | 
						var user = userResp.User
 | 
				
			||||||
	if user == nil {
 | 
						if user == nil {
 | 
				
			||||||
		this.NotFound("user", params.UserId)
 | 
							this.NotFound("user", params.UserId)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
@@ -42,7 +43,7 @@ func (this *UpdateAction) RunGet(params struct {
 | 
				
			|||||||
		this.ErrorPage(err)
 | 
							this.ErrorPage(err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	countAccessKeys := countAccessKeyResp.Count
 | 
						var countAccessKeys = countAccessKeyResp.Count
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 是否有实名认证
 | 
						// 是否有实名认证
 | 
				
			||||||
	hasNewIndividualIdentity, hasNewEnterpriseIdentity, identityTag, err := userutils.CheckUserIdentity(this.RPC(), this.AdminContext(), params.UserId)
 | 
						hasNewIndividualIdentity, hasNewEnterpriseIdentity, identityTag, err := userutils.CheckUserIdentity(this.RPC(), this.AdminContext(), params.UserId)
 | 
				
			||||||
@@ -51,6 +52,12 @@ func (this *UpdateAction) RunGet(params struct {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// OTP认证
 | 
				
			||||||
 | 
						var otpLoginIsOn = false
 | 
				
			||||||
 | 
						if user.OtpLogin != nil {
 | 
				
			||||||
 | 
							otpLoginIsOn = user.OtpLogin.IsOn
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	this.Data["user"] = maps.Map{
 | 
						this.Data["user"] = maps.Map{
 | 
				
			||||||
		"id":              user.Id,
 | 
							"id":              user.Id,
 | 
				
			||||||
		"username":        user.Username,
 | 
							"username":        user.Username,
 | 
				
			||||||
@@ -66,6 +73,9 @@ func (this *UpdateAction) RunGet(params struct {
 | 
				
			|||||||
		"hasNewIndividualIdentity": hasNewIndividualIdentity,
 | 
							"hasNewIndividualIdentity": hasNewIndividualIdentity,
 | 
				
			||||||
		"hasNewEnterpriseIdentity": hasNewEnterpriseIdentity,
 | 
							"hasNewEnterpriseIdentity": hasNewEnterpriseIdentity,
 | 
				
			||||||
		"identityTag":              identityTag,
 | 
							"identityTag":              identityTag,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// otp
 | 
				
			||||||
 | 
							"otpLoginIsOn": otpLoginIsOn,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	this.Data["clusterId"] = 0
 | 
						this.Data["clusterId"] = 0
 | 
				
			||||||
@@ -89,6 +99,9 @@ func (this *UpdateAction) RunPost(params struct {
 | 
				
			|||||||
	IsOn      bool
 | 
						IsOn      bool
 | 
				
			||||||
	ClusterId int64
 | 
						ClusterId int64
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// OTP
 | 
				
			||||||
 | 
						OtpOn bool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Must *actions.Must
 | 
						Must *actions.Must
 | 
				
			||||||
	CSRF *actionutils.CSRF
 | 
						CSRF *actionutils.CSRF
 | 
				
			||||||
}) {
 | 
					}) {
 | 
				
			||||||
@@ -152,5 +165,50 @@ func (this *UpdateAction) RunPost(params struct {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 修改OTP
 | 
				
			||||||
 | 
						otpLoginResp, err := this.RPC().LoginRPC().FindEnabledLogin(this.AdminContext(), &pb.FindEnabledLoginRequest{
 | 
				
			||||||
 | 
							UserId: params.UserId,
 | 
				
			||||||
 | 
							Type:   "otp",
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							this.ErrorPage(err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							var otpLogin = otpLoginResp.Login
 | 
				
			||||||
 | 
							if params.OtpOn {
 | 
				
			||||||
 | 
								if otpLogin == nil {
 | 
				
			||||||
 | 
									otpLogin = &pb.Login{
 | 
				
			||||||
 | 
										Id:   0,
 | 
				
			||||||
 | 
										Type: "otp",
 | 
				
			||||||
 | 
										ParamsJSON: maps.Map{
 | 
				
			||||||
 | 
											"secret": gotp.RandomSecret(16), // TODO 改成可以设置secret长度
 | 
				
			||||||
 | 
										}.AsJSON(),
 | 
				
			||||||
 | 
										IsOn:   true,
 | 
				
			||||||
 | 
										UserId: params.UserId,
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									// 如果已经有了,就覆盖,这样可以保留既有的参数
 | 
				
			||||||
 | 
									otpLogin.IsOn = true
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								_, err = this.RPC().LoginRPC().UpdateLogin(this.AdminContext(), &pb.UpdateLoginRequest{Login: otpLogin})
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									this.ErrorPage(err)
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								_, err = this.RPC().LoginRPC().UpdateLogin(this.AdminContext(), &pb.UpdateLoginRequest{Login: &pb.Login{
 | 
				
			||||||
 | 
									Type:   "otp",
 | 
				
			||||||
 | 
									IsOn:   false,
 | 
				
			||||||
 | 
									UserId: params.UserId,
 | 
				
			||||||
 | 
								}})
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									this.ErrorPage(err)
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	this.Success()
 | 
						this.Success()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
package users
 | 
					package users
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
	"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
 | 
						"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
 | 
				
			||||||
	"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/users/userutils"
 | 
						"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/users/userutils"
 | 
				
			||||||
	"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
 | 
						"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
 | 
				
			||||||
@@ -76,6 +77,21 @@ func (this *UserAction) RunGet(params struct {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// OTP
 | 
				
			||||||
 | 
						this.Data["otp"] = nil
 | 
				
			||||||
 | 
						if user.OtpLogin != nil && user.OtpLogin.IsOn {
 | 
				
			||||||
 | 
							loginParams := maps.Map{}
 | 
				
			||||||
 | 
							err = json.Unmarshal(user.OtpLogin.ParamsJSON, &loginParams)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								this.ErrorPage(err)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							this.Data["otp"] = maps.Map{
 | 
				
			||||||
 | 
								"isOn":   true,
 | 
				
			||||||
 | 
								"params": loginParams,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	this.Data["user"] = maps.Map{
 | 
						this.Data["user"] = maps.Map{
 | 
				
			||||||
		"id":               user.Id,
 | 
							"id":               user.Id,
 | 
				
			||||||
		"username":         user.Username,
 | 
							"username":         user.Username,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -60,6 +60,13 @@
 | 
				
			|||||||
                    <input type="text" name="email" maxlength="100"/>
 | 
					                    <input type="text" name="email" maxlength="100"/>
 | 
				
			||||||
                </td>
 | 
					                </td>
 | 
				
			||||||
            </tr>
 | 
					            </tr>
 | 
				
			||||||
 | 
					            <tr>
 | 
				
			||||||
 | 
					                <td>OTP认证</td>
 | 
				
			||||||
 | 
					                <td>
 | 
				
			||||||
 | 
					                    <checkbox name="otpOn">启用OTP</checkbox>
 | 
				
			||||||
 | 
					                    <p class="comment">启用OTP认证后,在用户登录的时候需要同时填写OTP动态密码。</p>
 | 
				
			||||||
 | 
					                </td>
 | 
				
			||||||
 | 
					            </tr>
 | 
				
			||||||
            <tr>
 | 
					            <tr>
 | 
				
			||||||
                <td>备注</td>
 | 
					                <td>备注</td>
 | 
				
			||||||
                <td>
 | 
					                <td>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -67,6 +67,13 @@
 | 
				
			|||||||
                    <input type="text" name="email" maxlength="100" v-model="user.email"/>
 | 
					                    <input type="text" name="email" maxlength="100" v-model="user.email"/>
 | 
				
			||||||
                </td>
 | 
					                </td>
 | 
				
			||||||
            </tr>
 | 
					            </tr>
 | 
				
			||||||
 | 
					            <tr>
 | 
				
			||||||
 | 
					                <td>OTP认证</td>
 | 
				
			||||||
 | 
					                <td>
 | 
				
			||||||
 | 
					                    <checkbox name="otpOn" v-model="user.otpLoginIsOn">启用OTP</checkbox>
 | 
				
			||||||
 | 
					                    <p class="comment">启用OTP认证后,在用户登录的时候需要同时填写OTP动态密码。</p>
 | 
				
			||||||
 | 
					                </td>
 | 
				
			||||||
 | 
					            </tr>
 | 
				
			||||||
            <tr>
 | 
					            <tr>
 | 
				
			||||||
                <td>备注</td>
 | 
					                <td>备注</td>
 | 
				
			||||||
                <td>
 | 
					                <td>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -72,4 +72,29 @@
 | 
				
			|||||||
            <span v-else>{{user.registeredIP}}<span class="grey small">({{user.registeredRegion}})</span></span>
 | 
					            <span v-else>{{user.registeredIP}}<span class="grey small">({{user.registeredRegion}})</span></span>
 | 
				
			||||||
        </td>
 | 
					        </td>
 | 
				
			||||||
    </tr>
 | 
					    </tr>
 | 
				
			||||||
 | 
					</table>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<h3>OTP认证</h3>
 | 
				
			||||||
 | 
					<table class="ui table definition selectable">
 | 
				
			||||||
 | 
					    <tr>
 | 
				
			||||||
 | 
					        <td class="title">状态</td>
 | 
				
			||||||
 | 
					        <td>
 | 
				
			||||||
 | 
					            <span v-if="otp != null && otp.isOn" class="green">已启用</span>
 | 
				
			||||||
 | 
					            <span v-else class="disabled">未启用</span>
 | 
				
			||||||
 | 
					        </td>
 | 
				
			||||||
 | 
					    </tr>
 | 
				
			||||||
 | 
					    <tr v-if="otp != null && otp.isOn">
 | 
				
			||||||
 | 
					        <td colspan="2"><more-options-indicator>更多信息</more-options-indicator></td>
 | 
				
			||||||
 | 
					    </tr>
 | 
				
			||||||
 | 
					    <tr v-if="otp != null && otp.isOn && moreOptionsVisible">
 | 
				
			||||||
 | 
					        <td>认证二维码</td>
 | 
				
			||||||
 | 
					        <td>
 | 
				
			||||||
 | 
					            <img :src="'/users/otpQrcode?userId=' + user.id"/>
 | 
				
			||||||
 | 
					            <p class="comment">可以通过二维码快速添加OTP认证信息到App中。</p>
 | 
				
			||||||
 | 
					        </td>
 | 
				
			||||||
 | 
					    </tr>
 | 
				
			||||||
 | 
					    <tr v-if="otp != null && otp.isOn && moreOptionsVisible">
 | 
				
			||||||
 | 
					        <td>密钥</td>
 | 
				
			||||||
 | 
					        <td>{{otp.params.secret}}</td>
 | 
				
			||||||
 | 
					    </tr>
 | 
				
			||||||
</table>
 | 
					</table>
 | 
				
			||||||
		Reference in New Issue
	
	Block a user