From 33a5c86beb6969c320c9f16b525d28e440c2fbeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E7=A5=A5=E8=B6=85?= Date: Mon, 8 Apr 2024 10:24:10 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BD=BF=E7=94=A8=E6=9C=AC=E5=9C=B0SID?= =?UTF-8?q?=E4=BA=8C=E6=AC=A1=E6=A0=A1=E9=AA=8C=E5=A2=9E=E5=BC=BA=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E7=B3=BB=E7=BB=9F=E5=AE=89=E5=85=A8=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/nodes/session_manager.go | 6 ++ internal/web/actions/default/index/index.go | 6 +- internal/web/actions/default/index/otp.go | 6 +- internal/web/actions/default/login/init.go | 14 ++++ .../web/actions/default/login/validate.go | 74 +++++++++++++++++++ internal/web/helpers/user_must_auth.go | 18 +++-- internal/web/helpers/user_should_auth.go | 3 +- internal/web/import.go | 1 + web/views/@default/index/index.js | 25 ++++--- web/views/@default/index/otp.js | 17 +++-- web/views/@default/login/validate.html | 15 ++++ web/views/@default/login/validate.js | 34 +++++++++ 12 files changed, 195 insertions(+), 24 deletions(-) create mode 100644 internal/web/actions/default/login/init.go create mode 100644 internal/web/actions/default/login/validate.go create mode 100644 web/views/@default/login/validate.html create mode 100644 web/views/@default/login/validate.js diff --git a/internal/nodes/session_manager.go b/internal/nodes/session_manager.go index 43076dc6..07a70cff 100644 --- a/internal/nodes/session_manager.go +++ b/internal/nodes/session_manager.go @@ -72,6 +72,9 @@ func (this *SessionManager) Read(sid string) map[string]string { } func (this *SessionManager) WriteItem(sid string, key string, value string) bool { + // 删除缓存 + defer ttlcache.DefaultCache.Delete( "SESSION@" + sid) + // 忽略OTP if strings.HasSuffix(sid, "_otp") { return false @@ -95,6 +98,9 @@ func (this *SessionManager) WriteItem(sid string, key string, value string) bool } func (this *SessionManager) Delete(sid string) bool { + // 删除缓存 + defer ttlcache.DefaultCache.Delete( "SESSION@" + sid) + // 忽略OTP if strings.HasSuffix(sid, "_otp") { return false diff --git a/internal/web/actions/default/index/index.go b/internal/web/actions/default/index/index.go index c790427d..c1f777b1 100644 --- a/internal/web/actions/default/index/index.go +++ b/internal/web/actions/default/index/index.go @@ -21,6 +21,7 @@ import ( "github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs" "github.com/iwind/TeaGo/actions" "github.com/iwind/TeaGo/lists" + "github.com/iwind/TeaGo/rands" "github.com/iwind/TeaGo/types" stringutil "github.com/iwind/TeaGo/utils/string" "net" @@ -236,7 +237,10 @@ func (this *IndexAction) RunPost(params struct { } // 写入SESSION - params.Auth.StoreAdmin(adminId, params.Remember) + var localSid = rands.HexString(32) + this.Data["localSid"] = localSid + this.Data["ip"] = loginutils.RemoteIP(&this.ActionObject) + params.Auth.StoreAdmin(adminId, params.Remember, localSid) // 记录日志 err = dao.SharedLogDAO.CreateAdminLog(rpcClient.Context(adminId), oplogs.LevelInfo, this.Request.URL.Path, langs.DefaultMessage(codes.AdminLogin_LogSuccess, params.Username), loginutils.RemoteIP(&this.ActionObject), codes.AdminLogin_LogSuccess, []any{params.Username}) diff --git a/internal/web/actions/default/index/otp.go b/internal/web/actions/default/index/otp.go index aa0ecb26..9b2e6f25 100644 --- a/internal/web/actions/default/index/otp.go +++ b/internal/web/actions/default/index/otp.go @@ -19,6 +19,7 @@ import ( "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/iwind/TeaGo/actions" "github.com/iwind/TeaGo/maps" + "github.com/iwind/TeaGo/rands" stringutil "github.com/iwind/TeaGo/utils/string" "github.com/xlzd/gotp" "time" @@ -132,7 +133,10 @@ func (this *OtpAction) RunPost(params struct { } // 写入SESSION - params.Auth.StoreAdmin(adminId, params.Remember) + var localSid = rands.HexString(32) + this.Data["localSid"] = localSid + this.Data["ip"] = loginutils.RemoteIP(&this.ActionObject) + params.Auth.StoreAdmin(adminId, params.Remember, localSid) // 删除OTP SESSION _, err = this.RPC().LoginSessionRPC().DeleteLoginSession(this.AdminContext(), &pb.DeleteLoginSessionRequest{Sid: sid}) diff --git a/internal/web/actions/default/login/init.go b/internal/web/actions/default/login/init.go new file mode 100644 index 00000000..b59d8b7b --- /dev/null +++ b/internal/web/actions/default/login/init.go @@ -0,0 +1,14 @@ +package login + +import ( + "github.com/iwind/TeaGo" +) + +func init() { + TeaGo.BeforeStart(func(server *TeaGo.Server) { + server. + Prefix("/login"). + GetPost("/validate", new(ValidateAction)). + EndAll() + }) +} diff --git a/internal/web/actions/default/login/validate.go b/internal/web/actions/default/login/validate.go new file mode 100644 index 00000000..2043103b --- /dev/null +++ b/internal/web/actions/default/login/validate.go @@ -0,0 +1,74 @@ +// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . + +package login + +import ( + "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" + "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/index/loginutils" + "github.com/iwind/TeaGo/actions" + "github.com/iwind/TeaGo/rands" + "net" +) + +type ValidateAction struct { + actionutils.ParentAction +} + +func (this *ValidateAction) Init() { + this.Nav("", "", "") +} + +func (this *ValidateAction) RunGet(params struct { + From string +}) { + this.Data["from"] = params.From + + this.Show() +} + +func (this *ValidateAction) RunPost(params struct { + Must *actions.Must + + LocalSid string + Ip string +}) { + var isOk bool + + defer func() { + this.Data["isOk"] = isOk + + if !isOk { + loginutils.UnsetCookie(&this.ActionObject) + this.Session().Delete() + } + + this.Success() + }() + + if len(params.LocalSid) == 0 || len(params.LocalSid) != 32 { + return + } + if len(params.Ip) == 0 { + return + } + + if net.ParseIP(params.Ip) == nil { + return + } + + if params.LocalSid == this.Session().GetString("@localSid") { + isOk = true + + // renew ip and local sid + var newIP = loginutils.RemoteIP(&this.ActionObject) + var newLocalSid = rands.HexString(32) + + this.Session().Write("@ip", newIP) + this.Session().Write("@localSid", newLocalSid) + + this.Data["ip"] = newIP + this.Data["localSid"] = newLocalSid + + return + } +} diff --git a/internal/web/helpers/user_must_auth.go b/internal/web/helpers/user_must_auth.go index 21f871bc..a3b084b3 100644 --- a/internal/web/helpers/user_must_auth.go +++ b/internal/web/helpers/user_must_auth.go @@ -200,18 +200,22 @@ func (this *userMustAuth) BeforeAction(actionPtr actions.ActionWrapper, paramNam } // 检查区域 - if securityConfig != nil && securityConfig.CheckClientRegion { - var oldClientIP = session.GetString("@ip") - var currentClientIP = loginutils.RemoteIP(action) - if len(oldClientIP) > 0 && len(currentClientIP) > 0 && oldClientIP != currentClientIP { - var oldRegion = loginutils.LookupIPRegion(oldClientIP) - var newRegion = loginutils.LookupIPRegion(currentClientIP) - if newRegion != oldRegion { + var oldClientIP = session.GetString("@ip") + var currentClientIP = loginutils.RemoteIP(action) + if len(oldClientIP) > 0 && len(currentClientIP) > 0 && oldClientIP != currentClientIP { + var oldRegion = loginutils.LookupIPRegion(oldClientIP) + var newRegion = loginutils.LookupIPRegion(currentClientIP) + if newRegion != oldRegion { + if securityConfig != nil && securityConfig.CheckClientRegion { loginutils.UnsetCookie(action) session.Delete() this.login(action) return false + } else { + // TODO 考虑IP变化时也需要验证,主要是考虑被反向代理的情形 + action.RedirectURL("/login/validate?from=" + url.QueryEscape(action.Request.URL.String())) + return false } } } diff --git a/internal/web/helpers/user_should_auth.go b/internal/web/helpers/user_should_auth.go index 7eda0e15..c13df2ca 100644 --- a/internal/web/helpers/user_should_auth.go +++ b/internal/web/helpers/user_should_auth.go @@ -60,12 +60,13 @@ func (this *UserShouldAuth) BeforeAction(actionPtr actions.ActionWrapper, paramN } // StoreAdmin 存储用户名到SESSION -func (this *UserShouldAuth) StoreAdmin(adminId int64, remember bool) { +func (this *UserShouldAuth) StoreAdmin(adminId int64, remember bool, localSid string) { loginutils.SetCookie(this.action, remember) var session = this.action.Session() session.Write("adminId", numberutils.FormatInt64(adminId)) session.Write("@fingerprint", loginutils.CalculateClientFingerprint(this.action)) session.Write("@ip", loginutils.RemoteIP(this.action)) + session.Write("@localSid", localSid) } func (this *UserShouldAuth) IsUser() bool { diff --git a/internal/web/import.go b/internal/web/import.go index 461ac764..dc6dd8cc 100644 --- a/internal/web/import.go +++ b/internal/web/import.go @@ -29,6 +29,7 @@ import ( _ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/dns/tasks" _ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/index" _ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/log" + _ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/login" _ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/logout" _ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/messages" _ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/nodes" diff --git a/web/views/@default/index/index.js b/web/views/@default/index/index.js index e4bfafa5..c3951539 100644 --- a/web/views/@default/index/index.js +++ b/web/views/@default/index/index.js @@ -39,14 +39,21 @@ Tea.context(function () { }; this.submitSuccess = function (resp) { - if (resp.data.requireOTP) { - window.location = "/index/otp?sid=" + resp.data.sid + "&remember=" + (resp.data.remember ? 1 : 0) + "&from=" + window.encodeURIComponent(this.from) - return - } - if (this.from.length == 0) { - window.location = "/dashboard"; - } else { - window.location = this.from; - } + // store information to local + localStorage.setItem("sid", resp.data.localSid) + localStorage.setItem("ip", resp.data.ip) + + // redirect back + this.$delay(function () { + if (resp.data.requireOTP) { + window.location = "/index/otp?sid=" + resp.data.sid + "&remember=" + (resp.data.remember ? 1 : 0) + "&from=" + window.encodeURIComponent(this.from) + return + } + if (this.from.length == 0) { + window.location = "/dashboard"; + } else { + window.location = this.from; + } + }) }; }); \ No newline at end of file diff --git a/web/views/@default/index/otp.js b/web/views/@default/index/otp.js index a975511f..ac056d81 100644 --- a/web/views/@default/index/otp.js +++ b/web/views/@default/index/otp.js @@ -22,10 +22,17 @@ Tea.context(function () { }; this.submitSuccess = function (resp) { - if (this.from.length == 0) { - window.location = "/dashboard"; - } else { - window.location = this.from; - } + // store information to local + localStorage.setItem("sid", resp.data.localSid) + localStorage.setItem("ip", resp.data.ip) + + // redirect back + this.$delay(function () { + if (this.from.length == 0) { + window.location = "/dashboard"; + } else { + window.location = this.from; + } + }) }; }); \ No newline at end of file diff --git a/web/views/@default/login/validate.html b/web/views/@default/login/validate.html new file mode 100644 index 00000000..755635de --- /dev/null +++ b/web/views/@default/login/validate.html @@ -0,0 +1,15 @@ + + + + + + {$TEA.VUE} + {$TEA.SEMANTIC} + + + +
+ +
+ + \ No newline at end of file diff --git a/web/views/@default/login/validate.js b/web/views/@default/login/validate.js new file mode 100644 index 00000000..ebd9b58b --- /dev/null +++ b/web/views/@default/login/validate.js @@ -0,0 +1,34 @@ +Tea.context(function () { + this.$delay(function () { + let sid = localStorage.getItem("sid") + let ip = localStorage.getItem("ip") + + if (sid == null || sid.length == 0 || ip == null || ip.length == 0) { + window.location = "/logout" + return + } + + this.$post("$") + .params({localSid: sid, "ip": ip}) + .post() + .success(function (resp) { + if (!resp.data.isOk) { + window.location = "/logout" + return + } + + // renew local data + localStorage.setItem("sid", resp.data.localSid) + localStorage.setItem("ip", resp.data.ip) + + // redirect back (MUST delay) + this.$delay(function () { + if (this.from.length > 0) { + window.location = this.from + } else { + window.location = "/dashboard" + } + }) + }) + }) +}) \ No newline at end of file