diff --git a/lib/config.js b/lib/config.js index 049d20d4..9fc26945 100644 --- a/lib/config.js +++ b/lib/config.js @@ -31,25 +31,36 @@ 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' + }, }, // but you will want change these diff --git a/lib/plugins/leetcode.cn.js b/lib/plugins/leetcode.cn.js index 4df528f3..07c78f98 100644 --- a/lib/plugins/leetcode.cn.js +++ b/lib/plugins/leetcode.cn.js @@ -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 diff --git a/lib/plugins/leetcode.js b/lib/plugins/leetcode.js index 004a6034..12d7ccbe 100644 --- a/lib/plugins/leetcode.js +++ b/lib/plugins/leetcode.js @@ -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); @@ -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); @@ -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?'); } @@ -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) { @@ -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', @@ -594,27 +609,48 @@ 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) { + return requestLeetcodeAndSave(_request, leetcodeUrl, user, cb); + } + // 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'); } - const cookieData = parseCookie(resp.request.headers.cookie, cb); - saveAndGetUser(user, cb, cookieData); + 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'); } @@ -623,7 +659,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', @@ -640,14 +676,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); }); }); }; diff --git a/package.json b/package.json index 3d8286e5..14371304 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,7 @@ "nock": "10.0.2", "nyc": "^13.3.0", "pkg": "^4.3.4", + "prompt-sync": "^4.2.0", "rewire": "4.0.1" } }