@gugananuvem/aws-local-simulator 1.0.15 → 1.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/README.md +789 -594
  2. package/bin/aws-local-simulator.js +63 -63
  3. package/package.json +2 -2
  4. package/src/config/config-loader.js +114 -114
  5. package/src/config/default-config.js +68 -68
  6. package/src/config/env-loader.js +68 -68
  7. package/src/index.js +146 -146
  8. package/src/index.mjs +123 -123
  9. package/src/server.js +227 -227
  10. package/src/services/apigateway/index.js +75 -73
  11. package/src/services/apigateway/server.js +570 -507
  12. package/src/services/apigateway/simulator.js +1261 -1261
  13. package/src/services/athena/index.js +75 -75
  14. package/src/services/athena/server.js +101 -101
  15. package/src/services/athena/simulador.js +998 -998
  16. package/src/services/athena/simulator.js +346 -346
  17. package/src/services/cloudformation/index.js +106 -106
  18. package/src/services/cloudformation/server.js +417 -417
  19. package/src/services/cloudformation/simulador.js +1045 -1045
  20. package/src/services/cloudtrail/index.js +84 -84
  21. package/src/services/cloudtrail/server.js +235 -235
  22. package/src/services/cloudtrail/simulador.js +719 -719
  23. package/src/services/cloudwatch/index.js +84 -84
  24. package/src/services/cloudwatch/server.js +366 -366
  25. package/src/services/cloudwatch/simulador.js +1173 -1173
  26. package/src/services/cognito/index.js +79 -79
  27. package/src/services/cognito/server.js +301 -301
  28. package/src/services/cognito/simulator.js +1655 -1655
  29. package/src/services/config/index.js +96 -96
  30. package/src/services/config/server.js +215 -215
  31. package/src/services/config/simulador.js +1260 -1260
  32. package/src/services/dynamodb/index.js +74 -74
  33. package/src/services/dynamodb/server.js +125 -125
  34. package/src/services/dynamodb/simulator.js +630 -630
  35. package/src/services/ecs/index.js +65 -65
  36. package/src/services/ecs/server.js +235 -235
  37. package/src/services/ecs/simulator.js +844 -844
  38. package/src/services/eventbridge/index.js +89 -89
  39. package/src/services/eventbridge/server.js +209 -209
  40. package/src/services/eventbridge/simulator.js +684 -684
  41. package/src/services/index.js +45 -45
  42. package/src/services/kms/index.js +75 -75
  43. package/src/services/kms/server.js +67 -67
  44. package/src/services/kms/simulator.js +324 -324
  45. package/src/services/lambda/handler-loader.js +183 -183
  46. package/src/services/lambda/index.js +78 -78
  47. package/src/services/lambda/route-registry.js +274 -274
  48. package/src/services/lambda/server.js +145 -145
  49. package/src/services/lambda/simulator.js +199 -199
  50. package/src/services/parameter-store/index.js +80 -80
  51. package/src/services/parameter-store/server.js +50 -50
  52. package/src/services/parameter-store/simulator.js +201 -201
  53. package/src/services/s3/index.js +73 -73
  54. package/src/services/s3/server.js +329 -329
  55. package/src/services/s3/simulator.js +565 -565
  56. package/src/services/secret-manager/index.js +80 -80
  57. package/src/services/secret-manager/server.js +50 -50
  58. package/src/services/secret-manager/simulator.js +171 -171
  59. package/src/services/sns/index.js +89 -89
  60. package/src/services/sns/server.js +580 -580
  61. package/src/services/sns/simulator.js +1482 -1482
  62. package/src/services/sqs/index.js +98 -93
  63. package/src/services/sqs/server.js +349 -349
  64. package/src/services/sqs/simulator.js +441 -441
  65. package/src/services/sts/index.js +37 -37
  66. package/src/services/sts/server.js +144 -144
  67. package/src/services/sts/simulator.js +69 -69
  68. package/src/services/xray/index.js +83 -83
  69. package/src/services/xray/server.js +308 -308
  70. package/src/services/xray/simulador.js +994 -994
  71. package/src/template/aws-config-template.js +87 -87
  72. package/src/template/aws-config-template.mjs +90 -90
  73. package/src/template/config-template.json +203 -203
  74. package/src/utils/aws-config.js +91 -91
  75. package/src/utils/cloudtrail-audit.js +129 -129
  76. package/src/utils/local-store.js +83 -83
  77. package/src/utils/logger.js +59 -59
@@ -1,1655 +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
- 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;
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;