Add support for different 2fa provider

pull/81/head
MillenniumEarl 2021-03-11 14:35:57 +01:00
parent 2751b9bc3b
commit 9f1b241fb5
2 changed files with 42 additions and 8 deletions

View File

@ -13,6 +13,10 @@ export const GENERIC = {
* Banner containing any error messages as text. * Banner containing any error messages as text.
*/ */
ERROR_BANNER: "div.p-body-pageContent > div.blockMessage", ERROR_BANNER: "div.p-body-pageContent > div.blockMessage",
/**
* Provider that the platform expects to use to verify the code for two-factor authentication.
*/
EXPECTED_2FA_PROVIDER: 'input[name="provider"]',
/** /**
* Locate the token used for the session. * Locate the token used for the session.
*/ */

View File

@ -32,6 +32,8 @@ type LookupMapCodeT = {
message: string; message: string;
}; };
type ProviderT = "auto" | "totp" | "email";
// Global variables // Global variables
const USER_AGENT = const USER_AGENT =
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15) " + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15) " +
@ -144,11 +146,13 @@ export async function authenticate(
* Send an OTP code if the login procedure requires it. * Send an OTP code if the login procedure requires it.
* @param code OTP code. * @param code OTP code.
* @param token Unique token for the session associated with the credentials in use. * @param token Unique token for the session associated with the credentials in use.
* @param provider Provider used to generate the access code.
* @param trustedDevice If the device in use is trusted, 2FA authentication is not required for 30 days. * @param trustedDevice If the device in use is trusted, 2FA authentication is not required for 30 days.
*/ */
export async function send2faCode( export async function send2faCode(
code: number, code: number,
token: string, token: string,
provider: ProviderT = "auto",
trustedDevice: boolean = false trustedDevice: boolean = false
): Promise<Result<GenericAxiosError, LoginResult>> { ): Promise<Result<GenericAxiosError, LoginResult>> {
// Prepare the parameters to send via POST request // Prepare the parameters to send via POST request
@ -160,20 +164,25 @@ export async function send2faCode(
_xfWithData: "1", _xfWithData: "1",
code: code.toString(), code: code.toString(),
confirm: "1", confirm: "1",
provider: "totp", provider: provider,
remember: "1", remember: "1",
trust: trustedDevice ? "1" : "0" trust: trustedDevice ? "1" : "0"
}; };
// Send 2FA params // Send 2FA params
const response = await fetchPOSTResponse(urls.LOGIN_2FA, params); const response = await fetchPOSTResponse(urls.LOGIN_2FA, params);
return response.applyOnSuccess((r: AxiosResponse<any>) => {
// r.data.status is 'ok' if the authentication is successful // Check if the authentication is valid
const result = r.data.status === "ok"; const validAuth = response.applyOnSuccess((r) => manage2faResponse(r));
const message: string = result ? AUTH_SUCCESSFUL_MESSAGE : r.data.errors.join(",");
const code = messageToCode(message); if (validAuth.isSuccess() && validAuth.value.isSuccess()) {
return new LoginResult(result, code, message); // Valid login
}); return success(validAuth.value.value);
} else if (validAuth.isSuccess() && validAuth.value.isFailure()) {
// Wrong provider, try with another
const expectedProvider = validAuth.value.value;
return await send2faCode(code, token, expectedProvider, trustedDevice);
} else failure(validAuth.value);
} }
/** /**
@ -403,4 +412,25 @@ function messageToCode(message: string): number {
return result ? result.code : LoginResult.UNKNOWN_ERROR; return result ? result.code : LoginResult.UNKNOWN_ERROR;
} }
/**
* Manage the response given by the platform when the 2FA is required.
*/
function manage2faResponse(r: AxiosResponse<any>): Result<ProviderT, LoginResult> {
// The html property exists only if the provider is wrong
const rightProvider = !("html" in r.data);
// Wrong provider!
if (!rightProvider) {
const $ = cheerio.load(r.data.html.content);
const expectedProvider = $(GENERIC.EXPECTED_2FA_PROVIDER).attr("value");
return failure(expectedProvider as ProviderT);
}
// r.data.status is 'ok' if the authentication is successful
const result = r.data.status === "ok";
const message: string = result ? AUTH_SUCCESSFUL_MESSAGE : r.data.errors.join(",");
const loginCode = messageToCode(message);
return success(new LoginResult(result, loginCode, message));
}
//#endregion //#endregion