@gagandeep023/api-gateway 0.5.0 → 0.6.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.
@@ -1,5 +1,5 @@
1
1
  import { Router, Request, Response, NextFunction } from 'express';
2
- import { RateLimitConfig, LogConfig, RequestLog, GatewayAnalytics, DeviceEntry, GatewayMiddlewareConfig, ApiKeysConfig, IpRules } from '../types/index.mjs';
2
+ import { RateLimitConfig, LogConfig, RequestLog, GatewayAnalytics, DeviceEntry, GatewayMiddlewareConfig, ApiKeysConfig, TOTPValidator, IpRules } from '../types/index.mjs';
3
3
 
4
4
  declare class RateLimiterService {
5
5
  private tokenBucket;
@@ -65,19 +65,23 @@ declare class DeviceRegistryService {
65
65
  destroy(): void;
66
66
  }
67
67
 
68
+ type OptionalConfigKeys$1 = 'logConfig' | 'validateTOTP';
69
+ type ResolvedConfig$1 = Required<Omit<GatewayMiddlewareConfig, OptionalConfigKeys$1>> & Pick<GatewayMiddlewareConfig, OptionalConfigKeys$1>;
68
70
  interface GatewayInstances {
69
71
  rateLimiterService: RateLimiterService;
70
72
  analyticsService: AnalyticsService;
71
73
  deviceRegistry?: DeviceRegistryService;
72
74
  middleware: Router;
73
- config: Required<Omit<GatewayMiddlewareConfig, 'logConfig'>> & Pick<GatewayMiddlewareConfig, 'logConfig'>;
75
+ config: ResolvedConfig$1;
74
76
  }
75
77
  declare function createGatewayMiddleware(userConfig?: GatewayMiddlewareConfig): GatewayInstances;
76
78
 
79
+ type OptionalConfigKeys = 'logConfig' | 'validateTOTP';
80
+ type ResolvedConfig = Required<Omit<GatewayMiddlewareConfig, OptionalConfigKeys>> & Pick<GatewayMiddlewareConfig, OptionalConfigKeys>;
77
81
  interface GatewayRoutesOptions {
78
82
  rateLimiterService: RateLimiterService;
79
83
  analyticsService: AnalyticsService;
80
- config: Required<GatewayMiddlewareConfig>;
84
+ config: ResolvedConfig;
81
85
  }
82
86
  declare function createGatewayRoutes(options: GatewayRoutesOptions): Router;
83
87
 
@@ -103,7 +107,7 @@ declare class FileLogWriter {
103
107
  private rotateFile;
104
108
  }
105
109
 
106
- declare function createApiKeyAuth(getKeys: () => ApiKeysConfig, deviceRegistry?: DeviceRegistryService): (req: Request, res: Response, next: NextFunction) => void;
110
+ declare function createApiKeyAuth(getKeys: () => ApiKeysConfig, deviceRegistry?: DeviceRegistryService, customTotpValidator?: TOTPValidator): (req: Request, res: Response, next: NextFunction) => void;
107
111
 
108
112
  declare function createIpFilter(getRules: () => IpRules): (req: Request, res: Response, next: NextFunction) => void;
109
113
 
@@ -1,5 +1,5 @@
1
1
  import { Router, Request, Response, NextFunction } from 'express';
2
- import { RateLimitConfig, LogConfig, RequestLog, GatewayAnalytics, DeviceEntry, GatewayMiddlewareConfig, ApiKeysConfig, IpRules } from '../types/index.js';
2
+ import { RateLimitConfig, LogConfig, RequestLog, GatewayAnalytics, DeviceEntry, GatewayMiddlewareConfig, ApiKeysConfig, TOTPValidator, IpRules } from '../types/index.js';
3
3
 
4
4
  declare class RateLimiterService {
5
5
  private tokenBucket;
@@ -65,19 +65,23 @@ declare class DeviceRegistryService {
65
65
  destroy(): void;
66
66
  }
67
67
 
68
+ type OptionalConfigKeys$1 = 'logConfig' | 'validateTOTP';
69
+ type ResolvedConfig$1 = Required<Omit<GatewayMiddlewareConfig, OptionalConfigKeys$1>> & Pick<GatewayMiddlewareConfig, OptionalConfigKeys$1>;
68
70
  interface GatewayInstances {
69
71
  rateLimiterService: RateLimiterService;
70
72
  analyticsService: AnalyticsService;
71
73
  deviceRegistry?: DeviceRegistryService;
72
74
  middleware: Router;
73
- config: Required<Omit<GatewayMiddlewareConfig, 'logConfig'>> & Pick<GatewayMiddlewareConfig, 'logConfig'>;
75
+ config: ResolvedConfig$1;
74
76
  }
75
77
  declare function createGatewayMiddleware(userConfig?: GatewayMiddlewareConfig): GatewayInstances;
76
78
 
79
+ type OptionalConfigKeys = 'logConfig' | 'validateTOTP';
80
+ type ResolvedConfig = Required<Omit<GatewayMiddlewareConfig, OptionalConfigKeys>> & Pick<GatewayMiddlewareConfig, OptionalConfigKeys>;
77
81
  interface GatewayRoutesOptions {
78
82
  rateLimiterService: RateLimiterService;
79
83
  analyticsService: AnalyticsService;
80
- config: Required<GatewayMiddlewareConfig>;
84
+ config: ResolvedConfig;
81
85
  }
82
86
  declare function createGatewayRoutes(options: GatewayRoutesOptions): Router;
83
87
 
@@ -103,7 +107,7 @@ declare class FileLogWriter {
103
107
  private rotateFile;
104
108
  }
105
109
 
106
- declare function createApiKeyAuth(getKeys: () => ApiKeysConfig, deviceRegistry?: DeviceRegistryService): (req: Request, res: Response, next: NextFunction) => void;
110
+ declare function createApiKeyAuth(getKeys: () => ApiKeysConfig, deviceRegistry?: DeviceRegistryService, customTotpValidator?: TOTPValidator): (req: Request, res: Response, next: NextFunction) => void;
107
111
 
108
112
  declare function createIpFilter(getRules: () => IpRules): (req: Request, res: Response, next: NextFunction) => void;
109
113
 
@@ -609,7 +609,7 @@ var DeviceRegistryService = class {
609
609
  };
610
610
 
611
611
  // src/backend/middleware/apiKeyAuth.ts
612
- function createApiKeyAuth(getKeys, deviceRegistry) {
612
+ function createApiKeyAuth(getKeys, deviceRegistry, customTotpValidator) {
613
613
  return function apiKeyAuth(req, res, next) {
614
614
  const apiKey = req.header("X-API-Key") || req.query.apiKey;
615
615
  if (!apiKey) {
@@ -618,28 +618,42 @@ function createApiKeyAuth(getKeys, deviceRegistry) {
618
618
  next();
619
619
  return;
620
620
  }
621
- if (apiKey.startsWith("totp_") && deviceRegistry) {
622
- const parsed = parseKey(apiKey);
623
- if (!parsed) {
624
- res.status(401).json({ error: "Malformed TOTP key" });
625
- return;
626
- }
627
- const device = deviceRegistry.getDevice(parsed.browserId);
628
- if (!device) {
629
- res.status(401).json({ error: "Device not registered or expired" });
621
+ if (apiKey.startsWith("totp_")) {
622
+ if (customTotpValidator) {
623
+ const result = customTotpValidator(apiKey);
624
+ if (!result) {
625
+ res.status(401).json({ error: "Invalid or expired TOTP session" });
626
+ return;
627
+ }
628
+ req.clientId = result.sessionId;
629
+ req.tier = "free";
630
+ req.apiKeyValue = apiKey;
631
+ next();
630
632
  return;
631
633
  }
632
- if (!validateTOTP(parsed.browserId, device.sharedSecret, parsed.code)) {
633
- res.status(401).json({ error: "Invalid or expired TOTP code" });
634
+ if (deviceRegistry) {
635
+ const parsed = parseKey(apiKey);
636
+ if (!parsed) {
637
+ res.status(401).json({ error: "Malformed TOTP key" });
638
+ return;
639
+ }
640
+ const device = deviceRegistry.getDevice(parsed.browserId);
641
+ if (!device) {
642
+ res.status(401).json({ error: "Device not registered or expired" });
643
+ return;
644
+ }
645
+ if (!validateTOTP(parsed.browserId, device.sharedSecret, parsed.code)) {
646
+ res.status(401).json({ error: "Invalid or expired TOTP code" });
647
+ return;
648
+ }
649
+ const ip = req.ip || req.socket.remoteAddress || "unknown";
650
+ deviceRegistry.updateLastSeen(parsed.browserId, ip);
651
+ req.clientId = parsed.browserId;
652
+ req.tier = "free";
653
+ req.apiKeyValue = apiKey;
654
+ next();
634
655
  return;
635
656
  }
636
- const ip = req.ip || req.socket.remoteAddress || "unknown";
637
- deviceRegistry.updateLastSeen(parsed.browserId, ip);
638
- req.clientId = parsed.browserId;
639
- req.tier = "free";
640
- req.apiKeyValue = apiKey;
641
- next();
642
- return;
643
657
  }
644
658
  const config = getKeys();
645
659
  const keyEntry = config.keys.find((k) => k.key === apiKey && k.active);
@@ -727,7 +741,8 @@ function createGatewayMiddleware(userConfig) {
727
741
  ipRules: userConfig?.ipRules ?? DEFAULT_IP_RULES,
728
742
  apiKeys: userConfig?.apiKeys ?? DEFAULT_API_KEYS,
729
743
  deviceRegistryPath: userConfig?.deviceRegistryPath ?? "",
730
- logConfig: userConfig?.logConfig
744
+ logConfig: userConfig?.logConfig,
745
+ validateTOTP: userConfig?.validateTOTP
731
746
  };
732
747
  const rateLimiterService = new RateLimiterService(config.rateLimits);
733
748
  const analyticsService = new AnalyticsService(userConfig?.logConfig);
@@ -737,7 +752,7 @@ function createGatewayMiddleware(userConfig) {
737
752
  }
738
753
  const router = (0, import_express.Router)();
739
754
  router.use(createRequestLogger(analyticsService));
740
- router.use(createApiKeyAuth(() => config.apiKeys, deviceRegistry));
755
+ router.use(createApiKeyAuth(() => config.apiKeys, deviceRegistry, userConfig?.validateTOTP));
741
756
  router.use(createIpFilter(() => config.ipRules));
742
757
  router.use(createRateLimiter(rateLimiterService));
743
758
  return {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/backend/index.ts","../../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":["export { createGatewayMiddleware } from './middleware/gateway';\nexport type { GatewayInstances } from './middleware/gateway';\nexport { createGatewayRoutes } from './routes/gateway';\nexport type { GatewayRoutesOptions } from './routes/gateway';\nexport { createDeviceAuthRoutes } from './routes/deviceAuth';\nexport type { DeviceAuthRoutesOptions } from './routes/deviceAuth';\nexport { DeviceRegistryService } from './services/DeviceRegistryService';\nexport { RateLimiterService } from './services/RateLimiterService';\nexport { AnalyticsService } from './services/AnalyticsService';\nexport { FileLogWriter } from './services/FileLogWriter';\nexport { createApiKeyAuth } from './middleware/apiKeyAuth';\nexport { createIpFilter } from './middleware/ipFilter';\nexport { createRateLimiter } from './middleware/rateLimiter';\nexport { createRequestLogger } from './middleware/requestLogger';\nexport { generateTOTP, validateTOTP, formatKey, parseKey, generateSecret } from './utils/totp';\n","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","import_fs","import_path","import_crypto","crypto","fs","path","import_express","import_crypto","crypto","import_express"]}
1
+ {"version":3,"sources":["../../src/backend/index.ts","../../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":["export { createGatewayMiddleware } from './middleware/gateway';\nexport type { GatewayInstances } from './middleware/gateway';\nexport { createGatewayRoutes } from './routes/gateway';\nexport type { GatewayRoutesOptions } from './routes/gateway';\nexport { createDeviceAuthRoutes } from './routes/deviceAuth';\nexport type { DeviceAuthRoutesOptions } from './routes/deviceAuth';\nexport { DeviceRegistryService } from './services/DeviceRegistryService';\nexport { RateLimiterService } from './services/RateLimiterService';\nexport { AnalyticsService } from './services/AnalyticsService';\nexport { FileLogWriter } from './services/FileLogWriter';\nexport { createApiKeyAuth } from './middleware/apiKeyAuth';\nexport { createIpFilter } from './middleware/ipFilter';\nexport { createRateLimiter } from './middleware/rateLimiter';\nexport { createRequestLogger } from './middleware/requestLogger';\nexport { generateTOTP, validateTOTP, formatKey, parseKey, generateSecret } from './utils/totp';\n","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\ntype OptionalConfigKeys = 'logConfig' | 'validateTOTP';\ntype ResolvedConfig = Required<Omit<GatewayMiddlewareConfig, OptionalConfigKeys>> & Pick<GatewayMiddlewareConfig, OptionalConfigKeys>;\n\nexport interface GatewayInstances {\n rateLimiterService: RateLimiterService;\n analyticsService: AnalyticsService;\n deviceRegistry?: DeviceRegistryService;\n middleware: Router;\n config: ResolvedConfig;\n}\n\nexport function createGatewayMiddleware(userConfig?: GatewayMiddlewareConfig): GatewayInstances {\n const config: ResolvedConfig = {\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 validateTOTP: userConfig?.validateTOTP,\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, userConfig?.validateTOTP));\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, TOTPValidator } from '../../types';\nimport { parseKey, validateTOTP } from '../utils/totp';\nimport { DeviceRegistryService } from '../services/DeviceRegistryService';\n\nexport function createApiKeyAuth(\n getKeys: () => ApiKeysConfig,\n deviceRegistry?: DeviceRegistryService,\n customTotpValidator?: TOTPValidator\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_')) {\n // Custom validator takes precedence\n if (customTotpValidator) {\n const result = customTotpValidator(apiKey);\n if (!result) {\n res.status(401).json({ error: 'Invalid or expired TOTP session' });\n return;\n }\n\n (req as any).clientId = result.sessionId;\n (req as any).tier = 'free';\n (req as any).apiKeyValue = apiKey;\n next();\n return;\n }\n\n // Fallback to built-in device registry\n if (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 // No TOTP handler available, fall through to static key check\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\ntype OptionalConfigKeys = 'logConfig' | 'validateTOTP';\ntype ResolvedConfig = Required<Omit<GatewayMiddlewareConfig, OptionalConfigKeys>> & Pick<GatewayMiddlewareConfig, OptionalConfigKeys>;\n\nexport interface GatewayRoutesOptions {\n rateLimiterService: RateLimiterService;\n analyticsService: AnalyticsService;\n config: ResolvedConfig;\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","import_fs","import_path","import_crypto","crypto","fs","path","import_express","import_crypto","crypto","import_express"]}
@@ -558,7 +558,7 @@ var DeviceRegistryService = class {
558
558
  };
559
559
 
560
560
  // src/backend/middleware/apiKeyAuth.ts
561
- function createApiKeyAuth(getKeys, deviceRegistry) {
561
+ function createApiKeyAuth(getKeys, deviceRegistry, customTotpValidator) {
562
562
  return function apiKeyAuth(req, res, next) {
563
563
  const apiKey = req.header("X-API-Key") || req.query.apiKey;
564
564
  if (!apiKey) {
@@ -567,28 +567,42 @@ function createApiKeyAuth(getKeys, deviceRegistry) {
567
567
  next();
568
568
  return;
569
569
  }
570
- if (apiKey.startsWith("totp_") && deviceRegistry) {
571
- const parsed = parseKey(apiKey);
572
- if (!parsed) {
573
- res.status(401).json({ error: "Malformed TOTP key" });
574
- return;
575
- }
576
- const device = deviceRegistry.getDevice(parsed.browserId);
577
- if (!device) {
578
- res.status(401).json({ error: "Device not registered or expired" });
570
+ if (apiKey.startsWith("totp_")) {
571
+ if (customTotpValidator) {
572
+ const result = customTotpValidator(apiKey);
573
+ if (!result) {
574
+ res.status(401).json({ error: "Invalid or expired TOTP session" });
575
+ return;
576
+ }
577
+ req.clientId = result.sessionId;
578
+ req.tier = "free";
579
+ req.apiKeyValue = apiKey;
580
+ next();
579
581
  return;
580
582
  }
581
- if (!validateTOTP(parsed.browserId, device.sharedSecret, parsed.code)) {
582
- res.status(401).json({ error: "Invalid or expired TOTP code" });
583
+ if (deviceRegistry) {
584
+ const parsed = parseKey(apiKey);
585
+ if (!parsed) {
586
+ res.status(401).json({ error: "Malformed TOTP key" });
587
+ return;
588
+ }
589
+ const device = deviceRegistry.getDevice(parsed.browserId);
590
+ if (!device) {
591
+ res.status(401).json({ error: "Device not registered or expired" });
592
+ return;
593
+ }
594
+ if (!validateTOTP(parsed.browserId, device.sharedSecret, parsed.code)) {
595
+ res.status(401).json({ error: "Invalid or expired TOTP code" });
596
+ return;
597
+ }
598
+ const ip = req.ip || req.socket.remoteAddress || "unknown";
599
+ deviceRegistry.updateLastSeen(parsed.browserId, ip);
600
+ req.clientId = parsed.browserId;
601
+ req.tier = "free";
602
+ req.apiKeyValue = apiKey;
603
+ next();
583
604
  return;
584
605
  }
585
- const ip = req.ip || req.socket.remoteAddress || "unknown";
586
- deviceRegistry.updateLastSeen(parsed.browserId, ip);
587
- req.clientId = parsed.browserId;
588
- req.tier = "free";
589
- req.apiKeyValue = apiKey;
590
- next();
591
- return;
592
606
  }
593
607
  const config = getKeys();
594
608
  const keyEntry = config.keys.find((k) => k.key === apiKey && k.active);
@@ -676,7 +690,8 @@ function createGatewayMiddleware(userConfig) {
676
690
  ipRules: userConfig?.ipRules ?? DEFAULT_IP_RULES,
677
691
  apiKeys: userConfig?.apiKeys ?? DEFAULT_API_KEYS,
678
692
  deviceRegistryPath: userConfig?.deviceRegistryPath ?? "",
679
- logConfig: userConfig?.logConfig
693
+ logConfig: userConfig?.logConfig,
694
+ validateTOTP: userConfig?.validateTOTP
680
695
  };
681
696
  const rateLimiterService = new RateLimiterService(config.rateLimits);
682
697
  const analyticsService = new AnalyticsService(userConfig?.logConfig);
@@ -686,7 +701,7 @@ function createGatewayMiddleware(userConfig) {
686
701
  }
687
702
  const router = Router();
688
703
  router.use(createRequestLogger(analyticsService));
689
- router.use(createApiKeyAuth(() => config.apiKeys, deviceRegistry));
704
+ router.use(createApiKeyAuth(() => config.apiKeys, deviceRegistry, userConfig?.validateTOTP));
690
705
  router.use(createIpFilter(() => config.ipRules));
691
706
  router.use(createRateLimiter(rateLimiterService));
692
707
  return {