diff --git a/backend/app/dto/setting.go b/backend/app/dto/setting.go
index ac15e7c60..254c83803 100644
--- a/backend/app/dto/setting.go
+++ b/backend/app/dto/setting.go
@@ -70,6 +70,7 @@ type SettingInfo struct {
ApiInterfaceStatus string `json:"apiInterfaceStatus"`
ApiKey string `json:"apiKey"`
IpWhiteList string `json:"ipWhiteList"`
+ ApiKeyValidityTime string `json:"apiKeyValidityTime"`
}
type SettingUpdate struct {
@@ -240,4 +241,5 @@ type ApiInterfaceConfig struct {
ApiInterfaceStatus string `json:"apiInterfaceStatus"`
ApiKey string `json:"apiKey"`
IpWhiteList string `json:"ipWhiteList"`
+ ApiKeyValidityTime string `json:"apiKeyValidityTime"`
}
diff --git a/backend/app/service/setting.go b/backend/app/service/setting.go
index 1e05d458f..0049d0335 100644
--- a/backend/app/service/setting.go
+++ b/backend/app/service/setting.go
@@ -510,5 +510,9 @@ func (u *SettingService) UpdateApiConfig(req dto.ApiInterfaceConfig) error {
return err
}
global.CONF.System.IpWhiteList = req.IpWhiteList
+ if err := settingRepo.Update("ApiKeyValidityTime", req.ApiKeyValidityTime); err != nil {
+ return err
+ }
+ global.CONF.System.ApiKeyValidityTime = req.ApiKeyValidityTime
return nil
}
diff --git a/backend/configs/system.go b/backend/configs/system.go
index 1f13b2618..c603d8cbb 100644
--- a/backend/configs/system.go
+++ b/backend/configs/system.go
@@ -30,4 +30,5 @@ type System struct {
ApiInterfaceStatus string `mapstructure:"api_interface_status"`
ApiKey string `mapstructure:"api_key"`
IpWhiteList string `mapstructure:"ip_white_list"`
+ ApiKeyValidityTime string `mapstructure:"api_key_validity_time"`
}
diff --git a/backend/constant/errs.go b/backend/constant/errs.go
index a76e8b3f3..943d23037 100644
--- a/backend/constant/errs.go
+++ b/backend/constant/errs.go
@@ -37,22 +37,23 @@ var (
// api
var (
- ErrTypeInternalServer = "ErrInternalServer"
- ErrTypeInvalidParams = "ErrInvalidParams"
- ErrTypeNotLogin = "ErrNotLogin"
- ErrTypePasswordExpired = "ErrPasswordExpired"
- ErrNameIsExist = "ErrNameIsExist"
- ErrDemoEnvironment = "ErrDemoEnvironment"
- ErrCmdIllegal = "ErrCmdIllegal"
- ErrXpackNotFound = "ErrXpackNotFound"
- ErrXpackNotActive = "ErrXpackNotActive"
- ErrXpackLost = "ErrXpackLost"
- ErrXpackTimeout = "ErrXpackTimeout"
- ErrXpackOutOfDate = "ErrXpackOutOfDate"
- ErrApiConfigStatusInvalid = "ErrApiConfigStatusInvalid"
- ErrApiConfigKeyInvalid = "ErrApiConfigKeyInvalid"
- ErrApiConfigIPInvalid = "ErrApiConfigIPInvalid"
- ErrApiConfigDisable = "ErrApiConfigDisable"
+ ErrTypeInternalServer = "ErrInternalServer"
+ ErrTypeInvalidParams = "ErrInvalidParams"
+ ErrTypeNotLogin = "ErrNotLogin"
+ ErrTypePasswordExpired = "ErrPasswordExpired"
+ ErrNameIsExist = "ErrNameIsExist"
+ ErrDemoEnvironment = "ErrDemoEnvironment"
+ ErrCmdIllegal = "ErrCmdIllegal"
+ ErrXpackNotFound = "ErrXpackNotFound"
+ ErrXpackNotActive = "ErrXpackNotActive"
+ ErrXpackLost = "ErrXpackLost"
+ ErrXpackTimeout = "ErrXpackTimeout"
+ ErrXpackOutOfDate = "ErrXpackOutOfDate"
+ ErrApiConfigStatusInvalid = "ErrApiConfigStatusInvalid"
+ ErrApiConfigKeyInvalid = "ErrApiConfigKeyInvalid"
+ ErrApiConfigIPInvalid = "ErrApiConfigIPInvalid"
+ ErrApiConfigDisable = "ErrApiConfigDisable"
+ ErrApiConfigKeyTimeInvalid = "ErrApiConfigKeyTimeInvalid"
)
// app
diff --git a/backend/i18n/lang/en.yaml b/backend/i18n/lang/en.yaml
index 6b816d6bd..6034c2888 100644
--- a/backend/i18n/lang/en.yaml
+++ b/backend/i18n/lang/en.yaml
@@ -12,6 +12,7 @@ ErrApiConfigStatusInvalid: "API Interface access prohibited: {{ .detail }}"
ErrApiConfigKeyInvalid: "API Interface key error: {{ .detail }}"
ErrApiConfigIPInvalid: "API Interface IP is not on the whitelist: {{ .detail }}"
ErrApiConfigDisable: "This interface prohibits the use of API Interface calls: {{ .detail }}"
+ErrApiConfigKeyTimeInvalid: "API Interface timestamp error: {{ .detail }}"
#common
ErrNameIsExist: "Name already exists"
diff --git a/backend/i18n/lang/ru.yaml b/backend/i18n/lang/ru.yaml
index 62eb5fb94..7f5b2e0d7 100644
--- a/backend/i18n/lang/ru.yaml
+++ b/backend/i18n/lang/ru.yaml
@@ -12,6 +12,7 @@ ErrApiConfigStatusInvalid: "Доступ к API интерфейсу запре
ErrApiConfigKeyInvalid: "Ошибка ключа API интерфейса: {{ .detail }}"
ErrApiConfigIPInvalid: "IP API интерфейса отсутствует в белом списке: {{ .detail }}"
ErrApiConfigDisable: "Этот интерфейс запрещает использование вызовов API интерфейса: {{ .detail }}"
+ErrApiConfigKeyTimeInvalid: "Ошибка временной метки интерфейса API: {{ .detail }}"
#common
ErrNameIsExist: "Имя уже существует"
diff --git a/backend/i18n/lang/zh-Hant.yaml b/backend/i18n/lang/zh-Hant.yaml
index 5a11509ec..f34185838 100644
--- a/backend/i18n/lang/zh-Hant.yaml
+++ b/backend/i18n/lang/zh-Hant.yaml
@@ -12,6 +12,7 @@ ErrApiConfigStatusInvalid: "API 介面禁止訪問: {{ .detail }}"
ErrApiConfigKeyInvalid: "API 介面金鑰錯誤: {{ .detail }}"
ErrApiConfigIPInvalid: "呼叫 API 介面 IP 不在白名單: {{ .detail }}"
ErrApiConfigDisable: "此介面禁止使用 API 介面呼叫: {{ .detail }}"
+ErrApiConfigKeyTimeInvalid: "API 介面時間戳記錯誤: {{ .detail }}"
#common
ErrNameIsExist: "名稱已存在"
diff --git a/backend/i18n/lang/zh.yaml b/backend/i18n/lang/zh.yaml
index 72fabe4c7..9b6d1e777 100644
--- a/backend/i18n/lang/zh.yaml
+++ b/backend/i18n/lang/zh.yaml
@@ -12,6 +12,7 @@ ErrApiConfigStatusInvalid: "API 接口禁止访问: {{ .detail }}"
ErrApiConfigKeyInvalid: "API 接口密钥错误: {{ .detail }}"
ErrApiConfigIPInvalid: "调用 API 接口 IP 不在白名单: {{ .detail }}"
ErrApiConfigDisable: "此接口禁止使用 API 接口调用: {{ .detail }}"
+ErrApiConfigKeyTimeInvalid: "API 接口时间戳错误: {{ .detail }}"
#common
ErrNameIsExist: "名称已存在"
diff --git a/backend/init/hook/hook.go b/backend/init/hook/hook.go
index b0334966f..6157a5211 100644
--- a/backend/init/hook/hook.go
+++ b/backend/init/hook/hook.go
@@ -77,6 +77,11 @@ func Init() {
global.LOG.Errorf("load service ip white list from setting failed, err: %v", err)
}
global.CONF.System.IpWhiteList = ipWhiteListSetting.Value
+ apiKeyValidityTimeSetting, err := settingRepo.Get(settingRepo.WithByKey("ApiKeyValidityTime"))
+ if err != nil {
+ global.LOG.Errorf("load service api key validity time from setting failed, err: %v", err)
+ }
+ global.CONF.System.ApiKeyValidityTime = apiKeyValidityTimeSetting.Value
}
handleUserInfo(global.CONF.System.ChangeUserInfo, settingRepo)
diff --git a/backend/init/migration/migrate.go b/backend/init/migration/migrate.go
index 52c243fea..0a3660643 100644
--- a/backend/init/migration/migrate.go
+++ b/backend/init/migration/migrate.go
@@ -98,6 +98,7 @@ func Init() {
migrations.AddAutoRestart,
migrations.AddApiInterfaceConfig,
+ migrations.AddApiKeyValidityTime,
})
if err := m.Migrate(); err != nil {
global.LOG.Error(err)
diff --git a/backend/init/migration/migrations/v_1_10.go b/backend/init/migration/migrations/v_1_10.go
index e0e97e7bf..c234f0901 100644
--- a/backend/init/migration/migrations/v_1_10.go
+++ b/backend/init/migration/migrations/v_1_10.go
@@ -350,3 +350,13 @@ var AddApiInterfaceConfig = &gormigrate.Migration{
return nil
},
}
+
+var AddApiKeyValidityTime = &gormigrate.Migration{
+ ID: "20241226-add-api-key-validity-time",
+ Migrate: func(tx *gorm.DB) error {
+ if err := tx.Create(&model.Setting{Key: "ApiKeyValidityTime", Value: "120"}).Error; err != nil {
+ return err
+ }
+ return nil
+ },
+}
diff --git a/backend/middleware/session.go b/backend/middleware/session.go
index 965259c13..51d535edc 100644
--- a/backend/middleware/session.go
+++ b/backend/middleware/session.go
@@ -3,15 +3,15 @@ package middleware
import (
"crypto/md5"
"encoding/hex"
- "net"
- "strconv"
- "strings"
-
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/backend/app/repo"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/gin-gonic/gin"
+ "net"
+ "strconv"
+ "strings"
+ "time"
)
func SessionAuth() gin.HandlerFunc {
@@ -25,6 +25,11 @@ func SessionAuth() gin.HandlerFunc {
if panelToken != "" || panelTimestamp != "" {
if global.CONF.System.ApiInterfaceStatus == "enable" {
clientIP := c.ClientIP()
+ if !isValid1PanelTimestamp(panelTimestamp) {
+ helper.ErrorWithDetail(c, constant.CodeErrUnauthorized, constant.ErrApiConfigKeyTimeInvalid, nil)
+ return
+ }
+
if !isValid1PanelToken(panelToken, panelTimestamp) {
helper.ErrorWithDetail(c, constant.CodeErrUnauthorized, constant.ErrApiConfigKeyInvalid, nil)
return
@@ -63,6 +68,23 @@ func SessionAuth() gin.HandlerFunc {
}
}
+func isValid1PanelTimestamp(panelTimestamp string) bool {
+ apiKeyValidityTime := global.CONF.System.ApiKeyValidityTime
+ apiTime, err := strconv.Atoi(apiKeyValidityTime)
+ if err != nil {
+ return false
+ }
+ panelTime, err := strconv.ParseInt(panelTimestamp, 10, 64)
+ if err != nil {
+ return false
+ }
+ nowTime := time.Now().Unix()
+ if panelTime > nowTime {
+ return false
+ }
+ return apiTime == 0 || nowTime-panelTime <= int64(apiTime*60)
+}
+
func isValid1PanelToken(panelToken string, panelTimestamp string) bool {
system1PanelToken := global.CONF.System.ApiKey
if panelToken == GenerateMD5("1panel"+system1PanelToken+panelTimestamp) {
diff --git a/frontend/src/api/interface/setting.ts b/frontend/src/api/interface/setting.ts
index 88ad99539..cde776038 100644
--- a/frontend/src/api/interface/setting.ts
+++ b/frontend/src/api/interface/setting.ts
@@ -61,6 +61,7 @@ export namespace Setting {
apiInterfaceStatus: string;
apiKey: string;
ipWhiteList: string;
+ apiKeyValidityTime: number;
}
export interface SettingUpdate {
key: string;
@@ -193,5 +194,6 @@ export namespace Setting {
apiInterfaceStatus: string;
apiKey: string;
ipWhiteList: string;
+ apiKeyValidityTime: number;
}
}
diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts
index f6db94189..101c8e794 100644
--- a/frontend/src/lang/modules/en.ts
+++ b/frontend/src/lang/modules/en.ts
@@ -1431,6 +1431,10 @@ const message = {
ipWhiteList: 'IP allowlist',
ipWhiteListEgs: 'One per line. For example,\n172.161.10.111\n172.161.10.0/24',
ipWhiteListHelper: 'IPs within the allowlist can access the API.',
+ apiKeyValidityTime: 'Validity period of interface key',
+ apiKeyValidityTimeEgs: 'Validity period of interface key (in minutes)',
+ apiKeyValidityTimeHelper:
+ 'The interface timestamp is valid if its difference from the current timestamp (in minutes) is within the allowed range. A value of 0 disables verification.',
apiKeyReset: 'Interface key reset',
apiKeyResetHelper: 'the associated key service will become invalid. Please add a new key to the service',
confDockerProxy: 'Configure docker proxy',
diff --git a/frontend/src/lang/modules/ru.ts b/frontend/src/lang/modules/ru.ts
index 607874d69..212858561 100644
--- a/frontend/src/lang/modules/ru.ts
+++ b/frontend/src/lang/modules/ru.ts
@@ -1437,6 +1437,10 @@ const message = {
ipWhiteList: 'Белый список IP',
ipWhiteListEgs: 'По одному в строке. Например,\n172.161.10.111\n172.161.10.0/24',
ipWhiteListHelper: 'IP-адреса из белого списка могут получить доступ к API.',
+ apiKeyValidityTime: 'Срок действия ключа интерфейса',
+ apiKeyValidityTimeEgs: 'Срок действия ключа интерфейса (в единицах)',
+ apiKeyValidityTimeHelper:
+ 'Интерфейс времени метки между текущей меткой времени на момент запроса действителен (в единицах), установлен как 0, не проводится проверка метки времени',
apiKeyReset: 'Сброс ключа интерфейса',
apiKeyResetHelper:
'связанный ключевой сервис станет недействительным. Пожалуйста, добавьте новый ключ к сервису',
diff --git a/frontend/src/lang/modules/tw.ts b/frontend/src/lang/modules/tw.ts
index 6789c404c..322043fa5 100644
--- a/frontend/src/lang/modules/tw.ts
+++ b/frontend/src/lang/modules/tw.ts
@@ -1357,6 +1357,9 @@ const message = {
ipWhiteList: 'IP白名單',
ipWhiteListEgs: '當存在多個 IP 時,需要換行顯示,例:\n172.16.10.111 \n172.16.10.0/24',
ipWhiteListHelper: '必需在 IP 白名單清單中的 IP 才能瀏覽面板 API 介面',
+ apiKeyValidityTime: '介面金鑰有效期',
+ apiKeyValidityTimeEgs: '介面金鑰有效期(組織分)',
+ apiKeyValidityTimeHelper: '介面時間戳記到請求時的當前時間戳之間有效(組織分),設定為0時,不做時間戳記校驗',
apiKeyReset: '介面密鑰重設',
apiKeyResetHelper: '重設密鑰後,已關聯密鑰服務將失效,請重新新增新密鑰至服務。',
confDockerProxy: '配寘 Docker 代理',
diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts
index 8ac88cb41..4941f8e23 100644
--- a/frontend/src/lang/modules/zh.ts
+++ b/frontend/src/lang/modules/zh.ts
@@ -1358,6 +1358,9 @@ const message = {
ipWhiteList: 'IP 白名单',
ipWhiteListEgs: '当存在多个 IP 时,需要换行显示,例: \n172.16.10.111 \n172.16.10.0/24',
ipWhiteListHelper: '必需在 IP 白名单列表中的 IP 才能访问面板 API 接口',
+ apiKeyValidityTime: '接口密钥有效期',
+ apiKeyValidityTimeEgs: '接口密钥有效期(单位分)',
+ apiKeyValidityTimeHelper: '接口时间戳到请求时的当前时间戳之间有效(单位分),设置为 0 时,不做时间戳校验',
apiKeyReset: '接口密钥重置',
apiKeyResetHelper: '重置密钥后,已关联密钥服务将失效,请重新添加新密钥至服务。',
confDockerProxy: '配置 Docker 代理',
diff --git a/frontend/src/views/setting/panel/api-interface/index.vue b/frontend/src/views/setting/panel/api-interface/index.vue
index aa606b38a..cbf2b1e7f 100644
--- a/frontend/src/views/setting/panel/api-interface/index.vue
+++ b/frontend/src/views/setting/panel/api-interface/index.vue
@@ -65,6 +65,17 @@
/>
{{ $t('setting.ipWhiteListHelper') }}
+
+
+ {{ $t('commons.units.minute') }}
+
+
+ {{ $t('setting.apiKeyValidityTimeHelper') }}
+
+
@@ -103,17 +114,20 @@ const form = reactive({
apiKey: '',
ipWhiteList: '',
apiInterfaceStatus: '',
+ apiKeyValidityTime: 120,
});
const rules = reactive({
ipWhiteList: [Rules.requiredInput, { validator: checkIPs, trigger: 'blur' }],
apiKey: [Rules.requiredInput],
+ apiKeyValidityTime: [Rules.requiredInput, Rules.integerNumberWith0],
});
interface DialogProps {
apiInterfaceStatus: string;
apiKey: string;
ipWhiteList: string;
+ apiKeyValidityTime: number;
}
function checkIPs(rule: any, value: any, callback: any) {
@@ -146,6 +160,7 @@ const acceptParams = async (params: DialogProps): Promise => {
});
}
form.ipWhiteList = params.ipWhiteList;
+ form.apiKeyValidityTime = params.apiKeyValidityTime;
drawerVisible.value = true;
};
@@ -179,6 +194,7 @@ const onSave = async (formEl: FormInstance | undefined) => {
apiKey: form.apiKey,
ipWhiteList: form.ipWhiteList,
apiInterfaceStatus: form.apiInterfaceStatus,
+ apiKeyValidityTime: form.apiKeyValidityTime,
};
loading.value = true;
await updateApiConfig(param)
diff --git a/frontend/src/views/setting/panel/index.vue b/frontend/src/views/setting/panel/index.vue
index c737b2934..3883077a2 100644
--- a/frontend/src/views/setting/panel/index.vue
+++ b/frontend/src/views/setting/panel/index.vue
@@ -285,6 +285,7 @@ const form = reactive({
apiInterfaceStatus: 'disable',
apiKey: '',
ipWhiteList: '',
+ apiKeyValidityTime: 120,
proHideMenus: ref(i18n.t('setting.unSetting')),
hideMenuList: '',
@@ -353,6 +354,7 @@ const search = async () => {
form.apiInterfaceStatus = res.data.apiInterfaceStatus;
form.apiKey = res.data.apiKey;
form.ipWhiteList = res.data.ipWhiteList;
+ form.apiKeyValidityTime = res.data.apiKeyValidityTime;
const json: Node = JSON.parse(res.data.xpackHideMenu);
const checkedTitles = getCheckedTitles(json);
@@ -428,6 +430,7 @@ const onChangeApiInterfaceStatus = async () => {
apiInterfaceStatus: form.apiInterfaceStatus,
apiKey: form.apiKey,
ipWhiteList: form.ipWhiteList,
+ apiKeyValidityTime: form.apiKeyValidityTime,
});
return;
}
@@ -442,6 +445,7 @@ const onChangeApiInterfaceStatus = async () => {
apiKey: form.apiKey,
ipWhiteList: form.ipWhiteList,
apiInterfaceStatus: form.apiInterfaceStatus,
+ apiKeyValidityTime: form.apiKeyValidityTime,
};
await updateApiConfig(param)
.then(() => {