diff --git a/internal/web/actions/default/admins/update.go b/internal/web/actions/default/admins/update.go index 259132fd..9bb2ba0e 100644 --- a/internal/web/actions/default/admins/update.go +++ b/internal/web/actions/default/admins/update.go @@ -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 diff --git a/internal/web/actions/default/users/createPopup.go b/internal/web/actions/default/users/createPopup.go index 0bf7a750..2f742d06 100644 --- a/internal/web/actions/default/users/createPopup.go +++ b/internal/web/actions/default/users/createPopup.go @@ -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() } diff --git a/internal/web/actions/default/users/init.go b/internal/web/actions/default/users/init.go index 73f94f4f..d0164b93 100644 --- a/internal/web/actions/default/users/init.go +++ b/internal/web/actions/default/users/init.go @@ -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"). diff --git a/internal/web/actions/default/users/otpQrcode.go b/internal/web/actions/default/users/otpQrcode.go new file mode 100644 index 00000000..19a0d5a6 --- /dev/null +++ b/internal/web/actions/default/users/otpQrcode.go @@ -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) +} diff --git a/internal/web/actions/default/users/update.go b/internal/web/actions/default/users/update.go index 4bf617e7..963bb284 100644 --- a/internal/web/actions/default/users/update.go +++ b/internal/web/actions/default/users/update.go @@ -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() } diff --git a/internal/web/actions/default/users/user.go b/internal/web/actions/default/users/user.go index 37dcca66..86547c44 100644 --- a/internal/web/actions/default/users/user.go +++ b/internal/web/actions/default/users/user.go @@ -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, diff --git a/web/views/@default/users/createPopup.html b/web/views/@default/users/createPopup.html index 81fb0e96..fa0455c9 100644 --- a/web/views/@default/users/createPopup.html +++ b/web/views/@default/users/createPopup.html @@ -60,6 +60,13 @@ + + OTP认证 + + 启用OTP +

启用OTP认证后,在用户登录的时候需要同时填写OTP动态密码。

+ + 备注 diff --git a/web/views/@default/users/update.html b/web/views/@default/users/update.html index 11d88a99..272355f5 100644 --- a/web/views/@default/users/update.html +++ b/web/views/@default/users/update.html @@ -67,6 +67,13 @@ + + OTP认证 + + 启用OTP +

启用OTP认证后,在用户登录的时候需要同时填写OTP动态密码。

+ + 备注 diff --git a/web/views/@default/users/user.html b/web/views/@default/users/user.html index f09a1d60..2e50bd7e 100644 --- a/web/views/@default/users/user.html +++ b/web/views/@default/users/user.html @@ -72,4 +72,29 @@ {{user.registeredIP}}({{user.registeredRegion}}) + + +

OTP认证

+ + + + + + + + + + + + + + + +
状态 + 已启用 + 未启用 +
更多信息
认证二维码 + +

可以通过二维码快速添加OTP认证信息到App中。

+
密钥{{otp.params.secret}}
\ No newline at end of file