internal static HttpCookies LogInMfa(UserInfo userInfo, R.LogInResult logInResult, string token, IUi ui, ISecureStorage storage, RestClient rest) { void CheckCancel(Ui.Passcode passcode) { if (passcode == Ui.Passcode.Cancel) { throw new CanceledMultiFactorException("Second factor step is canceled by the user"); } } var methods = logInResult.MfaMethods?.AllowedMethods; if (methods == null) { throw MakeInvalidResponseError("allowed MFA methods not found"); } Ui.Passcode code; if (methods.Contains("totp")) { code = ui.ProvideGoogleAuthPasscode(); CheckCancel(code); SubmitTotp(userInfo, code, token, logInResult.MfaToken, rest); } else if (methods.Contains("yubikey")) { throw new UnsupportedFeatureException( $"Zoho removed support for the classic YubiKey. FIDO U2F is not supported yet."); } else { var unsupportedMethods = methods.JoinToString(", "); throw new UnsupportedFeatureException($"MFA methods '{unsupportedMethods}' are not supported"); } // Has to be done regardless of the "remember me" setting return(MarkDeviceTrusted(userInfo, code.RememberMe, token, logInResult.MfaToken, rest)); }
// Returns a valid session or throws internal static Session LoginWithOtp(string username, string password, int keyIterationCount, OtpMethod method, ClientInfo clientInfo, IUi ui, RestClient rest) { var passcode = method switch { OtpMethod.GoogleAuth => ui.ProvideGoogleAuthPasscode(), OtpMethod.MicrosoftAuth => ui.ProvideMicrosoftAuthPasscode(), OtpMethod.Yubikey => ui.ProvideYubikeyPasscode(), _ => throw new InternalErrorException("Invalid OTP method") }; if (passcode == OtpResult.Cancel) { throw new CanceledMultiFactorException("Second factor step is canceled by the user"); } var response = PerformSingleLoginRequest(username, password, keyIterationCount, new Dictionary <string, object> { ["otp"] = passcode.Passcode }, clientInfo, rest); var session = ExtractSessionFromLoginResponse(response, keyIterationCount, clientInfo); if (session == null) { throw MakeLoginError(response); } if (passcode.RememberMe) { MarkDeviceAsTrusted(session, clientInfo, rest); } return(session); }
internal static string Login(string username, byte[] passwordHash, string deviceId, IUi ui, ISecureStorage storage, RestClient rest) { // Try simple password login, potentially with a stored second factor token if // "remember me" was used before. var rememberMeOptions = GetRememberMeOptions(storage); var response = RequestAuthToken(username, passwordHash, deviceId, rememberMeOptions, rest); // Simple password login (no 2FA) succeeded if (response.AuthToken != null) { return(response.AuthToken); } var secondFactor = response.SecondFactor; if (secondFactor.Methods == null || secondFactor.Methods.Count == 0) { throw new InternalErrorException("Expected a non empty list of available 2FA methods"); } // We had a "remember me" token saved, but the login failed anyway. This token is not valid anymore. if (rememberMeOptions != null) { EraseRememberMeToken(storage); } var method = ChooseSecondFactorMethod(secondFactor, ui); var extra = secondFactor.Methods[method]; Passcode passcode = null; switch (method) { case Response.SecondFactorMethod.GoogleAuth: passcode = ui.ProvideGoogleAuthPasscode(); break; case Response.SecondFactorMethod.Email: // When only the email 2FA present, the email is sent by the server right away. // Trigger only when other methods are present. if (secondFactor.Methods.Count != 1) { TriggerEmailMfaPasscode(username, passwordHash, rest); } passcode = ui.ProvideEmailPasscode((string)extra["Email"] ?? ""); break; case Response.SecondFactorMethod.Duo: case Response.SecondFactorMethod.DuoOrg: { var duo = Duo.Authenticate((string)extra["Host"] ?? "", (string)extra["Signature"] ?? "", ui, rest.Transport); if (duo != null) { passcode = new Passcode(duo.Passcode, duo.RememberMe); } break; } case Response.SecondFactorMethod.YubiKey: passcode = ui.ProvideYubiKeyPasscode(); break; case Response.SecondFactorMethod.U2f: passcode = AskU2fPasscode(JObject.Parse((string)extra["Challenge"]), ui); break; default: throw new UnsupportedFeatureException($"2FA method {method} is not supported"); } // We're done interacting with the UI ui.Close(); if (passcode == null) { throw MakeCancelledMfaError(); } var secondFactorResponse = RequestAuthToken(username, passwordHash, deviceId, new SecondFactorOptions(method, passcode.Code, passcode.RememberMe), rest); // Password + 2FA is successful if (secondFactorResponse.AuthToken != null) { SaveRememberMeToken(secondFactorResponse, storage); return(secondFactorResponse.AuthToken); } throw new BadMultiFactorException("Second factor code is not correct"); }