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"); }