108 lines
3.5 KiB
TypeScript
108 lines
3.5 KiB
TypeScript
import config from "config";
|
|
import flash from "connect-flash";
|
|
import {Router} from "express";
|
|
import session from "express-session";
|
|
|
|
import ApplicationComponent from "../ApplicationComponent.js";
|
|
import SecurityError from "../SecurityError.js";
|
|
import FrontendToolsComponent from "./FrontendToolsComponent.js";
|
|
import RedisComponent from "./RedisComponent.js";
|
|
|
|
export default class SessionComponent extends ApplicationComponent {
|
|
private readonly storeComponent: RedisComponent;
|
|
|
|
public constructor(storeComponent: RedisComponent) {
|
|
super();
|
|
this.storeComponent = storeComponent;
|
|
}
|
|
|
|
public async init(): Promise<void> {
|
|
const globals = this.getApp().asOptional(FrontendToolsComponent)?.getGlobals();
|
|
if (globals) {
|
|
globals.set('flash', () => '');
|
|
}
|
|
}
|
|
|
|
public async checkSecuritySettings(): Promise<void> {
|
|
this.checkSecurityConfigField('session.secret');
|
|
if (!config.get<boolean>('session.cookie.secure')) {
|
|
throw new SecurityError('Cannot set cookie secure field to false.');
|
|
}
|
|
}
|
|
|
|
public async initRoutes(router: Router): Promise<void> {
|
|
router.use(session({
|
|
saveUninitialized: true,
|
|
secret: config.get('session.secret'),
|
|
store: this.storeComponent.getStore(),
|
|
resave: false,
|
|
cookie: {
|
|
httpOnly: true,
|
|
secure: config.get('session.cookie.secure'),
|
|
},
|
|
rolling: true,
|
|
}));
|
|
|
|
router.use(flash());
|
|
|
|
router.use((req, res, next) => {
|
|
// Request session getters
|
|
req.getSessionOptional = () => {
|
|
return req.session;
|
|
};
|
|
req.getSession = () => {
|
|
const session = req.getSessionOptional();
|
|
if (!session) throw new Error('Session not initialized.');
|
|
return session;
|
|
};
|
|
|
|
// Session persistence
|
|
const session = req.getSession();
|
|
if (session.persistent) {
|
|
session.cookie.maxAge = config.get('session.cookie.maxAge');
|
|
} else {
|
|
session.cookie.maxAge = session.cookie.expires = undefined;
|
|
}
|
|
|
|
// Views session local
|
|
res.locals.session = session;
|
|
|
|
// Views flash function
|
|
const _flash: FlashStorage = {};
|
|
res.locals.flash = (key?: string): FlashMessages | unknown[] => {
|
|
if (key !== undefined) {
|
|
if (_flash[key] === undefined) _flash[key] = req.flash(key);
|
|
return _flash[key] || [];
|
|
}
|
|
|
|
if (_flash._messages === undefined) {
|
|
_flash._messages = {
|
|
info: req.flash('info'),
|
|
success: req.flash('success'),
|
|
warning: req.flash('warning'),
|
|
error: req.flash('error'),
|
|
'error-alert': req.flash('error-alert'),
|
|
};
|
|
}
|
|
return _flash._messages;
|
|
};
|
|
next();
|
|
});
|
|
}
|
|
}
|
|
|
|
export type FlashMessages = {
|
|
[k: string]: unknown[] | undefined
|
|
};
|
|
|
|
export type DefaultFlashMessages = FlashMessages & {
|
|
info?: unknown[] | undefined;
|
|
success?: unknown[] | undefined;
|
|
warning?: unknown[] | undefined;
|
|
error?: unknown[] | undefined;
|
|
};
|
|
|
|
type FlashStorage = FlashMessages & {
|
|
_messages?: DefaultFlashMessages,
|
|
};
|