@gugananuvem/aws-local-simulator 1.0.14 → 1.0.16

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/README.md CHANGED
@@ -81,7 +81,9 @@ npm install --save-dev aws-local-simulator
81
81
  ]
82
82
  },
83
83
  "s3": {
84
- "buckets": ["my-bucket"]
84
+ "buckets": [
85
+ { "name": "my-bucket", "region": "us-east-1" }
86
+ ]
85
87
  },
86
88
  "sqs": {
87
89
  "queues": ["my-queue", "dead-letter-queue"]
@@ -190,6 +192,7 @@ npx aws-local-simulator status
190
192
  |---------|----------|-------|
191
193
  | DynamoDB | http://localhost:8000 | http://localhost:8000/__admin/tables |
192
194
  | S3 | http://localhost:4566 | http://localhost:4566/__admin/buckets |
195
+ | S3 Website | http://localhost:4566/website/{bucket}/ | — |
193
196
  | SQS | http://localhost:9324 | http://localhost:9324/__admin/queues |
194
197
  | Lambda | http://localhost:3001 | http://localhost:3001/__admin/functions |
195
198
  | Cognito | http://localhost:9229 | http://localhost:9229/__admin/userpools |
@@ -230,6 +233,46 @@ aws lambda invoke \
230
233
  # Cognito
231
234
  aws cognito-idp list-user-pools --max-results 10 --endpoint-url http://localhost:9229
232
235
 
236
+ # Cognito cadastrar um usuario pelo adminstrador
237
+ aws cognito-idp admin-create-user \
238
+ --user-pool-id us-east-xxxx\
239
+ --username usuario@email.com \
240
+ --user-attributes \
241
+ Name=email,Value=usuario@email.com \
242
+ Name=email_verified,Value=false \
243
+ Name=name,Value="nome usuario" \
244
+ Name=custom:role,Value="user" \
245
+ temporary-password "Teste@123456" \
246
+ --message-action SUPPRESS \
247
+ --endpoint-url http://localhost:9229
248
+
249
+ # Cognito excluir um usuario
250
+ aws cognito-idp admin-delete-user \
251
+ --user-pool-id us-east-xxxx \
252
+ --username usuario@email.com
253
+ --endpoint-url http://localhost:9229
254
+
255
+ # Cognito Registrar usuário
256
+ aws cognito-idp sign-up \
257
+ --client-id $CLIENT_ID \
258
+ --username usuario@email.com \
259
+ --password "Teste@123456" \
260
+ --user-attributes Name=email,Value=usuario@email.com Name=name,Value="nome usuario"
261
+ --endpoint-url http://localhost:9229
262
+
263
+ # 2. Confirmar usuário administrativamente
264
+ aws cognito-idp admin-confirm-sign-up \
265
+ --user-pool-id us-east-xxxx \
266
+ --username usuario@email.com
267
+ --endpoint-url http://localhost:9229
268
+
269
+ # O código chega no e-mail do usuário. Algo como: "Your confirmation code is 123456"
270
+ aws cognito-idp confirm-sign-up \
271
+ --client-id 3n4b5urk1ft4fl3mg5e62d9ado \
272
+ --username usuario@email.com \
273
+ --confirmation-code 123456
274
+ --endpoint-url http://localhost:9229
275
+
233
276
  # STS
234
277
  aws sts get-caller-identity --endpoint-url http://localhost:9326
235
278
  aws sts assume-role \
@@ -359,6 +402,76 @@ aws athena create-named-query \
359
402
  aws athena list-named-queries --endpoint-url http://localhost:4599
360
403
  ```
361
404
 
405
+ ## ⚙️ Configuração S3
406
+
407
+ ### Buckets simples
408
+
409
+ ```json
410
+ {
411
+ "s3": {
412
+ "buckets": [
413
+ "my-bucket"
414
+ ]
415
+ }
416
+ }
417
+ ```
418
+
419
+ ### Buckets com região e website estático
420
+
421
+ ```json
422
+ {
423
+ "s3": {
424
+ "buckets": [
425
+ { "name": "my-bucket", "region": "us-east-1" },
426
+ {
427
+ "name": "my-site-bucket",
428
+ "region": "us-east-1",
429
+ "websiteConfiguration": {
430
+ "IndexDocument": { "Suffix": "index.html" },
431
+ "ErrorDocument": { "Key": "error.html" }
432
+ }
433
+ }
434
+ ]
435
+ }
436
+ }
437
+ ```
438
+
439
+ Quando `websiteConfiguration` está presente, o bucket serve arquivos estáticos.
440
+
441
+ ### URL do website estático
442
+
443
+ ```
444
+ http://localhost:4566/website/{bucket-name}/
445
+ http://localhost:4566/website/{bucket-name}/caminho/pagina.html
446
+ ```
447
+
448
+ Exemplos:
449
+ ```
450
+ http://localhost:4566/website/my-site-bucket/ → serve index.html
451
+ http://localhost:4566/website/my-site-bucket/about.html → serve about.html
452
+ http://localhost:4566/website/my-site-bucket/app/ → serve app/index.html
453
+ ```
454
+
455
+ ### Gerenciar website config via AWS CLI
456
+
457
+ ```bash
458
+ # Habilitar website em um bucket existente
459
+ aws s3api put-bucket-website \
460
+ --bucket my-site-bucket \
461
+ --website-configuration '{"IndexDocument":{"Suffix":"index.html"},"ErrorDocument":{"Key":"error.html"}}' \
462
+ --endpoint-url http://localhost:4566
463
+
464
+ # Ver configuração de website
465
+ aws s3api get-bucket-website \
466
+ --bucket my-site-bucket \
467
+ --endpoint-url http://localhost:4566
468
+
469
+ # Remover website
470
+ aws s3api delete-bucket-website \
471
+ --bucket my-site-bucket \
472
+ --endpoint-url http://localhost:4566
473
+ ```
474
+
362
475
  ## ⚙️ Configuração de Lambdas
363
476
 
364
477
  Lambdas são registradas por **nome** e invocadas via API de invocação (igual à AWS real). O roteamento HTTP é feito pelo API Gateway.
@@ -378,6 +491,50 @@ Lambdas são registradas por **nome** e invocadas via API de invocação (igual
378
491
  }
379
492
  ```
380
493
 
494
+ ## ⚙️ Configuração SQS com Lambda Trigger
495
+
496
+ Para disparar uma Lambda automaticamente quando uma mensagem chega na fila, use o formato de objeto na lista de filas com `lambdaName`:
497
+
498
+ ```json
499
+ {
500
+ "lambdas": [
501
+ {
502
+ "name": "process-orders",
503
+ "handler": "./src/handlers/process-orders.js"
504
+ }
505
+ ],
506
+ "sqs": {
507
+ "queues": [
508
+ "simple-queue",
509
+ {
510
+ "name": "orders-queue",
511
+ "lambdaName": "process-orders",
512
+ "batchSize": 5
513
+ }
514
+ ]
515
+ }
516
+ }
517
+ ```
518
+
519
+ - Filas simples (string) são criadas sem trigger
520
+ - Filas com objeto aceitam `lambdaName` (nome da Lambda registrada) e `batchSize` (padrão: 10)
521
+ - Quando uma mensagem é enviada para `orders-queue`, a Lambda `process-orders` é invocada automaticamente com o evento no formato SQS padrão da AWS
522
+
523
+ O handler recebe o evento no formato padrão AWS SQS:
524
+
525
+ ```javascript
526
+ exports.handler = async (event) => {
527
+ for (const record of event.Records) {
528
+ const body = JSON.parse(record.body);
529
+ console.log('Mensagem recebida:', body);
530
+ // processar...
531
+ }
532
+
533
+ // Retornar batchItemFailures para reprocessar mensagens específicas
534
+ return { batchItemFailures: [] };
535
+ };
536
+ ```
537
+
381
538
  ## ⚙️ Configuração API GATEWAY
382
539
 
383
540
  O valor do **lambdaName** deve igual ao nome Lambda que está registrada com o valor **name**. Ex: "my-user-function".
@@ -413,15 +570,166 @@ O valor do **lambdaName** deve igual ao nome Lambda que está registrada com o v
413
570
  "method": "DELETE",
414
571
  "lambdaName": "my-user-function",
415
572
  "integrationType": "lambda"
573
+ },
574
+ {
575
+ "path": "/user/{id}",
576
+ "method": "ANY",
577
+ "lambdaName": "my-user-function",
578
+ "integrationType": "lambda"
416
579
  }
