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)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	admin := adminResp.Admin
 | 
			
		||||
	var admin = adminResp.Admin
 | 
			
		||||
	if admin == nil {
 | 
			
		||||
		this.NotFound("admin", params.AdminId)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// OTP认证
 | 
			
		||||
	otpLoginIsOn := false
 | 
			
		||||
	var otpLoginIsOn = false
 | 
			
		||||
	if admin.OtpLogin != nil {
 | 
			
		||||
		otpLoginIsOn = admin.OtpLogin.IsOn
 | 
			
		||||
	}
 | 
			
		||||
@@ -45,7 +45,7 @@ func (this *UpdateAction) RunGet(params struct {
 | 
			
		||||
		this.ErrorPage(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	countAccessKeys := countAccessKeyResp.Count
 | 
			
		||||
	var countAccessKeys = countAccessKeyResp.Count
 | 
			
		||||
 | 
			
		||||
	this.Data["admin"] = maps.Map{
 | 
			
		||||
		"id":              admin.Id,
 | 
			
		||||
@@ -59,7 +59,7 @@ func (this *UpdateAction) RunGet(params struct {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 权限
 | 
			
		||||
	moduleMaps := configloaders.AllModuleMaps()
 | 
			
		||||
	var moduleMaps = configloaders.AllModuleMaps()
 | 
			
		||||
	for _, m := range moduleMaps {
 | 
			
		||||
		code := m.GetString("code")
 | 
			
		||||
		isChecked := false
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,8 @@ import (
 | 
			
		||||
	"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
 | 
			
		||||
	"github.com/iwind/TeaGo/actions"
 | 
			
		||||
	"github.com/iwind/TeaGo/maps"
 | 
			
		||||
	"github.com/xlzd/gotp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type CreatePopupAction struct {
 | 
			
		||||
@@ -30,6 +32,9 @@ func (this *CreatePopupAction) RunPost(params struct {
 | 
			
		||||
	Remark    string
 | 
			
		||||
	ClusterId int64
 | 
			
		||||
 | 
			
		||||
	// OTP
 | 
			
		||||
	OtpOn bool
 | 
			
		||||
 | 
			
		||||
	Must *actions.Must
 | 
			
		||||
	CSRF *actionutils.CSRF
 | 
			
		||||
}) {
 | 
			
		||||
@@ -91,7 +96,28 @@ func (this *CreatePopupAction) RunPost(params struct {
 | 
			
		||||
		this.ErrorPage(err)
 | 
			
		||||
		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()
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,7 @@ func init() {
 | 
			
		||||
			Post("/delete", new(DeleteAction)).
 | 
			
		||||
			GetPost("/features", new(FeaturesAction)).
 | 
			
		||||
			GetPost("/verifyPopup", new(VerifyPopupAction)).
 | 
			
		||||
			Get("/otpQrcode", new(OtpQrcodeAction)).
 | 
			
		||||
 | 
			
		||||
			// 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/iwind/TeaGo/actions"
 | 
			
		||||
	"github.com/iwind/TeaGo/maps"
 | 
			
		||||
	"github.com/xlzd/gotp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type UpdateAction struct {
 | 
			
		||||
@@ -30,7 +31,7 @@ func (this *UpdateAction) RunGet(params struct {
 | 
			
		||||
		this.ErrorPage(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	user := userResp.User
 | 
			
		||||
	var user = userResp.User
 | 
			
		||||
	if user == nil {
 | 
			
		||||
		this.NotFound("user", params.UserId)
 | 
			
		||||
		return
 | 
			
		||||
@@ -42,7 +43,7 @@ func (this *UpdateAction) RunGet(params struct {
 | 
			
		||||
		this.ErrorPage(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	countAccessKeys := countAccessKeyResp.Count
 | 
			
		||||
	var countAccessKeys = countAccessKeyResp.Count
 | 
			
		||||
 | 
			
		||||
	// 是否有实名认证
 | 
			
		||||
	hasNewIndividualIdentity, hasNewEnterpriseIdentity, identityTag, err := userutils.CheckUserIdentity(this.RPC(), this.AdminContext(), params.UserId)
 | 
			
		||||
@@ -51,6 +52,12 @@ func (this *UpdateAction) RunGet(params struct {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// OTP认证
 | 
			
		||||
	var otpLoginIsOn = false
 | 
			
		||||
	if user.OtpLogin != nil {
 | 
			
		||||
		otpLoginIsOn = user.OtpLogin.IsOn
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this.Data["user"] = maps.Map{
 | 
			
		||||
		"id":              user.Id,
 | 
			
		||||
		"username":        user.Username,
 | 
			
		||||
@@ -66,6 +73,9 @@ func (this *UpdateAction) RunGet(params struct {
 | 
			
		||||
		"hasNewIndividualIdentity": hasNewIndividualIdentity,
 | 
			
		||||
		"hasNewEnterpriseIdentity": hasNewEnterpriseIdentity,
 | 
			
		||||
		"identityTag":              identityTag,
 | 
			
		||||
 | 
			
		||||
		// otp
 | 
			
		||||
		"otpLoginIsOn": otpLoginIsOn,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this.Data["clusterId"] = 0
 | 
			
		||||
@@ -89,6 +99,9 @@ func (this *UpdateAction) RunPost(params struct {
 | 
			
		||||
	IsOn      bool
 | 
			
		||||
	ClusterId int64
 | 
			
		||||
 | 
			
		||||
	// OTP
 | 
			
		||||
	OtpOn bool
 | 
			
		||||
 | 
			
		||||
	Must *actions.Must
 | 
			
		||||
	CSRF *actionutils.CSRF
 | 
			
		||||
}) {
 | 
			
		||||
@@ -152,5 +165,50 @@ func (this *UpdateAction) RunPost(params struct {
 | 
			
		||||
		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()
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
package users
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/users/userutils"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
 | 
			
		||||
@@ -76,6 +77,21 @@ func (this *UserAction) RunGet(params struct {
 | 
			
		||||
		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{
 | 
			
		||||
		"id":               user.Id,
 | 
			
		||||
		"username":         user.Username,
 | 
			
		||||
 
 | 
			
		||||
@@ -60,6 +60,13 @@
 | 
			
		||||
                    <input type="text" name="email" maxlength="100"/>
 | 
			
		||||
                </td>
 | 
			
		||||
            </tr>
 | 
			
		||||
            <tr>
 | 
			
		||||
                <td>OTP认证</td>
 | 
			
		||||
                <td>
 | 
			
		||||
                    <checkbox name="otpOn">启用OTP</checkbox>
 | 
			
		||||
                    <p class="comment">启用OTP认证后,在用户登录的时候需要同时填写OTP动态密码。</p>
 | 
			
		||||
                </td>
 | 
			
		||||
            </tr>
 | 
			
		||||
            <tr>
 | 
			
		||||
                <td>备注</td>
 | 
			
		||||
                <td>
 | 
			
		||||
 
 | 
			
		||||
@@ -67,6 +67,13 @@
 | 
			
		||||
                    <input type="text" name="email" maxlength="100" v-model="user.email"/>
 | 
			
		||||
                </td>
 | 
			
		||||
            </tr>
 | 
			
		||||
            <tr>
 | 
			
		||||
                <td>OTP认证</td>
 | 
			
		||||
                <td>
 | 
			
		||||
                    <checkbox name="otpOn" v-model="user.otpLoginIsOn">启用OTP</checkbox>
 | 
			
		||||
                    <p class="comment">启用OTP认证后,在用户登录的时候需要同时填写OTP动态密码。</p>
 | 
			
		||||
                </td>
 | 
			
		||||
            </tr>
 | 
			
		||||
            <tr>
 | 
			
		||||
                <td>备注</td>
 | 
			
		||||
                <td>
 | 
			
		||||
 
 | 
			
		||||
@@ -73,3 +73,28 @@
 | 
			
		||||
        </td>
 | 
			
		||||
    </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>
 | 
			
		||||
		Reference in New Issue
	
	Block a user