Protect upload cleanup and atomic completion paths, add durable media worker leases and brand asset validation, and introduce verified migration/bootstrap/deployment gates.
46 lines
1.3 KiB
TypeScript
46 lines
1.3 KiB
TypeScript
export const DEFAULT_HEADER_LOGO_URL = '/brand/header-logo.png'
|
|
export const DEFAULT_LOGIN_LOGO_URL = '/brand/login-logo.png'
|
|
export const DEFAULT_FAVICON_URL = '/brand/favicon.png'
|
|
|
|
export const DEFAULT_SITE_NAME = '由壹点3D素材'
|
|
export const BRAND_LOGO_FALLBACK_TEXT = '由壹点'
|
|
|
|
const LOCAL_BRAND_ASSET_PATTERN = /^\/brand\/[A-Za-z0-9][A-Za-z0-9._/-]*$/
|
|
|
|
/**
|
|
* 系统设置只保存可长期访问的品牌资源地址。
|
|
* 展示用的 /api/proxy-media 地址是临时派生值,绝不能写回数据库。
|
|
*/
|
|
export function isAllowedBrandLogoUrl(value: string): boolean {
|
|
const trimmed = value.trim()
|
|
if (!trimmed || trimmed.includes('..') || trimmed.includes('\\')) {
|
|
return false
|
|
}
|
|
|
|
if (LOCAL_BRAND_ASSET_PATTERN.test(trimmed)) {
|
|
return true
|
|
}
|
|
|
|
try {
|
|
const parsed = new URL(trimmed)
|
|
return (
|
|
parsed.protocol === 'https:' &&
|
|
parsed.hostname === 's3.jiansun.vip' &&
|
|
parsed.port === '' &&
|
|
parsed.username === '' &&
|
|
parsed.password === '' &&
|
|
parsed.pathname.startsWith('/gongxiangpan/') &&
|
|
parsed.search === '' &&
|
|
parsed.hash === ''
|
|
)
|
|
} catch {
|
|
return false
|
|
}
|
|
}
|
|
|
|
export function normalizeBrandLogoUrl(value: unknown): string | null {
|
|
if (typeof value !== 'string') return null
|
|
const trimmed = value.trim()
|
|
return isAllowedBrandLogoUrl(trimmed) ? trimmed : null
|
|
}
|