Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

solve GitHub login two-factor auth problem and add new node package p… #35

Merged
merged 9 commits into from
Dec 30, 2019
54 changes: 36 additions & 18 deletions lib/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,24 +31,42 @@ const DEFAULT_CONFIG = {
'swift'
],
urls: {
base: 'https://leetcode.com',
graphql: 'https://leetcode.com/graphql',
login: 'https://leetcode.com/accounts/login/',
// third part login base urls. TODO facebook google
github_login: 'https://leetcode.com/accounts/github/login/?next=%2F',
facebook_login: 'https://leetcode.com/accounts/facebook/login/?next=%2F',
linkedin_login: 'https://leetcode.com/accounts/linkedin_oauth2/login/?next=%2F',
problems: 'https://leetcode.com/api/problems/$category/',
problem: 'https://leetcode.com/problems/$slug/description/',
test: 'https://leetcode.com/problems/$slug/interpret_solution/',
session: 'https://leetcode.com/session/',
submit: 'https://leetcode.com/problems/$slug/submit/',
submissions: 'https://leetcode.com/api/submissions/$slug',
submission: 'https://leetcode.com/submissions/detail/$id/',
verify: 'https://leetcode.com/submissions/detail/$id/check/',
favorites: 'https://leetcode.com/list/api/questions',
favorite_delete: 'https://leetcode.com/list/api/questions/$hash/$id',
plugin: 'https://raw.githubusercontent.com/leetcode-tools/leetcode-cli-plugins/master/plugins/$name.js'
// base urls
base: 'https://leetcode.com',
graphql: 'https://leetcode.com/graphql',
login: 'https://leetcode.com/accounts/login/',
// third part login base urls. TODO facebook google
github_login: 'https://leetcode.com/accounts/github/login/?next=%2F',
facebook_login: 'https://leetcode.com/accounts/facebook/login/?next=%2F',
linkedin_login: 'https://leetcode.com/accounts/linkedin_oauth2/login/?next=%2F',
// redirect urls
leetcode_redirect: 'https://leetcode.com/',
github_tf_redirect: 'https://github.com/sessions/two-factor',
// simulate login urls
github_login_request: 'https://github.com/login',
github_session_request: 'https://github.com/session',
github_tf_session_request: 'https://github.com/sessions/two-factor',
linkedin_login_request: 'https://www.linkedin.com',
linkedin_session_request: 'https://www.linkedin.com/uas/login-submit',
// questions urls
problems: 'https://leetcode.com/api/problems/$category/',
problem: 'https://leetcode.com/problems/$slug/description/',
test: 'https://leetcode.com/problems/$slug/interpret_solution/',
session: 'https://leetcode.com/session/',
submit: 'https://leetcode.com/problems/$slug/submit/',
submissions: 'https://leetcode.com/api/submissions/$slug',
submission: 'https://leetcode.com/submissions/detail/$id/',
verify: 'https://leetcode.com/submissions/detail/$id/check/',
favorites: 'https://leetcode.com/list/api/questions',
favorite_delete: 'https://leetcode.com/list/api/questions/$hash/$id',
plugin: 'https://raw.githubusercontent.com/leetcode-tools/leetcode-cli-plugins/master/plugins/$name.js'
},
// login methods enum
login_methods: {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This field can also be removed

LeetCode: 'LeetCode',
Cookie: 'Cookie',
GitHub: 'Github',
LinkedIn: 'LinkedIn'
}
},

