feat(安全): 强化安全策略与配置校验
- 增加 CORS/CSP/安全响应头与代理信任配置 - 引入 URL 白名单与私网开关,校验上游与价格源 - 改善 API Key 处理与网关错误返回 - 管理端设置隐藏敏感字段并优化前端提示 - 增加计费熔断与相关配置示例 测试: go test ./...
This commit is contained in:
@@ -136,16 +136,16 @@
|
||||
<ol
|
||||
class="list-inside list-decimal space-y-1 text-xs text-amber-700 dark:text-amber-300"
|
||||
>
|
||||
<li v-html="t('admin.accounts.oauth.step1')"></li>
|
||||
<li v-html="t('admin.accounts.oauth.step2')"></li>
|
||||
<li v-html="t('admin.accounts.oauth.step3')"></li>
|
||||
<li v-html="t('admin.accounts.oauth.step4')"></li>
|
||||
<li v-html="t('admin.accounts.oauth.step5')"></li>
|
||||
<li v-html="t('admin.accounts.oauth.step6')"></li>
|
||||
<li>{{ t('admin.accounts.oauth.step1') }}</li>
|
||||
<li>{{ t('admin.accounts.oauth.step2') }}</li>
|
||||
<li>{{ t('admin.accounts.oauth.step3') }}</li>
|
||||
<li>{{ t('admin.accounts.oauth.step4') }}</li>
|
||||
<li>{{ t('admin.accounts.oauth.step5') }}</li>
|
||||
<li>{{ t('admin.accounts.oauth.step6') }}</li>
|
||||
</ol>
|
||||
<p
|
||||
class="mt-2 text-xs text-amber-600 dark:text-amber-400"
|
||||
v-html="t('admin.accounts.oauth.sessionKeyFormat')"
|
||||
v-text="t('admin.accounts.oauth.sessionKeyFormat')"
|
||||
></p>
|
||||
</div>
|
||||
|
||||
@@ -390,7 +390,7 @@
|
||||
>
|
||||
<p
|
||||
class="text-xs text-amber-800 dark:text-amber-300"
|
||||
v-html="oauthImportantNotice"
|
||||
v-text="oauthImportantNotice"
|
||||
></p>
|
||||
</div>
|
||||
<!-- Proxy Warning (for non-OpenAI) -->
|
||||
@@ -400,7 +400,7 @@
|
||||
>
|
||||
<p
|
||||
class="text-xs text-yellow-800 dark:text-yellow-300"
|
||||
v-html="t('admin.accounts.oauth.proxyWarning')"
|
||||
v-text="t('admin.accounts.oauth.proxyWarning')"
|
||||
></p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -423,7 +423,7 @@
|
||||
</p>
|
||||
<p
|
||||
class="mb-3 text-sm text-blue-700 dark:text-blue-300"
|
||||
v-html="oauthAuthCodeDesc"
|
||||
v-text="oauthAuthCodeDesc"
|
||||
></p>
|
||||
<div>
|
||||
<label class="input-label">
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
</button>
|
||||
</div>
|
||||
<!-- Code Content -->
|
||||
<pre class="p-4 text-sm font-mono text-gray-100 overflow-x-auto"><code v-html="file.highlighted"></code></pre>
|
||||
<pre class="p-4 text-sm font-mono text-gray-100 overflow-x-auto"><code v-text="file.content"></code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -142,7 +142,6 @@ interface TabConfig {
|
||||
interface FileConfig {
|
||||
path: string
|
||||
content: string
|
||||
highlighted: string
|
||||
hint?: string // Optional hint message for this file
|
||||
}
|
||||
|
||||
@@ -227,13 +226,6 @@ const platformNote = computed(() => {
|
||||
})
|
||||
|
||||
// Syntax highlighting helpers
|
||||
const keyword = (text: string) => `<span class="text-purple-400">${text}</span>`
|
||||
const variable = (text: string) => `<span class="text-cyan-400">${text}</span>`
|
||||
const string = (text: string) => `<span class="text-green-400">${text}</span>`
|
||||
const operator = (text: string) => `<span class="text-yellow-400">${text}</span>`
|
||||
const comment = (text: string) => `<span class="text-gray-500">${text}</span>`
|
||||
const key = (text: string) => `<span class="text-blue-400">${text}</span>`
|
||||
|
||||
// Generate file configs based on platform and active tab
|
||||
const currentFiles = computed((): FileConfig[] => {
|
||||
const baseUrl = props.baseUrl || window.location.origin
|
||||
@@ -249,37 +241,29 @@ const currentFiles = computed((): FileConfig[] => {
|
||||
function generateAnthropicFiles(baseUrl: string, apiKey: string): FileConfig[] {
|
||||
let path: string
|
||||
let content: string
|
||||
let highlighted: string
|
||||
|
||||
switch (activeTab.value) {
|
||||
case 'unix':
|
||||
path = 'Terminal'
|
||||
content = `export ANTHROPIC_BASE_URL="${baseUrl}"
|
||||
export ANTHROPIC_AUTH_TOKEN="${apiKey}"`
|
||||
highlighted = `${keyword('export')} ${variable('ANTHROPIC_BASE_URL')}${operator('=')}${string(`"${baseUrl}"`)}
|
||||
${keyword('export')} ${variable('ANTHROPIC_AUTH_TOKEN')}${operator('=')}${string(`"${apiKey}"`)}`
|
||||
break
|
||||
case 'cmd':
|
||||
path = 'Command Prompt'
|
||||
content = `set ANTHROPIC_BASE_URL=${baseUrl}
|
||||
set ANTHROPIC_AUTH_TOKEN=${apiKey}`
|
||||
highlighted = `${keyword('set')} ${variable('ANTHROPIC_BASE_URL')}${operator('=')}${baseUrl}
|
||||
${keyword('set')} ${variable('ANTHROPIC_AUTH_TOKEN')}${operator('=')}${apiKey}`
|
||||
break
|
||||
case 'powershell':
|
||||
path = 'PowerShell'
|
||||
content = `$env:ANTHROPIC_BASE_URL="${baseUrl}"
|
||||
$env:ANTHROPIC_AUTH_TOKEN="${apiKey}"`
|
||||
highlighted = `${keyword('$env:')}${variable('ANTHROPIC_BASE_URL')}${operator('=')}${string(`"${baseUrl}"`)}
|
||||
${keyword('$env:')}${variable('ANTHROPIC_AUTH_TOKEN')}${operator('=')}${string(`"${apiKey}"`)}`
|
||||
break
|
||||
default:
|
||||
path = 'Terminal'
|
||||
content = ''
|
||||
highlighted = ''
|
||||
}
|
||||
|
||||
return [{ path, content, highlighted }]
|
||||
return [{ path, content }]
|
||||
}
|
||||
|
||||
function generateOpenAIFiles(baseUrl: string, apiKey: string): FileConfig[] {
|
||||
@@ -301,40 +285,20 @@ base_url = "${baseUrl}"
|
||||
wire_api = "responses"
|
||||
requires_openai_auth = true`
|
||||
|
||||
const configHighlighted = `${key('model_provider')} ${operator('=')} ${string('"sub2api"')}
|
||||
${key('model')} ${operator('=')} ${string('"gpt-5.2-codex"')}
|
||||
${key('model_reasoning_effort')} ${operator('=')} ${string('"high"')}
|
||||
${key('network_access')} ${operator('=')} ${string('"enabled"')}
|
||||
${key('disable_response_storage')} ${operator('=')} ${keyword('true')}
|
||||
${key('windows_wsl_setup_acknowledged')} ${operator('=')} ${keyword('true')}
|
||||
${key('model_verbosity')} ${operator('=')} ${string('"high"')}
|
||||
|
||||
${comment('[model_providers.sub2api]')}
|
||||
${key('name')} ${operator('=')} ${string('"sub2api"')}
|
||||
${key('base_url')} ${operator('=')} ${string(`"${baseUrl}"`)}
|
||||
${key('wire_api')} ${operator('=')} ${string('"responses"')}
|
||||
${key('requires_openai_auth')} ${operator('=')} ${keyword('true')}`
|
||||
|
||||
// auth.json content
|
||||
const authContent = `{
|
||||
"OPENAI_API_KEY": "${apiKey}"
|
||||
}`
|
||||
|
||||
const authHighlighted = `{
|
||||
${key('"OPENAI_API_KEY"')}: ${string(`"${apiKey}"`)}
|
||||
}`
|
||||
|
||||
return [
|
||||
{
|
||||
path: `${configDir}/config.toml`,
|
||||
content: configContent,
|
||||
highlighted: configHighlighted,
|
||||
hint: t('keys.useKeyModal.openai.configTomlHint')
|
||||
},
|
||||
{
|
||||
path: `${configDir}/auth.json`,
|
||||
content: authContent,
|
||||
highlighted: authHighlighted
|
||||
content: authContent
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { getPublicSettings } from '@/api/auth'
|
||||
import { sanitizeUrl } from '@/utils/url'
|
||||
|
||||
const siteName = ref('Sub2API')
|
||||
const siteLogo = ref('')
|
||||
@@ -74,7 +75,7 @@ onMounted(async () => {
|
||||
try {
|
||||
const settings = await getPublicSettings()
|
||||
siteName.value = settings.site_name || 'Sub2API'
|
||||
siteLogo.value = settings.site_logo || ''
|
||||
siteLogo.value = sanitizeUrl(settings.site_logo || '', { allowRelative: true })
|
||||
siteSubtitle.value = settings.site_subtitle || 'Subscription to API Conversion Platform'
|
||||
} catch (error) {
|
||||
console.error('Failed to load public settings:', error)
|
||||
|
||||
Reference in New Issue
Block a user