Google Calendar MCP Server

by takumi0706
Verified
// src/auth/token-manager.ts import crypto from 'crypto'; import logger from '../utils/logger'; /** * TokenManager - セキュアなトークン管理クラス * * トークンを暗号化してメモリ内に保存し、必要に応じて復号化して取得する * AES-256-GCM暗号化を使用して高いセキュリティを提供 */ class TokenManager { private algorithm = 'aes-256-gcm'; private encryptionKey: Buffer; private tokens: Map<string, string> = new Map(); private tokenExpirations: Map<string, number> = new Map(); private cleanupInterval: NodeJS.Timeout | null = null; constructor() { // 環境変数から暗号化キーを取得するか、ランダムに生成する const keyString = process.env.TOKEN_ENCRYPTION_KEY || crypto.randomBytes(32).toString('hex'); this.encryptionKey = Buffer.from(keyString, 'hex'); if (typeof logger.info === 'function') { logger.info('TokenManager initialized with secure encryption'); } // 定期的に期限切れトークンをクリーンアップ this.cleanupInterval = setInterval(this.cleanupExpiredTokens.bind(this), 60 * 60 * 1000); // 1時間ごと } /** * トークンを暗号化して保存 * * @param userId ユーザーID * @param token 保存するトークン * @param expiresIn トークンの有効期限(ミリ秒)、デフォルト30日 */ public storeToken(userId: string, token: string, expiresIn: number = 30 * 24 * 60 * 60 * 1000): void { try { const iv = crypto.randomBytes(16); const cipher = crypto.createCipheriv(this.algorithm, this.encryptionKey, iv); let encrypted = cipher.update(token, 'utf8', 'hex'); encrypted += cipher.final('hex'); // crypto.Cipher.prototype.getAuthTag は @types/node に定義されていないが、実際のNodeJSには存在する const authTag = (cipher as any).getAuthTag(); // 初期化ベクトル、認証タグ、暗号文を連結して保存 const tokenData = `${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted}`; this.tokens.set(userId, tokenData); // 有効期限を設定 const expiryTime = Date.now() + expiresIn; this.tokenExpirations.set(userId, expiryTime); if (typeof logger.debug === 'function') { logger.debug(`Token stored for user: ${userId}, expires: ${new Date(expiryTime).toISOString()}`); } } catch (err: unknown) { const error = err as Error; if (typeof logger.error === 'function') { logger.error('Failed to encrypt and store token', { userId, error: error.message }); } throw new Error('Token encryption failed'); } } /** * 保存されたトークンを復号化して取得 * * @param userId ユーザーID * @returns 復号化されたトークン、または存在しない場合はnull */ public getToken(userId: string): string | null { const tokenData = this.tokens.get(userId); if (!tokenData) { if (typeof logger.debug === 'function') { logger.debug(`No token found for user: ${userId}`); } return null; } // トークンの有効期限をチェック const expiry = this.tokenExpirations.get(userId); if (expiry && expiry < Date.now()) { if (typeof logger.debug === 'function') { logger.debug(`Token expired for user: ${userId}`); } this.removeToken(userId); return null; } try { const [ivHex, authTagHex, encrypted] = tokenData.split(':'); const iv = Buffer.from(ivHex, 'hex'); const authTag = Buffer.from(authTagHex, 'hex'); const decipher = crypto.createDecipheriv(this.algorithm, this.encryptionKey, iv); // crypto.Decipher.prototype.setAuthTag は @types/node に定義されていないが、実際のNodeJSには存在する (decipher as any).setAuthTag(authTag); let decrypted = decipher.update(encrypted, 'hex', 'utf8'); decrypted += decipher.final('utf8'); return decrypted; } catch (err: unknown) { const error = err as Error; if (typeof logger.error === 'function') { logger.error('Failed to decrypt token', { userId, error: error.message }); } return null; } } /** * トークンを削除 * * @param userId ユーザーID */ public removeToken(userId: string): void { this.tokens.delete(userId); this.tokenExpirations.delete(userId); if (typeof logger.debug === 'function') { logger.debug(`Token removed for user: ${userId}`); } } /** * 期限切れのトークンをクリーンアップ */ private cleanupExpiredTokens(): void { const now = Date.now(); let expiredCount = 0; for (const [userId, expiry] of this.tokenExpirations.entries()) { if (expiry < now) { this.removeToken(userId); expiredCount++; } } if (expiredCount > 0) { if (typeof logger.info === 'function') { logger.info(`Cleaned up ${expiredCount} expired tokens`); } } } /** * クリーンアップタイマーを停止し、リソースを解放する * テスト環境やアプリケーション終了時に呼び出すべき */ public stopCleanupTimer(): void { if (this.cleanupInterval) { clearInterval(this.cleanupInterval); this.cleanupInterval = null; if (typeof logger.debug === 'function') { logger.debug('TokenManager cleanup timer stopped'); } } } } // シングルトンインスタンスをエクスポート export const tokenManager = new TokenManager();