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
94 changes: 62 additions & 32 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 @@ -471,8 +471,8 @@ plugin.deleteSession = function(session, cb) {
};

plugin.signin = function(user, cb) {
log.debug('running leetcode.signin');
const spin = h.spin('Signing in leetcode.com');
const isCN = config.app === 'leetcode.cn';
const spin = isCN ? h.spin('Signing in leetcode-cn.com') : h.spin('Signing in leetcode.com');
request(config.sys.urls.login, function(e, resp, body) {
spin.stop();
e = plugin.checkError(e, resp, 200);
Expand Down Expand Up @@ -538,11 +538,18 @@ plugin.login = function(user, cb) {
});
};

function parseCookie(cookie, cb) {
function parseCookie(cookie, body, cb) {
const isCN = config.app === 'leetcode.cn';
const SessionPattern = /LEETCODE_SESSION=(.+?)(;|$)/;
const csrfPattern = /csrftoken=(.+?)(;|$)/;
let csrfPattern;
// leetcode-cn.com Cookie is not the same as leetcode.com in third parties
if (isCN) {
csrfPattern = /name="csrfmiddlewaretoken" value="(.*?)"/;
} else {
csrfPattern = /csrftoken=(.+?)(;|$)/;
}
const reSessionResult = SessionPattern.exec(cookie);
const reCsrfResult = csrfPattern.exec(cookie);
const reCsrfResult = csrfPattern.exec(isCN? body: cookie);
if (reSessionResult === null || reCsrfResult === null) {
return cb('invalid cookie?');
}
Expand All @@ -552,11 +559,18 @@ function parseCookie(cookie, cb) {
};
}

function saveAndGetUser(user, cb, cookieData) {
user.sessionId = cookieData.sessionId;
user.sessionCSRF = cookieData.sessionCSRF;
session.saveUser(user);
plugin.getUser(user, cb);
function requestLeetcodeAndSave(request, leetcodeUrl, user, cb) {
request.get({url: leetcodeUrl}, function(e, resp, body) {
const redirectUri = resp.request.uri.href;
if (redirectUri !== config.sys.urls.leetcode_redirect) {
return cb('Login failed. Please make sure the credential is correct.');
}
const cookieData = parseCookie(resp.request.headers.cookie, body, cb);
user.sessionId = cookieData.sessionId;
user.sessionCSRF = cookieData.sessionCSRF;
session.saveUser(user);
plugin.getUser(user, cb);
});
}

plugin.cookieLogin = function(user, cb) {
Expand All @@ -568,15 +582,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 +609,49 @@ 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) {
// 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);
});
} else {
requestLeetcodeAndSave(_request, leetcodeUrl, user, cb);
}
});
});
};

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 +660,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 +677,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);
});
});
};
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"
}
}