417
580
  ]
418
581
  }
419
582
  ]
420
- },
583
+ }
584
+ }
585
+ ```
421
586
 
587
+ ### Método ANY
588
+
589
+ Use `"method": "ANY"` para registrar um endpoint que aceita todos os verbos HTTP (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS) — equivalente ao `ANY` do AWS API Gateway real.
590
+
591
+ ```json
592
+ {
593
+ "path": "/webhook",
594
+ "method": "ANY",
595
+ "lambdaName": "my-webhook-handler",
596
+ "integrationType": "lambda"
422
597
  }
423
598
  ```
424
599
 
600
+ ### API Key
601
+
602
+ O API Gateway suporta validação de API Key via header `x-api-key`. Para proteger um endpoint, declare as `apiKeys` na configuração e use `"apiKeyRequired": true` no endpoint:
603
+
604
+ ```json
605
+ {
606
+ "apigateway": {
607
+ "apiKeys": [
608
+ {
609
+ "name": "my-app-key",
610
+ "value": "minha-chave-secreta-123"
611
+ }
612
+ ],
613
+ "apis": [
614
+ {
615
+ "name": "Protected API",
616
+ "endpoints": [
617
+ {
618
+ "path": "/protected",
619
+ "method": "GET",
620
+ "lambdaName": "my-user-function",
621
+ "integrationType": "lambda",
622
+ "apiKeyRequired": true
623
+ },
624
+ {
625
+ "path": "/public",
626
+ "method": "GET",
627
+ "lambdaName": "my-user-function",
628
+ "integrationType": "lambda"
629
+ }
630
+ ]
631
+ }
632
+ ]
633
+ }
634
+ }
635
+ ```
636
+
637
+ Ao chamar um endpoint protegido, envie o header:
638
+
639
+ ```bash
640
+ curl http://localhost:4567/protected \
641
+ -H "x-api-key: minha-chave-secreta-123"
642
+
643
+ # Sem a key → 403 Forbidden
644
+ curl http://localhost:4567/protected
645
+ ```
646
+
647
+ > **Nota:** A validação de API Key só está ativa no fluxo de proxy (`/:apiId/:stageName/*`). Endpoints declarados diretamente no `aws-local-simulator.json` via `setupConfigRoutes` não aplicam a validação de API Key no momento.
648
+
649
+ ### Cognito Authorizer
650
+
651
+ O API Gateway suporta autenticação via Cognito User Pools. Configure um `authorizer` na API e marque os endpoints protegidos com `"authorizerRequired": true`:
652
+
653
+ ```json
654
+ {
655
+ "cognito": {
656
+ "userPools": [
657
+ {
658
+ "PoolName": "my-user-pool",
659
+ "UserPoolId": "us-east-XXXXX",
660
+ "ClientId": "XXXXXX",
661
+ "AutoVerifiedAttributes": ["email"]
662
+ }
663
+ ]
664
+ },
665
+ "apigateway": {
666
+ "apis": [
667
+ {
668
+ "name": "My API",
669
+ "authorizer": {
670
+ "type": "COGNITO_USER_POOLS",
671
+ "userPoolId": "us-east-XXXXX"
672
+ },
673
+ "endpoints": [
674
+ {
675
+ "path": "/profile",
676
+ "method": "GET",
677
+ "lambdaName": "my-user-function",
678
+ "integrationType": "lambda",
679
+ "authorizerRequired": true
680
+ },
681
+ {
682
+ "path": "/public",
683
+ "method": "GET",
684
+ "lambdaName": "my-user-function",
685
+ "integrationType": "lambda"
686
+ }
687
+ ]
688
+ }
689
+ ]
690
+ }
691
+ }
692
+ ```
693
+
694
+ O simulador valida o JWT do Cognito local no header `Authorization: Bearer <token>`. Se o token for inválido ou ausente, retorna `401 Unauthorized`.
695
+
696
+ ```bash
697
+ # 1. Autenticar e obter o token
698
+ TOKEN=$(curl -s -X POST http://localhost:9229/ \
699
+ -H "Content-Type: application/x-amz-json-1.1" \
700
+ -H "X-Amz-Target: AWSCognitoIdentityProviderService.InitiateAuth" \
701
+ -d '{
702
+ "AuthFlow": "USER_PASSWORD_AUTH",
703
+ "ClientId": "XXXXXX",
704
+ "AuthParameters": {
705
+ "USERNAME": "usuario@email.com",
706
+ "PASSWORD": "Senha@123"
707
+ }
708
+ }' | jq -r '.AuthenticationResult.IdToken')
709
+
710
+ # 2. Chamar endpoint protegido com o token
711
+ curl http://localhost:4567/profile \
712
+ -H "Authorization: Bearer $TOKEN"
713
+
714
+ # Sem token → 401 Unauthorized
715
+ curl http://localhost:4567/profile
716
+ ```
717
+
718
+ O token decodificado fica disponível no Lambda em `event.requestContext.authorizer.claims`:
719
+
720
+ ```javascript
721
+ exports.handler = async (event) => {
722
+ const claims = event.requestContext.authorizer?.claims;
723
+ const userId = claims?.sub;
724
+ const email = claims?.email;
725
+
726
+ return {
727
+ statusCode: 200,
728
+ body: JSON.stringify({ userId, email })
729
+ };
730
+ };
731
+ ```
732
+
425
733
  O handler deve exportar uma função padrão:
426
734
 
427
735
  ```javascript
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gugananuvem/aws-local-simulator",
3
- "version": "1.0.14",
3
+ "version": "1.0.16",
4
4
  "description": "Simulador local completo para serviços AWS",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -81,22 +81,23 @@
81
81
  "directory": "dist"
82
82
  },
83
83
  "dependencies": {
84
- "express": "^4.18.2",
85
84
  "cors": "^2.8.5",
86
- "uuid": "^9.0.0",
87
- "mkdirp": "^3.0.1",
88
- "glob": "^10.3.0",
89
85
  "dotenv": "^16.3.1",
86
+ "express": "^4.18.2",
87
+ "glob": "^10.3.0",
90
88
  "jsonwebtoken": "^9.0.2",
91
- "urlpattern-polyfill": "^10.0.0"
89
+ "mkdirp": "^3.0.1",
90
+ "urlpattern-polyfill": "^10.0.0",
91
+ "uuid": "^9.0.0"
92
92
  },
93
93
  "peerDependencies": {
94
+ "@aws-sdk/client-api-gateway": "^3.0.0",
95
+ "@aws-sdk/client-cognito-identity-provider": "^3.0.0",
94
96
  "@aws-sdk/client-dynamodb": "^3.0.0",
97
+ "@aws-sdk/client-ecs": "^3.0.0",
98
+ "@aws-sdk/client-lambda": "^3.1032.0",
95
99
  "@aws-sdk/client-s3": "^3.0.0",
96
100
  "@aws-sdk/client-sqs": "^3.0.0",
97
- "@aws-sdk/client-cognito-identity-provider": "^3.0.0",
98
- "@aws-sdk/client-api-gateway": "^3.0.0",
99
- "@aws-sdk/client-ecs": "^3.0.0",
100
101
  "@aws-sdk/lib-dynamodb": "^3.0.0"
101
102
  },
102
103
  "peerDependenciesMeta": {
@@ -119,6 +120,6 @@
119
120
  "optional": true
120
121
  }
121
122
  },
122
- "buildDate": "2026-04-05T12:32:46.437Z",
123
+ "buildDate": "2026-04-20T18:31:32.841Z",
123
124
  "published": true
124
125
  }
package/src/server.js CHANGED
@@ -81,9 +81,9 @@ class Server {
81
81
  { name: "sqs", class: SQSService, depends: ["lambda"] },
82
82
  { name: "sns", class: SNSService, depends: [] },
83
83
  { name: "eventbridge", class: EventBridgeService, depends: [] },
84
- { name: "cognito", class: CognitoService, depends: [] },
84
+ { name: "cognito", class: CognitoService, depends: ["lambda"] },
85
85
  { name: "ecs", class: ECSService, depends: [] },
86
- { name: "apigateway", class: APIGatewayService, depends: ["lambda"] },
86
+ { name: "apigateway", class: APIGatewayService, depends: ["lambda", "cognito"] },
87
87
  { name: "kms", class: KMSService, depends: [] },
88
88
  { name: "cloudwatch", class: CloudWatchService, depends: [] },
89
89
  { name: "cloudtrail", class: CloudTrailService, depends: [] },
@@ -15,6 +15,7 @@ class APIGatewayService {
15
15
  this.simulator = null;
16
16
  this.isRunning = false;
17
17
  this.lambdaService = dependencies.lambda || null;
18
+ this.cognitoService = dependencies.cognito || null;
18
19
  }
19
20
 
20
21
  async initialize() {
@@ -27,6 +28,7 @@ class APIGatewayService {
27
28
  this.server = new APIGatewayServer(this.port, this.config);
28
29
  this.server.simulator = this.simulator;
29
30
  this.server.lambdaService = this.lambdaService;
31
+ this.server.cognitoService = this.cognitoService;
30
32
 
31
33
  await this.server.initialize();
32
34
 
@@ -64,8 +64,11 @@ class APIGatewayServer {
64
64
  // Register routes from aws-local-simulator.json config directly
65
65
  const apis = this.config.apigateway?.apis || [];
66
66
  for (const api of apis) {
67
+ // Build Cognito authorizer middleware for this API if configured
68
+ const cognitoAuthorizer = this._buildCognitoAuthorizer(api.authorizer);
69
+
67
70
  for (const endpoint of (api.endpoints || [])) {
68
- const { path, method, lambdaName, integrationType } = endpoint;
71
+ const { path, method, lambdaName, integrationType, authorizerRequired } = endpoint;
69
72
  if (!path || !method) continue;
70
73
 
71
74
  const expressPath = path.replace(/\{([^}]+)\}/g, ':$1');
@@ -73,7 +76,7 @@ class APIGatewayServer {
73
76
 
74
77
  logger.debug(`📡 Registrando rota: ${method} ${path} -> ${lambdaName}`);
75
78
 
76
- this.app[httpMethod](expressPath, async (req, res) => {
79
+ const handler = async (req, res) => {
77
80
  try {
78
81
  const lambdaService = this.lambdaService;
79
82
  if (!lambdaService) {
@@ -92,7 +95,8 @@ class APIGatewayServer {
92
95
  path: req.path,
93
96
  stage: 'local',
94
97
  requestId: Math.random().toString(36).substring(7),
95
- identity: { sourceIp: req.ip }
98
+ identity: { sourceIp: req.ip },
99
+ authorizer: req.cognitoAuthorizer || null
96
100
  }
97
101
  };
98
102
 
@@ -107,11 +111,70 @@ class APIGatewayServer {
107
111
  logger.error(`Lambda invoke error (${lambdaName}):`, err);
108
112
  res.status(500).json({ error: err.message });
109
113
  }
110
- });
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
+ }
111
130
  }
112
131
  }
113
132
  }
114
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
+
115
178
  setupRoutes() {
116
179
  // Health check
117
180
  this.app.get('/health', (req, res) => {
@@ -34,6 +34,15 @@ class CognitoService {
34
34
  injectDependencies(server) {
35
35
  const ct = server.getService('cloudtrail');
36
36
  if (ct?.simulator) this.simulator.audit.setTrail(ct.simulator);
37
+
38
+ const lambda = server.getService('lambda');
39
+ if (lambda?.simulator) {
40
+ this.simulator.setLambdaSimulator(lambda.simulator);
41
+ // Warn about unregistered triggers now that Lambda is fully initialized
42
+ for (const pool of this.simulator.userPools.values()) {
43
+ this.simulator._warnUnregisteredTriggers(pool);
44
+ }
45
+ }
37
46
  }
38
47
 
39
48
  async start() {
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  const express = require('express');
6
+ const cors = require('cors');
6
7
  const logger = require('../../utils/logger');
7
8
 
8
9
  class CognitoServer {
@@ -16,6 +17,7 @@ class CognitoServer {
16
17
  }
17
18
 
18
19
  setupMiddlewares() {
20
+ this.app.use(cors());
19
21
  this.app.use(express.raw({ type: '*/*', limit: '10mb' }));
