Refactor login code

Users can now get around reCATPCHA without the downloader being built with GUI enabled by using their browser to login
This commit is contained in:
Sude 2023-03-12 15:16:49 +02:00
parent 9e5e833691
commit c26e28a564
3 changed files with 272 additions and 221 deletions

View File

@ -33,6 +33,12 @@ class Website
bool IsLoggedInComplex(const std::string& email);
std::map<std::string, std::string> getTagsFromJson(const Json::Value& json);
int retries;
std::string LoginGetAuthCode(const std::string& email, const std::string& password);
std::string LoginGetAuthCodeCurl(const std::string& login_form_html, const std::string& email, const std::string& password);
std::string LoginGetAuthCodeBrowser(const std::string& auth_url);
#ifdef USE_QT_GUI_LOGIN
std::string LoginGetAuthCodeGUI(const std::string& email, const std::string& password);
#endif
};
#endif // WEBSITE_H

View File

@ -316,18 +316,9 @@ int Downloader::login()
if (!boost::filesystem::remove(Globals::globalConfig.curlConf.sCookiePath))
std::cerr << "Failed to delete " << Globals::globalConfig.curlConf.sCookiePath << std::endl;
int iWebsiteLoginResult = gogWebsite->Login(email, password);
if (iWebsiteLoginResult < 1)
{
std::cerr << "HTTP: Login failed" << std::endl;
return 0;
}
else
{
std::cerr << "HTTP: Login successful" << std::endl;
}
int iLoginResult = gogWebsite->Login(email, password);
if (iWebsiteLoginResult < 2)
if (iLoginResult < 1)
{
std::cerr << "Galaxy: Login failed" << std::endl;
return 0;
@ -335,12 +326,21 @@ int Downloader::login()
else
{
std::cerr << "Galaxy: Login successful" << std::endl;
if (!Globals::galaxyConf.getJSON().empty())
{
this->saveGalaxyJSON();
}
}
if (gogWebsite->IsLoggedIn())
{
std::cerr << "HTTP: Login successful" << std::endl;
}
else
{
std::cerr << "HTTP: Login failed" << std::endl;
return 0;
}
}
}
return 1;

View File

