Skip to content

Commit a11e137

Browse files
yihong0618jdneo
authored andcommitted
solve GitHub login two-factor auth problem (#35)
1 parent dae0c8e commit a11e137

File tree

4 files changed

+109
-64
lines changed

4 files changed

+109
-64
lines changed

lib/config.js

+30-19
Original file line numberDiff line numberDiff line change
@@ -31,25 +31,36 @@ const DEFAULT_CONFIG = {
3131
'swift'
3232
],
3333
urls: {
34-
base: 'https://leetcode.com',
35-
graphql: 'https://leetcode.com/graphql',
36-
login: 'https://leetcode.com/accounts/login/',
37-
// third part login base urls. TODO facebook google
38-
github_login: 'https://leetcode.com/accounts/github/login/?next=%2F',
39-
facebook_login: 'https://leetcode.com/accounts/facebook/login/?next=%2F',
40-
linkedin_login: 'https://leetcode.com/accounts/linkedin_oauth2/login/?next=%2F',
41-
problems: 'https://leetcode.com/api/problems/$category/',
42-
problem: 'https://leetcode.com/problems/$slug/description/',
43-
test: 'https://leetcode.com/problems/$slug/interpret_solution/',
44-
session: 'https://leetcode.com/session/',
45-
submit: 'https://leetcode.com/problems/$slug/submit/',
46-
submissions: 'https://leetcode.com/api/submissions/$slug',
47-
submission: 'https://leetcode.com/submissions/detail/$id/',
48-
verify: 'https://leetcode.com/submissions/detail/$id/check/',
49-
favorites: 'https://leetcode.com/list/api/questions',
50-
favorite_delete: 'https://leetcode.com/list/api/questions/$hash/$id',
51-
plugin: 'https://raw.githubusercontent.com/leetcode-tools/leetcode-cli-plugins/master/plugins/$name.js'
52-
}
34+
// base urls
35+
base: 'https://leetcode.com',
36+
graphql: 'https://leetcode.com/graphql',
37+
login: 'https://leetcode.com/accounts/login/',
38+
// third part login base urls. TODO facebook google
39+
github_login: 'https://leetcode.com/accounts/github/login/?next=%2F',
40+
facebook_login: 'https://leetcode.com/accounts/facebook/login/?next=%2F',
41+
linkedin_login: 'https://leetcode.com/accounts/linkedin_oauth2/login/?next=%2F',
42+
// redirect urls
43+
leetcode_redirect: 'https://leetcode.com/',
44+
github_tf_redirect: 'https://github.com/sessions/two-factor',
45+
// simulate login urls
46+
github_login_request: 'https://github.com/login',
47+
github_session_request: 'https://github.com/session',
48+
github_tf_session_request: 'https://github.com/sessions/two-factor',
49+
linkedin_login_request: 'https://www.linkedin.com',
50+
linkedin_session_request: 'https://www.linkedin.com/uas/login-submit',
51+
// questions urls
52+
problems: 'https://leetcode.com/api/problems/$category/',
53+
problem: 'https://leetcode.com/problems/$slug/description/',
54+
test: 'https://leetcode.com/problems/$slug/interpret_solution/',
55+
session: 'https://leetcode.com/session/',
56+
submit: 'https://leetcode.com/problems/$slug/submit/',
57+
submissions: 'https://leetcode.com/api/submissions/$slug',
58+
submission: 'https://leetcode.com/submissions/detail/$id/',
59+
verify: 'https://leetcode.com/submissions/detail/$id/check/',
60+
favorites: 'https://leetcode.com/list/api/questions',
61+
favorite_delete: 'https://leetcode.com/list/api/questions/$hash/$id',
62+
plugin: 'https://raw.githubusercontent.com/leetcode-tools/leetcode-cli-plugins/master/plugins/$name.js'
63+
},
5364
},
5465

5566
// but you will want change these

lib/plugins/leetcode.cn.js

