/** * Token 脱敏工具 * 用于在日志中安全显示 token,只显示70%的内容,其余用*代替 */ /** * 对 token 进行脱敏处理 * @param {string} token - 需要脱敏的 token * @param {number} visiblePercent - 可见部分的百分比,默认 70 * @returns {string} 脱敏后的 token */ function maskToken(token, visiblePercent = 70) { if (!token || typeof token !== 'string') { return '[EMPTY]' } const { length } = token // 对于非常短的 token,至少隐藏一部分 if (length <= 2) { return '*'.repeat(length) } if (length <= 5) { return token.slice(0, 1) + '*'.repeat(length - 1) } if (length <= 10) { const visibleLength = Math.min(5, length - 2) const front = token.slice(0, visibleLength) return front + '*'.repeat(length - visibleLength) } // 计算可见字符数量 const visibleLength = Math.floor(length * (visiblePercent / 100)) // 在前部和尾部分配可见字符 const frontLength = Math.ceil(visibleLength * 0.6) const backLength = visibleLength - frontLength // 构建脱敏后的 token const front = token.slice(0, frontLength) const back = token.slice(-backLength) const middle = '*'.repeat(length - visibleLength) return `${front}${middle}${back}` } /** * 对包含 token 的对象进行脱敏处理 * @param {Object} obj - 包含 token 的对象 * @param {Array} tokenFields - 需要脱敏的字段名列表 * @returns {Object} 脱敏后的对象副本 */ function maskTokensInObject( obj, tokenFields = ['accessToken', 'refreshToken', 'access_token', 'refresh_token'] ) { if (!obj || typeof obj !== 'object') { return obj } const masked = { ...obj } tokenFields.forEach((field) => { if (masked[field]) { masked[field] = maskToken(masked[field]) } }) return masked } /** * 格式化 token 刷新日志 * @param {string} accountId - 账户 ID * @param {string} accountName - 账户名称 * @param {Object} tokens - 包含 access_token 和 refresh_token 的对象 * @param {string} status - 刷新状态 (success/failed) * @param {string} message - 额外的消息 * @returns {Object} 格式化的日志对象 */ function formatTokenRefreshLog(accountId, accountName, tokens, status, message = '') { const log = { timestamp: new Date().toISOString(), event: 'token_refresh', accountId, accountName, status, message } if (tokens) { log.tokens = { accessToken: tokens.accessToken ? maskToken(tokens.accessToken) : '[NOT_PROVIDED]', refreshToken: tokens.refreshToken ? maskToken(tokens.refreshToken) : '[NOT_PROVIDED]', expiresAt: tokens.expiresAt || '[NOT_PROVIDED]' } } return log } module.exports = { maskToken, maskTokensInObject, formatTokenRefreshLog }