-
Notifications
You must be signed in to change notification settings - Fork 16
Expand file tree
/
Copy pathauth-handler.ts
More file actions
187 lines (172 loc) · 6.75 KB
/
auth-handler.ts
File metadata and controls
187 lines (172 loc) · 6.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
import { cliux, log, messageHandler } from '@contentstack/cli-utilities';
import { User } from '../interfaces';
import { askOTPChannel, askOTP } from './interactive';
/**
* @class
* Auth handler
*/
class AuthHandler {
private _client;
private _host;
set client(contentStackClient) {
log.debug('Setting Contentstack client.', { module: 'auth-handler' });
this._client = contentStackClient;
}
set host(contentStackHost) {
log.debug(`Setting Contentstack host: ${contentStackHost}`, { module: 'auth-handler' });
this._host = contentStackHost;
}
/**
*
*
* Login into Contentstack
* @param {string} email Contentstack email address
* @param {string} password User's password for contentstack account
* @returns {Promise} Promise object returns authtoken on success
* TBD: take out the otp implementation from login and create a new method/function to handle otp
*/
/**
* Handle the OTP flow for 2FA authentication
* @param tfaToken Optional pre-provided TFA token
* @param loginPayload Login payload containing user credentials
* @returns Promise<string> The TFA token to use for authentication
*/
private async handleOTPFlow(tfaToken?: string, loginPayload?: any): Promise<string> {
try {
if (tfaToken) {
log.info('Using provided TFA token', { module: 'auth-handler' });
return tfaToken;
}
log.debug('2FA required, requesting OTP channel', { module: 'auth-handler' });
const otpChannel = await askOTPChannel();
log.debug(`OTP channel selected: ${otpChannel}`, { module: 'auth-handler' });
if (otpChannel === 'sms') {
try {
await this.requestSMSOTP(loginPayload);
} catch (error) {
log.debug('SMS OTP request failed', { module: 'auth-handler', error });
cliux.print('CLI_AUTH_SMS_OTP_FAILED', { color: 'red' });
throw error;
}
}
log.debug('Requesting OTP input', { module: 'auth-handler', channel: otpChannel });
return await askOTP();
} catch (error) {
log.debug('2FA flow failed', { module: 'auth-handler', error });
throw error;
}
}
/**
* Request SMS OTP for 2FA authentication
* @param loginPayload Login payload containing user credentials
* @throws CLIError if SMS request fails
*/
private async requestSMSOTP(loginPayload: any): Promise<void> {
log.debug('Sending SMS OTP request.', { module: 'auth-handler' });
try {
await this._client.axiosInstance.post('/user/request_token_sms', { user: loginPayload });
log.debug('SMS OTP request successful.', { module: 'auth-handler' });
cliux.print('CLI_AUTH_LOGIN_SECURITY_CODE_SEND_SUCCESS');
} catch (error) {
log.debug('SMS OTP request failed.', { module: 'auth-handler', error });
throw error;
}
}
async login(email: string, password: string, tfaToken?: string): Promise<User> {
const hasCredentials = typeof password === 'string' && password.length > 0;
const hasTfaToken = typeof tfaToken === 'string' && tfaToken.length > 0;
log.debug('Starting login process', {
module: 'auth-handler',
email,
hasCredentials,
hasTfaToken,
});
return new Promise((resolve, reject) => {
if (email && password) {
const loginPayload: {
email: string;
password: string;
tfa_token?: string;
} = { email, password };
if (tfaToken) {
loginPayload.tfa_token = tfaToken;
log.debug('Adding TFA token to login payload.', { module: 'auth-handler' });
}
log.debug('Making login API call', {
module: 'auth-handler',
payload: { email, hasCredentials, hasTfaToken },
});
this._client
.login(loginPayload)
.then(async (result: any) => {
log.debug('Login API response received', {
module: 'auth-handler',
hasUser: !!result.user,
errorCode: result.error_code,
});
if (result.user) {
log.debug('Login successful, user found', { module: 'auth-handler', userEmail: result.user.email });
resolve(result.user as User);
} else {
log.debug('Login failed: no user found.', { module: 'auth-handler', result });
reject(new Error(messageHandler.parse('CLI_AUTH_LOGIN_NO_USER')));
}
})
.catch(async (error: any) => {
if (error.errorCode === 294) {
try {
const tfToken = await this.handleOTPFlow(tfaToken, loginPayload);
resolve(await this.login(email, password, tfToken));
} catch (error) {
log.debug('Login with TFA token failed.', { module: 'auth-handler', error });
cliux.print('CLI_AUTH_2FA_FAILED', { color: 'red' });
reject(error);
}
} else {
log.debug('Login API call failed.', { module: 'auth-handler', error: error?.errorMessage || error });
cliux.print('CLI_AUTH_LOGIN_FAILED', { color: 'yellow' });
reject(error);
}
});
} else {
const hasEmail = !!email;
const hasCredentials = !!password;
log.debug('Login failed - missing credentials', {
module: 'auth-handler',
hasEmail,
hasCredentials,
});
log.debug('Login failed - missing credentials', { module: 'auth-handler', hasEmail, hasCredentials });
reject(new Error(messageHandler.parse('CLI_AUTH_LOGIN_NO_CREDENTIALS')));
}
});
}
/**
* Logout from Contentstack
* @param {string} authtoken authtoken that needs to invalidated when logging out
* @returns {Promise} Promise object returns response object from Contentstack
*/
async logout(authtoken: string): Promise<object> {
log.debug('Starting logout process.', { module: 'auth-handler', hasAuthToken: !!authtoken });
return new Promise((resolve, reject) => {
if (authtoken) {
log.debug('Making logout API call.', { module: 'auth-handler' });
this._client
.logout(authtoken)
.then(function (response: object) {
log.debug('Logout API call successful.', { module: 'auth-handler', response });
return resolve(response);
})
.catch((error: Error) => {
log.debug('Logout API call failed.', { module: 'auth-handler', error: error.message });
cliux.print('CLI_AUTH_LOGOUT_FAILED', { color: 'yellow' });
reject(error);
});
} else {
log.debug('Logout failed: no auth token provided.', { module: 'auth-handler' });
reject(new Error(messageHandler.parse('CLI_AUTH_LOGOUT_NO_TOKEN')));
}
});
}
}
export default new AuthHandler();