@gugananuvem/aws-local-simulator 1.0.33 → 1.0.34

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