Google OAuth

This commit is contained in:
undefined committed 2020-06-22 13:29:14 +08:00
1 parent 8476eac3b4
commit d2173eeb38
12 files changed
+261 -117

No files matched your search

+1 -1
View File
@@ -5,7 +5,7 @@ const fs = require('fs');
const yaml = require('js-yaml');
const builtinLib = [
'axios', 'download', 'i18n', 'mail', 'markdown',
'jwt', 'download', 'i18n', 'mail', 'markdown',
'md5', 'misc', 'paginate', 'hash.hydro', 'rank',
'template', 'validator', 'nav', 'sysinfo', 'testdata.convert.ini',
'readConfig', 'logger', 'useragent',
+109 -49
View File
@@ -1,4 +1,4 @@
const axios = require('axios');
const superagent = require('superagent');
const { Route, Handler } = require('../service/server.js');
const user = require('../model/user');
const token = require('../model/token');
@@ -162,67 +162,127 @@ class UserSearchHandler extends Handler {
}
class OauthHandler extends Handler {
async github() {
const [appid, [state]] = await Promise.all([
system.get('oauth.githubappid'),
token.add(token.TYPE_OAUTH, 600, { redirect: this.request.referer }),
]);
this.response.redirect = `https://github.com/login/oauth/authorize?client_id=${appid}&state=${state}`;
}
async google() {
const [appid, url, [state]] = await Promise.all([
system.get('oauth.googleappid'),
system.get('server.url'),
token.add(token.TYPE_OAUTH, 600, { redirect: this.request.referer }),
]);
this.response.redirect = `https://accounts.google.com/o/oauth2/v2/auth?client_id=${appid}&response_type=code&redirect_uri=${url}oauth/google/callback&scope=https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile&state=${state}`;
}
async get({ type }) {
if (type === 'github') {
const [appid, [state]] = await Promise.all([
system.get('oauth.githubappid'),
token.add(token.TYPE_OAUTH, 600, { redirect: this.request.referer }),
]);
this.response.redirect = `https://github.com/login/oauth/authorize?client_id=${appid}&state=${state}`;
}
if (type === 'github') await this.github();
else if (type === 'google') await this.google();
}
}
class OauthCallbackHandler extends Handler {
async get({ type, state, code }) {
if (type === 'github') {
const [appid, secret, url, s] = await Promise.all([
system.get('oauth.githubappid'),
system.get('oauth.githubsecret'),
system.get('server.url'),
token.get(state, token.TYPE_OAUTH),
]);
const res = await axios.post('https://github.com/login/oauth/access_token', {
async github({ state, code }) {
const [appid, secret, url, s] = await Promise.all([
system.get('oauth.githubappid'),
system.get('oauth.githubsecret'),
system.get('server.url'),
token.get(state, token.TYPE_OAUTH),
]);
const res = await superagent.post('https://github.com/login/oauth/access_token')
.send({
client_id: appid,
client_secret: secret,
code,
redirect_uri: `${url}oauth/github/callback`,
state,
}, { headers: { accept: 'application/json' } });
if (res.data.error) {
throw new UserFacingError(
res.data.error, res.data.error_description, res.data.error_uri,
);
}
const t = res.data.access_token;
const userInfo = await axios.get('https://api.github.com/user', {
headers: {
Authorization: `token ${t}`,
},
})
.set('accept', 'application/json');
if (res.body.error) {
throw new UserFacingError(
res.body.error, res.body.error_description, res.body.error_uri,
);
}
const t = res.body.access_token;
const userInfo = await superagent.get('https://api.github.com/user')
.set('Authorization', `token ${t}`);
const ret = {
email: userInfo.body.mail,
bio: userInfo.body.bio,
uname: [userInfo.body.name, userInfo.body.login],
};
this.response.redirect = s.redirect;
await token.del(s, token.TYPE_OAUTH);
return ret;
}
async google({
code, scope, authuser, prompt, error, state,
}) {
if (error) throw new UserFacingError(error);
console.log(scope, authuser, prompt);
const [
[appid, secret, url, proxy],
s,
] = await Promise.all([
system.getMany([
'oauth.googleappid', 'oauth.googlesecret', 'server.url', 'proxy',
]),
token.get(state, token.TYPE_OAUTH),
]);
const res = await superagent.post('https://oauth2.googleapis.com/token')
.proxy(proxy)
.send({
client_id: appid,
client_secret: secret,
code,
grant_type: 'authorization_code',
redirect_uri: `${url}oauth/google/callback`,
});
const {
email: mail, bio, name, login,
} = userInfo.data;
const udoc = await user.getByEmail('system', mail, true);
if (udoc) {
this.session.uid = udoc._id;
await token.del(s, token.TYPE_OAUTH);
} else {
let uname;
const nudoc = await user.getByUname('system', name, true);
if (!nudoc) uname = name;
else {
const ludoc = await user.getByUname('system', login, true);
if (!ludoc) uname = login;
else uname = String.random(16);
const payload = global.Hydro.lib.jwt.decode(res.body.id_token);
await token.del(state, token.TYPE_OAUTH);
this.response.redirect = s.redirect;
return {
email: payload.email,
uname: [payload.given_name, payload.name, payload.family_name],
viewLang: payload.locale.replace('-', '_'),
};
}
async get(args) {
let r;
if (args.type === 'github') r = await this.github(args);
else if (args.type === 'google') r = await this.google(args);
else throw new UserFacingError('Oauth type');
const udoc = await user.getByEmail('system', r.email, true);
if (udoc) {
this.session.uid = udoc._id;
} else {
let username = '';
r.uname = r.uname || [];
r.uname.push(String.random(16));
for (const uname of r.uname) {
// eslint-disable-next-line no-await-in-loop
const nudoc = await user.getByUname('system', uname, true);
if (!nudoc) {
username = uname;
break;
}
const uid = await user.create({
mail, uname, password: String.random(32), regip: this.request.ip,
});
await user.setById(uid, { bio, oauth: 'github' });
this.session.uid = uid;
}
this.response.redirect = s.redirect;
const uid = await user.create({
mail: r.email, uname: username, password: String.random(32), regip: this.request.ip,
});
const $set = {
oauth: args.type,
};
if (r.bio) $set.bio = r.bio;
if (r.viewLang) $set.viewLang = r.viewLang;
await user.setById(uid, $set);
this.session.uid = uid;
}
}
}
-23
View File
@@ -1,23 +0,0 @@
const axios = require('axios');
async function get(url, options) {
let res;
try {
res = await axios.get(url, options);
} catch (e) {
res = e;
}
return res;
}
async function post(url, data, options) {
let res;
try {
res = await axios.post(url, data, options);
} catch (e) {
res = e;
}
return res;
}
global.Hydro.lib.axios = module.exports = { get, post };
+10 -16
View File
@@ -1,23 +1,17 @@
const axios = require('axios');
const superagent = require('superagent');
const fs = require('fs');
async function download(url, path, retry = 3) {
let r;
try {
const res = await axios.get(url, { responseType: 'stream' });
if (path) {
const w = await fs.createWriteStream(path);
res.data.pipe(w);
await new Promise((resolve, reject) => {
w.on('finish', resolve);
w.on('error', reject);
});
} else r = res.data;
} catch (e) {
if (retry) r = await download(url, path, retry - 1);
else throw e;
if (path) {
const w = await fs.createWriteStream(path);
await superagent.get(url).retry(retry).pipe(w);
await new Promise((resolve, reject) => {
w.on('finish', resolve);
w.on('error', reject);
});
return path;
}
return r;
return superagent.get(url);
}
global.Hydro.lib.download = module.exports = download;
+35
View File
@@ -0,0 +1,35 @@
function decodeBase64WithUriEncoding(encodedText) {
return Buffer.from(encodedText, 'base64').toString('utf8');
}
function unescapedString(escapedString) {
escapedString += new Array(5 - escapedString.length % 4).join('=');
return escapedString.replace(/\-/g, '+').replace(/_/g, '/');
}
function decodeJWT(idToken) {
const token = idToken.split('.');
if (token.length !== 3) {
throw new Error('Invalid idToken');
}
try {
const headerSegment = JSON.parse(decodeBase64WithUriEncoding(token[0]));
const payloadSegment = JSON.parse(decodeBase64WithUriEncoding(token[1]));
const signature = unescapedString(token[2]);
return {
dataToSign: [token[0], token[1]].join('.'),
header: headerSegment,
payload: payloadSegment,
signature,
};
} catch (e) {
throw new Error('Invalid payload');
}
}
function decode(idToken) {
const decodedJWT = decodeJWT(idToken);
return decodedJWT.payload;
}
global.Hydro.lib.jwt = module.exports = { decode };
+1 -1
View File
@@ -75,8 +75,8 @@ async function messageHandler(worker, msg) {
async function load() {
global.nodeModules = {
'adm-zip': require('adm-zip'),
axios: require('axios'),
bson: require('bson'),
superagent: require('superagent'),
'js-yaml': require('js-yaml'),
mongodb: require('mongodb'),
};
+3
View File
@@ -104,6 +104,9 @@ SystemSetting(
Setting('setting_server', 'server.port', null, 8888, 'number', 'Server Port'),
Setting('setting_oauth', 'oauth.githubappid', null, null, 'text', 'Github Oauth AppID'),
Setting('setting_oauth', 'oauth.githubsecret', null, null, 'text', 'Github Oauth Secret'),
Setting('setting_oauth', 'oauth.googleappid', null, null, 'text', 'Google Oauth ClientID'),
Setting('setting_oauth', 'oauth.googlesecret', null, null, 'text', 'Google Oauth Secret'),
Setting('setting_proxy', 'proxy', null, null, 'text', 'Proxy Server URL'),
Setting('setting_constant', 'PROBLEM_PER_PAGE', null, 100, 'number', 'Problems per Page'),
Setting('setting_constant', 'CONTEST_PER_PAGE', null, 20, 'number', 'Contests per Page'),
Setting('setting_constant', 'DISCUSSION_PER_PAGE', null, 50, 'number', 'Discussion per Page'),
+16
View File
@@ -7,10 +7,25 @@ async function get(_id) {
if (doc) return doc.value;
return null;
}
async function getMany(keys) {
const docs = await coll.find({ _id: { $in: keys } }).toArray();
const dict = {};
const res = [];
for (const doc of docs) {
dict[doc._id] = doc.value;
}
for (const key of keys) {
res.push(dict[key] || null);
}
return res;
}
async function update(_id, operation, config) {
await coll.findOneAndUpdate({ _id }, operation, config);
return get(_id);
}
async function set(_id, value) {
await coll.findOneAndUpdate({ _id }, { $set: { value } }, { upsert: true });
return get(_id);
@@ -25,6 +40,7 @@ function inc(field) {
global.Hydro.model.system = module.exports = {
get,
getMany,
update,
inc,
set,
+5
View File
@@ -1,3 +1,8 @@
const superagent = require('superagent');
const proxy = require('superagent-proxy');
proxy(superagent);
const dict = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
String.random = function random(digit = 32) {