gongxiangp2/lib/brand-assets.ts
chenpei 7326e273e1 fix: harden media upload and release safety
Protect upload cleanup and atomic completion paths, add durable media worker leases and brand asset validation, and introduce verified migration/bootstrap/deployment gates.
2026-08-12 18:04:08 +08:00

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
}