otp/utils/util.js
“xHuPo” 2b8870a40e init
2025-06-09 13:35:15 +08:00

343 lines
No EOL
8.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 统一工具接口
* 所有外部模块只能通过此文件访问功能
*/
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
};