@gugananuvem/aws-local-simulator 1.0.12 → 1.0.15

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 +594 -257
  2. package/bin/aws-local-simulator.js +63 -63
  3. package/package.json +21 -10
  4. package/src/config/config-loader.js +114 -114
  5. package/src/config/default-config.js +68 -67
  6. package/src/config/env-loader.js +68 -68
  7. package/src/index.js +146 -130
  8. package/src/index.mjs +123 -123
  9. package/src/server.js +227 -223
  10. package/src/services/apigateway/index.js +73 -68
  11. package/src/services/apigateway/server.js +507 -487
  12. package/src/services/apigateway/simulator.js +1261 -1251
  13. package/src/services/athena/index.js +75 -0
  14. package/src/services/athena/server.js +101 -0
  15. package/src/services/athena/simulador.js +998 -0
  16. package/src/services/athena/simulator.js +346 -0
  17. package/src/services/cloudformation/index.js +106 -0
  18. package/src/services/cloudformation/server.js +417 -0
  19. package/src/services/cloudformation/simulador.js +1045 -0
  20. package/src/services/cloudtrail/index.js +84 -0
  21. package/src/services/cloudtrail/server.js +235 -0
  22. package/src/services/cloudtrail/simulador.js +719 -0
  23. package/src/services/cloudwatch/index.js +84 -0
  24. package/src/services/cloudwatch/server.js +366 -0
  25. package/src/services/cloudwatch/simulador.js +1173 -0
  26. package/src/services/cognito/index.js +79 -65
  27. package/src/services/cognito/server.js +301 -279
  28. package/src/services/cognito/simulator.js +1655 -1115
  29. package/src/services/config/index.js +96 -0
  30. package/src/services/config/server.js +215 -0
  31. package/src/services/config/simulador.js +1260 -0
  32. package/src/services/dynamodb/index.js +74 -70
  33. package/src/services/dynamodb/server.js +125 -121
  34. package/src/services/dynamodb/simulator.js +630 -620
  35. package/src/services/ecs/index.js +65 -65
  36. package/src/services/ecs/server.js +235 -233
  37. package/src/services/ecs/simulator.js +844 -844
  38. package/src/services/eventbridge/index.js +89 -85
  39. package/src/services/eventbridge/server.js +209 -0
  40. package/src/services/eventbridge/simulator.js +684 -0
  41. package/src/services/index.js +45 -19
  42. package/src/services/kms/index.js +75 -0
  43. package/src/services/kms/server.js +67 -0
  44. package/src/services/kms/simulator.js +324 -0
  45. package/src/services/lambda/handler-loader.js +183 -183
  46. package/src/services/lambda/index.js +78 -73
  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 -172
  50. package/src/services/parameter-store/index.js +80 -0
  51. package/src/services/parameter-store/server.js +50 -0
  52. package/src/services/parameter-store/simulator.js +201 -0
  53. package/src/services/s3/index.js +73 -69
  54. package/src/services/s3/server.js +329 -238
  55. package/src/services/s3/simulator.js +565 -740
  56. package/src/services/secret-manager/index.js +80 -0
  57. package/src/services/secret-manager/server.js +50 -0
  58. package/src/services/secret-manager/simulator.js +171 -0
  59. package/src/services/sns/index.js +89 -76
  60. package/src/services/sns/server.js +580 -0
  61. package/src/services/sns/simulator.js +1482 -0
  62. package/src/services/sqs/index.js +93 -95
  63. package/src/services/sqs/server.js +349 -345
  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 -142
  67. package/src/services/sts/simulator.js +69 -69
  68. package/src/services/xray/index.js +83 -0
  69. package/src/services/xray/server.js +308 -0
  70. package/src/services/xray/simulador.js +994 -0
  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 -0
  76. package/src/utils/local-store.js +83 -67
  77. package/src/utils/logger.js +59 -59
