1
- import { cliux , CLIError , log , cliErrorHandler } from '@contentstack/cli-utilities' ;
1
+ import { cliux , log , handleAndLogError , messageHandler } from '@contentstack/cli-utilities' ;
2
2
import { User } from '../interfaces' ;
3
3
import { askOTPChannel , askOTP } from './interactive' ;
4
4
@@ -27,9 +27,61 @@ class AuthHandler {
27
27
* @returns {Promise } Promise object returns authtoken on success
28
28
* TBD: take out the otp implementation from login and create a new method/function to handle otp
29
29
*/
30
+ /**
31
+ * Handle the OTP flow for 2FA authentication
32
+ * @param tfaToken Optional pre-provided TFA token
33
+ * @param loginPayload Login payload containing user credentials
34
+ * @returns Promise<string> The TFA token to use for authentication
35
+ */
36
+ private async handleOTPFlow ( tfaToken ?: string , loginPayload ?: any ) : Promise < string > {
37
+ try {
38
+ if ( tfaToken ) {
39
+ log . info ( 'Using provided TFA token' , { module : 'auth-handler' } ) ;
40
+ return tfaToken ;
41
+ }
42
+
43
+ log . debug ( '2FA required, requesting OTP channel' , { module : 'auth-handler' } ) ;
44
+ const otpChannel = await askOTPChannel ( ) ;
45
+ log . debug ( `OTP channel selected: ${ otpChannel } ` , { module : 'auth-handler' } ) ;
46
+
47
+ if ( otpChannel === 'sms' ) {
48
+ try {
49
+ await this . requestSMSOTP ( loginPayload ) ;
50
+ } catch ( error ) {
51
+ log . debug ( 'SMS OTP request failed' , { module : 'auth-handler' , error } ) ;
52
+ cliux . print ( 'CLI_AUTH_SMS_OTP_FAILED' , { color : 'red' } ) ;
53
+ throw error ;
54
+ }
55
+ }
56
+
57
+ log . debug ( 'Requesting OTP input' , { module : 'auth-handler' , channel : otpChannel } ) ;
58
+ return await askOTP ( ) ;
59
+ } catch ( error ) {
60
+ log . debug ( '2FA flow failed' , { module : 'auth-handler' , error } ) ;
61
+ throw error ;
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Request SMS OTP for 2FA authentication
67
+ * @param loginPayload Login payload containing user credentials
68
+ * @throws CLIError if SMS request fails
69
+ */
70
+ private async requestSMSOTP ( loginPayload : any ) : Promise < void > {
71
+ log . debug ( 'Sending SMS OTP request' , { module : 'auth-handler' } ) ;
72
+ try {
73
+ await this . _client . axiosInstance . post ( '/user/request_token_sms' , { user : loginPayload } ) ;
74
+ log . debug ( 'SMS OTP request successful' , { module : 'auth-handler' } ) ;
75
+ cliux . print ( 'CLI_AUTH_LOGIN_SECURITY_CODE_SEND_SUCCESS' ) ;
76
+ } catch ( error ) {
77
+ log . debug ( 'SMS OTP request failed' , { module : 'auth-handler' , error } ) ;
78
+ throw error ;
79
+ }
80
+ }
81
+
30
82
async login ( email : string , password : string , tfaToken ?: string ) : Promise < User > {
31
- const hasCredentials = ! ! password ;
32
- const hasTfaToken = ! ! tfaToken ;
83
+ const hasCredentials = typeof password === 'string' && password . length > 0 ;
84
+ const hasTfaToken = typeof tfaToken === 'string' && tfaToken . length > 0 ;
33
85
log . debug ( 'Starting login process' , {
34
86
module : 'auth-handler' ,
35
87
email,
@@ -49,11 +101,9 @@ class AuthHandler {
49
101
log . debug ( 'Adding TFA token to login payload' , { module : 'auth-handler' } ) ;
50
102
}
51
103
52
- const hasCredentials = ! ! password ;
53
- const hasTfaTokenPresent = ! ! tfaToken ;
54
104
log . debug ( 'Making login API call' , {
55
105
module : 'auth-handler' ,
56
- payload : { email, hasCredentials, hasTfaTokenPresent } ,
106
+ payload : { email, hasCredentials, hasTfaToken } ,
57
107
} ) ;
58
108
59
109
this . _client
@@ -69,46 +119,24 @@ class AuthHandler {
69
119
log . debug ( 'Login successful, user found' , { module : 'auth-handler' , userEmail : result . user . email } ) ;
70
120
resolve ( result . user as User ) ;
71
121
} else if ( result . error_code === 294 ) {
72
- log . debug ( 'TFA required, requesting OTP channel' , { module : 'auth-handler' } ) ;
73
- const otpChannel = await askOTPChannel ( ) ;
74
- log . debug ( `OTP channel selected: ${ otpChannel } ` , { module : 'auth-handler' } ) ;
75
-
76
- // need to send sms to the mobile
77
- if ( otpChannel === 'sms' ) {
78
- log . debug ( 'Sending SMS OTP request' , { module : 'auth-handler' } ) ;
79
- try {
80
- await this . _client . axiosInstance . post ( '/user/request_token_sms' , { user : loginPayload } ) ;
81
- log . debug ( 'SMS OTP request successful' , { module : 'auth-handler' } ) ;
82
- cliux . print ( 'CLI_AUTH_LOGIN_SECURITY_CODE_SEND_SUCCESS' ) ;
83
- } catch ( error ) {
84
- log . debug ( 'SMS OTP request failed' , { module : 'auth-handler' , error } ) ;
85
- const err = cliErrorHandler . classifyError ( error ) ;
86
- reject ( err ) ;
87
- return ;
88
- }
89
- }
90
-
91
- log . debug ( 'Requesting OTP input from user' , { module : 'auth-handler' } ) ;
92
- const tfToken = await askOTP ( ) ;
93
- log . debug ( 'OTP received, retrying login' , { module : 'auth-handler' } ) ;
122
+ const tfToken = await this . handleOTPFlow ( tfaToken , loginPayload ) ;
94
123
95
124
try {
96
125
resolve ( await this . login ( email , password , tfToken ) ) ;
97
126
} catch ( error ) {
98
127
log . debug ( 'Login with TFA token failed' , { module : 'auth-handler' , error } ) ;
99
- const err = cliErrorHandler . classifyError ( error ) ;
100
- reject ( err ) ;
101
- return ;
128
+ cliux . print ( 'CLI_AUTH_2FA_FAILED' , { color : 'red' } ) ;
129
+ reject ( error ) ;
102
130
}
103
131
} else {
104
132
log . debug ( 'Login failed - no user found' , { module : 'auth-handler' , result } ) ;
105
- reject ( new CLIError ( { message : 'No user found with the credentials' } ) ) ;
133
+ reject ( new Error ( messageHandler . parse ( 'CLI_AUTH_LOGIN_NO_USER' ) ) ) ;
106
134
}
107
135
} )
108
136
. catch ( ( error : any ) => {
109
- log . debug ( 'Login API call failed' , { module : 'auth-handler' , error : error . message || error } ) ;
110
- const err = cliErrorHandler . classifyError ( error ) ;
111
- reject ( err ) ;
137
+ log . debug ( 'Login API call failed' , { module : 'auth-handler' , error : error ?. errorMessage || error } ) ;
138
+ cliux . print ( 'CLI_AUTH_LOGIN_FAILED' , { color : 'yellow' } ) ;
139
+ handleAndLogError ( error , { module : 'auth-handler' } ) ;
112
140
} ) ;
113
141
} else {
114
142
const hasEmail = ! ! email ;
@@ -118,7 +146,8 @@ class AuthHandler {
118
146
hasEmail,
119
147
hasCredentials,
120
148
} ) ;
121
- reject ( new CLIError ( { message : 'No credential found to login' } ) ) ;
149
+ log . debug ( 'Login failed - missing credentials' , { module : 'auth-handler' , hasEmail, hasCredentials } ) ;
150
+ reject ( new Error ( messageHandler . parse ( 'CLI_AUTH_LOGIN_NO_CREDENTIALS' ) ) ) ;
122
151
}
123
152
} ) ;
124
153
}
@@ -143,12 +172,12 @@ class AuthHandler {
143
172
} )
144
173
. catch ( ( error : Error ) => {
145
174
log . debug ( 'Logout API call failed' , { module : 'auth-handler' , error : error . message } ) ;
146
- const err = cliErrorHandler . classifyError ( error ) ;
147
- reject ( err ) ;
175
+ cliux . print ( 'CLI_AUTH_LOGOUT_FAILED' , { color : 'yellow' } ) ;
176
+ reject ( error ) ;
148
177
} ) ;
149
178
} else {
150
179
log . debug ( 'Logout failed - no auth token provided' , { module : 'auth-handler' } ) ;
151
- reject ( new CLIError ( { message : 'No auth token found to logout' } ) ) ;
180
+ reject ( new Error ( messageHandler . parse ( 'CLI_AUTH_LOGOUT_NO_TOKEN' ) ) ) ;
152
181
}
153
182
} ) ;
154
183
}
@@ -173,12 +202,12 @@ class AuthHandler {
173
202
} )
174
203
. catch ( ( error : Error ) => {
175
204
log . debug ( 'Token validation failed' , { module : 'auth-handler' , error : error . message } ) ;
176
- const err = cliErrorHandler . classifyError ( error ) ;
177
- reject ( err ) ;
205
+ cliux . print ( 'CLI_AUTH_TOKEN_VALIDATION_FAILED' , { color : 'yellow' } ) ;
206
+ handleAndLogError ( error , { module : 'auth-handler' } ) ;
178
207
} ) ;
179
208
} else {
180
209
log . debug ( 'Token validation failed - no auth token provided' , { module : 'auth-handler' } ) ;
181
- reject ( new CLIError ( { message : 'No auth token found to validate' } ) ) ;
210
+ reject ( new Error ( messageHandler . parse ( 'CLI_AUTH_TOKEN_VALIDATION_NO_TOKEN' ) ) ) ;
182
211
}
183
212
} ) ;
184
213
}
0 commit comments