Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add expiration time setting for API key #7584

Merged
merged 1 commit into from
Dec 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions backend/app/dto/setting.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -240,4 +241,5 @@ type ApiInterfaceConfig struct {
ApiInterfaceStatus string `json:"apiInterfaceStatus"`
ApiKey string `json:"apiKey"`
IpWhiteList string `json:"ipWhiteList"`
ApiKeyValidityTime string `json:"apiKeyValidityTime"`
}
4 changes: 4 additions & 0 deletions backend/app/service/setting.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
1 change: 1 addition & 0 deletions backend/configs/system.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
}
33 changes: 17 additions & 16 deletions backend/constant/errs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code snippet you provided does not contain any major irregularities, potential issues, or significant optimizations to suggest. There are just minor formatting updates made between the third and fourth line of code comments. The changes include adding an ellipsis (...) at the end of each comment to align with the previous comments' format. These changes do not affect the functionality of the code and are considered stylistic adjustments rather than functional improvements.

If there is anything else specific you would like checked regarding this or other code snippets, please let me know!

Expand Down
1 change: 1 addition & 0 deletions backend/i18n/lang/en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions backend/i18n/lang/ru.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ ErrApiConfigStatusInvalid: "Доступ к API интерфейсу запре
ErrApiConfigKeyInvalid: "Ошибка ключа API интерфейса: {{ .detail }}"
ErrApiConfigIPInvalid: "IP API интерфейса отсутствует в белом списке: {{ .detail }}"
ErrApiConfigDisable: "Этот интерфейс запрещает использование вызовов API интерфейса: {{ .detail }}"
ErrApiConfigKeyTimeInvalid: "Ошибка временной метки интерфейса API: {{ .detail }}"

#common
ErrNameIsExist: "Имя уже существует"
Expand Down
1 change: 1 addition & 0 deletions backend/i18n/lang/zh-Hant.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ ErrApiConfigStatusInvalid: "API 介面禁止訪問: {{ .detail }}"
ErrApiConfigKeyInvalid: "API 介面金鑰錯誤: {{ .detail }}"
ErrApiConfigIPInvalid: "呼叫 API 介面 IP 不在白名單: {{ .detail }}"
ErrApiConfigDisable: "此介面禁止使用 API 介面呼叫: {{ .detail }}"
ErrApiConfigKeyTimeInvalid: "API 介面時間戳記錯誤: {{ .detail }}"

#common
ErrNameIsExist: "名稱已存在"
Expand Down
1 change: 1 addition & 0 deletions backend/i18n/lang/zh.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ ErrApiConfigStatusInvalid: "API 接口禁止访问: {{ .detail }}"
ErrApiConfigKeyInvalid: "API 接口密钥错误: {{ .detail }}"
ErrApiConfigIPInvalid: "调用 API 接口 IP 不在白名单: {{ .detail }}"
ErrApiConfigDisable: "此接口禁止使用 API 接口调用: {{ .detail }}"
ErrApiConfigKeyTimeInvalid: "API 接口时间戳错误: {{ .detail }}"

#common
ErrNameIsExist: "名称已存在"
Expand Down
5 changes: 5 additions & 0 deletions backend/init/hook/hook.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions backend/init/migration/migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ func Init() {

migrations.AddAutoRestart,
migrations.AddApiInterfaceConfig,
migrations.AddApiKeyValidityTime,
})
if err := m.Migrate(); err != nil {
global.LOG.Error(err)
Expand Down
10 changes: 10 additions & 0 deletions backend/init/migration/migrations/v_1_10.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
},
}
30 changes: 26 additions & 4 deletions backend/middleware/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand Down Expand Up @@ -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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Differences Analysis:

Changes Identified:

  1. Imports Moved to Top: The import statements at the top have been moved from one section to another without any issues.

  2. Function Definition:

    • SessionAuth() is defined as a gin.HandlerFunc, which is correct and matches other similar functions in the file.
    • However, the logic after checking the timestamp looks incomplete. It should validate that the timestamp is within an allowed window considering apiKeyValidityTime.
  3. Comment Update: In the second comment, there’s no change compared to the previous version but is grammatically clear.

  4. New Function isValid1PanelTimestamp:

    • Added this new function immediately after defining SessionAuth(). This is useful for validating if the API key timestamp is still valid given the configured apiKeyValidityTime.
    • Checks if apiKeyValidityTime can be converted successfully to an integer and then checks if the current time minus the parsed timestamp is less than or equal to the validity duration (in minutes).
  5. Code Optimization Suggestions:

    • Consider using constants where appropriate, like for the default timeout duration of zero if apiKeyValidityTime equals zero.
    • Improve error handling: Ensure that all imported packages' methods handle errors appropriately when converting strings to integers and parsing times.
    • Add comments explaining the purpose of each part of the timestamp validation.

