更新完善页面

This commit is contained in:
董海洋
2026-06-03 14:15:55 +08:00
parent 4b7ae9c933
commit 1675662537
57 changed files with 7625 additions and 883 deletions
+112
View File
@@ -0,0 +1,112 @@
/**
* JWTJSON Web Token)工具函数
* @module services/utils/jwt
*/
const crypto = require('crypto')
const SECRET = process.env.JWT_SECRET || (() => {
if (process.env.NODE_ENV === 'production') {
throw new Error('JWT_SECRET environment variable is required in production')
}
return 'dev-only-jwt-secret-change-in-production-' + crypto.randomBytes(16).toString('hex')
})()
const ACCESS_TTL = parseInt(process.env.JWT_ACCESS_TTL || 7 * 24 * 3600)
const REFRESH_TTL = parseInt(process.env.JWT_REFRESH_TTL || 30 * 24 * 3600)
const ISSUER = 'miniprogram'
const ALG = 'HS256'
/**
* 编码为 URL 安全的 base64
* @param {string|Buffer} input - 输入
* @returns {string} 编码后的字符串
*/
function base64url(input) {
const buf = Buffer.isBuffer(input) ? input : Buffer.from(input)
return buf.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
}
/**
* 解码 URL 安全的 base64
* @param {string} input - 编码后的字符串
* @returns {Buffer} 解码后的 Buffer
*/
function base64urlDecode(input) {
const pad = 4 - (input.length % 4)
const normalized = input.replace(/-/g, '+').replace(/_/g, '/') + (pad < 4 ? '='.repeat(pad) : '')
return Buffer.from(normalized, 'base64')
}
/**
* 签名 JWT
* @param {Object} payload - JWT 载荷
* @param {number} [ttlSeconds=ACCESS_TTL] - TTL(秒)
* @returns {string} JWT token
*/
function sign(payload, ttlSeconds = ACCESS_TTL) {
const now = Math.floor(Date.now() / 1000)
const header = { alg: ALG, typ: 'JWT' }
const body = {
...payload,
iat: now,
exp: now + ttlSeconds,
iss: ISSUER
}
const head = base64url(JSON.stringify(header))
const data = base64url(JSON.stringify(body))
const sig = crypto.createHmac('sha256', SECRET).update(`${head}.${data}`).digest()
return `${head}.${data}.${base64url(sig)}`
}
/**
* 验证 JWT
* @param {string} token - JWT token
* @returns {Object|null} 验证后的载荷或 null
*/
function verify(token) {
if (!token || typeof token !== 'string') return null
const parts = token.split('.')
if (parts.length !== 3) return null
const [head, data, sig] = parts
const expected = crypto.createHmac('sha256', SECRET).update(`${head}.${data}`).digest()
const provided = base64urlDecode(sig)
if (expected.length !== provided.length || !crypto.timingSafeEqual(expected, provided)) {
return null
}
try {
const payload = JSON.parse(base64urlDecode(data).toString('utf8'))
if (payload.exp && Math.floor(Date.now() / 1000) >= payload.exp) return null
if (payload.iss && payload.iss !== ISSUER) return null
return payload
} catch {
return null
}
}
/**
* 签名访问令牌
* @param {Object} user - 用户对象
* @returns {string} 访问令牌
*/
function signAccess(user) {
return sign({ sub: user.id, role: user.role, type: 'access' }, ACCESS_TTL)
}
/**
* 签名刷新令牌
* @param {Object} user - 用户对象
* @returns {string} 刷新令牌
*/
function signRefresh(user) {
return sign({ sub: user.id, type: 'refresh' }, REFRESH_TTL)
}
module.exports = {
sign,
verify,
signAccess,
signRefresh,
ACCESS_TTL,
REFRESH_TTL
}