Upgrade dependencies and update to express session new typings

This commit is contained in:
Alice Gaudon 2020-12-04 14:42:09 +01:00
parent 7be3e00c46
commit 87b4facea0
21 changed files with 387 additions and 324 deletions

View File

@ -23,9 +23,9 @@
},
"devDependencies": {
"@types/compression": "^1.7.0",
"@types/config": "^0.0.36",
"@types/connect-flash": "^0.0.35",
"@types/connect-redis": "^0.0.14",
"@types/config": "^0.0.37",
"@types/connect-flash": "^0.0.36",
"@types/connect-redis": "^0.0.15",
"@types/cookie": "^0.4.0",
"@types/cookie-parser": "^1.4.2",
"@types/express": "^4.17.6",
@ -74,7 +74,7 @@
"on-finished": "^2.3.0",
"redis": "^3.0.2",
"ts-node": "^9.0.0",
"tslog": "^2.10.0",
"tslog": "^3.0.1",
"uuid": "^8.0.0",
"ws": "^7.2.3"
}

View File

@ -36,7 +36,7 @@ export default abstract class Application implements Extendable<ApplicationCompo
protected abstract getMigrations(): MigrationType<Migration>[];
protected abstract async init(): Promise<void>;
protected abstract init(): Promise<void>;
protected use(thing: Controller | WebSocketListener<this> | ApplicationComponent): void {
if (thing instanceof Controller) {

View File

@ -37,7 +37,7 @@ export default abstract class ApplicationComponent {
protected async close(thingName: string, fn: (callback: (err?: Error | null) => void) => void): Promise<void> {
try {
await new Promise((resolve, reject) => fn((err?: Error | null) => {
await new Promise<void>((resolve, reject) => fn((err?: Error | null) => {
if (err) reject(err);
else resolve();
}));

View File

@ -10,7 +10,7 @@ export default abstract class Middleware {
}
protected abstract async handle(req: Request, res: Response, next: NextFunction): Promise<void>;
protected abstract handle(req: Request, res: Response, next: NextFunction): Promise<void>;
public getRequestHandler(): RequestHandler {
return async (req, res, next): Promise<void> => {

View File

@ -1,6 +1,7 @@
import WebSocket from "ws";
import {IncomingMessage} from "http";
import Application from "./Application";
import {Session} from "express-session";
export default abstract class WebSocketListener<T extends Application> {
private app!: T;
@ -15,9 +16,9 @@ export default abstract class WebSocketListener<T extends Application> {
public abstract path(): string;
public abstract async handle(
public abstract handle(
socket: WebSocket,
request: IncomingMessage,
session: Express.Session | null,
session: Session | null,
): Promise<void>;
}

View File

@ -10,6 +10,7 @@ import config from "config";
import Application from "../Application";
import NunjucksComponent from "../components/NunjucksComponent";
import AuthMethod from "./AuthMethod";
import {Session, SessionData} from "express-session";
export default class AuthGuard {
private readonly authMethods: AuthMethod<AuthProof<User>>[];
@ -54,14 +55,14 @@ export default class AuthGuard {
public async getProofs(req: Request): Promise<AuthProof<User>[]> {
const proofs = [];
if (req.session) {
if (req.getSessionOptional()) {
proofs.push(...await this.getProofsForSession(req.session));
}
proofs.push(...await this.getProofsForRequest(req));
return proofs;
}
public async getProofsForSession(session: Express.Session): Promise<AuthProof<User>[]> {
public async getProofsForSession(session: Session & Partial<SessionData>): Promise<AuthProof<User>[]> {
if (!session.is_authenticated) return [];
const proofs = [];
@ -104,7 +105,7 @@ export default class AuthGuard {
}
public async authenticateOrRegister(
session: Express.Session,
session: Session & Partial<SessionData>,
proof: AuthProof<User>,
onLogin?: (user: User) => Promise<void>,
beforeRegister?: (connection: Connection, user: User) => Promise<RegisterCallback[]>,

View File

@ -1,6 +1,7 @@
import User from "./models/User";
import AuthProof from "./AuthProof";
import {Request, Response} from "express";
import {Session} from "express-session";
export default interface AuthMethod<P extends AuthProof<User>> {
@ -19,7 +20,7 @@ export default interface AuthMethod<P extends AuthProof<User>> {
findUserByIdentifier(identifier: string): Promise<User | null>;
getProofsForSession?(session: Express.Session): Promise<P[]>;
getProofsForSession?(session: Session): Promise<P[]>;
getProofsForRequest?(req: Request): Promise<P[]>;

View File

@ -14,6 +14,7 @@ import AuthMagicLinkActionType from "./AuthMagicLinkActionType";
import Validator, {EMAIL_REGEX} from "../../db/Validator";
import ModelFactory from "../../db/ModelFactory";
import UserNameComponent from "../models/UserNameComponent";
import {Session} from "express-session";
export default class MagicLinkAuthMethod implements AuthMethod<MagicLink> {
public constructor(
@ -39,7 +40,7 @@ export default class MagicLinkAuthMethod implements AuthMethod<MagicLink> {
.first())?.user.getOrFail() || null;
}
public async getProofsForSession(session: Express.Session): Promise<MagicLink[]> {
public async getProofsForSession(session: Session): Promise<MagicLink[]> {
return await MagicLink.select()
.where('session_id', session.id)
.where('action_type', [AuthMagicLinkActionType.LOGIN, AuthMagicLinkActionType.REGISTER], WhereTest.IN)

View File

@ -3,6 +3,7 @@ import {IncomingMessage} from "http";
import WebSocketListener from "../../WebSocketListener";
import MagicLink from "../models/MagicLink";
import Application from "../../Application";
import {Session} from "express-session";
export default class MagicLinkWebSocketListener<A extends Application> extends WebSocketListener<A> {
private readonly connections: { [p: string]: (() => void)[] | undefined } = {};
@ -14,7 +15,7 @@ export default class MagicLinkWebSocketListener<A extends Application> extends W
}
}
public async handle(socket: WebSocket, request: IncomingMessage, session: Express.Session | null): Promise<void> {
public async handle(socket: WebSocket, request: IncomingMessage, session: Session | null): Promise<void> {
// Drop if requested without session
if (!session) {
socket.close(1002, 'Session is required for this request.');

View File

@ -13,6 +13,7 @@ import UserPasswordComponent from "./UserPasswordComponent";
import UserNameComponent, {USERNAME_REGEXP} from "../models/UserNameComponent";
import ModelFactory from "../../db/ModelFactory";
import {ServerError} from "../../HttpError";
import {Session} from "express-session";
export default class PasswordAuthMethod implements AuthMethod<PasswordAuthProof> {
public constructor(
@ -46,7 +47,7 @@ export default class PasswordAuthMethod implements AuthMethod<PasswordAuthProof>
return null;
}
public async getProofsForSession(session: Express.Session): Promise<PasswordAuthProof[]> {
public async getProofsForSession(session: Session): Promise<PasswordAuthProof[]> {
const proof = PasswordAuthProof.getProofForSession(session);
return proof ? [proof] : [];
}

View File

@ -1,14 +1,15 @@
import AuthProof from "../AuthProof";
import User from "../models/User";
import UserPasswordComponent from "./UserPasswordComponent";
import {Session, SessionData} from "express-session";
export default class PasswordAuthProof implements AuthProof<User> {
public static getProofForSession(session: Express.Session): PasswordAuthProof | null {
public static getProofForSession(session: Session & Partial<SessionData>): PasswordAuthProof | null {
return session.auth_password_proof ? new PasswordAuthProof(session) : null;
}
public static createAuthorizedProofForRegistration(session: Express.Session): PasswordAuthProof {
public static createAuthorizedProofForRegistration(session: Session): PasswordAuthProof {
const proofForSession = new PasswordAuthProof(session);
proofForSession.authorized = true;
proofForSession.forRegistration = true;
@ -16,17 +17,17 @@ export default class PasswordAuthProof implements AuthProof<User> {
return proofForSession;
}
public static createProofForLogin(session: Express.Session): PasswordAuthProof {
public static createProofForLogin(session: Session & Partial<SessionData>): PasswordAuthProof {
return new PasswordAuthProof(session);
}
private readonly session: Express.Session;
private readonly session: Session & Partial<SessionData>;
private authorized: boolean;
private forRegistration: boolean = false;
private userId: number | null;
private userPassword: UserPasswordComponent | null = null;
private constructor(session: Express.Session) {
private constructor(session: Session & Partial<SessionData>) {
this.session = session;
this.authorized = session.auth_password_proof?.authorized || false;
this.forRegistration = session.auth_password_proof?.forRegistration || false;
@ -80,3 +81,9 @@ export default class PasswordAuthProof implements AuthProof<User> {
};
}
}
export type PasswordAuthProofSessionData = {
authorized: boolean,
forRegistration: boolean,
userId: number | null,
};

View File

@ -3,11 +3,12 @@ import {Request, Router} from "express";
import crypto from "crypto";
import {BadRequestError} from "../HttpError";
import {AuthMiddleware} from "../auth/AuthComponent";
import {Session, SessionData} from "express-session";
export default class CsrfProtectionComponent extends ApplicationComponent {
private static readonly excluders: ((req: Request) => boolean)[] = [];
public static getCsrfToken(session: Express.Session): string {
public static getCsrfToken(session: Session & Partial<SessionData>): string {
if (typeof session.csrf !== 'string') {
session.csrf = crypto.randomBytes(64).toString('base64');
}

View File

@ -31,7 +31,7 @@ export default class LogRequestsComponent extends ApplicationComponent {
body: req.body,
files: req.files,
cookies: req.cookies,
sessionId: req.session?.id,
sessionId: req.sessionID,
result: {
code: res.statusCode,
},

View File

@ -5,7 +5,7 @@ import onFinished from "on-finished";
export default class RedirectBackComponent extends ApplicationComponent {
public static getPreviousURL(req: Request, defaultUrl?: string): string | undefined {
return req.session?.previousUrl || defaultUrl;
return req.getSessionOptional()?.previousUrl || defaultUrl;
}
public async handle(router: Router): Promise<void> {
@ -21,7 +21,7 @@ export default class RedirectBackComponent extends ApplicationComponent {
};
onFinished(res, (err) => {
const session = req.session;
const session = req.getSessionOptional();
if (session) {
const contentType = res.getHeader('content-type');
if (!err && res.statusCode === 200 && (

View File

@ -38,10 +38,14 @@ export default class SessionComponent extends ApplicationComponent {
router.use(flash());
router.use((req, res, next) => {
req.getSession = () => {
if (!req.session) throw new Error('Session not initialized.');
req.getSessionOptional = () => {
return req.session;
};
req.getSession = () => {
const session = req.getSessionOptional();
if (!session) throw new Error('Session not initialized.');
return session;
};
res.locals.session = req.getSession();

View File

@ -65,7 +65,7 @@ export default class WebSocketServerComponent extends ApplicationComponent {
session.id = sid;
store.createSession(<Request>request, session);
listener.handle(socket, request, <Express.Session>session).catch(err => {
listener.handle(socket, request, (<Request>request).session).catch(err => {
log.error(err, 'Error in websocket listener.');
});
});

View File

@ -14,9 +14,9 @@ export default abstract class Migration {
return this.version > currentVersion;
}
public abstract async install(): Promise<void>;
public abstract install(): Promise<void>;
public abstract async rollback(): Promise<void>;
public abstract rollback(): Promise<void>;
public registerModels?(): void;

View File

@ -97,8 +97,11 @@ export default abstract class ModelRelation<S extends Model, O extends Model, R
public async has(model: O): Promise<boolean> {
const models = await this.get();
if (Array.isArray(models)) return models.find(m => m.equals(model)) !== undefined;
else return models && models.equals(model);
if (models instanceof Array) {
return models.find(m => m.equals(model)) !== undefined;
} else {
return models !== null && models.equals(model);
}
}
}

View File

@ -57,7 +57,7 @@ export default class MysqlConnectionManager {
password: config.get('mysql.password'),
charset: 'utf8mb4',
});
await new Promise((resolve, reject) => {
await new Promise<void>((resolve, reject) => {
connection.query(`CREATE DATABASE IF NOT EXISTS ${dbName}`, (error) => {
return error !== null ?
reject(error) :
@ -140,7 +140,7 @@ export default class MysqlConnectionManager {
connection.beginTransaction((err?: MysqlError) => {
if (err) {
reject(err);
this.pool.releaseConnection(connection);
connection.release();
return;
}
@ -148,16 +148,16 @@ export default class MysqlConnectionManager {
connection.commit((err?: MysqlError) => {
if (err) {
this.rejectAndRollback(connection, err, reject);
this.pool.releaseConnection(connection);
connection.release();
return;
}
this.pool.releaseConnection(connection);
connection.release();
resolve(val);
});
}).catch(err => {
this.rejectAndRollback(connection, err, reject);
this.pool.releaseConnection(connection);
connection.release();
});
});
});

View File

@ -3,13 +3,18 @@ import {Type} from "../Utils";
import Middleware from "../Middleware";
import {FlashMessages} from "../components/SessionComponent";
import {Logger} from "tslog";
import {Session, SessionData} from "express-session";
import {PasswordAuthProofSessionData} from "../auth/password/PasswordAuthProof";
declare global {
namespace Express {
export interface Request {
log: Logger;
getSession(): Session;
getSession(): Session & Partial<SessionData>;
getSessionOptional(): Session & Partial<SessionData> | undefined;
files: Files;
@ -32,3 +37,17 @@ declare global {
}
}
}
declare module 'express-session' {
interface SessionData {
id?: string;
previousUrl?: string;
is_authenticated?: boolean;
auth_password_proof?: PasswordAuthProofSessionData;
csrf?: string;
}
}

595
yarn.lock

File diff suppressed because it is too large Load Diff