@gugananuvem/aws-local-simulator 1.0.31 → 1.0.34

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.
Files changed (79) hide show
  1. package/README.md +834 -834
  2. package/aws-config +153 -153
  3. package/bin/aws-local-simulator.js +63 -63
  4. package/package.json +3 -2
  5. package/src/config/config-loader.js +114 -114
  6. package/src/config/default-config.js +79 -79
  7. package/src/config/env-loader.js +68 -68
  8. package/src/index.js +146 -146
  9. package/src/index.mjs +123 -123
  10. package/src/server.js +463 -463
  11. package/src/services/apigateway/index.js +75 -75
  12. package/src/services/apigateway/server.js +607 -607
  13. package/src/services/apigateway/simulator.js +1405 -1405
  14. package/src/services/athena/index.js +75 -75
  15. package/src/services/athena/server.js +101 -101
  16. package/src/services/athena/simulador.js +998 -998
  17. package/src/services/athena/simulator.js +346 -346
  18. package/src/services/cloudformation/index.js +106 -106
  19. package/src/services/cloudformation/server.js +417 -417
  20. package/src/services/cloudformation/simulador.js +1020 -1020
  21. package/src/services/cloudtrail/index.js +84 -84
  22. package/src/services/cloudtrail/server.js +235 -235
  23. package/src/services/cloudtrail/simulador.js +719 -719
  24. package/src/services/cloudwatch/index.js +84 -84
  25. package/src/services/cloudwatch/server.js +366 -366
  26. package/src/services/cloudwatch/simulador.js +1173 -1173
  27. package/src/services/cognito/index.js +79 -79
  28. package/src/services/cognito/server.js +297 -297
  29. package/src/services/cognito/simulator.js +1992 -1761
  30. package/src/services/config/index.js +96 -96
  31. package/src/services/config/server.js +215 -215
  32. package/src/services/config/simulador.js +1260 -1260
  33. package/src/services/dynamodb/index.js +74 -74
  34. package/src/services/dynamodb/server.js +139 -139
  35. package/src/services/dynamodb/simulator.js +1005 -982
  36. package/src/services/dynamodb/sqlite-store.js +722 -0
  37. package/src/services/ecs/index.js +65 -65
  38. package/src/services/ecs/server.js +235 -235
  39. package/src/services/ecs/simulator.js +844 -844
  40. package/src/services/eventbridge/index.js +89 -89
  41. package/src/services/eventbridge/server.js +209 -209
  42. package/src/services/eventbridge/simulator.js +684 -684
  43. package/src/services/index.js +45 -45
  44. package/src/services/kms/index.js +75 -75
  45. package/src/services/kms/server.js +81 -81
  46. package/src/services/kms/simulator.js +344 -344
  47. package/src/services/lambda/handler-loader.js +183 -183
  48. package/src/services/lambda/index.js +81 -81
  49. package/src/services/lambda/route-registry.js +274 -274
  50. package/src/services/lambda/server.js +191 -191
  51. package/src/services/lambda/simulator.js +364 -364
  52. package/src/services/parameter-store/index.js +80 -80
  53. package/src/services/parameter-store/server.js +50 -50
  54. package/src/services/parameter-store/simulator.js +201 -201
  55. package/src/services/s3/index.js +73 -73
  56. package/src/services/s3/server.js +350 -350
  57. package/src/services/s3/simulator.js +568 -568
  58. package/src/services/secret-manager/index.js +80 -80
  59. package/src/services/secret-manager/server.js +51 -51
  60. package/src/services/secret-manager/simulator.js +182 -182
  61. package/src/services/sns/index.js +89 -89
  62. package/src/services/sns/server.js +607 -607
  63. package/src/services/sns/simulator.js +1482 -1482
  64. package/src/services/sqs/index.js +98 -98
  65. package/src/services/sqs/server.js +360 -360
  66. package/src/services/sqs/simulator.js +509 -509
  67. package/src/services/sts/index.js +37 -37
  68. package/src/services/sts/server.js +144 -144
  69. package/src/services/sts/simulator.js +69 -69
  70. package/src/services/xray/index.js +83 -83
  71. package/src/services/xray/server.js +308 -308
  72. package/src/services/xray/simulador.js +994 -994
  73. package/src/template/aws-config-template.js +87 -87
  74. package/src/template/aws-config-template.mjs +90 -90
  75. package/src/template/config-template.json +203 -203
  76. package/src/utils/aws-config.js +91 -91
  77. package/src/utils/cloudtrail-audit.js +129 -129
  78. package/src/utils/local-store.js +83 -83
  79. package/src/utils/logger.js +59 -59
