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

add third party github login for future updates in vscode-leetcode #34

Merged
merged 5 commits into from
Dec 24, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,13 @@ Great thanks to leetcode.com, a really awesome website!

## Quick Start

Read help first $ leetcode help
Login with your leetcode account $ leetcode user -l
Cookie login with cookie $ leetcode user -c
Browse all questions $ leetcode list
Choose one question $ leetcode show 1 -g -l cpp
Read help first $ leetcode help
Login with your leetcode account $ leetcode user -l
Login with third party account--GitHub $ leetcode user -g
Login with third party account--LinkedIn $ leetcode user -i
Cookie login with cookie $ leetcode user -c
Browse all questions $ leetcode list
Choose one question $ leetcode show 1 -g -l cpp
Coding it!
Run test(s) and pray... $ leetcode test ./two-sum.cpp -t '[3,2,4]\n7'
Submit final solution! $ leetcode submit ./two-sum.cpp
Run test(s) and pray... $ leetcode test ./two-sum.cpp -t '[3,2,4]\n7'
Submit final solution! $ leetcode submit ./two-sum.cpp
98 changes: 69 additions & 29 deletions lib/commands/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,42 @@ const cmd = {
desc: 'Manage account',
builder: function(yargs) {
return yargs
.option('l', {
alias: 'login',
type: 'boolean',
default: false,
describe: 'Login'
})
.option('c', {
alias: 'cookie',
type: 'boolean',
default: false,
describe: 'cookieLogin'
})
.option('L', {
alias: 'logout',
type: 'boolean',
default: false,
describe: 'Logout'
})
.example(chalk.yellow('leetcode user'), 'Show current user')
.example(chalk.yellow('leetcode user -l'), 'User login')
.example(chalk.yellow('leetcode user -c'), 'User Cookie login')
.example(chalk.yellow('leetcode user -L'), 'User logout');
.option('l', {
alias: 'login',
type: 'boolean',
default: false,
describe: 'Login'
})
.option('c', {
alias: 'cookie',
type: 'boolean',
default: false,
describe: 'cookieLogin'
})
.option('g', {
alias: 'github',
type: 'boolean',
default: false,
describe: 'githubLogin'
})
.option('i', {
alias: 'linkedin',
type: 'boolean',
default: false,
describe: 'linkedinLogin'
})
.option('L', {
alias: 'logout',
type: 'boolean',
default: false,
describe: 'Logout'
})
.example(chalk.yellow('leetcode user'), 'Show current user')
.example(chalk.yellow('leetcode user -l'), 'User login')
.example(chalk.yellow('leetcode user -c'), 'User Cookie login')
.example(chalk.yellow('leetcode user -g'), 'User GitHub login')
.example(chalk.yellow('leetcode user -i'), 'User LinkedIn login')
.example(chalk.yellow('leetcode user -L'), 'User logout');
}
};

