This commit is contained in:
“xHuPo” 2025-06-09 13:35:15 +08:00
commit 2b8870a40e
51 changed files with 5845 additions and 0 deletions

343
utils/util.js Normal file
View file

@ -0,0 +1,343 @@
/**
* 统一工具接口
* 所有外部模块只能通过此文件访问功能
*/
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<string>} 生成的验证码
*/
const generateCode = async (config) => {
try {
const options = {
algorithm: config.algorithm || config.algo || '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<boolean>} 验证结果
*/
const verifyCode = async (token, config) => {
try {
const options = {
algorithm: config.algorithm || config.algo || '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<void>}
*/
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 token = {
...tokenData,
id,
createTime: formatTime(new Date())
};
// 验证是否可以生成代码
try {
await generateCode(token);
} catch (error) {
throw new Error('无效的令牌配置: ' + error.message);
}
// 保存令牌
await storage.addToken(token);
};
/**
* 从URL添加令牌
* @param {string} url - OTP URL
* @returns {Promise<void>}
*/
const addTokenFromUrl = async (url) => {
const tokenData = format.parseURL(url);
if (!tokenData) {
throw new Error('无效的OTP URL');
}
await addToken(tokenData);
};
/**
* 获取所有令牌
* @returns {Promise<Array>}
*/
const getTokens = async () => {
return await storage.getTokens();
};
/**
* 更新令牌
* @param {string} tokenId - 令牌ID
* @param {Object} updates - 更新的字段
* @returns {Promise<void>}
*/
const updateToken = async (tokenId, updates) => {
await storage.updateToken(tokenId, updates);
};
/**
* 删除令牌
* @param {string} tokenId - 令牌ID
* @returns {Promise<void>}
*/
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<Array>} 同步后的令牌列表
*/
const syncTokens = async (tokens) => {
return await cloud.syncTokens(tokens);
};
/**
* 从云端获取令牌
* @returns {Promise<Array>} 云端的令牌列表
*/
const getCloudTokens = async () => {
return await cloud.getTokens();
};
// ============ 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
};