Conclusion:

The code overall maintains functionality with minor adjustments made to enhance readability and maintainability. The new isValid1PanelTimestamp function provides a simple way to ensure that API keys used do not exceed their expiration period based on the server configuration.

Expand Down
2 changes: 2 additions & 0 deletions frontend/src/api/interface/setting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export namespace Setting {
apiInterfaceStatus: string;
apiKey: string;
ipWhiteList: string;
apiKeyValidityTime: number;
}
export interface SettingUpdate {
key: string;
Expand Down Expand Up @@ -193,5 +194,6 @@ export namespace Setting {
apiInterfaceStatus: string;
apiKey: string;
ipWhiteList: string;
apiKeyValidityTime: number;
}
}
4 changes: 4 additions & 0 deletions frontend/src/lang/modules/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/lang/modules/ru.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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:
'связанный ключевой сервис станет недействительным. Пожалуйста, добавьте новый ключ к сервису',
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/lang/modules/tw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 代理',
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/lang/modules/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 代理',
Expand Down
16 changes: 16 additions & 0 deletions frontend/src/views/setting/panel/api-interface/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,17 @@
/>
<span class="input-help">{{ $t('setting.ipWhiteListHelper') }}</span>
</el-form-item>
<el-form-item :label="$t('setting.apiKeyValidityTime')" prop="apiKeyValidityTime">
<el-input
:placeholder="$t('setting.apiKeyValidityTimeEgs')"
v-model="form.apiKeyValidityTime"
>
<template #append>{{ $t('commons.units.minute') }}</template>
</el-input>
<span class="input-help">
{{ $t('setting.apiKeyValidityTimeHelper') }}
</span>
</el-form-item>
</el-col>
</el-row>
</el-form>
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -146,6 +160,7 @@ const acceptParams = async (params: DialogProps): Promise<void> => {
});
}
form.ipWhiteList = params.ipWhiteList;
form.apiKeyValidityTime = params.apiKeyValidityTime;
drawerVisible.value = true;
};

Expand Down Expand Up @@ -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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The provided code has a few minor improvements and clarifications, but there are no obvious irregularities or issues. Here is a brief review:

  1. Form Item Labels: The label for apiKeyValidityTime is now in the correct position within the el-form.

  2. Template Append Unit: The unit "minute" correctly appended to the el-input for apiKeyValidityTime. This helps users understand that time durations should be expressed in minutes.

  3. Initial Values: The initial value of apiKeyValidityTime is set to 120, which makes sense given its default setting in many systems. However, it would be beneficial if you could verify this with appropriate system requirements.

Overall, the changes make the form more user-friendly by providing clear instructions on how to input data and ensuring consistency across similar fields. If there are specific performance optimizations needed based on your application's use case, feel free to ask!

Expand Down
4 changes: 4 additions & 0 deletions frontend/src/views/setting/panel/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ const form = reactive({
apiInterfaceStatus: 'disable',
apiKey: '',
ipWhiteList: '',
apiKeyValidityTime: 120,

proHideMenus: ref(i18n.t('setting.unSetting')),
hideMenuList: '',
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -428,6 +430,7 @@ const onChangeApiInterfaceStatus = async () => {
apiInterfaceStatus: form.apiInterfaceStatus,
apiKey: form.apiKey,
ipWhiteList: form.ipWhiteList,
apiKeyValidityTime: form.apiKeyValidityTime,
});
return;
}
Expand All @@ -442,6 +445,7 @@ const onChangeApiInterfaceStatus = async () => {
apiKey: form.apiKey,
ipWhiteList: form.ipWhiteList,
apiInterfaceStatus: form.apiInterfaceStatus,
apiKeyValidityTime: form.apiKeyValidityTime,
};
await updateApiConfig(param)
.then(() => {
Expand Down
Loading