From 16016071a424ae6c68ea7a86cf9119cf37c3471c Mon Sep 17 00:00:00 2001 From: "Navy.gif" Date: Tue, 9 May 2023 14:20:15 +0300 Subject: [PATCH] rolling session --- src/server/middleware/Authenticator.ts | 59 ++++++++++++++++---------- 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/src/server/middleware/Authenticator.ts b/src/server/middleware/Authenticator.ts index 448aba4..c4f43b8 100644 --- a/src/server/middleware/Authenticator.ts +++ b/src/server/middleware/Authenticator.ts @@ -17,7 +17,7 @@ import { MiddlewareFunction, Request, Response } from "../../../@types/Server.js import { NextFunction } from "express"; import { User } from "../structures/index.js"; -const OAuthLinks: {[key: string]: string} = {}; +const OAuthLinks: { [key: string]: string } = {}; type Cookie = { secure?: boolean, @@ -45,7 +45,7 @@ class Authenticator { #passport: passport.Authenticator; #logger: LoggerClient; - #OAuthParams: {[key: string]: OAuthParams | null}; + #OAuthParams: { [key: string]: OAuthParams | null }; /** * Creates an instance of Authenticator. @@ -64,19 +64,31 @@ class Authenticator { constructor ( server: Server, users: UserDatabaseInterface, - { sessionStorage, secret, name, cookie = {} }: { sessionStorage: MongoStore, secret: string, name: string, cookie: Cookie } + { + rollingSession = true, + sessionStorage, + secret, + name, + cookie = {} + }: { + sessionStorage: MongoStore, + rollingSession?: boolean, + secret: string, + name: string, + cookie: Cookie + } ) { - + // if (!(users instanceof UserDatabaseInterface)) // Util.fatal(new Error(`Expecting user database to be an instance inheriting UserDatabaseInterface`)); this.#_userdb = users; - if (!sessionStorage) + if (!sessionStorage) Util.fatal(new Error('Missing session storage')); this.#logger = server.createLogger(this); this.#_cookieName = name; - + cookie = { maxAge: 0.5 * 24 * 60 * 60 * 1000, secure: false, ...cookie }; cookie.secure = cookie.secure && process.env.NODE_ENV !== 'development'; server.app.use(session({ @@ -85,7 +97,8 @@ class Authenticator { store: sessionStorage, secret, resave: false, - saveUninitialized: false + saveUninitialized: false, + rolling: rollingSession })); this.#passport = passport; @@ -107,8 +120,8 @@ class Authenticator { this.#OAuthParams = {}; server.OAuthProviders.forEach(provider => { const providerName = provider.name.toLowerCase(); - OAuthLinks[providerName] = provider.authoriseURL; - OAuthLinks[providerName +'Token'] = provider.tokenURL; + OAuthLinks[providerName] = provider.authoriseURL; + OAuthLinks[providerName + 'Token'] = provider.tokenURL; }); } @@ -121,7 +134,7 @@ class Authenticator { * @param {object} [{ failureRedirect = '/api/login/fail', successRedirect = '/home' }={}] * @memberof Authenticator */ - addStrategy (name: string, strategy: passport.Strategy, { failureRedirect = '/api/login/fail', successRedirect, authParams }: {failureRedirect?: string, successRedirect?: string, authParams?: OAuthParams } = {}) { + addStrategy (name: string, strategy: passport.Strategy, { failureRedirect = '/api/login/fail', successRedirect, authParams }: { failureRedirect?: string, successRedirect?: string, authParams?: OAuthParams } = {}) { this.#logger.info(`Adding ${name} authentication strategy`); this.#passport.use(name, strategy); this.#OAuthParams[name] = authParams || null; @@ -161,31 +174,31 @@ class Authenticator { // For API requests, does not redirect to a login page async #_auth (req: Request, res: Response, next: NextFunction) { - if (await this.#_authenticate(req, res)) + if (await this.#_authenticate(req, res)) return next(); } // Meant for non-api paths #_authenticateRedirect (req: Request, res: Response, next: NextFunction) { // '/login' should point towards a login page, should probably make this configurable - if (!req.isAuthenticated()) + if (!req.isAuthenticated()) return res.redirect('/login'); - if (req.user.temporary) + if (req.user.temporary) return res.redirect('/login/finalise'); next(); } - + // Check if request is authenticated, meant for API requests // Takes care of responding to unauthenticated requests async #_authenticate (req: Request, res: Response) { if (req.user) { if (req.user.temporary) { - res.status(403).send('Temporary accounts must be finalised before proceeding'); + res.status(403).send('Temporary accounts must be finalised before proceeding'); return false; } return true; } else if (req.get('cookie')?.includes(this.#_cookieName)) { - res.status(401).send('Invalid session'); + res.status(401).send('Invalid session'); return false; } @@ -202,16 +215,16 @@ class Authenticator { const application = await this.#_userdb.matchToken(key); if (application) { - req.user = application; + req.user = application; } else { - res.status(401).send('Unknown identity'); + res.status(401).send('Unknown identity'); return false; } return true; } - + /** * Authorisation implicitly checks for authentication * @@ -221,17 +234,17 @@ class Authenticator { * @memberof Authenticator */ createAuthoriser (permission: string, level = 1) { - + PermissionManager.ensurePermission(permission); const func = async (req: Request, res: Response, next: NextFunction) => { // Request does not have a user bound to it, response already sent from #_authenticate - if (!await this.#_authenticate(req, res)) + if (!await this.#_authenticate(req, res)) return; // If the authentication is done through a token, the user will be attached in the authentication step const { user } = req; // Has permission - if (user.hasPermission(permission, level)) + if (user.hasPermission(permission, level)) return next(); return res.status(403).send('Insufficient permissions'); }; @@ -244,7 +257,7 @@ class Authenticator { service = service.toLowerCase(); const link = OAuthLinks[service]; const _params = this.#OAuthParams[service]; - if (!_params) + if (!_params) return null; const params = { response_type: 'code',