mirror of
https://github.com/TeaOSLab/EdgeAdmin.git
synced 2025-11-03 12:20:28 +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