@@ -1,1115 +1,1655 @@
1
- /**
2
- * Cognito Simulator Core
3
- * Simula User Pools, Identity Pools, Autenticação, Tokens JWT
4
- */
5
-
6
- const crypto = require('crypto');
7
- const jwt = require('jsonwebtoken');
8
- const { v4: uuidv4 } = require('uuid');
9
- const logger = require('../../utils/logger');
10
- const LocalStore = require('../../utils/local-store');
11
- const path = require('path');
12
-
13
- class CognitoSimulator {
14
- constructor(config) {
15
- this.config = config;
16
- this.dataDir = path.join(process.env.AWS_LOCAL_SIMULATOR_DATA_DIR, 'cognito');
17
- this.store = new LocalStore(this.dataDir);
18
- this.userPools = new Map();
19
- this.identityPools = new Map();
20
- this.users = new Map();
21
- this.sessions = new Map();
22
- this.refreshTokens = new Map();
23
- this.accessTokens = new Map();
24
- this.jwtSecret = crypto.randomBytes(64).toString('hex');
25
- }
26
-
27
- async initialize() {
28
- logger.debug('Inicializando Cognito Simulator...');
29
- this.loadUserPools();
30
- this.loadIdentityPools();
31
- this.loadUsers();
32
- this.loadSessions();
33
-
34
- logger.debug(`✅ Cognito Simulator inicializado com ${this.userPools.size} user pools, ${this.identityPools.size} identity pools, ${this.users.size} usuários`);
35
- }
36
-
37
- // ============ User Pool Operations ============
38
-
39
- createUserPool(params) {
40
- const { PoolName, Policies, LambdaConfig, AutoVerifiedAttributes, AliasAttributes, UsernameAttributes, MfaConfiguration } = params;
41
-
42
- const poolId = `local_${PoolName}_${Date.now()}`;
43
- const userPool = {
44
- Id: poolId,
45
- Name: PoolName,
46
- Arn: `arn:aws:cognito:local:000000000000:userpool/${poolId}`,
47
- Status: 'ACTIVE',
48
- CreationDate: new Date().toISOString(),
49
- LastModifiedDate: new Date().toISOString(),
50
- Policies: Policies || {
51
- PasswordPolicy: {
52
- MinimumLength: 8,
53
- RequireUppercase: true,
54
- RequireLowercase: true,
55
- RequireNumbers: true,
56
- RequireSymbols: false
57
- }
58
- },
59
- LambdaConfig: LambdaConfig || {},
60
- AutoVerifiedAttributes: AutoVerifiedAttributes || ['email'],
61
- AliasAttributes: AliasAttributes || [],
62
- UsernameAttributes: UsernameAttributes || ['email'],
63
- MfaConfiguration: MfaConfiguration || 'OFF',
64
- EstimatedNumberOfUsers: 0,
65
- Users: [],
66
- Clients: new Map(),
67
- Groups: new Map(),
68
- IdentityProviders: new Map(),
69
- ResourceServers: new Map()
70
- };
71
-
72
- this.userPools.set(poolId, userPool);
73
- this.persistUserPools();
74
-
75
- logger.debug(`✅ User Pool criado: ${PoolName} (${poolId})`);
76
-
77
- return {
78
- UserPool: {
79
- Id: userPool.Id,
80
- Name: userPool.Name,
81
- Arn: userPool.Arn,
82
- Status: userPool.Status,
83
- CreationDate: userPool.CreationDate,
84
- LastModifiedDate: userPool.LastModifiedDate,
85
- MfaConfiguration: userPool.MfaConfiguration,
86
- EstimatedNumberOfUsers: 0
87
- }
88
- };
89
- }
90
-
91
- listUserPools(params = {}) {
92
- const { MaxResults = 60, NextToken } = params;
93
- let userPools = Array.from(this.userPools.values());
94
-
95
- if (NextToken) {
96
- const startIndex = parseInt(NextToken);
97
- userPools = userPools.slice(startIndex);
98
- }
99
-
100
- const results = userPools.slice(0, MaxResults);
101
- const nextToken = results.length === MaxResults ? String(MaxResults) : null;
102
-
103
- return {
104
- UserPools: results.map(pool => ({
105
- Id: pool.Id,
106
- Name: pool.Name,
107
- Arn: pool.Arn,
108
- Status: pool.Status,
109
- CreationDate: pool.CreationDate,
110
- LastModifiedDate: pool.LastModifiedDate
111
- })),
112
- NextToken: nextToken
113
- };
114
- }
115
-
116
- describeUserPool(params) {
117
- const { UserPoolId } = params;
118
- const userPool = this.userPools.get(UserPoolId);
119
-
120
- if (!userPool) {
121
- throw new Error(`User pool ${UserPoolId} not found`);
122
- }
123
-
124
- return {
125
- UserPool: {
126
- Id: userPool.Id,
127
- Name: userPool.Name,
128
- Arn: userPool.Arn,
129
- Status: userPool.Status,
130
- CreationDate: userPool.CreationDate,
131
- LastModifiedDate: userPool.LastModifiedDate,
132
- Policies: userPool.Policies,
133
- LambdaConfig: userPool.LambdaConfig,
134
- AutoVerifiedAttributes: userPool.AutoVerifiedAttributes,
135
- AliasAttributes: userPool.AliasAttributes,
136
- UsernameAttributes: userPool.UsernameAttributes,
137
- MfaConfiguration: userPool.MfaConfiguration,
138
- EstimatedNumberOfUsers: userPool.Users.length
139
- }
140
- };
141
- }
142
-
143
- listUsers(params = {}) {
144
- const { UserPoolId, Filter, Limit = 60, PaginationToken } = params;
145
- const userPool = this.userPools.get(UserPoolId);
146
-
147
- if (!userPool) {
148
- throw new Error(`User pool ${UserPoolId} not found`);
149
- }
150
-
151
- let users = Array.from(this.users.values()).filter(u => u.UserPoolId === UserPoolId);
152
-
153
- if (PaginationToken) {
154
- const startIndex = parseInt(PaginationToken);
155
- users = users.slice(startIndex);
156
- }
157
-
158
- const results = users.slice(0, Limit);
159
- const nextToken = results.length === Limit && users.length > Limit ? String(Limit) : null;
160
-
161
- return {
162
- Users: results.map(u => ({
163
- Username: u.Username,
164
- UserStatus: u.UserStatus,
165
- Enabled: u.Enabled,
166
- UserCreateDate: u.CreatedDate,
167
- UserLastModifiedDate: u.LastModifiedDate,
168
- Attributes: this.formatUserAttributes(u.Attributes)
169
- })),
170
- PaginationToken: nextToken
171
- };
172
- }
173
-
174
- listUserPoolClients(params = {}) {
175
- const { UserPoolId, MaxResults = 60, NextToken } = params;
176
- const userPool = this.userPools.get(UserPoolId);
177
- if (!userPool) throw new Error(`User pool ${UserPoolId} not found`);
178
-
179
- let clients = Array.from(userPool.Clients.values());
180
- if (NextToken) clients = clients.slice(parseInt(NextToken));
181
- const results = clients.slice(0, MaxResults);
182
-
183
- return {
184
- UserPoolClients: results.map(c => ({
185
- ClientId: c.ClientId,
186
- ClientName: c.ClientName,
187
- UserPoolId: c.UserPoolId
188
- })),
189
- NextToken: results.length === MaxResults && clients.length > MaxResults ? String(MaxResults) : null
190
- };
191
- }
192
-
193
- describeUserPoolClient(params = {}) {
194
- const { UserPoolId, ClientId } = params;
195
- const userPool = this.userPools.get(UserPoolId);
196
- if (!userPool) throw new Error(`User pool ${UserPoolId} not found`);
197
- const client = userPool.Clients.get(ClientId);
198
- if (!client) throw new Error(`Client ${ClientId} not found`);
199
- return { UserPoolClient: client };
200
- }
201
-
202
- deleteUserPoolClient(params = {}) {
203
- const { UserPoolId, ClientId } = params;
204
- const userPool = this.userPools.get(UserPoolId);
205
- if (!userPool) throw new Error(`User pool ${UserPoolId} not found`);
206
- userPool.Clients.delete(ClientId);
207
- this.persistUserPools();
208
- return {};
209
- }
210
-
211
- forgotPassword(params = {}) {
212
- const { ClientId, Username } = params;
213
- const userPool = this.findUserPoolByClientId(ClientId);
214
- if (!userPool) throw new Error(`Client ${ClientId} not found`);
215
- return { CodeDeliveryDetails: { Destination: 'test@example.com', DeliveryMedium: 'EMAIL', AttributeName: 'email' } };
216
- }
217
-
218
- confirmForgotPassword(params = {}) {
219
- const { ClientId, Username, ConfirmationCode, Password } = params;
220
- const user = this.findUserByUsername(Username, ClientId);
221
- if (!user) throw new Error(`User not found: ${Username}`);
222
- user.Password = this.hashPassword(Password);
223
- user.UserStatus = 'CONFIRMED';
224
- this.persistUsers();
225
- return {};
226
- }
227
-
228
- changePassword(params = {}) {
229
- const { AccessToken, PreviousPassword, ProposedPassword } = params;
230
- const session = this.accessTokens.get(AccessToken);
231
- if (!session) throw new Error('Invalid access token');
232
- const user = this.users.get(session.UserId);
233
- if (!user) throw new Error('User not found');
234
- if (!this.verifyPassword(PreviousPassword, user.Password)) throw new Error('Incorrect previous password');
235
- user.Password = this.hashPassword(ProposedPassword);
236
- this.persistUsers();
237
- return {};
238
- }
239
-
240
- respondToAuthChallenge(params = {}) {
241
- return { AuthenticationResult: null, ChallengeName: null };
242
- }
243
-
244
- revokeToken(params = {}) {
245
- const { Token, ClientId } = params;
246
- // Remove refresh token session if it exists
247
- const session = this.refreshTokens.get(Token);
248
- if (session) {
249
- this.sessions.delete(session.Id);
250
- this.accessTokens.delete(session.AccessToken);
251
- this.refreshTokens.delete(Token);
252
- this.persistSessions();
253
- }
254
- return {};
255
- }
256
-
257
- globalSignOut(params = {}) {
258
- const { AccessToken } = params;
259
- const session = this.accessTokens.get(AccessToken);
260
- if (session) {
261
- this.sessions.delete(session.Id);
262
- this.accessTokens.delete(AccessToken);
263
- this.refreshTokens.delete(session.RefreshToken);
264
- this.persistSessions();
265
- }
266
- return {};
267
- }
268
-
269
- getUser(params = {}) {
270
- const { AccessToken } = params;
271
- const session = this.accessTokens.get(AccessToken);
272
- if (!session) throw new Error('Invalid access token');
273
- // Check session is still active
274
- if (!this.sessions.has(session.Id)) throw new Error('Token has been revoked');
275
- const user = this.users.get(session.UserId);
276
- if (!user) throw new Error('User not found');
277
- return {
278
- Username: user.Username,
279
- UserAttributes: this.formatUserAttributes(user.Attributes),
280
- UserStatus: user.UserStatus
281
- };
282
- }
283
-
284
- updateUserAttributes(params = {}) {
285
- const { AccessToken, UserAttributes } = params;
286
- const session = this.accessTokens.get(AccessToken);
287
- if (!session) throw new Error('Invalid access token');
288
- const user = this.users.get(session.UserId);
289
- if (!user) throw new Error('User not found');
290
- const updates = this.normalizeUserAttributes(UserAttributes || []);
291
- Object.assign(user.Attributes, updates);
292
- this.persistUsers();
293
- return { CodeDeliveryDetailsList: [] };
294
- }
295
-
296
- deleteUser(params = {}) {
297
- const { AccessToken } = params;
298
- const session = this.accessTokens.get(AccessToken);
299
- if (!session) throw new Error('Invalid access token');
300
- this.users.delete(session.UserId);
301
- this.persistUsers();
302
- return {};
303
- }
304
-
305
- adminDisableUser(params = {}) {
306
- const { UserPoolId, Username } = params;
307
- const user = this.findUserByUsername(Username, null, UserPoolId);
308
- if (!user) throw new Error(`User not found: ${Username}`);
309
- user.Enabled = false;
310
- this.persistUsers();
311
- return {};
312
- }
313
-
314
- adminEnableUser(params = {}) {
315
- const { UserPoolId, Username } = params;
316
- const user = this.findUserByUsername(Username, null, UserPoolId);
317
- if (!user) throw new Error(`User not found: ${Username}`);
318
- user.Enabled = true;
319
- this.persistUsers();
320
- return {};
321
- }
322
-
323
- adminResetUserPassword(params = {}) {
324
- const { UserPoolId, Username } = params;
325
- const user = this.findUserByUsername(Username, null, UserPoolId);
326
- if (!user) throw new Error(`User not found: ${Username}`);
327
- user.UserStatus = 'RESET_REQUIRED';
328
- this.persistUsers();
329
- return {};
330
- }
331
-
332
- adminUserGlobalSignOut(params = {}) {
333
- const { UserPoolId, Username } = params;
334
- const user = this.findUserByUsername(Username, null, UserPoolId);
335
- if (!user) throw new Error(`User not found: ${Username}`);
336
- // Invalidate all sessions for this user
337
- for (const [id, session] of this.sessions.entries()) {
338
- if (session.UserId === user.UserId) {
339
- this.accessTokens.delete(session.AccessToken);
340
- this.refreshTokens.delete(session.RefreshToken);
341
- this.sessions.delete(id);
342
- }
343
- }
344
- this.persistSessions();
345
- return {};
346
- }
347
-
348
- adminListGroupsForUser(params = {}) {
349
- return { Groups: [], NextToken: null };
350
- }
351
-
352
- deleteUserPool(params) {
353
- const { UserPoolId } = params;
354
-
355
- if (!this.userPools.has(UserPoolId)) {
356
- throw new Error(`User pool ${UserPoolId} not found`);
357
- }
358
-
359
- this.userPools.delete(UserPoolId);
360
- this.persistUserPools();
361
-
362
- return {};
363
- }
364
-
365
- // ============ User Pool Client Operations ============
366
-
367
- createUserPoolClient(params) {
368
- const { UserPoolId, ClientName, GenerateSecret, RefreshTokenValidity, AccessTokenValidity, IdTokenValidity, AllowedOAuthFlows, AllowedOAuthScopes, CallbackURLs, LogoutURLs } = params;
369
-
370
- const userPool = this.userPools.get(UserPoolId);
371
- if (!userPool) {
372
- throw new Error(`User pool ${UserPoolId} not found`);
373
- }
374
-
375
- const clientId = crypto.randomBytes(20).toString('hex');
376
- const clientSecret = GenerateSecret ? crypto.randomBytes(32).toString('hex') : null;
377
-
378
- const client = {
379
- ClientId: clientId,
380
- ClientName: ClientName,
381
- ClientSecret: clientSecret,
382
- UserPoolId: UserPoolId,
383
- RefreshTokenValidity: RefreshTokenValidity || 30,
384
- AccessTokenValidity: AccessTokenValidity || 1,
385
- IdTokenValidity: IdTokenValidity || 1,
386
- AllowedOAuthFlows: AllowedOAuthFlows || ['code'],
387
- AllowedOAuthScopes: AllowedOAuthScopes || ['openid', 'email', 'profile'],
388
- CallbackURLs: CallbackURLs || [],
389
- LogoutURLs: LogoutURLs || [],
390
- CreatedDate: new Date().toISOString(),
391
- LastModifiedDate: new Date().toISOString()
392
- };
393
-
394
- userPool.Clients.set(clientId, client);
395
- this.persistUserPools();
396
-
397
- logger.debug(`✅ User Pool Client criado: ${ClientName} (${clientId})`);
398
-
399
- return {
400
- UserPoolClient: {
401
- ClientId: client.ClientId,
402
- ClientName: client.ClientName,
403
- ClientSecret: client.ClientSecret,
404
- UserPoolId: client.UserPoolId,
405
- RefreshTokenValidity: client.RefreshTokenValidity,
406
- AccessTokenValidity: client.AccessTokenValidity,
407
- IdTokenValidity: client.IdTokenValidity,
408
- AllowedOAuthFlows: client.AllowedOAuthFlows,
409
- AllowedOAuthScopes: client.AllowedOAuthScopes,
410
- CallbackURLs: client.CallbackURLs,
411
- LogoutURLs: client.LogoutURLs,
412
- CreationDate: client.CreatedDate
413
- }
414
- };
415
- }
416
-
417
- // ============ User Operations ============
418
-
419
- signUp(params = {}) {
420
- const { ClientId, Username, Password, UserAttributes, ValidationData } = params;
421
-
422
- // Encontra o user pool pelo client id
423
- const userPool = this.findUserPoolByClientId(ClientId);
424
- if (!userPool) {
425
- throw new Error(`Client ${ClientId} not found`);
426
- }
427
-
428
- // Verifica se usuário já existe
429
- const existingUser = Array.from(this.users.values()).find(
430
- u => u.Username === Username && u.UserPoolId === userPool.Id
431
- );
432
-
433
- if (existingUser) {
434
- throw new Error(`User already exists: ${Username}`);
435
- }
436
-
437
- const userId = uuidv4();
438
- const user = {
439
- Username: Username,
440
- UserPoolId: userPool.Id,
441
- UserId: userId,
442
- Attributes: this.normalizeUserAttributes(UserAttributes || []),
443
- Enabled: true,
444
- UserStatus: 'CONFIRMED', // Por padrão, confirma imediatamente (para teste)
445
- CreatedDate: new Date().toISOString(),
446
- LastModifiedDate: new Date().toISOString(),
447
- Password: this.hashPassword(Password),
448
- MfaOptions: [],
449
- PreferredMfaSetting: null,
450
- UserMFASettingList: []
451
- };
452
-
453
- this.users.set(userId, user);
454
- userPool.Users.push(userId);
455
- userPool.EstimatedNumberOfUsers++;
456
- this.persistUsers();
457
- this.persistUserPools();
458
-
459
- logger.debug(`✅ Usuário criado: ${Username} (${userId})`);
460
-
461
- return {
462
- UserConfirmed: true,
463
- UserSub: userId,
464
- CodeDeliveryDetails: null
465
- };
466
- }
467
-
468
- confirmSignUp(params) {
469
- const { ClientId, Username, ConfirmationCode } = params;
470
-
471
- const user = this.findUserByUsername(Username, ClientId);
472
- if (!user) {
473
- throw new Error(`User not found: ${Username}`);
474
- }
475
-
476
- user.UserStatus = 'CONFIRMED';
477
- user.LastModifiedDate = new Date().toISOString();
478
- this.persistUsers();
479
-
480
- return {};
481
- }
482
-
483
- initiateAuth(params) {
484
- const { AuthFlow, ClientId, AuthParameters } = params;
485
- const userPool = this.findUserPoolByClientId(ClientId);
486
-
487
- if (!userPool) {
488
- throw new Error(`Client ${ClientId} not found`);
489
- }
490
-
491
- const username = AuthParameters.USERNAME;
492
- const password = AuthParameters.PASSWORD;
493
-
494
- const user = this.findUserByUsername(username, ClientId);
495
- if (!user) {
496
- throw new Error(`User not found: ${username}`);
497
- }
498
-
499
- if (user.UserStatus !== 'CONFIRMED') {
500
- throw new Error(`User not confirmed: ${username}`);
501
- }
502
-
503
- if (!this.verifyPassword(password, user.Password)) {
504
- throw new Error('Incorrect username or password');
505
- }
506
-
507
- // Gera tokens JWT
508
- const accessToken = this.generateAccessToken(user, userPool, ClientId);
509
- const idToken = this.generateIdToken(user, userPool, ClientId);
510
- const refreshToken = this.generateRefreshToken(user, userPool, ClientId);
511
-
512
- const sessionId = uuidv4();
513
- const session = {
514
- Id: sessionId,
515
- UserId: user.UserId,
516
- UserPoolId: userPool.Id,
517
- ClientId: ClientId,
518
- AccessToken: accessToken,
519
- IdToken: idToken,
520
- RefreshToken: refreshToken,
521
- CreatedAt: new Date().toISOString(),
522
- ExpiresAt: new Date(Date.now() + 3600000).toISOString() // 1 hora
523
- };
524
-
525
- this.sessions.set(sessionId, session);
526
- this.accessTokens.set(accessToken, session);
527
- this.refreshTokens.set(refreshToken, session);
528
- this.persistSessions();
529
-
530
- logger.debug(`🔐 Usuário autenticado: ${username}`);
531
-
532
- return {
533
- AuthenticationResult: {
534
- AccessToken: accessToken,
535
- IdToken: idToken,
536
- RefreshToken: refreshToken,
537
- TokenType: 'Bearer',
538
- ExpiresIn: 3600
539
- },
540
- ChallengeName: null,
541
- Session: null
542
- };
543
- }
544
-
545
- getToken(params) {
546
- const { AuthFlow, ClientId, AuthParameters } = params;
547
-
548
- if (AuthFlow === 'REFRESH_TOKEN_AUTH') {
549
- const refreshToken = AuthParameters.REFRESH_TOKEN;
550
- const session = this.refreshTokens.get(refreshToken);
551
-
552
- if (!session) {
553
- throw new Error('Invalid refresh token');
554
- }
555
-
556
- const user = this.users.get(session.UserId);
557
- const userPool = this.userPools.get(session.UserPoolId);
558
-
559
- if (!user || !userPool) {
560
- throw new Error('Invalid session');
561
- }
562
-
563
- // Gera novos tokens
564
- const newAccessToken = this.generateAccessToken(user, userPool, session.ClientId);
565
- const newIdToken = this.generateIdToken(user, userPool, session.ClientId);
566
-
567
- session.AccessToken = newAccessToken;
568
- session.IdToken = newIdToken;
569
- session.ExpiresAt = new Date(Date.now() + 3600000).toISOString();
570
-
571
- this.accessTokens.set(newAccessToken, session);
572
- this.persistSessions();
573
-
574
- return {
575
- AuthenticationResult: {
576
- AccessToken: newAccessToken,
577
- IdToken: newIdToken,
578
- TokenType: 'Bearer',
579
- ExpiresIn: 3600
580
- }
581
- };
582
- }
583
-
584
- throw new Error(`Unsupported AuthFlow: ${AuthFlow}`);
585
- }
586
-
587
- // ============ Token Management ============
588
-
589
- generateAccessToken(user, userPool, clientId) {
590
- const payload = {
591
- sub: user.UserId,
592
- token_use: 'access',
593
- client_id: clientId,
594
- username: user.Username,
595
- scope: 'aws.cognito.signin.user.admin',
596
- iss: `https://cognito-idp.local/${userPool.Id}`,
597
- exp: Math.floor(Date.now() / 1000) + 3600,
598
- iat: Math.floor(Date.now() / 1000)
599
- };
600
-
601
- return jwt.sign(payload, this.jwtSecret, { algorithm: 'HS256' });
602
- }
603
-
604
- generateIdToken(user, userPool, clientId) {
605
- const payload = {
606
- sub: user.UserId,
607
- token_use: 'id',
608
- client_id: clientId,
609
- email: user.Attributes.email,
610
- email_verified: user.Attributes.email_verified || true,
611
- username: user.Username,
612
- iss: `https://cognito-idp.local/${userPool.Id}`,
613
- exp: Math.floor(Date.now() / 1000) + 3600,
614
- iat: Math.floor(Date.now() / 1000)
615
- };
616
-
617
- // Adiciona outros atributos do usuário
618
- for (const [key, value] of Object.entries(user.Attributes)) {
619
- if (key !== 'email' && key !== 'email_verified') {
620
- payload[key] = value;
621
- }
622
- }
623
-
624
- return jwt.sign(payload, this.jwtSecret, { algorithm: 'HS256' });
625
- }
626
-
627
- generateRefreshToken(user, userPool, clientId) {
628
- const payload = {
629
- sub: user.UserId,
630
- token_use: 'refresh',
631
- client_id: clientId,
632
- username: user.Username,
633
- iss: `https://cognito-idp.local/${userPool.Id}`,
634
- exp: Math.floor(Date.now() / 1000) + 2592000, // 30 dias
635
- iat: Math.floor(Date.now() / 1000)
636
- };
637
-
638
- return jwt.sign(payload, this.jwtSecret, { algorithm: 'HS256' });
639
- }
640
-
641
- verifyAccessToken(token) {
642
- try {
643
- const decoded = jwt.verify(token, this.jwtSecret);
644
- const session = this.accessTokens.get(token);
645
-
646
- if (!session || session.ExpiresAt < new Date().toISOString()) {
647
- return null;
648
- }
649
-
650
- return decoded;
651
- } catch (error) {
652
- return null;
653
- }
654
- }
655
-
656
- // ============ Admin Operations ============
657
-
658
- adminGetUser(params) {
659
- const { UserPoolId, Username } = params;
660
- const userPool = this.userPools.get(UserPoolId);
661
-
662
- if (!userPool) {
663
- throw new Error(`User pool ${UserPoolId} not found`);
664
- }
665
-
666
- const user = this.findUserByUsername(Username, null, UserPoolId);
667
- if (!user) {
668
- throw new Error(`User not found: ${Username}`);
669
- }
670
-
671
- return {
672
- Username: user.Username,
673
- UserAttributes: this.formatUserAttributes(user.Attributes),
674
- UserCreateDate: user.CreatedDate,
675
- UserLastModifiedDate: user.LastModifiedDate,
676
- Enabled: user.Enabled,
677
- UserStatus: user.UserStatus,
678
- MFAOptions: user.MfaOptions,
679
- PreferredMfaSetting: user.PreferredMfaSetting,
680
- UserMFASettingList: user.UserMFASettingList
681
- };
682
- }
683
-
684
- adminCreateUser(params) {
685
- const { UserPoolId, Username, UserAttributes, TemporaryPassword, DesiredDeliveryMediums } = params;
686
- const userPool = this.userPools.get(UserPoolId);
687
-
688
- if (!userPool) {
689
- throw new Error(`User pool ${UserPoolId} not found`);
690
- }
691
-
692
- const userId = uuidv4();
693
- const user = {
694
- Username: Username,
695
- UserPoolId: UserPoolId,
696
- UserId: userId,
697
- Attributes: this.normalizeUserAttributes(UserAttributes || []),
698
- Enabled: true,
699
- UserStatus: 'FORCE_CHANGE_PASSWORD',
700
- CreatedDate: new Date().toISOString(),
701
- LastModifiedDate: new Date().toISOString(),
702
- Password: this.hashPassword(TemporaryPassword || 'Temp123!'),
703
- MfaOptions: [],
704
- PreferredMfaSetting: null,
705
- UserMFASettingList: []
706
- };
707
-
708
- this.users.set(userId, user);
709
- userPool.Users.push(userId);
710
- userPool.EstimatedNumberOfUsers++;
711
- this.persistUsers();
712
- this.persistUserPools();
713
-
714
- logger.debug(`👤 Usuário administrador criado: ${Username}`);
715
-
716
- return {
717
- User: {
718
- Username: user.Username,
719
- UserAttributes: this.formatUserAttributes(user.Attributes),
720
- UserCreateDate: user.CreatedDate,
721
- UserLastModifiedDate: user.LastModifiedDate,
722
- Enabled: user.Enabled,
723
- UserStatus: user.UserStatus
724
- }
725
- };
726
- }
727
-
728
- adminSetUserPassword(params) {
729
- const { UserPoolId, Username, Password, Permanent } = params;
730
- const user = this.findUserByUsername(Username, null, UserPoolId);
731
-
732
- if (!user) {
733
- throw new Error(`User not found: ${Username}`);
734
- }
735
-
736
- user.Password = this.hashPassword(Password);
737
- if (Permanent) {
738
- user.UserStatus = 'CONFIRMED';
739
- }
740
- user.LastModifiedDate = new Date().toISOString();
741
- this.persistUsers();
742
-
743
- return {};
744
- }
745
-
746
- adminDeleteUser(params) {
747
- const { UserPoolId, Username } = params;
748
- const user = this.findUserByUsername(Username, null, UserPoolId);
749
-
750
- if (!user) {
751
- throw new Error(`User not found: ${Username}`);
752
- }
753
-
754
- const userPool = this.userPools.get(UserPoolId);
755
- if (userPool) {
756
- const index = userPool.Users.indexOf(user.UserId);
757
- if (index !== -1) {
758
- userPool.Users.splice(index, 1);
759
- userPool.EstimatedNumberOfUsers--;
760
- }
761
- }
762
-
763
- this.users.delete(user.UserId);
764
- this.persistUsers();
765
- this.persistUserPools();
766
-
767
- return {};
768
- }
769
-
770
- // ============ Helper Methods ============
771
-
772
- findUserPoolByClientId(clientId) {
773
- for (const userPool of this.userPools.values()) {
774
- if (userPool.Clients.has(clientId)) {
775
- return userPool;
776
- }
777
- }
778
- return null;
779
- }
780
-
781
- findUserByUsername(username, clientId = null, userPoolId = null) {
782
- let targetUserPoolId = userPoolId;
783
-
784
- if (clientId && !targetUserPoolId) {
785
- const userPool = this.findUserPoolByClientId(clientId);
786
- if (userPool) {
787
- targetUserPoolId = userPool.Id;
788
- }
789
- }
790
-
791
- for (const user of this.users.values()) {
792
- if (user.Username === username && user.UserPoolId === targetUserPoolId) {
793
- return user;
794
- }
795
- }
796
-
797
- return null;
798
- }
799
-
800
- normalizeUserAttributes(attributes) {
801
- const normalized = {};
802
- for (const attr of attributes) {
803
- normalized[attr.Name] = attr.Value;
804
- }
805
- return normalized;
806
- }
807
-
808
- formatUserAttributes(attributes) {
809
- return Object.entries(attributes).map(([Name, Value]) => ({ Name, Value }));
810
- }
811
-
812
- hashPassword(password) {
813
- // Simulação de hash (não usar em produção real)
814
- return crypto.createHash('sha256').update(password).digest('hex');
815
- }
816
-
817
- verifyPassword(password, hash) {
818
- return this.hashPassword(password) === hash;
819
- }
820
-
821
- // ============ Identity Pool Operations ============
822
-
823
- createIdentityPool(params) {
824
- const { IdentityPoolName, AllowUnauthenticatedIdentities, SupportedLoginProviders, CognitoIdentityProviders } = params;
825
-
826
- const identityPoolId = `local:${IdentityPoolName}_${Date.now()}`;
827
- const identityPool = {
828
- IdentityPoolId: identityPoolId,
829
- IdentityPoolName: IdentityPoolName,
830
- AllowUnauthenticatedIdentities: AllowUnauthenticatedIdentities || false,
831
- SupportedLoginProviders: SupportedLoginProviders || {},
832
- CognitoIdentityProviders: CognitoIdentityProviders || [],
833
- Identities: new Map()
834
- };
835
-
836
- this.identityPools.set(identityPoolId, identityPool);
837
- this.persistIdentityPools();
838
-
839
- logger.debug(`✅ Identity Pool criado: ${IdentityPoolName} (${identityPoolId})`);
840
-
841
- return {
842
- IdentityPoolId: identityPoolId,
843
- IdentityPoolName: identityPoolName,
844
- AllowUnauthenticatedIdentities: identityPool.AllowUnauthenticatedIdentities
845
- };
846
- }
847
-
848
- getId(params) {
849
- const { IdentityPoolId, Logins } = params;
850
- const identityPool = this.identityPools.get(IdentityPoolId);
851
-
852
- if (!identityPool) {
853
- throw new Error(`Identity pool ${IdentityPoolId} not found`);
854
- }
855
-
856
- let identityId = null;
857
-
858
- if (Logins) {
859
- // Procura identidade existente com os logins fornecidos
860
- for (const [id, identity] of identityPool.Identities) {
861
- if (identity.Logins && this.matchesLogins(identity.Logins, Logins)) {
862
- identityId = id;
863
- break;
864
- }
865
- }
866
- }
867
-
868
- if (!identityId) {
869
- identityId = uuidv4();
870
- identityPool.Identities.set(identityId, {
871
- IdentityId: identityId,
872
- Logins: Logins || {},
873
- CreationDate: new Date().toISOString(),
874
- LastModifiedDate: new Date().toISOString()
875
- });
876
- this.persistIdentityPools();
877
- }
878
-
879
- return {
880
- IdentityId: identityId
881
- };
882
- }
883
-
884
- getCredentialsForIdentity(params) {
885
- const { IdentityId, Logins } = params;
886
- const identityPool = this.findIdentityPoolByIdentityId(IdentityId);
887
-
888
- if (!identityPool) {
889
- throw new Error(`Identity ${IdentityId} not found`);
890
- }
891
-
892
- const identity = identityPool.Identities.get(IdentityId);
893
- if (!identity) {
894
- throw new Error(`Identity ${IdentityId} not found in pool`);
895
- }
896
-
897
- // Gera credenciais temporárias (simuladas)
898
- const credentials = {
899
- AccessKeyId: `AKIA${crypto.randomBytes(16).toString('hex').toUpperCase()}`,
900
- SecretKey: crypto.randomBytes(32).toString('hex'),
901
- SessionToken: crypto.randomBytes(64).toString('base64'),
902
- Expiration: new Date(Date.now() + 3600000).toISOString()
903
- };
904
-
905
- return {
906
- Credentials: credentials,
907
- IdentityId: IdentityId
908
- };
909
- }
910
-
911
- findIdentityPoolByIdentityId(identityId) {
912
- for (const pool of this.identityPools.values()) {
913
- if (pool.Identities.has(identityId)) {
914
- return pool;
915
- }
916
- }
917
- return null;
918
- }
919
-
920
- matchesLogins(existingLogins, newLogins) {
921
- const existingKeys = Object.keys(existingLogins);
922
- const newKeys = Object.keys(newLogins);
923
-
924
- if (existingKeys.length !== newKeys.length) return false;
925
-
926
- for (const key of existingKeys) {
927
- if (existingLogins[key] !== newLogins[key]) {
928
- return false;
929
- }
930
- }
931
-
932
- return true;
933
- }
934
-
935
- // ============ Persistence ============
936
-
937
- loadUserPools() {
938
- // Load persisted pools first
939
- const saved = this.store.read('__userpools__');
940
- if (saved) {
941
- for (const [id, data] of Object.entries(saved)) {
942
- data.Clients = new Map(Object.entries(data.Clients || {}));
943
- data.Groups = new Map(Object.entries(data.Groups || {}));
944
- data.IdentityProviders = new Map(Object.entries(data.IdentityProviders || {}));
945
- data.ResourceServers = new Map(Object.entries(data.ResourceServers || {}));
946
- this.userPools.set(id, data);
947
- }
948
- }
949
-
950
- // Create user pools from config if not already persisted
951
- if (this.config.cognito?.userPools) {
952
- let configChanged = false;
953
- const configPath = this.config._configPath;
954
-
955
- for (let i = 0; i < this.config.cognito.userPools.length; i++) {
956
- const poolConfig = this.config.cognito.userPools[i];
957
- const existing = Array.from(this.userPools.values()).find(p => p.Name === poolConfig.PoolName);
958
-
959
- if (!existing) {
960
- const result = this.createUserPool(poolConfig);
961
- const poolId = result.UserPool.Id;
962
- logger.debug(`✅ User Pool criado a partir da config: ${poolConfig.PoolName} (${poolId})`);
963
-
964
- // Auto-create a default client if not specified
965
- if (!poolConfig.ClientId) {
966
- const clientResult = this.createUserPoolClient({
967
- UserPoolId: poolId,
968
- ClientName: `${poolConfig.PoolName}-client`,
969
- GenerateSecret: false
970
- });
971
- const clientId = clientResult.UserPoolClient.ClientId;
972
- this.config.cognito.userPools[i].ClientId = clientId;
973
- this.config.cognito.userPools[i].UserPoolId = poolId;
974
- configChanged = true;
975
- logger.debug(`✅ Client criado automaticamente: ${clientId}`);
976
- }
977
- } else if (!poolConfig.ClientId) {
978
- // Pool exists but no clientId in config — write it back
979
- const firstClient = existing.Clients.size > 0 ? existing.Clients.values().next().value : null;
980
- if (firstClient) {
981
- this.config.cognito.userPools[i].ClientId = firstClient.ClientId;
982
- this.config.cognito.userPools[i].UserPoolId = existing.Id;
983
- configChanged = true;
984
- }
985
- }
986
- }
987
-
988
- // Write clientId back to aws-local-simulator.json
989
- if (configChanged && configPath) {
990
- try {
991
- const fs = require('fs');
992
- const fileContent = JSON.parse(fs.readFileSync(configPath, 'utf8'));
993
- fileContent.cognito = fileContent.cognito || {};
994
- fileContent.cognito.userPools = this.config.cognito.userPools.map(p => ({
995
- PoolName: p.PoolName,
996
- AutoVerifiedAttributes: p.AutoVerifiedAttributes,
997
- UserPoolId: p.UserPoolId,
998
- ClientId: p.ClientId
999
- }));
1000
- fs.writeFileSync(configPath, JSON.stringify(fileContent, null, 2));
1001
- logger.info(`✅ ClientId gravado em: ${configPath}`);
1002
- } catch (err) {
1003
- logger.warn(`⚠️ Não foi possível gravar clientId no config: ${err.message}`);
1004
- }
1005
- }
1006
- }
1007
- }
1008
-
1009
- loadIdentityPools() {
1010
- const saved = this.store.read('__identitypools__');
1011
- if (saved) {
1012
- for (const [id, data] of Object.entries(saved)) {
1013
- data.Identities = new Map(Object.entries(data.Identities || {}));
1014
- this.identityPools.set(id, data);
1015
- }
1016
- }
1017
- }
1018
-
1019
- loadUsers() {
1020
- const saved = this.store.read('__users__');
1021
- if (saved) {
1022
- for (const [id, user] of Object.entries(saved)) {
1023
- this.users.set(id, user);
1024
- }
1025
- }
1026
- }
1027
-
1028
- loadSessions() {
1029
- const saved = this.store.read('__sessions__');
1030
- if (saved) {
1031
- for (const [id, session] of Object.entries(saved)) {
1032
- this.sessions.set(id, session);
1033
- this.accessTokens.set(session.AccessToken, session);
1034
- this.refreshTokens.set(session.RefreshToken, session);
1035
- }
1036
- }
1037
- }
1038
-
1039
- persistUserPools() {
1040
- const poolsObj = {};
1041
- for (const [id, pool] of this.userPools.entries()) {
1042
- poolsObj[id] = {
1043
- ...pool,
1044
- Clients: Object.fromEntries(pool.Clients),
1045
- Groups: Object.fromEntries(pool.Groups),
1046
- IdentityProviders: Object.fromEntries(pool.IdentityProviders),
1047
- ResourceServers: Object.fromEntries(pool.ResourceServers)
1048
- };
1049
- }
1050
- this.store.write('__userpools__', poolsObj);
1051
- }
1052
-
1053
- persistIdentityPools() {
1054
- const poolsObj = {};
1055
- for (const [id, pool] of this.identityPools.entries()) {
1056
- poolsObj[id] = {
1057
- ...pool,
1058
- Identities: Object.fromEntries(pool.Identities)
1059
- };
1060
- }
1061
- this.store.write('__identitypools__', poolsObj);
1062
- }
1063
-
1064
- persistUsers() {
1065
- const usersObj = {};
1066
- for (const [id, user] of this.users.entries()) {
1067
- usersObj[id] = user;
1068
- }
1069
- this.store.write('__users__', usersObj);
1070
- }
1071
-
1072
- persistSessions() {
1073
- const sessionsObj = {};
1074
- for (const [id, session] of this.sessions.entries()) {
1075
- sessionsObj[id] = session;
1076
- }
1077
- this.store.write('__sessions__', sessionsObj);
1078
- }
1079
-
1080
- async reset() {
1081
- this.userPools.clear();
1082
- this.identityPools.clear();
1083
- this.users.clear();
1084
- this.sessions.clear();
1085
- this.accessTokens.clear();
1086
- this.refreshTokens.clear();
1087
-
1088
- this.persistUserPools();
1089
- this.persistIdentityPools();
1090
- this.persistUsers();
1091
- this.persistSessions();
1092
-
1093
- logger.debug('Cognito: Todos os dados resetados');
1094
- }
1095
-
1096
- // ============ Stats ============
1097
-
1098
- getUserPoolsCount() {
1099
- return this.userPools.size;
1100
- }
1101
-
1102
- getTotalUsersCount() {
1103
- return this.users.size;
1104
- }
1105
-
1106
- getIdentityPoolsCount() {
1107
- return this.identityPools.size;
1108
- }
1109
-
1110
- getActiveSessionsCount() {
1111
- return this.sessions.size;
1112
- }
1113
- }
1114
-
1115
- module.exports = CognitoSimulator;
1
+ /**
2
+ * Cognito Simulator Core
3
+ * Simula User Pools, Identity Pools, Autenticação, Tokens JWT
4
+ */
5
+
6
+ const crypto = require("crypto");
7
+ const jwt = require("jsonwebtoken");
8
+ const { v4: uuidv4 } = require("uuid");
9
+ const logger = require("../../utils/logger");
10
+ const LocalStore = require("../../utils/local-store");
11
+ const path = require("path");
12
+ const { CloudTrailAudit } = require("../../utils/cloudtrail-audit");
13
+
14
+ class CognitoSimulator {
15
+ constructor(config) {
16
+ this.config = config;
17
+ this.dataDir = path.join(process.env.AWS_LOCAL_SIMULATOR_DATA_DIR, "cognito");
18
+ this.store = new LocalStore(this.dataDir);
19
+ this.userPools = new Map();
20
+ this.identityPools = new Map();
21
+ this.users = new Map();
22
+ this.sessions = new Map();
23
+ this.refreshTokens = new Map();
24
+ this.accessTokens = new Map();
25
+ this.jwtSecret = crypto.randomBytes(64).toString("hex");
26
+ this.audit = new CloudTrailAudit("cognito-idp.amazonaws.com");
27
+ this.lambdaSimulator = null;
28
+ this.customAuthSessions = new Map();
29
+ }
30
+
31
+ setLambdaSimulator(lambdaSimulator) {
32
+ this.lambdaSimulator = lambdaSimulator;
33
+ }
34
+
35
+ _warnUnregisteredTriggers(pool) {
36
+ const triggers = pool.LambdaTriggers || {};
37
+ for (const [triggerName, fnName] of Object.entries(triggers)) {
38
+ if (fnName && !this.lambdaSimulator?.getLambda(fnName)) {
39
+ logger.warn(`⚠️ Cognito pool "${pool.Name}": trigger "${triggerName}" references Lambda "${fnName}" which is not registered`);
40
+ }
41
+ }
42
+ }
43
+
44
+ _buildTriggerEvent(triggerSource, userPool, user, clientId, requestFields) {
45
+ return {
46
+ version: "1",
47
+ triggerSource,
48
+ region: "us-east-1",
49
+ userPoolId: userPool.Id,
50
+ userName: user.Username,
51
+ callerContext: {
52
+ awsSdkVersion: "aws-sdk-unknown-unknown",
53
+ clientId,
54
+ },
55
+ request: { ...requestFields },
56
+ response: {},
57
+ };
58
+ }
59
+
60
+ // Returns userAttributes in the flat key/value format that real Cognito sends to triggers
61
+ _triggerUserAttributes(user) {
62
+ return {
63
+ sub: user.UserId,
64
+ "cognito:user_status": user.UserStatus,
65
+ ...user.Attributes,
66
+ };
67
+ }
68
+
69
+ async _invokeTrigger(userPool, triggerName, event) {
70
+ const fnName = userPool.LambdaTriggers?.[triggerName];
71
+ if (!fnName) {
72
+ return null;
73
+ }
74
+
75
+ if (!this.lambdaSimulator || !this.lambdaSimulator.getLambda(fnName)) {
76
+ logger.warn(`⚠️ Cognito trigger "${triggerName}": Lambda "${fnName}" is not available, skipping`);
77
+ return null;
78
+ }
79
+
80
+ const result = await this.lambdaSimulator.invoke(fnName, event, "RequestResponse");
81
+ return result.Payload;
82
+ }
83
+
84
+ async initialize() {
85
+ logger.debug("Inicializando Cognito Simulator...");
86
+ this.loadUserPools();
87
+ this.loadIdentityPools();
88
+ this.loadUsers();
89
+ this.loadSessions();
90
+
91
+ logger.debug(`✅ Cognito Simulator inicializado com ${this.userPools.size} user pools, ${this.identityPools.size} identity pools, ${this.users.size} usuários`);
92
+ }
93
+
94
+ // ============ User Pool Operations ============
95
+
96
+ createUserPool(params) {
97
+ const { PoolName, Policies, LambdaConfig, AutoVerifiedAttributes, AliasAttributes, UsernameAttributes, MfaConfiguration, UserPoolId } = params;
98
+
99
+ const poolId = UserPoolId ? UserPoolId : `local_${PoolName}_${Date.now()}`;
100
+ const userPool = {
101
+ Id: poolId,
102
+ Name: PoolName,
103
+ Arn: `arn:aws:cognito:local:000000000000:userpool/${poolId}`,
104
+ Status: "ACTIVE",
105
+ CreationDate: new Date().toISOString(),
106
+ LastModifiedDate: new Date().toISOString(),
107
+ Policies: Policies || {
108
+ PasswordPolicy: {
109
+ MinimumLength: 8,
110
+ RequireUppercase: true,
111
+ RequireLowercase: true,
112
+ RequireNumbers: true,
113
+ RequireSymbols: false,
114
+ },
115
+ },
116
+ LambdaConfig: LambdaConfig || {},
117
+ AutoVerifiedAttributes: AutoVerifiedAttributes || ["email"],
118
+ AliasAttributes: AliasAttributes || [],
119
+ UsernameAttributes: UsernameAttributes || ["email"],
120
+ MfaConfiguration: MfaConfiguration || "OFF",
121
+ EstimatedNumberOfUsers: 0,
122
+ Users: [],
123
+ Clients: new Map(),
124
+ Groups: new Map(),
125
+ IdentityProviders: new Map(),
126
+ ResourceServers: new Map(),
127
+ };
128
+
129
+ this.userPools.set(poolId, userPool);
130
+ this.persistUserPools();
131
+
132
+ logger.debug(`✅ User Pool criado: ${PoolName} (${poolId})`);
133
+ this.audit.record({ eventName: "CreateUserPool", readOnly: false, resources: [{ ARN: userPool.Arn, type: "AWS::Cognito::UserPool" }], requestParameters: { poolName: PoolName } });
134
+
135
+ return {
136
+ UserPool: {
137
+ Id: userPool.Id,
138
+ Name: userPool.Name,
139
+ Arn: userPool.Arn,
140
+ Status: userPool.Status,
141
+ CreationDate: userPool.CreationDate,
142
+ LastModifiedDate: userPool.LastModifiedDate,
143
+ MfaConfiguration: userPool.MfaConfiguration,
144
+ EstimatedNumberOfUsers: 0,
145
+ },
146
+ };
147
+ }
148
+
149
+ listUserPools(params = {}) {
150
+ const { MaxResults = 60, NextToken } = params;
151
+ let userPools = Array.from(this.userPools.values());
152
+
153
+ if (NextToken) {
154
+ const startIndex = parseInt(NextToken);
155
+ userPools = userPools.slice(startIndex);
156
+ }
157
+
158
+ const results = userPools.slice(0, MaxResults);
159
+ const nextToken = results.length === MaxResults ? String(MaxResults) : null;
160
+
161
+ return {
162
+ UserPools: results.map((pool) => ({
163
+ Id: pool.Id,
164
+ Name: pool.Name,
165
+ Arn: pool.Arn,
166
+ Status: pool.Status,
167
+ CreationDate: pool.CreationDate,
168
+ LastModifiedDate: pool.LastModifiedDate,
169
+ })),
170
+ NextToken: nextToken,
171
+ };
172
+ }
173
+
174
+ describeUserPool(params) {
175
+ const { UserPoolId } = params;
176
+ const userPool = this.userPools.get(UserPoolId);
177
+
178
+ if (!userPool) {
179
+ throw new Error(`User pool ${UserPoolId} not found`);
180
+ }
181
+
182
+ return {
183
+ UserPool: {
184
+ Id: userPool.Id,
185
+ Name: userPool.Name,
186
+ Arn: userPool.Arn,
187
+ Status: userPool.Status,
188
+ CreationDate: userPool.CreationDate,
189
+ LastModifiedDate: userPool.LastModifiedDate,
190
+ Policies: userPool.Policies,
191
+ LambdaConfig: userPool.LambdaConfig,
192
+ AutoVerifiedAttributes: userPool.AutoVerifiedAttributes,
193
+ AliasAttributes: userPool.AliasAttributes,
194
+ UsernameAttributes: userPool.UsernameAttributes,
195
+ MfaConfiguration: userPool.MfaConfiguration,
196
+ EstimatedNumberOfUsers: userPool.Users.length,
197
+ },
198
+ };
199
+ }
200
+
201
+ listUsers(params = {}) {
202
+ const { UserPoolId, Filter, Limit = 60, PaginationToken } = params;
203
+ const userPool = this.userPools.get(UserPoolId);
204
+
205
+ if (!userPool) {
206
+ throw new Error(`User pool ${UserPoolId} not found`);
207
+ }
208
+
209
+ let users = Array.from(this.users.values()).filter((u) => u.UserPoolId === UserPoolId);
210
+
211
+ if (PaginationToken) {
212
+ const startIndex = parseInt(PaginationToken);
213
+ users = users.slice(startIndex);
214
+ }
215
+
216
+ const results = users.slice(0, Limit);
217
+ const nextToken = results.length === Limit && users.length > Limit ? String(Limit) : null;
218
+
219
+ return {
220
+ Users: results.map((u) => ({
221
+ Username: u.Username,
222
+ UserStatus: u.UserStatus,
223
+ Enabled: u.Enabled,
224
+ UserCreateDate: u.CreatedDate,
225
+ UserLastModifiedDate: u.LastModifiedDate,
226
+ Attributes: this.formatUserAttributes(u.Attributes),
227
+ })),
228
+ PaginationToken: nextToken,
229
+ };
230
+ }
231
+
232
+ listUserPoolClients(params = {}) {
233
+ const { UserPoolId, MaxResults = 60, NextToken } = params;
234
+ const userPool = this.userPools.get(UserPoolId);
235
+ if (!userPool) throw new Error(`User pool ${UserPoolId} not found`);
236
+
237
+ let clients = Array.from(userPool.Clients.values());
238
+ if (NextToken) clients = clients.slice(parseInt(NextToken));
239
+ const results = clients.slice(0, MaxResults);
240
+
241
+ return {
242
+ UserPoolClients: results.map((c) => ({
243
+ ClientId: c.ClientId,
244
+ ClientName: c.ClientName,
245
+ UserPoolId: c.UserPoolId,
246
+ })),
247
+ NextToken: results.length === MaxResults && clients.length > MaxResults ? String(MaxResults) : null,
248
+ };
249
+ }
250
+
251
+ describeUserPoolClient(params = {}) {
252
+ const { UserPoolId, ClientId } = params;
253
+ const userPool = this.userPools.get(UserPoolId);
254
+ if (!userPool) throw new Error(`User pool ${UserPoolId} not found`);
255
+ const client = userPool.Clients.get(ClientId);
256
+ if (!client) throw new Error(`Client ${ClientId} not found`);
257
+ return { UserPoolClient: client };
258
+ }
259
+
260
+ deleteUserPoolClient(params = {}) {
261
+ const { UserPoolId, ClientId } = params;
262
+ const userPool = this.userPools.get(UserPoolId);
263
+ if (!userPool) throw new Error(`User pool ${UserPoolId} not found`);
264
+ userPool.Clients.delete(ClientId);
265
+ this.persistUserPools();
266
+ return {};
267
+ }
268
+
269
+ forgotPassword(params = {}) {
270
+ const { ClientId, Username } = params;
271
+ const userPool = this.findUserPoolByClientId(ClientId);
272
+ if (!userPool) throw new Error(`Client ${ClientId} not found`);
273
+
274
+ const user = this.findUserByUsername(Username, ClientId);
275
+ if (!user) throw new Error(`User not found: ${Username}`);
276
+
277
+ if (user.UserStatus !== "CONFIRMED") {
278
+ const err = new Error("Cannot reset password for the user as there is no registered/verified email or phone_number");
279
+ err.code = "InvalidParameterException";
280
+ throw err;
281
+ }
282
+
283
+ const resetCode = Math.floor(100000 + Math.random() * 900000).toString();
284
+ user.PasswordResetCode = resetCode;
285
+ this.persistUsers();
286
+
287
+ const userEmail = user.Attributes.email || Username;
288
+ logger.info(`📧 [COGNITO] Código de redefinição de senha para "${Username}": ${resetCode}`);
289
+
290
+ return {
291
+ CodeDeliveryDetails: {
292
+ Destination: userEmail,
293
+ DeliveryMedium: "EMAIL",
294
+ AttributeName: "email",
295
+ },
296
+ };
297
+ }
298
+
299
+ confirmForgotPassword(params = {}) {
300
+ const { ClientId, Username, ConfirmationCode, Password } = params;
301
+ const userPool = this.findUserPoolByClientId(ClientId);
302
+ if (!userPool) throw new Error(`Client ${ClientId} not found`);
303
+
304
+ const user = this.findUserByUsername(Username, ClientId);
305
+ if (!user) throw new Error(`User not found: ${Username}`);
306
+
307
+ if (user.UserStatus !== "CONFIRMED") {
308
+ const err = new Error("Cannot reset password for the user as there is no registered/verified email or phone_number");
309
+ err.code = "InvalidParameterException";
310
+ throw err;
311
+ }
312
+
313
+ if (user.PasswordResetCode && user.PasswordResetCode !== ConfirmationCode) {
314
+ const err = new Error("Invalid verification code provided, please try again.");
315
+ err.code = "CodeMismatchException";
316
+ throw err;
317
+ }
318
+
319
+ user.Password = this.hashPassword(Password);
320
+ delete user.PasswordResetCode;
321
+ this.persistUsers();
322
+ return {};
323
+ }
324
+
325
+ changePassword(params = {}) {
326
+ const { AccessToken, PreviousPassword, ProposedPassword } = params;
327
+ const session = this.accessTokens.get(AccessToken);
328
+ if (!session) throw new Error("Invalid access token");
329
+ const user = this.users.get(session.UserId);
330
+ if (!user) throw new Error("User not found");
331
+ if (!this.verifyPassword(PreviousPassword, user.Password)) throw new Error("Incorrect previous password");
332
+ user.Password = this.hashPassword(ProposedPassword);
333
+ this.persistUsers();
334
+ return {};
335
+ }
336
+
337
+ async respondToAuthChallenge(params = {}) {
338
+ const { ChallengeName } = params;
339
+
340
+ // NEW_PASSWORD_REQUIRED — user was created by admin and must set a permanent password
341
+ if (ChallengeName === "NEW_PASSWORD_REQUIRED") {
342
+ const session = this.customAuthSessions.get(params.Session);
343
+ if (!session || session.challenge !== "NEW_PASSWORD_REQUIRED") {
344
+ throw new Error("Invalid session token");
345
+ }
346
+
347
+ const newPassword = params.ChallengeResponses?.NEW_PASSWORD;
348
+ if (!newPassword) throw new Error("NEW_PASSWORD is required");
349
+
350
+ const user = this.users.get(session.userId);
351
+ const userPool = this.userPools.get(session.userPoolId);
352
+ if (!user || !userPool) throw new Error("Invalid session");
353
+
354
+ // 1. PreAuthentication — dispara antes de processar a nova senha
355
+ const preAuthEvent = this._buildTriggerEvent("PreAuthentication_Authentication", userPool, user, session.clientId, {
356
+ userAttributes: this._triggerUserAttributes(user),
357
+ validationData: {},
358
+ });
359
+ await this._invokeTrigger(userPool, "PreAuthentication", preAuthEvent);
360
+
361
+ // 2. Aplica nova senha e confirma usuário
362
+ user.Password = this.hashPassword(newPassword);
363
+ user.UserStatus = "CONFIRMED";
364
+ user.LastModifiedDate = new Date().toISOString();
365
+ this.persistUsers();
366
+ this.customAuthSessions.delete(params.Session);
367
+
368
+ logger.debug(`🔑 Senha alterada e usuário confirmado: ${user.Username}`);
369
+
370
+ // 3. PostConfirmation — dispara após confirmação do usuário (troca de senha forçada)
371
+ const postConfirmEvent = this._buildTriggerEvent("PostConfirmation_ConfirmSignUp", userPool, user, session.clientId, {
372
+ userAttributes: this._triggerUserAttributes(user),
373
+ });
374
+ try {
375
+ await this._invokeTrigger(userPool, "PostConfirmation", postConfirmEvent);
376
+ } catch (err) {
377
+ logger.error(`PostConfirmation trigger error (ignored): ${err.message}`);
378
+ }
379
+
380
+ // 4. PreTokenGeneration — antes de gerar tokens
381
+ const preTokenEvent = this._buildTriggerEvent("TokenGeneration_Authentication", userPool, user, session.clientId, {
382
+ userAttributes: this._triggerUserAttributes(user),
383
+ groupConfiguration: { groupsToOverride: [], iamRolesToOverride: [], preferredRole: null },
384
+ });
385
+ const preTokenResponse = await this._invokeTrigger(userPool, "PreTokenGeneration", preTokenEvent);
386
+ const claimsOverride = preTokenResponse?.response?.claimsOverrideDetails || null;
387
+
388
+ // 5. Gera tokens
389
+ const accessToken = this.generateAccessToken(user, userPool, session.clientId);
390
+ const idToken = this.generateIdToken(user, userPool, session.clientId, claimsOverride);
391
+ const refreshToken = this.generateRefreshToken(user, userPool, session.clientId);
392
+
393
+ const sessionId = uuidv4();
394
+ const authSession = {
395
+ Id: sessionId,
396
+ UserId: user.UserId,
397
+ UserPoolId: userPool.Id,
398
+ ClientId: session.clientId,
399
+ AccessToken: accessToken,
400
+ IdToken: idToken,
401
+ RefreshToken: refreshToken,
402
+ CreatedAt: new Date().toISOString(),
403
+ ExpiresAt: new Date(Date.now() + 3600000).toISOString(),
404
+ };
405
+ this.sessions.set(sessionId, authSession);
406
+ this.accessTokens.set(accessToken, authSession);
407
+ this.refreshTokens.set(refreshToken, authSession);
408
+ this.persistSessions();
409
+
410
+ // 6. PostAuthentication — após auth bem-sucedida (non-blocking)
411
+ const postAuthEvent = this._buildTriggerEvent("PostAuthentication_Authentication", userPool, user, session.clientId, {
412
+ userAttributes: this._triggerUserAttributes(user),
413
+ newDeviceUsed: false,
414
+ });
415
+ try {
416
+ await this._invokeTrigger(userPool, "PostAuthentication", postAuthEvent);
417
+ } catch (err) {
418
+ logger.error(`PostAuthentication trigger error (ignored): ${err.message}`);
419
+ }
420
+
421
+ return {
422
+ AuthenticationResult: {
423
+ AccessToken: accessToken,
424
+ IdToken: idToken,
425
+ RefreshToken: refreshToken,
426
+ TokenType: "Bearer",
427
+ ExpiresIn: 3600,
428
+ },
429
+ ChallengeName: null,
430
+ };
431
+ }
432
+
433
+ if (ChallengeName === "CUSTOM_CHALLENGE") {
434
+ const session = this.customAuthSessions.get(params.Session);
435
+ if (!session) {
436
+ throw new Error("Invalid session token");
437
+ }
438
+
439
+ const user = this.users.get(session.userId);
440
+ const userPool = this.userPools.get(session.userPoolId);
441
+
442
+ // Invoke VerifyAuthChallengeResponse
443
+ const verifyEvent = this._buildTriggerEvent("VerifyAuthChallengeResponse_Authentication", userPool, user, session.clientId, {
444
+ challengeAnswer: params.ChallengeResponses?.ANSWER,
445
+ privateChallengeParameters: session.privateChallengeParameters,
446
+ });
447
+ const verifyResponse = await this._invokeTrigger(userPool, "VerifyAuthChallengeResponse", verifyEvent);
448
+
449
+ // Append challenge result to session
450
+ session.session.push({
451
+ challengeName: "CUSTOM_CHALLENGE",
452
+ challengeResult: verifyResponse?.response?.answerCorrect === true,
453
+ challengeMetadata: "",
454
+ });
455
+
456
+ // Invoke DefineAuthChallenge again with updated session
457
+ const defineEvent = this._buildTriggerEvent("DefineAuthChallenge_Authentication", userPool, user, session.clientId, {
458
+ session: session.session,
459
+ });
460
+ const defineResponse = await this._invokeTrigger(userPool, "DefineAuthChallenge", defineEvent);
461
+
462
+ if (defineResponse?.response?.issueTokens === true) {
463
+ // 1. PreTokenGeneration antes de gerar tokens
464
+ const preTokenEvent = this._buildTriggerEvent("TokenGeneration_Authentication", userPool, user, session.clientId, {
465
+ userAttributes: this._triggerUserAttributes(user),
466
+ groupConfiguration: { groupsToOverride: [], iamRolesToOverride: [], preferredRole: null },
467
+ });
468
+ const preTokenResponse = await this._invokeTrigger(userPool, "PreTokenGeneration", preTokenEvent);
469
+ const claimsOverride = preTokenResponse?.response?.claimsOverrideDetails || null;
470
+
471
+ // 2. Gera tokens
472
+ const accessToken = this.generateAccessToken(user, userPool, session.clientId);
473
+ const idToken = this.generateIdToken(user, userPool, session.clientId, claimsOverride);
474
+ const refreshToken = this.generateRefreshToken(user, userPool, session.clientId);
475
+
476
+ const sessionId = uuidv4();
477
+ const authSession = {
478
+ Id: sessionId,
479
+ UserId: user.UserId,
480
+ UserPoolId: userPool.Id,
481
+ ClientId: session.clientId,
482
+ AccessToken: accessToken,
483
+ IdToken: idToken,
484
+ RefreshToken: refreshToken,
485
+ CreatedAt: new Date().toISOString(),
486
+ ExpiresAt: new Date(Date.now() + 3600000).toISOString(),
487
+ };
488
+
489
+ this.sessions.set(sessionId, authSession);
490
+ this.accessTokens.set(accessToken, authSession);
491
+ this.refreshTokens.set(refreshToken, authSession);
492
+ this.persistSessions();
493
+
494
+ this.customAuthSessions.delete(params.Session);
495
+
496
+ // 3. PostAuthentication (non-blocking)
497
+ const postAuthEvent = this._buildTriggerEvent("PostAuthentication_Authentication", userPool, user, session.clientId, {
498
+ userAttributes: this._triggerUserAttributes(user),
499
+ newDeviceUsed: false,
500
+ });
501
+ try {
502
+ await this._invokeTrigger(userPool, "PostAuthentication", postAuthEvent);
503
+ } catch (err) {
504
+ logger.error(`PostAuthentication trigger error (ignored): ${err.message}`);
505
+ }
506
+
507
+ return {
508
+ AuthenticationResult: {
509
+ AccessToken: accessToken,
510
+ IdToken: idToken,
511
+ RefreshToken: refreshToken,
512
+ TokenType: "Bearer",
513
+ ExpiresIn: 3600,
514
+ },
515
+ ChallengeName: null,
516
+ };
517
+ }
518
+
519
+ // Issue next challenge
520
+ const createEvent = this._buildTriggerEvent("CreateAuthChallenge_Authentication", userPool, user, session.clientId, {
521
+ challengeName: "CUSTOM_CHALLENGE",
522
+ session: session.session,
523
+ });
524
+ const createResponse = await this._invokeTrigger(userPool, "CreateAuthChallenge", createEvent);
525
+
526
+ // Update stored private challenge parameters for next round
527
+ session.privateChallengeParameters = createResponse?.response?.privateChallengeParameters || {};
528
+
529
+ return {
530
+ ChallengeName: "CUSTOM_CHALLENGE",
531
+ ChallengeParameters: createResponse?.response?.publicChallengeParameters || {},
532
+ Session: params.Session,
533
+ AuthenticationResult: null,
534
+ };
535
+ }
536
+
537
+ return { AuthenticationResult: null, ChallengeName: null };
538
+ }
539
+
540
+ revokeToken(params = {}) {
541
+ const { Token, ClientId } = params;
542
+ // Remove refresh token session if it exists
543
+ const session = this.refreshTokens.get(Token);
544
+ if (session) {
545
+ this.sessions.delete(session.Id);
546
+ this.accessTokens.delete(session.AccessToken);
547
+ this.refreshTokens.delete(Token);
548
+ this.persistSessions();
549
+ }
550
+ return {};
551
+ }
552
+
553
+ globalSignOut(params = {}) {
554
+ const { AccessToken } = params;
555
+ const session = this.accessTokens.get(AccessToken);
556
+ if (session) {
557
+ this.sessions.delete(session.Id);
558
+ this.accessTokens.delete(AccessToken);
559
+ this.refreshTokens.delete(session.RefreshToken);
560
+ this.persistSessions();
561
+ }
562
+ return {};
563
+ }
564
+
565
+ getUser(params = {}) {
566
+ const { AccessToken } = params;
567
+ const session = this.accessTokens.get(AccessToken);
568
+ if (!session) throw new Error("Invalid access token");
569
+ // Check session is still active
570
+ if (!this.sessions.has(session.Id)) throw new Error("Token has been revoked");
571
+ const user = this.users.get(session.UserId);
572
+ if (!user) throw new Error("User not found");
573
+ return {
574
+ Username: user.Username,
575
+ UserAttributes: this.formatUserAttributes(user.Attributes),
576
+ UserStatus: user.UserStatus,
577
+ };
578
+ }
579
+
580
+ updateUserAttributes(params = {}) {
581
+ const { AccessToken, UserAttributes } = params;
582
+ const session = this.accessTokens.get(AccessToken);
583
+ if (!session) throw new Error("Invalid access token");
584
+ const user = this.users.get(session.UserId);
585
+ if (!user) throw new Error("User not found");
586
+ const updates = this.normalizeUserAttributes(UserAttributes || []);
587
+ Object.assign(user.Attributes, updates);
588
+ this.persistUsers();
589
+ return { CodeDeliveryDetailsList: [] };
590
+ }
591
+
592
+ deleteUser(params = {}) {
593
+ const { AccessToken } = params;
594
+ const session = this.accessTokens.get(AccessToken);
595
+ if (!session) throw new Error("Invalid access token");
596
+ this.users.delete(session.UserId);
597
+ this.persistUsers();
598
+ return {};
599
+ }
600
+
601
+ adminDisableUser(params = {}) {
602
+ const { UserPoolId, Username } = params;
603
+ const user = this.findUserByUsername(Username, null, UserPoolId);
604
+ if (!user) throw new Error(`User not found: ${Username}`);
605
+ user.Enabled = false;
606
+ this.persistUsers();
607
+ return {};
608
+ }
609
+
610
+ adminEnableUser(params = {}) {
611
+ const { UserPoolId, Username } = params;
612
+ const user = this.findUserByUsername(Username, null, UserPoolId);
613
+ if (!user) throw new Error(`User not found: ${Username}`);
614
+ user.Enabled = true;
615
+ this.persistUsers();
616
+ return {};
617
+ }
618
+
619
+ adminResetUserPassword(params = {}) {
620
+ const { UserPoolId, Username } = params;
621
+ const user = this.findUserByUsername(Username, null, UserPoolId);
622
+ if (!user) throw new Error(`User not found: ${Username}`);
623
+ user.UserStatus = "RESET_REQUIRED";
624
+ this.persistUsers();
625
+ return {};
626
+ }
627
+
628
+ adminUserGlobalSignOut(params = {}) {
629
+ const { UserPoolId, Username } = params;
630
+ const user = this.findUserByUsername(Username, null, UserPoolId);
631
+ if (!user) throw new Error(`User not found: ${Username}`);
632
+ // Invalidate all sessions for this user
633
+ for (const [id, session] of this.sessions.entries()) {
634
+ if (session.UserId === user.UserId) {
635
+ this.accessTokens.delete(session.AccessToken);
636
+ this.refreshTokens.delete(session.RefreshToken);
637
+ this.sessions.delete(id);
638
+ }
639
+ }
640
+ this.persistSessions();
641
+ return {};
642
+ }
643
+
644
+ adminListGroupsForUser(params = {}) {
645
+ return { Groups: [], NextToken: null };
646
+ }
647
+
648
+ deleteUserPool(params) {
649
+ const { UserPoolId } = params;
650
+
651
+ if (!this.userPools.has(UserPoolId)) {
652
+ throw new Error(`User pool ${UserPoolId} not found`);
653
+ }
654
+
655
+ this.userPools.delete(UserPoolId);
656
+ this.persistUserPools();
657
+
658
+ return {};
659
+ }
660
+
661
+ // ============ User Pool Client Operations ============
662
+
663
+ createUserPoolClient(params) {
664
+ const { UserPoolId, ClientName, GenerateSecret, RefreshTokenValidity, AccessTokenValidity, IdTokenValidity, AllowedOAuthFlows, AllowedOAuthScopes, CallbackURLs, LogoutURLs } = params;
665
+
666
+ const userPool = this.userPools.get(UserPoolId);
667
+ if (!userPool) {
668
+ throw new Error(`User pool ${UserPoolId} not found`);
669
+ }
670
+
671
+ const clientId = crypto.randomBytes(20).toString("hex");
672
+ const clientSecret = GenerateSecret ? crypto.randomBytes(32).toString("hex") : null;
673
+
674
+ const client = {
675
+ ClientId: clientId,
676
+ ClientName: ClientName,
677
+ ClientSecret: clientSecret,
678
+ UserPoolId: UserPoolId,
679
+ RefreshTokenValidity: RefreshTokenValidity || 30,
680
+ AccessTokenValidity: AccessTokenValidity || 1,
681
+ IdTokenValidity: IdTokenValidity || 1,
682
+ AllowedOAuthFlows: AllowedOAuthFlows || ["code"],
683
+ AllowedOAuthScopes: AllowedOAuthScopes || ["openid", "email", "profile"],
684
+ CallbackURLs: CallbackURLs || [],
685
+ LogoutURLs: LogoutURLs || [],
686
+ CreatedDate: new Date().toISOString(),
687
+ LastModifiedDate: new Date().toISOString(),
688
+ };
689
+
690
+ userPool.Clients.set(clientId, client);
691
+ this.persistUserPools();
692
+
693
+ logger.debug(`✅ User Pool Client criado: ${ClientName} (${clientId})`);
694
+
695
+ return {
696
+ UserPoolClient: {
697
+ ClientId: client.ClientId,
698
+ ClientName: client.ClientName,
699
+ ClientSecret: client.ClientSecret,
700
+ UserPoolId: client.UserPoolId,
701
+ RefreshTokenValidity: client.RefreshTokenValidity,
702
+ AccessTokenValidity: client.AccessTokenValidity,
703
+ IdTokenValidity: client.IdTokenValidity,
704
+ AllowedOAuthFlows: client.AllowedOAuthFlows,
705
+ AllowedOAuthScopes: client.AllowedOAuthScopes,
706
+ CallbackURLs: client.CallbackURLs,
707
+ LogoutURLs: client.LogoutURLs,
708
+ CreationDate: client.CreatedDate,
709
+ },
710
+ };
711
+ }
712
+
713
+ // ============ User Operations ============
714
+
715
+ async signUp(params = {}) {
716
+ const { ClientId, Username, Password, UserAttributes, ValidationData } = params;
717
+
718
+ // Encontra o user pool pelo client id
719
+ const userPool = this.findUserPoolByClientId(ClientId);
720
+ if (!userPool) {
721
+ throw new Error(`Client ${ClientId} not found`);
722
+ }
723
+
724
+ // Verifica se usuário já existe
725
+ const existingUser = Array.from(this.users.values()).find((u) => u.Username === Username && u.UserPoolId === userPool.Id);
726
+
727
+ if (existingUser) {
728
+ throw new Error(`User already exists: ${Username}`);
729
+ }
730
+
731
+ const userId = uuidv4();
732
+ const confirmationCode = Math.floor(100000 + Math.random() * 900000).toString();
733
+
734
+ const user = {
735
+ Username: Username,
736
+ UserPoolId: userPool.Id,
737
+ UserId: userId,
738
+ Attributes: this.normalizeUserAttributes(UserAttributes || []),
739
+ Enabled: true,
740
+ UserStatus: "UNCONFIRMED",
741
+ CreatedDate: new Date().toISOString(),
742
+ LastModifiedDate: new Date().toISOString(),
743
+ Password: this.hashPassword(Password),
744
+ ConfirmationCode: confirmationCode,
745
+ MfaOptions: [],
746
+ PreferredMfaSetting: null,
747
+ UserMFASettingList: [],
748
+ };
749
+
750
+ // Invoke PreSignUp trigger before persisting the user
751
+ const event = this._buildTriggerEvent("PreSignUp_SignUp", userPool, user, ClientId, {
752
+ userAttributes: this.normalizeUserAttributes(UserAttributes || []),
753
+ validationData: ValidationData || {},
754
+ clientMetadata: {},
755
+ });
756
+ const triggerResponse = await this._invokeTrigger(userPool, "PreSignUp", event);
757
+
758
+ if (triggerResponse !== null) {
759
+ if (triggerResponse.response?.autoConfirmUser === true) {
760
+ user.UserStatus = "CONFIRMED";
761
+ delete user.ConfirmationCode;
762
+ }
763
+ if (triggerResponse.response?.autoVerifyEmail === true) {
764
+ user.Attributes.email_verified = "true";
765
+ }
766
+ }
767
+
768
+ this.users.set(userId, user);
769
+ userPool.Users.push(userId);
770
+ userPool.EstimatedNumberOfUsers++;
771
+ this.persistUsers();
772
+ this.persistUserPools();
773
+
774
+ const userEmail = user.Attributes.email || Username;
775
+
776
+ if (user.UserStatus === "UNCONFIRMED") {
777
+ logger.info(`📧 [COGNITO] Código de confirmação para "${Username}": ${confirmationCode}`);
778
+ }
779
+
780
+ logger.debug(`✅ Usuário criado: ${Username} (${userId}) — status: ${user.UserStatus}`);
781
+
782
+ // PostConfirmation dispara se usuário foi auto-confirmado pelo PreSignUp
783
+ if (user.UserStatus === "CONFIRMED") {
784
+ const postConfirmEvent = this._buildTriggerEvent("PostConfirmation_ConfirmSignUp", userPool, user, ClientId, {
785
+ userAttributes: this._triggerUserAttributes(user),
786
+ });
787
+ try {
788
+ await this._invokeTrigger(userPool, "PostConfirmation", postConfirmEvent);
789
+ } catch (err) {
790
+ logger.error(`PostConfirmation trigger error (ignored): ${err.message}`);
791
+ }
792
+ }
793
+
794
+ return {
795
+ UserConfirmed: user.UserStatus === "CONFIRMED",
796
+ UserSub: userId,
797
+ CodeDeliveryDetails: user.UserStatus === "UNCONFIRMED"
798
+ ? { Destination: userEmail, DeliveryMedium: "EMAIL", AttributeName: "email" }
799
+ : null,
800
+ };
801
+ }
802
+
803
+ async confirmSignUp(params) {
804
+ const { ClientId, Username, ConfirmationCode } = params;
805
+
806
+ const userPool = this.findUserPoolByClientId(ClientId);
807
+ if (!userPool) {
808
+ throw new Error(`Client ${ClientId} not found`);
809
+ }
810
+
811
+ const user = this.findUserByUsername(Username, ClientId);
812
+ if (!user) {
813
+ throw new Error(`User not found: ${Username}`);
814
+ }
815
+
816
+ if (user.ConfirmationCode && user.ConfirmationCode !== ConfirmationCode) {
817
+ throw new Error("Invalid verification code provided, please try again.");
818
+ }
819
+
820
+ user.UserStatus = "CONFIRMED";
821
+ user.LastModifiedDate = new Date().toISOString();
822
+ delete user.ConfirmationCode;
823
+ this.persistUsers();
824
+
825
+ const event = this._buildTriggerEvent("PostConfirmation_ConfirmSignUp", userPool, user, ClientId, {
826
+ userAttributes: this._triggerUserAttributes(user),
827
+ });
828
+ try {
829
+ await this._invokeTrigger(userPool, "PostConfirmation", event);
830
+ } catch (err) {
831
+ logger.error(`PostConfirmation trigger error (ignored): ${err.message}`);
832
+ }
833
+
834
+ return {};
835
+ }
836
+
837
+ async initiateAuth(params) {
838
+ const { AuthFlow, ClientId, AuthParameters } = params;
839
+ const userPool = this.findUserPoolByClientId(ClientId);
840
+
841
+ if (!userPool) {
842
+ throw new Error(`Client ${ClientId} not found`);
843
+ }
844
+
845
+ // CUSTOM_AUTH flow — no password check, challenge-based
846
+ if (AuthFlow === "CUSTOM_AUTH") {
847
+ const username = AuthParameters.USERNAME;
848
+ const user = this.findUserByUsername(username, ClientId);
849
+ if (!user) {
850
+ throw new Error(`User not found: ${username}`);
851
+ }
852
+
853
+ // 1. PreAuthentication dispara antes de qualquer challenge
854
+ const preAuthEvent = this._buildTriggerEvent("PreAuthentication_Authentication", userPool, user, ClientId, {
855
+ userAttributes: this._triggerUserAttributes(user),
856
+ validationData: AuthParameters.ValidationData || {},
857
+ });
858
+ await this._invokeTrigger(userPool, "PreAuthentication", preAuthEvent);
859
+
860
+ // 2. DefineAuthChallenge define qual challenge usar
861
+ const defineEvent = this._buildTriggerEvent("DefineAuthChallenge_Authentication", userPool, user, ClientId, {
862
+ session: [],
863
+ });
864
+ const defineResponse = await this._invokeTrigger(userPool, "DefineAuthChallenge", defineEvent);
865
+
866
+ if (defineResponse?.response?.challengeName === "CUSTOM_CHALLENGE") {
867
+ const createEvent = this._buildTriggerEvent("CreateAuthChallenge_Authentication", userPool, user, ClientId, {
868
+ challengeName: "CUSTOM_CHALLENGE",
869
+ session: [],
870
+ });
871
+ const createResponse = await this._invokeTrigger(userPool, "CreateAuthChallenge", createEvent);
872
+
873
+ const sessionToken = uuidv4();
874
+ this.customAuthSessions.set(sessionToken, {
875
+ sessionToken,
876
+ userId: user.UserId,
877
+ userPoolId: userPool.Id,
878
+ clientId: ClientId,
879
+ session: [],
880
+ privateChallengeParameters: createResponse?.response?.privateChallengeParameters || {},
881
+ });
882
+
883
+ return {
884
+ ChallengeName: "CUSTOM_CHALLENGE",
885
+ ChallengeParameters: createResponse?.response?.publicChallengeParameters || {},
886
+ Session: sessionToken,
887
+ AuthenticationResult: null,
888
+ };
889
+ }
890
+
891
+ // DefineAuthChallenge did not return CUSTOM_CHALLENGE — nothing to do
892
+ return {
893
+ ChallengeName: null,
894
+ ChallengeParameters: {},
895
+ Session: null,
896
+ AuthenticationResult: null,
897
+ };
898
+ }
899
+
900
+ const username = AuthParameters.USERNAME;
901
+ const password = AuthParameters.PASSWORD;
902
+
903
+ const user = this.findUserByUsername(username, ClientId);
904
+ if (!user) {
905
+ throw new Error(`User not found: ${username}`);
906
+ }
907
+
908
+ // FORCE_CHANGE_PASSWORD — validate temp password then return NEW_PASSWORD_REQUIRED challenge
909
+ if (user.UserStatus === "FORCE_CHANGE_PASSWORD") {
910
+ if (!this.verifyPassword(password, user.Password)) {
911
+ throw new Error("Incorrect username or password");
912
+ }
913
+ const sessionToken = uuidv4();
914
+ this.customAuthSessions.set(sessionToken, {
915
+ sessionToken,
916
+ userId: user.UserId,
917
+ userPoolId: userPool.Id,
918
+ clientId: ClientId,
919
+ challenge: "NEW_PASSWORD_REQUIRED",
920
+ });
921
+ return {
922
+ ChallengeName: "NEW_PASSWORD_REQUIRED",
923
+ ChallengeParameters: {
924
+ USER_ID_FOR_SRP: user.Username,
925
+ requiredAttributes: "[]",
926
+ userAttributes: JSON.stringify(this._triggerUserAttributes(user)),
927
+ },
928
+ Session: sessionToken,
929
+ AuthenticationResult: null,
930
+ };
931
+ }
932
+
933
+ if (user.UserStatus !== "CONFIRMED") {
934
+ // Valida senha antes de revelar o status — igual ao Cognito real
935
+ if (!this.verifyPassword(password, user.Password)) {
936
+ throw new Error("Incorrect username or password");
937
+ }
938
+ const err = new Error("User is not confirmed.");
939
+ err.code = "UserNotConfirmedException";
940
+ throw err;
941
+ }
942
+
943
+ // 1. PreAuthentication dispara antes de validar senha
944
+ const preAuthEvent = this._buildTriggerEvent("PreAuthentication_Authentication", userPool, user, ClientId, {
945
+ userAttributes: this._triggerUserAttributes(user),
946
+ validationData: AuthParameters.ValidationData || {},
947
+ });
948
+ await this._invokeTrigger(userPool, "PreAuthentication", preAuthEvent);
949
+
950
+ // 2. Valida senha
951
+ if (!this.verifyPassword(password, user.Password)) {
952
+ throw new Error("Incorrect username or password");
953
+ }
954
+
955
+ // 3. PreTokenGeneration dispara antes de gerar tokens, pode sobrescrever claims
956
+ const preTokenEvent = this._buildTriggerEvent("TokenGeneration_Authentication", userPool, user, ClientId, {
957
+ userAttributes: this._triggerUserAttributes(user),
958
+ groupConfiguration: { groupsToOverride: [], iamRolesToOverride: [], preferredRole: null },
959
+ });
960
+ const preTokenResponse = await this._invokeTrigger(userPool, "PreTokenGeneration", preTokenEvent);
961
+ const claimsOverride = preTokenResponse?.response?.claimsOverrideDetails || null;
962
+
963
+ // 4. Gera tokens com claims override se houver
964
+ const accessToken = this.generateAccessToken(user, userPool, ClientId);
965
+ const idToken = this.generateIdToken(user, userPool, ClientId, claimsOverride);
966
+ const refreshToken = this.generateRefreshToken(user, userPool, ClientId);
967
+
968
+ const sessionId = uuidv4();
969
+ const session = {
970
+ Id: sessionId,
971
+ UserId: user.UserId,
972
+ UserPoolId: userPool.Id,
973
+ ClientId: ClientId,
974
+ AccessToken: accessToken,
975
+ IdToken: idToken,
976
+ RefreshToken: refreshToken,
977
+ CreatedAt: new Date().toISOString(),
978
+ ExpiresAt: new Date(Date.now() + 3600000).toISOString(),
979
+ };
980
+
981
+ this.sessions.set(sessionId, session);
982
+ this.accessTokens.set(accessToken, session);
983
+ this.refreshTokens.set(refreshToken, session);
984
+ this.persistSessions();
985
+
986
+ // 5. PostAuthentication — dispara após auth bem-sucedida (não bloqueia)
987
+ const postAuthEvent = this._buildTriggerEvent("PostAuthentication_Authentication", userPool, user, ClientId, {
988
+ userAttributes: this._triggerUserAttributes(user),
989
+ newDeviceUsed: false,
990
+ });
991
+ try {
992
+ await this._invokeTrigger(userPool, "PostAuthentication", postAuthEvent);
993
+ } catch (err) {
994
+ logger.error(`PostAuthentication trigger error (ignored): ${err.message}`);
995
+ }
996
+
997
+ logger.debug(`🔐 Usuário autenticado: ${username}`);
998
+ this.audit.record({
999
+ eventName: "InitiateAuth",
1000
+ readOnly: false,
1001
+ resources: [{ ARN: userPool.Arn, type: "AWS::Cognito::UserPool" }],
1002
+ requestParameters: { clientId: ClientId, authFlow: AuthFlow },
1003
+ });
1004
+
1005
+ return {
1006
+ AuthenticationResult: {
1007
+ AccessToken: accessToken,
1008
+ IdToken: idToken,
1009
+ RefreshToken: refreshToken,
1010
+ TokenType: "Bearer",
1011
+ ExpiresIn: 3600,
1012
+ },
1013
+ ChallengeName: null,
1014
+ Session: null,
1015
+ };
1016
+ }
1017
+
1018
+ getToken(params) {
1019
+ const { AuthFlow, ClientId, AuthParameters } = params;
1020
+
1021
+ if (AuthFlow === "REFRESH_TOKEN_AUTH") {
1022
+ const refreshToken = AuthParameters.REFRESH_TOKEN;
1023
+ const session = this.refreshTokens.get(refreshToken);
1024
+
1025
+ if (!session) {
1026
+ throw new Error("Invalid refresh token");
1027
+ }
1028
+
1029
+ const user = this.users.get(session.UserId);
1030
+ const userPool = this.userPools.get(session.UserPoolId);
1031
+
1032
+ if (!user || !userPool) {
1033
+ throw new Error("Invalid session");
1034
+ }
1035
+
1036
+ // Gera novos tokens
1037
+ const newAccessToken = this.generateAccessToken(user, userPool, session.ClientId);
1038
+ const newIdToken = this.generateIdToken(user, userPool, session.ClientId);
1039
+
1040
+ session.AccessToken = newAccessToken;
1041
+ session.IdToken = newIdToken;
1042
+ session.ExpiresAt = new Date(Date.now() + 3600000).toISOString();
1043
+
1044
+ this.accessTokens.set(newAccessToken, session);
1045
+ this.persistSessions();
1046
+
1047
+ return {
1048
+ AuthenticationResult: {
1049
+ AccessToken: newAccessToken,
1050
+ IdToken: newIdToken,
1051
+ TokenType: "Bearer",
1052
+ ExpiresIn: 3600,
1053
+ },
1054
+ };
1055
+ }
1056
+
1057
+ throw new Error(`Unsupported AuthFlow: ${AuthFlow}`);
1058
+ }
1059
+
1060
+ // ============ Token Management ============
1061
+
1062
+ generateAccessToken(user, userPool, clientId) {
1063
+ const payload = {
1064
+ sub: user.UserId,
1065
+ token_use: "access",
1066
+ client_id: clientId,
1067
+ username: user.Username,
1068
+ scope: "aws.cognito.signin.user.admin",
1069
+ iss: `https://cognito-idp.local/${userPool.Id}`,
1070
+ exp: Math.floor(Date.now() / 1000) + 3600,
1071
+ iat: Math.floor(Date.now() / 1000),
1072
+ };
1073
+
1074
+ return jwt.sign(payload, this.jwtSecret, { algorithm: "HS256" });
1075
+ }
1076
+
1077
+ generateIdToken(user, userPool, clientId, claimsOverride = null) {
1078
+ const payload = {
1079
+ sub: user.UserId,
1080
+ token_use: "id",
1081
+ client_id: clientId,
1082
+ email: user.Attributes.email,
1083
+ email_verified: user.Attributes.email_verified || true,
1084
+ username: user.Username,
1085
+ iss: `https://cognito-idp.local/${userPool.Id}`,
1086
+ exp: Math.floor(Date.now() / 1000) + 3600,
1087
+ iat: Math.floor(Date.now() / 1000),
1088
+ };
1089
+
1090
+ // Adiciona outros atributos do usuário
1091
+ for (const [key, value] of Object.entries(user.Attributes)) {
1092
+ if (key !== "email" && key !== "email_verified") {
1093
+ payload[key] = value;
1094
+ }
1095
+ }
1096
+
1097
+ // Apply PreTokenGeneration claims override
1098
+ if (claimsOverride !== null) {
1099
+ if (claimsOverride.claimsToAddOrOverride) {
1100
+ Object.assign(payload, claimsOverride.claimsToAddOrOverride);
1101
+ }
1102
+ if (Array.isArray(claimsOverride.claimsToSuppress)) {
1103
+ for (const key of claimsOverride.claimsToSuppress) {
1104
+ delete payload[key];
1105
+ }
1106
+ }
1107
+ }
1108
+
1109
+ return jwt.sign(payload, this.jwtSecret, { algorithm: "HS256" });
1110
+ }
1111
+
1112
+ generateRefreshToken(user, userPool, clientId) {
1113
+ const payload = {
1114
+ sub: user.UserId,
1115
+ token_use: "refresh",
1116
+ client_id: clientId,
1117
+ username: user.Username,
1118
+ iss: `https://cognito-idp.local/${userPool.Id}`,
1119
+ exp: Math.floor(Date.now() / 1000) + 2592000, // 30 dias
1120
+ iat: Math.floor(Date.now() / 1000),
1121
+ };
1122
+
1123
+ return jwt.sign(payload, this.jwtSecret, { algorithm: "HS256" });
1124
+ }
1125
+
1126
+ verifyAccessToken(token) {
1127
+ try {
1128
+ const decoded = jwt.verify(token, this.jwtSecret);
1129
+ const session = this.accessTokens.get(token);
1130
+
1131
+ if (!session || session.ExpiresAt < new Date().toISOString()) {
1132
+ return null;
1133
+ }
1134
+
1135
+ return decoded;
1136
+ } catch (error) {
1137
+ return null;
1138
+ }
1139
+ }
1140
+
1141
+ // ============ Admin Operations ============
1142
+
1143
+ adminGetUser(params) {
1144
+ const { UserPoolId, Username } = params;
1145
+ const userPool = this.userPools.get(UserPoolId);
1146
+
1147
+ if (!userPool) {
1148
+ throw new Error(`User pool ${UserPoolId} not found`);
1149
+ }
1150
+
1151
+ const user = this.findUserByUsername(Username, null, UserPoolId);
1152
+ if (!user) {
1153
+ throw new Error(`User not found: ${Username}`);
1154
+ }
1155
+
1156
+ return {
1157
+ Username: user.Username,
1158
+ UserAttributes: this.formatUserAttributes(user.Attributes),
1159
+ UserCreateDate: user.CreatedDate,
1160
+ UserLastModifiedDate: user.LastModifiedDate,
1161
+ Enabled: user.Enabled,
1162
+ UserStatus: user.UserStatus,
1163
+ MFAOptions: user.MfaOptions,
1164
+ PreferredMfaSetting: user.PreferredMfaSetting,
1165
+ UserMFASettingList: user.UserMFASettingList,
1166
+ };
1167
+ }
1168
+
1169
+ adminCreateUser(params) {
1170
+ const { UserPoolId, Username, UserAttributes, TemporaryPassword, DesiredDeliveryMediums } = params;
1171
+ const userPool = this.userPools.get(UserPoolId);
1172
+
1173
+ if (!userPool) {
1174
+ throw new Error(`User pool ${UserPoolId} not found`);
1175
+ }
1176
+
1177
+ const tempPassword = TemporaryPassword || this._generateTemporaryPassword();
1178
+
1179
+ const userId = uuidv4();
1180
+ const user = {
1181
+ Username: Username,
1182
+ UserPoolId: UserPoolId,
1183
+ UserId: userId,
1184
+ Attributes: this.normalizeUserAttributes(UserAttributes || []),
1185
+ Enabled: true,
1186
+ UserStatus: "FORCE_CHANGE_PASSWORD",
1187
+ CreatedDate: new Date().toISOString(),
1188
+ LastModifiedDate: new Date().toISOString(),
1189
+ Password: this.hashPassword(tempPassword),
1190
+ MfaOptions: [],
1191
+ PreferredMfaSetting: null,
1192
+ UserMFASettingList: [],
1193
+ };
1194
+
1195
+ // PreSignUp trigger — dispara antes de criar o usuário (admin context)
1196
+ const preSignUpEvent = this._buildTriggerEvent("PreSignUp_AdminCreateUser", userPool, user, "ADMIN", {
1197
+ userAttributes: this.normalizeUserAttributes(UserAttributes || []),
1198
+ validationData: {},
1199
+ clientMetadata: {},
1200
+ });
1201
+ // Fire and forget — AdminCreateUser PreSignUp errors are non-blocking in local sim
1202
+ this._invokeTrigger(userPool, "PreSignUp", preSignUpEvent).catch((err) => {
1203
+ logger.warn(`PreSignUp trigger error on AdminCreateUser (ignored): ${err.message}`);
1204
+ });
1205
+
1206
+ this.users.set(userId, user);
1207
+ userPool.Users.push(userId);
1208
+ userPool.EstimatedNumberOfUsers++;
1209
+ this.persistUsers();
1210
+ this.persistUserPools();
1211
+
1212
+ logger.info(`👤 Usuário criado: ${Username} | Senha temporária: ${tempPassword}`);
1213
+
1214
+ return {
1215
+ User: {
1216
+ Username: user.Username,
1217
+ UserAttributes: this.formatUserAttributes(user.Attributes),
1218
+ UserCreateDate: user.CreatedDate,
1219
+ UserLastModifiedDate: user.LastModifiedDate,
1220
+ Enabled: user.Enabled,
1221
+ UserStatus: user.UserStatus,
1222
+ TemporaryPassword: tempPassword,
1223
+ },
1224
+ };
1225
+ }
1226
+
1227
+ _generateTemporaryPassword() {
1228
+ const upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
1229
+ const lower = "abcdefghijklmnopqrstuvwxyz";
1230
+ const digits = "0123456789";
1231
+ const special = "!@#$%^&*";
1232
+ const all = upper + lower + digits + special;
1233
+ const rand = (set) => set[Math.floor(Math.random() * set.length)];
1234
+ const password = [rand(upper), rand(lower), rand(digits), rand(special)];
1235
+ for (let i = 4; i < 10; i++) password.push(rand(all));
1236
+ return password.sort(() => Math.random() - 0.5).join("");
1237
+ }
1238
+
1239
+ adminSetUserPassword(params) {
1240
+ const { UserPoolId, Username, Password, Permanent } = params;
1241
+ const user = this.findUserByUsername(Username, null, UserPoolId);
1242
+
1243
+ if (!user) {
1244
+ throw new Error(`User not found: ${Username}`);
1245
+ }
1246
+
1247
+ user.Password = this.hashPassword(Password);
1248
+ if (Permanent) {
1249
+ user.UserStatus = "CONFIRMED";
1250
+ }
1251
+ user.LastModifiedDate = new Date().toISOString();
1252
+ this.persistUsers();
1253
+
1254
+ return {};
1255
+ }
1256
+
1257
+ adminDeleteUser(params) {
1258
+ const { UserPoolId, Username } = params;
1259
+ const user = this.findUserByUsername(Username, null, UserPoolId);
1260
+
1261
+ if (!user) {
1262
+ throw new Error(`User not found: ${Username}`);
1263
+ }
1264
+
1265
+ const userPool = this.userPools.get(UserPoolId);
1266
+ if (userPool) {
1267
+ const index = userPool.Users.indexOf(user.UserId);
1268
+ if (index !== -1) {
1269
+ userPool.Users.splice(index, 1);
1270
+ userPool.EstimatedNumberOfUsers--;
1271
+ }
1272
+ }
1273
+
1274
+ this.users.delete(user.UserId);
1275
+ this.persistUsers();
1276
+ this.persistUserPools();
1277
+
1278
+ return {};
1279
+ }
1280
+
1281
+ // ============ Helper Methods ============
1282
+
1283
+ findUserPoolByClientId(clientId) {
1284
+ for (const userPool of this.userPools.values()) {
1285
+ if (userPool.Clients.has(clientId)) {
1286
+ return userPool;
1287
+ }
1288
+ }
1289
+ return null;
1290
+ }
1291
+
1292
+ findUserByUsername(username, clientId = null, userPoolId = null) {
1293
+ let targetUserPoolId = userPoolId;
1294
+
1295
+ if (clientId && !targetUserPoolId) {
1296
+ const userPool = this.findUserPoolByClientId(clientId);
1297
+ if (userPool) {
1298
+ targetUserPoolId = userPool.Id;
1299
+ }
1300
+ }
1301
+
1302
+ for (const user of this.users.values()) {
1303
+ if (user.UserPoolId !== targetUserPoolId) continue;
1304
+ // Match by Username or by email attribute (when UsernameAttributes includes 'email')
1305
+ if (user.Username === username) return user;
1306
+ if (user.Attributes?.email === username) return user;
1307
+ }
1308
+
1309
+ return null;
1310
+ }
1311
+
1312
+ normalizeUserAttributes(attributes) {
1313
+ const normalized = {};
1314
+ for (const attr of attributes) {
1315
+ normalized[attr.Name] = attr.Value;
1316
+ }
1317
+ return normalized;
1318
+ }
1319
+
1320
+ formatUserAttributes(attributes) {
1321
+ return Object.entries(attributes).map(([Name, Value]) => ({ Name, Value }));
1322
+ }
1323
+
1324
+ hashPassword(password) {
1325
+ // Simulação de hash (não usar em produção real)
1326
+ return crypto.createHash("sha256").update(password).digest("hex");
1327
+ }
1328
+
1329
+ verifyPassword(password, hash) {
1330
+ return this.hashPassword(password) === hash;
1331
+ }
1332
+
1333
+ // ============ Identity Pool Operations ============
1334
+
1335
+ createIdentityPool(params) {
1336
+ const { IdentityPoolName, AllowUnauthenticatedIdentities, SupportedLoginProviders, CognitoIdentityProviders } = params;
1337
+
1338
+ const identityPoolId = `local:${IdentityPoolName}_${Date.now()}`;
1339
+ const identityPool = {
1340
+ IdentityPoolId: identityPoolId,
1341
+ IdentityPoolName: IdentityPoolName,
1342
+ AllowUnauthenticatedIdentities: AllowUnauthenticatedIdentities || false,
1343
+ SupportedLoginProviders: SupportedLoginProviders || {},
1344
+ CognitoIdentityProviders: CognitoIdentityProviders || [],
1345
+ Identities: new Map(),
1346
+ };
1347
+
1348
+ this.identityPools.set(identityPoolId, identityPool);
1349
+ this.persistIdentityPools();
1350
+
1351
+ logger.debug(`✅ Identity Pool criado: ${IdentityPoolName} (${identityPoolId})`);
1352
+
1353
+ return {
1354
+ IdentityPoolId: identityPoolId,
1355
+ IdentityPoolName: identityPoolName,
1356
+ AllowUnauthenticatedIdentities: identityPool.AllowUnauthenticatedIdentities,
1357
+ };
1358
+ }
1359
+
1360
+ getId(params) {
1361
+ const { IdentityPoolId, Logins } = params;
1362
+ const identityPool = this.identityPools.get(IdentityPoolId);
1363
+
1364
+ if (!identityPool) {
1365
+ throw new Error(`Identity pool ${IdentityPoolId} not found`);
1366
+ }
1367
+
1368
+ let identityId = null;
1369
+
1370
+ if (Logins) {
1371
+ // Procura identidade existente com os logins fornecidos
1372
+ for (const [id, identity] of identityPool.Identities) {
1373
+ if (identity.Logins && this.matchesLogins(identity.Logins, Logins)) {
1374
+ identityId = id;
1375
+ break;
1376
+ }
1377
+ }
1378
+ }
1379
+
1380
+ if (!identityId) {
1381
+ identityId = uuidv4();
1382
+ identityPool.Identities.set(identityId, {
1383
+ IdentityId: identityId,
1384
+ Logins: Logins || {},
1385
+ CreationDate: new Date().toISOString(),
1386
+ LastModifiedDate: new Date().toISOString(),
1387
+ });
1388
+ this.persistIdentityPools();
1389
+ }
1390
+
1391
+ return {
1392
+ IdentityId: identityId,
1393
+ };
1394
+ }
1395
+
1396
+ getCredentialsForIdentity(params) {
1397
+ const { IdentityId, Logins } = params;
1398
+ const identityPool = this.findIdentityPoolByIdentityId(IdentityId);
1399
+
1400
+ if (!identityPool) {
1401
+ throw new Error(`Identity ${IdentityId} not found`);
1402
+ }
1403
+
1404
+ const identity = identityPool.Identities.get(IdentityId);
1405
+ if (!identity) {
1406
+ throw new Error(`Identity ${IdentityId} not found in pool`);
1407
+ }
1408
+
1409
+ // Gera credenciais temporárias (simuladas)
1410
+ const credentials = {
1411
+ AccessKeyId: `AKIA${crypto.randomBytes(16).toString("hex").toUpperCase()}`,
1412
+ SecretKey: crypto.randomBytes(32).toString("hex"),
1413
+ SessionToken: crypto.randomBytes(64).toString("base64"),
1414
+ Expiration: new Date(Date.now() + 3600000).toISOString(),
1415
+ };
1416
+
1417
+ return {
1418
+ Credentials: credentials,
1419
+ IdentityId: IdentityId,
1420
+ };
1421
+ }
1422
+
1423
+ findIdentityPoolByIdentityId(identityId) {
1424
+ for (const pool of this.identityPools.values()) {
1425
+ if (pool.Identities.has(identityId)) {
1426
+ return pool;
1427
+ }
1428
+ }
1429
+ return null;
1430
+ }
1431
+
1432
+ matchesLogins(existingLogins, newLogins) {
1433
+ const existingKeys = Object.keys(existingLogins);
1434
+ const newKeys = Object.keys(newLogins);
1435
+
1436
+ if (existingKeys.length !== newKeys.length) return false;
1437
+
1438
+ for (const key of existingKeys) {
1439
+ if (existingLogins[key] !== newLogins[key]) {
1440
+ return false;
1441
+ }
1442
+ }
1443
+
1444
+ return true;
1445
+ }
1446
+
1447
+ // ============ Persistence ============
1448
+
1449
+ loadUserPools() {
1450
+ // Load persisted pools first
1451
+ const saved = this.store.read("__userpools__");
1452
+ if (saved) {
1453
+ for (const [id, data] of Object.entries(saved)) {
1454
+ data.Clients = new Map(Object.entries(data.Clients || {}));
1455
+ data.Groups = new Map(Object.entries(data.Groups || {}));
1456
+ data.IdentityProviders = new Map(Object.entries(data.IdentityProviders || {}));
1457
+ data.ResourceServers = new Map(Object.entries(data.ResourceServers || {}));
1458
+ this.userPools.set(id, data);
1459
+ }
1460
+ }
1461
+
1462
+ // Create user pools from config if not already persisted
1463
+ if (this.config.cognito?.userPools) {
1464
+ let configChanged = false;
1465
+ const configPath = this.config._configPath;
1466
+
1467
+ for (let i = 0; i < this.config.cognito.userPools.length; i++) {
1468
+ const poolConfig = this.config.cognito.userPools[i];
1469
+ const existing = Array.from(this.userPools.values()).find((p) => p.Name === poolConfig.PoolName);
1470
+
1471
+ if (!existing) {
1472
+ const result = this.createUserPool(poolConfig);
1473
+ const poolId = result.UserPool.Id;
1474
+ logger.debug(`✅ User Pool criado a partir da config: ${poolConfig.PoolName} (${poolId})`);
1475
+
1476
+ // Copy LambdaTriggers from config onto the pool object
1477
+ const pool = this.userPools.get(poolId);
1478
+ pool.LambdaTriggers = poolConfig.LambdaTriggers || {};
1479
+
1480
+ // Auto-create a default client if not specified
1481
+ if (!poolConfig.ClientId) {
1482
+ const clientResult = this.createUserPoolClient({
1483
+ UserPoolId: poolId,
1484
+ ClientName: `${poolConfig.PoolName}-client`,
1485
+ GenerateSecret: false,
1486
+ });
1487
+ const clientId = clientResult.UserPoolClient.ClientId;
1488
+ this.config.cognito.userPools[i].ClientId = clientId;
1489
+ this.config.cognito.userPools[i].UserPoolId = poolId;
1490
+ configChanged = true;
1491
+ logger.debug(`✅ Client criado automaticamente: ${clientId}`);
1492
+ }
1493
+ } else {
1494
+ // Pool already exists — apply LambdaTriggers from config
1495
+ existing.LambdaTriggers = poolConfig.LambdaTriggers || {};
1496
+
1497
+ if (!poolConfig.ClientId) {
1498
+ // Pool exists but no clientId in config — write it back
1499
+ const firstClient = existing.Clients.size > 0 ? existing.Clients.values().next().value : null;
1500
+ if (firstClient) {
1501
+ this.config.cognito.userPools[i].ClientId = firstClient.ClientId;
1502
+ this.config.cognito.userPools[i].UserPoolId = existing.Id;
1503
+ configChanged = true;
1504
+ }
1505
+ } else if (!existing.Clients.has(poolConfig.ClientId)) {
1506
+ // ClientId is in config but not in the pool's Clients map — register it
1507
+ existing.Clients.set(poolConfig.ClientId, {
1508
+ ClientId: poolConfig.ClientId,
1509
+ ClientName: `${poolConfig.PoolName}-client`,
1510
+ ClientSecret: null,
1511
+ UserPoolId: existing.Id,
1512
+ RefreshTokenValidity: 30,
1513
+ AccessTokenValidity: 1,
1514
+ IdTokenValidity: 1,
1515
+ AllowedOAuthFlows: ['code'],
1516
+ AllowedOAuthScopes: ['openid', 'email', 'profile'],
1517
+ CallbackURLs: [],
1518
+ LogoutURLs: [],
1519
+ CreatedDate: new Date().toISOString(),
1520
+ LastModifiedDate: new Date().toISOString(),
1521
+ });
1522
+ this.persistUserPools();
1523
+ logger.debug(`✅ ClientId ${poolConfig.ClientId} registrado no pool ${existing.Id}`);
1524
+ }
1525
+ }
1526
+ }
1527
+
1528
+ // Write clientId back to aws-local-simulator.json
1529
+ if (configChanged && configPath) {
1530
+ try {
1531
+ const fs = require("fs");
1532
+ const fileContent = JSON.parse(fs.readFileSync(configPath, "utf8"));
1533
+ fileContent.cognito = fileContent.cognito || {};
1534
+ fileContent.cognito.userPools = this.config.cognito.userPools.map((p) => ({
1535
+ PoolName: p.PoolName,
1536
+ AutoVerifiedAttributes: p.AutoVerifiedAttributes,
1537
+ UserPoolId: p.UserPoolId,
1538
+ ClientId: p.ClientId,
1539
+ }));
1540
+ fs.writeFileSync(configPath, JSON.stringify(fileContent, null, 2));
1541
+ logger.info(`✅ ClientId gravado em: ${configPath}`);
1542
+ } catch (err) {
1543
+ logger.warn(`⚠️ Não foi possível gravar clientId no config: ${err.message}`);
1544
+ }
1545
+ }
1546
+ }
1547
+ }
1548
+
1549
+ loadIdentityPools() {
1550
+ const saved = this.store.read("__identitypools__");
1551
+ if (saved) {
1552
+ for (const [id, data] of Object.entries(saved)) {
1553
+ data.Identities = new Map(Object.entries(data.Identities || {}));
1554
+ this.identityPools.set(id, data);
1555
+ }
1556
+ }
1557
+ }
1558
+
1559
+ loadUsers() {
1560
+ const saved = this.store.read("__users__");
1561
+ if (saved) {
1562
+ for (const [id, user] of Object.entries(saved)) {
1563
+ this.users.set(id, user);
1564
+ }
1565
+ }
1566
+ }
1567
+
1568
+ loadSessions() {
1569
+ const saved = this.store.read("__sessions__");
1570
+ if (saved) {
1571
+ for (const [id, session] of Object.entries(saved)) {
1572
+ this.sessions.set(id, session);
1573
+ this.accessTokens.set(session.AccessToken, session);
1574
+ this.refreshTokens.set(session.RefreshToken, session);
1575
+ }
1576
+ }
1577
+ }
1578
+
1579
+ persistUserPools() {
1580
+ const poolsObj = {};
1581
+ for (const [id, pool] of this.userPools.entries()) {
1582
+ poolsObj[id] = {
1583
+ ...pool,
1584
+ Clients: Object.fromEntries(pool.Clients),
1585
+ Groups: Object.fromEntries(pool.Groups),
1586
+ IdentityProviders: Object.fromEntries(pool.IdentityProviders),
1587
+ ResourceServers: Object.fromEntries(pool.ResourceServers),
1588
+ };
1589
+ }
1590
+ this.store.write("__userpools__", poolsObj);
1591
+ }
1592
+
1593
+ persistIdentityPools() {
1594
+ const poolsObj = {};
1595
+ for (const [id, pool] of this.identityPools.entries()) {
1596
+ poolsObj[id] = {
1597
+ ...pool,
1598
+ Identities: Object.fromEntries(pool.Identities),
1599
+ };
1600
+ }
1601
+ this.store.write("__identitypools__", poolsObj);
1602
+ }
1603
+
1604
+ persistUsers() {
1605
+ const usersObj = {};
1606
+ for (const [id, user] of this.users.entries()) {
1607
+ usersObj[id] = user;
1608
+ }
1609
+ this.store.write("__users__", usersObj);
1610
+ }
1611
+
1612
+ persistSessions() {
1613
+ const sessionsObj = {};
1614
+ for (const [id, session] of this.sessions.entries()) {
1615
+ sessionsObj[id] = session;
1616
+ }
1617
+ this.store.write("__sessions__", sessionsObj);
1618
+ }
1619
+
1620
+ async reset() {
1621
+ this.userPools.clear();
1622
+ this.identityPools.clear();
1623
+ this.users.clear();
1624
+ this.sessions.clear();
1625
+ this.accessTokens.clear();
1626
+ this.refreshTokens.clear();
1627
+
1628
+ this.persistUserPools();
1629
+ this.persistIdentityPools();
1630
+ this.persistUsers();
1631
+ this.persistSessions();
1632
+
1633
+ logger.debug("Cognito: Todos os dados resetados");
1634
+ }
1635
+
1636
+ // ============ Stats ============
1637
+
1638
+ getUserPoolsCount() {
1639
+ return this.userPools.size;
1640
+ }
1641
+
1642
+ getTotalUsersCount() {
1643
+ return this.users.size;
1644
+ }
1645
+
1646
+ getIdentityPoolsCount() {
1647
+ return this.identityPools.size;
1648
+ }
1649
+
1650
+ getActiveSessionsCount() {
1651
+ return this.sessions.size;
1652
+ }
1653
+ }
1654
+
1655
+ module.exports = CognitoSimulator;