agentlang 0.0.2 → 0.0.4
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 +16 -47
- package/out/api/http.d.ts.map +1 -1
- package/out/api/http.js +16 -2
- package/out/api/http.js.map +1 -1
- package/out/cli/main.d.ts +2 -269
- package/out/cli/main.d.ts.map +1 -1
- package/out/cli/main.js +6 -12
- package/out/cli/main.js.map +1 -1
- package/out/index.d.ts +19 -0
- package/out/index.d.ts.map +1 -0
- package/out/index.js +25 -0
- package/out/index.js.map +1 -0
- package/out/language/agentlang-validator.d.ts +2 -2
- package/out/language/agentlang-validator.d.ts.map +1 -1
- package/out/language/generated/ast.d.ts +274 -70
- package/out/language/generated/ast.d.ts.map +1 -1
- package/out/language/generated/ast.js +119 -26
- package/out/language/generated/ast.js.map +1 -1
- package/out/language/generated/grammar.d.ts +1 -1
- package/out/language/generated/grammar.d.ts.map +1 -1
- package/out/language/generated/grammar.js +831 -238
- package/out/language/generated/grammar.js.map +1 -1
- package/out/language/generated/module.d.ts +1 -1
- package/out/language/generated/module.js +1 -1
- package/out/language/main.cjs +995 -331
- package/out/language/main.cjs.map +2 -2
- package/out/language/parser.d.ts +3 -2
- package/out/language/parser.d.ts.map +1 -1
- package/out/language/parser.js +13 -6
- package/out/language/parser.js.map +1 -1
- package/out/language/syntax.d.ts +5 -1
- package/out/language/syntax.d.ts.map +1 -1
- package/out/runtime/agents/common.d.ts +1 -1
- package/out/runtime/agents/common.d.ts.map +1 -1
- package/out/runtime/agents/common.js +1 -1
- package/out/runtime/auth/cognito.d.ts +4 -1
- package/out/runtime/auth/cognito.d.ts.map +1 -1
- package/out/runtime/auth/cognito.js +540 -73
- package/out/runtime/auth/cognito.js.map +1 -1
- package/out/runtime/auth/defs.d.ts +4 -1
- package/out/runtime/auth/defs.d.ts.map +1 -1
- package/out/runtime/auth/defs.js +17 -1
- package/out/runtime/auth/defs.js.map +1 -1
- package/out/runtime/auth/interface.d.ts +6 -1
- package/out/runtime/auth/interface.d.ts.map +1 -1
- package/out/runtime/defs.d.ts +37 -0
- package/out/runtime/defs.d.ts.map +1 -0
- package/out/runtime/defs.js +35 -0
- package/out/runtime/defs.js.map +1 -1
- package/out/runtime/interpreter.d.ts +6 -1
- package/out/runtime/interpreter.d.ts.map +1 -1
- package/out/runtime/interpreter.js +45 -36
- package/out/runtime/interpreter.js.map +1 -1
- package/out/runtime/loader.d.ts +16 -7
- package/out/runtime/loader.d.ts.map +1 -1
- package/out/runtime/loader.js +148 -29
- package/out/runtime/loader.js.map +1 -1
- package/out/runtime/module.d.ts +88 -17
- package/out/runtime/module.d.ts.map +1 -1
- package/out/runtime/module.js +200 -9
- package/out/runtime/module.js.map +1 -1
- package/out/runtime/modules/ai.d.ts +8 -4
- package/out/runtime/modules/ai.d.ts.map +1 -1
- package/out/runtime/modules/ai.js +50 -24
- package/out/runtime/modules/ai.js.map +1 -1
- package/out/runtime/modules/auth.d.ts +19 -10
- package/out/runtime/modules/auth.d.ts.map +1 -1
- package/out/runtime/modules/auth.js +282 -30
- package/out/runtime/modules/auth.js.map +1 -1
- package/out/runtime/modules/core.d.ts +8 -0
- package/out/runtime/modules/core.d.ts.map +1 -1
- package/out/runtime/modules/core.js +3 -1
- package/out/runtime/modules/core.js.map +1 -1
- package/out/runtime/relgraph.d.ts.map +1 -1
- package/out/runtime/relgraph.js +2 -2
- package/out/runtime/relgraph.js.map +1 -1
- package/out/runtime/resolvers/interface.d.ts +45 -2
- package/out/runtime/resolvers/interface.d.ts.map +1 -1
- package/out/runtime/resolvers/interface.js +103 -5
- package/out/runtime/resolvers/interface.js.map +1 -1
- package/out/runtime/resolvers/registry.d.ts +3 -2
- package/out/runtime/resolvers/registry.d.ts.map +1 -1
- package/out/runtime/resolvers/registry.js +3 -0
- package/out/runtime/resolvers/registry.js.map +1 -1
- package/out/runtime/resolvers/sqldb/database.d.ts +30 -4
- package/out/runtime/resolvers/sqldb/database.d.ts.map +1 -1
- package/out/runtime/resolvers/sqldb/dbutil.d.ts +8 -1
- package/out/runtime/resolvers/sqldb/dbutil.d.ts.map +1 -1
- package/out/runtime/resolvers/sqldb/impl.d.ts +5 -1
- package/out/runtime/resolvers/sqldb/impl.d.ts.map +1 -1
- package/out/runtime/state.d.ts +295 -0
- package/out/runtime/state.d.ts.map +1 -0
- package/out/runtime/state.js +11 -1
- package/out/runtime/state.js.map +1 -1
- package/out/runtime/util.d.ts +26 -2
- package/out/runtime/util.d.ts.map +1 -1
- package/out/runtime/util.js +16 -0
- package/out/runtime/util.js.map +1 -1
- package/out/syntaxes/agentlang.monarch.js +2 -2
- package/out/syntaxes/agentlang.monarch.js.map +1 -1
- package/out/utils/http.d.ts +2 -0
- package/out/utils/http.d.ts.map +1 -0
- package/out/utils/http.js +5 -0
- package/out/utils/http.js.map +1 -0
- package/package.json +9 -5
- package/src/api/http.ts +15 -0
- package/src/cli/main.ts +6 -12
- package/src/language/agentlang.langium +31 -10
- package/src/language/generated/ast.ts +212 -44
- package/src/language/generated/grammar.ts +831 -238
- package/src/language/generated/module.ts +1 -1
- package/src/language/parser.ts +12 -8
- package/src/runtime/agents/common.ts +1 -1
- package/src/runtime/auth/cognito.ts +605 -74
- package/src/runtime/auth/defs.ts +17 -1
- package/src/runtime/auth/interface.ts +6 -1
- package/src/runtime/defs.ts +45 -0
- package/src/runtime/interpreter.ts +43 -34
- package/src/runtime/loader.ts +159 -30
- package/src/runtime/module.ts +243 -10
- package/src/runtime/modules/ai.ts +52 -28
- package/src/runtime/modules/auth.ts +330 -38
- package/src/runtime/modules/core.ts +3 -1
- package/src/runtime/relgraph.ts +2 -8
- package/src/runtime/resolvers/interface.ts +141 -6
- package/src/runtime/resolvers/registry.ts +5 -2
- package/src/runtime/state.ts +11 -1
- package/src/runtime/util.ts +17 -0
- package/src/syntaxes/agentlang.monarch.ts +2 -2
- package/src/utils/http.ts +5 -0
|
@@ -3,10 +3,11 @@ import { logger } from '../logger.js';
|
|
|
3
3
|
import { sleepMilliseconds } from '../util.js';
|
|
4
4
|
import { CognitoJwtVerifier } from 'aws-jwt-verify';
|
|
5
5
|
import { isNodeEnv } from '../../utils/runtime.js';
|
|
6
|
-
import { UnauthorisedError } from '../defs.js';
|
|
6
|
+
import { UnauthorisedError, UserNotFoundError, UserNotConfirmedError, PasswordResetRequiredError, TooManyRequestsError, InvalidParameterError, ExpiredCodeError, CodeMismatchError, BadRequestError, } from '../defs.js';
|
|
7
7
|
let fromEnv = undefined;
|
|
8
8
|
let CognitoIdentityProviderClient = undefined;
|
|
9
9
|
let SignUpCommand = undefined;
|
|
10
|
+
let AdminGetUserCommand = undefined;
|
|
10
11
|
let AuthenticationDetails = undefined;
|
|
11
12
|
let CognitoUser = undefined;
|
|
12
13
|
let CognitoUserPool = undefined;
|
|
@@ -16,6 +17,7 @@ if (isNodeEnv) {
|
|
|
16
17
|
const cip = await import('@aws-sdk/client-cognito-identity-provider');
|
|
17
18
|
CognitoIdentityProviderClient = cip.CognitoIdentityProviderClient;
|
|
18
19
|
SignUpCommand = cip.SignUpCommand;
|
|
20
|
+
AdminGetUserCommand = cip.AdminGetUserCommand;
|
|
19
21
|
const ci = await import('amazon-cognito-identity-js');
|
|
20
22
|
AuthenticationDetails = ci.AuthenticationDetails;
|
|
21
23
|
CognitoUser = ci.CognitoUser;
|
|
@@ -26,6 +28,189 @@ const defaultConfig = isNodeEnv
|
|
|
26
28
|
.set('UserPoolId', process.env.COGNITO_USER_POOL_ID)
|
|
27
29
|
.set('ClientId', process.env.COGNITO_CLIENT_ID)
|
|
28
30
|
: new Map();
|
|
31
|
+
// Helper function to parse Cognito error and throw appropriate custom error
|
|
32
|
+
function handleCognitoError(err, context) {
|
|
33
|
+
var _a;
|
|
34
|
+
// Log error details for debugging (sanitize sensitive information)
|
|
35
|
+
const sanitizedMessage = sanitizeErrorMessage(err.message || '');
|
|
36
|
+
logger.error(`Cognito error in ${context}: ${err.name} - ${sanitizedMessage}`, {
|
|
37
|
+
errorName: err.name,
|
|
38
|
+
errorCode: err.code,
|
|
39
|
+
context: context,
|
|
40
|
+
statusCode: (_a = err.$metadata) === null || _a === void 0 ? void 0 : _a.httpStatusCode,
|
|
41
|
+
});
|
|
42
|
+
// Handle specific Cognito errors with user-friendly messages
|
|
43
|
+
switch (err.name) {
|
|
44
|
+
case 'UserNotFoundException':
|
|
45
|
+
logger.debug(`User not found in context: ${context}`);
|
|
46
|
+
throw new UserNotFoundError('User account not found. Please check your email or sign up.');
|
|
47
|
+
case 'NotAuthorizedException':
|
|
48
|
+
// Check if this is a password-related error vs other auth issues
|
|
49
|
+
if (err.message && err.message.includes('password')) {
|
|
50
|
+
logger.debug(`Invalid password attempt in context: ${context}`);
|
|
51
|
+
throw new UnauthorisedError('Invalid password. Please try again.');
|
|
52
|
+
}
|
|
53
|
+
else if (err.message && err.message.includes('not confirmed')) {
|
|
54
|
+
logger.debug(`User not confirmed in context: ${context}`);
|
|
55
|
+
throw new UserNotConfirmedError();
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
logger.debug(`Authentication failed in context: ${context}`);
|
|
59
|
+
throw new UnauthorisedError('Authentication failed. Please check your credentials.');
|
|
60
|
+
}
|
|
61
|
+
case 'UserNotConfirmedException':
|
|
62
|
+
logger.debug(`User not confirmed in context: ${context}`);
|
|
63
|
+
throw new UserNotConfirmedError();
|
|
64
|
+
case 'PasswordResetRequiredException':
|
|
65
|
+
logger.debug(`Password reset required in context: ${context}`);
|
|
66
|
+
throw new PasswordResetRequiredError();
|
|
67
|
+
case 'TooManyRequestsException':
|
|
68
|
+
logger.warn(`Rate limit exceeded in context: ${context}`);
|
|
69
|
+
throw new TooManyRequestsError();
|
|
70
|
+
case 'TooManyFailedAttemptsException':
|
|
71
|
+
logger.warn(`Too many failed attempts in context: ${context}`);
|
|
72
|
+
throw new TooManyRequestsError('Too many failed login attempts. Please try again later.');
|
|
73
|
+
case 'InvalidParameterException':
|
|
74
|
+
logger.debug(`Invalid parameters in context: ${context}`);
|
|
75
|
+
throw new InvalidParameterError(sanitizeErrorMessage(err.message) || 'Invalid parameters provided');
|
|
76
|
+
case 'ExpiredCodeException':
|
|
77
|
+
logger.debug(`Expired code in context: ${context}`);
|
|
78
|
+
throw new ExpiredCodeError();
|
|
79
|
+
case 'CodeMismatchException':
|
|
80
|
+
logger.debug(`Code mismatch in context: ${context}`);
|
|
81
|
+
throw new CodeMismatchError();
|
|
82
|
+
case 'UsernameExistsException':
|
|
83
|
+
logger.debug(`Username exists in context: ${context}`);
|
|
84
|
+
throw new BadRequestError('An account with this email already exists.');
|
|
85
|
+
case 'InvalidPasswordException':
|
|
86
|
+
logger.debug(`Invalid password format in context: ${context}`);
|
|
87
|
+
throw new BadRequestError('Password does not meet requirements. It must be at least 8 characters long and contain uppercase, lowercase, numbers, and special characters.');
|
|
88
|
+
case 'LimitExceededException':
|
|
89
|
+
logger.warn(`Service limit exceeded in context: ${context}`);
|
|
90
|
+
throw new TooManyRequestsError('Service limit exceeded. Please try again later.');
|
|
91
|
+
case 'InternalErrorException':
|
|
92
|
+
logger.error(`Internal Cognito error in context: ${context}`);
|
|
93
|
+
throw new Error('Authentication service is temporarily unavailable. Please try again later.');
|
|
94
|
+
case 'ResourceNotFoundException':
|
|
95
|
+
logger.error(`Resource not found in context: ${context}`);
|
|
96
|
+
throw new Error('Authentication service configuration error. Please contact support.');
|
|
97
|
+
case 'AliasExistsException':
|
|
98
|
+
logger.debug(`Alias exists in context: ${context}`);
|
|
99
|
+
throw new BadRequestError('An account with this email already exists.');
|
|
100
|
+
case 'InvalidEmailRoleAccessPolicyException':
|
|
101
|
+
logger.error(`Invalid email role access policy in context: ${context}`);
|
|
102
|
+
throw new Error('Email service configuration error. Please contact support.');
|
|
103
|
+
case 'UserLambdaValidationException':
|
|
104
|
+
logger.error(`User lambda validation error in context: ${context}`);
|
|
105
|
+
throw new BadRequestError('User validation failed. Please check your input and try again.');
|
|
106
|
+
case 'UnsupportedUserStateException':
|
|
107
|
+
logger.debug(`Unsupported user state in context: ${context}`);
|
|
108
|
+
throw new UserNotConfirmedError('User account is in an unsupported state. Please contact support.');
|
|
109
|
+
case 'MFAMethodNotFoundException':
|
|
110
|
+
logger.debug(`MFA method not found in context: ${context}`);
|
|
111
|
+
throw new BadRequestError('MFA method not found. Please set up MFA and try again.');
|
|
112
|
+
case 'CodeDeliveryFailureException':
|
|
113
|
+
logger.error(`Code delivery failure in context: ${context}`);
|
|
114
|
+
throw new Error('Unable to deliver verification code. Please try again later.');
|
|
115
|
+
case 'DuplicateProviderException':
|
|
116
|
+
logger.error(`Duplicate provider in context: ${context}`);
|
|
117
|
+
throw new BadRequestError('Authentication provider already exists.');
|
|
118
|
+
case 'EnableSoftwareTokenMFAException':
|
|
119
|
+
logger.debug(`Software token MFA required in context: ${context}`);
|
|
120
|
+
throw new BadRequestError('Software token MFA setup required.');
|
|
121
|
+
case 'ForbiddenException':
|
|
122
|
+
logger.warn(`Forbidden access in context: ${context}`);
|
|
123
|
+
throw new UnauthorisedError('Access forbidden. Please check your permissions.');
|
|
124
|
+
case 'GroupExistsException':
|
|
125
|
+
logger.debug(`Group exists in context: ${context}`);
|
|
126
|
+
throw new BadRequestError('Group already exists.');
|
|
127
|
+
case 'InvalidLambdaResponseException':
|
|
128
|
+
logger.error(`Invalid lambda response in context: ${context}`);
|
|
129
|
+
throw new Error('Authentication service error. Please try again later.');
|
|
130
|
+
case 'InvalidOAuthFlowException':
|
|
131
|
+
logger.error(`Invalid OAuth flow in context: ${context}`);
|
|
132
|
+
throw new BadRequestError('Invalid OAuth flow. Please try again.');
|
|
133
|
+
case 'InvalidSmsRoleAccessPolicyException':
|
|
134
|
+
logger.error(`Invalid SMS role access policy in context: ${context}`);
|
|
135
|
+
throw new Error('SMS service configuration error. Please contact support.');
|
|
136
|
+
case 'InvalidSmsRoleTrustRelationshipException':
|
|
137
|
+
logger.error(`Invalid SMS role trust relationship in context: ${context}`);
|
|
138
|
+
throw new Error('SMS service configuration error. Please contact support.');
|
|
139
|
+
case 'InvalidUserPoolConfigurationException':
|
|
140
|
+
logger.error(`Invalid user pool configuration in context: ${context}`);
|
|
141
|
+
throw new Error('Authentication service configuration error. Please contact support.');
|
|
142
|
+
case 'PreconditionNotMetException':
|
|
143
|
+
logger.debug(`Precondition not met in context: ${context}`);
|
|
144
|
+
throw new BadRequestError('Precondition not met. Please check your request and try again.');
|
|
145
|
+
case 'ScopeDoesNotExistException':
|
|
146
|
+
logger.error(`Scope does not exist in context: ${context}`);
|
|
147
|
+
throw new BadRequestError('Invalid scope. Please check your request.');
|
|
148
|
+
case 'UnexpectedLambdaException':
|
|
149
|
+
logger.error(`Unexpected lambda exception in context: ${context}`);
|
|
150
|
+
throw new Error('Authentication service error. Please try again later.');
|
|
151
|
+
case 'UserImportInProgressException':
|
|
152
|
+
logger.warn(`User import in progress in context: ${context}`);
|
|
153
|
+
throw new TooManyRequestsError('User import in progress. Please try again later.');
|
|
154
|
+
case 'UserPoolTaggingException':
|
|
155
|
+
logger.error(`User pool tagging exception in context: ${context}`);
|
|
156
|
+
throw new Error('Authentication service configuration error. Please contact support.');
|
|
157
|
+
default:
|
|
158
|
+
// For any other errors, throw a generic error with sanitized message
|
|
159
|
+
logger.error(`Unhandled Cognito error: ${err.name}`, {
|
|
160
|
+
errorName: err.name,
|
|
161
|
+
errorCode: err.code,
|
|
162
|
+
context: context,
|
|
163
|
+
});
|
|
164
|
+
throw new Error(`Authentication error: ${sanitizeErrorMessage(err.message) || 'An unexpected error occurred'}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// Helper function to sanitize error messages to prevent sensitive information exposure
|
|
168
|
+
function sanitizeErrorMessage(message) {
|
|
169
|
+
if (!message)
|
|
170
|
+
return '';
|
|
171
|
+
// Remove any potential sensitive information patterns
|
|
172
|
+
return message
|
|
173
|
+
.replace(/password/gi, '[REDACTED]')
|
|
174
|
+
.replace(/token/gi, '[REDACTED]')
|
|
175
|
+
.replace(/secret/gi, '[REDACTED]')
|
|
176
|
+
.replace(/key/gi, '[REDACTED]')
|
|
177
|
+
.replace(/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, '[EMAIL_REDACTED]')
|
|
178
|
+
.replace(/\b[A-Fa-f0-9]{32,}\b/g, '[TOKEN_REDACTED]')
|
|
179
|
+
.replace(/\b\d{4,}\b/g, '[NUMBER_REDACTED]');
|
|
180
|
+
}
|
|
181
|
+
// Helper function to get HTTP status code for error type
|
|
182
|
+
export function getHttpStatusForError(error) {
|
|
183
|
+
if (error instanceof UnauthorisedError)
|
|
184
|
+
return 401;
|
|
185
|
+
if (error instanceof UserNotFoundError)
|
|
186
|
+
return 404;
|
|
187
|
+
if (error instanceof TooManyRequestsError)
|
|
188
|
+
return 429;
|
|
189
|
+
if (error instanceof BadRequestError)
|
|
190
|
+
return 400;
|
|
191
|
+
if (error instanceof InvalidParameterError)
|
|
192
|
+
return 400;
|
|
193
|
+
if (error instanceof UserNotConfirmedError)
|
|
194
|
+
return 403;
|
|
195
|
+
if (error instanceof PasswordResetRequiredError)
|
|
196
|
+
return 403;
|
|
197
|
+
if (error instanceof ExpiredCodeError)
|
|
198
|
+
return 400;
|
|
199
|
+
if (error instanceof CodeMismatchError)
|
|
200
|
+
return 400;
|
|
201
|
+
// Check error message for additional context
|
|
202
|
+
if (error.message) {
|
|
203
|
+
if (error.message.includes('temporarily unavailable') ||
|
|
204
|
+
error.message.includes('service error') ||
|
|
205
|
+
error.message.includes('configuration error')) {
|
|
206
|
+
return 503; // Service Unavailable
|
|
207
|
+
}
|
|
208
|
+
if (error.message.includes('contact support')) {
|
|
209
|
+
return 500; // Internal Server Error
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return 500; // Internal server error for unknown errors
|
|
213
|
+
}
|
|
29
214
|
export class CognitoAuth {
|
|
30
215
|
constructor(config) {
|
|
31
216
|
this.config = config ? config : defaultConfig;
|
|
@@ -77,90 +262,235 @@ export class CognitoAuth {
|
|
|
77
262
|
ValidationData: userAttrs,
|
|
78
263
|
};
|
|
79
264
|
const command = new SignUpCommand(input);
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
265
|
+
try {
|
|
266
|
+
logger.debug(`Attempting signup for user: ${username}`);
|
|
267
|
+
const response = await client.send(command);
|
|
268
|
+
if (response.$metadata.httpStatusCode == 200) {
|
|
269
|
+
logger.info(`Signup successful for user: ${username}`);
|
|
270
|
+
const user = await ensureUser(username, '', '', env);
|
|
271
|
+
const userInfo = {
|
|
272
|
+
username: username,
|
|
273
|
+
id: user.id,
|
|
274
|
+
systemUserInfo: response.UserSub,
|
|
275
|
+
};
|
|
276
|
+
cb(userInfo);
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
logger.error(`Signup failed with HTTP status ${response.$metadata.httpStatusCode}`, {
|
|
280
|
+
username: username,
|
|
281
|
+
statusCode: response.$metadata.httpStatusCode,
|
|
282
|
+
});
|
|
283
|
+
throw new BadRequestError(`Signup failed with status ${response.$metadata.httpStatusCode}`);
|
|
284
|
+
}
|
|
89
285
|
}
|
|
90
|
-
|
|
91
|
-
|
|
286
|
+
catch (err) {
|
|
287
|
+
if (err instanceof BadRequestError)
|
|
288
|
+
throw err;
|
|
289
|
+
logger.error(`Signup error for user ${username}:`, {
|
|
290
|
+
errorName: err.name,
|
|
291
|
+
errorMessage: sanitizeErrorMessage(err.message),
|
|
292
|
+
});
|
|
293
|
+
handleCognitoError(err, 'signUp');
|
|
92
294
|
}
|
|
93
295
|
}
|
|
94
296
|
async login(username, password, env, cb) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
297
|
+
// Check if Cognito is configured
|
|
298
|
+
const cognitoConfigured = process.env.COGNITO_USER_POOL_ID && process.env.COGNITO_CLIENT_ID;
|
|
299
|
+
if (cognitoConfigured) {
|
|
300
|
+
// Cognito-first: authenticate directly with Cognito without local store dependency
|
|
301
|
+
const user = new CognitoUser({
|
|
302
|
+
Username: username,
|
|
303
|
+
Pool: this.fetchUserPool(),
|
|
304
|
+
});
|
|
305
|
+
const authDetails = new AuthenticationDetails({
|
|
306
|
+
Username: username,
|
|
307
|
+
Password: password,
|
|
308
|
+
});
|
|
309
|
+
let result;
|
|
310
|
+
let authError;
|
|
311
|
+
user.authenticateUser(authDetails, {
|
|
312
|
+
onSuccess: (session) => {
|
|
313
|
+
result = session;
|
|
314
|
+
},
|
|
315
|
+
onFailure: (err) => {
|
|
316
|
+
logger.debug(`Cognito authentication failed for user ${username}:`, {
|
|
317
|
+
errorName: err.name,
|
|
318
|
+
errorMessage: sanitizeErrorMessage(err.message),
|
|
319
|
+
});
|
|
320
|
+
authError = err;
|
|
321
|
+
},
|
|
322
|
+
mfaRequired: (challengeName, _challengeParameters) => {
|
|
323
|
+
logger.info(`MFA required for user ${username}: ${challengeName}`);
|
|
324
|
+
authError = new Error('MFA authentication required');
|
|
325
|
+
},
|
|
326
|
+
newPasswordRequired: (_userAttributes, _requiredAttributes) => {
|
|
327
|
+
logger.info(`New password required for user ${username}`);
|
|
328
|
+
authError = new PasswordResetRequiredError('New password required. Please reset your password.');
|
|
329
|
+
},
|
|
330
|
+
});
|
|
331
|
+
while (result == undefined && authError == undefined) {
|
|
332
|
+
await sleepMilliseconds(100);
|
|
333
|
+
}
|
|
334
|
+
if (authError) {
|
|
335
|
+
if (authError instanceof PasswordResetRequiredError) {
|
|
336
|
+
throw authError;
|
|
337
|
+
}
|
|
338
|
+
logger.error(`Login failed for user ${username}:`, {
|
|
339
|
+
errorName: authError.name,
|
|
340
|
+
errorMessage: sanitizeErrorMessage(authError.message),
|
|
341
|
+
});
|
|
342
|
+
handleCognitoError(authError, 'login');
|
|
343
|
+
}
|
|
344
|
+
if (result) {
|
|
345
|
+
// After successful Cognito authentication, create/update local records
|
|
346
|
+
let localUser = await findUserByEmail(username, env);
|
|
347
|
+
if (!localUser) {
|
|
348
|
+
localUser = await ensureUser(username, '', '', env);
|
|
349
|
+
}
|
|
350
|
+
const userid = localUser.lookup('id');
|
|
351
|
+
const idToken = result.getIdToken().getJwtToken();
|
|
352
|
+
const accessToken = result.getAccessToken().getJwtToken();
|
|
353
|
+
const refreshToken = result.getRefreshToken().getToken();
|
|
354
|
+
const localSess = await ensureUserSession(userid, idToken, env);
|
|
355
|
+
const sessInfo = {
|
|
356
|
+
sessionId: localSess.lookup('id'),
|
|
357
|
+
userId: userid,
|
|
358
|
+
authToken: idToken,
|
|
359
|
+
idToken: idToken,
|
|
360
|
+
accessToken: accessToken,
|
|
361
|
+
refreshToken: refreshToken,
|
|
362
|
+
systemSesionInfo: result,
|
|
363
|
+
};
|
|
364
|
+
cb(sessInfo);
|
|
365
|
+
}
|
|
366
|
+
else {
|
|
367
|
+
logger.error(`Login failed for ${username} - no result received`);
|
|
368
|
+
throw new UnauthorisedError('Login failed. Please try again.');
|
|
369
|
+
}
|
|
135
370
|
}
|
|
136
371
|
else {
|
|
137
|
-
|
|
372
|
+
// Cognito not configured, fall back to local authentication
|
|
373
|
+
let localUser = await findUserByEmail(username, env);
|
|
374
|
+
if (!localUser) {
|
|
375
|
+
logger.warn(`User ${username} not found in local store`);
|
|
376
|
+
localUser = await ensureUser(username, '', '', env);
|
|
377
|
+
}
|
|
378
|
+
const user = new CognitoUser({
|
|
379
|
+
Username: username,
|
|
380
|
+
Pool: this.fetchUserPool(),
|
|
381
|
+
});
|
|
382
|
+
const authDetails = new AuthenticationDetails({
|
|
383
|
+
Username: username,
|
|
384
|
+
Password: password,
|
|
385
|
+
});
|
|
386
|
+
let result;
|
|
387
|
+
let authError;
|
|
388
|
+
user.authenticateUser(authDetails, {
|
|
389
|
+
onSuccess: (session) => {
|
|
390
|
+
result = session;
|
|
391
|
+
},
|
|
392
|
+
onFailure: (err) => {
|
|
393
|
+
logger.debug(`Cognito authentication failed for user ${username}:`, {
|
|
394
|
+
errorName: err.name,
|
|
395
|
+
errorMessage: sanitizeErrorMessage(err.message),
|
|
396
|
+
});
|
|
397
|
+
authError = err;
|
|
398
|
+
},
|
|
399
|
+
mfaRequired: (challengeName, _challengeParameters) => {
|
|
400
|
+
logger.info(`MFA required for user ${username}: ${challengeName}`);
|
|
401
|
+
authError = new Error('MFA authentication required');
|
|
402
|
+
},
|
|
403
|
+
newPasswordRequired: (_userAttributes, _requiredAttributes) => {
|
|
404
|
+
logger.info(`New password required for user ${username}`);
|
|
405
|
+
authError = new PasswordResetRequiredError('New password required. Please reset your password.');
|
|
406
|
+
},
|
|
407
|
+
});
|
|
408
|
+
while (result == undefined && authError == undefined) {
|
|
409
|
+
await sleepMilliseconds(100);
|
|
410
|
+
}
|
|
411
|
+
if (authError) {
|
|
412
|
+
if (authError instanceof PasswordResetRequiredError) {
|
|
413
|
+
throw authError;
|
|
414
|
+
}
|
|
415
|
+
logger.error(`Login failed for user ${username}:`, {
|
|
416
|
+
errorName: authError.name,
|
|
417
|
+
errorMessage: sanitizeErrorMessage(authError.message),
|
|
418
|
+
});
|
|
419
|
+
handleCognitoError(authError, 'login');
|
|
420
|
+
}
|
|
421
|
+
if (result) {
|
|
422
|
+
const userid = localUser.lookup('id');
|
|
423
|
+
const idToken = result.getIdToken().getJwtToken();
|
|
424
|
+
const accessToken = result.getAccessToken().getJwtToken();
|
|
425
|
+
const refreshToken = result.getRefreshToken().getToken();
|
|
426
|
+
const localSess = await ensureUserSession(userid, idToken, env);
|
|
427
|
+
const sessInfo = {
|
|
428
|
+
sessionId: localSess.lookup('id'),
|
|
429
|
+
userId: userid,
|
|
430
|
+
authToken: idToken,
|
|
431
|
+
idToken: idToken,
|
|
432
|
+
accessToken: accessToken,
|
|
433
|
+
refreshToken: refreshToken,
|
|
434
|
+
systemSesionInfo: result,
|
|
435
|
+
};
|
|
436
|
+
cb(sessInfo);
|
|
437
|
+
}
|
|
438
|
+
else {
|
|
439
|
+
logger.error(`Login failed for ${username} - no result received`);
|
|
440
|
+
throw new UnauthorisedError('Login failed. Please try again.');
|
|
441
|
+
}
|
|
138
442
|
}
|
|
139
443
|
}
|
|
140
444
|
async logout(sessionInfo, env, cb) {
|
|
141
|
-
|
|
142
|
-
|
|
445
|
+
try {
|
|
446
|
+
const localUser = await findUser(sessionInfo.userId, env);
|
|
447
|
+
if (!localUser) {
|
|
448
|
+
logger.warn(`User ${sessionInfo.userId} not found during logout`);
|
|
449
|
+
if (cb)
|
|
450
|
+
cb(true);
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
const user = new CognitoUser({
|
|
454
|
+
Username: localUser.email,
|
|
455
|
+
Pool: this.fetchUserPool(),
|
|
456
|
+
});
|
|
457
|
+
let done = false;
|
|
458
|
+
let logoutError;
|
|
459
|
+
user.signOut(() => {
|
|
460
|
+
done = true;
|
|
461
|
+
});
|
|
462
|
+
// Add error handling for signOut
|
|
463
|
+
user.signOut((err) => {
|
|
464
|
+
if (err) {
|
|
465
|
+
logger.error(`Cognito signOut error for user ${sessionInfo.userId}:`, {
|
|
466
|
+
errorName: err.name,
|
|
467
|
+
errorMessage: sanitizeErrorMessage(err.message),
|
|
468
|
+
});
|
|
469
|
+
logoutError = err;
|
|
470
|
+
}
|
|
471
|
+
done = true;
|
|
472
|
+
});
|
|
473
|
+
while (!done) {
|
|
474
|
+
await sleepMilliseconds(100);
|
|
475
|
+
}
|
|
476
|
+
if (logoutError) {
|
|
477
|
+
logger.error(`Error during Cognito logout for user ${sessionInfo.userId}: ${logoutError.message}`);
|
|
478
|
+
// Continue with local session cleanup even if Cognito logout fails
|
|
479
|
+
}
|
|
480
|
+
const sess = await findUserSession(localUser.id, env);
|
|
481
|
+
if (sess) {
|
|
482
|
+
await removeSession(sess.id, env);
|
|
483
|
+
}
|
|
484
|
+
logger.debug(`Successfully logged out user ${sessionInfo.userId}`);
|
|
143
485
|
if (cb)
|
|
144
486
|
cb(true);
|
|
145
|
-
return;
|
|
146
487
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
user.signOut(() => {
|
|
153
|
-
done = true;
|
|
154
|
-
});
|
|
155
|
-
while (!done) {
|
|
156
|
-
await sleepMilliseconds(100);
|
|
157
|
-
}
|
|
158
|
-
const sess = await findUserSession(localUser.id, env);
|
|
159
|
-
if (sess) {
|
|
160
|
-
await removeSession(sess.id, env);
|
|
488
|
+
catch (err) {
|
|
489
|
+
logger.error(`Logout failed for user ${sessionInfo.userId}: ${err.message}`);
|
|
490
|
+
if (cb)
|
|
491
|
+
cb(false);
|
|
492
|
+
throw err;
|
|
161
493
|
}
|
|
162
|
-
if (cb)
|
|
163
|
-
cb(true);
|
|
164
494
|
}
|
|
165
495
|
fetchUserPool() {
|
|
166
496
|
if (this.userPool) {
|
|
@@ -179,7 +509,144 @@ export class CognitoAuth {
|
|
|
179
509
|
logger.debug(`Decoded JWT for ${payload.email}`);
|
|
180
510
|
}
|
|
181
511
|
catch (err) {
|
|
182
|
-
|
|
512
|
+
logger.error(`Token verification failed:`, {
|
|
513
|
+
errorName: err.name,
|
|
514
|
+
errorMessage: sanitizeErrorMessage(err.message),
|
|
515
|
+
});
|
|
516
|
+
// Handle specific token verification errors
|
|
517
|
+
if (err.message && err.message.includes('expired')) {
|
|
518
|
+
throw new UnauthorisedError('Token has expired. Please login again.');
|
|
519
|
+
}
|
|
520
|
+
if (err.message && err.message.includes('invalid')) {
|
|
521
|
+
throw new UnauthorisedError('Invalid token format.');
|
|
522
|
+
}
|
|
523
|
+
if (err.message && err.message.includes('not before')) {
|
|
524
|
+
throw new UnauthorisedError('Token is not yet valid.');
|
|
525
|
+
}
|
|
526
|
+
if (err.message && err.message.includes('audience')) {
|
|
527
|
+
throw new UnauthorisedError('Token audience mismatch.');
|
|
528
|
+
}
|
|
529
|
+
throw new UnauthorisedError(`Token verification failed: ${sanitizeErrorMessage(err.message) || 'Invalid token'}`);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
async getUser(userId, env) {
|
|
533
|
+
const localUser = await findUser(userId, env);
|
|
534
|
+
if (!localUser) {
|
|
535
|
+
throw new UserNotFoundError(`User ${userId} not found in local database`);
|
|
536
|
+
}
|
|
537
|
+
const userEmail = localUser.lookup('email');
|
|
538
|
+
// Check if Cognito is configured
|
|
539
|
+
const cognitoConfigured = process.env.COGNITO_USER_POOL_ID && process.env.COGNITO_CLIENT_ID;
|
|
540
|
+
if (cognitoConfigured && userEmail) {
|
|
541
|
+
try {
|
|
542
|
+
// Get additional user details from Cognito
|
|
543
|
+
const client = new CognitoIdentityProviderClient({
|
|
544
|
+
region: process.env.AWS_REGION || 'us-west-2',
|
|
545
|
+
credentials: fromEnv(),
|
|
546
|
+
});
|
|
547
|
+
const command = new AdminGetUserCommand({
|
|
548
|
+
UserPoolId: this.fetchUserPoolId(),
|
|
549
|
+
Username: userEmail,
|
|
550
|
+
});
|
|
551
|
+
const response = await client.send(command);
|
|
552
|
+
// Return user info with both local and Cognito data
|
|
553
|
+
return {
|
|
554
|
+
id: userId,
|
|
555
|
+
username: userEmail,
|
|
556
|
+
systemUserInfo: {
|
|
557
|
+
localUser: localUser,
|
|
558
|
+
cognitoData: {
|
|
559
|
+
userAttributes: response.UserAttributes,
|
|
560
|
+
userCreateDate: response.UserCreateDate,
|
|
561
|
+
userLastModifiedDate: response.UserLastModifiedDate,
|
|
562
|
+
userStatus: response.UserStatus,
|
|
563
|
+
enabled: response.Enabled,
|
|
564
|
+
preferredMfaSetting: response.PreferredMfaSetting,
|
|
565
|
+
userMFASettingList: response.UserMFASettingList,
|
|
566
|
+
},
|
|
567
|
+
},
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
catch (err) {
|
|
571
|
+
logger.warn(`Failed to get Cognito user info for ${userEmail}, using local data only:`, {
|
|
572
|
+
errorName: err.name,
|
|
573
|
+
errorMessage: sanitizeErrorMessage(err.message),
|
|
574
|
+
});
|
|
575
|
+
// Fall back to local data only
|
|
576
|
+
return {
|
|
577
|
+
id: userId,
|
|
578
|
+
username: userEmail,
|
|
579
|
+
systemUserInfo: localUser,
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
else {
|
|
584
|
+
// Cognito not configured or no email, use local data only
|
|
585
|
+
return {
|
|
586
|
+
id: userId,
|
|
587
|
+
username: userEmail || userId,
|
|
588
|
+
systemUserInfo: localUser,
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
async getUserByEmail(email, env) {
|
|
593
|
+
const localUser = await findUserByEmail(email, env);
|
|
594
|
+
if (!localUser) {
|
|
595
|
+
throw new UserNotFoundError(`User with email ${email} not found in local database`);
|
|
596
|
+
}
|
|
597
|
+
const userId = localUser.lookup('id');
|
|
598
|
+
// Check if Cognito is configured
|
|
599
|
+
const cognitoConfigured = process.env.COGNITO_USER_POOL_ID && process.env.COGNITO_CLIENT_ID;
|
|
600
|
+
if (cognitoConfigured) {
|
|
601
|
+
try {
|
|
602
|
+
// Get additional user details from Cognito
|
|
603
|
+
const client = new CognitoIdentityProviderClient({
|
|
604
|
+
region: process.env.AWS_REGION || 'us-west-2',
|
|
605
|
+
credentials: fromEnv(),
|
|
606
|
+
});
|
|
607
|
+
const command = new AdminGetUserCommand({
|
|
608
|
+
UserPoolId: this.fetchUserPoolId(),
|
|
609
|
+
Username: email,
|
|
610
|
+
});
|
|
611
|
+
const response = await client.send(command);
|
|
612
|
+
// Return user info with both local and Cognito data
|
|
613
|
+
return {
|
|
614
|
+
id: userId,
|
|
615
|
+
username: email,
|
|
616
|
+
systemUserInfo: {
|
|
617
|
+
localUser: localUser,
|
|
618
|
+
cognitoData: {
|
|
619
|
+
userAttributes: response.UserAttributes,
|
|
620
|
+
userCreateDate: response.UserCreateDate,
|
|
621
|
+
userLastModifiedDate: response.UserLastModifiedDate,
|
|
622
|
+
userStatus: response.UserStatus,
|
|
623
|
+
enabled: response.Enabled,
|
|
624
|
+
preferredMfaSetting: response.PreferredMfaSetting,
|
|
625
|
+
userMFASettingList: response.UserMFASettingList,
|
|
626
|
+
},
|
|
627
|
+
},
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
catch (err) {
|
|
631
|
+
logger.warn(`Failed to get Cognito user info for email ${email}, using local data only:`, {
|
|
632
|
+
errorName: err.name,
|
|
633
|
+
errorMessage: sanitizeErrorMessage(err.message),
|
|
634
|
+
});
|
|
635
|
+
// Fall back to local data only
|
|
636
|
+
return {
|
|
637
|
+
id: userId,
|
|
638
|
+
username: email,
|
|
639
|
+
systemUserInfo: localUser,
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
else {
|
|
644
|
+
// Cognito not configured, use local data only
|
|
645
|
+
return {
|
|
646
|
+
id: userId,
|
|
647
|
+
username: email,
|
|
648
|
+
systemUserInfo: localUser,
|
|
649
|
+
};
|
|
183
650
|
}
|
|
184
651
|
}
|
|
185
652
|
}
|