20
22
  this.app.use((req, res, next) => {
21
23
  if (req.body && Buffer.isBuffer(req.body)) {
@@ -57,25 +59,35 @@ class CognitoServer {
57
59
  });
58
60
  });
59
61
 
60
- // User Pool operations
61
- this.app.post('/', async (req, res) => {
62
+ // User Pool operations — aceita POST / e POST /:userPoolId (compatibilidade com SDK)
63
+ const cognitoHandler = async (req, res) => {
62
64
  const target = req.headers['x-amz-target'];
63
- logger.info(`Cognito incoming: target=${target} body=${JSON.stringify(req.body)}`);
65
+ logger.info(`Cognito incoming: method=${req.method} path=${req.path} target=${target} body=${JSON.stringify(req.body)}`);
66
+
64
67
  if (!target) {
65
- return res.status(400).json({ error: 'Missing X-Amz-Target header' });
68
+ return res.status(400).json({
69
+ __type: 'InvalidParameterException',
70
+ message: 'Missing X-Amz-Target header'
71
+ });
66
72
  }
67
73
 
68
74
  try {
69
75
  const result = await this.handleRequest(target, req.body || {});
70
76
  res.json(result);
71
77
  } catch (error) {
72
- logger.error('Cognito Error:', error);
78
+ logger.error('Cognito Error:', error.message);
73
79
  res.status(400).json({
74
80
  __type: error.code || 'InternalServerError',
75
81
  message: error.message
76
82
  });
77
83
  }
78
- });
84
+ };
85
+
86
+ // OPTIONS preflight para CORS
87
+ this.app.options('*', (req, res) => res.sendStatus(204));
88
+
89
+ this.app.post('/', cognitoHandler);
90
+ this.app.post('/:userPoolId', cognitoHandler);
79
91
 
80
92
  // Admin endpoints
81
93
  this.setupAdminRoutes();
@@ -182,6 +194,16 @@ class CognitoServer {
182
194
  });
183
195
  });
184
196
 
197
+ // Confirma um usuário (útil para dev local)
198
+ this.app.post('/__admin/userpools/:poolId/users/:username/confirm', (req, res) => {
199
+ const user = this.simulator.findUserByUsername(req.params.username, null, req.params.poolId);
200
+ if (!user) return res.status(404).json({ error: 'User not found' });
201
+ user.UserStatus = 'CONFIRMED';
202
+ user.LastModifiedDate = new Date().toISOString();
203
+ this.simulator.persistUsers();
204
+ res.json({ message: `User ${req.params.username} confirmed` });
205
+ });
206
+
185
207
  this.app.get('/__admin/userpools/:poolId/users', (req, res) => {
186
208
  const pool = this.simulator.userPools.get(req.params.poolId);
187
209
  if (!pool) {