Skip to content

Commit

Permalink
wip: rewrotte all session middlewares
Browse files Browse the repository at this point in the history
koa-session-socketio didn't work with the new auth layer, when i tried
rewriting it i noticed that koa-memory-store was a big memory leak
and koa-session was creating sessions for all requests, which is a DDoS
rist, so i decided to ditch all of that and jsut write my own session
management which is fully type safe and performant.
  • Loading branch information
tabarra committed Oct 22, 2023
1 parent f773b3f commit 6d0a446
Show file tree
Hide file tree
Showing 23 changed files with 333 additions and 151 deletions.
16 changes: 8 additions & 8 deletions core/components/AdminVault/providers/CitizenFX.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ const userInfoSchema = z.object({
});
export type UserInfoType = z.infer<typeof userInfoSchema> & { picture: string | undefined };

const getOauthState = (stateKern: string) => {
const stateSeed = `tx:cfxre:${stateKern}`;
return crypto.createHash('SHA1').update(stateSeed).digest('hex');
};


export default class CitizenFXProvider {
private client?: BaseClient;
Expand All @@ -28,7 +33,7 @@ export default class CitizenFXProvider {
client_secret: 'txadmin_test',
response_types: ['openid'],
});
this.client[custom.clock_tolerance] = 2 * 60 * 60; //Two hours due to the DST change. Reduce to 300s.
this.client[custom.clock_tolerance] = 2 * 60 * 60; //Two hours due to the DST change.
custom.setHttpOptionsDefaults({
timeout: 10000,
});
Expand All @@ -42,11 +47,9 @@ export default class CitizenFXProvider {
getAuthURL(redirectUri: string, stateKern: string) {
if (!this.client) throw new Error(`${modulename} is not ready`);

const stateSeed = `txAdmin:${stateKern}`;
const state = crypto.createHash('SHA1').update(stateSeed).digest('hex');
const url = this.client.authorizationUrl({
redirect_uri: redirectUri,
state: state,
state: getOauthState(stateKern),
response_type: 'code',
scope: 'openid identify',
});
Expand All @@ -65,11 +68,8 @@ export default class CitizenFXProvider {
const params = this.client.callbackParams(ctx as any); //FIXME: idk why it works, but it does
if (typeof params.code == 'undefined') throw new Error('code not present');

//Check the state
const stateSeed = `txAdmin:${stateKern}`;
const stateExpected = crypto.createHash('SHA1').update(stateSeed).digest('hex');

//Exchange code for token
const stateExpected = getOauthState(stateKern);
const tokenSet = await this.client.callback(redirectUri, params, { state: stateExpected });
if (typeof tokenSet !== 'object') throw new Error('tokenSet is not an object');
if (typeof tokenSet.access_token == 'undefined') throw new Error('access_token not present');
Expand Down
15 changes: 11 additions & 4 deletions core/components/WebServer/authLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { z } from "zod";
import { convars } from '@core/globalData';
import consoleFactory from '@extras/console';
import TxAdmin from "@core/txAdmin";
import { SessToolsType } from "./middlewares/sessionMws";
const console = consoleFactory(modulename);


Expand Down Expand Up @@ -132,11 +133,11 @@ export const checkRequestAuth = (
txAdmin: TxAdmin,
reqHeader: { [key: string]: unknown },
reqIP: string,
sess: any,
sessTools: SessToolsType,
) => {
return typeof reqHeader['x-txadmin-token'] === 'string'
? nuiAuthLogic(txAdmin, reqIP, reqHeader)
: normalAuthLogic(txAdmin, sess);
: normalAuthLogic(txAdmin, sessTools);
}


Expand All @@ -145,9 +146,15 @@ export const checkRequestAuth = (
*/
export const normalAuthLogic = (
txAdmin: TxAdmin,
sess: any
sessTools: SessToolsType
): AuthLogicReturnType => {
try {
// Getting session
const sess = sessTools.get();
if (!sess) {
return failResp();
}

// Parsing session auth
const validationResult = validSessAuthSchema.safeParse(sess?.auth);
if (!validationResult.success) {
Expand All @@ -157,7 +164,7 @@ export const normalAuthLogic = (

// Checking for expiration
if (sessAuth.expiresAt !== false && Date.now() > sessAuth.expiresAt) {
return failResp(`Expired session from '${sess.auth.username}'.`);
return failResp(`Expired session from '${sess.auth?.username}'.`);
}

// Searching for admin in AdminVault
Expand Down
26 changes: 8 additions & 18 deletions core/components/WebServer/ctxTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,10 @@ import { ParameterizedContext } from "koa";
import { CtxTxVars } from "./middlewares/ctxVarsMw";
import { CtxTxUtils } from "./middlewares/ctxUtilsMw";
import { AuthedAdminType } from "./authLogic";
import { SessToolsType } from "./middlewares/sessionMws";
import { Socket } from "socket.io";

/**
* Session stuff
*/
//From the koa-session docs, the DefinitelyTyped package is wrong.
export type DefaultCtxSession = Readonly<{
isNew?: true;
maxAge: number;
externalKey: string;
save: () => void;
manuallyCommit: () => void;
}> & {
auth?: any;
[key: string]: unknown | undefined;
};

/**
* The context types
*/
//Right as it comes from Koa
export type RawKoaCtx = ParameterizedContext<
{ [key: string]: unknown }, //state
Expand All @@ -31,7 +16,7 @@ export type RawKoaCtx = ParameterizedContext<

//After passing through the libs (session, serve, body parse, etc)
export type CtxWithSession = RawKoaCtx & {
session: DefaultCtxSession;
sessTools: SessToolsType;
request: any;
}

Expand All @@ -48,3 +33,8 @@ export type InitializedCtx = CtxWithVars & CtxTxUtils;
export type AuthedCtx = InitializedCtx & {
admin: AuthedAdminType;
}

//The socket.io version of "context"
export type SocketWithSession = Socket & {
sessTools: SessToolsType;
};
2 changes: 1 addition & 1 deletion core/components/WebServer/getReactIndex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export default async function getReactIndex(ctx: CtxWithVars | AuthedCtx) {
ctx.txAdmin,
ctx.request.headers,
ctx.ip,
ctx.session
ctx.sessTools
);
let authedAdmin: AuthedAdminType | false = false;
if (authResult.success) {
Expand Down
28 changes: 8 additions & 20 deletions core/components/WebServer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,9 @@ import Koa from 'koa';
import KoaBodyParser from 'koa-bodyparser';
//@ts-ignore
import KoaServe from 'koa-static';
//@ts-ignore
import KoaSession from 'koa-session';
import KoaSessionMemoryStoreClass from 'koa-session-memory';
import KoaCors from '@koa/cors';

import { Server as SocketIO } from 'socket.io';
//@ts-ignore
import SessionIO from 'koa-session-socketio';
import WebSocket from './webSocket';

import { customAlphabet } from 'nanoid';
Expand All @@ -27,6 +22,7 @@ import TxAdmin from '@core/txAdmin';
import topLevelMw from './middlewares/topLevelMw';
import ctxVarsMw from './middlewares/ctxVarsMw';
import ctxUtilsMw from './middlewares/ctxUtilsMw';
import { SessionMemoryStorage, koaSessMw, socketioSessMw } from './middlewares/sessionMws';
const console = consoleFactory(modulename);
const nanoid = customAlphabet(dict51, 32);

Expand All @@ -41,12 +37,11 @@ export default class WebServer {
readonly #txAdmin: TxAdmin;
public isListening = false;
private httpRequestsCounter = 0;
private koaSessionKey: string;
private sessionCookieName: string;
public luaComToken: string;
//setupKoa
private app: Koa;
public koaSessionMemoryStore: typeof KoaSessionMemoryStoreClass;
private sessionInstance: typeof KoaSession;
public sessionStore: SessionMemoryStorage;
private koaCallback: (req: any, res: any) => Promise<void>;
//setupWebSocket
private io: SocketIO;
Expand Down Expand Up @@ -74,7 +69,7 @@ export default class WebServer {
const pathHash = crypto.createHash('shake256', { outputLength: 6 })
.update(txAdmin.info.serverProfilePath)
.digest('hex');
this.koaSessionKey = `tx:${txAdmin.info.serverProfile}:${pathHash}`;
this.sessionCookieName = `tx:${txAdmin.info.serverProfile}:${pathHash}`;
this.luaComToken = nanoid();


Expand All @@ -88,15 +83,6 @@ export default class WebServer {
// due to the many possible ways you can connect to koa.
// this.app.proxy = true;

//Session
this.koaSessionMemoryStore = new KoaSessionMemoryStoreClass();
this.sessionInstance = KoaSession({
store: this.koaSessionMemoryStore,
key: this.koaSessionKey,
rolling: true,
maxAge: 24 * 60 * 60 * 1000, //one day
}, this.app);

//Setting up app
this.app.on('error', (error, ctx) => {
if (!(
Expand Down Expand Up @@ -126,10 +112,11 @@ export default class WebServer {
: path.join(txEnv.txAdminResourcePath, 'panel');
this.app.use(KoaServe(path.join(txEnv.txAdminResourcePath, 'web/public'), { index: false, defer: false }));
this.app.use(KoaServe(panelPublicPath, { index: false, defer: false }));
this.app.use(this.sessionInstance);
this.app.use(KoaBodyParser({ jsonLimit }));

//Custom stuff
this.sessionStore = new SessionMemoryStorage();
this.app.use(koaSessMw(this.sessionCookieName, this.sessionStore));
this.app.use(ctxVarsMw(txAdmin));
this.app.use(ctxUtilsMw);

Expand All @@ -155,7 +142,7 @@ export default class WebServer {
// Setting up SocketIO
// ===================
this.io = new SocketIO(HttpClass.createServer(), { serveClient: false });
this.io.use(SessionIO(this.koaSessionKey, this.koaSessionMemoryStore));
this.io.use(socketioSessMw(this.sessionCookieName, this.sessionStore));
this.webSocket = new WebSocket(this.#txAdmin, this.io);
//@ts-ignore
this.io.on('connection', this.webSocket.handleConnection.bind(this.webSocket));
Expand All @@ -174,6 +161,7 @@ export default class WebServer {
httpCallbackHandler(req: Request, res: Response) {
//Calls the appropriate callback
try {
// console.debug(`HTTP ${req.method} ${req.url}`);
this.httpRequestsCounter++;
if (req.url.startsWith('/socket.io')) {
//@ts-ignore
Expand Down
10 changes: 5 additions & 5 deletions core/components/WebServer/middlewares/authMws.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const console = consoleFactory(modulename);

/**
* Intercom auth middleware
* This does not set ctx.admin and does not use ctx.session whatsoever.
* This does not set ctx.admin and does not use session/cookies whatsoever.
*/
export const intercomAuthMw = async (ctx: InitializedCtx, next: Function) => {
if (
Expand All @@ -30,10 +30,10 @@ export const webAuthMw = async (ctx: InitializedCtx, next: Function) => {
ctx.txAdmin,
ctx.request.headers,
ctx.ip,
ctx.session
ctx.sessTools
);
if (!authResult.success) {
ctx.session.auth = {}; //clearing session
ctx.sessTools.destroy();
if (authResult.rejectReason) {
console.verbose.warn(`Invalid session auth: ${authResult.rejectReason}`);
}
Expand All @@ -60,10 +60,10 @@ export const apiAuthMw = async (ctx: InitializedCtx, next: Function) => {
ctx.txAdmin,
ctx.request.headers,
ctx.ip,
ctx.session
ctx.sessTools
);
if (!authResult.success) {
ctx.session.auth = {}; //clearing session
ctx.sessTools.destroy();
if (authResult.rejectReason) {
console.verbose.warn(`Invalid session auth: ${authResult.rejectReason}`);
}
Expand Down
2 changes: 1 addition & 1 deletion core/components/WebServer/middlewares/ctxUtilsMw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ async function renderLoginView(data: any, txVars: CtxTxVars, dynamicAds: Dynamic
/**
* Middleware that adds some helper functions and data to the koa ctx object
*/
export default async function setupUtilsMw(ctx: CtxWithVars, next: Next) {
export default async function ctxUtilsMw(ctx: CtxWithVars, next: Next) {
//Shortcuts
const isWebInterface = ctx.txVars.isWebInterface;
const txAdmin = ctx.txAdmin;
Expand Down
4 changes: 2 additions & 2 deletions core/components/WebServer/middlewares/ctxVarsMw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export type CtxTxVars = {
/**
* Middleware responsible for setting up the ctx.txVars
*/
const setupVarsMw = (txAdmin: TxAdmin) => {
const ctxVarsMw = (txAdmin: TxAdmin) => {
return (ctx: CtxWithSession, next: Next) => {
//Prepare variables
const txVars: CtxTxVars = {
Expand Down Expand Up @@ -61,4 +61,4 @@ const setupVarsMw = (txAdmin: TxAdmin) => {
}
}

export default setupVarsMw;
export default ctxVarsMw;
Loading

0 comments on commit 6d0a446

Please sign in to comment.