mirror of
https://github.com/TeaOSLab/EdgeAdmin.git
synced 2025-11-27 05:00:25 +08:00
安全设置增加允许访问的国家地区、省份、是否局域网访问
This commit is contained in:
@@ -23,6 +23,9 @@ var sharedSecurityConfig *SecurityConfig = nil
|
|||||||
|
|
||||||
type SecurityConfig struct {
|
type SecurityConfig struct {
|
||||||
Frame string `json:"frame"`
|
Frame string `json:"frame"`
|
||||||
|
AllowCountryIds []int64 `json:"allowCountryIds"`
|
||||||
|
AllowProvinceIds []int64 `json:"allowProvinceIds"`
|
||||||
|
AllowLocal bool `json:"allowLocal"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadSecurityConfig() (*SecurityConfig, error) {
|
func LoadSecurityConfig() (*SecurityConfig, error) {
|
||||||
@@ -50,7 +53,7 @@ func UpdateSecurityConfig(securityConfig *SecurityConfig) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = rpcClient.SysSettingRPC().UpdateSysSetting(rpcClient.Context(1), &pb.UpdateSysSettingRequest{
|
_, err = rpcClient.SysSettingRPC().UpdateSysSetting(rpcClient.Context(0), &pb.UpdateSysSettingRequest{
|
||||||
Code: SecuritySettingName,
|
Code: SecuritySettingName,
|
||||||
ValueJSON: valueJSON,
|
ValueJSON: valueJSON,
|
||||||
})
|
})
|
||||||
@@ -69,7 +72,7 @@ func loadSecurityConfig() (*SecurityConfig, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
resp, err := rpcClient.SysSettingRPC().ReadSysSetting(rpcClient.Context(1), &pb.ReadSysSettingRequest{
|
resp, err := rpcClient.SysSettingRPC().ReadSysSetting(rpcClient.Context(0), &pb.ReadSysSettingRequest{
|
||||||
Code: SecuritySettingName,
|
Code: SecuritySettingName,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -94,5 +97,6 @@ func loadSecurityConfig() (*SecurityConfig, error) {
|
|||||||
func defaultSecurityConfig() *SecurityConfig {
|
func defaultSecurityConfig() *SecurityConfig {
|
||||||
return &SecurityConfig{
|
return &SecurityConfig{
|
||||||
Frame: FrameSameOrigin,
|
Frame: FrameSameOrigin,
|
||||||
|
AllowLocal: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ func (this *SyncClusterTask) loop() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ctx := rpcClient.Context(1)
|
ctx := rpcClient.Context(0)
|
||||||
resp, err := rpcClient.NodeClusterRPC().FindAllChangedNodeClusters(ctx, &pb.FindAllChangedNodeClustersRequest{})
|
resp, err := rpcClient.NodeClusterRPC().FindAllChangedNodeClusters(ctx, &pb.FindAllChangedNodeClustersRequest{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import "strings"
|
import (
|
||||||
|
"github.com/iwind/TeaGo/types"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
// format address
|
// format address
|
||||||
func FormatAddress(addr string) string {
|
func FormatAddress(addr string) string {
|
||||||
@@ -13,3 +16,16 @@ func FormatAddress(addr string) string {
|
|||||||
addr = strings.TrimSpace(addr)
|
addr = strings.TrimSpace(addr)
|
||||||
return addr
|
return addr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 分割数字
|
||||||
|
func SplitNumbers(numbers string) (result []int64) {
|
||||||
|
if len(numbers) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pieces := strings.Split(numbers, ",")
|
||||||
|
for _, piece := range pieces {
|
||||||
|
number := types.Int64(strings.TrimSpace(piece))
|
||||||
|
result = append(result, number)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
package security
|
package security
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/securitymanager"
|
"github.com/TeaOSLab/EdgeAdmin/internal/securitymanager"
|
||||||
"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/iwind/TeaGo/actions"
|
"github.com/iwind/TeaGo/actions"
|
||||||
|
"github.com/iwind/TeaGo/maps"
|
||||||
)
|
)
|
||||||
|
|
||||||
type IndexAction struct {
|
type IndexAction struct {
|
||||||
@@ -20,12 +23,52 @@ func (this *IndexAction) RunGet(params struct{}) {
|
|||||||
this.ErrorPage(err)
|
this.ErrorPage(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 国家和地区
|
||||||
|
countryMaps := []maps.Map{}
|
||||||
|
for _, countryId := range config.AllowCountryIds {
|
||||||
|
countryResp, err := this.RPC().RegionCountryRPC().FindEnabledRegionCountry(this.AdminContext(), &pb.FindEnabledRegionCountryRequest{CountryId: countryId})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
country := countryResp.Country
|
||||||
|
if country != nil {
|
||||||
|
countryMaps = append(countryMaps, maps.Map{
|
||||||
|
"id": country.Id,
|
||||||
|
"name": country.Name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.Data["countries"] = countryMaps
|
||||||
|
|
||||||
|
// 省份
|
||||||
|
provinceMaps := []maps.Map{}
|
||||||
|
for _, provinceId := range config.AllowProvinceIds {
|
||||||
|
provinceResp, err := this.RPC().RegionProvinceRPC().FindEnabledRegionProvince(this.AdminContext(), &pb.FindEnabledRegionProvinceRequest{ProvinceId: provinceId})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
province := provinceResp.Province
|
||||||
|
if province != nil {
|
||||||
|
provinceMaps = append(provinceMaps, maps.Map{
|
||||||
|
"id": province.Id,
|
||||||
|
"name": province.Name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.Data["provinces"] = provinceMaps
|
||||||
|
|
||||||
this.Data["config"] = config
|
this.Data["config"] = config
|
||||||
this.Show()
|
this.Show()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *IndexAction) RunPost(params struct {
|
func (this *IndexAction) RunPost(params struct {
|
||||||
Frame string
|
Frame string
|
||||||
|
CountryIdsJSON []byte
|
||||||
|
ProvinceIdsJSON []byte
|
||||||
|
AllowLocal bool
|
||||||
|
|
||||||
Must *actions.Must
|
Must *actions.Must
|
||||||
CSRF *actionutils.CSRF
|
CSRF *actionutils.CSRF
|
||||||
@@ -38,7 +81,34 @@ func (this *IndexAction) RunPost(params struct {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 框架
|
||||||
config.Frame = params.Frame
|
config.Frame = params.Frame
|
||||||
|
|
||||||
|
// 国家和地区
|
||||||
|
countryIds := []int64{}
|
||||||
|
if len(params.CountryIdsJSON) > 0 {
|
||||||
|
err = json.Unmarshal(params.CountryIdsJSON, &countryIds)
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
config.AllowCountryIds = countryIds
|
||||||
|
|
||||||
|
// 省份
|
||||||
|
provinceIds := []int64{}
|
||||||
|
if len(params.ProvinceIdsJSON) > 0 {
|
||||||
|
err = json.Unmarshal(params.ProvinceIdsJSON, &provinceIds)
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
config.AllowProvinceIds = provinceIds
|
||||||
|
|
||||||
|
// 允许本地
|
||||||
|
config.AllowLocal = params.AllowLocal
|
||||||
|
|
||||||
err = securitymanager.UpdateSecurityConfig(config)
|
err = securitymanager.UpdateSecurityConfig(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
this.ErrorPage(err)
|
this.ErrorPage(err)
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ func init() {
|
|||||||
server.
|
server.
|
||||||
Prefix("/ui").
|
Prefix("/ui").
|
||||||
Get("/download", new(DownloadAction)).
|
Get("/download", new(DownloadAction)).
|
||||||
|
GetPost("/selectProvincesPopup", new(SelectProvincesPopupAction)).
|
||||||
|
GetPost("/selectCountriesPopup", new(SelectCountriesPopupAction)).
|
||||||
|
|
||||||
// 以下的需要压缩
|
// 以下的需要压缩
|
||||||
Helper(&actions.Gzip{Level: gzip.BestCompression}).
|
Helper(&actions.Gzip{Level: gzip.BestCompression}).
|
||||||
|
|||||||
70
internal/web/actions/default/ui/selectCountriesPopup.go
Normal file
70
internal/web/actions/default/ui/selectCountriesPopup.go
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
|
||||||
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
|
"github.com/iwind/TeaGo/actions"
|
||||||
|
"github.com/iwind/TeaGo/lists"
|
||||||
|
"github.com/iwind/TeaGo/maps"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SelectCountriesPopupAction struct {
|
||||||
|
actionutils.ParentAction
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *SelectCountriesPopupAction) Init() {
|
||||||
|
this.Nav("", "", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *SelectCountriesPopupAction) RunGet(params struct {
|
||||||
|
CountryIds string
|
||||||
|
}) {
|
||||||
|
selectedCountryIds := utils.SplitNumbers(params.CountryIds)
|
||||||
|
|
||||||
|
countriesResp, err := this.RPC().RegionCountryRPC().FindAllEnabledRegionCountries(this.AdminContext(), &pb.FindAllEnabledRegionCountriesRequest{})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
countryMaps := []maps.Map{}
|
||||||
|
for _, country := range countriesResp.Countries {
|
||||||
|
countryMaps = append(countryMaps, maps.Map{
|
||||||
|
"id": country.Id,
|
||||||
|
"name": country.Name,
|
||||||
|
"letter": strings.ToUpper(string(country.Pinyin[0][0])),
|
||||||
|
"isChecked": lists.ContainsInt64(selectedCountryIds, country.Id),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.Data["countries"] = countryMaps
|
||||||
|
|
||||||
|
this.Show()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *SelectCountriesPopupAction) RunPost(params struct {
|
||||||
|
CountryIds []int64
|
||||||
|
|
||||||
|
Must *actions.Must
|
||||||
|
CSRF *actionutils.CSRF
|
||||||
|
}) {
|
||||||
|
countryMaps := []maps.Map{}
|
||||||
|
for _, countryId := range params.CountryIds {
|
||||||
|
countryResp, err := this.RPC().RegionCountryRPC().FindEnabledRegionCountry(this.AdminContext(), &pb.FindEnabledRegionCountryRequest{CountryId: countryId})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
country := countryResp.Country
|
||||||
|
if country == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
countryMaps = append(countryMaps, maps.Map{
|
||||||
|
"id": country.Id,
|
||||||
|
"name": country.Name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.Data["countries"] = countryMaps
|
||||||
|
|
||||||
|
this.Success()
|
||||||
|
}
|
||||||
70
internal/web/actions/default/ui/selectProvincesPopup.go
Normal file
70
internal/web/actions/default/ui/selectProvincesPopup.go
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
|
||||||
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
|
"github.com/iwind/TeaGo/actions"
|
||||||
|
"github.com/iwind/TeaGo/lists"
|
||||||
|
"github.com/iwind/TeaGo/maps"
|
||||||
|
)
|
||||||
|
|
||||||
|
const ChinaCountryId = 1
|
||||||
|
|
||||||
|
type SelectProvincesPopupAction struct {
|
||||||
|
actionutils.ParentAction
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *SelectProvincesPopupAction) Init() {
|
||||||
|
this.Nav("", "", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *SelectProvincesPopupAction) RunGet(params struct {
|
||||||
|
ProvinceIds string
|
||||||
|
}) {
|
||||||
|
selectedProvinceIds := utils.SplitNumbers(params.ProvinceIds)
|
||||||
|
|
||||||
|
provincesResp, err := this.RPC().RegionProvinceRPC().FindAllEnabledRegionProvincesWithCountryId(this.AdminContext(), &pb.FindAllEnabledRegionProvincesWithCountryIdRequest{CountryId: ChinaCountryId})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
provinceMaps := []maps.Map{}
|
||||||
|
for _, province := range provincesResp.Provinces {
|
||||||
|
provinceMaps = append(provinceMaps, maps.Map{
|
||||||
|
"id": province.Id,
|
||||||
|
"name": province.Name,
|
||||||
|
"isChecked": lists.ContainsInt64(selectedProvinceIds, province.Id),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.Data["provinces"] = provinceMaps
|
||||||
|
|
||||||
|
this.Show()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *SelectProvincesPopupAction) RunPost(params struct {
|
||||||
|
ProvinceIds []int64
|
||||||
|
|
||||||
|
Must *actions.Must
|
||||||
|
CSRF *actionutils.CSRF
|
||||||
|
}) {
|
||||||
|
provinceMaps := []maps.Map{}
|
||||||
|
for _, provinceId := range params.ProvinceIds {
|
||||||
|
provinceResp, err := this.RPC().RegionProvinceRPC().FindEnabledRegionProvince(this.AdminContext(), &pb.FindEnabledRegionProvinceRequest{ProvinceId: provinceId})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
province := provinceResp.Province
|
||||||
|
if province == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
provinceMaps = append(provinceMaps, maps.Map{
|
||||||
|
"id": province.Id,
|
||||||
|
"name": province.Name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.Data["provinces"] = provinceMaps
|
||||||
|
|
||||||
|
this.Success()
|
||||||
|
}
|
||||||
@@ -35,6 +35,12 @@ func (this *UserMustAuth) BeforeAction(actionPtr actions.ActionWrapper, paramNam
|
|||||||
}
|
}
|
||||||
action.AddHeader("Content-Security-Policy", "default-src 'self' data:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'")
|
action.AddHeader("Content-Security-Policy", "default-src 'self' data:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'")
|
||||||
|
|
||||||
|
// 检查IP
|
||||||
|
if !checkIP(securityConfig, action.RequestRemoteIP()) {
|
||||||
|
action.ResponseWriter.WriteHeader(http.StatusForbidden)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// 检查系统是否已经配置过
|
// 检查系统是否已经配置过
|
||||||
if !setup.IsConfigured() {
|
if !setup.IsConfigured() {
|
||||||
action.RedirectURL("/setup")
|
action.RedirectURL("/setup")
|
||||||
@@ -82,8 +88,48 @@ func (this *UserMustAuth) BeforeAction(actionPtr actions.ActionWrapper, paramNam
|
|||||||
return action.Data["teaTitle"].(string)
|
return action.Data["teaTitle"].(string)
|
||||||
})
|
})
|
||||||
|
|
||||||
// 初始化变量
|
action.Data["teaTitle"] = teaconst.ProductNameZH
|
||||||
modules := []maps.Map{
|
action.Data["teaName"] = teaconst.ProductNameZH
|
||||||
|
|
||||||
|
resp, err := rpc.AdminRPC().FindAdminFullname(rpc.Context(0), &pb.FindAdminFullnameRequest{AdminId: int64(this.AdminId)})
|
||||||
|
if err != nil {
|
||||||
|
utils.PrintError(err)
|
||||||
|
action.Data["teaUsername"] = ""
|
||||||
|
} else {
|
||||||
|
action.Data["teaUsername"] = resp.Fullname
|
||||||
|
}
|
||||||
|
|
||||||
|
action.Data["teaUserAvatar"] = ""
|
||||||
|
|
||||||
|
action.Data["teaMenu"] = ""
|
||||||
|
action.Data["teaModules"] = this.modules()
|
||||||
|
action.Data["teaSubMenus"] = []map[string]interface{}{}
|
||||||
|
action.Data["teaTabbar"] = []map[string]interface{}{}
|
||||||
|
action.Data["teaVersion"] = teaconst.Version
|
||||||
|
action.Data["teaIsSuper"] = false
|
||||||
|
action.Data["teaDemoEnabled"] = teaconst.IsDemo
|
||||||
|
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() []maps.Map {
|
||||||
|
return []maps.Map{
|
||||||
{
|
{
|
||||||
"code": "servers",
|
"code": "servers",
|
||||||
"name": "网站服务",
|
"name": "网站服务",
|
||||||
@@ -136,46 +182,9 @@ func (this *UserMustAuth) BeforeAction(actionPtr actions.ActionWrapper, paramNam
|
|||||||
"icon": "history",
|
"icon": "history",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
action.Data["teaTitle"] = teaconst.ProductNameZH
|
|
||||||
action.Data["teaName"] = teaconst.ProductNameZH
|
|
||||||
|
|
||||||
resp, err := rpc.AdminRPC().FindAdminFullname(rpc.Context(0), &pb.FindAdminFullnameRequest{AdminId: int64(this.AdminId)})
|
|
||||||
if err != nil {
|
|
||||||
utils.PrintError(err)
|
|
||||||
action.Data["teaUsername"] = ""
|
|
||||||
} else {
|
|
||||||
action.Data["teaUsername"] = resp.Fullname
|
|
||||||
}
|
|
||||||
|
|
||||||
action.Data["teaUserAvatar"] = ""
|
|
||||||
|
|
||||||
action.Data["teaMenu"] = ""
|
|
||||||
action.Data["teaModules"] = modules
|
|
||||||
action.Data["teaSubMenus"] = []map[string]interface{}{}
|
|
||||||
action.Data["teaTabbar"] = []map[string]interface{}{}
|
|
||||||
action.Data["teaVersion"] = teaconst.Version
|
|
||||||
action.Data["teaIsSuper"] = false
|
|
||||||
action.Data["teaDemoEnabled"] = teaconst.IsDemo
|
|
||||||
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) login(action *actions.ActionObject) {
|
func (this *UserMustAuth) login(action *actions.ActionObject) {
|
||||||
action.RedirectURL("/")
|
action.RedirectURL("/")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,12 @@ func (this *UserShouldAuth) BeforeAction(actionPtr actions.ActionWrapper, paramN
|
|||||||
}
|
}
|
||||||
action.AddHeader("Content-Security-Policy", "default-src 'self' data:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'")
|
action.AddHeader("Content-Security-Policy", "default-src 'self' data:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'")
|
||||||
|
|
||||||
|
// 检查IP
|
||||||
|
if !checkIP(securityConfig, action.RequestRemoteIP()) {
|
||||||
|
action.ResponseWriter.WriteHeader(http.StatusForbidden)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
60
internal/web/helpers/utils.go
Normal file
60
internal/web/helpers/utils.go
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package helpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
nodes "github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||||
|
"github.com/TeaOSLab/EdgeAdmin/internal/securitymanager"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
|
"github.com/iwind/TeaGo/lists"
|
||||||
|
"github.com/iwind/TeaGo/logs"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 检查用户IP
|
||||||
|
func checkIP(config *securitymanager.SecurityConfig, ipAddr string) bool {
|
||||||
|
if config == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 本地IP
|
||||||
|
ip := net.ParseIP(ipAddr).To4()
|
||||||
|
if ip == nil {
|
||||||
|
logs.Println("[USER_MUST_AUTH]invalid client address: " + ipAddr)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if config.AllowLocal && isLocalIP(ip) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查位置
|
||||||
|
if len(config.AllowCountryIds) > 0 || len(config.AllowProvinceIds) > 0 {
|
||||||
|
rpc, err := nodes.SharedRPC()
|
||||||
|
if err != nil {
|
||||||
|
logs.Println("[USER_MUST_AUTH][ERROR]" + err.Error())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
resp, err := rpc.IPLibraryRPC().LookupIPRegion(rpc.Context(0), &pb.LookupIPRegionRequest{Ip: ipAddr})
|
||||||
|
if err != nil {
|
||||||
|
logs.Println("[USER_MUST_AUTH][ERROR]" + err.Error())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if resp.Region == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if len(config.AllowCountryIds) > 0 && !lists.ContainsInt64(config.AllowCountryIds, resp.Region.CountryId) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if len(config.AllowProvinceIds) > 0 && !lists.ContainsInt64(config.AllowProvinceIds, resp.Region.ProvinceId) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断是否为本地IP
|
||||||
|
func isLocalIP(ip net.IP) bool {
|
||||||
|
return ip[0] == 127 ||
|
||||||
|
ip[0] == 10 ||
|
||||||
|
(ip[0] == 172 && ip[1]&0xf0 == 16) ||
|
||||||
|
(ip[0] == 192 && ip[1] == 168)
|
||||||
|
}
|
||||||
51
web/public/js/components/common/countries-selector.js
Normal file
51
web/public/js/components/common/countries-selector.js
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
Vue.component("countries-selector", {
|
||||||
|
props: ["v-countries"],
|
||||||
|
data: function () {
|
||||||
|
let countries = this.vCountries
|
||||||
|
if (countries == null) {
|
||||||
|
countries = []
|
||||||
|
}
|
||||||
|
let countryIds = countries.$map(function (k, v) {
|
||||||
|
return v.id
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
countries: countries,
|
||||||
|
countryIds: countryIds
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
add: function () {
|
||||||
|
let countryStringIds = this.countryIds.map(function (v) {
|
||||||
|
return v.toString()
|
||||||
|
})
|
||||||
|
let that = this
|
||||||
|
teaweb.popup("/ui/selectCountriesPopup?countryIds=" + countryStringIds.join(","), {
|
||||||
|
width: "48em",
|
||||||
|
height: "23em",
|
||||||
|
callback: function (resp) {
|
||||||
|
that.countries = resp.data.countries
|
||||||
|
that.change()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
remove: function (index) {
|
||||||
|
this.countries.$remove(index)
|
||||||
|
this.change()
|
||||||
|
},
|
||||||
|
change: function () {
|
||||||
|
this.countryIds = this.countries.$map(function (k, v) {
|
||||||
|
return v.id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
template: `<div>
|
||||||
|
<input type="hidden" name="countryIdsJSON" :value="JSON.stringify(countryIds)"/>
|
||||||
|
<div v-if="countries.length > 0" style="margin-bottom: 0.5em">
|
||||||
|
<div v-for="(country, index) in countries" class="ui label tiny">{{country.name}} <a href="" title="删除" @click.prevent="remove(index)"><i class="icon remove"></i></a></div>
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button class="ui button small" type="button" @click.prevent="add">+</button>
|
||||||
|
</div>
|
||||||
|
</div>`
|
||||||
|
})
|
||||||
51
web/public/js/components/common/provinces-selector.js
Normal file
51
web/public/js/components/common/provinces-selector.js
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
Vue.component("provinces-selector", {
|
||||||
|
props: ["v-provinces"],
|
||||||
|
data: function () {
|
||||||
|
let provinces = this.vProvinces
|
||||||
|
if (provinces == null) {
|
||||||
|
provinces = []
|
||||||
|
}
|
||||||
|
let provinceIds = provinces.$map(function (k, v) {
|
||||||
|
return v.id
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
provinces: provinces,
|
||||||
|
provinceIds: provinceIds
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
add: function () {
|
||||||
|
let provinceStringIds = this.provinceIds.map(function (v) {
|
||||||
|
return v.toString()
|
||||||
|
})
|
||||||
|
let that = this
|
||||||
|
teaweb.popup("/ui/selectProvincesPopup?provinceIds=" + provinceStringIds.join(","), {
|
||||||
|
width: "48em",
|
||||||
|
height: "23em",
|
||||||
|
callback: function (resp) {
|
||||||
|
that.provinces = resp.data.provinces
|
||||||
|
that.change()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
remove: function (index) {
|
||||||
|
this.provinces.$remove(index)
|
||||||
|
this.change()
|
||||||
|
},
|
||||||
|
change: function () {
|
||||||
|
this.provinceIds = this.provinces.$map(function (k, v) {
|
||||||
|
return v.id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
template: `<div>
|
||||||
|
<input type="hidden" name="provinceIdsJSON" :value="JSON.stringify(provinceIds)"/>
|
||||||
|
<div v-if="provinces.length > 0" style="margin-bottom: 0.5em">
|
||||||
|
<div v-for="(province, index) in provinces" class="ui label tiny">{{province.name}} <a href="" title="删除" @click.prevent="remove(index)"><i class="icon remove"></i></a></div>
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button class="ui button small" type="button" @click.prevent="add">+</button>
|
||||||
|
</div>
|
||||||
|
</div>`
|
||||||
|
})
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
{$TEA.VUE}
|
{$TEA.VUE}
|
||||||
{$echo "header"}
|
{$echo "header"}
|
||||||
<script type="text/javascript" src="/_/@default/@layout.js"></script>
|
<script type="text/javascript" src="/_/@default/@layout.js"></script>
|
||||||
<script type="text/javascript" src="/ui/components.js?v=1.0.0"></script>
|
<script type="text/javascript" src="/ui/components.js?v=v{$.teaVersion}"></script>
|
||||||
<script type="text/javascript" src="/js/utils.js"></script>
|
<script type="text/javascript" src="/js/utils.js"></script>
|
||||||
<script type="text/javascript" src="/js/sweetalert2/dist/sweetalert2.all.min.js"></script>
|
<script type="text/javascript" src="/js/sweetalert2/dist/sweetalert2.all.min.js"></script>
|
||||||
<script type="text/javascript" src="/js/date.tea.js"></script>
|
<script type="text/javascript" src="/js/date.tea.js"></script>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<link rel="stylesheet" type="text/css" href="/_/@default/@layout_override.css" media="all"/>
|
<link rel="stylesheet" type="text/css" href="/_/@default/@layout_override.css" media="all"/>
|
||||||
{$echo "header"}
|
{$echo "header"}
|
||||||
<script type="text/javascript" src="/_/@default/@layout.js"></script>
|
<script type="text/javascript" src="/_/@default/@layout.js"></script>
|
||||||
<script type="text/javascript" src="/ui/components.js"></script>
|
<script type="text/javascript" src="/ui/components.js?v=v{$.teaVersion}"></script>
|
||||||
<script type="text/javascript" src="/js/utils.js"></script>
|
<script type="text/javascript" src="/js/utils.js"></script>
|
||||||
<script type="text/javascript" src="/js/sweetalert2/dist/sweetalert2.all.min.js"></script>
|
<script type="text/javascript" src="/js/sweetalert2/dist/sweetalert2.all.min.js"></script>
|
||||||
<script type="text/javascript" src="/js/date.tea.js"></script>
|
<script type="text/javascript" src="/js/date.tea.js"></script>
|
||||||
|
|||||||
@@ -15,6 +15,27 @@
|
|||||||
<p class="comment">当前服务被别的网页框架嵌套的条件限制。</p>
|
<p class="comment">当前服务被别的网页框架嵌套的条件限制。</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>允许访问的国家和地区</td>
|
||||||
|
<td>
|
||||||
|
<countries-selector :v-countries="countries"></countries-selector>
|
||||||
|
<p class="comment">设置后,只有这些国家和地区才能访问管理界面,如果不设置表示没有限制。</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>允许访问的省份(中国)</td>
|
||||||
|
<td>
|
||||||
|
<provinces-selector :v-provinces="provinces"></provinces-selector>
|
||||||
|
<p class="comment">设置后,只有这些省份才能访问管理界面,如果不设置表示没有限制。</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>允许局域网访问</td>
|
||||||
|
<td>
|
||||||
|
<checkbox name="allowLocal" v-model="config.allowLocal"></checkbox>
|
||||||
|
<p class="comment">选中表示允许在本机和局域网访问。</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<submit-btn></submit-btn>
|
<submit-btn></submit-btn>
|
||||||
</form>
|
</form>
|
||||||
16
web/views/@default/ui/selectCountriesPopup.css
Normal file
16
web/views/@default/ui/selectCountriesPopup.css
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
.region-letter-group .item {
|
||||||
|
padding-left: 1em !important;
|
||||||
|
padding-right: 1em !important;
|
||||||
|
}
|
||||||
|
.country-group {
|
||||||
|
padding-bottom: 1em;
|
||||||
|
}
|
||||||
|
.country-group .country-list .item {
|
||||||
|
float: left;
|
||||||
|
width: 12em;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
.country-group .country-list .item .checkbox label {
|
||||||
|
font-size: 12px !important;
|
||||||
|
}
|
||||||
|
/*# sourceMappingURL=selectCountriesPopup.css.map */
|
||||||
1
web/views/@default/ui/selectCountriesPopup.css.map
Normal file
1
web/views/@default/ui/selectCountriesPopup.css.map
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"sources":["selectCountriesPopup.less"],"names":[],"mappings":"AAAA,oBACC;EACC,4BAAA;EACA,6BAAA;;AAIF;EAaC,mBAAA;;AAbD,cACC,cACC;EACC,WAAA;EACA,WAAA;EACA,oBAAA;;AALH,cACC,cACC,MAKC,UAAU;EACT,0BAAA","file":"selectCountriesPopup.css"}
|
||||||
27
web/views/@default/ui/selectCountriesPopup.html
Normal file
27
web/views/@default/ui/selectCountriesPopup.html
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{$layout "layout_popup"}
|
||||||
|
|
||||||
|
<h3>选择国家和地区</h3>
|
||||||
|
<form method="post" data-tea-success="success" data-tea-action="$" class="ui form">
|
||||||
|
<csrf-token></csrf-token>
|
||||||
|
<table class="ui table selectable">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<div class="ui menu tabular tiny region-letter-group">
|
||||||
|
<a href="" v-for="group in letterGroups" class="item" :class="{active: group == selectedGroup}" @click.prevent="selectGroup(group)">{{group}}</a>
|
||||||
|
</div>
|
||||||
|
<div v-for="group in letterGroups">
|
||||||
|
<div v-for="letter in group" v-show="letterCountries[letter] != null && group == selectedGroup" class="country-group">
|
||||||
|
<h4>{{letter}}</h4>
|
||||||
|
<div class="country-list">
|
||||||
|
<div class="item" v-for="country in letterCountries[letter]">
|
||||||
|
<checkbox name="countryIds" :v-value="country.id" v-model="country.isChecked">{{country.name}}</checkbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="clear"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<submit-btn></submit-btn>
|
||||||
|
</form>
|
||||||
21
web/views/@default/ui/selectCountriesPopup.js
Normal file
21
web/views/@default/ui/selectCountriesPopup.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
Tea.context(function () {
|
||||||
|
this.letterGroups = [
|
||||||
|
"ABC", "DEF", "GHI", "JKL", "MNO", "PQR", "STU", "VWX", "YZ"
|
||||||
|
];
|
||||||
|
this.selectedGroup = "ABC"
|
||||||
|
this.letterCountries = {}
|
||||||
|
let that = this
|
||||||
|
this.countries.forEach(function (country) {
|
||||||
|
if (typeof (that.letterCountries[country.letter]) == "undefined") {
|
||||||
|
that.letterCountries[country.letter] = []
|
||||||
|
}
|
||||||
|
that.letterCountries[country.letter].push(country)
|
||||||
|
})
|
||||||
|
this.isCheckingAll = false
|
||||||
|
|
||||||
|
this.selectGroup = function (group) {
|
||||||
|
this.selectedGroup = group
|
||||||
|
}
|
||||||
|
|
||||||
|
this.success = NotifyPopup
|
||||||
|
})
|
||||||
22
web/views/@default/ui/selectCountriesPopup.less
Normal file
22
web/views/@default/ui/selectCountriesPopup.less
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
.region-letter-group {
|
||||||
|
.item {
|
||||||
|
padding-left: 1em !important;
|
||||||
|
padding-right: 1em !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.country-group {
|
||||||
|
.country-list {
|
||||||
|
.item {
|
||||||
|
float: left;
|
||||||
|
width: 12em;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
|
||||||
|
.checkbox label {
|
||||||
|
font-size: 12px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
padding-bottom: 1em;
|
||||||
|
}
|
||||||
9
web/views/@default/ui/selectProvincesPopup.css
Normal file
9
web/views/@default/ui/selectProvincesPopup.css
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
.province-list .item {
|
||||||
|
float: left;
|
||||||
|
width: 12em;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
.province-list .item .checkbox label {
|
||||||
|
font-size: 12px !important;
|
||||||
|
}
|
||||||
|
/*# sourceMappingURL=selectProvincesPopup.css.map */
|
||||||
1
web/views/@default/ui/selectProvincesPopup.css.map
Normal file
1
web/views/@default/ui/selectProvincesPopup.css.map
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"sources":["selectProvincesPopup.less"],"names":[],"mappings":"AAAA,cACC;EACC,WAAA;EACA,WAAA;EACA,oBAAA;;AAJF,cACC,MAKC,UAAU;EACT,0BAAA","file":"selectProvincesPopup.css"}
|
||||||
19
web/views/@default/ui/selectProvincesPopup.html
Normal file
19
web/views/@default/ui/selectProvincesPopup.html
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{$layout "layout_popup"}
|
||||||
|
|
||||||
|
<h3>选择省份</h3>
|
||||||
|
|
||||||
|
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
|
||||||
|
<csrf-token></csrf-token>
|
||||||
|
<table class="ui table selectable">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<div class="province-list">
|
||||||
|
<div class="item" v-for="province in provinces">
|
||||||
|
<checkbox name="provinceIds" :v-value="province.id" v-model="province.isChecked">{{province.name}}</checkbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<submit-btn></submit-btn>
|
||||||
|
</form>
|
||||||
3
web/views/@default/ui/selectProvincesPopup.js
Normal file
3
web/views/@default/ui/selectProvincesPopup.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
Tea.context(function () {
|
||||||
|
this.success = NotifyPopup
|
||||||
|
})
|
||||||
11
web/views/@default/ui/selectProvincesPopup.less
Normal file
11
web/views/@default/ui/selectProvincesPopup.less
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
.province-list {
|
||||||
|
.item {
|
||||||
|
float: left;
|
||||||
|
width: 12em;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
|
||||||
|
.checkbox label {
|
||||||
|
font-size: 12px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user