@gagandeep023/api-gateway 0.5.1 → 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.
- package/dist/backend/index.d.mts +8 -4
- package/dist/backend/index.d.ts +8 -4
- package/dist/backend/index.js +36 -21
- package/dist/backend/index.js.map +1 -1
- package/dist/backend/index.mjs +36 -21
- package/dist/backend/index.mjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +36 -21
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +36 -21
- package/dist/index.mjs.map +1 -1
- package/dist/types/index.d.mts +14 -1
- package/dist/types/index.d.ts +14 -1
- package/dist/types/index.js.map +1 -1
- package/package.json +1 -1
package/dist/backend/index.d.mts
CHANGED
|
@@ -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:
|
|
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:
|
|
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
|
|
package/dist/backend/index.d.ts
CHANGED
|
@@ -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:
|
|
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:
|
|
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
|
|
package/dist/backend/index.js
CHANGED
|
@@ -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_")
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
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 (
|
|
633
|
-
|
|
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<Omit<GatewayMiddlewareConfig, 'logConfig'>> & Pick<GatewayMiddlewareConfig, 'logConfig'>;\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"]}
|
package/dist/backend/index.mjs
CHANGED
|
@@ -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_")
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
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 (
|
|
582
|
-
|
|
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 {
|