feat:日志敏感信息过滤

This commit is contained in:
dzc committed 2026-10-29 15:46:36 +08:00
1 parent 0fafe8762f
commit 7bd1895d4d
4 files changed
+153 -1

No files matched your search

+33 -1
View File
@@ -21,10 +21,19 @@ export function fileAccessSignature(file: string, app: string, kid: string, ts:
if (!/^[A-Za-z0-9+/]+={0,2}$/.test(key) || CryptoJS.enc.Base64.parse(key).sigBytes < 32) throw new Error('Invalid file access key')
return CryptoJS.HmacSHA256(fileAccessCanonical(file, app, kid, ts), CryptoJS.enc.Base64.parse(key)).toString(CryptoJS.enc.Hex)
}
export function fileUploadCanonical(target: string, app: string, kid: string, ts: number, nonce: string): string {
if (!['upload', 'inner-upload', 'qn-upload'].includes(target)) throw new Error('Invalid file upload target')
return ['v1', 'oss-file-upload', 'POST', target, app, kid, String(ts), nonce].join('\n')
}
export function fileUploadSignature(target: string, app: string, kid: string, ts: number, nonce: string, key: string): string {
if (!/^[A-Za-z0-9+/]+={0,2}$/.test(key) || CryptoJS.enc.Base64.parse(key).sigBytes < 32) throw new Error('Invalid file upload key')
return CryptoJS.HmacSHA256(fileUploadCanonical(target, app, kid, ts, nonce), CryptoJS.enc.Base64.parse(key)).toString(CryptoJS.enc.Hex)
}
export function createFileAccess(options: FileAccessOptions) {
const now = options.now || Date.now
let offset = 0, ttl = 1800
const cache = new Map<string, { url: string; ts: number }>()
let uploadSequence = 0
const base = () => (options.baseUrl() || '').replace(/\/+$/, '')
const allowed = () => [origin(base()), ...(options.allowedOrigins || []).map(origin)].filter(Boolean)
function resolve(source?: string | null): string {
@@ -88,13 +97,36 @@ export function createFileAccess(options: FileAccessOptions) {
const query = head.slice(at + 1).split('&').filter(p => !FILE_ACCESS_FIELDS.includes(decode(p.split('=')[0])))
return head.slice(0, at) + (query.length ? '?' + query.join('&') : '') + hash
}
function uploadTarget(source: string): string | null {
const resolved = resolve(source), host = origin(resolved)
if ((host && !allowed().includes(host)) || (/^[a-z][a-z0-9+.-]*:/i.test(resolved) && !host)) return null
const path = (host ? resolved.replace(/^https?:\/\/[^/?#]+/i, '') : resolved).split(/[?#]/)[0]
const prefix = base().replace(/^https?:\/\/[^/?#]+/i, '')
for (const [target, suffix] of [['upload', 'upload'], ['inner-upload', 'inner-upload'], ['qn-upload', 'qn/upload']]) {
if (['/sys-file/', '/admin/sys-file/', '/api/sys-file/', '/api/admin/sys-file/', prefix + '/admin/sys-file/', prefix + '/sys-file/']
.some(start => path === start + suffix)) return target
}
return null
}
function uploadHeaders(source: string, fixedNonce?: string): Record<string, string> {
const target = uploadTarget(source)
if (!target) return {}
const seconds = Math.floor((now() + offset) / 1000)
// The nonce is a unique request identifier, not a secret; HMAC provides authorization.
const nonce = fixedNonce || Math.floor(now()).toString(36) + '-' + (++uploadSequence).toString(36) + '-'
+ Array.from({ length: 4 }, () => Math.floor(Math.random() * 0x100000000).toString(16).padStart(8, '0')).join('')
return {
'X-File-App-Id': options.appId, 'X-File-Key-Id': options.kid, 'X-File-Timestamp': String(seconds), 'X-File-Nonce': nonce,
'X-File-Signature': fileUploadSignature(target, options.appId, options.kid, seconds, nonce, options.key),
}
}
function synchronize(serverTime: number, ttlSeconds = ttl, roundTripMs = 0) {
if (!Number.isFinite(serverTime) || serverTime <= 0) return
offset = serverTime + roundTripMs / 2 - now()
if (Number.isFinite(ttlSeconds) && ttlSeconds > 300 && ttlSeconds <= 86400) ttl = ttlSeconds
cache.clear()
}
return { url, resolve, strip, protected: (s?: string | null) => !!parse(s), synchronize, clear: () => cache.clear(), ttl: () => ttl }
return { uploadHeaders, uploadTarget, url, resolve, strip, protected: (s?: string | null) => !!parse(s), synchronize, clear: () => cache.clear(), ttl: () => ttl }
}
const unescapeAttribute = (s: string) => s.replace(/&amp;/gi, '&').replace(/&quot;/gi, '"').replace(/&#(?:39|x27);/gi, "'").replace(/&#(x[0-9a-f]+|\d+);/gi, (_, n) => { const code = n[0].toLowerCase() === 'x' ? parseInt(n.slice(1), 16) : Number(n); return code <= 0x10ffff ? String.fromCodePoint(code) : ''; })
+8
View File
@@ -22,6 +22,7 @@ export const accessFileUrl = (source?: string | null) => isProtectedFile(source)
export const fileUrls = (sources: any) => Array.isArray(sources) ? sources.map(accessFileUrl) : []
export const originalFileUrl = (source?: string | null) => client.strip(source)
export const resolveFileUrl = (source?: string | null) => client.resolve(source)
export const fileUploadHeaders = (source: string) => client.uploadHeaders(source)
export const fileHtml = (html: string) => rewriteFileHtml(html, accessFileUrl)
export const stripFileHtml = (html: string) => rewriteFileHtml(html, originalFileUrl)
export const fileNodes = (nodes: any) => rewriteFileNodes(nodes, accessFileUrl)
@@ -63,6 +64,13 @@ export async function retryFileAccess(source?: string | null) {
return true
}
export function installFileAccess(app: any) {
if (typeof uni.addInterceptor === 'function') {
uni.addInterceptor('uploadFile', {
invoke(options: any) {
options.header = { ...(options.header || {}), ...fileUploadHeaders(options.url) }
},
})
}
Object.assign(app.config.globalProperties, {
$fileUrl: accessFileUrl, $fileUrls: fileUrls, $fileHtml: fileHtml, $fileNodes: fileNodes, $fileRetry: retryFileAccess,
})