@contentstack/cli-auth 1.5.0 → 1.6.0
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 +1 -1
- package/lib/commands/auth/login.js +15 -2
- package/lib/commands/auth/whoami.js +0 -1
- package/lib/utils/auth-handler.d.ts +13 -0
- package/lib/utils/auth-handler.js +68 -40
- package/lib/utils/index.d.ts +1 -0
- package/lib/utils/index.js +3 -1
- package/lib/utils/interactive.js +2 -2
- package/lib/utils/mfa-handler.d.ts +41 -0
- package/lib/utils/mfa-handler.js +133 -0
- package/messages/index.json +13 -2
- package/oclif.manifest.json +1 -1
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ $ npm install -g @contentstack/cli-auth
|
|
|
18
18
|
$ csdx COMMAND
|
|
19
19
|
running command...
|
|
20
20
|
$ csdx (--version)
|
|
21
|
-
@contentstack/cli-auth/1.
|
|
21
|
+
@contentstack/cli-auth/1.6.0 linux-x64 node-v22.18.0
|
|
22
22
|
$ csdx --help [COMMAND]
|
|
23
23
|
USAGE
|
|
24
24
|
$ csdx COMMAND
|
|
@@ -32,7 +32,9 @@ class LoginCommand extends base_command_1.BaseCommand {
|
|
|
32
32
|
}
|
|
33
33
|
catch (error) {
|
|
34
34
|
cli_utilities_1.log.debug('Login command failed', Object.assign(Object.assign({}, this.contextDetails), { error }));
|
|
35
|
-
|
|
35
|
+
if (((error === null || error === void 0 ? void 0 : error.message) && (error === null || error === void 0 ? void 0 : error.message.includes('2FA'))) || (error === null || error === void 0 ? void 0 : error.message.includes('MFA'))) {
|
|
36
|
+
error.message = `${error.message}\nFor more information about MFA, visit: https://www.contentstack.com/docs/developers/security/multi-factor-authentication`;
|
|
37
|
+
}
|
|
36
38
|
(0, cli_utilities_1.handleAndLogError)(error, Object.assign({}, this.contextDetails));
|
|
37
39
|
process.exit();
|
|
38
40
|
}
|
|
@@ -41,7 +43,18 @@ class LoginCommand extends base_command_1.BaseCommand {
|
|
|
41
43
|
cli_utilities_1.log.debug('Starting login process', Object.assign(Object.assign({}, this.contextDetails), { username }));
|
|
42
44
|
try {
|
|
43
45
|
cli_utilities_1.log.debug('Calling auth handler login', this.contextDetails);
|
|
44
|
-
|
|
46
|
+
let tfaToken;
|
|
47
|
+
try {
|
|
48
|
+
tfaToken = await utils_1.mfaHandler.getMFACode();
|
|
49
|
+
if (tfaToken) {
|
|
50
|
+
cli_utilities_1.log.debug('MFA token generated from stored configuration', this.contextDetails);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
cli_utilities_1.log.debug('Failed to generate MFA token from config', Object.assign(Object.assign({}, this.contextDetails), { error }));
|
|
55
|
+
tfaToken = undefined;
|
|
56
|
+
}
|
|
57
|
+
const user = await utils_1.authHandler.login(username, password, tfaToken);
|
|
45
58
|
cli_utilities_1.log.debug('Auth handler login completed', Object.assign(Object.assign({}, this.contextDetails), { hasUser: !!user, hasAuthToken: !!(user === null || user === void 0 ? void 0 : user.authtoken), userEmail: user === null || user === void 0 ? void 0 : user.email }));
|
|
46
59
|
if (typeof user !== 'object' || !user.authtoken || !user.email) {
|
|
47
60
|
cli_utilities_1.log.debug('Login failed - invalid user response', Object.assign(Object.assign({}, this.contextDetails), { user }));
|
|
@@ -11,7 +11,6 @@ class WhoamiCommand extends base_command_1.BaseCommand {
|
|
|
11
11
|
cli_utilities_1.log.debug('User email found, displaying user information', Object.assign(Object.assign({}, this.contextDetails), { email: this.email }));
|
|
12
12
|
cli_utilities_1.cliux.print('CLI_AUTH_WHOAMI_LOGGED_IN_AS', { color: 'white' });
|
|
13
13
|
cli_utilities_1.cliux.print(this.email, { color: 'green' });
|
|
14
|
-
cli_utilities_1.log.info(cli_utilities_1.messageHandler.parse('CLI_AUTH_WHOAMI_LOGGED_IN_AS', this.email), this.contextDetails);
|
|
15
14
|
cli_utilities_1.log.debug('Whoami command completed successfully', this.contextDetails);
|
|
16
15
|
}
|
|
17
16
|
else {
|
|
@@ -17,6 +17,19 @@ declare class AuthHandler {
|
|
|
17
17
|
* @returns {Promise} Promise object returns authtoken on success
|
|
18
18
|
* TBD: take out the otp implementation from login and create a new method/function to handle otp
|
|
19
19
|
*/
|
|
20
|
+
/**
|
|
21
|
+
* Handle the OTP flow for 2FA authentication
|
|
22
|
+
* @param tfaToken Optional pre-provided TFA token
|
|
23
|
+
* @param loginPayload Login payload containing user credentials
|
|
24
|
+
* @returns Promise<string> The TFA token to use for authentication
|
|
25
|
+
*/
|
|
26
|
+
private handleOTPFlow;
|
|
27
|
+
/**
|
|
28
|
+
* Request SMS OTP for 2FA authentication
|
|
29
|
+
* @param loginPayload Login payload containing user credentials
|
|
30
|
+
* @throws CLIError if SMS request fails
|
|
31
|
+
*/
|
|
32
|
+
private requestSMSOTP;
|
|
20
33
|
login(email: string, password: string, tfaToken?: string): Promise<User>;
|
|
21
34
|
/**
|
|
22
35
|
* Logout from Contentstack
|
|
@@ -24,9 +24,59 @@ class AuthHandler {
|
|
|
24
24
|
* @returns {Promise} Promise object returns authtoken on success
|
|
25
25
|
* TBD: take out the otp implementation from login and create a new method/function to handle otp
|
|
26
26
|
*/
|
|
27
|
+
/**
|
|
28
|
+
* Handle the OTP flow for 2FA authentication
|
|
29
|
+
* @param tfaToken Optional pre-provided TFA token
|
|
30
|
+
* @param loginPayload Login payload containing user credentials
|
|
31
|
+
* @returns Promise<string> The TFA token to use for authentication
|
|
32
|
+
*/
|
|
33
|
+
async handleOTPFlow(tfaToken, loginPayload) {
|
|
34
|
+
try {
|
|
35
|
+
if (tfaToken) {
|
|
36
|
+
cli_utilities_1.log.info('Using provided TFA token', { module: 'auth-handler' });
|
|
37
|
+
return tfaToken;
|
|
38
|
+
}
|
|
39
|
+
cli_utilities_1.log.debug('2FA required, requesting OTP channel', { module: 'auth-handler' });
|
|
40
|
+
const otpChannel = await (0, interactive_1.askOTPChannel)();
|
|
41
|
+
cli_utilities_1.log.debug(`OTP channel selected: ${otpChannel}`, { module: 'auth-handler' });
|
|
42
|
+
if (otpChannel === 'sms') {
|
|
43
|
+
try {
|
|
44
|
+
await this.requestSMSOTP(loginPayload);
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
cli_utilities_1.log.debug('SMS OTP request failed', { module: 'auth-handler', error });
|
|
48
|
+
cli_utilities_1.cliux.print('CLI_AUTH_SMS_OTP_FAILED', { color: 'red' });
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
cli_utilities_1.log.debug('Requesting OTP input', { module: 'auth-handler', channel: otpChannel });
|
|
53
|
+
return await (0, interactive_1.askOTP)();
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
cli_utilities_1.log.debug('2FA flow failed', { module: 'auth-handler', error });
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Request SMS OTP for 2FA authentication
|
|
62
|
+
* @param loginPayload Login payload containing user credentials
|
|
63
|
+
* @throws CLIError if SMS request fails
|
|
64
|
+
*/
|
|
65
|
+
async requestSMSOTP(loginPayload) {
|
|
66
|
+
cli_utilities_1.log.debug('Sending SMS OTP request', { module: 'auth-handler' });
|
|
67
|
+
try {
|
|
68
|
+
await this._client.axiosInstance.post('/user/request_token_sms', { user: loginPayload });
|
|
69
|
+
cli_utilities_1.log.debug('SMS OTP request successful', { module: 'auth-handler' });
|
|
70
|
+
cli_utilities_1.cliux.print('CLI_AUTH_LOGIN_SECURITY_CODE_SEND_SUCCESS');
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
cli_utilities_1.log.debug('SMS OTP request failed', { module: 'auth-handler', error });
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
27
77
|
async login(email, password, tfaToken) {
|
|
28
|
-
const hasCredentials =
|
|
29
|
-
const hasTfaToken =
|
|
78
|
+
const hasCredentials = typeof password === 'string' && password.length > 0;
|
|
79
|
+
const hasTfaToken = typeof tfaToken === 'string' && tfaToken.length > 0;
|
|
30
80
|
cli_utilities_1.log.debug('Starting login process', {
|
|
31
81
|
module: 'auth-handler',
|
|
32
82
|
email,
|
|
@@ -40,11 +90,9 @@ class AuthHandler {
|
|
|
40
90
|
loginPayload.tfa_token = tfaToken;
|
|
41
91
|
cli_utilities_1.log.debug('Adding TFA token to login payload', { module: 'auth-handler' });
|
|
42
92
|
}
|
|
43
|
-
const hasCredentials = !!password;
|
|
44
|
-
const hasTfaTokenPresent = !!tfaToken;
|
|
45
93
|
cli_utilities_1.log.debug('Making login API call', {
|
|
46
94
|
module: 'auth-handler',
|
|
47
|
-
payload: { email, hasCredentials,
|
|
95
|
+
payload: { email, hasCredentials, hasTfaToken },
|
|
48
96
|
});
|
|
49
97
|
this._client
|
|
50
98
|
.login(loginPayload)
|
|
@@ -59,46 +107,25 @@ class AuthHandler {
|
|
|
59
107
|
resolve(result.user);
|
|
60
108
|
}
|
|
61
109
|
else if (result.error_code === 294) {
|
|
62
|
-
|
|
63
|
-
const otpChannel = await (0, interactive_1.askOTPChannel)();
|
|
64
|
-
cli_utilities_1.log.debug(`OTP channel selected: ${otpChannel}`, { module: 'auth-handler' });
|
|
65
|
-
// need to send sms to the mobile
|
|
66
|
-
if (otpChannel === 'sms') {
|
|
67
|
-
cli_utilities_1.log.debug('Sending SMS OTP request', { module: 'auth-handler' });
|
|
68
|
-
try {
|
|
69
|
-
await this._client.axiosInstance.post('/user/request_token_sms', { user: loginPayload });
|
|
70
|
-
cli_utilities_1.log.debug('SMS OTP request successful', { module: 'auth-handler' });
|
|
71
|
-
cli_utilities_1.cliux.print('CLI_AUTH_LOGIN_SECURITY_CODE_SEND_SUCCESS');
|
|
72
|
-
}
|
|
73
|
-
catch (error) {
|
|
74
|
-
cli_utilities_1.log.debug('SMS OTP request failed', { module: 'auth-handler', error });
|
|
75
|
-
const err = cli_utilities_1.cliErrorHandler.classifyError(error);
|
|
76
|
-
reject(err);
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
cli_utilities_1.log.debug('Requesting OTP input from user', { module: 'auth-handler' });
|
|
81
|
-
const tfToken = await (0, interactive_1.askOTP)();
|
|
82
|
-
cli_utilities_1.log.debug('OTP received, retrying login', { module: 'auth-handler' });
|
|
110
|
+
const tfToken = await this.handleOTPFlow(tfaToken, loginPayload);
|
|
83
111
|
try {
|
|
84
112
|
resolve(await this.login(email, password, tfToken));
|
|
85
113
|
}
|
|
86
114
|
catch (error) {
|
|
87
115
|
cli_utilities_1.log.debug('Login with TFA token failed', { module: 'auth-handler', error });
|
|
88
|
-
|
|
89
|
-
reject(
|
|
90
|
-
return;
|
|
116
|
+
cli_utilities_1.cliux.print('CLI_AUTH_2FA_FAILED', { color: 'red' });
|
|
117
|
+
reject(error);
|
|
91
118
|
}
|
|
92
119
|
}
|
|
93
120
|
else {
|
|
94
121
|
cli_utilities_1.log.debug('Login failed - no user found', { module: 'auth-handler', result });
|
|
95
|
-
reject(new cli_utilities_1.
|
|
122
|
+
reject(new Error(cli_utilities_1.messageHandler.parse('CLI_AUTH_LOGIN_NO_USER')));
|
|
96
123
|
}
|
|
97
124
|
})
|
|
98
125
|
.catch((error) => {
|
|
99
|
-
cli_utilities_1.log.debug('Login API call failed', { module: 'auth-handler', error: error.
|
|
100
|
-
|
|
101
|
-
|
|
126
|
+
cli_utilities_1.log.debug('Login API call failed', { module: 'auth-handler', error: (error === null || error === void 0 ? void 0 : error.errorMessage) || error });
|
|
127
|
+
cli_utilities_1.cliux.print('CLI_AUTH_LOGIN_FAILED', { color: 'yellow' });
|
|
128
|
+
(0, cli_utilities_1.handleAndLogError)(error, { module: 'auth-handler' });
|
|
102
129
|
});
|
|
103
130
|
}
|
|
104
131
|
else {
|
|
@@ -109,7 +136,8 @@ class AuthHandler {
|
|
|
109
136
|
hasEmail,
|
|
110
137
|
hasCredentials,
|
|
111
138
|
});
|
|
112
|
-
|
|
139
|
+
cli_utilities_1.log.debug('Login failed - missing credentials', { module: 'auth-handler', hasEmail, hasCredentials });
|
|
140
|
+
reject(new Error(cli_utilities_1.messageHandler.parse('CLI_AUTH_LOGIN_NO_CREDENTIALS')));
|
|
113
141
|
}
|
|
114
142
|
});
|
|
115
143
|
}
|
|
@@ -131,13 +159,13 @@ class AuthHandler {
|
|
|
131
159
|
})
|
|
132
160
|
.catch((error) => {
|
|
133
161
|
cli_utilities_1.log.debug('Logout API call failed', { module: 'auth-handler', error: error.message });
|
|
134
|
-
|
|
135
|
-
reject(
|
|
162
|
+
cli_utilities_1.cliux.print('CLI_AUTH_LOGOUT_FAILED', { color: 'yellow' });
|
|
163
|
+
reject(error);
|
|
136
164
|
});
|
|
137
165
|
}
|
|
138
166
|
else {
|
|
139
167
|
cli_utilities_1.log.debug('Logout failed - no auth token provided', { module: 'auth-handler' });
|
|
140
|
-
reject(new cli_utilities_1.
|
|
168
|
+
reject(new Error(cli_utilities_1.messageHandler.parse('CLI_AUTH_LOGOUT_NO_TOKEN')));
|
|
141
169
|
}
|
|
142
170
|
});
|
|
143
171
|
}
|
|
@@ -159,13 +187,13 @@ class AuthHandler {
|
|
|
159
187
|
})
|
|
160
188
|
.catch((error) => {
|
|
161
189
|
cli_utilities_1.log.debug('Token validation failed', { module: 'auth-handler', error: error.message });
|
|
162
|
-
|
|
163
|
-
|
|
190
|
+
cli_utilities_1.cliux.print('CLI_AUTH_TOKEN_VALIDATION_FAILED', { color: 'yellow' });
|
|
191
|
+
(0, cli_utilities_1.handleAndLogError)(error, { module: 'auth-handler' });
|
|
164
192
|
});
|
|
165
193
|
}
|
|
166
194
|
else {
|
|
167
195
|
cli_utilities_1.log.debug('Token validation failed - no auth token provided', { module: 'auth-handler' });
|
|
168
|
-
reject(new cli_utilities_1.
|
|
196
|
+
reject(new Error(cli_utilities_1.messageHandler.parse('CLI_AUTH_TOKEN_VALIDATION_NO_TOKEN')));
|
|
169
197
|
}
|
|
170
198
|
});
|
|
171
199
|
}
|
package/lib/utils/index.d.ts
CHANGED
package/lib/utils/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.tokenValidation = exports.interactive = exports.authHandler = void 0;
|
|
3
|
+
exports.tokenValidation = exports.interactive = exports.mfaHandler = exports.authHandler = void 0;
|
|
4
4
|
var auth_handler_1 = require("./auth-handler");
|
|
5
5
|
Object.defineProperty(exports, "authHandler", { enumerable: true, get: function () { return auth_handler_1.default; } });
|
|
6
|
+
var mfa_handler_1 = require("./mfa-handler");
|
|
7
|
+
Object.defineProperty(exports, "mfaHandler", { enumerable: true, get: function () { return mfa_handler_1.default; } });
|
|
6
8
|
exports.interactive = require("./interactive");
|
|
7
9
|
exports.tokenValidation = require("./tokens-validation");
|
package/lib/utils/interactive.js
CHANGED
|
@@ -19,7 +19,7 @@ const askOTPChannel = async () => {
|
|
|
19
19
|
name: 'otpChannel',
|
|
20
20
|
message: 'CLI_AUTH_LOGIN_ASK_CHANNEL_FOR_OTP',
|
|
21
21
|
choices: [
|
|
22
|
-
{ name: '
|
|
22
|
+
{ name: 'Authenticator App', value: 'authenticator_app' },
|
|
23
23
|
{ name: 'SMS', value: 'sms' },
|
|
24
24
|
],
|
|
25
25
|
});
|
|
@@ -49,7 +49,7 @@ const askTokenType = async () => {
|
|
|
49
49
|
choices: [
|
|
50
50
|
{ name: 'Management Token', value: 'management' },
|
|
51
51
|
{ name: 'Delivery Token', value: 'delivery' },
|
|
52
|
-
]
|
|
52
|
+
],
|
|
53
53
|
});
|
|
54
54
|
};
|
|
55
55
|
exports.askTokenType = askTokenType;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @class
|
|
3
|
+
* MFA handler for managing multi-factor authentication
|
|
4
|
+
*/
|
|
5
|
+
declare class MFAHandler {
|
|
6
|
+
private readonly encrypter;
|
|
7
|
+
constructor();
|
|
8
|
+
/**
|
|
9
|
+
* Validates if a string is a valid base32 secret
|
|
10
|
+
* @param secret The secret to validate
|
|
11
|
+
* @returns true if valid, false otherwise
|
|
12
|
+
*/
|
|
13
|
+
private isValidBase32;
|
|
14
|
+
/**
|
|
15
|
+
* Generates an MFA code from a provided secret
|
|
16
|
+
* @param secret The MFA secret to use
|
|
17
|
+
* @returns string The generated MFA code
|
|
18
|
+
* @throws Error if the secret is invalid or code generation fails
|
|
19
|
+
*/
|
|
20
|
+
generateMFACode(secret: string): string;
|
|
21
|
+
/**
|
|
22
|
+
* Gets MFA code from stored configuration
|
|
23
|
+
* @returns Promise<string> The MFA code
|
|
24
|
+
* @throws Error if MFA code generation fails
|
|
25
|
+
*/
|
|
26
|
+
getMFACode(): Promise<string>;
|
|
27
|
+
/**
|
|
28
|
+
* Gets MFA code through manual user input
|
|
29
|
+
* @returns Promise<string> The MFA code
|
|
30
|
+
* @throws Error if code format is invalid
|
|
31
|
+
*/
|
|
32
|
+
getManualMFACode(): Promise<string>;
|
|
33
|
+
/**
|
|
34
|
+
* Validates an MFA code format
|
|
35
|
+
* @param code The MFA code to validate
|
|
36
|
+
* @returns boolean True if valid, false otherwise
|
|
37
|
+
*/
|
|
38
|
+
isValidMFACode(code: string): boolean;
|
|
39
|
+
}
|
|
40
|
+
declare const _default: MFAHandler;
|
|
41
|
+
export default _default;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const cli_utilities_1 = require("@contentstack/cli-utilities");
|
|
4
|
+
const otplib_1 = require("otplib");
|
|
5
|
+
const interactive_1 = require("./interactive");
|
|
6
|
+
/**
|
|
7
|
+
* @class
|
|
8
|
+
* MFA handler for managing multi-factor authentication
|
|
9
|
+
*/
|
|
10
|
+
class MFAHandler {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.encrypter = new cli_utilities_1.NodeCrypto();
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Validates if a string is a valid base32 secret
|
|
16
|
+
* @param secret The secret to validate
|
|
17
|
+
* @returns true if valid, false otherwise
|
|
18
|
+
*/
|
|
19
|
+
isValidBase32(secret) {
|
|
20
|
+
// Base32 string must:
|
|
21
|
+
// 1. Contain only uppercase letters A-Z and digits 2-7
|
|
22
|
+
// 2. Be at least 16 characters long (before padding)
|
|
23
|
+
// 3. Have valid padding (no single = character)
|
|
24
|
+
const base32Regex = /^[A-Z2-7]+(?:={2,6})?$/;
|
|
25
|
+
const nonPaddedLength = secret.replace(/=+$/, '').length;
|
|
26
|
+
return base32Regex.test(secret) && nonPaddedLength >= 16;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Generates an MFA code from a provided secret
|
|
30
|
+
* @param secret The MFA secret to use
|
|
31
|
+
* @returns string The generated MFA code
|
|
32
|
+
* @throws Error if the secret is invalid or code generation fails
|
|
33
|
+
*/
|
|
34
|
+
generateMFACode(secret) {
|
|
35
|
+
cli_utilities_1.log.debug('Generating MFA code from provided secret', { module: 'mfa-handler' });
|
|
36
|
+
try {
|
|
37
|
+
// Validate and normalize secret
|
|
38
|
+
const normalizedSecret = secret.toUpperCase();
|
|
39
|
+
if (!this.isValidBase32(normalizedSecret)) {
|
|
40
|
+
cli_utilities_1.log.debug('Invalid MFA secret format', { module: 'mfa-handler' });
|
|
41
|
+
cli_utilities_1.cliux.print('CLI_AUTH_MFA_INVALID_SECRET', { color: 'yellow' });
|
|
42
|
+
cli_utilities_1.cliux.print('For more information about MFA, visit: https://www.contentstack.com/docs/developers/security/multi-factor-authentication', { color: 'yellow' });
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
// Generate MFA code
|
|
46
|
+
const code = otplib_1.authenticator.generate(normalizedSecret);
|
|
47
|
+
cli_utilities_1.log.debug('Generated MFA code successfully', { module: 'mfa-handler' });
|
|
48
|
+
return code;
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
cli_utilities_1.log.debug('Failed to generate MFA code', { module: 'mfa-handler', error });
|
|
52
|
+
cli_utilities_1.cliux.print('CLI_AUTH_MFA_GENERATION_FAILED', { color: 'yellow' });
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Gets MFA code from stored configuration
|
|
58
|
+
* @returns Promise<string> The MFA code
|
|
59
|
+
* @throws Error if MFA code generation fails
|
|
60
|
+
*/
|
|
61
|
+
async getMFACode() {
|
|
62
|
+
cli_utilities_1.log.debug('Getting MFA code', { module: 'mfa-handler' });
|
|
63
|
+
let secret;
|
|
64
|
+
let source;
|
|
65
|
+
const envSecret = process.env.CONTENTSTACK_MFA_SECRET;
|
|
66
|
+
if (envSecret) {
|
|
67
|
+
cli_utilities_1.log.debug('Found MFA secret in environment variable', { module: 'mfa-handler' });
|
|
68
|
+
if (!this.isValidBase32(envSecret.toUpperCase())) {
|
|
69
|
+
cli_utilities_1.log.debug('Invalid MFA secret format from environment variable', { module: 'mfa-handler' });
|
|
70
|
+
cli_utilities_1.cliux.print('CLI_AUTH_MFA_INVALID_SECRET', { color: 'yellow' });
|
|
71
|
+
cli_utilities_1.cliux.print('For more information about MFA, visit: https://www.contentstack.com/docs/developers/security/multi-factor-authentication', { color: 'yellow' });
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
secret = envSecret;
|
|
75
|
+
source = 'environment variable';
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (!secret) {
|
|
79
|
+
cli_utilities_1.log.debug('Checking stored MFA secret', { module: 'mfa-handler' });
|
|
80
|
+
const mfaConfig = cli_utilities_1.configHandler.get('mfa');
|
|
81
|
+
if (mfaConfig === null || mfaConfig === void 0 ? void 0 : mfaConfig.secret) {
|
|
82
|
+
try {
|
|
83
|
+
secret = this.encrypter.decrypt(mfaConfig.secret);
|
|
84
|
+
source = 'stored configuration';
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
cli_utilities_1.log.debug('Failed to decrypt stored MFA secret', { module: 'mfa-handler', error });
|
|
88
|
+
(0, cli_utilities_1.handleAndLogError)(error, { module: 'mfa-handler' }, cli_utilities_1.messageHandler.parse('CLI_AUTH_MFA_DECRYPT_FAILED'));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (secret) {
|
|
93
|
+
try {
|
|
94
|
+
const code = this.generateMFACode(secret);
|
|
95
|
+
cli_utilities_1.log.debug('Generated MFA code', { module: 'mfa-handler', source });
|
|
96
|
+
return code;
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
cli_utilities_1.log.debug('Failed to generate MFA code', { module: 'mfa-handler', error, source });
|
|
100
|
+
cli_utilities_1.cliux.print('CLI_AUTH_MFA_GENERATION_FAILED', { color: 'yellow' });
|
|
101
|
+
cli_utilities_1.cliux.print('CLI_AUTH_MFA_RECONFIGURE_HINT');
|
|
102
|
+
cli_utilities_1.cliux.print('For more information about MFA, visit: https://www.contentstack.com/docs/developers/security/multi-factor-authentication', { color: 'yellow' });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Gets MFA code through manual user input
|
|
108
|
+
* @returns Promise<string> The MFA code
|
|
109
|
+
* @throws Error if code format is invalid
|
|
110
|
+
*/
|
|
111
|
+
async getManualMFACode() {
|
|
112
|
+
try {
|
|
113
|
+
const code = await (0, interactive_1.askOTP)();
|
|
114
|
+
if (!/^\d{6}$/.test(code)) {
|
|
115
|
+
throw new Error(cli_utilities_1.messageHandler.parse('CLI_AUTH_MFA_INVALID_CODE'));
|
|
116
|
+
}
|
|
117
|
+
return code;
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
cli_utilities_1.log.debug('Failed to get MFA code', { module: 'mfa-handler', error });
|
|
121
|
+
throw error;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Validates an MFA code format
|
|
126
|
+
* @param code The MFA code to validate
|
|
127
|
+
* @returns boolean True if valid, false otherwise
|
|
128
|
+
*/
|
|
129
|
+
isValidMFACode(code) {
|
|
130
|
+
return /^\d{6}$/.test(code);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
exports.default = new MFAHandler();
|
package/messages/index.json
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"CLI_AUTH_LOGOUT_ALREADY": "You're already logged out",
|
|
21
21
|
"CLI_AUTH_LOGOUT_NO_AUTHORIZATIONS": "No authorizations found",
|
|
22
22
|
"CLI_AUTH_LOGOUT_NO_AUTHORIZATIONS_USER": "No authorizations found for current user",
|
|
23
|
-
"CLI_AUTH_WHOAMI_LOGGED_IN_AS": "You are currently logged in with email
|
|
23
|
+
"CLI_AUTH_WHOAMI_LOGGED_IN_AS": "You are currently logged in with email:",
|
|
24
24
|
"CLI_AUTH_WHOAMI_FAILED": "Failed to get the current user details",
|
|
25
25
|
"CLI_AUTH_WHOAMI_DESCRIPTION": "Display current users email address",
|
|
26
26
|
"CLI_AUTH_TOKENS_ADD_ASK_TOKEN_ALIAS": "Provide alias to store token",
|
|
@@ -49,5 +49,16 @@
|
|
|
49
49
|
"CLI_AUTH_TOKENS_VALIDATION_INVALID_API_KEY": "Invalid api key",
|
|
50
50
|
"CLI_AUTH_EXIT_PROCESS": "Exiting the process...",
|
|
51
51
|
"CLI_SELECT_TOKEN_TYPE": "Select the type of token to add",
|
|
52
|
-
"
|
|
52
|
+
"CLI_AUTH_MFA_INVALID_SECRET": "Invalid MFA secret format. Verify your authentication setup.",
|
|
53
|
+
"CLI_AUTH_MFA_GENERATION_FAILED": "Failed to generate MFA code. Switching to manual MFA code entry.",
|
|
54
|
+
"CLI_AUTH_MFA_DECRYPT_FAILED": "Failed to decrypt stored MFA secret. Try Resetting the MFA secret. Proceeding for Manual MFA code input.",
|
|
55
|
+
"CLI_AUTH_MFA_INVALID_CODE": "Invalid authentication code format.",
|
|
56
|
+
"CLI_AUTH_MFA_RECONFIGURE_HINT": "Consider reconfiguring MFA using config:mfa:add",
|
|
57
|
+
"CLI_AUTH_SMS_OTP_FAILED": "Failed to send SMS OTP. Try again or use a different two-factor authentication method.",
|
|
58
|
+
"CLI_AUTH_2FA_FAILED": "Two-factor authentication failed! Try again.",
|
|
59
|
+
"CLI_AUTH_LOGIN_NO_USER": "No user found with the provided credentials!",
|
|
60
|
+
"CLI_AUTH_LOGIN_NO_CREDENTIALS": "No credentials provided. Enter your email and password to log in.",
|
|
61
|
+
"CLI_AUTH_LOGOUT_NO_TOKEN": "No auth token found. Log in before logging out.",
|
|
62
|
+
"CLI_AUTH_TOKEN_VALIDATION_FAILED": "Token validation failed. Log in again.",
|
|
63
|
+
"CLI_AUTH_TOKEN_VALIDATION_NO_TOKEN": "No auth token found. Log in to continue.."
|
|
53
64
|
}
|
package/oclif.manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contentstack/cli-auth",
|
|
3
3
|
"description": "Contentstack CLI plugin for authentication activities",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.6.0",
|
|
5
5
|
"author": "Contentstack",
|
|
6
6
|
"bugs": "https://github.com/contentstack/cli/issues",
|
|
7
7
|
"scripts": {
|
|
@@ -23,9 +23,10 @@
|
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@contentstack/cli-command": "~1.6.0",
|
|
26
|
-
"@contentstack/cli-utilities": "~1.13.
|
|
26
|
+
"@contentstack/cli-utilities": "~1.13.1",
|
|
27
27
|
"@oclif/core": "^4.3.0",
|
|
28
|
-
"@oclif/plugin-help": "^6.2.28"
|
|
28
|
+
"@oclif/plugin-help": "^6.2.28",
|
|
29
|
+
"otplib": "^12.0.1"
|
|
29
30
|
},
|
|
30
31
|
"devDependencies": {
|
|
31
32
|
"@fancy-test/nock": "^0.1.1",
|