|
|
@@ -1,14 +1,158 @@
|
|
|
-import Express from "express"
|
|
|
+import Express, {Request, Response} from "express"
|
|
|
+import gUserService from "../services/userService";
|
|
|
+import ConfigurationManager from "../config";
|
|
|
+
|
|
|
+export class UnauthorizedUser extends Error {
|
|
|
+ private isLoggedIn: boolean;
|
|
|
+
|
|
|
+ constructor(isLoggedIn: boolean) {
|
|
|
+ super("Unauthorized user");
|
|
|
+ this.isLoggedIn = isLoggedIn;
|
|
|
+ }
|
|
|
+
|
|
|
+ public isLoggedUser(): boolean {
|
|
|
+ return this.isLoggedIn;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+export interface SessionData {
|
|
|
+ logged: boolean;
|
|
|
+}
|
|
|
+
|
|
|
+export interface Session {
|
|
|
+ data: SessionData;
|
|
|
+ validUntil: number;
|
|
|
+}
|
|
|
+
|
|
|
+class SessionManager {
|
|
|
+ sessions: Map<string, Session> = new Map();
|
|
|
+ private sessionTimeout: number = 60 * 60* 1000;
|
|
|
+
|
|
|
+ constructor() {
|
|
|
+ setInterval(() => this.clean.bind(this), 60 * 1000);
|
|
|
+ }
|
|
|
+ public get(sessionId: string|undefined): SessionData|null {
|
|
|
+ if (!sessionId)
|
|
|
+ return null;
|
|
|
+ const session = this.sessions.get(sessionId);
|
|
|
+ if (!session || session.validUntil < Date.now())
|
|
|
+ return null;
|
|
|
+ return this.sessions.get(sessionId)?.data || null;
|
|
|
+ }
|
|
|
+ public pingSession(sessionId: string|undefined) {
|
|
|
+ if (!sessionId)
|
|
|
+ return;
|
|
|
+ const session = this.sessions.get(sessionId);
|
|
|
+ if (!session || session.validUntil < Date.now())
|
|
|
+ return;
|
|
|
+ this.sessions.get(sessionId)!.validUntil = Date.now() + this.sessionTimeout;
|
|
|
+ }
|
|
|
+ public create(): string {
|
|
|
+ const uuid = crypto.randomUUID();
|
|
|
+ this.sessions.set(uuid, <Session> {
|
|
|
+ data: {
|
|
|
+ logged: true
|
|
|
+ },
|
|
|
+ validUntil: Date.now() + this.sessionTimeout
|
|
|
+ });
|
|
|
+ return uuid;
|
|
|
+ }
|
|
|
+ public remove(sessionId?: string) {
|
|
|
+ if (!sessionId)
|
|
|
+ return;
|
|
|
+ this.sessions.delete(sessionId);
|
|
|
+ }
|
|
|
+ public clean() {
|
|
|
+ const now = Date.now();
|
|
|
+ for (var i of this.sessions) {
|
|
|
+ if (i[1].validUntil < now)
|
|
|
+ this.sessions.delete(i[0]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+export type { SessionManager }
|
|
|
+export const gSessionManager = new SessionManager();
|
|
|
+const COOKIE_SESSION = "sessionId";
|
|
|
+
|
|
|
+export class SecurityRequirement {
|
|
|
+ private static throwIfRequired(req: Request, value: boolean, nothrow: boolean|undefined): boolean {
|
|
|
+ if (!value && nothrow !== true)
|
|
|
+ throw new UnauthorizedUser(!!this.getSessionObj(req));
|
|
|
+ return value;
|
|
|
+ }
|
|
|
+ public static getSessionObj(req: Request): SessionData|null {
|
|
|
+ return gSessionManager.get(req.cookies?.[COOKIE_SESSION])
|
|
|
+ }
|
|
|
+ public static requireLoggedUser(req: Request, nothrow?: boolean): boolean {
|
|
|
+ if (req.query.apiKey && ConfigurationManager.apiKeys.includes(req.query.apiKey.toString()))
|
|
|
+ return true;
|
|
|
+ const sessionObj = this.getSessionObj(req);
|
|
|
+ return this.throwIfRequired(req, sessionObj !== null && sessionObj.logged, nothrow);
|
|
|
+ }
|
|
|
+ public static setLoggedUser(req: Request, res: Response) {
|
|
|
+ if (!req.cookies[COOKIE_SESSION]) {
|
|
|
+ res.cookie(COOKIE_SESSION, gSessionManager.create());
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ let sessionObj = this.getSessionObj(req.cookies[COOKIE_SESSION]);
|
|
|
+ if (sessionObj)
|
|
|
+ sessionObj.logged = true;
|
|
|
+ else
|
|
|
+ res.cookie(COOKIE_SESSION, gSessionManager.create());
|
|
|
+ }
|
|
|
+ public static logout(req: Request, res: Response) {
|
|
|
+ gSessionManager.remove(req.cookies?.[COOKIE_SESSION]);
|
|
|
+ res.cookie(COOKIE_SESSION, null);
|
|
|
+ }
|
|
|
+ public static tryLogin(req: Request, res: Response, username: string, password: string): boolean {
|
|
|
+ if (!gUserService.tryLogin(username, password))
|
|
|
+ return false;
|
|
|
+ this.setLoggedUser(req, res);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ public static pingSession(req: Request) {
|
|
|
+ gSessionManager.pingSession(req.cookies?.[COOKIE_SESSION]);
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
export class HtmlController {
|
|
|
private static GenerateContext(override: any): object {
|
|
|
override = override || {};
|
|
|
override.title = override.title || "Title";
|
|
|
- override.javascript = override.javascript !== undefined ? override.javascript : "js/front.js"
|
|
|
+ override.javascript = override.javascript !== undefined ? override.javascript : "js/front.min.js"
|
|
|
return override;
|
|
|
}
|
|
|
|
|
|
+ private static renderIndex(req: Request, res: Response) {
|
|
|
+ if (!SecurityRequirement.requireLoggedUser(req, true))
|
|
|
+ return res.redirect("/login");
|
|
|
+ res.render("index", HtmlController.GenerateContext(null));
|
|
|
+ }
|
|
|
+
|
|
|
+ private static renderLogin(req: Request, res: Response) {
|
|
|
+ if (SecurityRequirement.requireLoggedUser(req, true))
|
|
|
+ return res.redirect("/");
|
|
|
+ res.render("login", HtmlController.GenerateContext({javascript: null}));
|
|
|
+ }
|
|
|
+
|
|
|
+ private static renderLogout(req: Request, res: Response) {
|
|
|
+ SecurityRequirement.logout(req, res);
|
|
|
+ return res.redirect("/");
|
|
|
+ }
|
|
|
+
|
|
|
+ private static postLogin(req: Request, res: Response) {
|
|
|
+ if (SecurityRequirement.tryLogin(req, res, req.body.username, req.body.password)) {
|
|
|
+ res.redirect("/");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ res.render("login", HtmlController.GenerateContext({javascript: null, failed: true}));
|
|
|
+ }
|
|
|
+
|
|
|
public static RegisterHtmlPages(app: Express.Express): void {
|
|
|
- app.get("/", (_, res) => { res.render("index", HtmlController.GenerateContext(null)); });
|
|
|
+ app.get("/", (req, res) => this.renderIndex(req, res));
|
|
|
+ app.get("/login", (req, res) => this.renderLogin(req, res));
|
|
|
+ app.post("/login", (req, res) => this.postLogin(req, res));
|
|
|
+ app.get("/logout", (req, res) => this.renderLogout(req, res));
|
|
|
}
|
|
|
}
|