+18-14
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,24 @@ var plugin = new Plugin(15, 'leetcode.cn', '2018.11.25',
1717

1818
plugin.init = function() {
1919
config.app = 'leetcode.cn';
20-
config.sys.urls.base = 'https://leetcode-cn.com';
21-
config.sys.urls.login = 'https://leetcode-cn.com/accounts/login/';
22-
config.sys.urls.problems = 'https://leetcode-cn.com/api/problems/$category/';
23-
config.sys.urls.problem = 'https://leetcode-cn.com/problems/$slug/description/';
24-
config.sys.urls.graphql = 'https://leetcode-cn.com/graphql';
25-
config.sys.urls.problem_detail = 'https://leetcode-cn.com/graphql';
26-
config.sys.urls.test = 'https://leetcode-cn.com/problems/$slug/interpret_solution/';
27-
config.sys.urls.session = 'https://leetcode-cn.com/session/';
28-
config.sys.urls.submit = 'https://leetcode-cn.com/problems/$slug/submit/';
29-
config.sys.urls.submissions = 'https://leetcode-cn.com/api/submissions/$slug';
30-
config.sys.urls.submission = 'https://leetcode-cn.com/submissions/detail/$id/';
31-
config.sys.urls.verify = 'https://leetcode-cn.com/submissions/detail/$id/check/';
32-
config.sys.urls.favorites = 'https://leetcode-cn.com/list/api/questions';
33-
config.sys.urls.favorite_delete = 'https://leetcode-cn.com/list/api/questions/$hash/$id';
20+
config.sys.urls.base = 'https://leetcode-cn.com';
21+
config.sys.urls.login = 'https://leetcode-cn.com/accounts/login/';
22+
config.sys.urls.problems = 'https://leetcode-cn.com/api/problems/$category/';
23+
config.sys.urls.problem = 'https://leetcode-cn.com/problems/$slug/description/';
24+
config.sys.urls.graphql = 'https://leetcode-cn.com/graphql';
25+
config.sys.urls.problem_detail = 'https://leetcode-cn.com/graphql';
26+
config.sys.urls.test = 'https://leetcode-cn.com/problems/$slug/interpret_solution/';
27+
config.sys.urls.session = 'https://leetcode-cn.com/session/';
28+
config.sys.urls.submit = 'https://leetcode-cn.com/problems/$slug/submit/';
29+
config.sys.urls.submissions = 'https://leetcode-cn.com/api/submissions/$slug';
30+
config.sys.urls.submission = 'https://leetcode-cn.com/submissions/detail/$id/';
31+
config.sys.urls.verify = 'https://leetcode-cn.com/submissions/detail/$id/check/';
32+
config.sys.urls.favorites = 'https://leetcode-cn.com/list/api/questions';
33+
config.sys.urls.favorite_delete = 'https://leetcode-cn.com/list/api/questions/$hash/$id';
34+
// third parties
35+
config.sys.urls.github_login = 'https://leetcode-cn.com/accounts/github/login/?next=%2F';
36+
config.sys.urls.linkedin_login = 'https://leetcode-cn.com/accounts/linkedin_oauth2/login/?next=%2F';
37+
config.sys.urls.leetcode_redirect = 'https://leetcode-cn.com/';
3438
};
3539

3640
// FIXME: refactor those

lib/plugins/leetcode.js

+60-31
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ plugin.getSubmissions = function(problem, cb) {
333333

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

339339
return cb(null, submissions);
@@ -471,8 +471,8 @@ plugin.deleteSession = function(session, cb) {
471471
};
472472

473473
plugin.signin = function(user, cb) {
474-
log.debug('running leetcode.signin');
475-
const spin = h.spin('Signing in leetcode.com');
474+
const isCN = config.app === 'leetcode.cn';
475+
const spin = isCN ? h.spin('Signing in leetcode-cn.com') : h.spin('Signing in leetcode.com');
476476
request(config.sys.urls.login, function(e, resp, body) {
477477
spin.stop();
478478
e = plugin.checkError(e, resp, 200);
@@ -538,11 +538,18 @@ plugin.login = function(user, cb) {
538538
});
539539
};
540540

541-
function parseCookie(cookie, cb) {
541+
function parseCookie(cookie, body, cb) {
542+
const isCN = config.app === 'leetcode.cn';
542543
const SessionPattern = /LEETCODE_SESSION=(.+?)(;|$)/;
543-
const csrfPattern = /csrftoken=(.+?)(;|$)/;
544+
let csrfPattern;
545+
// leetcode-cn.com Cookie is not the same as leetcode.com in third parties
546+
if (isCN) {
547+
csrfPattern = /name="csrfmiddlewaretoken" value="(.*?)"/;
548+
} else {
549+
csrfPattern = /csrftoken=(.+?)(;|$)/;
550+
}
544551
const reSessionResult = SessionPattern.exec(cookie);
545-
const reCsrfResult = csrfPattern.exec(cookie);
552+
const reCsrfResult = csrfPattern.exec(isCN? body: cookie);
546553
if (reSessionResult === null || reCsrfResult === null) {
547554
return cb('invalid cookie?');
548555
}
@@ -552,11 +559,18 @@ function parseCookie(cookie, cb) {
552559
};
553560
}
554561

555-
function saveAndGetUser(user, cb, cookieData) {
556-
user.sessionId = cookieData.sessionId;
557-
user.sessionCSRF = cookieData.sessionCSRF;
558-
session.saveUser(user);
559-
plugin.getUser(user, cb);
562+
function requestLeetcodeAndSave(request, leetcodeUrl, user, cb) {
563+
request.get({url: leetcodeUrl}, function(e, resp, body) {
564+
const redirectUri = resp.request.uri.href;
565+
if (redirectUri !== config.sys.urls.leetcode_redirect) {
566+
return cb('Login failed. Please make sure the credential is correct.');
567+
}
568+
const cookieData = parseCookie(resp.request.headers.cookie, body, cb);
569+
user.sessionId = cookieData.sessionId;
570+
user.sessionCSRF = cookieData.sessionCSRF;
571+
session.saveUser(user);
572+
plugin.getUser(user, cb);
573+
});
560574
}
561575

562576
plugin.cookieLogin = function(user, cb) {
@@ -568,15 +582,16 @@ plugin.cookieLogin = function(user, cb) {
568582
};
569583

570584
plugin.githubLogin = function(user, cb) {
571-
const leetcodeUrl = config.sys.urls.github_login;
585+
const urls = config.sys.urls;
586+
const leetcodeUrl = urls.github_login;
572587
const _request = request.defaults({jar: true});
573-
_request('https://github.com/login', function(e, resp, body) {
588+
_request(urls.github_login_request, function(e, resp, body) {
574589
const authenticityToken = body.match(/name="authenticity_token" value="(.*?)"/);
575590
if (authenticityToken === null) {
576591
return cb('Get GitHub token failed');
577592
}
578593
const options = {
579-
url: 'https://github.com/session',
594+
url: urls.github_session_request,
580595
method: 'POST',
581596
headers: {
582597
'Content-Type': 'application/x-www-form-urlencoded',
@@ -594,27 +609,48 @@ plugin.githubLogin = function(user, cb) {
594609
if (resp.statusCode !== 200) {
595610
return cb('GitHub login failed');
596611
}
597-
_request.get({url: leetcodeUrl}, function(e, resp, body) {
598-
const redirectUri = resp.request.uri.href;
599-
if (redirectUri !== 'https://leetcode.com/') {
600-
return cb('GitHub login failed or GitHub did not link to LeetCode');
612+
if (resp.request.uri.href !== urls.github_tf_redirect) {
613+
return requestLeetcodeAndSave(_request, leetcodeUrl, user, cb);
614+
}
615+
// read two-factor code must be sync.
616+
const twoFactorcode = require('prompt-sync')()('Please enter your two-factor code: ');
617+
const authenticityTokenTwoFactor = body.match(/name="authenticity_token" value="(.*?)"/);
618+
if (authenticityTokenTwoFactor === null) {
619+
return cb('Get GitHub two-factor token failed');
620+
}
621+
const optionsTwoFactor = {
622+
url: urls.github_tf_session_request,
623+
method: 'POST',
624+
headers: {
625+
'Content-Type': 'application/x-www-form-urlencoded',
626+
},
627+
followAllRedirects: true,
628+
form: {
629+
'otp': twoFactorcode,
630+
'authenticity_token': authenticityTokenTwoFactor[1],
631+
'utf8': encodeURIComponent('✓'),
632+
},
633+
};
634+
_request(optionsTwoFactor, function(e, resp, body) {
635+
if (resp.request.uri.href === urls.github_tf_session_request) {
636+
return cb('Invalid two-factor code please check');
601637
}
602-
const cookieData = parseCookie(resp.request.headers.cookie, cb);
603-
saveAndGetUser(user, cb, cookieData);
638+
requestLeetcodeAndSave(_request, leetcodeUrl, user, cb);
604639
});
605640
});
606641
});
607642
};
608643

609644
plugin.linkedinLogin = function(user, cb) {
610-
const leetcodeUrl = config.sys.urls.linkedin_login;
645+
const urls = config.sys.urls;
646+
const leetcodeUrl = urls.linkedin_login;
611647
const _request = request.defaults({
612648
jar: true,
613649
headers: {
614650
'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'
615651
}
616652
});
617-
_request('https://www.linkedin.com', function(e, resp, body) {
653+
_request(urls.linkedin_login_request, function(e, resp, body) {
618654
if ( resp.statusCode !== 200) {
619655
return cb('Get LinkedIn session failed');
620656
}
@@ -623,7 +659,7 @@ plugin.linkedinLogin = function(user, cb) {
623659
return cb('Get LinkedIn token failed');
624660
}
625661
const options = {
626-
url: 'https://www.linkedin.com/uas/login-submit',
662+
url: urls.linkedin_session_request,
627663
method: 'POST',
628664
headers: {
629665
'Content-Type': 'application/x-www-form-urlencoded',
@@ -640,14 +676,7 @@ plugin.linkedinLogin = function(user, cb) {
640676
if (resp.statusCode !== 200) {
641677
return cb('LinkedIn login failed');
642678
}
643-
_request.get({url: leetcodeUrl}, function(e, resp, body) {
644-
const redirectUri = resp.request.uri.href;
645-
if (redirectUri !== 'https://leetcode.com/') {
646-
return cb('LinkedIn login failed or LinkedIn did not link to LeetCode');
647-
}
648-
const cookieData = parseCookie(resp.request.headers.cookie, cb);
649-
saveAndGetUser(user, cb, cookieData);
650-
});
679+
requestLeetcodeAndSave(_request, leetcodeUrl, user, cb);
651680
});
652681
});
653682
};

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
"nock": "10.0.2",
7272
"nyc": "^13.3.0",
7373
"pkg": "^4.3.4",
74+
"prompt-sync": "^4.2.0",
7475
"rewire": "4.0.1"
7576
}
7677
}

0 commit comments

Comments
 (0)