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