mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 01:38:19 +08:00
Github Oauth,支持自动转换config.ini配置
This commit is contained in:
1 parent
5707f973d7
commit
8476eac3b4
17 files changed
+240
-109
No files matched your search
@@ -55,6 +55,10 @@ async function load() {
|
||||
await script(pending, fail, active);
|
||||
pending = [];
|
||||
await server.start();
|
||||
setInterval(() => {
|
||||
process.send({ event: 'stat', count: global.Hydro.stat.reqCount });
|
||||
global.Hydro.stat.reqCount = 0;
|
||||
}, 30 * 1000);
|
||||
return { active, fail };
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const user = require('../model/user');
|
||||
const domain = require('../model/domain');
|
||||
const system = require('../model/system');
|
||||
const { DOMAIN_SETTINGS, DOMAIN_SETTINGS_BY_KEY } = require('../model/setting');
|
||||
const paginate = require('../lib/paginate');
|
||||
const { Route, Handler } = require('../service/server');
|
||||
const { PERM_MANAGE } = require('../permission');
|
||||
@@ -67,14 +68,16 @@ class DomainEditHandler extends ManageHandler {
|
||||
['domain_edit', null],
|
||||
];
|
||||
this.response.template = 'domain_edit.html';
|
||||
this.response.body = { domain: this.domain, path };
|
||||
this.response.body = { current: this.domain, settings: DOMAIN_SETTINGS, path };
|
||||
}
|
||||
|
||||
async post({
|
||||
domainId, name, gravatar, bulletin,
|
||||
}) {
|
||||
await domain.edit(domainId, { name, gravatar, bulletin });
|
||||
this.response.redirect = '/domain/dashboard';
|
||||
async post(args) {
|
||||
const $set = {};
|
||||
for (const key of args) {
|
||||
if (DOMAIN_SETTINGS_BY_KEY[key]) $set[key] = args[key];
|
||||
}
|
||||
await domain.edit(args.domainId, $set);
|
||||
this.response.redirect = this.url('domain_dashboard');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+71
-3
@@ -1,3 +1,4 @@
|
||||
const axios = require('axios');
|
||||
const { Route, Handler } = require('../service/server.js');
|
||||
const user = require('../model/user');
|
||||
const token = require('../model/token');
|
||||
@@ -7,7 +8,7 @@ const misc = require('../lib/misc');
|
||||
const {
|
||||
UserAlreadyExistError, InvalidTokenError, VerifyPasswordError,
|
||||
UserNotFoundError, LoginError, SystemError,
|
||||
PermissionError, BlacklistedError,
|
||||
PermissionError, BlacklistedError, UserFacingError,
|
||||
} = require('../error');
|
||||
|
||||
class UserLoginHandler extends Handler {
|
||||
@@ -25,8 +26,7 @@ class UserLoginHandler extends Handler {
|
||||
if (udoc.ban) throw new BlacklistedError(uname);
|
||||
this.session.uid = udoc._id;
|
||||
this.session.rememberme = rememberme;
|
||||
const referer = this.request.headers.referer || '/';
|
||||
this.response.redirect = referer.endsWith('/login') ? '/' : referer;
|
||||
this.response.redirect = this.request.referer.endsWith('/login') ? '/' : this.request.referer;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,8 +161,76 @@ class UserSearchHandler extends Handler {
|
||||
}
|
||||
}
|
||||
|
||||
class OauthHandler extends Handler {
|
||||
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}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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', {
|
||||
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}`,
|
||||
},
|
||||
});
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function apply() {
|
||||
Route('user_login', '/login', UserLoginHandler);
|
||||
Route('user_oauth', '/oauth/:type', OauthHandler);
|
||||
Route('user_oauth_callback', '/oauth/:type/callback', OauthCallbackHandler);
|
||||
Route('user_register', '/register', UserRegisterHandler);
|
||||
Route('user_register_with_code', '/register/:code', UserRegisterWithCodeHandler);
|
||||
Route('user_logout', '/logout', UserLogoutHandler);
|
||||
|
||||
+2
-3
@@ -1,8 +1,7 @@
|
||||
const ansiup = require('ansi_up');
|
||||
const { default: AnsiUp } = require('ansi_up');
|
||||
const md5 = require('./md5');
|
||||
|
||||
// eslint-disable-next-line new-cap
|
||||
const AU = new ansiup.default();
|
||||
const AU = new AnsiUp();
|
||||
|
||||
function ansiToHtml(str) {
|
||||
return AU.ansi_to_html(str);
|
||||
|
||||
@@ -6,12 +6,27 @@ function convert(ini) {
|
||||
const res = { cases: [] };
|
||||
for (let i = 1; i <= count; i++) {
|
||||
const [input, output, time, score, memory] = f[i].split('|');
|
||||
const cur = { input, output, score };
|
||||
if (parseInt(time) !== 1) cur.time = `${time}s`;
|
||||
if (!Number.isNaN(parseInt(memory))) cur.memory = parseInt(memory) / 1024;
|
||||
const cur = { input, output, score: parseInt(score) };
|
||||
cur.time = `${time}s`;
|
||||
if (!Number.isNaN(parseInt(memory))) cur.memory = `${Math.floor(parseInt(memory) / 1024)}m`;
|
||||
else cur.memory = '128m';
|
||||
res.cases.push(cur);
|
||||
}
|
||||
return yaml.safeDump(res);
|
||||
if (res.cases.length > 0) {
|
||||
const { time, score, memory } = res.cases[0];
|
||||
for (let i = 1; i < res.cases.length; i++) {
|
||||
if (res.cases[i].time !== time
|
||||
|| res.cases[i].score !== score
|
||||
|| res.cases[i].memory !== memory) {
|
||||
return yaml.safeDump(res);
|
||||
}
|
||||
}
|
||||
}
|
||||
return yaml.safeDump({
|
||||
time: res.cases[0].time,
|
||||
score: res.cases[0].score,
|
||||
memory: res.cases[0].memory,
|
||||
});
|
||||
}
|
||||
|
||||
global.Hydro.lib['testdata.convert.ini'] = module.exports = convert;
|
||||
+14
-8
@@ -58,24 +58,30 @@ async function executeCommand(input) {
|
||||
|
||||
async function messageHandler(worker, msg) {
|
||||
if (!msg) msg = worker;
|
||||
if (msg.event && msg.event === 'restart') {
|
||||
console.log('Restarting');
|
||||
await stopWorker();
|
||||
console.log('Worker stopped');
|
||||
await startWorker(msg.count);
|
||||
} else if (msg.event && msg.event === 'run') {
|
||||
await executeCommand(msg.command);
|
||||
if (msg.event) {
|
||||
if (msg.event === 'stat') {
|
||||
global.Hydro.stat.reqCount += msg.count;
|
||||
} else if (msg.event === 'restart') {
|
||||
console.log('Restarting');
|
||||
await stopWorker();
|
||||
console.log('Worker stopped');
|
||||
await startWorker(msg.count);
|
||||
} else if (msg.event === 'run') {
|
||||
await executeCommand(msg.command);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function load() {
|
||||
global.nodeModules = {
|
||||
'adm-zip': require('adm-zip'),
|
||||
axios: require('axios'),
|
||||
bson: require('bson'),
|
||||
'js-yaml': require('js-yaml'),
|
||||
mongodb: require('mongodb'),
|
||||
};
|
||||
global.Hydro = {
|
||||
config: {},
|
||||
stat: { reqCount: 0 },
|
||||
handler: {},
|
||||
service: {},
|
||||
model: {},
|
||||
|
||||
+2
-1
@@ -1,5 +1,5 @@
|
||||
const fs = require('fs');
|
||||
const { ForbiddenError } = require('../error');
|
||||
const { ForbiddenError, NotFoundError } = require('../error');
|
||||
const db = require('../service/db');
|
||||
const gridfs = require('../service/gridfs');
|
||||
const hash = require('../lib/hash.hydro');
|
||||
@@ -55,6 +55,7 @@ async function dec(_id) {
|
||||
|
||||
async function get(_id, secret) {
|
||||
const file = await coll.findOne({ _id });
|
||||
if (!file) throw new NotFoundError(_id);
|
||||
if (typeof secret !== 'undefined') {
|
||||
const timestamp = _timestamp();
|
||||
if (!(hash(file.secret, timestamp.toString()) === secret)) {
|
||||
|
||||
+21
-1
@@ -12,9 +12,11 @@ const timezones = Array.from(tzs).sort().map((tz) => [tz, tz]);
|
||||
|
||||
const PREFERENCE_SETTINGS = [];
|
||||
const ACCOUNT_SETTINGS = [];
|
||||
const DOMAIN_SETTINGS = [];
|
||||
const SYSTEM_SETTINGS = [];
|
||||
const SETTINGS = [];
|
||||
const SETTINGS_BY_KEY = {};
|
||||
const DOMAIN_SETTINGS_BY_KEY = {};
|
||||
const SYSTEM_SETTINGS_BY_KEY = {};
|
||||
|
||||
const Setting = (
|
||||
@@ -39,6 +41,12 @@ const AccountSetting = (...settings) => {
|
||||
SETTINGS_BY_KEY[setting.key] = setting;
|
||||
}
|
||||
};
|
||||
const DomainSetting = (...settings) => {
|
||||
for (const setting of settings) {
|
||||
DOMAIN_SETTINGS.push(setting);
|
||||
DOMAIN_SETTINGS_BY_KEY[setting.key] = setting;
|
||||
}
|
||||
};
|
||||
const SystemSetting = (...settings) => {
|
||||
for (const setting of settings) {
|
||||
SYSTEM_SETTINGS.push(setting);
|
||||
@@ -47,8 +55,9 @@ const SystemSetting = (...settings) => {
|
||||
};
|
||||
|
||||
PreferenceSetting(
|
||||
// TODO generate by global.Hydro.locales
|
||||
Setting('setting_display', 'viewLang', builtin.VIEW_LANGS.map((i) => [i.code, i.name]),
|
||||
options.default_locale, 'select', 'UI Language'),
|
||||
'zh_CN', 'select', 'UI Language'),
|
||||
Setting('setting_display', 'timezone', timezones,
|
||||
'Asia/Shanghai', 'select', 'Timezone'),
|
||||
Setting('setting_usage', 'codeLang', builtin.LANG_TEXTS,
|
||||
@@ -73,6 +82,12 @@ AccountSetting(
|
||||
'Choose the background image in your profile page.'),
|
||||
);
|
||||
|
||||
DomainSetting(
|
||||
Setting('setting_domain', 'name', null, 'New domain', 'text', 'name'),
|
||||
Setting('setting_domain', 'gravatar', null, '', 'text', 'gravatar', 'Will be used as the domain icon.'),
|
||||
Setting('setting_domain', 'bulletin', null, '', 'markdown', 'Bulletin'),
|
||||
);
|
||||
|
||||
SystemSetting(
|
||||
Setting('setting_basic', 'user.register', null, false, 'checkbox', 'Allow register'),
|
||||
Setting('setting_basic', 'user.create_domain', null, true, 'checkbox', 'Allow domain creation'),
|
||||
@@ -87,6 +102,8 @@ SystemSetting(
|
||||
Setting('setting_server', 'server.host', null, null, 'text', 'Server Host'),
|
||||
Setting('setting_server', 'server.url', null, null, 'text', 'Server BaseURL'),
|
||||
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_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'),
|
||||
@@ -100,6 +117,7 @@ global.Hydro.model.setting = module.exports = {
|
||||
Setting,
|
||||
PreferenceSetting,
|
||||
AccountSetting,
|
||||
DomainSetting,
|
||||
SystemSetting,
|
||||
PREFERENCE_SETTINGS,
|
||||
ACCOUNT_SETTINGS,
|
||||
@@ -107,4 +125,6 @@ global.Hydro.model.setting = module.exports = {
|
||||
SETTINGS_BY_KEY,
|
||||
SYSTEM_SETTINGS,
|
||||
SYSTEM_SETTINGS_BY_KEY,
|
||||
DOMAIN_SETTINGS,
|
||||
DOMAIN_SETTINGS_BY_KEY,
|
||||
};
|
||||
@@ -97,6 +97,7 @@ global.Hydro.model.token = module.exports = {
|
||||
TYPE_CSRF_TOKEN: 1,
|
||||
TYPE_REGISTER: 2,
|
||||
TYPE_CHANGEMAIL: 3,
|
||||
TYPE_OAUTH: 4,
|
||||
|
||||
ensureIndexes,
|
||||
add,
|
||||
|
||||
+27
-17
@@ -2,6 +2,7 @@ const builtin = require('./builtin');
|
||||
const document = require('./document');
|
||||
const system = require('./system');
|
||||
const token = require('./token');
|
||||
const setting = require('./setting');
|
||||
const { UserNotFoundError, UserAlreadyExistError, LoginError } = require('../error');
|
||||
const perm = require('../permission');
|
||||
const pwhash = require('../lib/hash.hydro');
|
||||
@@ -19,14 +20,16 @@ class USER {
|
||||
this.salt = () => udoc.salt;
|
||||
this.hash = () => udoc.hash;
|
||||
this.priv = udoc.priv;
|
||||
this.timeZone = udoc.timeZone || 'Asia/Shanghai';
|
||||
this.viewLang = udoc.viewLang || 'zh_CN';
|
||||
this.codeLang = udoc.codeLang || 'c';
|
||||
this.codeTemplate = udoc.codeTemplate || '';
|
||||
|
||||
for (const key in setting.SETTINGS_BY_KEY) {
|
||||
if (udoc[key]) this[key] = udoc[key];
|
||||
else if (setting.SETTINGS_BY_KEY[key].value) {
|
||||
this[key] = setting.SETTINGS_BY_KEY[key].value;
|
||||
}
|
||||
}
|
||||
|
||||
this.regat = udoc.regat;
|
||||
this.loginat = udoc.loginat;
|
||||
this.bio = udoc.bio || '';
|
||||
this.gravatar = udoc.gravatar || '';
|
||||
this.ban = udoc.ban || false;
|
||||
this.nAccept = dudoc.nAccept || 0;
|
||||
this.nSubmit = dudoc.nSubmit || 0;
|
||||
@@ -76,10 +79,13 @@ async function getList(domainId, uids) {
|
||||
return r;
|
||||
}
|
||||
|
||||
async function getByUname(domainId, uname) {
|
||||
async function getByUname(domainId, uname, ignoreMissing = false) {
|
||||
const unameLower = uname.trim().toLowerCase();
|
||||
const udoc = await coll.findOne({ unameLower });
|
||||
if (!udoc) throw new UserNotFoundError(uname);
|
||||
if (!udoc) {
|
||||
if (ignoreMissing) return null;
|
||||
throw new UserNotFoundError(uname);
|
||||
}
|
||||
const dudoc = await getInDomain(domainId, udoc);
|
||||
return new USER(udoc, dudoc);
|
||||
}
|
||||
@@ -97,9 +103,10 @@ async function getByEmail(domainId, mail, ignoreMissing = false) {
|
||||
|
||||
function setPassword(uid, password) {
|
||||
const salt = String.random();
|
||||
return coll.findOneAndUpdate({ _id: uid }, {
|
||||
$set: { salt, hash: pwhash(password, salt), hashType: 'hydro' },
|
||||
});
|
||||
return coll.findOneAndUpdate(
|
||||
{ _id: uid },
|
||||
{ $set: { salt, hash: pwhash(password, salt), hashType: 'hydro' } },
|
||||
);
|
||||
}
|
||||
|
||||
function setById(uid, args) {
|
||||
@@ -114,11 +121,10 @@ async function changePassword(uid, currentPassword, newPassword) {
|
||||
const udoc = await getById(uid);
|
||||
udoc.checkPassword(currentPassword);
|
||||
const salt = String.random();
|
||||
return await coll.findOneAndUpdate({
|
||||
_id: udoc._id,
|
||||
}, {
|
||||
$set: { salt, hash: pwhash.hash(newPassword, salt), hashType: 'hydro' },
|
||||
});
|
||||
return await coll.findOneAndUpdate(
|
||||
{ _id: udoc._id },
|
||||
{ $set: { salt, hash: pwhash(newPassword, salt), hashType: 'hydro' } },
|
||||
);
|
||||
}
|
||||
|
||||
async function inc(_id, field, n = 1) {
|
||||
@@ -139,11 +145,14 @@ async function incDomain(domainId, uid, field, n = 1) {
|
||||
return dudoc;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise<number>} uid
|
||||
*/
|
||||
async function create({
|
||||
uid, mail, uname, password, regip = '127.0.0.1', priv = perm.PRIV_NONE,
|
||||
}) {
|
||||
const salt = String.random();
|
||||
if (!uid) uid = system.inc('user');
|
||||
if (!uid) uid = await system.inc('user');
|
||||
try {
|
||||
await coll.insertOne({
|
||||
_id: uid,
|
||||
@@ -164,6 +173,7 @@ async function create({
|
||||
} catch (e) {
|
||||
throw new UserAlreadyExistError([uid, uname, mail]);
|
||||
}
|
||||
return uid;
|
||||
}
|
||||
|
||||
function getMulti(params) {
|
||||
|
||||
@@ -7,9 +7,10 @@ async function update() {
|
||||
const [mid, $set] = await sysinfo.update();
|
||||
await coll.updateOne(
|
||||
{ mid, type: 'server' },
|
||||
{ $set: { ...$set, updateAt: new Date() } },
|
||||
{ $set: { ...$set, updateAt: new Date(), reqCount: global.Hydro.stat.reqCount } },
|
||||
{ upsert: true },
|
||||
);
|
||||
global.Hydro.stat.reqCount = 0;
|
||||
}
|
||||
|
||||
async function postInit() {
|
||||
@@ -19,7 +20,7 @@ async function postInit() {
|
||||
{ $set: { ...info, updateAt: new Date(), type: 'server' } },
|
||||
{ upsert: true },
|
||||
);
|
||||
setInterval(update, 3 * 60 * 1000);
|
||||
setInterval(update, 60 * 1000);
|
||||
}
|
||||
|
||||
global.Hydro.service.monitor = module.exports = { postInit };
|
||||
@@ -133,6 +133,7 @@ class Handler {
|
||||
files: ctx.request.files,
|
||||
query: ctx.query,
|
||||
path: ctx.path,
|
||||
referer: ctx.request.headers.referer || '/',
|
||||
};
|
||||
this.response = {
|
||||
body: '',
|
||||
@@ -348,6 +349,7 @@ class Handler {
|
||||
}
|
||||
|
||||
async function handle(ctx, HandlerClass, permission) {
|
||||
global.Hydro.stat.reqCount++;
|
||||
const h = new HandlerClass(ctx);
|
||||
try {
|
||||
const method = ctx.method.toLowerCase();
|
||||
|
||||
Reference in new issue
Block a user