Upgrade dependencies and update to express session new typings
This commit is contained in:
parent
7be3e00c46
commit
87b4facea0
@ -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"
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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();
|
||||
}));
|
||||
|
@ -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> => {
|
||||
|
@ -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>;
|
||||
}
|
||||
|
@ -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[]>,
|
||||
|
@ -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[]>;
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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.');
|
||||
|
@ -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] : [];
|
||||
}
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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');
|
||||
}
|
||||
|
@ -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,
|
||||
},
|
||||
|
@ -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 && (
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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.');
|
||||
});
|
||||
});
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
21
src/types/Express.d.ts
vendored
21
src/types/Express.d.ts
vendored
@ -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;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user