Skip to main content
Glama

VibeDoge MCP Server

by chenxing3060
security.ts7.96 kB
import { randomBytes, createHash, timingSafeEqual } from 'crypto'; /** * 安全工具类 * 提供密码哈希、随机ID生成、数据验证等安全功能 */ export class SecurityUtils { /** * 生成随机用户ID * @returns 32位随机用户ID */ static generateUserId(): string { return randomBytes(16).toString('hex'); } /** * 生成随机会话ID * @returns 64位随机会话ID */ static generateSessionId(): string { return randomBytes(32).toString('hex'); } /** * 生成随机设备ID * @returns 32位随机设备ID */ static generateDeviceId(): string { return randomBytes(16).toString('hex'); } /** * 生成随机API密钥 * @returns 64位随机API密钥 */ static generateApiKey(): string { return randomBytes(32).toString('hex'); } /** * 生成随机盐值 * @param length 盐值长度(字节数,默认16) * @returns 随机盐值 */ static generateSalt(length: number = 16): string { return randomBytes(length).toString('hex'); } /** * 哈希密码 * @param password 原始密码 * @param salt 盐值(可选,不提供则自动生成) * @returns 哈希结果对象 */ static hashPassword(password: string, salt?: string): { hash: string; salt: string; } { const actualSalt = salt || this.generateSalt(); const hash = createHash('sha256') .update(password + actualSalt) .digest('hex'); return { hash, salt: actualSalt }; } /** * 验证密码 * @param password 原始密码 * @param hash 存储的哈希值 * @param salt 存储的盐值 * @returns 是否匹配 */ static verifyPassword(password: string, hash: string, salt: string): boolean { const { hash: computedHash } = this.hashPassword(password, salt); // 使用时间安全的比较函数防止时序攻击 return timingSafeEqual( Buffer.from(hash, 'hex'), Buffer.from(computedHash, 'hex') ); } /** * 生成数据签名 * @param data 要签名的数据 * @param secret 签名密钥 * @returns 签名字符串 */ static signData(data: string, secret: string): string { return createHash('sha256') .update(data + secret) .digest('hex'); } /** * 验证数据签名 * @param data 原始数据 * @param signature 签名 * @param secret 签名密钥 * @returns 是否有效 */ static verifySignature(data: string, signature: string, secret: string): boolean { const expectedSignature = this.signData(data, secret); return timingSafeEqual( Buffer.from(signature, 'hex'), Buffer.from(expectedSignature, 'hex') ); } /** * 生成随机字符串 * @param length 字符串长度 * @param charset 字符集(默认为字母数字) * @returns 随机字符串 */ static generateRandomString( length: number, charset: string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' ): string { let result = ''; const bytes = randomBytes(length); for (let i = 0; i < length; i++) { result += charset[bytes[i] % charset.length]; } return result; } /** * 验证邮箱格式 * @param email 邮箱地址 * @returns 是否有效 */ static isValidEmail(email: string): boolean { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); } /** * 验证用户名格式 * @param username 用户名 * @returns 是否有效 */ static isValidUsername(username: string): boolean { // 用户名:3-20位,只能包含字母、数字、下划线 const usernameRegex = /^[a-zA-Z0-9_]{3,20}$/; return usernameRegex.test(username); } /** * 验证密码强度 * @param password 密码 * @returns 验证结果 */ static validatePasswordStrength(password: string): { valid: boolean; score: number; issues: string[]; } { const issues: string[] = []; let score = 0; // 长度检查 if (password.length < 8) { issues.push('密码长度至少8位'); } else { score += 1; } // 包含小写字母 if (!/[a-z]/.test(password)) { issues.push('密码必须包含小写字母'); } else { score += 1; } // 包含大写字母 if (!/[A-Z]/.test(password)) { issues.push('密码必须包含大写字母'); } else { score += 1; } // 包含数字 if (!/\d/.test(password)) { issues.push('密码必须包含数字'); } else { score += 1; } // 包含特殊字符 if (!/[!@#$%^&*(),.?":{}|<>]/.test(password)) { issues.push('密码必须包含特殊字符'); } else { score += 1; } return { valid: issues.length === 0, score, issues }; } /** * 清理和验证输入数据 * @param input 输入字符串 * @param maxLength 最大长度 * @returns 清理后的字符串 */ static sanitizeInput(input: string, maxLength: number = 1000): string { if (typeof input !== 'string') { return ''; } return input .trim() .substring(0, maxLength) .replace(/[<>"'&]/g, ''); // 移除潜在的XSS字符 } /** * 生成CSRF令牌 * @returns CSRF令牌 */ static generateCSRFToken(): string { return randomBytes(32).toString('hex'); } /** * 验证CSRF令牌 * @param token 提供的令牌 * @param expectedToken 期望的令牌 * @returns 是否有效 */ static verifyCSRFToken(token: string, expectedToken: string): boolean { if (!token || !expectedToken) { return false; } return timingSafeEqual( Buffer.from(token, 'hex'), Buffer.from(expectedToken, 'hex') ); } /** * 生成安全的随机数 * @param min 最小值 * @param max 最大值 * @returns 随机数 */ static secureRandomInt(min: number, max: number): number { const range = max - min + 1; const bytesNeeded = Math.ceil(Math.log2(range) / 8); const maxValue = Math.pow(256, bytesNeeded); const threshold = maxValue - (maxValue % range); let randomValue; do { const randomBytes = require('crypto').randomBytes(bytesNeeded); randomValue = 0; for (let i = 0; i < bytesNeeded; i++) { randomValue = randomValue * 256 + randomBytes[i]; } } while (randomValue >= threshold); return min + (randomValue % range); } } /** * 速率限制器 */ export class RateLimiter { private requests: Map<string, number[]> = new Map(); private readonly windowMs: number; private readonly maxRequests: number; constructor(windowMs: number = 60000, maxRequests: number = 100) { this.windowMs = windowMs; this.maxRequests = maxRequests; } /** * 检查是否允许请求 * @param identifier 标识符(如IP地址、用户ID) * @returns 是否允许 */ isAllowed(identifier: string): boolean { const now = Date.now(); const windowStart = now - this.windowMs; // 获取或创建请求记录 let requests = this.requests.get(identifier) || []; // 清理过期的请求记录 requests = requests.filter(timestamp => timestamp > windowStart); // 检查是否超过限制 if (requests.length >= this.maxRequests) { return false; } // 记录新请求 requests.push(now); this.requests.set(identifier, requests); return true; } /** * 清理过期的记录 */ cleanup(): void { const now = Date.now(); const windowStart = now - this.windowMs; for (const [identifier, requests] of this.requests.entries()) { const validRequests = requests.filter(timestamp => timestamp > windowStart); if (validRequests.length === 0) { this.requests.delete(identifier); } else { this.requests.set(identifier, validRequests); } } } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/chenxing3060/vibedoge-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server