@gugananuvem/aws-local-simulator 1.0.15 → 1.0.17

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