mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 01:38:19 +08:00
Google OAuth
This commit is contained in:
1 parent
8476eac3b4
commit
d2173eeb38
12 files changed
+261
-117
No files matched your search
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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;
|
||||
@@ -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
@@ -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'),
|
||||
};
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
const superagent = require('superagent');
|
||||
const proxy = require('superagent-proxy');
|
||||
|
||||
proxy(superagent);
|
||||
|
||||
const dict = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
|
||||
|
||||
String.random = function random(digit = 32) {
|
||||
|
||||
Reference in new issue
Block a user