@gagandeep023/api-gateway 0.4.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/backend/index.d.mts +23 -3
- package/dist/backend/index.d.ts +23 -3
- package/dist/backend/index.js +164 -35
- package/dist/backend/index.js.map +1 -1
- package/dist/backend/index.mjs +149 -21
- package/dist/backend/index.mjs.map +1 -1
- package/dist/index.d.mts +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.js +166 -35
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +150 -21
- package/dist/index.mjs.map +1 -1
- package/dist/types/index.d.mts +26 -1
- package/dist/types/index.d.ts +26 -1
- package/dist/types/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/backend/middleware/gateway.ts","../../src/backend/services/RateLimiterService.ts","../../src/backend/services/AnalyticsService.ts","../../src/backend/services/DeviceRegistryService.ts","../../src/backend/utils/totp.ts","../../src/config/defaults.ts","../../src/backend/middleware/apiKeyAuth.ts","../../src/backend/middleware/ipFilter.ts","../../src/backend/middleware/rateLimiter.ts","../../src/backend/middleware/requestLogger.ts","../../src/backend/routes/gateway.ts","../../src/backend/routes/deviceAuth.ts"],"sourcesContent":["import { Router } from 'express';\nimport { RateLimiterService } from '../services/RateLimiterService';\nimport { AnalyticsService } from '../services/AnalyticsService';\nimport { DeviceRegistryService } from '../services/DeviceRegistryService';\nimport type { GatewayMiddlewareConfig } from '../../types';\nimport { DEFAULT_RATE_LIMIT_CONFIG, DEFAULT_IP_RULES, DEFAULT_API_KEYS } from '../../config/defaults';\nimport { createApiKeyAuth } from './apiKeyAuth';\nimport { createIpFilter } from './ipFilter';\nimport { createRateLimiter } from './rateLimiter';\nimport { createRequestLogger } from './requestLogger';\n\nexport interface GatewayInstances {\n rateLimiterService: RateLimiterService;\n analyticsService: AnalyticsService;\n deviceRegistry?: DeviceRegistryService;\n middleware: Router;\n config: Required<GatewayMiddlewareConfig>;\n}\n\nexport function createGatewayMiddleware(userConfig?: GatewayMiddlewareConfig): GatewayInstances {\n const config: Required<GatewayMiddlewareConfig> = {\n rateLimits: userConfig?.rateLimits ?? DEFAULT_RATE_LIMIT_CONFIG,\n ipRules: userConfig?.ipRules ?? DEFAULT_IP_RULES,\n apiKeys: userConfig?.apiKeys ?? DEFAULT_API_KEYS,\n deviceRegistryPath: userConfig?.deviceRegistryPath ?? '',\n };\n\n const rateLimiterService = new RateLimiterService(config.rateLimits);\n const analyticsService = new AnalyticsService();\n\n let deviceRegistry: DeviceRegistryService | undefined;\n if (config.deviceRegistryPath) {\n deviceRegistry = new DeviceRegistryService(config.deviceRegistryPath);\n }\n\n const router = Router();\n router.use(createRequestLogger(analyticsService));\n router.use(createApiKeyAuth(() => config.apiKeys, deviceRegistry));\n router.use(createIpFilter(() => config.ipRules));\n router.use(createRateLimiter(rateLimiterService));\n\n return {\n rateLimiterService,\n analyticsService,\n deviceRegistry,\n middleware: router,\n config,\n };\n}\n","import type { BucketState, SlidingWindowState, FixedWindowState, TierConfig, RateLimitConfig } from '../../types';\n\nclass TokenBucket {\n private buckets = new Map<string, BucketState>();\n\n tryConsume(ip: string, maxTokens: number, refillRate: number): { allowed: boolean; remaining: number; resetMs: number } {\n const now = Date.now();\n let bucket = this.buckets.get(ip);\n\n if (!bucket) {\n bucket = { tokens: maxTokens, lastRefill: now };\n this.buckets.set(ip, bucket);\n }\n\n const elapsed = (now - bucket.lastRefill) / 1000;\n bucket.tokens = Math.min(maxTokens, bucket.tokens + elapsed * refillRate);\n bucket.lastRefill = now;\n\n if (bucket.tokens >= 1) {\n bucket.tokens -= 1;\n const resetMs = bucket.tokens <= 0 ? Math.ceil((1 / refillRate) * 1000) : 0;\n return { allowed: true, remaining: Math.floor(bucket.tokens), resetMs };\n }\n\n const resetMs = Math.ceil(((1 - bucket.tokens) / refillRate) * 1000);\n return { allowed: false, remaining: 0, resetMs };\n }\n}\n\nclass SlidingWindowLog {\n private windows = new Map<string, SlidingWindowState>();\n\n tryConsume(ip: string, maxRequests: number, windowMs: number): { allowed: boolean; remaining: number; resetMs: number } {\n const now = Date.now();\n let state = this.windows.get(ip);\n\n if (!state) {\n state = { timestamps: [] };\n this.windows.set(ip, state);\n }\n\n state.timestamps = state.timestamps.filter(t => now - t < windowMs);\n\n if (state.timestamps.length < maxRequests) {\n state.timestamps.push(now);\n return {\n allowed: true,\n remaining: maxRequests - state.timestamps.length,\n resetMs: state.timestamps.length > 0 ? windowMs - (now - state.timestamps[0]) : windowMs,\n };\n }\n\n const oldestInWindow = state.timestamps[0];\n const resetMs = windowMs - (now - oldestInWindow);\n return { allowed: false, remaining: 0, resetMs };\n }\n}\n\nclass FixedWindowCounter {\n private windows = new Map<string, FixedWindowState>();\n\n tryConsume(ip: string, maxRequests: number, windowMs: number): { allowed: boolean; remaining: number; resetMs: number } {\n const now = Date.now();\n let state = this.windows.get(ip);\n\n if (!state || now - state.windowStart >= windowMs) {\n state = { count: 0, windowStart: now };\n this.windows.set(ip, state);\n }\n\n const resetMs = windowMs - (now - state.windowStart);\n\n if (state.count < maxRequests) {\n state.count++;\n return { allowed: true, remaining: maxRequests - state.count, resetMs };\n }\n\n return { allowed: false, remaining: 0, resetMs };\n }\n}\n\nexport class RateLimiterService {\n private tokenBucket = new TokenBucket();\n private slidingWindow = new SlidingWindowLog();\n private fixedWindow = new FixedWindowCounter();\n private globalWindow = new FixedWindowCounter();\n private config: RateLimitConfig;\n private _rateLimitHits = 0;\n\n constructor(config: RateLimitConfig) {\n this.config = config;\n }\n\n get rateLimitHits(): number {\n return this._rateLimitHits;\n }\n\n checkLimit(ip: string, tier: string): { allowed: boolean; remaining: number; resetMs: number; limit: number } {\n const globalResult = this.globalWindow.tryConsume(\n '__global__',\n this.config.globalLimit.maxRequests,\n this.config.globalLimit.windowMs\n );\n\n if (!globalResult.allowed) {\n this._rateLimitHits++;\n return { allowed: false, remaining: 0, resetMs: globalResult.resetMs, limit: this.config.globalLimit.maxRequests };\n }\n\n const tierConfig = this.config.tiers[tier] || this.config.tiers[this.config.defaultTier];\n\n if (!tierConfig || tierConfig.algorithm === 'none') {\n return { allowed: true, remaining: -1, resetMs: 0, limit: -1 };\n }\n\n let result: { allowed: boolean; remaining: number; resetMs: number };\n\n switch (tierConfig.algorithm) {\n case 'tokenBucket':\n result = this.tokenBucket.tryConsume(\n ip,\n tierConfig.maxRequests!,\n tierConfig.refillRate || 1\n );\n break;\n case 'slidingWindow':\n result = this.slidingWindow.tryConsume(\n ip,\n tierConfig.maxRequests!,\n tierConfig.windowMs!\n );\n break;\n case 'fixedWindow':\n result = this.fixedWindow.tryConsume(\n ip,\n tierConfig.maxRequests!,\n tierConfig.windowMs!\n );\n break;\n default:\n return { allowed: true, remaining: -1, resetMs: 0, limit: -1 };\n }\n\n if (!result.allowed) {\n this._rateLimitHits++;\n }\n\n return { ...result, limit: tierConfig.maxRequests! };\n }\n\n getConfig(): RateLimitConfig {\n return this.config;\n }\n}\n","import type { RequestLog, GatewayAnalytics } from '../../types';\n\nconst MAX_LOG_SIZE = 10000;\nconst ACTIVE_WINDOW_MS = 300000; // 5 minutes\n\nexport class AnalyticsService {\n private logs: RequestLog[] = [];\n private head = 0;\n private count = 0;\n\n addLog(log: RequestLog): void {\n if (this.count < MAX_LOG_SIZE) {\n this.logs.push(log);\n this.count++;\n } else {\n this.logs[this.head] = log;\n this.head = (this.head + 1) % MAX_LOG_SIZE;\n }\n }\n\n getRecentLogs(limit = 20, offset = 0): RequestLog[] {\n const ordered = this.getOrderedLogs();\n return ordered.slice(offset, offset + limit);\n }\n\n getAnalytics(rateLimitHits: number): GatewayAnalytics {\n const now = Date.now();\n const oneMinuteAgo = now - 60000;\n const activeWindowStart = now - ACTIVE_WINDOW_MS;\n const ordered = this.getOrderedLogs();\n\n const recentLogs = ordered.filter(l => l.timestamp > oneMinuteAgo);\n const requestsPerMinute = recentLogs.length;\n\n // Top endpoints\n const endpointCounts = new Map<string, number>();\n for (const log of ordered) {\n const current = endpointCounts.get(log.path) || 0;\n endpointCounts.set(log.path, current + 1);\n }\n const topEndpoints = Array.from(endpointCounts.entries())\n .map(([path, count]) => ({ path, count }))\n .sort((a, b) => b.count - a.count)\n .slice(0, 5);\n\n // Error rate\n const errorCount = ordered.filter(l => l.statusCode >= 400).length;\n const errorRate = this.count > 0 ? (errorCount / this.count) * 100 : 0;\n\n // Average response time\n const totalResponseTime = ordered.reduce((sum, l) => sum + l.responseTime, 0);\n const avgResponseTime = this.count > 0 ? totalResponseTime / this.count : 0;\n\n // Active clients: unique IPs in last 5 minutes\n const activeLogs = ordered.filter(l => l.timestamp > activeWindowStart);\n const uniqueIps = new Set(activeLogs.map(l => l.ip));\n\n // Active key uses: unique (IP + apiKey) pairs in last 5 minutes\n const keyUsePairs = new Set<string>();\n for (const log of activeLogs) {\n if (log.apiKey) {\n keyUsePairs.add(`${log.ip}::${log.apiKey}`);\n }\n }\n\n return {\n totalRequests: this.count,\n requestsPerMinute,\n topEndpoints,\n errorRate: Math.round(errorRate * 100) / 100,\n avgResponseTime: Math.round(avgResponseTime * 100) / 100,\n activeClients: uniqueIps.size,\n activeKeyUses: keyUsePairs.size,\n rateLimitHits,\n };\n }\n\n private getOrderedLogs(): RequestLog[] {\n if (this.count < MAX_LOG_SIZE) {\n return [...this.logs].reverse();\n }\n const tail = this.logs.slice(0, this.head);\n const headPart = this.logs.slice(this.head);\n return [...headPart, ...tail].reverse();\n }\n}\n","import fs from 'fs';\nimport path from 'path';\nimport { generateSecret } from '../utils/totp';\nimport type { DeviceEntry, DevicesFile } from '../../types';\n\nconst ONE_WEEK_MS = 7 * 24 * 60 * 60 * 1000;\nconst CLEANUP_INTERVAL_MS = 60 * 60 * 1000; // 1 hour\nconst DEBOUNCE_WRITE_MS = 2000;\nconst MAX_REGISTRATIONS_PER_IP_PER_MIN = 10;\nconst MAX_REGISTRATIONS_PER_IP_TOTAL = 30;\nconst RATE_WINDOW_MS = 60 * 1000; // 1 minute\n\nexport class DeviceRegistryService {\n private devices: Map<string, DeviceEntry> = new Map();\n private filePath: string;\n private writeTimeout: ReturnType<typeof setTimeout> | null = null;\n private cleanupInterval: ReturnType<typeof setInterval> | null = null;\n\n // In-memory rate tracking: ip -> timestamps of recent registration attempts\n private registrationAttempts: Map<string, number[]> = new Map();\n\n constructor(filePath: string) {\n this.filePath = filePath;\n this.loadFromDisk();\n this.cleanupExpired();\n this.cleanupInterval = setInterval(() => this.cleanupExpired(), CLEANUP_INTERVAL_MS);\n }\n\n private loadFromDisk(): void {\n try {\n if (fs.existsSync(this.filePath)) {\n const raw = fs.readFileSync(this.filePath, 'utf-8');\n const data: DevicesFile = JSON.parse(raw);\n for (const device of data.devices) {\n this.devices.set(device.browserId, device);\n }\n console.log(`[DeviceRegistry] Loaded ${this.devices.size} devices from ${this.filePath}`);\n } else {\n // Create the directory and empty file\n const dir = path.dirname(this.filePath);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n this.saveToDiskSync();\n console.log(`[DeviceRegistry] Created new devices file at ${this.filePath}`);\n }\n } catch (err) {\n console.error('[DeviceRegistry] Failed to load devices file:', err);\n }\n }\n\n private saveToDiskSync(): void {\n const data: DevicesFile = {\n devices: Array.from(this.devices.values()),\n };\n fs.writeFileSync(this.filePath, JSON.stringify(data, null, 2), 'utf-8');\n }\n\n private debouncedSave(): void {\n if (this.writeTimeout) clearTimeout(this.writeTimeout);\n this.writeTimeout = setTimeout(() => {\n this.saveToDiskSync();\n }, DEBOUNCE_WRITE_MS);\n }\n\n private cleanupExpired(): void {\n const now = Date.now();\n let removed = 0;\n for (const [id, device] of this.devices) {\n if (new Date(device.expiresAt).getTime() <= now) {\n this.devices.delete(id);\n removed++;\n }\n }\n if (removed > 0) {\n console.log(`[DeviceRegistry] Cleaned up ${removed} expired devices`);\n this.debouncedSave();\n }\n }\n\n private getActiveCountByIp(ip: string): number {\n const now = Date.now();\n let count = 0;\n for (const device of this.devices.values()) {\n if (device.ip === ip && device.active && new Date(device.expiresAt).getTime() > now) {\n count++;\n }\n }\n return count;\n }\n\n private checkRateLimit(ip: string): boolean {\n const now = Date.now();\n const attempts = this.registrationAttempts.get(ip) || [];\n // Remove attempts older than the rate window\n const recent = attempts.filter(t => now - t < RATE_WINDOW_MS);\n this.registrationAttempts.set(ip, recent);\n return recent.length < MAX_REGISTRATIONS_PER_IP_PER_MIN;\n }\n\n private recordAttempt(ip: string): void {\n const attempts = this.registrationAttempts.get(ip) || [];\n attempts.push(Date.now());\n this.registrationAttempts.set(ip, attempts);\n }\n\n registerDevice(\n browserId: string,\n ip: string,\n userAgent: string\n ): { success: true; device: DeviceEntry } | { success: false; error: string; status: number } {\n // Check rate limit: 10 per IP per minute\n if (!this.checkRateLimit(ip)) {\n return {\n success: false,\n error: 'Registration rate limit exceeded. Max 10 per minute per IP.',\n status: 429,\n };\n }\n\n this.recordAttempt(ip);\n\n // Check total cap: 30 active devices per IP\n if (this.getActiveCountByIp(ip) >= MAX_REGISTRATIONS_PER_IP_TOTAL) {\n return {\n success: false,\n error: 'Maximum device registrations reached for this IP. Max 30 per IP.',\n status: 403,\n };\n }\n\n // Check if this browserId is already registered and still valid\n const existing = this.devices.get(browserId);\n if (existing && existing.active && new Date(existing.expiresAt).getTime() > Date.now()) {\n // Re-registration: return existing secret, refresh expiry\n existing.expiresAt = new Date(Date.now() + ONE_WEEK_MS).toISOString();\n existing.lastSeen = new Date().toISOString();\n existing.lastIp = ip;\n this.debouncedSave();\n return { success: true, device: existing };\n }\n\n const now = new Date();\n const device: DeviceEntry = {\n browserId,\n sharedSecret: generateSecret(),\n ip,\n userAgent,\n registeredAt: now.toISOString(),\n expiresAt: new Date(now.getTime() + ONE_WEEK_MS).toISOString(),\n lastSeen: now.toISOString(),\n lastIp: ip,\n active: true,\n };\n\n this.devices.set(browserId, device);\n this.debouncedSave();\n return { success: true, device };\n }\n\n getDevice(browserId: string): DeviceEntry | null {\n const device = this.devices.get(browserId) || null;\n if (!device) return null;\n\n // Check expiry\n if (new Date(device.expiresAt).getTime() <= Date.now()) {\n this.devices.delete(browserId);\n this.debouncedSave();\n return null;\n }\n\n if (!device.active) return null;\n return device;\n }\n\n updateLastSeen(browserId: string, ip: string): void {\n const device = this.devices.get(browserId);\n if (!device) return;\n device.lastSeen = new Date().toISOString();\n if (device.lastIp !== ip) {\n console.log(`[DeviceRegistry] IP change for ${browserId}: ${device.lastIp} -> ${ip}`);\n device.lastIp = ip;\n }\n this.debouncedSave();\n }\n\n revokeDevice(browserId: string): boolean {\n const device = this.devices.get(browserId);\n if (!device) return false;\n device.active = false;\n this.debouncedSave();\n return true;\n }\n\n getStats(): { total: number; active: number; expired: number } {\n const now = Date.now();\n let active = 0;\n let expired = 0;\n for (const device of this.devices.values()) {\n if (!device.active || new Date(device.expiresAt).getTime() <= now) {\n expired++;\n } else {\n active++;\n }\n }\n return { total: this.devices.size, active, expired };\n }\n\n destroy(): void {\n if (this.cleanupInterval) clearInterval(this.cleanupInterval);\n if (this.writeTimeout) {\n clearTimeout(this.writeTimeout);\n this.saveToDiskSync(); // Final save\n }\n }\n}\n","import crypto from 'crypto';\n\nconst TIME_WINDOW_MS = 3600 * 1000; // 1 hour\nconst CODE_LENGTH = 16; // truncated HMAC hex chars\n\nexport function generateTOTP(browserId: string, secret: string, timeOffset: number = 0): string {\n const timeWindow = Math.floor(Date.now() / TIME_WINDOW_MS) + timeOffset;\n const message = `${browserId}:${timeWindow}`;\n const hmac = crypto.createHmac('sha256', secret).update(message).digest('hex');\n return hmac.substring(0, CODE_LENGTH);\n}\n\nexport function validateTOTP(browserId: string, secret: string, providedCode: string): boolean {\n const currentCode = generateTOTP(browserId, secret, 0);\n const previousCode = generateTOTP(browserId, secret, -1);\n return timingSafeEqual(providedCode, currentCode) || timingSafeEqual(providedCode, previousCode);\n}\n\nfunction timingSafeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) return false;\n const bufA = Buffer.from(a);\n const bufB = Buffer.from(b);\n return crypto.timingSafeEqual(bufA, bufB);\n}\n\nexport function formatKey(browserId: string, code: string): string {\n return `totp_${browserId}_${code}`;\n}\n\nexport function parseKey(key: string): { browserId: string; code: string } | null {\n if (!key.startsWith('totp_')) return null;\n const parts = key.slice(5).split('_');\n // UUID has 4 dashes, so browserId has 5 parts when split by _\n // Format: totp_<uuid>_<code> where uuid = xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\n // But UUID uses dashes, not underscores, so it's a single segment\n if (parts.length < 2) return null;\n const code = parts[parts.length - 1];\n const browserId = parts.slice(0, -1).join('_');\n if (!browserId || !code) return null;\n return { browserId, code };\n}\n\nexport function generateSecret(): string {\n return crypto.randomBytes(32).toString('hex');\n}\n","import type { RateLimitConfig, IpRules, ApiKeysConfig } from '../types';\n\nexport const DEFAULT_RATE_LIMIT_CONFIG: RateLimitConfig = {\n tiers: {\n free: { algorithm: 'tokenBucket', maxRequests: 100, windowMs: 60000, refillRate: 10 },\n pro: { algorithm: 'slidingWindow', maxRequests: 1000, windowMs: 60000 },\n unlimited: { algorithm: 'none' },\n },\n defaultTier: 'free',\n globalLimit: { maxRequests: 10000, windowMs: 60000 },\n};\n\nexport const DEFAULT_IP_RULES: IpRules = {\n allowlist: [],\n blocklist: [],\n mode: 'blocklist',\n};\n\nexport const DEFAULT_API_KEYS: ApiKeysConfig = {\n keys: [],\n};\n","import { Request, Response, NextFunction } from 'express';\nimport type { ApiKeysConfig } from '../../types';\nimport { parseKey, validateTOTP } from '../utils/totp';\nimport { DeviceRegistryService } from '../services/DeviceRegistryService';\n\nexport function createApiKeyAuth(\n getKeys: () => ApiKeysConfig,\n deviceRegistry?: DeviceRegistryService\n) {\n return function apiKeyAuth(req: Request, res: Response, next: NextFunction): void {\n const apiKey = req.header('X-API-Key') || req.query.apiKey as string;\n\n if (!apiKey) {\n (req as any).clientId = req.ip || 'unknown';\n (req as any).tier = 'free';\n next();\n return;\n }\n\n // TOTP key: totp_<browserId>_<code>\n if (apiKey.startsWith('totp_') && deviceRegistry) {\n const parsed = parseKey(apiKey);\n if (!parsed) {\n res.status(401).json({ error: 'Malformed TOTP key' });\n return;\n }\n\n const device = deviceRegistry.getDevice(parsed.browserId);\n if (!device) {\n res.status(401).json({ error: 'Device not registered or expired' });\n return;\n }\n\n if (!validateTOTP(parsed.browserId, device.sharedSecret, parsed.code)) {\n res.status(401).json({ error: 'Invalid or expired TOTP code' });\n return;\n }\n\n // Valid TOTP - update tracking\n const ip = req.ip || req.socket.remoteAddress || 'unknown';\n deviceRegistry.updateLastSeen(parsed.browserId, ip);\n\n (req as any).clientId = parsed.browserId;\n (req as any).tier = 'free';\n (req as any).apiKeyValue = apiKey;\n next();\n return;\n }\n\n // Static key: gw_live_*\n const config = getKeys();\n const keyEntry = config.keys.find(k => k.key === apiKey && k.active);\n\n if (!keyEntry) {\n res.status(401).json({ error: 'Invalid or revoked API key' });\n return;\n }\n\n (req as any).clientId = keyEntry.id;\n (req as any).tier = keyEntry.tier;\n (req as any).apiKeyValue = keyEntry.key;\n next();\n };\n}\n","import { Request, Response, NextFunction } from 'express';\nimport type { IpRules } from '../../types';\n\nexport function createIpFilter(getRules: () => IpRules) {\n return function ipFilter(req: Request, res: Response, next: NextFunction): void {\n const rules = getRules();\n const clientIp = req.ip || req.socket.remoteAddress || 'unknown';\n\n if (rules.mode === 'allowlist' && rules.allowlist.length > 0) {\n if (!rules.allowlist.includes(clientIp)) {\n res.status(403).json({ error: 'IP not in allowlist' });\n return;\n }\n }\n\n if (rules.mode === 'blocklist' && rules.blocklist.length > 0) {\n if (rules.blocklist.includes(clientIp)) {\n res.status(403).json({ error: 'IP is blocked' });\n return;\n }\n }\n\n next();\n };\n}\n","import { Request, Response, NextFunction } from 'express';\nimport { RateLimiterService } from '../services/RateLimiterService';\n\nexport function createRateLimiter(service: RateLimiterService) {\n return function rateLimiter(req: Request, res: Response, next: NextFunction): void {\n const ip = req.ip || req.socket.remoteAddress || 'unknown';\n const tier = (req as any).tier || 'free';\n\n const result = service.checkLimit(ip, tier);\n\n if (result.limit > 0) {\n res.setHeader('X-RateLimit-Limit', result.limit);\n res.setHeader('X-RateLimit-Remaining', Math.max(0, result.remaining));\n res.setHeader('X-RateLimit-Reset', Math.ceil(result.resetMs / 1000));\n }\n\n if (!result.allowed) {\n res.status(429).json({\n error: 'Rate limit exceeded',\n retryAfter: Math.ceil(result.resetMs / 1000),\n });\n return;\n }\n\n next();\n };\n}\n","import { Request, Response, NextFunction } from 'express';\nimport { AnalyticsService } from '../services/AnalyticsService';\n\nexport function createRequestLogger(analytics: AnalyticsService) {\n return function requestLogger(req: Request, res: Response, next: NextFunction): void {\n const start = Date.now();\n\n res.on('finish', () => {\n const responseTime = Date.now() - start;\n const apiKeyValue = (req as any).apiKeyValue || undefined;\n analytics.addLog({\n timestamp: Date.now(),\n method: req.method,\n path: req.originalUrl,\n statusCode: res.statusCode,\n responseTime,\n clientId: (req as any).clientId || req.ip || 'unknown',\n ip: req.ip || req.socket.remoteAddress || 'unknown',\n apiKey: apiKeyValue,\n authenticated: !!apiKeyValue,\n });\n });\n\n next();\n };\n}\n","import { Router, Request, Response } from 'express';\nimport { RateLimiterService } from '../services/RateLimiterService';\nimport { AnalyticsService } from '../services/AnalyticsService';\nimport type { GatewayMiddlewareConfig } from '../../types';\nimport crypto from 'crypto';\n\nexport interface GatewayRoutesOptions {\n rateLimiterService: RateLimiterService;\n analyticsService: AnalyticsService;\n config: Required<GatewayMiddlewareConfig>;\n}\n\nexport function createGatewayRoutes(options: GatewayRoutesOptions): Router {\n const { rateLimiterService, analyticsService, config } = options;\n const router = Router();\n\n // GET /analytics - Returns current analytics snapshot\n router.get('/analytics', (_req: Request, res: Response) => {\n const analytics = analyticsService.getAnalytics(rateLimiterService.rateLimitHits);\n res.json(analytics);\n });\n\n // GET /analytics/live - SSE stream pushing analytics every 5 seconds\n router.get('/analytics/live', (_req: Request, res: Response) => {\n res.setHeader('Content-Type', 'text/event-stream');\n res.setHeader('Cache-Control', 'no-cache');\n res.setHeader('Connection', 'keep-alive');\n res.setHeader('X-Accel-Buffering', 'no');\n res.flushHeaders();\n\n const send = (): void => {\n const analytics = analyticsService.getAnalytics(rateLimiterService.rateLimitHits);\n res.write(`data: ${JSON.stringify(analytics)}\\n\\n`);\n };\n\n send();\n const interval = setInterval(send, 5000);\n\n _req.on('close', () => {\n clearInterval(interval);\n });\n });\n\n // GET /config - Returns current gateway config\n router.get('/config', (_req: Request, res: Response) => {\n const analytics = analyticsService.getAnalytics(rateLimiterService.rateLimitHits);\n res.json({\n rateLimits: config.rateLimits,\n ipRules: config.ipRules,\n activeKeys: config.apiKeys.keys.filter(k => k.active).length,\n activeKeyUses: analytics.activeKeyUses,\n });\n });\n\n // POST /keys - Create a new API key\n router.post('/keys', (req: Request, res: Response) => {\n const { name, tier } = req.body;\n\n if (!name) {\n res.status(400).json({ error: 'Name is required' });\n return;\n }\n\n const newKey = {\n id: `key_${String(config.apiKeys.keys.length + 1).padStart(3, '0')}`,\n key: `gw_live_${crypto.randomBytes(16).toString('hex')}`,\n name,\n tier: tier || 'free',\n createdAt: new Date().toISOString(),\n active: true,\n };\n\n config.apiKeys.keys.push(newKey);\n res.status(201).json(newKey);\n });\n\n // DELETE /keys/:keyId - Revoke an API key\n router.delete('/keys/:keyId', (req: Request, res: Response) => {\n const { keyId } = req.params;\n const key = config.apiKeys.keys.find(k => k.id === keyId);\n\n if (!key) {\n res.status(404).json({ error: 'API key not found' });\n return;\n }\n\n key.active = false;\n res.json({ message: 'API key revoked', id: keyId });\n });\n\n // GET /logs - Returns recent request logs (paginated)\n router.get('/logs', (req: Request, res: Response) => {\n const limit = parseInt(req.query.limit as string) || 20;\n const offset = parseInt(req.query.offset as string) || 0;\n const logs = analyticsService.getRecentLogs(limit, offset);\n res.json({ logs, limit, offset });\n });\n\n return router;\n}\n","import { Router, Request, Response } from 'express';\nimport { DeviceRegistryService } from '../services/DeviceRegistryService';\n\nexport interface DeviceAuthRoutesOptions {\n deviceRegistry: DeviceRegistryService;\n}\n\nexport function createDeviceAuthRoutes(options: DeviceAuthRoutesOptions): Router {\n const { deviceRegistry } = options;\n const router = Router();\n\n // POST /register - Register a browser device\n router.post('/register', (req: Request, res: Response) => {\n const { browserId } = req.body;\n\n if (!browserId || typeof browserId !== 'string') {\n res.status(400).json({ error: 'browserId is required' });\n return;\n }\n\n // Basic UUID format validation\n const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n if (!uuidRegex.test(browserId)) {\n res.status(400).json({ error: 'browserId must be a valid UUID' });\n return;\n }\n\n const ip = req.ip || req.socket.remoteAddress || 'unknown';\n const userAgent = req.headers['user-agent'] || 'unknown';\n\n const result = deviceRegistry.registerDevice(browserId, ip, userAgent);\n\n if (!result.success) {\n res.status(result.status).json({ error: result.error });\n return;\n }\n\n res.status(201).json({\n browserId: result.device.browserId,\n sharedSecret: result.device.sharedSecret,\n expiresAt: result.device.expiresAt,\n });\n });\n\n // GET /status/:browserId - Check device registration status\n router.get('/status/:browserId', (req: Request, res: Response) => {\n const browserId = req.params.browserId as string;\n const device = deviceRegistry.getDevice(browserId);\n\n if (!device) {\n res.status(404).json({ registered: false });\n return;\n }\n\n res.json({\n registered: true,\n browserId: device.browserId,\n expiresAt: device.expiresAt,\n registeredAt: device.registeredAt,\n });\n });\n\n // DELETE /:browserId - Revoke a device (admin)\n router.delete('/:browserId', (req: Request, res: Response) => {\n const browserId = req.params.browserId as string;\n const revoked = deviceRegistry.revokeDevice(browserId);\n\n if (!revoked) {\n res.status(404).json({ error: 'Device not found' });\n return;\n }\n\n res.json({ message: 'Device revoked', browserId });\n });\n\n // GET /stats - Device registry stats\n router.get('/stats', (_req: Request, res: Response) => {\n res.json(deviceRegistry.getStats());\n });\n\n return router;\n}\n"],"mappings":"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","names":["resetMs","path","Router","crypto","Router"]}
|
|
1
|
+
{"version":3,"sources":["../../src/backend/middleware/gateway.ts","../../src/backend/services/RateLimiterService.ts","../../src/backend/services/FileLogWriter.ts","../../src/config/defaults.ts","../../src/backend/services/AnalyticsService.ts","../../src/backend/services/DeviceRegistryService.ts","../../src/backend/utils/totp.ts","../../src/backend/middleware/apiKeyAuth.ts","../../src/backend/middleware/ipFilter.ts","../../src/backend/middleware/rateLimiter.ts","../../src/backend/middleware/requestLogger.ts","../../src/backend/routes/gateway.ts","../../src/backend/routes/deviceAuth.ts"],"sourcesContent":["import { Router } from 'express';\nimport { RateLimiterService } from '../services/RateLimiterService';\nimport { AnalyticsService } from '../services/AnalyticsService';\nimport { DeviceRegistryService } from '../services/DeviceRegistryService';\nimport type { GatewayMiddlewareConfig } from '../../types';\nimport { DEFAULT_RATE_LIMIT_CONFIG, DEFAULT_IP_RULES, DEFAULT_API_KEYS } from '../../config/defaults';\nimport { createApiKeyAuth } from './apiKeyAuth';\nimport { createIpFilter } from './ipFilter';\nimport { createRateLimiter } from './rateLimiter';\nimport { createRequestLogger } from './requestLogger';\n\nexport interface GatewayInstances {\n rateLimiterService: RateLimiterService;\n analyticsService: AnalyticsService;\n deviceRegistry?: DeviceRegistryService;\n middleware: Router;\n config: Required<Omit<GatewayMiddlewareConfig, 'logConfig'>> & Pick<GatewayMiddlewareConfig, 'logConfig'>;\n}\n\nexport function createGatewayMiddleware(userConfig?: GatewayMiddlewareConfig): GatewayInstances {\n const config: Required<Omit<GatewayMiddlewareConfig, 'logConfig'>> & Pick<GatewayMiddlewareConfig, 'logConfig'> = {\n rateLimits: userConfig?.rateLimits ?? DEFAULT_RATE_LIMIT_CONFIG,\n ipRules: userConfig?.ipRules ?? DEFAULT_IP_RULES,\n apiKeys: userConfig?.apiKeys ?? DEFAULT_API_KEYS,\n deviceRegistryPath: userConfig?.deviceRegistryPath ?? '',\n logConfig: userConfig?.logConfig,\n };\n\n const rateLimiterService = new RateLimiterService(config.rateLimits);\n const analyticsService = new AnalyticsService(userConfig?.logConfig);\n\n let deviceRegistry: DeviceRegistryService | undefined;\n if (config.deviceRegistryPath) {\n deviceRegistry = new DeviceRegistryService(config.deviceRegistryPath);\n }\n\n const router = Router();\n router.use(createRequestLogger(analyticsService));\n router.use(createApiKeyAuth(() => config.apiKeys, deviceRegistry));\n router.use(createIpFilter(() => config.ipRules));\n router.use(createRateLimiter(rateLimiterService));\n\n return {\n rateLimiterService,\n analyticsService,\n deviceRegistry,\n middleware: router,\n config,\n };\n}\n","import type { BucketState, SlidingWindowState, FixedWindowState, TierConfig, RateLimitConfig } from '../../types';\n\nclass TokenBucket {\n private buckets = new Map<string, BucketState>();\n\n tryConsume(ip: string, maxTokens: number, refillRate: number): { allowed: boolean; remaining: number; resetMs: number } {\n const now = Date.now();\n let bucket = this.buckets.get(ip);\n\n if (!bucket) {\n bucket = { tokens: maxTokens, lastRefill: now };\n this.buckets.set(ip, bucket);\n }\n\n const elapsed = (now - bucket.lastRefill) / 1000;\n bucket.tokens = Math.min(maxTokens, bucket.tokens + elapsed * refillRate);\n bucket.lastRefill = now;\n\n if (bucket.tokens >= 1) {\n bucket.tokens -= 1;\n const resetMs = bucket.tokens <= 0 ? Math.ceil((1 / refillRate) * 1000) : 0;\n return { allowed: true, remaining: Math.floor(bucket.tokens), resetMs };\n }\n\n const resetMs = Math.ceil(((1 - bucket.tokens) / refillRate) * 1000);\n return { allowed: false, remaining: 0, resetMs };\n }\n}\n\nclass SlidingWindowLog {\n private windows = new Map<string, SlidingWindowState>();\n\n tryConsume(ip: string, maxRequests: number, windowMs: number): { allowed: boolean; remaining: number; resetMs: number } {\n const now = Date.now();\n let state = this.windows.get(ip);\n\n if (!state) {\n state = { timestamps: [] };\n this.windows.set(ip, state);\n }\n\n state.timestamps = state.timestamps.filter(t => now - t < windowMs);\n\n if (state.timestamps.length < maxRequests) {\n state.timestamps.push(now);\n return {\n allowed: true,\n remaining: maxRequests - state.timestamps.length,\n resetMs: state.timestamps.length > 0 ? windowMs - (now - state.timestamps[0]) : windowMs,\n };\n }\n\n const oldestInWindow = state.timestamps[0];\n const resetMs = windowMs - (now - oldestInWindow);\n return { allowed: false, remaining: 0, resetMs };\n }\n}\n\nclass FixedWindowCounter {\n private windows = new Map<string, FixedWindowState>();\n\n tryConsume(ip: string, maxRequests: number, windowMs: number): { allowed: boolean; remaining: number; resetMs: number } {\n const now = Date.now();\n let state = this.windows.get(ip);\n\n if (!state || now - state.windowStart >= windowMs) {\n state = { count: 0, windowStart: now };\n this.windows.set(ip, state);\n }\n\n const resetMs = windowMs - (now - state.windowStart);\n\n if (state.count < maxRequests) {\n state.count++;\n return { allowed: true, remaining: maxRequests - state.count, resetMs };\n }\n\n return { allowed: false, remaining: 0, resetMs };\n }\n}\n\nexport class RateLimiterService {\n private tokenBucket = new TokenBucket();\n private slidingWindow = new SlidingWindowLog();\n private fixedWindow = new FixedWindowCounter();\n private globalWindow = new FixedWindowCounter();\n private config: RateLimitConfig;\n private _rateLimitHits = 0;\n\n constructor(config: RateLimitConfig) {\n this.config = config;\n }\n\n get rateLimitHits(): number {\n return this._rateLimitHits;\n }\n\n checkLimit(ip: string, tier: string): { allowed: boolean; remaining: number; resetMs: number; limit: number } {\n const globalResult = this.globalWindow.tryConsume(\n '__global__',\n this.config.globalLimit.maxRequests,\n this.config.globalLimit.windowMs\n );\n\n if (!globalResult.allowed) {\n this._rateLimitHits++;\n return { allowed: false, remaining: 0, resetMs: globalResult.resetMs, limit: this.config.globalLimit.maxRequests };\n }\n\n const tierConfig = this.config.tiers[tier] || this.config.tiers[this.config.defaultTier];\n\n if (!tierConfig || tierConfig.algorithm === 'none') {\n return { allowed: true, remaining: -1, resetMs: 0, limit: -1 };\n }\n\n let result: { allowed: boolean; remaining: number; resetMs: number };\n\n switch (tierConfig.algorithm) {\n case 'tokenBucket':\n result = this.tokenBucket.tryConsume(\n ip,\n tierConfig.maxRequests!,\n tierConfig.refillRate || 1\n );\n break;\n case 'slidingWindow':\n result = this.slidingWindow.tryConsume(\n ip,\n tierConfig.maxRequests!,\n tierConfig.windowMs!\n );\n break;\n case 'fixedWindow':\n result = this.fixedWindow.tryConsume(\n ip,\n tierConfig.maxRequests!,\n tierConfig.windowMs!\n );\n break;\n default:\n return { allowed: true, remaining: -1, resetMs: 0, limit: -1 };\n }\n\n if (!result.allowed) {\n this._rateLimitHits++;\n }\n\n return { ...result, limit: tierConfig.maxRequests! };\n }\n\n getConfig(): RateLimitConfig {\n return this.config;\n }\n}\n","import { mkdirSync, readdirSync, appendFileSync } from 'fs';\nimport { join } from 'path';\nimport { randomUUID } from 'crypto';\nimport type { RequestLog, LogConfig, FileLogEntry } from '../../types';\nimport { DEFAULT_LOG_MAX_LINES } from '../../config/defaults';\n\nfunction deriveLevel(statusCode: number): FileLogEntry['level'] {\n if (statusCode < 400) return 'info';\n if (statusCode < 500) return 'warn';\n if (statusCode === 503) return 'fatal';\n return 'error';\n}\n\nfunction formatDate(date: Date): string {\n const y = date.getFullYear();\n const m = String(date.getMonth() + 1).padStart(2, '0');\n const d = String(date.getDate()).padStart(2, '0');\n return `${y}-${m}-${d}`;\n}\n\nfunction formatTime(date: Date): string {\n const h = String(date.getHours()).padStart(2, '0');\n const min = String(date.getMinutes()).padStart(2, '0');\n const s = String(date.getSeconds()).padStart(2, '0');\n return `${h}${min}${s}`;\n}\n\nexport class FileLogWriter {\n private logDir: string;\n private appName: string;\n private maxLines: number;\n private currentDate: string;\n private currentLines = 0;\n private fileIndex = 0;\n private currentFilePath: string | null = null;\n private destroyed = false;\n\n constructor(config: LogConfig) {\n this.logDir = config.logDir;\n this.appName = config.appName;\n this.maxLines = config.maxLinesPerFile ?? DEFAULT_LOG_MAX_LINES;\n this.currentDate = formatDate(new Date());\n\n try {\n mkdirSync(this.logDir, { recursive: true });\n } catch (err) {\n process.stderr.write(`[FileLogWriter] Failed to create log directory: ${err}\\n`);\n }\n\n this.fileIndex = this.scanExistingFiles(this.currentDate);\n this.rotateFile();\n }\n\n write(log: RequestLog): void {\n if (this.destroyed) return;\n\n const now = new Date(log.timestamp);\n const today = formatDate(now);\n\n // Date rollover\n if (today !== this.currentDate) {\n this.currentDate = today;\n this.fileIndex = 1;\n this.currentLines = 0;\n this.rotateFile();\n }\n\n // Line-limit rotation\n if (this.currentLines >= this.maxLines) {\n this.fileIndex++;\n this.currentLines = 0;\n this.rotateFile();\n }\n\n const entry: FileLogEntry = {\n timestamp: now.toISOString(),\n level: deriveLevel(log.statusCode),\n service: this.appName,\n method: log.method,\n path: log.path,\n statusCode: log.statusCode,\n responseTime: log.responseTime,\n requestId: randomUUID(),\n clientId: log.clientId,\n ip: log.ip,\n authenticated: log.authenticated,\n };\n\n try {\n appendFileSync(this.currentFilePath!, JSON.stringify(entry) + '\\n');\n this.currentLines++;\n } catch (err) {\n process.stderr.write(`[FileLogWriter] Write error: ${err}\\n`);\n }\n }\n\n destroy(): void {\n this.destroyed = true;\n this.currentFilePath = null;\n }\n\n /** Scan existing log files for a date to determine the next incremental number. */\n private scanExistingFiles(date: string): number {\n try {\n const prefix = `${this.appName}_${date}_`;\n const files = readdirSync(this.logDir).filter(\n f => f.startsWith(prefix) && f.endsWith('.log'),\n );\n if (files.length === 0) return 1;\n\n let maxIndex = 0;\n for (const f of files) {\n // filename: {appName}_{YYYY-MM-DD}_{HHmmss}_{NNN}.log\n const parts = f.replace('.log', '').split('_');\n const idx = parseInt(parts[parts.length - 1], 10);\n if (!isNaN(idx) && idx > maxIndex) maxIndex = idx;\n }\n return maxIndex + 1;\n } catch {\n return 1;\n }\n }\n\n private rotateFile(): void {\n const time = formatTime(new Date());\n const idx = String(this.fileIndex).padStart(3, '0');\n const filename = `${this.appName}_${this.currentDate}_${time}_${idx}.log`;\n this.currentFilePath = join(this.logDir, filename);\n }\n}\n","import type { RateLimitConfig, IpRules, ApiKeysConfig } from '../types';\n\nexport const DEFAULT_LOG_MAX_LINES = 10000;\n\nexport const DEFAULT_RATE_LIMIT_CONFIG: RateLimitConfig = {\n tiers: {\n free: { algorithm: 'tokenBucket', maxRequests: 100, windowMs: 60000, refillRate: 10 },\n pro: { algorithm: 'slidingWindow', maxRequests: 1000, windowMs: 60000 },\n unlimited: { algorithm: 'none' },\n },\n defaultTier: 'free',\n globalLimit: { maxRequests: 10000, windowMs: 60000 },\n};\n\nexport const DEFAULT_IP_RULES: IpRules = {\n allowlist: [],\n blocklist: [],\n mode: 'blocklist',\n};\n\nexport const DEFAULT_API_KEYS: ApiKeysConfig = {\n keys: [],\n};\n","import type { RequestLog, GatewayAnalytics, LogConfig } from '../../types';\nimport { FileLogWriter } from './FileLogWriter';\n\nconst MAX_LOG_SIZE = 10000;\nconst ACTIVE_WINDOW_MS = 300000; // 5 minutes\n\nexport class AnalyticsService {\n private logs: RequestLog[] = [];\n private head = 0;\n private count = 0;\n private fileLogWriter: FileLogWriter | null = null;\n\n constructor(logConfig?: LogConfig) {\n if (logConfig) {\n this.fileLogWriter = new FileLogWriter(logConfig);\n }\n }\n\n addLog(log: RequestLog): void {\n if (this.count < MAX_LOG_SIZE) {\n this.logs.push(log);\n this.count++;\n } else {\n this.logs[this.head] = log;\n this.head = (this.head + 1) % MAX_LOG_SIZE;\n }\n this.fileLogWriter?.write(log);\n }\n\n destroy(): void {\n this.fileLogWriter?.destroy();\n this.fileLogWriter = null;\n }\n\n getRecentLogs(limit = 20, offset = 0): RequestLog[] {\n const ordered = this.getOrderedLogs();\n return ordered.slice(offset, offset + limit);\n }\n\n getAnalytics(rateLimitHits: number): GatewayAnalytics {\n const now = Date.now();\n const oneMinuteAgo = now - 60000;\n const activeWindowStart = now - ACTIVE_WINDOW_MS;\n const ordered = this.getOrderedLogs();\n\n const recentLogs = ordered.filter(l => l.timestamp > oneMinuteAgo);\n const requestsPerMinute = recentLogs.length;\n\n // Top endpoints\n const endpointCounts = new Map<string, number>();\n for (const log of ordered) {\n const current = endpointCounts.get(log.path) || 0;\n endpointCounts.set(log.path, current + 1);\n }\n const topEndpoints = Array.from(endpointCounts.entries())\n .map(([path, count]) => ({ path, count }))\n .sort((a, b) => b.count - a.count)\n .slice(0, 5);\n\n // Error rate\n const errorCount = ordered.filter(l => l.statusCode >= 400).length;\n const errorRate = this.count > 0 ? (errorCount / this.count) * 100 : 0;\n\n // Average response time\n const totalResponseTime = ordered.reduce((sum, l) => sum + l.responseTime, 0);\n const avgResponseTime = this.count > 0 ? totalResponseTime / this.count : 0;\n\n // Active clients: unique IPs in last 5 minutes\n const activeLogs = ordered.filter(l => l.timestamp > activeWindowStart);\n const uniqueIps = new Set(activeLogs.map(l => l.ip));\n\n // Active key uses: unique (IP + apiKey) pairs in last 5 minutes\n const keyUsePairs = new Set<string>();\n for (const log of activeLogs) {\n if (log.apiKey) {\n keyUsePairs.add(`${log.ip}::${log.apiKey}`);\n }\n }\n\n return {\n totalRequests: this.count,\n requestsPerMinute,\n topEndpoints,\n errorRate: Math.round(errorRate * 100) / 100,\n avgResponseTime: Math.round(avgResponseTime * 100) / 100,\n activeClients: uniqueIps.size,\n activeKeyUses: keyUsePairs.size,\n rateLimitHits,\n };\n }\n\n private getOrderedLogs(): RequestLog[] {\n if (this.count < MAX_LOG_SIZE) {\n return [...this.logs].reverse();\n }\n const tail = this.logs.slice(0, this.head);\n const headPart = this.logs.slice(this.head);\n return [...headPart, ...tail].reverse();\n }\n}\n","import fs from 'fs';\nimport path from 'path';\nimport { generateSecret } from '../utils/totp';\nimport type { DeviceEntry, DevicesFile } from '../../types';\n\nconst ONE_WEEK_MS = 7 * 24 * 60 * 60 * 1000;\nconst CLEANUP_INTERVAL_MS = 60 * 60 * 1000; // 1 hour\nconst DEBOUNCE_WRITE_MS = 2000;\nconst MAX_REGISTRATIONS_PER_IP_PER_MIN = 10;\nconst MAX_REGISTRATIONS_PER_IP_TOTAL = 30;\nconst RATE_WINDOW_MS = 60 * 1000; // 1 minute\n\nexport class DeviceRegistryService {\n private devices: Map<string, DeviceEntry> = new Map();\n private filePath: string;\n private writeTimeout: ReturnType<typeof setTimeout> | null = null;\n private cleanupInterval: ReturnType<typeof setInterval> | null = null;\n\n // In-memory rate tracking: ip -> timestamps of recent registration attempts\n private registrationAttempts: Map<string, number[]> = new Map();\n\n constructor(filePath: string) {\n this.filePath = filePath;\n this.loadFromDisk();\n this.cleanupExpired();\n this.cleanupInterval = setInterval(() => this.cleanupExpired(), CLEANUP_INTERVAL_MS);\n }\n\n private loadFromDisk(): void {\n try {\n if (fs.existsSync(this.filePath)) {\n const raw = fs.readFileSync(this.filePath, 'utf-8');\n const data: DevicesFile = JSON.parse(raw);\n for (const device of data.devices) {\n this.devices.set(device.browserId, device);\n }\n console.log(`[DeviceRegistry] Loaded ${this.devices.size} devices from ${this.filePath}`);\n } else {\n // Create the directory and empty file\n const dir = path.dirname(this.filePath);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n this.saveToDiskSync();\n console.log(`[DeviceRegistry] Created new devices file at ${this.filePath}`);\n }\n } catch (err) {\n console.error('[DeviceRegistry] Failed to load devices file:', err);\n }\n }\n\n private saveToDiskSync(): void {\n const data: DevicesFile = {\n devices: Array.from(this.devices.values()),\n };\n fs.writeFileSync(this.filePath, JSON.stringify(data, null, 2), 'utf-8');\n }\n\n private debouncedSave(): void {\n if (this.writeTimeout) clearTimeout(this.writeTimeout);\n this.writeTimeout = setTimeout(() => {\n this.saveToDiskSync();\n }, DEBOUNCE_WRITE_MS);\n }\n\n private cleanupExpired(): void {\n const now = Date.now();\n let removed = 0;\n for (const [id, device] of this.devices) {\n if (new Date(device.expiresAt).getTime() <= now) {\n this.devices.delete(id);\n removed++;\n }\n }\n if (removed > 0) {\n console.log(`[DeviceRegistry] Cleaned up ${removed} expired devices`);\n this.debouncedSave();\n }\n }\n\n private getActiveCountByIp(ip: string): number {\n const now = Date.now();\n let count = 0;\n for (const device of this.devices.values()) {\n if (device.ip === ip && device.active && new Date(device.expiresAt).getTime() > now) {\n count++;\n }\n }\n return count;\n }\n\n private checkRateLimit(ip: string): boolean {\n const now = Date.now();\n const attempts = this.registrationAttempts.get(ip) || [];\n // Remove attempts older than the rate window\n const recent = attempts.filter(t => now - t < RATE_WINDOW_MS);\n this.registrationAttempts.set(ip, recent);\n return recent.length < MAX_REGISTRATIONS_PER_IP_PER_MIN;\n }\n\n private recordAttempt(ip: string): void {\n const attempts = this.registrationAttempts.get(ip) || [];\n attempts.push(Date.now());\n this.registrationAttempts.set(ip, attempts);\n }\n\n registerDevice(\n browserId: string,\n ip: string,\n userAgent: string\n ): { success: true; device: DeviceEntry } | { success: false; error: string; status: number } {\n // Check rate limit: 10 per IP per minute\n if (!this.checkRateLimit(ip)) {\n return {\n success: false,\n error: 'Registration rate limit exceeded. Max 10 per minute per IP.',\n status: 429,\n };\n }\n\n this.recordAttempt(ip);\n\n // Check total cap: 30 active devices per IP\n if (this.getActiveCountByIp(ip) >= MAX_REGISTRATIONS_PER_IP_TOTAL) {\n return {\n success: false,\n error: 'Maximum device registrations reached for this IP. Max 30 per IP.',\n status: 403,\n };\n }\n\n // Check if this browserId is already registered and still valid\n const existing = this.devices.get(browserId);\n if (existing && existing.active && new Date(existing.expiresAt).getTime() > Date.now()) {\n // Re-registration: return existing secret, refresh expiry\n existing.expiresAt = new Date(Date.now() + ONE_WEEK_MS).toISOString();\n existing.lastSeen = new Date().toISOString();\n existing.lastIp = ip;\n this.debouncedSave();\n return { success: true, device: existing };\n }\n\n const now = new Date();\n const device: DeviceEntry = {\n browserId,\n sharedSecret: generateSecret(),\n ip,\n userAgent,\n registeredAt: now.toISOString(),\n expiresAt: new Date(now.getTime() + ONE_WEEK_MS).toISOString(),\n lastSeen: now.toISOString(),\n lastIp: ip,\n active: true,\n };\n\n this.devices.set(browserId, device);\n this.debouncedSave();\n return { success: true, device };\n }\n\n getDevice(browserId: string): DeviceEntry | null {\n const device = this.devices.get(browserId) || null;\n if (!device) return null;\n\n // Check expiry\n if (new Date(device.expiresAt).getTime() <= Date.now()) {\n this.devices.delete(browserId);\n this.debouncedSave();\n return null;\n }\n\n if (!device.active) return null;\n return device;\n }\n\n updateLastSeen(browserId: string, ip: string): void {\n const device = this.devices.get(browserId);\n if (!device) return;\n device.lastSeen = new Date().toISOString();\n if (device.lastIp !== ip) {\n console.log(`[DeviceRegistry] IP change for ${browserId}: ${device.lastIp} -> ${ip}`);\n device.lastIp = ip;\n }\n this.debouncedSave();\n }\n\n revokeDevice(browserId: string): boolean {\n const device = this.devices.get(browserId);\n if (!device) return false;\n device.active = false;\n this.debouncedSave();\n return true;\n }\n\n getStats(): { total: number; active: number; expired: number } {\n const now = Date.now();\n let active = 0;\n let expired = 0;\n for (const device of this.devices.values()) {\n if (!device.active || new Date(device.expiresAt).getTime() <= now) {\n expired++;\n } else {\n active++;\n }\n }\n return { total: this.devices.size, active, expired };\n }\n\n destroy(): void {\n if (this.cleanupInterval) clearInterval(this.cleanupInterval);\n if (this.writeTimeout) {\n clearTimeout(this.writeTimeout);\n this.saveToDiskSync(); // Final save\n }\n }\n}\n","import crypto from 'crypto';\n\nconst TIME_WINDOW_MS = 3600 * 1000; // 1 hour\nconst CODE_LENGTH = 16; // truncated HMAC hex chars\n\nexport function generateTOTP(browserId: string, secret: string, timeOffset: number = 0): string {\n const timeWindow = Math.floor(Date.now() / TIME_WINDOW_MS) + timeOffset;\n const message = `${browserId}:${timeWindow}`;\n const hmac = crypto.createHmac('sha256', secret).update(message).digest('hex');\n return hmac.substring(0, CODE_LENGTH);\n}\n\nexport function validateTOTP(browserId: string, secret: string, providedCode: string): boolean {\n const currentCode = generateTOTP(browserId, secret, 0);\n const previousCode = generateTOTP(browserId, secret, -1);\n return timingSafeEqual(providedCode, currentCode) || timingSafeEqual(providedCode, previousCode);\n}\n\nfunction timingSafeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) return false;\n const bufA = Buffer.from(a);\n const bufB = Buffer.from(b);\n return crypto.timingSafeEqual(bufA, bufB);\n}\n\nexport function formatKey(browserId: string, code: string): string {\n return `totp_${browserId}_${code}`;\n}\n\nexport function parseKey(key: string): { browserId: string; code: string } | null {\n if (!key.startsWith('totp_')) return null;\n const parts = key.slice(5).split('_');\n // UUID has 4 dashes, so browserId has 5 parts when split by _\n // Format: totp_<uuid>_<code> where uuid = xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\n // But UUID uses dashes, not underscores, so it's a single segment\n if (parts.length < 2) return null;\n const code = parts[parts.length - 1];\n const browserId = parts.slice(0, -1).join('_');\n if (!browserId || !code) return null;\n return { browserId, code };\n}\n\nexport function generateSecret(): string {\n return crypto.randomBytes(32).toString('hex');\n}\n","import { Request, Response, NextFunction } from 'express';\nimport type { ApiKeysConfig } from '../../types';\nimport { parseKey, validateTOTP } from '../utils/totp';\nimport { DeviceRegistryService } from '../services/DeviceRegistryService';\n\nexport function createApiKeyAuth(\n getKeys: () => ApiKeysConfig,\n deviceRegistry?: DeviceRegistryService\n) {\n return function apiKeyAuth(req: Request, res: Response, next: NextFunction): void {\n const apiKey = req.header('X-API-Key') || req.query.apiKey as string;\n\n if (!apiKey) {\n (req as any).clientId = req.ip || 'unknown';\n (req as any).tier = 'free';\n next();\n return;\n }\n\n // TOTP key: totp_<browserId>_<code>\n if (apiKey.startsWith('totp_') && deviceRegistry) {\n const parsed = parseKey(apiKey);\n if (!parsed) {\n res.status(401).json({ error: 'Malformed TOTP key' });\n return;\n }\n\n const device = deviceRegistry.getDevice(parsed.browserId);\n if (!device) {\n res.status(401).json({ error: 'Device not registered or expired' });\n return;\n }\n\n if (!validateTOTP(parsed.browserId, device.sharedSecret, parsed.code)) {\n res.status(401).json({ error: 'Invalid or expired TOTP code' });\n return;\n }\n\n // Valid TOTP - update tracking\n const ip = req.ip || req.socket.remoteAddress || 'unknown';\n deviceRegistry.updateLastSeen(parsed.browserId, ip);\n\n (req as any).clientId = parsed.browserId;\n (req as any).tier = 'free';\n (req as any).apiKeyValue = apiKey;\n next();\n return;\n }\n\n // Static key: gw_live_*\n const config = getKeys();\n const keyEntry = config.keys.find(k => k.key === apiKey && k.active);\n\n if (!keyEntry) {\n res.status(401).json({ error: 'Invalid or revoked API key' });\n return;\n }\n\n (req as any).clientId = keyEntry.id;\n (req as any).tier = keyEntry.tier;\n (req as any).apiKeyValue = keyEntry.key;\n next();\n };\n}\n","import { Request, Response, NextFunction } from 'express';\nimport type { IpRules } from '../../types';\n\nexport function createIpFilter(getRules: () => IpRules) {\n return function ipFilter(req: Request, res: Response, next: NextFunction): void {\n const rules = getRules();\n const clientIp = req.ip || req.socket.remoteAddress || 'unknown';\n\n if (rules.mode === 'allowlist' && rules.allowlist.length > 0) {\n if (!rules.allowlist.includes(clientIp)) {\n res.status(403).json({ error: 'IP not in allowlist' });\n return;\n }\n }\n\n if (rules.mode === 'blocklist' && rules.blocklist.length > 0) {\n if (rules.blocklist.includes(clientIp)) {\n res.status(403).json({ error: 'IP is blocked' });\n return;\n }\n }\n\n next();\n };\n}\n","import { Request, Response, NextFunction } from 'express';\nimport { RateLimiterService } from '../services/RateLimiterService';\n\nexport function createRateLimiter(service: RateLimiterService) {\n return function rateLimiter(req: Request, res: Response, next: NextFunction): void {\n const ip = req.ip || req.socket.remoteAddress || 'unknown';\n const tier = (req as any).tier || 'free';\n\n const result = service.checkLimit(ip, tier);\n\n if (result.limit > 0) {\n res.setHeader('X-RateLimit-Limit', result.limit);\n res.setHeader('X-RateLimit-Remaining', Math.max(0, result.remaining));\n res.setHeader('X-RateLimit-Reset', Math.ceil(result.resetMs / 1000));\n }\n\n if (!result.allowed) {\n res.status(429).json({\n error: 'Rate limit exceeded',\n retryAfter: Math.ceil(result.resetMs / 1000),\n });\n return;\n }\n\n next();\n };\n}\n","import { Request, Response, NextFunction } from 'express';\nimport { AnalyticsService } from '../services/AnalyticsService';\n\nexport function createRequestLogger(analytics: AnalyticsService) {\n return function requestLogger(req: Request, res: Response, next: NextFunction): void {\n const start = Date.now();\n\n res.on('finish', () => {\n const responseTime = Date.now() - start;\n const apiKeyValue = (req as any).apiKeyValue || undefined;\n analytics.addLog({\n timestamp: Date.now(),\n method: req.method,\n path: req.originalUrl,\n statusCode: res.statusCode,\n responseTime,\n clientId: (req as any).clientId || req.ip || 'unknown',\n ip: req.ip || req.socket.remoteAddress || 'unknown',\n apiKey: apiKeyValue,\n authenticated: !!apiKeyValue,\n });\n });\n\n next();\n };\n}\n","import { Router, Request, Response } from 'express';\nimport { RateLimiterService } from '../services/RateLimiterService';\nimport { AnalyticsService } from '../services/AnalyticsService';\nimport type { GatewayMiddlewareConfig } from '../../types';\nimport crypto from 'crypto';\n\nexport interface GatewayRoutesOptions {\n rateLimiterService: RateLimiterService;\n analyticsService: AnalyticsService;\n config: Required<GatewayMiddlewareConfig>;\n}\n\nexport function createGatewayRoutes(options: GatewayRoutesOptions): Router {\n const { rateLimiterService, analyticsService, config } = options;\n const router = Router();\n\n // GET /analytics - Returns current analytics snapshot\n router.get('/analytics', (_req: Request, res: Response) => {\n const analytics = analyticsService.getAnalytics(rateLimiterService.rateLimitHits);\n res.json(analytics);\n });\n\n // GET /analytics/live - SSE stream pushing analytics every 5 seconds\n router.get('/analytics/live', (_req: Request, res: Response) => {\n res.setHeader('Content-Type', 'text/event-stream');\n res.setHeader('Cache-Control', 'no-cache');\n res.setHeader('Connection', 'keep-alive');\n res.setHeader('X-Accel-Buffering', 'no');\n res.flushHeaders();\n\n const send = (): void => {\n const analytics = analyticsService.getAnalytics(rateLimiterService.rateLimitHits);\n res.write(`data: ${JSON.stringify(analytics)}\\n\\n`);\n };\n\n send();\n const interval = setInterval(send, 5000);\n\n _req.on('close', () => {\n clearInterval(interval);\n });\n });\n\n // GET /config - Returns current gateway config\n router.get('/config', (_req: Request, res: Response) => {\n const analytics = analyticsService.getAnalytics(rateLimiterService.rateLimitHits);\n res.json({\n rateLimits: config.rateLimits,\n ipRules: config.ipRules,\n activeKeys: config.apiKeys.keys.filter(k => k.active).length,\n activeKeyUses: analytics.activeKeyUses,\n });\n });\n\n // POST /keys - Create a new API key\n router.post('/keys', (req: Request, res: Response) => {\n const { name, tier } = req.body;\n\n if (!name) {\n res.status(400).json({ error: 'Name is required' });\n return;\n }\n\n const newKey = {\n id: `key_${String(config.apiKeys.keys.length + 1).padStart(3, '0')}`,\n key: `gw_live_${crypto.randomBytes(16).toString('hex')}`,\n name,\n tier: tier || 'free',\n createdAt: new Date().toISOString(),\n active: true,\n };\n\n config.apiKeys.keys.push(newKey);\n res.status(201).json(newKey);\n });\n\n // DELETE /keys/:keyId - Revoke an API key\n router.delete('/keys/:keyId', (req: Request, res: Response) => {\n const { keyId } = req.params;\n const key = config.apiKeys.keys.find(k => k.id === keyId);\n\n if (!key) {\n res.status(404).json({ error: 'API key not found' });\n return;\n }\n\n key.active = false;\n res.json({ message: 'API key revoked', id: keyId });\n });\n\n // GET /logs - Returns recent request logs (paginated)\n router.get('/logs', (req: Request, res: Response) => {\n const limit = parseInt(req.query.limit as string) || 20;\n const offset = parseInt(req.query.offset as string) || 0;\n const logs = analyticsService.getRecentLogs(limit, offset);\n res.json({ logs, limit, offset });\n });\n\n return router;\n}\n","import { Router, Request, Response } from 'express';\nimport { DeviceRegistryService } from '../services/DeviceRegistryService';\n\nexport interface DeviceAuthRoutesOptions {\n deviceRegistry: DeviceRegistryService;\n}\n\nexport function createDeviceAuthRoutes(options: DeviceAuthRoutesOptions): Router {\n const { deviceRegistry } = options;\n const router = Router();\n\n // POST /register - Register a browser device\n router.post('/register', (req: Request, res: Response) => {\n const { browserId } = req.body;\n\n if (!browserId || typeof browserId !== 'string') {\n res.status(400).json({ error: 'browserId is required' });\n return;\n }\n\n // Basic UUID format validation\n const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n if (!uuidRegex.test(browserId)) {\n res.status(400).json({ error: 'browserId must be a valid UUID' });\n return;\n }\n\n const ip = req.ip || req.socket.remoteAddress || 'unknown';\n const userAgent = req.headers['user-agent'] || 'unknown';\n\n const result = deviceRegistry.registerDevice(browserId, ip, userAgent);\n\n if (!result.success) {\n res.status(result.status).json({ error: result.error });\n return;\n }\n\n res.status(201).json({\n browserId: result.device.browserId,\n sharedSecret: result.device.sharedSecret,\n expiresAt: result.device.expiresAt,\n });\n });\n\n // GET /status/:browserId - Check device registration status\n router.get('/status/:browserId', (req: Request, res: Response) => {\n const browserId = req.params.browserId as string;\n const device = deviceRegistry.getDevice(browserId);\n\n if (!device) {\n res.status(404).json({ registered: false });\n return;\n }\n\n res.json({\n registered: true,\n browserId: device.browserId,\n expiresAt: device.expiresAt,\n registeredAt: device.registeredAt,\n });\n });\n\n // DELETE /:browserId - Revoke a device (admin)\n router.delete('/:browserId', (req: Request, res: Response) => {\n const browserId = req.params.browserId as string;\n const revoked = deviceRegistry.revokeDevice(browserId);\n\n if (!revoked) {\n res.status(404).json({ error: 'Device not found' });\n return;\n }\n\n res.json({ message: 'Device revoked', browserId });\n });\n\n // GET /stats - Device registry stats\n router.get('/stats', (_req: Request, res: Response) => {\n res.json(deviceRegistry.getStats());\n });\n\n return router;\n}\n"],"mappings":"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","names":["resetMs","path","Router","crypto","Router"]}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
export { AnalyticsService, DeviceAuthRoutesOptions, DeviceRegistryService, GatewayInstances, GatewayRoutesOptions, RateLimiterService, createApiKeyAuth, createDeviceAuthRoutes, createGatewayMiddleware, createGatewayRoutes, createIpFilter, createRateLimiter, createRequestLogger, formatKey, generateSecret, generateTOTP, parseKey, validateTOTP } from './backend/index.mjs';
|
|
1
|
+
export { AnalyticsService, DeviceAuthRoutesOptions, DeviceRegistryService, FileLogWriter, GatewayInstances, GatewayRoutesOptions, RateLimiterService, createApiKeyAuth, createDeviceAuthRoutes, createGatewayMiddleware, createGatewayRoutes, createIpFilter, createRateLimiter, createRequestLogger, formatKey, generateSecret, generateTOTP, parseKey, validateTOTP } from './backend/index.mjs';
|
|
2
2
|
export { GatewayDashboard, GatewayDashboardProps } from './frontend/index.mjs';
|
|
3
3
|
import { ApiKeysConfig, IpRules, RateLimitConfig } from './types/index.mjs';
|
|
4
|
-
export { ApiKey, BucketState, DeviceEntry, DevicesFile, FixedWindowState, GatewayAnalytics, GatewayConfig, GatewayMiddlewareConfig, RequestLog, SlidingWindowState, TierConfig } from './types/index.mjs';
|
|
4
|
+
export { ApiKey, BucketState, DeviceEntry, DevicesFile, FileLogEntry, FixedWindowState, GatewayAnalytics, GatewayConfig, GatewayMiddlewareConfig, LogConfig, RequestLog, SlidingWindowState, TierConfig } from './types/index.mjs';
|
|
5
5
|
import 'express';
|
|
6
6
|
import 'react/jsx-runtime';
|
|
7
7
|
|
|
8
|
+
declare const DEFAULT_LOG_MAX_LINES = 10000;
|
|
8
9
|
declare const DEFAULT_RATE_LIMIT_CONFIG: RateLimitConfig;
|
|
9
10
|
declare const DEFAULT_IP_RULES: IpRules;
|
|
10
11
|
declare const DEFAULT_API_KEYS: ApiKeysConfig;
|
|
11
12
|
|
|
12
|
-
export { ApiKeysConfig, DEFAULT_API_KEYS, DEFAULT_IP_RULES, DEFAULT_RATE_LIMIT_CONFIG, IpRules, RateLimitConfig };
|
|
13
|
+
export { ApiKeysConfig, DEFAULT_API_KEYS, DEFAULT_IP_RULES, DEFAULT_LOG_MAX_LINES, DEFAULT_RATE_LIMIT_CONFIG, IpRules, RateLimitConfig };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
export { AnalyticsService, DeviceAuthRoutesOptions, DeviceRegistryService, GatewayInstances, GatewayRoutesOptions, RateLimiterService, createApiKeyAuth, createDeviceAuthRoutes, createGatewayMiddleware, createGatewayRoutes, createIpFilter, createRateLimiter, createRequestLogger, formatKey, generateSecret, generateTOTP, parseKey, validateTOTP } from './backend/index.js';
|
|
1
|
+
export { AnalyticsService, DeviceAuthRoutesOptions, DeviceRegistryService, FileLogWriter, GatewayInstances, GatewayRoutesOptions, RateLimiterService, createApiKeyAuth, createDeviceAuthRoutes, createGatewayMiddleware, createGatewayRoutes, createIpFilter, createRateLimiter, createRequestLogger, formatKey, generateSecret, generateTOTP, parseKey, validateTOTP } from './backend/index.js';
|
|
2
2
|
export { GatewayDashboard, GatewayDashboardProps } from './frontend/index.js';
|
|
3
3
|
import { ApiKeysConfig, IpRules, RateLimitConfig } from './types/index.js';
|
|
4
|
-
export { ApiKey, BucketState, DeviceEntry, DevicesFile, FixedWindowState, GatewayAnalytics, GatewayConfig, GatewayMiddlewareConfig, RequestLog, SlidingWindowState, TierConfig } from './types/index.js';
|
|
4
|
+
export { ApiKey, BucketState, DeviceEntry, DevicesFile, FileLogEntry, FixedWindowState, GatewayAnalytics, GatewayConfig, GatewayMiddlewareConfig, LogConfig, RequestLog, SlidingWindowState, TierConfig } from './types/index.js';
|
|
5
5
|
import 'express';
|
|
6
6
|
import 'react/jsx-runtime';
|
|
7
7
|
|
|
8
|
+
declare const DEFAULT_LOG_MAX_LINES = 10000;
|
|
8
9
|
declare const DEFAULT_RATE_LIMIT_CONFIG: RateLimitConfig;
|
|
9
10
|
declare const DEFAULT_IP_RULES: IpRules;
|
|
10
11
|
declare const DEFAULT_API_KEYS: ApiKeysConfig;
|
|
11
12
|
|
|
12
|
-
export { ApiKeysConfig, DEFAULT_API_KEYS, DEFAULT_IP_RULES, DEFAULT_RATE_LIMIT_CONFIG, IpRules, RateLimitConfig };
|
|
13
|
+
export { ApiKeysConfig, DEFAULT_API_KEYS, DEFAULT_IP_RULES, DEFAULT_LOG_MAX_LINES, DEFAULT_RATE_LIMIT_CONFIG, IpRules, RateLimitConfig };
|
package/dist/index.js
CHANGED
|
@@ -33,8 +33,10 @@ __export(src_exports, {
|
|
|
33
33
|
AnalyticsService: () => AnalyticsService,
|
|
34
34
|
DEFAULT_API_KEYS: () => DEFAULT_API_KEYS,
|
|
35
35
|
DEFAULT_IP_RULES: () => DEFAULT_IP_RULES,
|
|
36
|
+
DEFAULT_LOG_MAX_LINES: () => DEFAULT_LOG_MAX_LINES,
|
|
36
37
|
DEFAULT_RATE_LIMIT_CONFIG: () => DEFAULT_RATE_LIMIT_CONFIG,
|
|
37
38
|
DeviceRegistryService: () => DeviceRegistryService,
|
|
39
|
+
FileLogWriter: () => FileLogWriter,
|
|
38
40
|
GatewayDashboard: () => GatewayDashboard,
|
|
39
41
|
RateLimiterService: () => RateLimiterService,
|
|
40
42
|
createApiKeyAuth: () => createApiKeyAuth,
|
|
@@ -180,6 +182,140 @@ var RateLimiterService = class {
|
|
|
180
182
|
}
|
|
181
183
|
};
|
|
182
184
|
|
|
185
|
+
// src/backend/services/FileLogWriter.ts
|
|
186
|
+
var import_fs = require("fs");
|
|
187
|
+
var import_path = require("path");
|
|
188
|
+
var import_crypto = require("crypto");
|
|
189
|
+
|
|
190
|
+
// src/config/defaults.ts
|
|
191
|
+
var DEFAULT_LOG_MAX_LINES = 1e4;
|
|
192
|
+
var DEFAULT_RATE_LIMIT_CONFIG = {
|
|
193
|
+
tiers: {
|
|
194
|
+
free: { algorithm: "tokenBucket", maxRequests: 100, windowMs: 6e4, refillRate: 10 },
|
|
195
|
+
pro: { algorithm: "slidingWindow", maxRequests: 1e3, windowMs: 6e4 },
|
|
196
|
+
unlimited: { algorithm: "none" }
|
|
197
|
+
},
|
|
198
|
+
defaultTier: "free",
|
|
199
|
+
globalLimit: { maxRequests: 1e4, windowMs: 6e4 }
|
|
200
|
+
};
|
|
201
|
+
var DEFAULT_IP_RULES = {
|
|
202
|
+
allowlist: [],
|
|
203
|
+
blocklist: [],
|
|
204
|
+
mode: "blocklist"
|
|
205
|
+
};
|
|
206
|
+
var DEFAULT_API_KEYS = {
|
|
207
|
+
keys: []
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
// src/backend/services/FileLogWriter.ts
|
|
211
|
+
function deriveLevel(statusCode) {
|
|
212
|
+
if (statusCode < 400) return "info";
|
|
213
|
+
if (statusCode < 500) return "warn";
|
|
214
|
+
if (statusCode === 503) return "fatal";
|
|
215
|
+
return "error";
|
|
216
|
+
}
|
|
217
|
+
function formatDate(date) {
|
|
218
|
+
const y = date.getFullYear();
|
|
219
|
+
const m = String(date.getMonth() + 1).padStart(2, "0");
|
|
220
|
+
const d = String(date.getDate()).padStart(2, "0");
|
|
221
|
+
return `${y}-${m}-${d}`;
|
|
222
|
+
}
|
|
223
|
+
function formatTime(date) {
|
|
224
|
+
const h = String(date.getHours()).padStart(2, "0");
|
|
225
|
+
const min = String(date.getMinutes()).padStart(2, "0");
|
|
226
|
+
const s = String(date.getSeconds()).padStart(2, "0");
|
|
227
|
+
return `${h}${min}${s}`;
|
|
228
|
+
}
|
|
229
|
+
var FileLogWriter = class {
|
|
230
|
+
logDir;
|
|
231
|
+
appName;
|
|
232
|
+
maxLines;
|
|
233
|
+
currentDate;
|
|
234
|
+
currentLines = 0;
|
|
235
|
+
fileIndex = 0;
|
|
236
|
+
currentFilePath = null;
|
|
237
|
+
destroyed = false;
|
|
238
|
+
constructor(config) {
|
|
239
|
+
this.logDir = config.logDir;
|
|
240
|
+
this.appName = config.appName;
|
|
241
|
+
this.maxLines = config.maxLinesPerFile ?? DEFAULT_LOG_MAX_LINES;
|
|
242
|
+
this.currentDate = formatDate(/* @__PURE__ */ new Date());
|
|
243
|
+
try {
|
|
244
|
+
(0, import_fs.mkdirSync)(this.logDir, { recursive: true });
|
|
245
|
+
} catch (err) {
|
|
246
|
+
process.stderr.write(`[FileLogWriter] Failed to create log directory: ${err}
|
|
247
|
+
`);
|
|
248
|
+
}
|
|
249
|
+
this.fileIndex = this.scanExistingFiles(this.currentDate);
|
|
250
|
+
this.rotateFile();
|
|
251
|
+
}
|
|
252
|
+
write(log) {
|
|
253
|
+
if (this.destroyed) return;
|
|
254
|
+
const now = new Date(log.timestamp);
|
|
255
|
+
const today = formatDate(now);
|
|
256
|
+
if (today !== this.currentDate) {
|
|
257
|
+
this.currentDate = today;
|
|
258
|
+
this.fileIndex = 1;
|
|
259
|
+
this.currentLines = 0;
|
|
260
|
+
this.rotateFile();
|
|
261
|
+
}
|
|
262
|
+
if (this.currentLines >= this.maxLines) {
|
|
263
|
+
this.fileIndex++;
|
|
264
|
+
this.currentLines = 0;
|
|
265
|
+
this.rotateFile();
|
|
266
|
+
}
|
|
267
|
+
const entry = {
|
|
268
|
+
timestamp: now.toISOString(),
|
|
269
|
+
level: deriveLevel(log.statusCode),
|
|
270
|
+
service: this.appName,
|
|
271
|
+
method: log.method,
|
|
272
|
+
path: log.path,
|
|
273
|
+
statusCode: log.statusCode,
|
|
274
|
+
responseTime: log.responseTime,
|
|
275
|
+
requestId: (0, import_crypto.randomUUID)(),
|
|
276
|
+
clientId: log.clientId,
|
|
277
|
+
ip: log.ip,
|
|
278
|
+
authenticated: log.authenticated
|
|
279
|
+
};
|
|
280
|
+
try {
|
|
281
|
+
(0, import_fs.appendFileSync)(this.currentFilePath, JSON.stringify(entry) + "\n");
|
|
282
|
+
this.currentLines++;
|
|
283
|
+
} catch (err) {
|
|
284
|
+
process.stderr.write(`[FileLogWriter] Write error: ${err}
|
|
285
|
+
`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
destroy() {
|
|
289
|
+
this.destroyed = true;
|
|
290
|
+
this.currentFilePath = null;
|
|
291
|
+
}
|
|
292
|
+
/** Scan existing log files for a date to determine the next incremental number. */
|
|
293
|
+
scanExistingFiles(date) {
|
|
294
|
+
try {
|
|
295
|
+
const prefix = `${this.appName}_${date}_`;
|
|
296
|
+
const files = (0, import_fs.readdirSync)(this.logDir).filter(
|
|
297
|
+
(f) => f.startsWith(prefix) && f.endsWith(".log")
|
|
298
|
+
);
|
|
299
|
+
if (files.length === 0) return 1;
|
|
300
|
+
let maxIndex = 0;
|
|
301
|
+
for (const f of files) {
|
|
302
|
+
const parts = f.replace(".log", "").split("_");
|
|
303
|
+
const idx = parseInt(parts[parts.length - 1], 10);
|
|
304
|
+
if (!isNaN(idx) && idx > maxIndex) maxIndex = idx;
|
|
305
|
+
}
|
|
306
|
+
return maxIndex + 1;
|
|
307
|
+
} catch {
|
|
308
|
+
return 1;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
rotateFile() {
|
|
312
|
+
const time = formatTime(/* @__PURE__ */ new Date());
|
|
313
|
+
const idx = String(this.fileIndex).padStart(3, "0");
|
|
314
|
+
const filename = `${this.appName}_${this.currentDate}_${time}_${idx}.log`;
|
|
315
|
+
this.currentFilePath = (0, import_path.join)(this.logDir, filename);
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
|
|
183
319
|
// src/backend/services/AnalyticsService.ts
|
|
184
320
|
var MAX_LOG_SIZE = 1e4;
|
|
185
321
|
var ACTIVE_WINDOW_MS = 3e5;
|
|
@@ -187,6 +323,12 @@ var AnalyticsService = class {
|
|
|
187
323
|
logs = [];
|
|
188
324
|
head = 0;
|
|
189
325
|
count = 0;
|
|
326
|
+
fileLogWriter = null;
|
|
327
|
+
constructor(logConfig) {
|
|
328
|
+
if (logConfig) {
|
|
329
|
+
this.fileLogWriter = new FileLogWriter(logConfig);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
190
332
|
addLog(log) {
|
|
191
333
|
if (this.count < MAX_LOG_SIZE) {
|
|
192
334
|
this.logs.push(log);
|
|
@@ -195,6 +337,11 @@ var AnalyticsService = class {
|
|
|
195
337
|
this.logs[this.head] = log;
|
|
196
338
|
this.head = (this.head + 1) % MAX_LOG_SIZE;
|
|
197
339
|
}
|
|
340
|
+
this.fileLogWriter?.write(log);
|
|
341
|
+
}
|
|
342
|
+
destroy() {
|
|
343
|
+
this.fileLogWriter?.destroy();
|
|
344
|
+
this.fileLogWriter = null;
|
|
198
345
|
}
|
|
199
346
|
getRecentLogs(limit = 20, offset = 0) {
|
|
200
347
|
const ordered = this.getOrderedLogs();
|
|
@@ -247,17 +394,17 @@ var AnalyticsService = class {
|
|
|
247
394
|
};
|
|
248
395
|
|
|
249
396
|
// src/backend/services/DeviceRegistryService.ts
|
|
250
|
-
var
|
|
251
|
-
var
|
|
397
|
+
var import_fs2 = __toESM(require("fs"));
|
|
398
|
+
var import_path2 = __toESM(require("path"));
|
|
252
399
|
|
|
253
400
|
// src/backend/utils/totp.ts
|
|
254
|
-
var
|
|
401
|
+
var import_crypto2 = __toESM(require("crypto"));
|
|
255
402
|
var TIME_WINDOW_MS = 3600 * 1e3;
|
|
256
403
|
var CODE_LENGTH = 16;
|
|
257
404
|
function generateTOTP(browserId, secret, timeOffset = 0) {
|
|
258
405
|
const timeWindow = Math.floor(Date.now() / TIME_WINDOW_MS) + timeOffset;
|
|
259
406
|
const message = `${browserId}:${timeWindow}`;
|
|
260
|
-
const hmac =
|
|
407
|
+
const hmac = import_crypto2.default.createHmac("sha256", secret).update(message).digest("hex");
|
|
261
408
|
return hmac.substring(0, CODE_LENGTH);
|
|
262
409
|
}
|
|
263
410
|
function validateTOTP(browserId, secret, providedCode) {
|
|
@@ -269,7 +416,7 @@ function timingSafeEqual(a, b) {
|
|
|
269
416
|
if (a.length !== b.length) return false;
|
|
270
417
|
const bufA = Buffer.from(a);
|
|
271
418
|
const bufB = Buffer.from(b);
|
|
272
|
-
return
|
|
419
|
+
return import_crypto2.default.timingSafeEqual(bufA, bufB);
|
|
273
420
|
}
|
|
274
421
|
function formatKey(browserId, code) {
|
|
275
422
|
return `totp_${browserId}_${code}`;
|
|
@@ -284,7 +431,7 @@ function parseKey(key) {
|
|
|
284
431
|
return { browserId, code };
|
|
285
432
|
}
|
|
286
433
|
function generateSecret() {
|
|
287
|
-
return
|
|
434
|
+
return import_crypto2.default.randomBytes(32).toString("hex");
|
|
288
435
|
}
|
|
289
436
|
|
|
290
437
|
// src/backend/services/DeviceRegistryService.ts
|
|
@@ -309,17 +456,17 @@ var DeviceRegistryService = class {
|
|
|
309
456
|
}
|
|
310
457
|
loadFromDisk() {
|
|
311
458
|
try {
|
|
312
|
-
if (
|
|
313
|
-
const raw =
|
|
459
|
+
if (import_fs2.default.existsSync(this.filePath)) {
|
|
460
|
+
const raw = import_fs2.default.readFileSync(this.filePath, "utf-8");
|
|
314
461
|
const data = JSON.parse(raw);
|
|
315
462
|
for (const device of data.devices) {
|
|
316
463
|
this.devices.set(device.browserId, device);
|
|
317
464
|
}
|
|
318
465
|
console.log(`[DeviceRegistry] Loaded ${this.devices.size} devices from ${this.filePath}`);
|
|
319
466
|
} else {
|
|
320
|
-
const dir =
|
|
321
|
-
if (!
|
|
322
|
-
|
|
467
|
+
const dir = import_path2.default.dirname(this.filePath);
|
|
468
|
+
if (!import_fs2.default.existsSync(dir)) {
|
|
469
|
+
import_fs2.default.mkdirSync(dir, { recursive: true });
|
|
323
470
|
}
|
|
324
471
|
this.saveToDiskSync();
|
|
325
472
|
console.log(`[DeviceRegistry] Created new devices file at ${this.filePath}`);
|
|
@@ -332,7 +479,7 @@ var DeviceRegistryService = class {
|
|
|
332
479
|
const data = {
|
|
333
480
|
devices: Array.from(this.devices.values())
|
|
334
481
|
};
|
|
335
|
-
|
|
482
|
+
import_fs2.default.writeFileSync(this.filePath, JSON.stringify(data, null, 2), "utf-8");
|
|
336
483
|
}
|
|
337
484
|
debouncedSave() {
|
|
338
485
|
if (this.writeTimeout) clearTimeout(this.writeTimeout);
|
|
@@ -466,25 +613,6 @@ var DeviceRegistryService = class {
|
|
|
466
613
|
}
|
|
467
614
|
};
|
|
468
615
|
|
|
469
|
-
// src/config/defaults.ts
|
|
470
|
-
var DEFAULT_RATE_LIMIT_CONFIG = {
|
|
471
|
-
tiers: {
|
|
472
|
-
free: { algorithm: "tokenBucket", maxRequests: 100, windowMs: 6e4, refillRate: 10 },
|
|
473
|
-
pro: { algorithm: "slidingWindow", maxRequests: 1e3, windowMs: 6e4 },
|
|
474
|
-
unlimited: { algorithm: "none" }
|
|
475
|
-
},
|
|
476
|
-
defaultTier: "free",
|
|
477
|
-
globalLimit: { maxRequests: 1e4, windowMs: 6e4 }
|
|
478
|
-
};
|
|
479
|
-
var DEFAULT_IP_RULES = {
|
|
480
|
-
allowlist: [],
|
|
481
|
-
blocklist: [],
|
|
482
|
-
mode: "blocklist"
|
|
483
|
-
};
|
|
484
|
-
var DEFAULT_API_KEYS = {
|
|
485
|
-
keys: []
|
|
486
|
-
};
|
|
487
|
-
|
|
488
616
|
// src/backend/middleware/apiKeyAuth.ts
|
|
489
617
|
function createApiKeyAuth(getKeys, deviceRegistry) {
|
|
490
618
|
return function apiKeyAuth(req, res, next) {
|
|
@@ -603,10 +731,11 @@ function createGatewayMiddleware(userConfig) {
|
|
|
603
731
|
rateLimits: userConfig?.rateLimits ?? DEFAULT_RATE_LIMIT_CONFIG,
|
|
604
732
|
ipRules: userConfig?.ipRules ?? DEFAULT_IP_RULES,
|
|
605
733
|
apiKeys: userConfig?.apiKeys ?? DEFAULT_API_KEYS,
|
|
606
|
-
deviceRegistryPath: userConfig?.deviceRegistryPath ?? ""
|
|
734
|
+
deviceRegistryPath: userConfig?.deviceRegistryPath ?? "",
|
|
735
|
+
logConfig: userConfig?.logConfig
|
|
607
736
|
};
|
|
608
737
|
const rateLimiterService = new RateLimiterService(config.rateLimits);
|
|
609
|
-
const analyticsService = new AnalyticsService();
|
|
738
|
+
const analyticsService = new AnalyticsService(userConfig?.logConfig);
|
|
610
739
|
let deviceRegistry;
|
|
611
740
|
if (config.deviceRegistryPath) {
|
|
612
741
|
deviceRegistry = new DeviceRegistryService(config.deviceRegistryPath);
|
|
@@ -627,7 +756,7 @@ function createGatewayMiddleware(userConfig) {
|
|
|
627
756
|
|
|
628
757
|
// src/backend/routes/gateway.ts
|
|
629
758
|
var import_express2 = require("express");
|
|
630
|
-
var
|
|
759
|
+
var import_crypto3 = __toESM(require("crypto"));
|
|
631
760
|
function createGatewayRoutes(options) {
|
|
632
761
|
const { rateLimiterService, analyticsService, config } = options;
|
|
633
762
|
const router = (0, import_express2.Router)();
|
|
@@ -670,7 +799,7 @@ function createGatewayRoutes(options) {
|
|
|
670
799
|
}
|
|
671
800
|
const newKey = {
|
|
672
801
|
id: `key_${String(config.apiKeys.keys.length + 1).padStart(3, "0")}`,
|
|
673
|
-
key: `gw_live_${
|
|
802
|
+
key: `gw_live_${import_crypto3.default.randomBytes(16).toString("hex")}`,
|
|
674
803
|
name,
|
|
675
804
|
tier: tier || "free",
|
|
676
805
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -1157,8 +1286,10 @@ function GatewayDashboard({ apiBaseUrl, apiKey }) {
|
|
|
1157
1286
|
AnalyticsService,
|
|
1158
1287
|
DEFAULT_API_KEYS,
|
|
1159
1288
|
DEFAULT_IP_RULES,
|
|
1289
|
+
DEFAULT_LOG_MAX_LINES,
|
|
1160
1290
|
DEFAULT_RATE_LIMIT_CONFIG,
|
|
1161
1291
|
DeviceRegistryService,
|
|
1292
|
+
FileLogWriter,
|
|
1162
1293
|
GatewayDashboard,
|
|
1163
1294
|
RateLimiterService,
|
|
1164
1295
|
createApiKeyAuth,
|