Expand Down
32 changes: 18 additions & 14 deletions lib/plugins/leetcode.cn.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,24 @@ var plugin = new Plugin(15, 'leetcode.cn', '2018.11.25',

plugin.init = function() {
config.app = 'leetcode.cn';
config.sys.urls.base = 'https://leetcode-cn.com';
config.sys.urls.login = 'https://leetcode-cn.com/accounts/login/';
config.sys.urls.problems = 'https://leetcode-cn.com/api/problems/$category/';
config.sys.urls.problem = 'https://leetcode-cn.com/problems/$slug/description/';
config.sys.urls.graphql = 'https://leetcode-cn.com/graphql';
config.sys.urls.problem_detail = 'https://leetcode-cn.com/graphql';
config.sys.urls.test = 'https://leetcode-cn.com/problems/$slug/interpret_solution/';
config.sys.urls.session = 'https://leetcode-cn.com/session/';
config.sys.urls.submit = 'https://leetcode-cn.com/problems/$slug/submit/';
config.sys.urls.submissions = 'https://leetcode-cn.com/api/submissions/$slug';
config.sys.urls.submission = 'https://leetcode-cn.com/submissions/detail/$id/';
config.sys.urls.verify = 'https://leetcode-cn.com/submissions/detail/$id/check/';
config.sys.urls.favorites = 'https://leetcode-cn.com/list/api/questions';
config.sys.urls.favorite_delete = 'https://leetcode-cn.com/list/api/questions/$hash/$id';
config.sys.urls.base = 'https://leetcode-cn.com';
config.sys.urls.login = 'https://leetcode-cn.com/accounts/login/';
config.sys.urls.problems = 'https://leetcode-cn.com/api/problems/$category/';
config.sys.urls.problem = 'https://leetcode-cn.com/problems/$slug/description/';
config.sys.urls.graphql = 'https://leetcode-cn.com/graphql';
config.sys.urls.problem_detail = 'https://leetcode-cn.com/graphql';
config.sys.urls.test = 'https://leetcode-cn.com/problems/$slug/interpret_solution/';
config.sys.urls.session = 'https://leetcode-cn.com/session/';
config.sys.urls.submit = 'https://leetcode-cn.com/problems/$slug/submit/';
config.sys.urls.submissions = 'https://leetcode-cn.com/api/submissions/$slug';
config.sys.urls.submission = 'https://leetcode-cn.com/submissions/detail/$id/';
config.sys.urls.verify = 'https://leetcode-cn.com/submissions/detail/$id/check/';
config.sys.urls.favorites = 'https://leetcode-cn.com/list/api/questions';
config.sys.urls.favorite_delete = 'https://leetcode-cn.com/list/api/questions/$hash/$id';
// third parties
config.sys.urls.github_login = 'https://leetcode-cn.com/accounts/github/login/?next=%2F';
config.sys.urls.linkedin_login = 'https://leetcode-cn.com/accounts/linkedin_oauth2/login/?next=%2F';
config.sys.urls.leetcode_redirect = 'https://leetcode-cn.com/';
};

// FIXME: refactor those
Expand Down
96 changes: 69 additions & 27 deletions lib/plugins/leetcode.js
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ plugin.getSubmissions = function(problem, cb) {

// FIXME: this only return the 1st 20 submissions, we should get next if necessary.
const submissions = JSON.parse(body).submissions_dump;
for (let submission of submissions)
for (const submission of submissions)
submission.id = _.last(_.compact(submission.url.split('/')));

return cb(null, submissions);
Expand Down Expand Up @@ -552,11 +552,36 @@ function parseCookie(cookie, cb) {
};
}

function saveAndGetUser(user, cb, cookieData) {
user.sessionId = cookieData.sessionId;
user.sessionCSRF = cookieData.sessionCSRF;
session.saveUser(user);
plugin.getUser(user, cb);
// leetcode-cn.com Cookie is not the same as leetcode.com in third parties
function parseCNCookie(cookie, body, cb) {
const SessionPattern = /LEETCODE_SESSION=(.+?)(;|$)/;
const csrfPattern = /name="csrfmiddlewaretoken" value="(.*?)"/;
const reSessionResult = SessionPattern.exec(cookie);
const reCsrfResult = csrfPattern.exec(body);
if (reSessionResult === null || reCsrfResult === null) {
return cb('invalid cookie?');
}
return {
sessionId: reSessionResult[1],
sessionCSRF: reCsrfResult[1],
};
}

function requestLeetcodeAndSave(request, leetcodeUrl, user, cb, party) {
request.get({url: leetcodeUrl}, function(e, resp, body) {
const redirectUri = resp.request.uri.href;
if (redirectUri !== config.sys.urls.leetcode_redirect) {
return cb(`${party} login failed or ${party} did not connect to LeetCode`);
jdneo marked this conversation as resolved.
Show resolved Hide resolved
}
let cookieData = {};
if (leetcodeUrl.includes('cn')) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cn -> leetcode-cn.com to avoid mismatch

cookieData = parseCNCookie(resp.request.headers.cookie, body, cb);
} else cookieData = parseCookie(resp.request.headers.cookie, cb);
jdneo marked this conversation as resolved.
Show resolved Hide resolved
user.sessionId = cookieData.sessionId;
user.sessionCSRF = cookieData.sessionCSRF;
session.saveUser(user);
plugin.getUser(user, cb);
});
}

plugin.cookieLogin = function(user, cb) {
Expand All @@ -568,15 +593,16 @@ plugin.cookieLogin = function(user, cb) {
};

plugin.githubLogin = function(user, cb) {
const leetcodeUrl = config.sys.urls.github_login;
const urls = config.sys.urls;
const leetcodeUrl = urls.github_login;
const _request = request.defaults({jar: true});
_request('https://github.com/login', function(e, resp, body) {
_request(urls.github_login_request, function(e, resp, body) {
const authenticityToken = body.match(/name="authenticity_token" value="(.*?)"/);
if (authenticityToken === null) {
return cb('Get GitHub token failed');
}
const options = {
url: 'https://github.com/session',
url: urls.github_session_request,
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Expand All @@ -594,27 +620,50 @@ plugin.githubLogin = function(user, cb) {
if (resp.statusCode !== 200) {
return cb('GitHub login failed');
}
_request.get({url: leetcodeUrl}, function(e, resp, body) {
const redirectUri = resp.request.uri.href;
if (redirectUri !== 'https://leetcode.com/') {
return cb('GitHub login failed or GitHub did not link to LeetCode');
if (resp.request.uri.href === urls.github_tf_redirect) {
cb('Your GitHub are using two-factor authentication');
jdneo marked this conversation as resolved.
Show resolved Hide resolved
// read two-factor code must be sync.
const twoFactorcode = require('prompt-sync')()('Please enter your two-factor code: ');
const authenticityTokenTwoFactor = body.match(/name="authenticity_token" value="(.*?)"/);
if (authenticityTokenTwoFactor === null) {
return cb('Get GitHub two-factor token failed');
}
const cookieData = parseCookie(resp.request.headers.cookie, cb);
saveAndGetUser(user, cb, cookieData);
});
const optionsTwoFactor = {
url: urls.github_tf_session_request,
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
followAllRedirects: true,
form: {
'otp': twoFactorcode,
'authenticity_token': authenticityTokenTwoFactor[1],
'utf8': encodeURIComponent('✓'),
},
};
_request(optionsTwoFactor, function(e, resp, body) {
if (resp.request.uri.href === urls.github_tf_session_request) {
return cb('Invalid two-factor code please check');
}
requestLeetcodeAndSave(_request, leetcodeUrl, user, cb, config.sys.login_methods.GitHub);
});
} else {
requestLeetcodeAndSave(_request, leetcodeUrl, user, cb, config.sys.login_methods.GitHub);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the calling of requestLeetcodeAndSave in line 648 and line 651 merged into one call -- move it out of the if-else block?

Copy link
Author

@yihong0618 yihong0618 Dec 30, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, these two are with different session(_request), the first one is two-factor, the second one is normal. Both need.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. How about

if (resp.request.uri.href !== urls.github_tf_redirect) {
  return requestLeetcodeAndSave(_request, leetcodeUrl, user, cb, config.sys.login_methods.GitHub);
}

cb('Your GitHub are using two-factor authentication');
// read two-factor code must be sync.
const twoFactorcode = require('prompt-sync')()('Please enter your two-factor code: ');
const authenticityTokenTwoFactor = body.match(/name="authenticity_token" value="(.*?)"/);
if (authenticityTokenTwoFactor === null) {
  return cb('Get GitHub two-factor token failed');
}
const optionsTwoFactor = {
  url:     urls.github_tf_session_request,
  method:  'POST',
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded',
  },
  followAllRedirects: true,
  form:               {
    'otp':                twoFactorcode,
    'authenticity_token': authenticityTokenTwoFactor[1],
    'utf8':               encodeURIComponent('✓'),
  },
};
_request(optionsTwoFactor, function(e, resp, body) {
  if (resp.request.uri.href === urls.github_tf_session_request) {
    return cb('Invalid two-factor code please check');
  }
  requestLeetcodeAndSave(_request, leetcodeUrl, user, cb, config.sys.login_methods.GitHub);
});

which can save some indentions.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! Very clean code. Thank you.

}
});
});
};

plugin.linkedinLogin = function(user, cb) {
const leetcodeUrl = config.sys.urls.linkedin_login;
const urls = config.sys.urls;
const leetcodeUrl = urls.linkedin_login;
const _request = request.defaults({
jar: true,
headers: {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36'
}
});
_request('https://www.linkedin.com', function(e, resp, body) {
_request(urls.linkedin_login_request, function(e, resp, body) {
if ( resp.statusCode !== 200) {
return cb('Get LinkedIn session failed');
}
Expand All @@ -623,7 +672,7 @@ plugin.linkedinLogin = function(user, cb) {
return cb('Get LinkedIn token failed');
}
const options = {
url: 'https://www.linkedin.com/uas/login-submit',
url: urls.linkedin_session_request,
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Expand All @@ -640,14 +689,7 @@ plugin.linkedinLogin = function(user, cb) {
if (resp.statusCode !== 200) {
return cb('LinkedIn login failed');
}
_request.get({url: leetcodeUrl}, function(e, resp, body) {
const redirectUri = resp.request.uri.href;
if (redirectUri !== 'https://leetcode.com/') {
return cb('LinkedIn login failed or LinkedIn did not link to LeetCode');
}
const cookieData = parseCookie(resp.request.headers.cookie, cb);
saveAndGetUser(user, cb, cookieData);
});
requestLeetcodeAndSave(_request, leetcodeUrl, user, cb, config.sys.login_methods.LinkedIn);
});
});
};
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
"nock": "10.0.2",
"nyc": "^13.3.0",
"pkg": "^4.3.4",
"prompt-sync": "^4.2.0",
"rewire": "4.0.1"
}
}