init
This commit is contained in:
commit
2b8870a40e
51 changed files with 5845 additions and 0 deletions
343
utils/util.js
Normal file
343
utils/util.js
Normal 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
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue