mirror of
				https://github.com/TeaOSLab/EdgeAdmin.git
				synced 2025-11-04 13:10:26 +08:00 
			
		
		
		
	使用本地SID二次校验增强管理系统安全性
This commit is contained in:
		@@ -72,6 +72,9 @@ func (this *SessionManager) Read(sid string) map[string]string {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (this *SessionManager) WriteItem(sid string, key string, value string) bool {
 | 
					func (this *SessionManager) WriteItem(sid string, key string, value string) bool {
 | 
				
			||||||
 | 
						// 删除缓存
 | 
				
			||||||
 | 
						defer ttlcache.DefaultCache.Delete( "SESSION@" + sid)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 忽略OTP
 | 
						// 忽略OTP
 | 
				
			||||||
	if strings.HasSuffix(sid, "_otp") {
 | 
						if strings.HasSuffix(sid, "_otp") {
 | 
				
			||||||
		return false
 | 
							return false
 | 
				
			||||||
@@ -95,6 +98,9 @@ func (this *SessionManager) WriteItem(sid string, key string, value string) bool
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (this *SessionManager) Delete(sid string) bool {
 | 
					func (this *SessionManager) Delete(sid string) bool {
 | 
				
			||||||
 | 
						// 删除缓存
 | 
				
			||||||
 | 
						defer ttlcache.DefaultCache.Delete( "SESSION@" + sid)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 忽略OTP
 | 
						// 忽略OTP
 | 
				
			||||||
	if strings.HasSuffix(sid, "_otp") {
 | 
						if strings.HasSuffix(sid, "_otp") {
 | 
				
			||||||
		return false
 | 
							return false
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,6 +21,7 @@ import (
 | 
				
			|||||||
	"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
 | 
						"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
 | 
				
			||||||
	"github.com/iwind/TeaGo/actions"
 | 
						"github.com/iwind/TeaGo/actions"
 | 
				
			||||||
	"github.com/iwind/TeaGo/lists"
 | 
						"github.com/iwind/TeaGo/lists"
 | 
				
			||||||
 | 
						"github.com/iwind/TeaGo/rands"
 | 
				
			||||||
	"github.com/iwind/TeaGo/types"
 | 
						"github.com/iwind/TeaGo/types"
 | 
				
			||||||
	stringutil "github.com/iwind/TeaGo/utils/string"
 | 
						stringutil "github.com/iwind/TeaGo/utils/string"
 | 
				
			||||||
	"net"
 | 
						"net"
 | 
				
			||||||
@@ -236,7 +237,10 @@ func (this *IndexAction) RunPost(params struct {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 写入SESSION
 | 
						// 写入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})
 | 
						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})
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,6 +19,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/iwind/TeaGo/rands"
 | 
				
			||||||
	stringutil "github.com/iwind/TeaGo/utils/string"
 | 
						stringutil "github.com/iwind/TeaGo/utils/string"
 | 
				
			||||||
	"github.com/xlzd/gotp"
 | 
						"github.com/xlzd/gotp"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
@@ -132,7 +133,10 @@ func (this *OtpAction) RunPost(params struct {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 写入SESSION
 | 
						// 写入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
 | 
						// 删除OTP SESSION
 | 
				
			||||||
	_, err = this.RPC().LoginSessionRPC().DeleteLoginSession(this.AdminContext(), &pb.DeleteLoginSessionRequest{Sid: sid})
 | 
						_, err = this.RPC().LoginSessionRPC().DeleteLoginSession(this.AdminContext(), &pb.DeleteLoginSessionRequest{Sid: sid})
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										14
									
								
								internal/web/actions/default/login/init.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								internal/web/actions/default/login/init.go
									
									
									
									
									
										Normal file
									
								
							@@ -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()
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										74
									
								
								internal/web/actions/default/login/validate.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								internal/web/actions/default/login/validate.go
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -200,18 +200,22 @@ func (this *userMustAuth) BeforeAction(actionPtr actions.ActionWrapper, paramNam
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 检查区域
 | 
						// 检查区域
 | 
				
			||||||
	if securityConfig != nil && securityConfig.CheckClientRegion {
 | 
					 | 
				
			||||||
	var oldClientIP = session.GetString("@ip")
 | 
						var oldClientIP = session.GetString("@ip")
 | 
				
			||||||
	var currentClientIP = loginutils.RemoteIP(action)
 | 
						var currentClientIP = loginutils.RemoteIP(action)
 | 
				
			||||||
	if len(oldClientIP) > 0 && len(currentClientIP) > 0 && oldClientIP != currentClientIP {
 | 
						if len(oldClientIP) > 0 && len(currentClientIP) > 0 && oldClientIP != currentClientIP {
 | 
				
			||||||
		var oldRegion = loginutils.LookupIPRegion(oldClientIP)
 | 
							var oldRegion = loginutils.LookupIPRegion(oldClientIP)
 | 
				
			||||||
		var newRegion = loginutils.LookupIPRegion(currentClientIP)
 | 
							var newRegion = loginutils.LookupIPRegion(currentClientIP)
 | 
				
			||||||
		if newRegion != oldRegion {
 | 
							if newRegion != oldRegion {
 | 
				
			||||||
 | 
								if securityConfig != nil && securityConfig.CheckClientRegion {
 | 
				
			||||||
				loginutils.UnsetCookie(action)
 | 
									loginutils.UnsetCookie(action)
 | 
				
			||||||
				session.Delete()
 | 
									session.Delete()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				this.login(action)
 | 
									this.login(action)
 | 
				
			||||||
				return false
 | 
									return false
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									// TODO 考虑IP变化时也需要验证,主要是考虑被反向代理的情形
 | 
				
			||||||
 | 
									action.RedirectURL("/login/validate?from=" + url.QueryEscape(action.Request.URL.String()))
 | 
				
			||||||
 | 
									return false
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -60,12 +60,13 @@ func (this *UserShouldAuth) BeforeAction(actionPtr actions.ActionWrapper, paramN
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// StoreAdmin 存储用户名到SESSION
 | 
					// 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)
 | 
						loginutils.SetCookie(this.action, remember)
 | 
				
			||||||
	var session = this.action.Session()
 | 
						var session = this.action.Session()
 | 
				
			||||||
	session.Write("adminId", numberutils.FormatInt64(adminId))
 | 
						session.Write("adminId", numberutils.FormatInt64(adminId))
 | 
				
			||||||
	session.Write("@fingerprint", loginutils.CalculateClientFingerprint(this.action))
 | 
						session.Write("@fingerprint", loginutils.CalculateClientFingerprint(this.action))
 | 
				
			||||||
	session.Write("@ip", loginutils.RemoteIP(this.action))
 | 
						session.Write("@ip", loginutils.RemoteIP(this.action))
 | 
				
			||||||
 | 
						session.Write("@localSid", localSid)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (this *UserShouldAuth) IsUser() bool {
 | 
					func (this *UserShouldAuth) IsUser() bool {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -29,6 +29,7 @@ import (
 | 
				
			|||||||
	_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/dns/tasks"
 | 
						_ "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/index"
 | 
				
			||||||
	_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/log"
 | 
						_ "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/logout"
 | 
				
			||||||
	_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/messages"
 | 
						_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/messages"
 | 
				
			||||||
	_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/nodes"
 | 
						_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/nodes"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -39,6 +39,12 @@ Tea.context(function () {
 | 
				
			|||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	this.submitSuccess = function (resp) {
 | 
						this.submitSuccess = function (resp) {
 | 
				
			||||||
 | 
							// 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) {
 | 
								if (resp.data.requireOTP) {
 | 
				
			||||||
				window.location = "/index/otp?sid=" + resp.data.sid + "&remember=" + (resp.data.remember ? 1 : 0) + "&from=" + window.encodeURIComponent(this.from)
 | 
									window.location = "/index/otp?sid=" + resp.data.sid + "&remember=" + (resp.data.remember ? 1 : 0) + "&from=" + window.encodeURIComponent(this.from)
 | 
				
			||||||
				return
 | 
									return
 | 
				
			||||||
@@ -48,5 +54,6 @@ Tea.context(function () {
 | 
				
			|||||||
			} else {
 | 
								} else {
 | 
				
			||||||
				window.location = this.from;
 | 
									window.location = this.from;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
@@ -22,10 +22,17 @@ Tea.context(function () {
 | 
				
			|||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	this.submitSuccess = function (resp) {
 | 
						this.submitSuccess = function (resp) {
 | 
				
			||||||
 | 
							// 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) {
 | 
								if (this.from.length == 0) {
 | 
				
			||||||
				window.location = "/dashboard";
 | 
									window.location = "/dashboard";
 | 
				
			||||||
			} else {
 | 
								} else {
 | 
				
			||||||
				window.location = this.from;
 | 
									window.location = this.from;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
							
								
								
									
										15
									
								
								web/views/@default/login/validate.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								web/views/@default/login/validate.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					<!DOCTYPE html>
 | 
				
			||||||
 | 
					<html lang="zh">
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
 | 
				
			||||||
 | 
					    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=0">
 | 
				
			||||||
 | 
					    {$TEA.VUE}
 | 
				
			||||||
 | 
					    {$TEA.SEMANTIC}
 | 
				
			||||||
 | 
					    <title></title>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					<div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										34
									
								
								web/views/@default/login/validate.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								web/views/@default/login/validate.js
									
									
									
									
									
										Normal file
									
								
							@@ -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"
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									})
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
		Reference in New Issue
	
	Block a user