@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.
- package/README.md +834 -834
- package/aws-config +153 -153
- package/bin/aws-local-simulator.js +63 -63
- package/package.json +3 -2
- package/src/config/config-loader.js +114 -114
- package/src/config/default-config.js +79 -79
- package/src/config/env-loader.js +68 -68
- package/src/index.js +146 -146
- package/src/index.mjs +123 -123
- package/src/server.js +463 -463
- package/src/services/apigateway/index.js +75 -75
- package/src/services/apigateway/server.js +607 -607
- package/src/services/apigateway/simulator.js +1405 -1405
- package/src/services/athena/index.js +75 -75
- package/src/services/athena/server.js +101 -101
- package/src/services/athena/simulador.js +998 -998
- package/src/services/athena/simulator.js +346 -346
- package/src/services/cloudformation/index.js +106 -106
- package/src/services/cloudformation/server.js +417 -417
- package/src/services/cloudformation/simulador.js +1020 -1020
- package/src/services/cloudtrail/index.js +84 -84
- package/src/services/cloudtrail/server.js +235 -235
- package/src/services/cloudtrail/simulador.js +719 -719
- package/src/services/cloudwatch/index.js +84 -84
- package/src/services/cloudwatch/server.js +366 -366
- package/src/services/cloudwatch/simulador.js +1173 -1173
- package/src/services/cognito/index.js +79 -79
- package/src/services/cognito/server.js +297 -297
- package/src/services/cognito/simulator.js +1992 -1761
- package/src/services/config/index.js +96 -96
- package/src/services/config/server.js +215 -215
- package/src/services/config/simulador.js +1260 -1260
- package/src/services/dynamodb/index.js +74 -74
- package/src/services/dynamodb/server.js +139 -139
- package/src/services/dynamodb/simulator.js +1005 -994
- package/src/services/dynamodb/sqlite-store.js +722 -0
- package/src/services/ecs/index.js +65 -65
- package/src/services/ecs/server.js +235 -235
- package/src/services/ecs/simulator.js +844 -844
- package/src/services/eventbridge/index.js +89 -89
- package/src/services/eventbridge/server.js +209 -209
- package/src/services/eventbridge/simulator.js +684 -684
- package/src/services/index.js +45 -45
- package/src/services/kms/index.js +75 -75
- package/src/services/kms/server.js +81 -81
- package/src/services/kms/simulator.js +344 -344
- package/src/services/lambda/handler-loader.js +183 -183
- package/src/services/lambda/index.js +81 -81
- package/src/services/lambda/route-registry.js +274 -274
- package/src/services/lambda/server.js +191 -191
- package/src/services/lambda/simulator.js +364 -364
- package/src/services/parameter-store/index.js +80 -80
- package/src/services/parameter-store/server.js +50 -50
- package/src/services/parameter-store/simulator.js +201 -201
- package/src/services/s3/index.js +73 -73
- package/src/services/s3/server.js +350 -350
- package/src/services/s3/simulator.js +568 -568
- package/src/services/secret-manager/index.js +80 -80
- package/src/services/secret-manager/server.js +51 -51
- package/src/services/secret-manager/simulator.js +182 -182
- package/src/services/sns/index.js +89 -89
- package/src/services/sns/server.js +607 -607
- package/src/services/sns/simulator.js +1482 -1482
- package/src/services/sqs/index.js +98 -98
- package/src/services/sqs/server.js +360 -360
- package/src/services/sqs/simulator.js +509 -509
- package/src/services/sts/index.js +37 -37
- package/src/services/sts/server.js +144 -144
- package/src/services/sts/simulator.js +69 -69
- package/src/services/xray/index.js +83 -83
- package/src/services/xray/server.js +308 -308
- package/src/services/xray/simulador.js +994 -994
- package/src/template/aws-config-template.js +87 -87
- package/src/template/aws-config-template.mjs +90 -90
- package/src/template/config-template.json +203 -203
- package/src/utils/aws-config.js +91 -91
- package/src/utils/cloudtrail-audit.js +129 -129
- package/src/utils/local-store.js +83 -83
- 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
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
})
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
if (
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
if (!
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
if (
|
|
361
|
-
const err = new Error(
|
|
362
|
-
err.code = "
|
|
363
|
-
throw err;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
const
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
const
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
const
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
this.
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
this.
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
}
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
const
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
const
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
user
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
return {};
|
|
883
|
-
}
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
const {
|
|
887
|
-
const
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
}
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
const
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
this.
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
if (
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
const
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
//
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
});
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
const
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
UserPoolId:
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
});
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
const
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
}
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
};
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
const
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
}
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
}
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
const
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
const
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
const
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
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;
|