package helpers import ( "encoding/json" "errors" "net/http" "net/url" "reflect" "strings" "github.com/TeaOSLab/EdgeCommon/pkg/configs" "github.com/TeaOSLab/EdgeCommon/pkg/langs" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/userconfigs" "github.com/TeaOSLab/EdgeUser/internal/configloaders" teaconst "github.com/TeaOSLab/EdgeUser/internal/const" "github.com/TeaOSLab/EdgeUser/internal/remotelogs" "github.com/TeaOSLab/EdgeUser/internal/rpc" "github.com/TeaOSLab/EdgeUser/internal/utils/portalutils" "github.com/TeaOSLab/EdgeUser/internal/web/actions/default/login/loginutils" "github.com/iwind/TeaGo/actions" "github.com/iwind/TeaGo/lists" "github.com/iwind/TeaGo/maps" ) // 认证拦截 type userMustAuth struct { UserId int64 module string } func NewUserMustAuth(module string) *userMustAuth { return &userMustAuth{module: module} } func (this *userMustAuth) BeforeAction(actionPtr actions.ActionWrapper, paramName string) (goNext bool) { var action = actionPtr.Object() // 检测注入 if !safeFilterRequest(action.Request) { action.ResponseWriter.WriteHeader(http.StatusForbidden) _, _ = action.ResponseWriter.Write([]byte("Denied By WAF")) return false } // 安全相关 action.AddHeader("X-Frame-Options", "SAMEORIGIN") action.AddHeader("Content-Security-Policy", "default-src 'self' data:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' blob: data:;") var session = action.Session() var userId = session.GetInt64(teaconst.SessionUserId) if userId <= 0 { var errString = session.GetString("@error") if len(errString) > 0 { // 这里为了安全起见不打印具体错误信息 action.WriteString("read session failed, please contact the system administrator.") return false } this.login(action) return false } // 检查指纹 // 将来用户可以自行设置 /**var clientFingerprint = session.GetString("@fingerprint") if len(clientFingerprint) > 0 && clientFingerprint != loginutils.CalculateClientFingerprint(action) { loginutils.UnsetCookie(action) session.Delete() this.login(action) return false }**/ registerConfig, err := configloaders.LoadRegisterConfig() if err != nil { remotelogs.Error("LOAD_REGISTER_CONFIG", err.Error()) return false } if registerConfig == nil { action.WriteString("invalid 'registerConfig'") return false } if registerConfig.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 { loginutils.UnsetCookie(action) session.Delete() this.login(action) return false } } } this.UserId = userId action.Context.Set("userId", this.UserId) userIsOn, fullname, isVerified, isIndividualIdentified, isEnterpriseIdentified, mobileIsVerified, userLang, err := this.findUserInfo(userId) if err != nil { if rpc.IsConnError(err) { remotelogs.Debug("USER_INFO", err.Error()) } else { remotelogs.Error("USER_INFO", err.Error()) } action.RedirectURL("/logout") return false } if !userIsOn { action.ResponseWriter.WriteHeader(http.StatusForbidden) action.WriteString("You account has been disabled by administrator, please contact with your administrator.") return false } // 强制绑定手机号 if !mobileIsVerified && registerConfig.MobileVerification.Force { var skipPackages = []string{"settings", "docs", "logout", "files", "csrf", "api", "email", "ui", "tickets", "index", "messages"} var skip bool for _, packageName := range skipPackages { if strings.Contains(action.Spec.PkgPath, "/actions/default/"+packageName) { skip = true break } } if !skip { action.RedirectURL("/settings/mobile-verify") return false } } var isIdentified = isIndividualIdentified || isEnterpriseIdentified action.Context.Set("isIndividualIdentified", isIndividualIdentified) action.Context.Set("isEnterpriseIdentified", isEnterpriseIdentified) action.Context.Set("isIdentified", isIdentified) action.Context.Set("isVerified", isVerified) if len(userLang) == 0 { userLang = langs.ParseLangFromAction(action) } action.Data["teaLang"] = userLang // 以下是Get专有 if action.Request.Method != http.MethodGet { return true } config, err := configloaders.LoadUIConfig() if err != nil { action.WriteString(err.Error()) return false } // 初始化内置方法 action.ViewFunc("teaTitle", func() string { return action.Data["teaTitle"].(string) }) // 注册 jsonEncode 函数 action.ViewFunc("jsonEncode", func(v interface{}) string { if v == nil { return "null" } data, err := json.Marshal(v) if err != nil { return "" } return string(data) }) action.Data["teaShowVersion"] = config.ShowVersion action.Data["teaTitle"] = config.UserSystemName action.Data["teaName"] = config.ProductName action.Data["teaFaviconFileId"] = config.FaviconFileId action.Data["teaLogoFileId"] = config.LogoFileId action.Data["teaUsername"] = fullname action.Data["teaUserAvatar"] = "" if !action.Data.Has("teaMenu") { action.Data["teaMenu"] = "" } action.Data["teaModules"] = this.modules(userId, isVerified, isIdentified) action.Data["teaSubMenus"] = []map[string]interface{}{} action.Data["teaTabbar"] = []map[string]interface{}{} // 注入品牌配置 brandConfig := configs.GetBrandConfig() action.Data["brandConfig"] = brandConfig.ToMap() if len(config.Version) == 0 { action.Data["teaVersion"] = teaconst.Version } else { action.Data["teaVersion"] = config.Version } action.Data["teaShowPageFooter"] = config.ShowPageFooter action.Data["teaPageFooterHTML"] = config.PageFooterHTML action.Data["teaThemeBackgroundColor"] = config.Theme.BackgroundColor action.Data["teaShowIndexPage"] = portalutils.HasPortalIndex() action.Data["teaIsSuper"] = false action.Data["teaDemoEnabled"] = false if !action.Data.Has("teaSubMenu") { action.Data["teaSubMenu"] = "" } // 菜单 action.Data["firstMenuItem"] = "" // 未读消息数 action.Data["teaBadge"] = 0 // 调用Init initMethod := reflect.ValueOf(actionPtr).MethodByName("Init") if initMethod.IsValid() { initMethod.Call([]reflect.Value{}) } return true } // 菜单配置 func (this *userMustAuth) modules(userId int64, isVerified bool, isIdentified bool) []maps.Map { // 开通的功能 var featureCodes = []string{} rpcClient, err := rpc.SharedRPC() if err == nil { userFeatureResp, err := rpcClient.UserRPC().FindUserFeatures(rpcClient.Context(userId), &pb.FindUserFeaturesRequest{UserId: userId}) if err == nil { for _, feature := range userFeatureResp.Features { featureCodes = append(featureCodes, feature.Code) } } } registerConfig, err := configloaders.LoadRegisterConfig() if err != nil { remotelogs.Error("LOAD_REGISTER_CONFIG", err.Error()) return []maps.Map{} } var requireVerification = false var requireIdentity = false if registerConfig != nil { requireVerification = registerConfig.RequireVerification requireIdentity = registerConfig.RequireIdentity } if (requireVerification && !isVerified) || (requireIdentity && !isIdentified) { featureCodes = []string{} } config, _ := configloaders.LoadUIConfig() var allMaps = []maps.Map{ { "code": "dashboard", "name": "概览", "icon": "dashboard", "isOn": true, }, } var dashboardSubItems = []maps.Map{} priceConfig, _ := configloaders.LoadCacheableUserPriceConfig() if (!requireVerification || isVerified) && (!requireIdentity || isIdentified) { allMaps = []maps.Map{ { "code": "dashboard", "name": "概览", "icon": "dashboard", "isOn": true, "subItems": dashboardSubItems, }, { "code": "servers", "name": "CDN加速", "icon": "clone outline", "isOn": registerConfig != nil && registerConfig.CDNIsOn, "subItems": []maps.Map{ { "name": "刷新预热", "code": "cache", "url": "/servers/cache", }, { "name": "证书管理", "code": "certs", "url": "/servers/certs", }, { "name": "证书申请", "code": "acme", "url": "/servers/certs/acme", "isOn": lists.ContainsString(featureCodes, userconfigs.UserFeatureCodeServerACME), }, { "name": "用量统计", "url": "/servers/traffic-stats", "code": "trafficStat", "isOn": config != nil && config.ShowBandwidthCharts, }, { "name": "计费方式", "code": "fee", "url": "/servers/fee", "isOn": priceConfig != nil && priceConfig.IsOn && !priceConfig.EnablePlans && priceConfig.UserUI.ShowPrices, }, { "name": "流量包", "code": "trafficPackage", "url": "/servers/packages", "isOn": priceConfig != nil && priceConfig.IsOn && !priceConfig.EnablePlans && priceConfig.EnableTrafficPackages && priceConfig.ShowTrafficPackages, }, }, }, { "code": "dns", "name": "域名解析", "icon": "globe", "isOn": false, //"subItems": []maps.Map{ // { // "name": "域名管理", // "code": "dns", // "url": "/dns/providers", // }, //}, }, { "code": "lb", "name": "负载均衡", "icon": "paper plane", "isOn": registerConfig != nil && registerConfig.CDNIsOn && (lists.ContainsString(featureCodes, "server.tcp") || lists.ContainsString(featureCodes, "server.udp")), }, { "code": "waf", "name": "WAF安全", "icon": "magnet", "isOn": registerConfig != nil && registerConfig.CDNIsOn && lists.ContainsString(featureCodes, "server.waf"), "subItems": []maps.Map{ { "name": "拦截日志", "code": "wafLogs", "url": "/waf/logs", }, { "name": "拦截IP", "code": "iplist", "url": "/waf/iplists", }, }, }, { "code": "plans", "name": "套餐管理", "icon": "puzzle piece", "isOn": registerConfig != nil && registerConfig.CDNIsOn && lists.ContainsString(featureCodes, "plan") && priceConfig != nil && priceConfig.IsOn && priceConfig.EnablePlans && priceConfig.ShowPlansInUserSystem, }, { "code": "anti-ddos", "name": "DDoS高防", "icon": "shield", "isOn": registerConfig != nil && registerConfig.ADIsOn, "subItems": []maps.Map{ { "name": "实例列表", "code": "instance", "url": "/anti-ddos/instances", }, { "name": "购买实例", "code": "package", "url": "/anti-ddos/packages", }, }, }, { "code": "ns", "name": "智能DNS", "icon": "globe", "isOn": registerConfig != nil && registerConfig.NSIsOn, "subItems": []maps.Map{ { "name": "我的域名", "code": "domain", "url": "/ns/domains", }, { "name": "域名分组", "code": "domainGroup", "url": "/ns/domains/groups", }, { "name": "批量操作", "code": "domainBatch", "url": "/ns/domains/batch", }, { "name": "线路管理", "code": "route", "url": "/ns/routes", }, { "name": "套餐", "code": "plan", "url": "/ns/plans", }, /**{ "name": "访问日志", "code": "accessLog", "url": "/ns/clusters/accessLogs", },**/ }, }, { "code": "httpdns", "name": "HTTPDNS", "icon": "shield alternate", "isOn": registerConfig != nil && registerConfig.HTTPDNSIsOn, "subItems": []maps.Map{ { "name": "应用管理", "code": "app", "url": "/httpdns/apps", }, { "name": "访问日志", "code": "resolveLogs", "url": "/httpdns/resolveLogs", }, { "name": "解析测试", "code": "sandbox", "url": "/httpdns/sandbox", }, }, }, { "code": "finance", "name": "财务管理", "icon": "yen sign", "isOn": true, "subItems": []maps.Map{ { "name": "费用账单", "code": "bills", "url": "/finance/bills", }, { "name": "收支明细", "code": "logs", "url": "/finance/logs", }, { "name": "充值", "code": "charge", "url": "/finance/charge", }, }, }, { "code": "tickets", "name": "工单系统", "icon": "ticket", "isOn": true, }, { "code": "acl", "name": "访问控制", "icon": "address book", "isOn": true, }, /**{ "code": "tickets", "name": "工单", "icon": "question circle outline", },**/ } } var result = []maps.Map{} for _, m := range allMaps { // 财务管理 if m.GetString("code") == "finance" { if config != nil && !config.ShowFinance { continue } } // 域名解析 if !m.GetBool("isOn") { continue } result = append(result, m) } return result } // 跳转到登录页 func (this *userMustAuth) login(action *actions.ActionObject) { action.RedirectURL("/login?from=" + url.QueryEscape(action.Request.RequestURI)) } // 查找用户名称 func (this *userMustAuth) findUserInfo(userId int64) (isOn bool, fullname string, isVerified bool, isIndividualIdentified bool, isEnterpriseIdentified bool, mobileIsVerified bool, lang string, err error) { rpcClient, err := rpc.SharedRPC() if err != nil { return false, "", false, false, false, false, "", err } resp, err := rpcClient.UserRPC().FindEnabledUser(rpcClient.Context(userId), &pb.FindEnabledUserRequest{UserId: userId}) if err != nil { return false, "", false, false, false, false, "", err } var user = resp.User if user == nil { return false, "", false, false, false, false, "", errors.New("can not find user") } if !user.IsOn { return false, "", false, false, false, false, "", nil } fullname = user.Fullname if !user.IsVerified { fullname += "(未审核)" } else { if user.IsRejected { fullname += "(已被拒绝)" } } return true, fullname, user.IsVerified, user.IsIndividualIdentified, user.IsEnterpriseIdentified, len(user.VerifiedMobile) > 0, user.Lang, nil }