mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-01-19 08:19:15 +08:00
feat: 实现域名绑定与授权 ip 功能 (#1089)
This commit is contained in:
parent
c2f5908a9d
commit
8fd4060562
@ -18,6 +18,8 @@ type SettingInfo struct {
|
|||||||
ServerPort string `json:"serverPort"`
|
ServerPort string `json:"serverPort"`
|
||||||
SSL string `json:"ssl"`
|
SSL string `json:"ssl"`
|
||||||
SSLType string `json:"sslType"`
|
SSLType string `json:"sslType"`
|
||||||
|
BindDomain string `json:"bindDomain"`
|
||||||
|
AllowIPs string `json:"allowIPs"`
|
||||||
SecurityEntrance string `json:"securityEntrance"`
|
SecurityEntrance string `json:"securityEntrance"`
|
||||||
ExpirationDays string `json:"expirationDays"`
|
ExpirationDays string `json:"expirationDays"`
|
||||||
ExpirationTime string `json:"expirationTime"`
|
ExpirationTime string `json:"expirationTime"`
|
||||||
|
@ -67,6 +67,12 @@ func (u *SettingService) Update(key, value string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if key == "BindDomain" {
|
||||||
|
global.CONF.System.BindDomain = value
|
||||||
|
}
|
||||||
|
if key == "AllowIPs" {
|
||||||
|
global.CONF.System.AllowIPs = value
|
||||||
|
}
|
||||||
if err := settingRepo.Update(key, value); err != nil {
|
if err := settingRepo.Update(key, value); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -21,4 +21,6 @@ type System struct {
|
|||||||
IsDemo bool `mapstructure:"is_demo"`
|
IsDemo bool `mapstructure:"is_demo"`
|
||||||
AppRepo string `mapstructure:"app_repo"`
|
AppRepo string `mapstructure:"app_repo"`
|
||||||
ChangeUserInfo bool `mapstructure:"change_user_info"`
|
ChangeUserInfo bool `mapstructure:"change_user_info"`
|
||||||
|
AllowIPs string `mapstructure:"allow_ips"`
|
||||||
|
BindDomain string `mapstructure:"bind_domain"`
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,8 @@ const (
|
|||||||
CodePasswordExpired = 405
|
CodePasswordExpired = 405
|
||||||
CodeAuth = 406
|
CodeAuth = 406
|
||||||
CodeGlobalLoading = 407
|
CodeGlobalLoading = 407
|
||||||
|
CodeErrIP = 408
|
||||||
|
CodeErrDomain = 409
|
||||||
CodeErrInternalServer = 500
|
CodeErrInternalServer = 500
|
||||||
CodeErrHeader = 406
|
CodeErrHeader = 406
|
||||||
)
|
)
|
||||||
|
@ -26,6 +26,18 @@ func Init() {
|
|||||||
}
|
}
|
||||||
global.CONF.System.SSL = sslSetting.Value
|
global.CONF.System.SSL = sslSetting.Value
|
||||||
|
|
||||||
|
ipsSetting, err := settingRepo.Get(settingRepo.WithByKey("AllowIPs"))
|
||||||
|
if err != nil {
|
||||||
|
global.LOG.Errorf("load allow ips from setting failed, err: %v", err)
|
||||||
|
}
|
||||||
|
global.CONF.System.AllowIPs = ipsSetting.Value
|
||||||
|
|
||||||
|
domainSetting, err := settingRepo.Get(settingRepo.WithByKey("BindDomain"))
|
||||||
|
if err != nil {
|
||||||
|
global.LOG.Errorf("load bind domain from setting failed, err: %v", err)
|
||||||
|
}
|
||||||
|
global.CONF.System.BindDomain = domainSetting.Value
|
||||||
|
|
||||||
if _, err := settingRepo.Get(settingRepo.WithByKey("SystemStatus")); err != nil {
|
if _, err := settingRepo.Get(settingRepo.WithByKey("SystemStatus")); err != nil {
|
||||||
_ = settingRepo.Create("SystemStatus", "Free")
|
_ = settingRepo.Create("SystemStatus", "Free")
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,7 @@ func Init() {
|
|||||||
migrations.AddEntranceAndSSL,
|
migrations.AddEntranceAndSSL,
|
||||||
migrations.UpdateTableSetting,
|
migrations.UpdateTableSetting,
|
||||||
migrations.UpdateTableAppDetail,
|
migrations.UpdateTableAppDetail,
|
||||||
|
migrations.AddBindAndAllowIPs,
|
||||||
})
|
})
|
||||||
if err := m.Migrate(); err != nil {
|
if err := m.Migrate(); err != nil {
|
||||||
global.LOG.Error(err)
|
global.LOG.Error(err)
|
||||||
|
@ -335,3 +335,16 @@ var UpdateTableAppDetail = &gormigrate.Migration{
|
|||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var AddBindAndAllowIPs = &gormigrate.Migration{
|
||||||
|
ID: "20230414-add-bind-and-allow",
|
||||||
|
Migrate: func(tx *gorm.DB) error {
|
||||||
|
if err := tx.Create(&model.Setting{Key: "BindDomain", Value: ""}).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := tx.Create(&model.Setting{Key: "AllowIPs", Value: ""}).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
@ -63,6 +63,8 @@ func Routers() *gin.Engine {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
PrivateGroup := Router.Group("/api/v1")
|
PrivateGroup := Router.Group("/api/v1")
|
||||||
|
PrivateGroup.Use(middleware.WhiteAllow())
|
||||||
|
PrivateGroup.Use(middleware.BindDomain())
|
||||||
PrivateGroup.Use(middleware.GlobalLoading())
|
PrivateGroup.Use(middleware.GlobalLoading())
|
||||||
{
|
{
|
||||||
systemRouter.InitBaseRouter(PrivateGroup)
|
systemRouter.InitBaseRouter(PrivateGroup)
|
||||||
|
31
backend/middleware/bind_domain.go
Normal file
31
backend/middleware/bind_domain.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/global"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BindDomain() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
if len(global.CONF.System.BindDomain) == 0 {
|
||||||
|
c.Next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
domains := c.Request.Host
|
||||||
|
parts := strings.Split(c.Request.Host, ":")
|
||||||
|
if len(parts) > 0 {
|
||||||
|
domains = parts[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
if domains != global.CONF.System.BindDomain {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrDomain, constant.ErrTypeInternalServer, errors.New("domain not allowed"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
28
backend/middleware/ip_limit.go
Normal file
28
backend/middleware/ip_limit.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/global"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func WhiteAllow() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
if len(global.CONF.System.AllowIPs) == 0 {
|
||||||
|
c.Next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
clientIP := c.ClientIP()
|
||||||
|
for _, ip := range strings.Split(global.CONF.System.AllowIPs, ",") {
|
||||||
|
if len(ip) != 0 && ip == clientIP {
|
||||||
|
c.Next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrIP, constant.ErrTypeInternalServer, errors.New("IP address not allowed"))
|
||||||
|
}
|
||||||
|
}
|
2
frontend/components.d.ts
vendored
2
frontend/components.d.ts
vendored
@ -71,6 +71,8 @@ declare module 'vue' {
|
|||||||
ElTag: typeof import('element-plus/es')['ElTag']
|
ElTag: typeof import('element-plus/es')['ElTag']
|
||||||
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
||||||
ElUpload: typeof import('element-plus/es')['ElUpload']
|
ElUpload: typeof import('element-plus/es')['ElUpload']
|
||||||
|
Err_domain: typeof import('./src/components/error-message/err_domain.vue')['default']
|
||||||
|
Err_ip: typeof import('./src/components/error-message/err_ip.vue')['default']
|
||||||
FileList: typeof import('./src/components/file-list/index.vue')['default']
|
FileList: typeof import('./src/components/file-list/index.vue')['default']
|
||||||
FileRole: typeof import('./src/components/file-role/index.vue')['default']
|
FileRole: typeof import('./src/components/file-role/index.vue')['default']
|
||||||
Footer: typeof import('./src/components/app-layout/footer/index.vue')['default']
|
Footer: typeof import('./src/components/app-layout/footer/index.vue')['default']
|
||||||
|
@ -49,9 +49,21 @@ class RequestHttp {
|
|||||||
});
|
});
|
||||||
return Promise.reject(data);
|
return Promise.reject(data);
|
||||||
}
|
}
|
||||||
if (data.code == ResultEnum.EXPIRED) {
|
if (data.code == ResultEnum.ERRIP) {
|
||||||
router.push({ name: 'Expired' });
|
globalStore.setLogStatus(false);
|
||||||
return data;
|
router.push({
|
||||||
|
name: 'entrance',
|
||||||
|
params: { code: 'err-ip' },
|
||||||
|
});
|
||||||
|
return Promise.reject(data);
|
||||||
|
}
|
||||||
|
if (data.code == ResultEnum.ERRDOMAIN) {
|
||||||
|
globalStore.setLogStatus(false);
|
||||||
|
router.push({
|
||||||
|
name: 'entrance',
|
||||||
|
params: { code: 'err-domain' },
|
||||||
|
});
|
||||||
|
return Promise.reject(data);
|
||||||
}
|
}
|
||||||
if (data.code == ResultEnum.ERRGLOBALLOADDING) {
|
if (data.code == ResultEnum.ERRGLOBALLOADDING) {
|
||||||
globalStore.setGlobalLoading(true);
|
globalStore.setGlobalLoading(true);
|
||||||
|
@ -17,6 +17,8 @@ export namespace Setting {
|
|||||||
serverPort: number;
|
serverPort: number;
|
||||||
ssl: string;
|
ssl: string;
|
||||||
sslType: string;
|
sslType: string;
|
||||||
|
allowIPs: string;
|
||||||
|
bindDomain: string;
|
||||||
securityEntrance: string;
|
securityEntrance: string;
|
||||||
expirationDays: number;
|
expirationDays: number;
|
||||||
expirationTime: string;
|
expirationTime: string;
|
||||||
|
66
frontend/src/components/error-message/err_domain.vue
Normal file
66
frontend/src/components/error-message/err_domain.vue
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<template>
|
||||||
|
<div class="not-container">
|
||||||
|
<img src="@/assets/images/unsafe.svg" class="not-img" alt="404" />
|
||||||
|
<div class="not-detail">
|
||||||
|
<h2>{{ $t('commons.login.notSafe') }}</h2>
|
||||||
|
<h4>{{ $t('commons.login.errDomain1') }}</h4>
|
||||||
|
<div>
|
||||||
|
<h4>{{ $t('commons.login.errHelper') }} 1pctl reset-domain</h4>
|
||||||
|
<div style="cursor: pointer; float: left">
|
||||||
|
<el-icon color="#409EFC" style="margin-left: 5px; margin-top: 33px" :size="18" @click="onCopy()">
|
||||||
|
<DocumentCopy />
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts" name="404">
|
||||||
|
import i18n from '@/lang';
|
||||||
|
import { MsgSuccess } from '@/utils/message';
|
||||||
|
|
||||||
|
const onCopy = () => {
|
||||||
|
let input = document.createElement('input');
|
||||||
|
input.value = '1pctl reset-domain';
|
||||||
|
document.body.appendChild(input);
|
||||||
|
input.select();
|
||||||
|
document.execCommand('Copy');
|
||||||
|
document.body.removeChild(input);
|
||||||
|
MsgSuccess(i18n.global.t('commons.msg.copySuccess'));
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.not-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
.not-img {
|
||||||
|
margin-top: 300px;
|
||||||
|
}
|
||||||
|
.not-detail {
|
||||||
|
margin-top: 300px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
h2,
|
||||||
|
h4 {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
font-size: 60px;
|
||||||
|
color: #434e59;
|
||||||
|
}
|
||||||
|
h4 {
|
||||||
|
margin: 30px 0 20px;
|
||||||
|
float: left;
|
||||||
|
font-size: 19px;
|
||||||
|
font-weight: normal;
|
||||||
|
color: #848587;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
65
frontend/src/components/error-message/err_ip.vue
Normal file
65
frontend/src/components/error-message/err_ip.vue
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<template>
|
||||||
|
<div class="not-container">
|
||||||
|
<img src="@/assets/images/unsafe.svg" class="not-img" alt="404" />
|
||||||
|
<div class="not-detail">
|
||||||
|
<h2>{{ $t('commons.login.notSafe') }}</h2>
|
||||||
|
<h4>{{ $t('commons.login.errIP1') }}</h4>
|
||||||
|
<div>
|
||||||
|
<h4>{{ $t('commons.login.errHelper') }} 1pctl reset-ips</h4>
|
||||||
|
<div style="cursor: pointer; float: left">
|
||||||
|
<el-icon color="#409EFC" style="margin-left: 5px; margin-top: 33px" :size="18" @click="onCopy()">
|
||||||
|
<DocumentCopy />
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts" name="404">
|
||||||
|
import i18n from '@/lang';
|
||||||
|
import { MsgSuccess } from '@/utils/message';
|
||||||
|
|
||||||
|
const onCopy = () => {
|
||||||
|
let input = document.createElement('input');
|
||||||
|
input.value = '1pctl reset-ips';
|
||||||
|
document.body.appendChild(input);
|
||||||
|
input.select();
|
||||||
|
document.execCommand('Copy');
|
||||||
|
document.body.removeChild(input);
|
||||||
|
MsgSuccess(i18n.global.t('commons.msg.copySuccess'));
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.not-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
.not-img {
|
||||||
|
margin-top: 300px;
|
||||||
|
}
|
||||||
|
.not-detail {
|
||||||
|
margin-top: 300px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
h2,
|
||||||
|
h4 {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
font-size: 60px;
|
||||||
|
color: #434e59;
|
||||||
|
}
|
||||||
|
h4 {
|
||||||
|
margin: 30px 0 20px;
|
||||||
|
float: left;
|
||||||
|
font-size: 19px;
|
||||||
|
font-weight: normal;
|
||||||
|
color: #848587;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -7,6 +7,8 @@ export enum ResultEnum {
|
|||||||
EXPIRED = 405,
|
EXPIRED = 405,
|
||||||
ERRAUTH = 406,
|
ERRAUTH = 406,
|
||||||
ERRGLOBALLOADDING = 407,
|
ERRGLOBALLOADDING = 407,
|
||||||
|
ERRIP = 408,
|
||||||
|
ERRDOMAIN = 409,
|
||||||
TIMEOUT = 20000,
|
TIMEOUT = 20000,
|
||||||
TYPE = 'success',
|
TYPE = 'success',
|
||||||
}
|
}
|
||||||
|
@ -115,6 +115,9 @@ const message = {
|
|||||||
notSafe: 'Access Denied',
|
notSafe: 'Access Denied',
|
||||||
safeEntrance1: 'The secure login has been enabled in the current environment',
|
safeEntrance1: 'The secure login has been enabled in the current environment',
|
||||||
safeEntrance2: 'Enter the following command on the SSH terminal to view the panel entry: 1pctl user-info',
|
safeEntrance2: 'Enter the following command on the SSH terminal to view the panel entry: 1pctl user-info',
|
||||||
|
errIP1: 'Authorized IP address access is enabled in the current environment',
|
||||||
|
errDomain1: 'Access domain name binding is enabled in the current environment',
|
||||||
|
errHelper: 'To reset the binding information, run the following command on the SSH terminal: ',
|
||||||
codeInput: 'Please enter the 6-digit verification code of the MFA validator',
|
codeInput: 'Please enter the 6-digit verification code of the MFA validator',
|
||||||
mfaTitle: 'MFA Certification',
|
mfaTitle: 'MFA Certification',
|
||||||
mfaCode: 'MFA verification code',
|
mfaCode: 'MFA verification code',
|
||||||
@ -933,6 +936,19 @@ const message = {
|
|||||||
complexity: 'Complexity verification',
|
complexity: 'Complexity verification',
|
||||||
complexityHelper:
|
complexityHelper:
|
||||||
'The password must contain at least eight characters and contain at least three uppercase letters, lowercase letters, digits, and special characters',
|
'The password must contain at least eight characters and contain at least three uppercase letters, lowercase letters, digits, and special characters',
|
||||||
|
|
||||||
|
bindDomain: 'Bind domain',
|
||||||
|
bindDomainHelper:
|
||||||
|
'After the domain binding, only the domain in the setting can be used to access 1Panel service',
|
||||||
|
bindDomainHelper1: 'If the binding domain is empty, the binding of the domain is cancelled',
|
||||||
|
bindDomainWarnning:
|
||||||
|
'If the binding domain is empty, the binding of the domain is cancelled. Do you want to continue?',
|
||||||
|
allowIPs: 'Authorized IP',
|
||||||
|
allowIPsHelper:
|
||||||
|
'After setting the authorized IP address, only the IP address in the setting can access the 1Panel service',
|
||||||
|
allowIPsWarnning:
|
||||||
|
'设After setting the authorized IP address, only the IP address in the setting can access the 1Panel service. Do you want to continue?',
|
||||||
|
allowIPsHelper1: 'If the authorized IP address is empty, the authorized IP address is canceled',
|
||||||
mfa: 'MFA',
|
mfa: 'MFA',
|
||||||
mfaAlert:
|
mfaAlert:
|
||||||
'MFA password is generated based on the current time. Please ensure that the server time is synchronized.',
|
'MFA password is generated based on the current time. Please ensure that the server time is synchronized.',
|
||||||
|
@ -119,6 +119,9 @@ const message = {
|
|||||||
notSafe: '暂无权限访问',
|
notSafe: '暂无权限访问',
|
||||||
safeEntrance1: '当前环境已经开启了安全入口登录',
|
safeEntrance1: '当前环境已经开启了安全入口登录',
|
||||||
safeEntrance2: '在 SSH 终端输入以下命令来查看面板入口: 1pctl user-info',
|
safeEntrance2: '在 SSH 终端输入以下命令来查看面板入口: 1pctl user-info',
|
||||||
|
errIP1: '当前环境已经开启了授权 IP 访问',
|
||||||
|
errDomain1: '当前环境已经开启了访问域名绑定',
|
||||||
|
errHelper: '可在 SSH 终端输入以下命令来重置绑定信息: ',
|
||||||
codeInput: '请输入 MFA 验证器的 6 位验证码',
|
codeInput: '请输入 MFA 验证器的 6 位验证码',
|
||||||
mfaTitle: 'MFA 认证',
|
mfaTitle: 'MFA 认证',
|
||||||
mfaCode: 'MFA 验证码',
|
mfaCode: 'MFA 验证码',
|
||||||
@ -965,6 +968,14 @@ const message = {
|
|||||||
timeoutHelper: '【 {0} 天后 】面板密码即将过期,过期后需要重新设置密码',
|
timeoutHelper: '【 {0} 天后 】面板密码即将过期,过期后需要重新设置密码',
|
||||||
complexity: '密码复杂度验证',
|
complexity: '密码复杂度验证',
|
||||||
complexityHelper: '开启后密码必须满足密码长度大于 8 位且包含字母、数字及特殊字符',
|
complexityHelper: '开启后密码必须满足密码长度大于 8 位且包含字母、数字及特殊字符',
|
||||||
|
bindDomain: '域名绑定',
|
||||||
|
bindDomainHelper: '设置域名绑定后,仅能通过设置中域名访问 1Panel 服务',
|
||||||
|
bindDomainHelper1: '绑定域名为空时,则取消域名绑定',
|
||||||
|
bindDomainWarnning: '设置域名绑定后,仅能通过设置中域名访问 1Panel 服务,是否继续?',
|
||||||
|
allowIPs: '授权 IP',
|
||||||
|
allowIPsHelper: '设置授权 IP 后,仅有设置中的 IP 可以访问 1Panel 服务',
|
||||||
|
allowIPsWarnning: '设置授权 IP 后,仅有设置中的 IP 可以访问 1Panel 服务,是否继续?',
|
||||||
|
allowIPsHelper1: '授权 IP 为空时,则取消授权 IP',
|
||||||
mfa: '两步验证',
|
mfa: '两步验证',
|
||||||
mfaAlert: '两步验证密码是基于当前时间生成,请确保服务器时间已同步',
|
mfaAlert: '两步验证密码是基于当前时间生成,请确保服务器时间已同步',
|
||||||
mfaHelper: '开启后会验证手机应用验证码',
|
mfaHelper: '开启后会验证手机应用验证码',
|
||||||
|
@ -158,6 +158,9 @@ export function getIcon(extention: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function checkIp(value: string): boolean {
|
export function checkIp(value: string): boolean {
|
||||||
|
if (value === '') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
const reg =
|
const reg =
|
||||||
/^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/;
|
/^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/;
|
||||||
if (!reg.test(value) && value !== '') {
|
if (!reg.test(value) && value !== '') {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="login-backgroud" v-if="isSafety">
|
<div class="login-backgroud" v-if="isSafety && !isErr">
|
||||||
<div class="login-wrapper">
|
<div class="login-wrapper">
|
||||||
<div :class="screenWidth > 1110 ? 'left inline-block' : ''">
|
<div :class="screenWidth > 1110 ? 'left inline-block' : ''">
|
||||||
<div class="login-title">
|
<div class="login-title">
|
||||||
@ -15,9 +15,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!isSafety">
|
<div v-if="!isSafety && !isErr">
|
||||||
<UnSafe />
|
<UnSafe />
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="isErr && mySafetyCode.code === 'err-ip'">
|
||||||
|
<ErrIP />
|
||||||
|
</div>
|
||||||
|
<div v-if="isErr && mySafetyCode.code === 'err-domain'">
|
||||||
|
<ErrDomain />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -25,12 +31,15 @@
|
|||||||
import { checkIsSafety } from '@/api/modules/auth';
|
import { checkIsSafety } from '@/api/modules/auth';
|
||||||
import LoginForm from '../components/login-form.vue';
|
import LoginForm from '../components/login-form.vue';
|
||||||
import UnSafe from '@/components/error-message/unsafe.vue';
|
import UnSafe from '@/components/error-message/unsafe.vue';
|
||||||
|
import ErrIP from '@/components/error-message/err_ip.vue';
|
||||||
|
import ErrDomain from '@/components/error-message/err_domain.vue';
|
||||||
import { ref, onMounted } from 'vue';
|
import { ref, onMounted } from 'vue';
|
||||||
import { GlobalStore } from '@/store';
|
import { GlobalStore } from '@/store';
|
||||||
const globalStore = GlobalStore();
|
const globalStore = GlobalStore();
|
||||||
|
|
||||||
const isSafety = ref(true);
|
const isSafety = ref(true);
|
||||||
const screenWidth = ref(null);
|
const screenWidth = ref(null);
|
||||||
|
const isErr = ref();
|
||||||
|
|
||||||
const mySafetyCode = defineProps({
|
const mySafetyCode = defineProps({
|
||||||
code: {
|
code: {
|
||||||
@ -41,7 +50,13 @@ const mySafetyCode = defineProps({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const getStatus = async () => {
|
const getStatus = async () => {
|
||||||
|
if (mySafetyCode.code === 'err-ip' || mySafetyCode.code === 'err-domain') {
|
||||||
|
isErr.value = true;
|
||||||
|
}
|
||||||
const res = await checkIsSafety(mySafetyCode.code);
|
const res = await checkIsSafety(mySafetyCode.code);
|
||||||
|
if (mySafetyCode.code === 'err-ip' || mySafetyCode.code === 'err-domain') {
|
||||||
|
isErr.value = false;
|
||||||
|
}
|
||||||
isSafety.value = res.data;
|
isSafety.value = res.data;
|
||||||
if (isSafety.value) {
|
if (isSafety.value) {
|
||||||
globalStore.entrance = mySafetyCode.code;
|
globalStore.entrance = mySafetyCode.code;
|
||||||
|
133
frontend/src/views/setting/safe/allowips/index.vue
Normal file
133
frontend/src/views/setting/safe/allowips/index.vue
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-drawer v-model="drawerVisiable" :destroy-on-close="true" :close-on-click-modal="false" size="30%">
|
||||||
|
<template #header>
|
||||||
|
<DrawerHeader :header="$t('setting.allowIPs')" :back="handleClose" />
|
||||||
|
</template>
|
||||||
|
<el-form label-position="top" @submit.prevent v-loading="loading">
|
||||||
|
<el-row type="flex" justify="center">
|
||||||
|
<el-col :span="22">
|
||||||
|
<el-form-item>
|
||||||
|
<table style="width: 100%" class="tab-table">
|
||||||
|
<tr v-if="allowIPs.length !== 0">
|
||||||
|
<th scope="col" width="90%" align="left">
|
||||||
|
<label>IP</label>
|
||||||
|
</th>
|
||||||
|
<th align="left"></th>
|
||||||
|
</tr>
|
||||||
|
<tr v-for="(row, index) in allowIPs" :key="index">
|
||||||
|
<td width="90%">
|
||||||
|
<el-input
|
||||||
|
:placeholder="$t('container.serverExample')"
|
||||||
|
style="width: 100%"
|
||||||
|
v-model="row.value"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<el-button link style="font-size: 10px" @click="handlePortsDelete(index)">
|
||||||
|
{{ $t('commons.button.delete') }}
|
||||||
|
</el-button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="left">
|
||||||
|
<el-button @click="handlePortsAdd()">{{ $t('commons.button.add') }}</el-button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<span class="input-help">{{ $t('setting.allowIPsHelper1') }}</span>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click="drawerVisiable = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||||
|
<el-button :disabled="loading" type="primary" @click="onSavePort()">
|
||||||
|
{{ $t('commons.button.confirm') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import i18n from '@/lang';
|
||||||
|
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||||
|
import { updateSetting } from '@/api/modules/setting';
|
||||||
|
import { ElMessageBox } from 'element-plus';
|
||||||
|
import { checkIp } from '@/utils/util';
|
||||||
|
|
||||||
|
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||||
|
|
||||||
|
const allowIPs = ref();
|
||||||
|
interface DialogProps {
|
||||||
|
allowIPs: string;
|
||||||
|
}
|
||||||
|
const drawerVisiable = ref();
|
||||||
|
const loading = ref();
|
||||||
|
|
||||||
|
const acceptParams = (params: DialogProps): void => {
|
||||||
|
allowIPs.value = [];
|
||||||
|
if (params.allowIPs) {
|
||||||
|
for (const ip of params.allowIPs.split(',')) {
|
||||||
|
if (ip) {
|
||||||
|
allowIPs.value.push({ value: ip });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
drawerVisiable.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePortsAdd = () => {
|
||||||
|
let item = {
|
||||||
|
value: '',
|
||||||
|
};
|
||||||
|
allowIPs.value.push(item);
|
||||||
|
};
|
||||||
|
const handlePortsDelete = (index: number) => {
|
||||||
|
allowIPs.value.splice(index, 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSavePort = async () => {
|
||||||
|
let allows = '';
|
||||||
|
if (allowIPs.value.length !== 0) {
|
||||||
|
for (const ip of allowIPs.value) {
|
||||||
|
if (checkIp(ip.value)) {
|
||||||
|
MsgError(i18n.global.t('firewall.addressFormatError'));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
allows += ip.value + ',';
|
||||||
|
}
|
||||||
|
allows = allows.substring(0, allows.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
ElMessageBox.confirm(i18n.global.t('setting.allowIPsHelper'), i18n.global.t('setting.allowIPs'), {
|
||||||
|
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||||
|
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||||
|
type: 'info',
|
||||||
|
}).then(async () => {
|
||||||
|
loading.value = true;
|
||||||
|
|
||||||
|
await updateSetting({ key: 'AllowIPs', value: allows })
|
||||||
|
.then(() => {
|
||||||
|
loading.value = false;
|
||||||
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
emit('search');
|
||||||
|
handleClose();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
drawerVisiable.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
acceptParams,
|
||||||
|
});
|
||||||
|
</script>
|
106
frontend/src/views/setting/safe/domain/index.vue
Normal file
106
frontend/src/views/setting/safe/domain/index.vue
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-drawer v-model="drawerVisiable" :destroy-on-close="true" :close-on-click-modal="false" size="30%">
|
||||||
|
<template #header>
|
||||||
|
<DrawerHeader :header="$t('setting.bindDomain')" :back="handleClose" />
|
||||||
|
</template>
|
||||||
|
<el-form
|
||||||
|
ref="formRef"
|
||||||
|
label-position="top"
|
||||||
|
:rules="rules"
|
||||||
|
:model="form"
|
||||||
|
@submit.prevent
|
||||||
|
v-loading="loading"
|
||||||
|
>
|
||||||
|
<el-row type="flex" justify="center">
|
||||||
|
<el-col :span="22">
|
||||||
|
<el-form-item :label="$t('setting.bindDomain')" prop="bindDomain">
|
||||||
|
<el-input clearable v-model="form.bindDomain" />
|
||||||
|
<span class="input-help">{{ $t('setting.bindDomainHelper1') }}</span>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click="drawerVisiable = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||||
|
<el-button :disabled="loading" type="primary" @click="onSavePort(formRef)">
|
||||||
|
{{ $t('commons.button.confirm') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { reactive, ref } from 'vue';
|
||||||
|
import i18n from '@/lang';
|
||||||
|
import { MsgSuccess } from '@/utils/message';
|
||||||
|
import { updateSetting } from '@/api/modules/setting';
|
||||||
|
import { ElMessageBox, FormInstance } from 'element-plus';
|
||||||
|
|
||||||
|
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||||
|
|
||||||
|
interface DialogProps {
|
||||||
|
bindDomain: string;
|
||||||
|
}
|
||||||
|
const drawerVisiable = ref();
|
||||||
|
const loading = ref();
|
||||||
|
|
||||||
|
const form = reactive({
|
||||||
|
bindDomain: '',
|
||||||
|
});
|
||||||
|
const rules = reactive({
|
||||||
|
bindDomain: [{ validator: checkSecurityEntrance, trigger: 'blur' }],
|
||||||
|
});
|
||||||
|
|
||||||
|
function checkSecurityEntrance(rule: any, value: any, callback: any) {
|
||||||
|
if (form.bindDomain !== '') {
|
||||||
|
const reg =
|
||||||
|
/^([\w\u4e00-\u9fa5\-\*]{1,100}\.){1,10}([\w\u4e00-\u9fa5\-]{1,24}|[\w\u4e00-\u9fa5\-]{1,24}\.[\w\u4e00-\u9fa5\-]{1,24})$/;
|
||||||
|
if (!reg.test(form.bindDomain)) {
|
||||||
|
return callback(new Error(i18n.global.t('commons.rule.domain')));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
const formRef = ref<FormInstance>();
|
||||||
|
|
||||||
|
const acceptParams = (params: DialogProps): void => {
|
||||||
|
form.bindDomain = params.bindDomain;
|
||||||
|
drawerVisiable.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSavePort = async (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return;
|
||||||
|
formEl.validate(async (valid) => {
|
||||||
|
if (!valid) return;
|
||||||
|
ElMessageBox.confirm(i18n.global.t('setting.bindDomainWarnning'), i18n.global.t('setting.bindDomain'), {
|
||||||
|
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||||
|
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||||
|
type: 'info',
|
||||||
|
}).then(async () => {
|
||||||
|
loading.value = true;
|
||||||
|
await updateSetting({ key: 'BindDomain', value: form.bindDomain })
|
||||||
|
.then(() => {
|
||||||
|
loading.value = false;
|
||||||
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
emit('search');
|
||||||
|
handleClose();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
drawerVisiable.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
acceptParams,
|
||||||
|
});
|
||||||
|
</script>
|
@ -39,6 +39,42 @@
|
|||||||
<span class="input-help">{{ $t('setting.entranceHelper') }}</span>
|
<span class="input-help">{{ $t('setting.entranceHelper') }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item :label="$t('setting.allowIPs')">
|
||||||
|
<el-input v-if="form.allowIPs" disabled v-model="form.allowIPs">
|
||||||
|
<template #append>
|
||||||
|
<el-button @click="onChangeAllowIPs" icon="Setting">
|
||||||
|
{{ $t('commons.button.set') }}
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
<el-input disabled v-if="!form.allowIPs" v-model="unset">
|
||||||
|
<template #append>
|
||||||
|
<el-button @click="onChangeAllowIPs" icon="Setting">
|
||||||
|
{{ $t('commons.button.set') }}
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
<span class="input-help">{{ $t('setting.allowIPsHelper') }}</span>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item :label="$t('setting.bindDomain')">
|
||||||
|
<el-input disabled v-if="form.bindDomain" v-model="form.bindDomain">
|
||||||
|
<template #append>
|
||||||
|
<el-button @click="onChangeBindDomain" icon="Setting">
|
||||||
|
{{ $t('commons.button.set') }}
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
<el-input disabled v-if="!form.bindDomain" v-model="unset">
|
||||||
|
<template #append>
|
||||||
|
<el-button @click="onChangeBindDomain" icon="Setting">
|
||||||
|
{{ $t('commons.button.set') }}
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
<span class="input-help">{{ $t('setting.bindDomainHelper') }}</span>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item :label="$t('setting.expirationTime')" prop="expirationTime">
|
<el-form-item :label="$t('setting.expirationTime')" prop="expirationTime">
|
||||||
<el-input disabled v-model="form.expirationTime">
|
<el-input disabled v-model="form.expirationTime">
|
||||||
<template #append>
|
<template #append>
|
||||||
@ -111,6 +147,8 @@
|
|||||||
<SSLSetting ref="sslRef" @search="search" />
|
<SSLSetting ref="sslRef" @search="search" />
|
||||||
<EntranceSetting ref="entranceRef" @search="search" />
|
<EntranceSetting ref="entranceRef" @search="search" />
|
||||||
<TimeoutSetting ref="timeoutref" @search="search" />
|
<TimeoutSetting ref="timeoutref" @search="search" />
|
||||||
|
<DomainSetting ref="domainRef" @search="search" />
|
||||||
|
<AllowIPsSetting ref="allowIPsRef" @search="search" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -123,6 +161,8 @@ import SSLSetting from '@/views/setting/safe/ssl/index.vue';
|
|||||||
import MfaSetting from '@/views/setting/safe/mfa/index.vue';
|
import MfaSetting from '@/views/setting/safe/mfa/index.vue';
|
||||||
import TimeoutSetting from '@/views/setting/safe/timeout/index.vue';
|
import TimeoutSetting from '@/views/setting/safe/timeout/index.vue';
|
||||||
import EntranceSetting from '@/views/setting/safe/entrance/index.vue';
|
import EntranceSetting from '@/views/setting/safe/entrance/index.vue';
|
||||||
|
import DomainSetting from '@/views/setting/safe/domain/index.vue';
|
||||||
|
import AllowIPsSetting from '@/views/setting/safe/allowips/index.vue';
|
||||||
import { updateSetting, getSettingInfo, getSystemAvailable, updateSSL, loadSSLInfo } from '@/api/modules/setting';
|
import { updateSetting, getSettingInfo, getSystemAvailable, updateSSL, loadSSLInfo } from '@/api/modules/setting';
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
import { MsgSuccess } from '@/utils/message';
|
import { MsgSuccess } from '@/utils/message';
|
||||||
@ -136,6 +176,8 @@ const mfaRef = ref();
|
|||||||
|
|
||||||
const sslRef = ref();
|
const sslRef = ref();
|
||||||
const sslInfo = ref<Setting.SSLInfo>();
|
const sslInfo = ref<Setting.SSLInfo>();
|
||||||
|
const domainRef = ref();
|
||||||
|
const allowIPsRef = ref();
|
||||||
|
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
serverPort: 9999,
|
serverPort: 9999,
|
||||||
@ -146,6 +188,8 @@ const form = reactive({
|
|||||||
expirationTime: '',
|
expirationTime: '',
|
||||||
complexityVerification: 'disable',
|
complexityVerification: 'disable',
|
||||||
mfaStatus: 'disable',
|
mfaStatus: 'disable',
|
||||||
|
allowIPs: '',
|
||||||
|
bindDomain: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
const unset = ref(i18n.global.t('setting.unSetting'));
|
const unset = ref(i18n.global.t('setting.unSetting'));
|
||||||
@ -163,6 +207,8 @@ const search = async () => {
|
|||||||
form.expirationTime = res.data.expirationTime;
|
form.expirationTime = res.data.expirationTime;
|
||||||
form.complexityVerification = res.data.complexityVerification;
|
form.complexityVerification = res.data.complexityVerification;
|
||||||
form.mfaStatus = res.data.mfaStatus;
|
form.mfaStatus = res.data.mfaStatus;
|
||||||
|
form.allowIPs = res.data.allowIPs || '';
|
||||||
|
form.bindDomain = res.data.bindDomain;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSaveComplexity = async () => {
|
const onSaveComplexity = async () => {
|
||||||
@ -199,12 +245,18 @@ const handleMFA = async () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onChangeEntrance = async () => {
|
const onChangeEntrance = () => {
|
||||||
entranceRef.value.acceptParams({ securityEntrance: form.securityEntrance });
|
entranceRef.value.acceptParams({ securityEntrance: form.securityEntrance });
|
||||||
};
|
};
|
||||||
const onChangePort = async () => {
|
const onChangePort = () => {
|
||||||
portRef.value.acceptParams({ serverPort: form.serverPort });
|
portRef.value.acceptParams({ serverPort: form.serverPort });
|
||||||
};
|
};
|
||||||
|
const onChangeBindDomain = () => {
|
||||||
|
domainRef.value.acceptParams({ bindDomain: form.bindDomain });
|
||||||
|
};
|
||||||
|
const onChangeAllowIPs = () => {
|
||||||
|
allowIPsRef.value.acceptParams({ allowIPs: form.allowIPs });
|
||||||
|
};
|
||||||
const handleSSL = async () => {
|
const handleSSL = async () => {
|
||||||
if (form.ssl === 'enable') {
|
if (form.ssl === 'enable') {
|
||||||
let params = {
|
let params = {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user