@ -268,6 +268,80 @@ int Website::Login(const std::string& email, const std::string& password)
Globals::galaxyConf.resetClient();
int res = 0;
std::string auth_code;
auth_code = this->LoginGetAuthCode(email, password);
if (!auth_code.empty())
{
std::string token_url = "https://auth.gog.com/token?client_id=" + Globals::galaxyConf.getClientId()
+ "&client_secret=" + Globals::galaxyConf.getClientSecret()
+ "&grant_type=authorization_code&code=" + auth_code
+ "&redirect_uri=" + (std::string)curl_easy_escape(curlhandle, Globals::galaxyConf.getRedirectUri().c_str(), Globals::galaxyConf.getRedirectUri().size());
std::string json = this->getResponse(token_url);
if (!json.empty())
{
Json::Value token_json;
std::istringstream json_stream(json);
try {
json_stream >> token_json;
Globals::galaxyConf.setJSON(token_json);
res = 1;
} catch (const Json::Exception& exc) {
std::cerr << "Failed to parse json" << std::endl << json << std::endl;
std::cerr << exc.what() << std::endl;
}
}
}
else
{
std::cout << "Failed to get auth code" << std::endl;
res = 0;
}
if (res >= 1)
curl_easy_setopt(curlhandle, CURLOPT_COOKIELIST, "FLUSH"); // Write all known cookies to the file specified by CURLOPT_COOKIEJAR
return res;
}
std::string Website::LoginGetAuthCode(const std::string& email, const std::string& password)
{
std::string auth_code;
bool bRecaptcha = false;
std::string auth_url = "https://auth.gog.com/auth?client_id=" + Globals::galaxyConf.getClientId() + "&redirect_uri=" + (std::string)curl_easy_escape(curlhandle, Globals::galaxyConf.getRedirectUri().c_str(), Globals::galaxyConf.getRedirectUri().size()) + "&response_type=code&layout=default&brand=gog";
std::string login_form_html = this->getResponse(auth_url);
if (Globals::globalConfig.iMsgLevel >= MSGLEVEL_DEBUG)
{
std::cerr << "DEBUG INFO (Website::LoginGetAuthCode)" << std::endl;
std::cerr << login_form_html << std::endl;
}
if (login_form_html.find("class=\"g-recaptcha form__recaptcha\"") != std::string::npos)
{
bRecaptcha = true;
}
auth_code = this->LoginGetAuthCodeCurl(login_form_html, email, password);
#ifdef USE_QT_GUI_LOGIN
if (Globals::globalConfig.bEnableLoginGUI && auth_code.empty())
auth_code = this->LoginGetAuthCodeGUI(email, password);
#endif
if (auth_code.empty() && bRecaptcha)
auth_code = this->LoginGetAuthCodeBrowser(auth_url);
return auth_code;
}
std::string Website::LoginGetAuthCodeCurl(const std::string& login_form_html, const std::string& email, const std::string& password)
{
std::string auth_code;
std::string postdata;
std::ostringstream memory;
std::string token;
@ -275,52 +349,7 @@ int Website::Login(const std::string& email, const std::string& password)
std::string tagname_password = "login[password]";
std::string tagname_login = "login[login]";
std::string tagname_token;
std::string auth_url = "https://auth.gog.com/auth?client_id=" + Globals::galaxyConf.getClientId() + "&redirect_uri=" + (std::string)curl_easy_escape(curlhandle, Globals::galaxyConf.getRedirectUri().c_str(), Globals::galaxyConf.getRedirectUri().size()) + "&response_type=code&layout=default&brand=gog";
std::string auth_code;
bool bRecaptcha = false;
std::string login_form_html = this->getResponse(auth_url);
if (Globals::globalConfig.iMsgLevel >= MSGLEVEL_DEBUG)
{
std::cerr << "DEBUG INFO (Website::Login)" << std::endl;
std::cerr << login_form_html << std::endl;
}
if (login_form_html.find("class=\"g-recaptcha form__recaptcha\"") != std::string::npos)
{
bRecaptcha = true;
#ifndef USE_QT_GUI_LOGIN
std::cout << "Login form contains reCAPTCHA (https://www.google.com/recaptcha/)" << std::endl
<< "Try to login later or compile LGOGDownloader with -DUSE_QT_GUI=ON" << std::endl;
return res = 0;
#else
if (!Globals::globalConfig.bEnableLoginGUI)
{
std::cout << "Login form contains reCAPTCHA but GUI login is disabled." << std::endl
<< "Enable GUI login with --enable-login-gui or try to login later." << std::endl;
return res = 0;
}
GuiLogin gl;
gl.Login(email, password);
auto cookies = gl.getCookies();
for (auto cookie : cookies)
{
curl_easy_setopt(curlhandle, CURLOPT_COOKIELIST, cookie.c_str());
}
auth_code = gl.getCode();
#endif
}
if (bRecaptcha)
{
// This should never be reached but do additional check here just in case
#ifndef USE_QT_GUI_LOGIN
return res = 0;
#endif
}
else
{
htmlcxx::HTML::ParserDom parser;
tree<htmlcxx::HTML::Node> login_dom = parser.parseTree(login_form_html);
tree<htmlcxx::HTML::Node>::iterator login_it = login_dom.begin();
@ -341,7 +370,7 @@ int Website::Login(const std::string& email, const std::string& password)
if (token.empty())
{
std::cout << "Failed to get login token" << std::endl;
return res = 0;
return std::string();
}
//Create postdata - escape characters in email/password to support special characters
@ -466,47 +495,63 @@ int Website::Login(const std::string& email, const std::string& password)
{
std::cout << curl_easy_strerror(result) << std::endl;
}
return auth_code;
}
if (this->IsLoggedInComplex(email))
std::string Website::LoginGetAuthCodeBrowser(const std::string& auth_url)
{
res = 1; // Login was successful
}
else
std::string auth_code;
std::string url;
std::cerr << "Login using browser at the following url" << std::endl;
std::cerr << auth_url << std::endl << std::endl;
std::cerr << "then copy & paste the full url here" << std::endl;
std::cerr << "URL: ";
std::getline(std::cin, url);
boost::regex re(".*code=(.*?)([\?&].*|$)", boost::regex_constants::icase);
boost::match_results<std::string::const_iterator> what;
if (boost::regex_search(url, what, re))
{
if (this->IsloggedInSimple())
res = 1; // Login was successful
auth_code = what[1];
}
if (res == 1 && !auth_code.empty())
std::ostringstream memory;
curl_easy_setopt(curlhandle, CURLOPT_WRITEFUNCTION, Util::CurlWriteMemoryCallback);
curl_easy_setopt(curlhandle, CURLOPT_WRITEDATA, &memory);
curl_easy_setopt(curlhandle, CURLOPT_NOPROGRESS, 1);
curl_easy_setopt(curlhandle, CURLOPT_URL, url.c_str());
curl_easy_setopt(curlhandle, CURLOPT_HTTPGET, 1);
curl_easy_setopt(curlhandle, CURLOPT_MAXREDIRS, -1);
curl_easy_setopt(curlhandle, CURLOPT_FOLLOWLOCATION, 1);
CURLcode result = curl_easy_perform(curlhandle);
memory.str(std::string());
if (result != CURLE_OK)
{
std::string token_url = "https://auth.gog.com/token?client_id=" + Globals::galaxyConf.getClientId()
+ "&client_secret=" + Globals::galaxyConf.getClientSecret()
+ "&grant_type=authorization_code&code=" + auth_code
+ "&redirect_uri=" + (std::string)curl_easy_escape(curlhandle, Globals::galaxyConf.getRedirectUri().c_str(), Globals::galaxyConf.getRedirectUri().size());
std::cout << curl_easy_strerror(result) << std::endl;
}
std::string json = this->getResponse(token_url);
if (!json.empty())
return auth_code;
}
#ifdef USE_QT_GUI_LOGIN
std::string Website::LoginGetAuthCodeGUI(const std::string& email, const std::string& password)
{
Json::Value token_json;
std::istringstream json_stream(json);
try {
json_stream >> token_json;
std::string auth_code;
GuiLogin gl;
gl.Login(email, password);
Globals::galaxyConf.setJSON(token_json);
res = 2;
} catch (const Json::Exception& exc) {
std::cerr << "Failed to parse json" << std::endl << json << std::endl;
std::cerr << exc.what() << std::endl;
auto cookies = gl.getCookies();
for (auto cookie : cookies)
{
curl_easy_setopt(curlhandle, CURLOPT_COOKIELIST, cookie.c_str());
}
auth_code = gl.getCode();
return auth_code;
}
}
if (res >= 1)
curl_easy_setopt(curlhandle, CURLOPT_COOKIELIST, "FLUSH"); // Write all known cookies to the file specified by CURLOPT_COOKIEJAR
return res;
}
#endif
bool Website::IsLoggedIn()
{