/** * 统一工具接口 * 所有外部模块只能通过此文件访问功能 */ const otp = require('./otp'); const storage = require('./storage'); const format = require('./format'); const cloud = require('./cloud'); const ui = require('./ui'); const base32 = require('./base32'); const crypto = require('./crypto'); const eventManager = require('./eventManager'); /** * 验证token是否在当前时间窗口内 * @param {Object} token - 令牌对象 * @param {number} timestamp - 当前时间戳(秒) * @param {number} period - TOTP周期(秒) * @returns {boolean} 是否在同一时间窗口 */ function verifyTokenWindow(token, timestamp, period) { if (!token || !token.timestamp) return false; const currentCounter = Math.floor(timestamp / period); const tokenCounter = Math.floor(token.timestamp / period); return currentCounter === tokenCounter; } // ============ OTP相关功能 ============ /** * 生成当前时间的验证码 * @param {Object} config - 配置对象 * @param {string} config.type - OTP类型 ('totp' 或 'hotp') * @param {string} config.secret - Base32编码的密钥 * @param {string} [config.algorithm='SHA1'] - 使用的哈希算法 * @param {number} [config.period=30] - TOTP的时间周期(秒) * @param {number} [config.digits=6] - OTP的位数 * @param {number} [config.counter] - HOTP的计数器值 * @param {boolean} [config._forceRefresh=false] - 是否强制刷新 * @returns {Promise} 生成的验证码 */ const generateCode = async (config) => { try { const options = { algorithm: config.algorithm || 'SHA1', digits: Number(config.digits) || 6, _forceRefresh: !!config._forceRefresh, timestamp: config.timestamp || otp.getCurrentTimestamp() }; if (config.type === 'otpauth') { // 智能处理otpauth类型 if (!config.secret) { throw new Error('otpauth类型必须提供secret参数'); } if (!config.secret.startsWith('otpauth://')) { // 如果不是otpauth URI格式,自动转换为TOTP处理 config.type = 'totp'; } } if (config.type === 'totp') { options.period = Number(config.period) || 30; // 如果提供了token对象且不需要强制刷新,验证时间窗口 if (config.token && !options._forceRefresh) { if (verifyTokenWindow(config.token, options.timestamp, options.period)) { return config.token.code; // 仍在同一窗口,返回缓存的code } } } else if (config.type === 'hotp') { if (config.counter === undefined) { throw new Error('HOTP需要计数器值'); } options.counter = Number(config.counter); } else { throw new Error('不支持的OTP类型'); } return await otp.generate(config.type, config.secret, options); } catch (error) { console.error('[Error] Failed to generate code:', error); throw error; } }; /** * 验证OTP码 * @param {string} token - 要验证的OTP码 * @param {Object} config - 配置对象(与generateCode相同) * @returns {Promise} 验证结果 */ const verifyCode = async (token, config) => { try { const options = { algorithm: config.algorithm || 'SHA1', digits: Number(config.digits) || 6, timestamp: otp.getCurrentTimestamp() // 使用统一的时间戳获取方法 }; if (config.type === 'otpauth') { // otpauth URI由底层otp.verify处理 } else if (config.type === 'totp') { options.period = Number(config.period) || 30; } else if (config.type === 'hotp') { if (config.counter === undefined) { throw new Error('HOTP需要计数器值'); } options.counter = Number(config.counter); } else { throw new Error('不支持的OTP类型'); } return await otp.verify(token, config.type, config.secret, options); } catch (error) { console.error('[Error] Failed to verify code:', error); throw error; } }; /** * 获取TOTP剩余秒数 * @param {number} [period=30] - TOTP周期(秒) * @returns {number} 剩余秒数 */ const getRemainingSeconds = (period = 30) => { try { // 使用otp模块的getRemainingSeconds,它已经使用了getCurrentTimestamp return otp.getRemainingSeconds({ period }); } catch (error) { console.error('[Error] Failed to get remaining seconds:', error); throw error; } }; // ============ 令牌管理相关功能 ============ /** * 添加新令牌 * @param {Object} tokenData - 令牌数据 * @returns {Promise} */ const addToken = async (tokenData) => { // 验证令牌数据 const errors = format.validateToken(tokenData); if (errors) { throw new Error(errors.join('; ')); } // 生成唯一ID const id = `token_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; const now = formatTime(new Date()); const token = { ...tokenData, id, createTime: now, lastUpdate: now, code: '' // 初始化为空字符串 }; // 对于HOTP类型,添加counter字段 if (tokenData.type && tokenData.type.toUpperCase() === 'HOTP') { token.counter = 0; // HOTP类型需要counter >= 0 } // 对于TOTP类型,不设置counter字段,让它在JSON序列化时被忽略 // 验证是否可以生成代码 try { await generateCode(token); } catch (error) { throw new Error('无效的令牌配置: ' + error.message); } // 保存令牌 await storage.addToken(token); }; /** * 从URL添加令牌 * @param {string} url - OTP URL * @returns {Promise} */ const addTokenFromUrl = async (url) => { const tokenData = format.parseURL(url); if (!tokenData) { throw new Error('无效的OTP URL'); } await addToken(tokenData); }; /** * 获取所有令牌 * @returns {Promise} */ const getTokens = async () => { return await storage.getTokens(); }; /** * 更新令牌 * @param {string} tokenId - 令牌ID * @param {Object} updates - 更新的字段 * @returns {Promise} */ const updateToken = async (tokenId, updates) => { await storage.updateToken(tokenId, updates); }; /** * 删除令牌 * @param {string} tokenId - 令牌ID * @returns {Promise} */ const deleteToken = async (tokenId) => { await storage.deleteToken(tokenId); }; // ============ 格式化相关功能 ============ /** * 格式化时间 * @param {Date} date - 日期对象 * @returns {string} 格式化后的时间字符串 */ const formatTime = (date) => { return format.formatTime(date); }; /** * 格式化日期 * @param {Date} date - 日期对象 * @returns {string} 格式化后的日期字符串 */ const formatDate = (date) => { return format.formatDate(date); }; // ============ 云同步相关功能 ============ /** * 同步令牌到云端 * @param {Array} tokens - 令牌列表 * @returns {Promise} 同步后的令牌列表 */ const syncTokens = async (tokens) => { try { // 上传本地令牌到云端 await cloud.uploadTokens(tokens); // 获取云端最新数据 const cloudData = await cloud.fetchLatestTokens(); // 使用cloud.js中的mergeTokens函数合并本地和云端数据 const mergedTokens = cloud.mergeTokens(tokens, cloudData.tokens, { preferCloud: true }); // 更新所有令牌的时间戳 for (const token of mergedTokens) { token.timestamp = cloudData.timestamp; } return mergedTokens; } catch (error) { console.error('同步令牌失败:', error); throw error; } }; /** * 从云端获取令牌 * @returns {Promise} 云端的令牌列表 */ const getCloudTokens = async () => { try { const cloudData = await cloud.fetchLatestTokens(); return cloudData.tokens; } catch (error) { // 如果是404错误(云端无数据),不打印错误日志 if (error.statusCode !== 404) { console.error('获取云端令牌失败:', error); } throw error; } }; // ============ UI相关功能 ============ /** * 显示加载提示 * @param {string} [title='加载中'] - 提示文字 */ const showLoading = (title = '加载中') => { ui.showLoading(title); }; /** * 隐藏加载提示 */ const hideLoading = () => { ui.hideLoading(); }; /** * 显示提示信息 * @param {string} title - 提示文字 * @param {string} [icon='none'] - 图标类型 */ const showToast = (title, icon = 'none') => { ui.showToast(title, icon); }; // ============ 加密相关功能 ============ /** * Base32编码 * @param {string|Uint8Array} input - 输入数据 * @returns {string} Base32编码字符串 */ const encodeBase32 = (input) => { return base32.encode(input); }; /** * Base32解码 * @param {string} input - Base32编码字符串 * @returns {Uint8Array} 解码后的数据 */ const decodeBase32 = (input) => { return base32.decode(input); }; /** * 生成随机密钥 * @param {number} [length=20] - 密钥长度(字节) * @returns {string} Base32编码的随机密钥 */ const generateSecret = (length = 20) => { return crypto.generateSecret(length); }; // 导出所有功能 // 从format模块重新导出验证和解析函数 const { validateToken, parseURL } = require('./format'); module.exports = { // OTP相关 generateCode, verifyCode, getRemainingSeconds, // 令牌管理 addToken, addTokenFromUrl, getTokens, updateToken, deleteToken, // 格式化 validateToken, // 添加验证函数 parseURL, // 添加解析函数 formatTime, formatDate, // 云同步 syncTokens, getCloudTokens, // UI showLoading, hideLoading, showToast, // 加密 encodeBase32, decodeBase32, generateSecret, // 事件管理 eventManager };