UNPKG

@gooin/garmin-connect

Version:

Makes it simple to interface with Garmin Connect to get or set any data point

281 lines 10.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.RedisMFASessionStorage = void 0; /** * Redis实现的MFA会话存储 */ class RedisMFASessionStorage { constructor(redisUrl, redisToken, redisClient) { this.DEFAULT_TIMEOUT = 5 * 60 * 1000; // 5分钟 this.KEY_PREFIX = 'mfa:session:'; if (redisClient) { this.redis = redisClient; } else if (redisUrl && redisToken) { // 使用Upstash Redis try { const { Redis } = require('@upstash/redis'); this.redis = new Redis({ url: redisUrl, token: redisToken }); } catch (error) { throw new Error('Upstash Redis module not found. Please install @upstash/redis: npm install @upstash/redis'); } } else if (redisUrl) { // 动态导入redis模块 try { const Redis = require('ioredis'); this.redis = new Redis(redisUrl); } catch (error) { throw new Error('Redis module not found. Please install ioredis: npm install ioredis'); } } else { throw new Error('Either redisUrl with redisToken (for Upstash) or redisClient must be provided'); } } /** * 获取会话键名 */ getSessionKey(sessionId) { return `${this.KEY_PREFIX}${sessionId}`; } /** * 创建一个等待MFA验证码的Promise * @param sessionId 会话ID * @param timeout 超时时间(毫秒) * @returns Promise<string> 返回验证码 */ async waitForMFACode(sessionId, timeout = this.DEFAULT_TIMEOUT) { // 清理可能存在的旧会话 await this.cleanupRequest(sessionId); // 创建会话数据 const sessionData = { sessionId, status: 'waiting', createdAt: new Date().toISOString(), timeout: timeout }; const sessionKey = this.getSessionKey(sessionId); // 使用Redis的SET命令存储会话数据,并设置过期时间 await this.redis.setex(sessionKey, Math.ceil(timeout / 1000), // Redis的TTL是秒 JSON.stringify(sessionData)); console.log(`MFA验证会话已创建: ${sessionId}`); // 轮询检查验证码是否已提交 return new Promise((resolve, reject) => { const startTime = Date.now(); const checkInterval = setInterval(async () => { try { // 检查是否超时 if (Date.now() - startTime > timeout) { clearInterval(checkInterval); await this.cleanupRequest(sessionId); reject(new Error('MFA验证超时,请重新登录')); return; } // 检查会话状态 const data = await this.redis.get(sessionKey); if (!data) { clearInterval(checkInterval); reject(new Error('MFA验证会话已过期')); return; } // 确保data是字符串,如果是对象则先转换为字符串 const dataStr = typeof data === 'string' ? data : JSON.stringify(data); const session = JSON.parse(dataStr); // 如果状态已更新为resolved,则返回验证码 if (session.status === 'resolved' && session.code) { clearInterval(checkInterval); await this.cleanupRequest(sessionId); resolve(session.code); return; } // 如果状态已更新为rejected,则抛出错误 if (session.status === 'rejected' && session.error) { clearInterval(checkInterval); await this.cleanupRequest(sessionId); reject(new Error(session.error)); return; } } catch (error) { clearInterval(checkInterval); await this.cleanupRequest(sessionId); reject(error instanceof Error ? error : new Error(String(error))); } }, 1000); // 每秒检查一次 }); } /** * 提交MFA验证码 * @param sessionId 会话ID * @param code 验证码 * @returns 是否成功提交 */ async submitMFACode(sessionId, code) { const sessionKey = this.getSessionKey(sessionId); try { // 获取会话数据 const data = await this.redis.get(sessionKey); if (!data) { console.log(`未找到MFA验证会话: ${sessionId}`); return false; } // 确保data是字符串,如果是对象则先转换为字符串 const dataStr = typeof data === 'string' ? data : JSON.stringify(data); const session = JSON.parse(dataStr); if (session.status !== 'waiting') { console.log(`MFA验证会话状态不正确: ${sessionId}, 状态: ${session.status}`); return false; } // 更新会话状态为已解决 session.status = 'resolved'; session.code = code; session.resolvedAt = new Date().toISOString(); await this.redis.set(sessionKey, JSON.stringify(session)); console.log(`MFA验证码已提交: code ${code}, sessionId ${sessionId}`); return true; } catch (error) { console.log(`提交MFA验证码失败: ${sessionId}, 错误: ${error instanceof Error ? error.message : String(error)}`); return false; } } /** * 取消MFA验证 * @param sessionId 会话ID * @param reason 取消原因 * @returns 是否成功取消 */ async cancelMFARequest(sessionId, reason = 'MFA验证已取消') { const sessionKey = this.getSessionKey(sessionId); try { // 获取会话数据 const data = await this.redis.get(sessionKey); if (!data) { console.log(`未找到MFA验证会话: ${sessionId}`); return false; } // 确保data是字符串,如果是对象则先转换为字符串 const dataStr = typeof data === 'string' ? data : JSON.stringify(data); const session = JSON.parse(dataStr); // 更新会话状态为已拒绝 session.status = 'rejected'; session.error = reason; session.rejectedAt = new Date().toISOString(); await this.redis.set(sessionKey, JSON.stringify(session)); console.log(`MFA验证已取消: ${sessionId}, 原因: ${reason}`); return true; } catch (error) { console.log(`取消MFA验证失败: ${sessionId}, 错误: ${error instanceof Error ? error.message : String(error)}`); return false; } } /** * 检查会话是否存在 * @param sessionId 会话ID * @returns 是否存在 */ async hasSession(sessionId) { const sessionKey = this.getSessionKey(sessionId); try { const exists = await this.redis.exists(sessionKey); return exists === 1; } catch (_a) { return false; } } /** * 获取所有活跃的会话ID * @returns 会话ID列表 */ async getActiveSessions() { try { const keys = await this.redis.keys(`${this.KEY_PREFIX}*`); return keys.map((key) => key.replace(this.KEY_PREFIX, '')); } catch (error) { console.error('获取活跃会话失败:', error instanceof Error ? error : String(error)); return []; } } /** * 清理过期的请求 * @param maxAge 最大存活时间(毫秒) */ async cleanupExpiredRequests(maxAge = this.DEFAULT_TIMEOUT) { try { const keys = await this.redis.keys(`${this.KEY_PREFIX}*`); const now = new Date(); let cleanedCount = 0; for (const key of keys) { try { const data = await this.redis.get(key); if (!data) continue; // 确保data是字符串,如果是对象则先转换为字符串 const dataStr = typeof data === 'string' ? data : JSON.stringify(data); const session = JSON.parse(dataStr); const createdAt = new Date(session.createdAt); // 检查是否过期 if (now.getTime() - createdAt.getTime() > maxAge) { await this.redis.del(key); cleanedCount++; } } catch (error) { // 如果处理失败,直接删除 try { await this.redis.del(key); cleanedCount++; } catch (unlinkError) { console.error(`删除过期会话失败: ${key}`, unlinkError instanceof Error ? unlinkError : String(unlinkError)); } } } if (cleanedCount > 0) { console.log(`清理了 ${cleanedCount} 个过期的MFA验证会话`); } } catch (error) { console.error('清理过期请求失败:', error instanceof Error ? error : String(error)); } } /** * 清理指定会话的资源 * @param sessionId 会话ID */ async cleanupRequest(sessionId) { const sessionKey = this.getSessionKey(sessionId); try { await this.redis.del(sessionKey); } catch (error) { // 忽略错误 } } /** * 关闭Redis连接 */ async disconnect() { if (this.redis && typeof this.redis.disconnect === 'function') { await this.redis.disconnect(); } else if (this.redis && typeof this.redis.quit === 'function') { await this.redis.quit(); } } } exports.RedisMFASessionStorage = RedisMFASessionStorage; //# sourceMappingURL=RedisMFASessionStorage.js.map