Files
Hydro/packages/hydrooj/src/service/layers/base.ts
T

78 lines
3.6 KiB
TypeScript

import type { Next } from 'koa';
import { cloneDeep, omit } from 'lodash';
import type { KoaContext } from '@hydrooj/framework';
import { randomPick, Time } from '@hydrooj/utils';
import { PERM } from '../../model/builtin';
import domain from '../../model/domain';
import system from '../../model/system';
import token from '../../model/token';
export interface UiContextBase {
cdn_prefix: string;
cdn_dynamic: boolean;
url_prefix: string;
ws_prefix: string;
}
export const UiContextBase: UiContextBase = {
cdn_prefix: '/',
cdn_dynamic: false,
url_prefix: '/',
ws_prefix: '/',
};
export default async (ctx: KoaContext, next: Next) => {
// Base Layer
const { domainId, domainInfo } = ctx;
const args = {
domainId, ...ctx.params, ...ctx.query, ...ctx.request.body, __start: Date.now(),
};
const UiContext: any = cloneDeep(UiContextBase);
if (!process.env.DEV) {
UiContext.cdn_prefix = system.get('server.cdn');
if (UiContext.cdn_prefix.includes(',')) UiContext.cdn_prefix = randomPick(UiContext.cdn_prefix.split(','));
UiContext.ws_prefix = system.get('server.ws');
if (UiContext.ws_prefix.includes(',')) UiContext.ws_prefix = randomPick(UiContext.ws_prefix.split(','));
UiContext.cdn_dynamic = system.get('server.cdn_dynamic');
}
UiContext.domainId = domainId;
UiContext.domainVersion = domain.getVersion(domainInfo);
ctx.HydroContext.UiContext = UiContext;
ctx.HydroContext.domain = domainInfo;
ctx.HydroContext.args = args;
const header = ctx.request.headers.authorization;
const sid = header
? header.split(' ')[1] // Accept bearer token
: ctx.cookies.get('sid') || ctx.query.sid; // FIXME maybe a better way for shared conn?
const session = sid ? await token.get(sid instanceof Array ? sid[0] : sid, token.TYPE_SESSION) : null;
ctx.session = Object.create(session || { uid: 0, scope: PERM.PERM_ALL.toString() });
await next();
const request = ctx.HydroContext.request;
const ua = request.headers['user-agent'] || '';
if (!ctx.session.uid && system.get('server.ignoreUA').replace(/\r/g, '').split('\n').filter((i) => i && ua.includes(i)).length) return;
const expireSeconds = ctx.session.save
? system.get('session.saved_expire_seconds')
: system.get('session.unsaved_expire_seconds');
const isRecent = ctx.session.updateAt ? Date.now() - new Date(ctx.session.updateAt).getTime() < 5 * Time.minute : true;
if (!Object.getOwnPropertyNames(ctx.session).length && isRecent) return;
Object.assign(ctx.session, { updateIp: request.ip, updateUa: ua });
if (ctx.session._id && !ctx.session.recreate) {
await token.update(ctx.session._id, token.TYPE_SESSION, expireSeconds, omit(ctx.session, ['_id', 'recreate']));
} else {
if (ctx.session._id) await token.del(ctx.session._id, token.TYPE_SESSION);
Object.assign(ctx.session, { createIp: request.ip, createUa: ua, createHost: request.host });
[ctx.session._id] = await token.add(token.TYPE_SESSION, expireSeconds, omit(ctx.session, ['_id', 'recreate']));
}
if (!request.websocket) {
const options: any = {
expires: new Date(Date.now() + expireSeconds * 1000),
httpOnly: false,
};
if (system.get('session.domain') && ctx.request.secure && ctx.request.host.endsWith(system.get('session.domain'))) {
options.domain = system.get('session.domain');
options.sameSite = 'none';
options.secure = true;
}
ctx.cookies.set('sid', ctx.session._id, options);
}
};