Add support for different 2fa provider
							parent
							
								
									2751b9bc3b
								
							
						
					
					
						commit
						9f1b241fb5
					
				| 
						 | 
					@ -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.
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue