mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-08 10:27:46 +08:00
feat: support api key ip restriction rules
This commit is contained in:
@@ -1420,6 +1420,7 @@ async function handleKeyFormSubmit(data: StandaloneKeyFormData) {
|
||||
allowed_providers: data.allowed_providers,
|
||||
allowed_api_formats: data.allowed_api_formats,
|
||||
allowed_models: data.allowed_models,
|
||||
ip_rules: data.ip_rules,
|
||||
feature_settings: data.feature_settings ?? null
|
||||
}
|
||||
const { message: _, ...updated } = await adminApi.updateApiKey(data.id, updateData)
|
||||
@@ -1451,6 +1452,7 @@ async function handleKeyFormSubmit(data: StandaloneKeyFormData) {
|
||||
allowed_providers: data.allowed_providers,
|
||||
allowed_api_formats: data.allowed_api_formats,
|
||||
allowed_models: data.allowed_models,
|
||||
ip_rules: data.ip_rules,
|
||||
feature_settings: data.feature_settings ?? null
|
||||
}
|
||||
const response = await adminApi.createStandaloneApiKey(createData)
|
||||
|
||||
@@ -1090,7 +1090,7 @@
|
||||
{{ apiKey.key_display || '****' }}
|
||||
</code>
|
||||
<span class="text-xs text-muted-foreground">
|
||||
IP 白名单:{{ formatAllowedIps(apiKey.allowed_ips) }}
|
||||
IP 限制:{{ formatIpRules(apiKey.ip_rules) }}
|
||||
</span>
|
||||
<button
|
||||
class="p-0.5 hover:bg-muted rounded transition-colors"
|
||||
@@ -1263,17 +1263,17 @@
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label
|
||||
for="admin-user-key-allowed-ips"
|
||||
for="admin-user-key-ip-rules"
|
||||
class="text-sm font-medium"
|
||||
>IP 白名单</Label>
|
||||
>IP 限制</Label>
|
||||
<Input
|
||||
id="admin-user-key-allowed-ips"
|
||||
v-model="userApiKeyForm.allowed_ips_text"
|
||||
id="admin-user-key-ip-rules"
|
||||
v-model="userApiKeyForm.ip_rules_text"
|
||||
class="h-10"
|
||||
placeholder="例如:203.0.113.10, 10.0.0.0/24"
|
||||
placeholder="例如:203.0.113.10, 10.0.0.0/24, !10.0.0.13"
|
||||
/>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
留空表示不限制来源 IP;支持 IP 或 CIDR,多个值用英文逗号分隔
|
||||
留空表示不限制;支持 IP、CIDR、IPv4 通配符、*,用 ! 前缀拒绝,多个规则用英文逗号分隔
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -1580,7 +1580,7 @@ const userApiKeyForm = ref({
|
||||
name: '',
|
||||
rate_limit: undefined as number | undefined,
|
||||
concurrent_limit: undefined as number | undefined,
|
||||
allowed_ips_text: '',
|
||||
ip_rules_text: '',
|
||||
chat_pii_redaction_enabled: false,
|
||||
chat_pii_redaction_placeholder_notice: true,
|
||||
})
|
||||
@@ -1888,11 +1888,11 @@ function formatConcurrentLimitSimple(concurrentLimit?: number | null): string {
|
||||
return `${concurrentLimit} 并发`
|
||||
}
|
||||
|
||||
function formatAllowedIps(allowedIps?: string[] | null): string {
|
||||
return allowedIps && allowedIps.length > 0 ? allowedIps.join(', ') : '不限制'
|
||||
function formatIpRules(ipRules?: string[] | null): string {
|
||||
return ipRules && ipRules.length > 0 ? ipRules.join(', ') : '不限制'
|
||||
}
|
||||
|
||||
function parseAllowedIpsInput(value: string): string[] | null {
|
||||
function parseIpRulesInput(value: string): string[] | null {
|
||||
const items = value
|
||||
.split(',')
|
||||
.map((item) => item.trim())
|
||||
@@ -2110,7 +2110,7 @@ function openCreateUserApiKeyDialog() {
|
||||
name: `Key-${new Date().toISOString().split('T')[0]}`,
|
||||
rate_limit: undefined,
|
||||
concurrent_limit: undefined,
|
||||
allowed_ips_text: '',
|
||||
ip_rules_text: '',
|
||||
chat_pii_redaction_enabled: redactionFeature.enabled,
|
||||
chat_pii_redaction_placeholder_notice: redactionFeature.inject_model_instruction,
|
||||
}
|
||||
@@ -2125,7 +2125,7 @@ function openEditUserApiKeyDialog(apiKey: ApiKey) {
|
||||
name: apiKey.name || '',
|
||||
rate_limit: apiKey.rate_limit ?? undefined,
|
||||
concurrent_limit: apiKey.concurrent_limit ?? undefined,
|
||||
allowed_ips_text: apiKey.allowed_ips?.join(', ') ?? '',
|
||||
ip_rules_text: apiKey.ip_rules?.join(', ') ?? '',
|
||||
chat_pii_redaction_enabled: redactionFeature.enabled,
|
||||
chat_pii_redaction_placeholder_notice: redactionFeature.inject_model_instruction,
|
||||
}
|
||||
@@ -2139,7 +2139,7 @@ function closeUserApiKeyFormDialog() {
|
||||
name: '',
|
||||
rate_limit: undefined,
|
||||
concurrent_limit: undefined,
|
||||
allowed_ips_text: '',
|
||||
ip_rules_text: '',
|
||||
chat_pii_redaction_enabled: false,
|
||||
chat_pii_redaction_placeholder_notice: true,
|
||||
}
|
||||
@@ -2154,13 +2154,13 @@ async function submitUserApiKeyForm() {
|
||||
|
||||
creatingApiKey.value = true
|
||||
try {
|
||||
const allowedIps = parseAllowedIpsInput(userApiKeyForm.value.allowed_ips_text)
|
||||
const ipRules = parseIpRulesInput(userApiKeyForm.value.ip_rules_text)
|
||||
if (editingUserApiKey.value) {
|
||||
await usersStore.updateApiKey(selectedUser.value.id, editingUserApiKey.value.id, {
|
||||
name: userApiKeyForm.value.name,
|
||||
rate_limit: userApiKeyForm.value.rate_limit ?? 0,
|
||||
concurrent_limit: userApiKeyForm.value.concurrent_limit,
|
||||
allowed_ips: allowedIps,
|
||||
ip_rules: ipRules,
|
||||
feature_settings: mergeChatPiiRedactionFeatureSettings(editingUserApiKey.value.feature_settings, {
|
||||
enabled: userApiKeyForm.value.chat_pii_redaction_enabled,
|
||||
inject_model_instruction: userApiKeyForm.value.chat_pii_redaction_placeholder_notice,
|
||||
@@ -2172,7 +2172,7 @@ async function submitUserApiKeyForm() {
|
||||
name: userApiKeyForm.value.name,
|
||||
rate_limit: userApiKeyForm.value.rate_limit ?? 0,
|
||||
concurrent_limit: userApiKeyForm.value.concurrent_limit,
|
||||
allowed_ips: allowedIps,
|
||||
ip_rules: ipRules,
|
||||
feature_settings: mergeChatPiiRedactionFeatureSettings(null, {
|
||||
enabled: userApiKeyForm.value.chat_pii_redaction_enabled,
|
||||
inject_model_instruction: userApiKeyForm.value.chat_pii_redaction_placeholder_notice,
|
||||
|
||||
@@ -426,21 +426,21 @@
|
||||
</div>
|
||||
|
||||
|
||||
<!-- IP 白名单 -->
|
||||
<!-- IP 限制 -->
|
||||
<div class="space-y-2">
|
||||
<Label
|
||||
for="token-ips"
|
||||
class="text-sm font-semibold"
|
||||
>IP 白名单</Label>
|
||||
>IP 限制</Label>
|
||||
<Input
|
||||
id="token-ips"
|
||||
v-model="formData.allowedIpsText"
|
||||
placeholder="例如:192.168.1.0/24, 10.0.0.1(逗号分隔,留空不限制)"
|
||||
placeholder="例如:192.168.*.*, 10.0.0.0/24, !10.0.0.13"
|
||||
class="h-11 border-border/60"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
限制只能从指定 IP 地址使用此令牌,支持 CIDR 格式
|
||||
留空表示不限制;支持 IP、CIDR、IPv4 通配符、*,用 ! 前缀拒绝,多个规则用英文逗号分隔
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -115,7 +115,7 @@
|
||||
创建于 {{ formatDate(apiKey.created_at) }}
|
||||
</div>
|
||||
<div class="text-xs text-muted-foreground mt-0.5 truncate">
|
||||
IP 白名单:{{ formatAllowedIps(apiKey.allowed_ips) }}
|
||||
IP 限制:{{ formatIpRules(apiKey.ip_rules) }}
|
||||
</div>
|
||||
</div>
|
||||
</TableCell>
|
||||
@@ -359,7 +359,7 @@
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-xs text-muted-foreground truncate">
|
||||
IP 白名单:{{ formatAllowedIps(apiKey.allowed_ips) }}
|
||||
IP 限制:{{ formatIpRules(apiKey.ip_rules) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -459,18 +459,18 @@
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label
|
||||
for="key-allowed-ips"
|
||||
for="key-ip-rules"
|
||||
class="text-sm font-semibold"
|
||||
>IP 白名单</Label>
|
||||
>IP 限制</Label>
|
||||
<Input
|
||||
id="key-allowed-ips"
|
||||
v-model="newKeyAllowedIpsText"
|
||||
placeholder="例如:203.0.113.10, 10.0.0.0/24"
|
||||
id="key-ip-rules"
|
||||
v-model="newKeyIpRulesText"
|
||||
placeholder="例如:203.0.113.10, 10.0.0.0/24, !10.0.0.13"
|
||||
class="h-11 border-border/60"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
留空表示不限制来源 IP;支持单个 IP 或 CIDR,多个值用英文逗号分隔
|
||||
留空表示不限制;支持 IP、CIDR、IPv4 通配符、*,用 ! 前缀拒绝,多个规则用英文逗号分隔
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -802,7 +802,7 @@ const showInstallDialog = ref(false)
|
||||
const newKeyName = ref('')
|
||||
const newKeyRateLimit = ref<number | undefined>(undefined)
|
||||
const newKeyConcurrentLimit = ref<number | undefined>(undefined)
|
||||
const newKeyAllowedIpsText = ref('')
|
||||
const newKeyIpRulesText = ref('')
|
||||
const keyRedactionMode = ref<'inherit' | 'custom'>('inherit')
|
||||
const newKeyRedactionEnabled = ref(false)
|
||||
const newKeyRedactionInjectNotice = ref(true)
|
||||
@@ -891,7 +891,7 @@ function openEditApiKeyDialog(apiKey: ApiKey) {
|
||||
newKeyName.value = apiKey.name || ''
|
||||
newKeyRateLimit.value = apiKey.rate_limit ?? undefined
|
||||
newKeyConcurrentLimit.value = apiKey.concurrent_limit ?? undefined
|
||||
newKeyAllowedIpsText.value = apiKey.allowed_ips?.join(', ') ?? ''
|
||||
newKeyIpRulesText.value = apiKey.ip_rules?.join(', ') ?? ''
|
||||
keyRedactionMode.value = hasRedactionFeature ? 'custom' : 'inherit'
|
||||
newKeyRedactionEnabled.value = redactionFeature.enabled
|
||||
newKeyRedactionInjectNotice.value = redactionFeature.inject_model_instruction
|
||||
@@ -903,7 +903,7 @@ function openCreateApiKeyDialog() {
|
||||
newKeyName.value = ''
|
||||
newKeyRateLimit.value = undefined
|
||||
newKeyConcurrentLimit.value = undefined
|
||||
newKeyAllowedIpsText.value = ''
|
||||
newKeyIpRulesText.value = ''
|
||||
keyRedactionMode.value = 'inherit'
|
||||
newKeyRedactionEnabled.value = false
|
||||
newKeyRedactionInjectNotice.value = true
|
||||
@@ -983,7 +983,7 @@ function closeApiKeyDialog() {
|
||||
newKeyName.value = ''
|
||||
newKeyRateLimit.value = undefined
|
||||
newKeyConcurrentLimit.value = undefined
|
||||
newKeyAllowedIpsText.value = ''
|
||||
newKeyIpRulesText.value = ''
|
||||
keyRedactionMode.value = 'inherit'
|
||||
newKeyRedactionEnabled.value = false
|
||||
newKeyRedactionInjectNotice.value = true
|
||||
@@ -997,14 +997,14 @@ async function saveApiKey() {
|
||||
|
||||
creating.value = true
|
||||
try {
|
||||
const allowedIps = parseAllowedIpsInput(newKeyAllowedIpsText.value)
|
||||
const ipRules = parseIpRulesInput(newKeyIpRulesText.value)
|
||||
const isCreatingFirstApiKey = !editingApiKey.value && apiKeys.value.length === 0
|
||||
if (editingApiKey.value) {
|
||||
await meApi.updateApiKey(editingApiKey.value.id, {
|
||||
name: newKeyName.value,
|
||||
rate_limit: newKeyRateLimit.value ?? 0,
|
||||
concurrent_limit: newKeyConcurrentLimit.value,
|
||||
allowed_ips: allowedIps,
|
||||
ip_rules: ipRules,
|
||||
feature_settings: keyRedactionMode.value === 'custom'
|
||||
? mergeChatPiiRedactionFeatureSettings(editingApiKey.value.feature_settings, {
|
||||
enabled: newKeyRedactionEnabled.value,
|
||||
@@ -1018,7 +1018,7 @@ async function saveApiKey() {
|
||||
name: newKeyName.value,
|
||||
rate_limit: newKeyRateLimit.value ?? 0,
|
||||
concurrent_limit: newKeyConcurrentLimit.value,
|
||||
allowed_ips: allowedIps,
|
||||
ip_rules: ipRules,
|
||||
...(keyRedactionMode.value === 'custom'
|
||||
? {
|
||||
feature_settings: mergeChatPiiRedactionFeatureSettings(null, {
|
||||
@@ -1148,11 +1148,11 @@ function formatConcurrentLimitSimple(concurrentLimit?: number | null): string {
|
||||
return `${concurrentLimit} 并发`
|
||||
}
|
||||
|
||||
function formatAllowedIps(allowedIps?: string[] | null): string {
|
||||
return allowedIps && allowedIps.length > 0 ? allowedIps.join(', ') : '不限制'
|
||||
function formatIpRules(ipRules?: string[] | null): string {
|
||||
return ipRules && ipRules.length > 0 ? ipRules.join(', ') : '不限制'
|
||||
}
|
||||
|
||||
function parseAllowedIpsInput(value: string): string[] | null {
|
||||
function parseIpRulesInput(value: string): string[] | null {
|
||||
const items = value
|
||||
.split(',')
|
||||
.map((item) => item.trim())
|
||||
|
||||
Reference in New Issue
Block a user