/** * OTP (One-Time Password) 工具集 * 包含TOTP和HOTP的实现,以及相关辅助功能 */ const { generateTOTP: baseTOTP, getRemainingSeconds: baseGetRemainingSeconds } = require('./totp'); const { generateHOTP } = require('./hotp'); const { parseURL } = require('./format'); /** * 获取当前时间戳(秒) * 确保在同一时间窗口内使用相同的时间戳 * @returns {number} 当前时间戳(秒) */ function getCurrentTimestamp() { return Math.floor(Date.now() / 1000); } /** * 生成OTP值 * * @param {string} type - OTP类型('totp'或'hotp') * @param {string} secret - Base32编码的密钥 * @param {Object} options - 配置选项 * @param {number} [options.counter] - 计数器值(仅HOTP需要) * @param {number} [options.timestamp] - 用于TOTP的时间戳(秒) * @param {boolean} [options._forceRefresh] - 是否强制刷新,不使用缓存 * @returns {Promise} 生成的OTP值 * @throws {Error} 参数无效时抛出错误 */ async function generateOTP(type, secret, options = {}) { // 处理otpauth URI if (type === 'otpauth') { const parsed = parseURL(secret); if (!parsed) { throw new Error('Invalid otpauth URI format'); } // 使用解析出的类型和参数 return await generateOTP(parsed.type, parsed.secret, { ...options, ...(parsed.type === 'totp' ? { period: parsed.period } : {}), ...(parsed.type === 'hotp' ? { counter: parsed.counter } : {}), algorithm: parsed.algorithm, digits: parsed.digits }); } if (type === 'totp') { const totpOptions = { ...options, timestamp: options.timestamp || getCurrentTimestamp(), _forceRefresh: !!options._forceRefresh }; return await baseTOTP(secret, totpOptions); } else if (type === 'hotp') { if (options.counter === undefined) { throw new Error('Counter is required for HOTP'); } return await generateHOTP(secret, options.counter, options); } else { throw new Error(`Unsupported OTP type: ${type}`); } } /** * 验证OTP值 * * @param {string} token - 要验证的OTP值 * @param {string} type - OTP类型('totp'或'hotp') * @param {string} secret - Base32编码的密钥 * @param {Object} options - 配置选项 * @param {number} [options.counter] - 当前计数器值(仅HOTP需要) * @returns {Promise} 验证结果 * @throws {Error} 参数无效时抛出错误 */ async function verifyOTP(token, type, secret, options = {}) { // 处理otpauth URI if (type === 'otpauth') { const parsed = parseURL(secret); if (!parsed) { throw new Error('Invalid otpauth URI format'); } // 使用解析出的类型和参数 return await verifyOTP(token, parsed.type, parsed.secret, { ...options, ...(parsed.type === 'totp' ? { period: parsed.period } : {}), ...(parsed.type === 'hotp' ? { counter: parsed.counter } : {}), algorithm: parsed.algorithm, digits: parsed.digits }); } if (type === 'totp') { // 始终使用基础版本TOTP,确保一致性 const generatedToken = await baseTOTP(secret, { ...options, timestamp: options.timestamp || getCurrentTimestamp() }); return generatedToken === token; } else if (type === 'hotp') { if (options.counter === undefined) { throw new Error('Counter is required for HOTP'); } const generatedToken = await generateHOTP(secret, options.counter, options); return generatedToken === token; } else { throw new Error(`Unsupported OTP type: ${type}`); } } /** * 获取TOTP剩余秒数 * @param {Object} options - 配置选项 * @param {number} [options.period=30] - TOTP周期(秒) * @param {number} [options.timestamp] - 指定时间戳(秒) * @returns {number} 剩余秒数 */ function getRemainingSeconds(options = {}) { // 始终使用基础版本,确保一致性 // 确保传递正确的参数 const period = options.period || 30; const timestamp = options.timestamp || getCurrentTimestamp(); const remaining = baseGetRemainingSeconds({ ...options, period, timestamp }); return remaining; } // 导出统一接口 module.exports = { now: async (type, secret, counter) => { if (type === 'totp') { // 始终使用基础版本TOTP,确保一致性 return await baseTOTP(secret, { timestamp: getCurrentTimestamp(), _forceRefresh: true }); } else if (type === 'hotp') { return await generateHOTP(secret, counter); } else { throw new Error(`Unsupported OTP type: ${type}`); } }, generate: generateOTP, verify: verifyOTP, getRemainingSeconds, getCurrentTimestamp };