Expand Down Expand Up @@ -66,6 +80,32 @@ cmd.handler = function(argv) {
log.info('Successfully logout as', chalk.yellow(user.name));
else
log.fail('You are not login yet?');
// third parties
} else if (argv.github || argv.linkedin) {
// add future third parties here
const functionMap = new Map(
[
['g', core.githubLogin],
['github', core.githubLogin],
['i', core.linkedinLogin],
['linkedin', core.linkedinLogin],
]
);
const keyword = Object.entries(argv).filter((i) => (i[1] === true))[0][0];
const coreFunction = functionMap.get(keyword);
prompt.colors = false;
prompt.message = '';
prompt.start();
prompt.get([
{name: 'login', required: true},
{name: 'pass', required: true, hidden: true}
], function(e, user) {
if (e) return log.fail(e);
coreFunction(user, function(e, user) {
if (e) return log.fail(e);
log.info('Successfully third party login as', chalk.yellow(user.name));
});
});
} else if (argv.cookie) {
// session
prompt.colors = false;
Expand All @@ -75,22 +115,22 @@ cmd.handler = function(argv) {
{name: 'login', required: true},
{name: 'cookie', required: true}
], function(e, user) {
if (e) return log.fail(e)
core.cookieLogin(user, function(e, user) {
if (e) return log.fail(e);
log.info('Successfully cookie login as', chalk.yellow(user.name));
core.cookieLogin(user, function(e, user) {
if (e) return log.fail(e);
log.info('Successfully cookie login as', chalk.yellow(user.name));
});
});
} else {
} else {
// show current user
user = session.getUser();
if (user) {
log.info(chalk.gray(sprintf(' %-9s %-20s %s', 'Premium', 'User', 'Host')));
log.info(chalk.gray('-'.repeat(60)));
log.printf(' %s %-20s %s',
h.prettyText('', user.paid || false),
chalk.yellow(user.name),
config.sys.urls.base);
h.prettyText('', user.paid || false),
chalk.yellow(user.name),
config.sys.urls.base);
} else
return log.fail('You are not login yet?');
}
Expand Down
10 changes: 7 additions & 3 deletions lib/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ const DEFAULT_CONFIG = {
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/',
Expand Down Expand Up @@ -79,15 +83,15 @@ function Config() {}

Config.prototype.init = function() {
nconf.file('local', file.configFile())
.add('global', {type: 'literal', store: DEFAULT_CONFIG})
.defaults({});
.add('global', {type: 'literal', store: DEFAULT_CONFIG})
.defaults({});

const cfg = nconf.get();
nconf.remove('local');
nconf.remove('global');

// HACK: remove old style configs
for (let x in cfg) {
for (const x in cfg) {
if (x === x.toUpperCase()) delete cfg[x];
}
delete DEFAULT_CONFIG.type;
Expand Down
131 changes: 115 additions & 16 deletions lib/plugins/leetcode.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ plugin.checkError = function(e, resp, expectedStatus) {

plugin.init = function() {
config.app = 'leetcode';
}
};

plugin.getProblems = function(cb) {
log.debug('running leetcode.getProblems');
Expand Down Expand Up @@ -95,7 +95,7 @@ plugin.getCategoryProblems = function(category, cb) {
}

const problems = json.stat_status_pairs
.filter(p => !p.stat.question__hide)
.filter((p) => !p.stat.question__hide)
.map(function(p) {
return {
state: p.status || 'None',
Expand Down Expand Up @@ -167,7 +167,7 @@ plugin.getProblem = function(problem, cb) {
problem.testable = q.enableRunCode;
problem.templateMeta = JSON.parse(q.metaData);
// @si-yao: seems below property is never used.
//problem.discuss = q.discussCategoryId;
// problem.discuss = q.discussCategoryId;

return cb(null, problem);
});
Expand Down Expand Up @@ -254,9 +254,9 @@ function formatResult(result) {
};

x.error = _.chain(result)
.pick((v, k) => /_error$/.test(k) && v.length > 0)
.values()
.value();
.pick((v, k) => /_error$/.test(k) && v.length > 0)
.values()
.value();

if (/[runcode|interpret].*/.test(result.submission_id)) {
// It's testing
Expand Down Expand Up @@ -374,8 +374,8 @@ plugin.starProblem = function(problem, starred, cb) {
};
} else {
opts.url = config.sys.urls.favorite_delete
.replace('$hash', user.hash)
.replace('$id', problem.id);
.replace('$hash', user.hash)
.replace('$id', problem.id);
opts.method = 'DELETE';
}

Expand Down Expand Up @@ -508,7 +508,7 @@ plugin.signin = function(user, cb) {
plugin.getUser = function(user, cb) {
plugin.getFavorites(function(e, favorites) {
if (!e) {
const f = favorites.favorites.private_favorites.find(f => f.name === 'Favorite');
const f = favorites.favorites.private_favorites.find((f) => f.name === 'Favorite');
if (f) {
user.hash = f.id_hash;
user.name = favorites.user_name;
Expand Down Expand Up @@ -538,19 +538,118 @@ plugin.login = function(user, cb) {
});
};

plugin.cookieLogin = function(user, cb) {
// re pattern for cookie chrome or firefox
function parseCookie(cookie, cb) {
const SessionPattern = /LEETCODE_SESSION=(.+?)(;|$)/;
const csrfPattern = /csrftoken=(.+?)(;|$)/;
const reSessionResult = SessionPattern.exec(user.cookie);
const reCsrfResult = csrfPattern.exec(user.cookie);
const reSessionResult = SessionPattern.exec(cookie);
const reCsrfResult = csrfPattern.exec(cookie);
if (reSessionResult === null || reCsrfResult === null) {
return cb('invalid cookie?')
return cb('invalid cookie?');
}
user.sessionId = reSessionResult[1];
user.sessionCSRF = reCsrfResult[1];
return {
sessionId: reSessionResult[1],
sessionCSRF: reCsrfResult[1],
};
}

function saveAndGetUser(user, cb, cookieData) {
user.sessionId = cookieData.sessionId;
user.sessionCSRF = cookieData.sessionCSRF;
session.saveUser(user);
plugin.getUser(user, cb);
}

plugin.cookieLogin = function(user, cb) {
const cookieData = parseCookie(user.cookie, cb);
user.sessionId = cookieData.sessionId;
user.sessionCSRF = cookieData.sessionCSRF;
session.saveUser(user);
plugin.getUser(user, cb);
};

plugin.githubLogin = function(user, cb) {
const leetcodeUrl = config.sys.urls.github_login;
const _request = request.defaults({jar: true});
_request('https://github.com/login', 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',
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
followAllRedirects: true,
form: {
'login': user.login,
'password': user.pass,
'authenticity_token': authenticityToken[1],
'utf8': encodeURIComponent('✓'),
'commit': encodeURIComponent('Sign in')
},
};
_request(options, function(e, resp, body) {
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');
}
const cookieData = parseCookie(resp.request.headers.cookie, cb);
saveAndGetUser(user, cb, cookieData);
});
});
});
};

plugin.linkedinLogin = function(user, cb) {
const leetcodeUrl = config.sys.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) {
if ( resp.statusCode !== 200) {
return cb('Get LinkedIn session failed');
}
const authenticityToken = body.match(/input name="loginCsrfParam" value="(.*)" /);
if (authenticityToken === null) {
return cb('Get LinkedIn token failed');
}
const options = {
url: 'https://www.linkedin.com/uas/login-submit',
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
followAllRedirects: true,
form: {
'session_key': user.login,
'session_password': user.pass,
'loginCsrfParam': authenticityToken[1],
'trk': 'guest_homepage-basic_sign-in-submit'
},
};
_request(options, function(e, resp, body) {
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);
});
});
});
};

module.exports = plugin;