@@ -1,608 +1,608 @@
1
- /**
2
- * API Gateway Server - Servidor HTTP para API Gateway
3
- */
4
-
5
- const express = require('express');
6
- const cors = require('cors');
7
- const logger = require('../../utils/logger');
8
-
9
- class APIGatewayServer {
10
- constructor(port, config) {
11
- this.port = port;
12
- this.config = config;
13
- this.app = express();
14
- this.simulator = null;
15
- this.server = null;
16
- this.setupMiddlewares();
17
- }
18
-
19
- setupMiddlewares() {
20
- this.app.use(express.json({ limit: '10mb' }));
21
- this.app.use(express.urlencoded({ extended: true, limit: '10mb' }));
22
- // Parse bodies with AWS content types (e.g. application/x-amz-json-1.1)
23
- this.app.use((req, res, next) => {
24
- const ct = req.headers['content-type'] || '';
25
- if ((req.body && JSON.stringify(req.body) == "{}") && ct.includes('application/x-amz-json')) {
26
- let data = "";
27
- req.on("data", (chunk) => {
28
- data += chunk;
29
- });
30
- req.on("end", () => {
31
- try {
32
- req.body = JSON.parse(data);
33
- } catch (error) {
34
- req.body = {};
35
- }
36
- next();
37
- });
38
- } else {
39
- next();
40
- }
41
- });
42
- this.app.use(cors());
43
-
44
- if (logger.currentLogLevel === 'verboso') {
45
- this.app.use((req, res, next) => {
46
- const start = Date.now();
47
- res.on('finish', () => {
48
- const duration = Date.now() - start;
49
- logger.verboso(`API Gateway: ${req.method} ${req.path} - ${duration}ms`);
50
- });
51
- next();
52
- });
53
- }
54
- }
55
-
56
- async initialize() {
57
- this.setupRoutes();
58
- this.setupConfigRoutes();
59
- this.setupProxyRoutes();
60
- logger.debug('API Gateway Server inicializado');
61
- }
62
-
63
- setupConfigRoutes() {
64
- // Register routes from aws-local-simulator.json config directly
65
- const apis = this.config.apigateway?.apis || [];
66
- for (const api of apis) {
67
- // Build Cognito authorizer middleware for this API if configured
68
- const cognitoAuthorizer = this._buildCognitoAuthorizer(api.authorizer);
69
-
70
- for (const endpoint of (api.endpoints || [])) {
71
- const { path, method, lambdaName, integrationType, authorizerRequired } = endpoint;
72
- if (!path || !method) continue;
73
-
74
- const expressPath = path.replace(/\{([^}]+)\}/g, ':$1');
75
- const httpMethod = method.toLowerCase();
76
-
77
- logger.debug(`📡 Registrando rota: ${method} ${path} -> ${lambdaName}`);
78
-
79
- const handler = async (req, res) => {
80
- try {
81
- const lambdaService = this.lambdaService;
82
- if (!lambdaService) {
83
- return res.status(500).json({ error: 'Lambda service not available' });
84
- }
85
-
86
- const event = {
87
- httpMethod: req.method,
88
- path: req.path,
89
- headers: req.headers,
90
- queryStringParameters: Object.keys(req.query).length ? req.query : null,
91
- pathParameters: Object.keys(req.params).length ? req.params : null,
92
- body: req.body ? JSON.stringify(req.body) : null,
93
- isBase64Encoded: false,
94
- requestContext: {
95
- path: req.path,
96
- stage: 'local',
97
- requestId: Math.random().toString(36).substring(7),
98
- identity: { sourceIp: req.ip },
99
- authorizer: req.cognitoAuthorizer || null
100
- }
101
- };
102
-
103
- const result = await lambdaService.simulator.invoke(lambdaName, event);
104
- const payload = result.Payload || {};
105
- const statusCode = payload.statusCode || 200;
106
- const headers = payload.headers || { 'Content-Type': 'application/json' };
107
- const body = payload.body;
108
-
109
- res.status(statusCode).set(headers).send(body);
110
- } catch (err) {
111
- logger.error(`Lambda invoke error (${lambdaName}):`, err);
112
- res.status(500).json({ error: err.message });
113
- }
114
- };
115
-
116
- const middlewares = [];
117
- if (authorizerRequired && cognitoAuthorizer) {
118
- middlewares.push(cognitoAuthorizer);
119
- }
120
- middlewares.push(handler);
121
-
122
- const ANY_METHODS = ['get', 'post', 'put', 'delete', 'patch', 'head', 'options'];
123
- if (httpMethod === 'any') {
124
- for (const m of ANY_METHODS) {
125
- this.app[m](expressPath, ...middlewares);
126
- }
127
- } else {
128
- this.app[httpMethod](expressPath, ...middlewares);
129
- }
130
- }
131
- }
132
- }
133
-
134
- _buildCognitoAuthorizer(authorizerConfig) {
135
- if (!authorizerConfig) return null;
136
- if (authorizerConfig.type !== 'COGNITO_USER_POOLS') return null;
137
-
138
- const { userPoolId } = authorizerConfig;
139
-
140
- return (req, res, next) => {
141
- const authHeader = req.headers['authorization'] || req.headers['Authorization'];
142
- if (!authHeader) {
143
- return res.status(401).json({ message: 'Unauthorized' });
144
- }
145
-
146
- const token = authHeader.startsWith('Bearer ') ? authHeader.slice(7) : authHeader;
147
-
148
- const cognitoSimulator = this.cognitoService?.simulator;
149
- if (!cognitoSimulator) {
150
- logger.warn('⚠️ Cognito authorizer configured but Cognito service is not available');
151
- return res.status(500).json({ message: 'Authorizer unavailable' });
152
- }
153
-
154
- // Validate the token belongs to the configured user pool
155
- const decoded = cognitoSimulator.verifyAccessToken(token);
156
- if (!decoded) {
157
- return res.status(401).json({ message: 'Unauthorized' });
158
- }
159
-
160
- // If a specific userPoolId is required, verify it matches
161
- if (userPoolId && decoded['cognito:username'] !== undefined) {
162
- const session = cognitoSimulator.accessTokens.get(token);
163
- if (session && userPoolId && session.UserPoolId !== userPoolId) {
164
- return res.status(401).json({ message: 'Unauthorized' });
165
- }
166
- }
167
-
168
- // Attach claims to request so Lambda receives them in requestContext.authorizer
169
- req.cognitoAuthorizer = {
170
- claims: decoded,
171
- principalId: decoded.sub
172
- };
173
-
174
- next();
175
- };
176
- }
177
-
178
- setupRoutes() {
179
- // Health check
180
- this.app.get('/health', (req, res) => {
181
- res.json({
182
- status: 'healthy',
183
- service: 'apigateway-simulator',
184
- version: '1.0.0'
185
- });
186
- });
187
-
188
- // Control Plane - REST API
189
- this.app.post('/restapis', async (req, res) => {
190
- try {
191
- const result = this.simulator.createRestApi(req.body);
192
- res.json(result);
193
- } catch (error) {
194
- res.status(400).json({ error: error.message });
195
- }
196
- });
197
-
198
- this.app.get('/restapis', (req, res) => {
199
- const result = this.simulator.getRestApis();
200
- res.json(result);
201
- });
202
-
203
- this.app.get('/restapis/:apiId', (req, res) => {
204
- try {
205
- const result = this.simulator.getRestApi({ restApiId: req.params.apiId });
206
- res.json(result);
207
- } catch (error) {
208
- res.status(404).json({ error: error.message });
209
- }
210
- });
211
-
212
- this.app.patch('/restapis/:apiId', (req, res) => {
213
- try {
214
- const result = this.simulator.updateRestApi({
215
- ...req.body,
216
- restApiId: req.params.apiId
217
- });
218
- res.json(result);
219
- } catch (error) {
220
- res.status(400).json({ error: error.message });
221
- }
222
- });
223
-
224
- this.app.delete('/restapis/:apiId', (req, res) => {
225
- try {
226
- this.simulator.deleteRestApi({ restApiId: req.params.apiId });
227
- res.json({});
228
- } catch (error) {
229
- res.status(404).json({ error: error.message });
230
- }
231
- });
232
-
233
- // Resources
234
- this.app.get('/restapis/:apiId/resources', (req, res) => {
235
- try {
236
- const result = this.simulator.getResources({ restApiId: req.params.apiId });
237
- res.json(result);
238
- } catch (error) {
239
- res.status(404).json({ error: error.message });
240
- }
241
- });
242
-
243
- this.app.post('/restapis/:apiId/resources', (req, res) => {
244
- try {
245
- const result = this.simulator.createResource({
246
- ...req.body,
247
- restApiId: req.params.apiId
248
- });
249
- res.json(result);
250
- } catch (error) {
251
- res.status(400).json({ error: error.message });
252
- }
253
- });
254
-
255
- this.app.delete('/restapis/:apiId/resources/:resourceId', (req, res) => {
256
- try {
257
- this.simulator.deleteResource({
258
- restApiId: req.params.apiId,
259
- resourceId: req.params.resourceId
260
- });
261
- res.json({});
262
- } catch (error) {
263
- res.status(400).json({ error: error.message });
264
- }
265
- });
266
-
267
- // Methods
268
- this.app.put('/restapis/:apiId/resources/:resourceId/methods/:method', (req, res) => {
269
- try {
270
- const result = this.simulator.putMethod({
271
- ...req.body,
272
- restApiId: req.params.apiId,
273
- resourceId: req.params.resourceId,
274
- httpMethod: req.params.method
275
- });
276
- res.json(result);
277
- } catch (error) {
278
- res.status(400).json({ error: error.message });
279
- }
280
- });
281
-
282
- this.app.get('/restapis/:apiId/resources/:resourceId/methods/:method', (req, res) => {
283
- try {
284
- const result = this.simulator.getMethod({
285
- restApiId: req.params.apiId,
286
- resourceId: req.params.resourceId,
287
- httpMethod: req.params.method
288
- });
289
- res.json(result);
290
- } catch (error) {
291
- res.status(404).json({ error: error.message });
292
- }
293
- });
294
-
295
- this.app.delete('/restapis/:apiId/resources/:resourceId/methods/:method', (req, res) => {
296
- try {
297
- this.simulator.deleteMethod({
298
- restApiId: req.params.apiId,
299
- resourceId: req.params.resourceId,
300
- httpMethod: req.params.method
301
- });
302
- res.json({});
303
- } catch (error) {
304
- res.status(404).json({ error: error.message });
305
- }
306
- });
307
-
308
- // Integrations
309
- this.app.put('/restapis/:apiId/resources/:resourceId/methods/:method/integration', (req, res) => {
310
- try {
311
- const result = this.simulator.putIntegration({
312
- ...req.body,
313
- restApiId: req.params.apiId,
314
- resourceId: req.params.resourceId,
315
- httpMethod: req.params.method
316
- });
317
- res.json(result);
318
- } catch (error) {
319
- res.status(400).json({ error: error.message });
320
- }
321
- });
322
-
323
- this.app.get('/restapis/:apiId/resources/:resourceId/methods/:method/integration', (req, res) => {
324
- try {
325
- const result = this.simulator.getIntegration({
326
- restApiId: req.params.apiId,
327
- resourceId: req.params.resourceId,
328
- httpMethod: req.params.method
329
- });
330
- res.json(result);
331
- } catch (error) {
332
- res.status(404).json({ error: error.message });
333
- }
334
- });
335
-
336
- this.app.delete('/restapis/:apiId/resources/:resourceId/methods/:method/integration', (req, res) => {
337
- try {
338
- this.simulator.deleteIntegration({
339
- restApiId: req.params.apiId,
340
- resourceId: req.params.resourceId,
341
- httpMethod: req.params.method
342
- });
343
- res.json({});
344
- } catch (error) {
345
- res.status(404).json({ error: error.message });
346
- }
347
- });
348
-
349
- // Deployments
350
- this.app.post('/restapis/:apiId/deployments', (req, res) => {
351
- try {
352
- const result = this.simulator.createDeployment({
353
- ...req.body,
354
- restApiId: req.params.apiId
355
- });
356
- res.json(result);
357
- } catch (error) {
358
- res.status(400).json({ error: error.message });
359
- }
360
- });
361
-
362
- // Stages
363
- this.app.post('/restapis/:apiId/stages', (req, res) => {
364
- try {
365
- const result = this.simulator.createStage({
366
- ...req.body,
367
- restApiId: req.params.apiId
368
- });
369
- res.json(result);
370
- } catch (error) {
371
- res.status(400).json({ error: error.message });
372
- }
373
- });
374
-
375
- this.app.get('/restapis/:apiId/stages/:stageName', (req, res) => {
376
- try {
377
- const result = this.simulator.getStage({
378
- restApiId: req.params.apiId,
379
- stageName: req.params.stageName
380
- });
381
- res.json(result);
382
- } catch (error) {
383
- res.status(404).json({ error: error.message });
384
- }
385
- });
386
-
387
- this.app.patch('/restapis/:apiId/stages/:stageName', (req, res) => {
388
- try {
389
- const result = this.simulator.updateStage({
390
- ...req.body,
391
- restApiId: req.params.apiId,
392
- stageName: req.params.stageName
393
- });
394
- res.json(result);
395
- } catch (error) {
396
- res.status(400).json({ error: error.message });
397
- }
398
- });
399
-
400
- this.app.delete('/restapis/:apiId/stages/:stageName', (req, res) => {
401
- try {
402
- this.simulator.deleteStage({
403
- restApiId: req.params.apiId,
404
- stageName: req.params.stageName
405
- });
406
- res.json({});
407
- } catch (error) {
408
- res.status(404).json({ error: error.message });
409
- }
410
- });
411
-
412
- // API Keys
413
- this.app.post('/apikeys', (req, res) => {
414
- try {
415
- const result = this.simulator.createApiKey(req.body);
416
- res.json(result);
417
- } catch (error) {
418
- res.status(400).json({ error: error.message });
419
- }
420
- });
421
-
422
- this.app.get('/apikeys', (req, res) => {
423
- const result = this.simulator.getApiKeys(req.query);
424
- res.json(result);
425
- });
426
-
427
- // Usage Plans
428
- this.app.post('/usageplans', (req, res) => {
429
- try {
430
- const result = this.simulator.createUsagePlan(req.body);
431
- res.json(result);
432
- } catch (error) {
433
- res.status(400).json({ error: error.message });
434
- }
435
- });
436
-
437
- // HTTP APIs
438
- this.app.post('/httpapis', (req, res) => {
439
- try {
440
- const result = this.simulator.createHttpApi(req.body);
441
- res.json(result);
442
- } catch (error) {
443
- res.status(400).json({ error: error.message });
444
- }
445
- });
446
-
447
- this.app.post('/httpapis/:apiId/routes', (req, res) => {
448
- try {
449
- const result = this.simulator.createRoute({
450
- ...req.body,
451
- apiId: req.params.apiId
452
- });
453
- res.json(result);
454
- } catch (error) {
455
- res.status(400).json({ error: error.message });
456
- }
457
- });
458
-
459
- // Admin endpoints
460
- this.setupAdminRoutes();
461
- }
462
-
463
- setupProxyRoutes() {
464
- // Proxy para execução das APIs
465
- this.app.all('/:apiId/:stageName/*', async (req, res) => {
466
- const apiId = req.params.apiId;
467
- const stageName = req.params.stageName;
468
- const path = '/' + (req.params[0] || '');
469
-
470
- logger.debug(`🌐 Executando: ${req.method} ${path} (${apiId}/${stageName})`);
471
-
472
- try {
473
- const result = await this.simulator.executeRequest(
474
- apiId,
475
- stageName,
476
- req.method,
477
- path,
478
- req.headers,
479
- req.body,
480
- req.query
481
- );
482
-
483
- res.status(result.statusCode);
484
-
485
- if (result.headers) {
486
- Object.entries(result.headers).forEach(([key, value]) => {
487
- res.set(key, value);
488
- });
489
- }
490
-
491
- res.send(result.body);
492
- } catch (error) {
493
- logger.error('Error executing request:', error);
494
- res.status(500).json({ error: error.message });
495
- }
496
- });
497
- }
498
-
499
- setupAdminRoutes() {
500
- this.app.get('/__admin/apis', (req, res) => {
501
- res.json({
502
- totalApis: this.simulator.getAPIsCount(),
503
- totalDeployments: this.simulator.getDeploymentsCount(),
504
- totalStages: this.simulator.getStagesCount(),
505
- totalResources: this.simulator.getResourcesCount()
506
- });
507
- });
508
-
509
- this.app.get('/__admin/apis/:apiId', (req, res) => {
510
- const api = this.simulator.apis.get(req.params.apiId);
511
- if (api) {
512
- res.json({
513
- id: api.id,
514
- name: api.name,
515
- resources: Array.from(api.resources.keys()),
516
- stages: Array.from(api.stages.keys()),
517
- deployments: Array.from(api.deployments.keys())
518
- });
519
- } else {
520
- res.status(404).json({ error: 'API not found' });
521
- }
522
- });
523
-
524
- this.app.post('/__admin/apis/:apiId/endpoints', (req, res) => {
525
- try {
526
- const result = this.simulator.putEndpoint({
527
- ...req.body,
528
- restApiId: req.params.apiId
529
- });
530
- res.json(result);
531
- } catch (error) {
532
- res.status(400).json({ error: error.message });
533
- }
534
- });
535
-
536
- this.app.delete('/__admin/apis/:apiId/endpoints', (req, res) => {
537
- try {
538
- const result = this.simulator.deleteEndpoint({
539
- restApiId: req.params.apiId,
540
- path: req.query.path,
541
- method: req.query.method
542
- });
543
- res.json(result);
544
- } catch (error) {
545
- res.status(400).json({ error: error.message });
546
- }
547
- });
548
-
549
- this.app.get('/__admin/apikeys', (req, res) => {
550
- const keys = Array.from(this.simulator.apiKeys.values()).map(k => ({
551
- id: k.id,
552
- name: k.name,
553
- enabled: k.enabled,
554
- createdDate: k.createdDate
555
- }));
556
- res.json(keys);
557
- });
558
-
559
- this.app.get('/__admin/usageplans', (req, res) => {
560
- const plans = Array.from(this.simulator.usagePlans.values());
561
- res.json(plans);
562
- });
563
- }
564
-
565
- start() {
566
- return new Promise((resolve) => {
567
- this.server = this.app.listen(this.port, () => {
568
- logger.info(`🌐 API Gateway rodando em http://localhost:${this.port}`);
569
- this.printInfo();
570
- resolve();
571
- });
572
- });
573
- }
574
-
575
- printInfo() {
576
- logger.info('\n📡 API Gateway Endpoints:');
577
- logger.info(` Control Plane: http://localhost:${this.port}/restapis`);
578
- logger.info(` Execute APIs: http://localhost:${this.port}/{apiId}/{stageName}/{path}`);
579
- logger.info('\n📚 Admin Endpoints:');
580
- logger.info(` GET http://localhost:${this.port}/__admin/apis`);
581
- logger.info(` GET http://localhost:${this.port}/__admin/apikeys`);
582
- logger.info(` GET http://localhost:${this.port}/__admin/usageplans`);
583
- }
584
-
585
- stop() {
586
- return new Promise((resolve) => {
587
- if (this.server) {
588
- this.server.close(() => resolve());
589
- } else {
590
- resolve();
591
- }
592
- });
593
- }
594
-
595
- getStatus() {
596
- return {
597
- running: !!this.server,
598
- port: this.port,
599
- endpoint: `http://localhost:${this.port}`,
600
- apisCount: this.simulator?.getAPIsCount() || 0,
601
- deploymentsCount: this.simulator?.getDeploymentsCount() || 0,
602
- stagesCount: this.simulator?.getStagesCount() || 0,
603
- resourcesCount: this.simulator?.getResourcesCount() || 0
604
- };
605
- }
606
- }
607
-
1
+ /**
2
+ * API Gateway Server - Servidor HTTP para API Gateway
3
+ */
4
+
5
+ const express = require('express');
6
+ const cors = require('cors');
7
+ const logger = require('../../utils/logger');
8
+
9
+ class APIGatewayServer {
10
+ constructor(port, config) {
11
+ this.port = port;
12
+ this.config = config;
13
+ this.app = express();
14
+ this.simulator = null;
15
+ this.server = null;
16
+ this.setupMiddlewares();
17
+ }
18
+
19
+ setupMiddlewares() {
20
+ this.app.use(express.json({ limit: '10mb' }));
21
+ this.app.use(express.urlencoded({ extended: true, limit: '10mb' }));
22
+ // Parse bodies with AWS content types (e.g. application/x-amz-json-1.1)
23
+ this.app.use((req, res, next) => {
24
+ const ct = req.headers['content-type'] || '';
25
+ if ((req.body && JSON.stringify(req.body) == "{}") && ct.includes('application/x-amz-json')) {
26
+ let data = "";
27
+ req.on("data", (chunk) => {
28
+ data += chunk;
29
+ });
30
+ req.on("end", () => {
31
+ try {
32
+ req.body = JSON.parse(data);
33
+ } catch (error) {
34
+ req.body = {};
35
+ }
36
+ next();
37
+ });
38
+ } else {
39
+ next();
40
+ }
41
+ });
42
+ this.app.use(cors());
43
+
44
+ if (logger.currentLogLevel === 'verboso') {
45
+ this.app.use((req, res, next) => {
46
+ const start = Date.now();
47
+ res.on('finish', () => {
48
+ const duration = Date.now() - start;
49
+ logger.verboso(`API Gateway: ${req.method} ${req.path} - ${duration}ms`);
50
+ });
51
+ next();
52
+ });
53
+ }
54
+ }
55
+
56
+ async initialize() {
57
+ this.setupRoutes();
58
+ this.setupConfigRoutes();
59
+ this.setupProxyRoutes();
60
+ logger.debug('API Gateway Server inicializado');
61
+ }
62
+
63
+ setupConfigRoutes() {
64
+ // Register routes from aws-local-simulator.json config directly
65
+ const apis = this.config.apigateway?.apis || [];
66
+ for (const api of apis) {
67
+ // Build Cognito authorizer middleware for this API if configured
68
+ const cognitoAuthorizer = this._buildCognitoAuthorizer(api.authorizer);
69
+
70
+ for (const endpoint of (api.endpoints || [])) {
71
+ const { path, method, lambdaName, integrationType, authorizerRequired } = endpoint;
72
+ if (!path || !method) continue;
73
+
74
+ const expressPath = path.replace(/\{([^}]+)\}/g, ':$1');
75
+ const httpMethod = method.toLowerCase();
76
+
77
+ logger.debug(`📡 Registrando rota: ${method} ${path} -> ${lambdaName}`);
78
+
79
+ const handler = async (req, res) => {
80
+ try {
81
+ const lambdaService = this.lambdaService;
82
+ if (!lambdaService) {
83
+ return res.status(500).json({ error: 'Lambda service not available' });
84
+ }
85
+
86
+ const event = {
87
+ httpMethod: req.method,
88
+ path: req.path,
89
+ headers: req.headers,
90
+ queryStringParameters: Object.keys(req.query).length ? req.query : null,
91
+ pathParameters: Object.keys(req.params).length ? req.params : null,
92
+ body: req.body ? JSON.stringify(req.body) : null,
93
+ isBase64Encoded: false,
94
+ requestContext: {
95
+ path: req.path,
96
+ stage: 'local',
97
+ requestId: Math.random().toString(36).substring(7),
98
+ identity: { sourceIp: req.ip },
99
+ authorizer: req.cognitoAuthorizer || null
100
+ }
101
+ };
102
+
103
+ const result = await lambdaService.simulator.invoke(lambdaName, event);
104
+ const payload = result.Payload || {};
105
+ const statusCode = payload.statusCode || 200;
106
+ const headers = payload.headers || { 'Content-Type': 'application/json' };
107
+ const body = payload.body;
108
+
109
+ res.status(statusCode).set(headers).send(body);
110
+ } catch (err) {
111
+ logger.error(`Lambda invoke error (${lambdaName}):`, err);
112
+ res.status(500).json({ error: err.message });
113
+ }
114
+ };
115
+
116
+ const middlewares = [];
117
+ if (authorizerRequired && cognitoAuthorizer) {
118
+ middlewares.push(cognitoAuthorizer);
119
+ }
120
+ middlewares.push(handler);
121
+
122
+ const ANY_METHODS = ['get', 'post', 'put', 'delete', 'patch', 'head', 'options'];
123
+ if (httpMethod === 'any') {
124
+ for (const m of ANY_METHODS) {
125
+ this.app[m](expressPath, ...middlewares);
126
+ }
127
+ } else {
128
+ this.app[httpMethod](expressPath, ...middlewares);
129
+ }
130
+ }
131
+ }
132
+ }
133
+
134
+ _buildCognitoAuthorizer(authorizerConfig) {
135
+ if (!authorizerConfig) return null;
136
+ if (authorizerConfig.type !== 'COGNITO_USER_POOLS') return null;
137
+
138
+ const { userPoolId } = authorizerConfig;
139
+
140
+ return (req, res, next) => {
141
+ const authHeader = req.headers['authorization'] || req.headers['Authorization'];
142
+ if (!authHeader) {
143
+ return res.status(401).json({ message: 'Unauthorized' });
144
+ }
145
+
146
+ const token = authHeader.startsWith('Bearer ') ? authHeader.slice(7) : authHeader;
147
+
148
+ const cognitoSimulator = this.cognitoService?.simulator;
149
+ if (!cognitoSimulator) {
150
+ logger.warn('⚠️ Cognito authorizer configured but Cognito service is not available');
151
+ return res.status(500).json({ message: 'Authorizer unavailable' });
152
+ }
153
+
154
+ // Validate the token belongs to the configured user pool
155
+ const decoded = cognitoSimulator.verifyAccessToken(token);
156
+ if (!decoded) {
157
+ return res.status(401).json({ message: 'Unauthorized' });
158
+ }
159
+
160
+ // If a specific userPoolId is required, verify it matches
161
+ if (userPoolId && decoded['cognito:username'] !== undefined) {
162
+ const session = cognitoSimulator.accessTokens.get(token);
163
+ if (session && userPoolId && session.UserPoolId !== userPoolId) {
164
+ return res.status(401).json({ message: 'Unauthorized' });
165
+ }
166
+ }
167
+
168
+ // Attach claims to request so Lambda receives them in requestContext.authorizer
169
+ req.cognitoAuthorizer = {
170
+ claims: decoded,
171
+ principalId: decoded.sub
172
+ };
173
+
174
+ next();
175
+ };
176
+ }
177
+
178
+ setupRoutes() {
179
+ // Health check
180
+ this.app.get('/health', (req, res) => {
181
+ res.json({
182
+ status: 'healthy',
183
+ service: 'apigateway-simulator',
184
+ version: '1.0.0'
185
+ });
186
+ });
187
+
188
+ // Control Plane - REST API
189
+ this.app.post('/restapis', async (req, res) => {
190
+ try {
191
+ const result = this.simulator.createRestApi(req.body);
192
+ res.json(result);
193
+ } catch (error) {
194
+ res.status(400).json({ error: error.message });
195
+ }
196
+ });
197
+
198
+ this.app.get('/restapis', (req, res) => {
199
+ const result = this.simulator.getRestApis();
200
+ res.json(result);
201
+ });
202
+
203
+ this.app.get('/restapis/:apiId', (req, res) => {
204
+ try {
205
+ const result = this.simulator.getRestApi({ restApiId: req.params.apiId });
206
+ res.json(result);
207
+ } catch (error) {
208
+ res.status(404).json({ error: error.message });
209
+ }
210
+ });
211
+
212
+ this.app.patch('/restapis/:apiId', (req, res) => {
213
+ try {
214
+ const result = this.simulator.updateRestApi({
215
+ ...req.body,
216
+ restApiId: req.params.apiId
217
+ });
218
+ res.json(result);
219
+ } catch (error) {
220
+ res.status(400).json({ error: error.message });
221
+ }
222
+ });
223
+
224
+ this.app.delete('/restapis/:apiId', (req, res) => {
225
+ try {
226
+ this.simulator.deleteRestApi({ restApiId: req.params.apiId });
227
+ res.json({});
228
+ } catch (error) {
229
+ res.status(404).json({ error: error.message });
230
+ }
231
+ });
232
+
233
+ // Resources
234
+ this.app.get('/restapis/:apiId/resources', (req, res) => {
235
+ try {
236
+ const result = this.simulator.getResources({ restApiId: req.params.apiId });
237
+ res.json(result);
238
+ } catch (error) {
239
+ res.status(404).json({ error: error.message });
240
+ }
241
+ });
242
+
243
+ this.app.post('/restapis/:apiId/resources', (req, res) => {
244
+ try {
245
+ const result = this.simulator.createResource({
246
+ ...req.body,
247
+ restApiId: req.params.apiId
248
+ });
249
+ res.json(result);
250
+ } catch (error) {
251
+ res.status(400).json({ error: error.message });
252
+ }
253
+ });
254
+
255
+ this.app.delete('/restapis/:apiId/resources/:resourceId', (req, res) => {
256
+ try {
257
+ this.simulator.deleteResource({
258
+ restApiId: req.params.apiId,
259
+ resourceId: req.params.resourceId
260
+ });
261
+ res.json({});
262
+ } catch (error) {
263
+ res.status(400).json({ error: error.message });
264
+ }
265
+ });
266
+
267
+ // Methods
268
+ this.app.put('/restapis/:apiId/resources/:resourceId/methods/:method', (req, res) => {
269
+ try {
270
+ const result = this.simulator.putMethod({
271
+ ...req.body,
272
+ restApiId: req.params.apiId,
273
+ resourceId: req.params.resourceId,
274
+ httpMethod: req.params.method
275
+ });
276
+ res.json(result);
277
+ } catch (error) {
278
+ res.status(400).json({ error: error.message });
279
+ }
280
+ });
281
+
282
+ this.app.get('/restapis/:apiId/resources/:resourceId/methods/:method', (req, res) => {
283
+ try {
284
+ const result = this.simulator.getMethod({
285
+ restApiId: req.params.apiId,
286
+ resourceId: req.params.resourceId,
287
+ httpMethod: req.params.method
288
+ });
289
+ res.json(result);
290
+ } catch (error) {
291
+ res.status(404).json({ error: error.message });
292
+ }
293
+ });
294
+
295
+ this.app.delete('/restapis/:apiId/resources/:resourceId/methods/:method', (req, res) => {
296
+ try {
297
+ this.simulator.deleteMethod({
298
+ restApiId: req.params.apiId,
299
+ resourceId: req.params.resourceId,
300
+ httpMethod: req.params.method
301
+ });
302
+ res.json({});
303
+ } catch (error) {
304
+ res.status(404).json({ error: error.message });
305
+ }
306
+ });
307
+
308
+ // Integrations
309
+ this.app.put('/restapis/:apiId/resources/:resourceId/methods/:method/integration', (req, res) => {
310
+ try {
311
+ const result = this.simulator.putIntegration({
312
+ ...req.body,
313
+ restApiId: req.params.apiId,
314
+ resourceId: req.params.resourceId,
315
+ httpMethod: req.params.method
316
+ });
317
+ res.json(result);
318
+ } catch (error) {
319
+ res.status(400).json({ error: error.message });
320
+ }
321
+ });
322
+
323
+ this.app.get('/restapis/:apiId/resources/:resourceId/methods/:method/integration', (req, res) => {
324
+ try {
325
+ const result = this.simulator.getIntegration({
326
+ restApiId: req.params.apiId,
327
+ resourceId: req.params.resourceId,
328
+ httpMethod: req.params.method
329
+ });
330
+ res.json(result);
331
+ } catch (error) {
332
+ res.status(404).json({ error: error.message });
333
+ }
334
+ });
335
+
336
+ this.app.delete('/restapis/:apiId/resources/:resourceId/methods/:method/integration', (req, res) => {
337
+ try {
338
+ this.simulator.deleteIntegration({
339
+ restApiId: req.params.apiId,
340
+ resourceId: req.params.resourceId,
341
+ httpMethod: req.params.method
342
+ });
343
+ res.json({});
344
+ } catch (error) {
345
+ res.status(404).json({ error: error.message });
346
+ }
347
+ });
348
+
349
+ // Deployments
350
+ this.app.post('/restapis/:apiId/deployments', (req, res) => {
351
+ try {
352
+ const result = this.simulator.createDeployment({
353
+ ...req.body,
354
+ restApiId: req.params.apiId
355
+ });
356
+ res.json(result);
357
+ } catch (error) {
358
+ res.status(400).json({ error: error.message });
359
+ }
360
+ });
361
+
362
+ // Stages
363
+ this.app.post('/restapis/:apiId/stages', (req, res) => {
364
+ try {
365
+ const result = this.simulator.createStage({
366
+ ...req.body,
367
+ restApiId: req.params.apiId
368
+ });
369
+ res.json(result);
370
+ } catch (error) {
371
+ res.status(400).json({ error: error.message });
372
+ }
373
+ });
374
+
375
+ this.app.get('/restapis/:apiId/stages/:stageName', (req, res) => {
376
+ try {
377
+ const result = this.simulator.getStage({
378
+ restApiId: req.params.apiId,
379
+ stageName: req.params.stageName
380
+ });
381
+ res.json(result);
382
+ } catch (error) {
383
+ res.status(404).json({ error: error.message });
384
+ }
385
+ });
386
+
387
+ this.app.patch('/restapis/:apiId/stages/:stageName', (req, res) => {
388
+ try {
389
+ const result = this.simulator.updateStage({
390
+ ...req.body,
391
+ restApiId: req.params.apiId,
392
+ stageName: req.params.stageName
393
+ });
394
+ res.json(result);
395
+ } catch (error) {
396
+ res.status(400).json({ error: error.message });
397
+ }
398
+ });
399
+
400
+ this.app.delete('/restapis/:apiId/stages/:stageName', (req, res) => {
401
+ try {
402
+ this.simulator.deleteStage({
403
+ restApiId: req.params.apiId,
404
+ stageName: req.params.stageName
405
+ });
406
+ res.json({});
407
+ } catch (error) {
408
+ res.status(404).json({ error: error.message });
409
+ }
410
+ });
411
+
412
+ // API Keys
413
+ this.app.post('/apikeys', (req, res) => {
414
+ try {
415
+ const result = this.simulator.createApiKey(req.body);
416
+ res.json(result);
417
+ } catch (error) {
418
+ res.status(400).json({ error: error.message });
419
+ }
420
+ });
421
+
422
+ this.app.get('/apikeys', (req, res) => {
423
+ const result = this.simulator.getApiKeys(req.query);
424
+ res.json(result);
425
+ });
426
+
427
+ // Usage Plans
428
+ this.app.post('/usageplans', (req, res) => {
429
+ try {
430
+ const result = this.simulator.createUsagePlan(req.body);
431
+ res.json(result);
432
+ } catch (error) {
433
+ res.status(400).json({ error: error.message });
434
+ }
435
+ });
436
+
437
+ // HTTP APIs
438
+ this.app.post('/httpapis', (req, res) => {
439
+ try {
440
+ const result = this.simulator.createHttpApi(req.body);
441
+ res.json(result);
442
+ } catch (error) {
443
+ res.status(400).json({ error: error.message });
444
+ }
445
+ });
446
+
447
+ this.app.post('/httpapis/:apiId/routes', (req, res) => {
448
+ try {
449
+ const result = this.simulator.createRoute({
450
+ ...req.body,
451
+ apiId: req.params.apiId
452
+ });
453
+ res.json(result);
454
+ } catch (error) {
455
+ res.status(400).json({ error: error.message });
456
+ }
457
+ });
458
+
459
+ // Admin endpoints
460
+ this.setupAdminRoutes();
461
+ }
462
+
463
+ setupProxyRoutes() {
464
+ // Proxy para execução das APIs
465
+ this.app.all('/:apiId/:stageName/*', async (req, res) => {
466
+ const apiId = req.params.apiId;
467
+ const stageName = req.params.stageName;
468
+ const path = '/' + (req.params[0] || '');
469
+
470
+ logger.debug(`🌐 Executando: ${req.method} ${path} (${apiId}/${stageName})`);
471
+
472
+ try {
473
+ const result = await this.simulator.executeRequest(
474
+ apiId,
475
+ stageName,
476
+ req.method,
477
+ path,
478
+ req.headers,
479
+ req.body,
480
+ req.query
481
+ );
482
+
483
+ res.status(result.statusCode);
484
+
485
+ if (result.headers) {
486
+ Object.entries(result.headers).forEach(([key, value]) => {
487
+ res.set(key, value);
488
+ });
489
+ }
490
+
491
+ res.send(result.body);
492
+ } catch (error) {
493
+ logger.error('Error executing request:', error);
494
+ res.status(500).json({ error: error.message });
495
+ }
496
+ });
497
+ }
498
+
499
+ setupAdminRoutes() {
500
+ this.app.get('/__admin/apis', (req, res) => {
501
+ res.json({
502
+ totalApis: this.simulator.getAPIsCount(),
503
+ totalDeployments: this.simulator.getDeploymentsCount(),
504
+ totalStages: this.simulator.getStagesCount(),
505
+ totalResources: this.simulator.getResourcesCount()
506
+ });
507
+ });
508
+
509
+ this.app.get('/__admin/apis/:apiId', (req, res) => {
510
+ const api = this.simulator.apis.get(req.params.apiId);
511
+ if (api) {
512
+ res.json({
513
+ id: api.id,
514
+ name: api.name,
515
+ resources: Array.from(api.resources.keys()),
516
+ stages: Array.from(api.stages.keys()),
517
+ deployments: Array.from(api.deployments.keys())
518
+ });
519
+ } else {
520
+ res.status(404).json({ error: 'API not found' });
521
+ }
522
+ });
523
+
524
+ this.app.post('/__admin/apis/:apiId/endpoints', (req, res) => {
525
+ try {
526
+ const result = this.simulator.putEndpoint({
527
+ ...req.body,
528
+ restApiId: req.params.apiId
529
+ });
530
+ res.json(result);
531
+ } catch (error) {
532
+ res.status(400).json({ error: error.message });
533
+ }
534
+ });
535
+
536
+ this.app.delete('/__admin/apis/:apiId/endpoints', (req, res) => {
537
+ try {
538
+ const result = this.simulator.deleteEndpoint({
539
+ restApiId: req.params.apiId,
540
+ path: req.query.path,
541
+ method: req.query.method
542
+ });
543
+ res.json(result);
544
+ } catch (error) {
545
+ res.status(400).json({ error: error.message });
546
+ }
547
+ });
548
+
549
+ this.app.get('/__admin/apikeys', (req, res) => {
550
+ const keys = Array.from(this.simulator.apiKeys.values()).map(k => ({
551
+ id: k.id,
552
+ name: k.name,
553
+ enabled: k.enabled,
554
+ createdDate: k.createdDate
555
+ }));
556
+ res.json(keys);
557
+ });
558
+
559
+ this.app.get('/__admin/usageplans', (req, res) => {
560
+ const plans = Array.from(this.simulator.usagePlans.values());
561
+ res.json(plans);
562
+ });
563
+ }
564
+
565
+ start() {
566
+ return new Promise((resolve) => {
567
+ this.server = this.app.listen(this.port, () => {
568
+ logger.info(`🌐 API Gateway rodando em http://localhost:${this.port}`);
569
+ this.printInfo();
570
+ resolve();
571
+ });
572
+ });
573
+ }
574
+
575
+ printInfo() {
576
+ logger.info('\n📡 API Gateway Endpoints:');
577
+ logger.info(` Control Plane: http://localhost:${this.port}/restapis`);
578
+ logger.info(` Execute APIs: http://localhost:${this.port}/{apiId}/{stageName}/{path}`);
579
+ logger.info('\n📚 Admin Endpoints:');
580
+ logger.info(` GET http://localhost:${this.port}/__admin/apis`);
581
+ logger.info(` GET http://localhost:${this.port}/__admin/apikeys`);
582
+ logger.info(` GET http://localhost:${this.port}/__admin/usageplans`);
583
+ }
584
+
585
+ stop() {
586
+ return new Promise((resolve) => {
587
+ if (this.server) {
588
+ this.server.close(() => resolve());
589
+ } else {
590
+ resolve();
591
+ }
592
+ });
593
+ }
594
+
595
+ getStatus() {
596
+ return {
597
+ running: !!this.server,
598
+ port: this.port,
599
+ endpoint: `http://localhost:${this.port}`,
600
+ apisCount: this.simulator?.getAPIsCount() || 0,
601
+ deploymentsCount: this.simulator?.getDeploymentsCount() || 0,
602
+ stagesCount: this.simulator?.getStagesCount() || 0,
603
+ resourcesCount: this.simulator?.getResourcesCount() || 0
604
+ };
605
+ }
606
+ }
607
+
608
608
  module.exports = APIGatewayServer;