File size: 2,816 Bytes
69b897d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
/**
 * 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<string>} 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
}