mirror of
				https://github.com/TeaOSLab/EdgeAdmin.git
				synced 2025-11-04 05:00:25 +08:00 
			
		
		
		
	[系统用户]增加OTP动态密码二次认证
This commit is contained in:
		
							
								
								
									
										74
									
								
								internal/web/actions/default/admins/admin.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								internal/web/actions/default/admins/admin.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,74 @@
 | 
			
		||||
package admins
 | 
			
		||||
 | 
			
		||||
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"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type AdminAction struct {
 | 
			
		||||
	actionutils.ParentAction
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *AdminAction) Init() {
 | 
			
		||||
	this.Nav("", "", "index")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *AdminAction) RunGet(params struct {
 | 
			
		||||
	AdminId int64
 | 
			
		||||
}) {
 | 
			
		||||
	adminResp, err := this.RPC().AdminRPC().FindEnabledAdmin(this.AdminContext(), &pb.FindEnabledAdminRequest{AdminId: params.AdminId})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		this.ErrorPage(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	admin := adminResp.Admin
 | 
			
		||||
	if admin == nil {
 | 
			
		||||
		this.NotFound("admin", params.AdminId)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this.Data["admin"] = maps.Map{
 | 
			
		||||
		"id":       admin.Id,
 | 
			
		||||
		"fullname": admin.Fullname,
 | 
			
		||||
		"username": admin.Username,
 | 
			
		||||
		"isOn":     admin.IsOn,
 | 
			
		||||
		"isSuper":  admin.IsSuper,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 权限
 | 
			
		||||
	moduleMaps := []maps.Map{}
 | 
			
		||||
	for _, m := range configloaders.AllModuleMaps() {
 | 
			
		||||
		code := m.GetString("code")
 | 
			
		||||
		isChecked := false
 | 
			
		||||
		for _, module := range admin.Modules {
 | 
			
		||||
			if module.Code == code {
 | 
			
		||||
				isChecked = true
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if isChecked {
 | 
			
		||||
			moduleMaps = append(moduleMaps, m)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	this.Data["modules"] = moduleMaps
 | 
			
		||||
 | 
			
		||||
	// OTP
 | 
			
		||||
	this.Data["otp"] = nil
 | 
			
		||||
	if admin.OtpLogin != nil && admin.OtpLogin.IsOn {
 | 
			
		||||
		loginParams := maps.Map{}
 | 
			
		||||
		err = json.Unmarshal(admin.OtpLogin.ParamsJSON, &loginParams)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			this.ErrorPage(err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		this.Data["otp"] = maps.Map{
 | 
			
		||||
			"isOn":   true,
 | 
			
		||||
			"params": loginParams,
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this.Show()
 | 
			
		||||
}
 | 
			
		||||
@@ -7,6 +7,8 @@ import (
 | 
			
		||||
	"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
 | 
			
		||||
	"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 {
 | 
			
		||||
	ModuleCodes []string
 | 
			
		||||
	IsSuper     bool
 | 
			
		||||
 | 
			
		||||
	// OTP
 | 
			
		||||
	OtpOn bool
 | 
			
		||||
 | 
			
		||||
	Must *actions.Must
 | 
			
		||||
	CSRF *actionutils.CSRF
 | 
			
		||||
}) {
 | 
			
		||||
@@ -89,6 +94,24 @@ func (this *CreatePopupAction) RunPost(params struct {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 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: createResp.AdminId,
 | 
			
		||||
			UserId:  0,
 | 
			
		||||
		}})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			this.ErrorPage(err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	defer this.CreateLogInfo("创建系统用户 %d", createResp.AdminId)
 | 
			
		||||
 | 
			
		||||
	// 通知更改
 | 
			
		||||
 
 | 
			
		||||
@@ -35,12 +35,13 @@ func (this *IndexAction) RunGet(params struct{}) {
 | 
			
		||||
	adminMaps := []maps.Map{}
 | 
			
		||||
	for _, admin := range adminsResp.Admins {
 | 
			
		||||
		adminMaps = append(adminMaps, maps.Map{
 | 
			
		||||
			"id":          admin.Id,
 | 
			
		||||
			"isOn":        admin.IsOn,
 | 
			
		||||
			"isSuper":     admin.IsSuper,
 | 
			
		||||
			"username":    admin.Username,
 | 
			
		||||
			"fullname":    admin.Fullname,
 | 
			
		||||
			"createdTime": timeutil.FormatTime("Y-m-d H:i:s", admin.CreatedAt),
 | 
			
		||||
			"id":           admin.Id,
 | 
			
		||||
			"isOn":         admin.IsOn,
 | 
			
		||||
			"isSuper":      admin.IsSuper,
 | 
			
		||||
			"username":     admin.Username,
 | 
			
		||||
			"fullname":     admin.Fullname,
 | 
			
		||||
			"createdTime":  timeutil.FormatTime("Y-m-d H:i:s", admin.CreatedAt),
 | 
			
		||||
			"otpLoginIsOn": admin.OtpLogin != nil && admin.OtpLogin.IsOn,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	this.Data["admins"] = adminMaps
 | 
			
		||||
 
 | 
			
		||||
@@ -14,8 +14,10 @@ func init() {
 | 
			
		||||
			Prefix("/admins").
 | 
			
		||||
			Get("", new(IndexAction)).
 | 
			
		||||
			GetPost("/createPopup", new(CreatePopupAction)).
 | 
			
		||||
			GetPost("/updatePopup", new(UpdatePopupAction)).
 | 
			
		||||
			GetPost("/update", new(UpdateAction)).
 | 
			
		||||
			Post("/delete", new(DeleteAction)).
 | 
			
		||||
			Get("/admin", new(AdminAction)).
 | 
			
		||||
			Get("/otpQrcode", new(OtpQrcodeAction)).
 | 
			
		||||
			EndAll()
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										71
									
								
								internal/web/actions/default/admins/otpQrcode.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								internal/web/actions/default/admins/otpQrcode.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,71 @@
 | 
			
		||||
package admins
 | 
			
		||||
 | 
			
		||||
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 {
 | 
			
		||||
	AdminId int64
 | 
			
		||||
}) {
 | 
			
		||||
	loginResp, err := this.RPC().LoginRPC().FindEnabledLogin(this.AdminContext(), &pb.FindEnabledLoginRequest{
 | 
			
		||||
		AdminId: params.AdminId,
 | 
			
		||||
		Type:    "otp",
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		this.ErrorPage(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	login := loginResp.Login
 | 
			
		||||
	if login == nil || !login.IsOn {
 | 
			
		||||
		this.NotFound("adminLogin", params.AdminId)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	loginParams := maps.Map{}
 | 
			
		||||
	err = json.Unmarshal(login.ParamsJSON, &loginParams)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		this.ErrorPage(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	secret := loginParams.GetString("secret")
 | 
			
		||||
 | 
			
		||||
	// 当前用户信息
 | 
			
		||||
	adminResp, err := this.RPC().AdminRPC().FindEnabledAdmin(this.AdminContext(), &pb.FindEnabledAdminRequest{AdminId: params.AdminId})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		this.ErrorPage(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	admin := adminResp.Admin
 | 
			
		||||
	if admin == nil {
 | 
			
		||||
		this.NotFound("admin", params.AdminId)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	uiConfig, err := configloaders.LoadAdminUIConfig()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		this.ErrorPage(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	url := gotp.NewDefaultTOTP(secret).ProvisioningUri(admin.Username, uiConfig.AdminSystemName)
 | 
			
		||||
	data, err := qrcode.Encode(url, qrcode.Medium, 256)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		this.ErrorPage(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	this.AddHeader("Content-Type", "image/png")
 | 
			
		||||
	this.Write(data)
 | 
			
		||||
}
 | 
			
		||||
@@ -8,35 +8,47 @@ import (
 | 
			
		||||
	"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
 | 
			
		||||
	"github.com/iwind/TeaGo/actions"
 | 
			
		||||
	"github.com/iwind/TeaGo/maps"
 | 
			
		||||
	"github.com/xlzd/gotp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type UpdatePopupAction struct {
 | 
			
		||||
type UpdateAction struct {
 | 
			
		||||
	actionutils.ParentAction
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *UpdatePopupAction) Init() {
 | 
			
		||||
	this.Nav("", "", "")
 | 
			
		||||
func (this *UpdateAction) Init() {
 | 
			
		||||
	this.Nav("", "", "update")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *UpdatePopupAction) RunGet(params struct {
 | 
			
		||||
func (this *UpdateAction) RunGet(params struct {
 | 
			
		||||
	AdminId int64
 | 
			
		||||
}) {
 | 
			
		||||
 | 
			
		||||
	adminResp, err := this.RPC().AdminRPC().FindEnabledAdmin(this.AdminContext(), &pb.FindEnabledAdminRequest{AdminId: params.AdminId})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		this.ErrorPage(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	admin := adminResp.Admin
 | 
			
		||||
 | 
			
		||||
	this.Data["admin"] = maps.Map{
 | 
			
		||||
		"id":       admin.Id,
 | 
			
		||||
		"fullname": admin.Fullname,
 | 
			
		||||
		"username": admin.Username,
 | 
			
		||||
		"isOn":     admin.IsOn,
 | 
			
		||||
		"isSuper":  admin.IsSuper,
 | 
			
		||||
	if admin == nil {
 | 
			
		||||
		this.NotFound("admin", params.AdminId)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// OTP认证
 | 
			
		||||
	otpLoginIsOn := false
 | 
			
		||||
	if admin.OtpLogin != nil {
 | 
			
		||||
		otpLoginIsOn = admin.OtpLogin.IsOn
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this.Data["admin"] = maps.Map{
 | 
			
		||||
		"id":           admin.Id,
 | 
			
		||||
		"fullname":     admin.Fullname,
 | 
			
		||||
		"username":     admin.Username,
 | 
			
		||||
		"isOn":         admin.IsOn,
 | 
			
		||||
		"isSuper":      admin.IsSuper,
 | 
			
		||||
		"otpLoginIsOn": otpLoginIsOn,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 权限
 | 
			
		||||
	moduleMaps := configloaders.AllModuleMaps()
 | 
			
		||||
	for _, m := range moduleMaps {
 | 
			
		||||
		code := m.GetString("code")
 | 
			
		||||
@@ -54,7 +66,7 @@ func (this *UpdatePopupAction) RunGet(params struct {
 | 
			
		||||
	this.Show()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *UpdatePopupAction) RunPost(params struct {
 | 
			
		||||
func (this *UpdateAction) RunPost(params struct {
 | 
			
		||||
	AdminId int64
 | 
			
		||||
 | 
			
		||||
	Fullname    string
 | 
			
		||||
@@ -65,6 +77,9 @@ func (this *UpdatePopupAction) RunPost(params struct {
 | 
			
		||||
	IsOn        bool
 | 
			
		||||
	IsSuper     bool
 | 
			
		||||
 | 
			
		||||
	// OTP
 | 
			
		||||
	OtpOn bool
 | 
			
		||||
 | 
			
		||||
	Must *actions.Must
 | 
			
		||||
	CSRF *actionutils.CSRF
 | 
			
		||||
}) {
 | 
			
		||||
@@ -130,12 +145,58 @@ func (this *UpdatePopupAction) RunPost(params struct {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 通知更改
 | 
			
		||||
	err = configloaders.NotifyAdminModuleMappingChange()
 | 
			
		||||
	// 修改OTP
 | 
			
		||||
	otpLoginResp, err := this.RPC().LoginRPC().FindEnabledLogin(this.AdminContext(), &pb.FindEnabledLoginRequest{
 | 
			
		||||
		AdminId: params.AdminId,
 | 
			
		||||
		Type:    "otp",
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		this.ErrorPage(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	{
 | 
			
		||||
		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,
 | 
			
		||||
					AdminId: params.AdminId,
 | 
			
		||||
					UserId:  0,
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				// 如果已经有了,就覆盖,这样可以保留既有的参数
 | 
			
		||||
				otpLogin.IsOn = true
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
	this.Success()
 | 
			
		||||
			_, 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,
 | 
			
		||||
				AdminId: params.AdminId,
 | 
			
		||||
			}})
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				this.ErrorPage(err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// 通知更改
 | 
			
		||||
		err = configloaders.NotifyAdminModuleMappingChange()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			this.ErrorPage(err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		this.Success()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user