From 0450eb9169cfe423278563c4d18abb0822aee118 Mon Sep 17 00:00:00 2001 From: Carlos Valente Date: Thu, 16 Jan 2025 21:41:16 +0100 Subject: [PATCH] refactor: set token from query params --- apps/server/src/app.ts | 2 +- apps/server/src/externals.ts | 2 + apps/server/src/middleware/authenticate.ts | 54 +++++++++------------- 3 files changed, 25 insertions(+), 33 deletions(-) diff --git a/apps/server/src/app.ts b/apps/server/src/app.ts index a0884ca84..d5e339afc 100644 --- a/apps/server/src/app.ts +++ b/apps/server/src/app.ts @@ -84,9 +84,9 @@ app.use(cookieParser()); const { authenticate, authenticateAndRedirect } = makeAuthenticateMiddleware(prefix); // Implement route endpoints +app.use(`${prefix}/login`, loginRouter); // router for login flow app.use(`${prefix}/data`, authenticate, appRouter); // router for application data app.use(`${prefix}/api`, authenticate, integrationRouter); // router for integrations -app.use(`${prefix}/login`, loginRouter); // router for login flow // serve static external files app.use(`${prefix}/external`, express.static(publicDir.externalDir)); diff --git a/apps/server/src/externals.ts b/apps/server/src/externals.ts index 13e886e41..3cd9014cd 100644 --- a/apps/server/src/externals.ts +++ b/apps/server/src/externals.ts @@ -15,6 +15,8 @@ export const environment = isTest ? 'test' : env; export const isDocker = env === 'docker'; export const isProduction = isDocker || (env === 'production' && !isTest); export const isOntimeCloud = Boolean(process.env.IS_CLOUD); +export const password = process.env.SESSION_PASSWORD; + /** * Updates the router prefix in the index.html file * This is only needed in the cloud environment where the client is not at the root segment diff --git a/apps/server/src/middleware/authenticate.ts b/apps/server/src/middleware/authenticate.ts index 0d03a6baf..08075ed37 100644 --- a/apps/server/src/middleware/authenticate.ts +++ b/apps/server/src/middleware/authenticate.ts @@ -8,8 +8,7 @@ import { parse as parseCookie } from 'cookie'; import { hashPassword } from '../utils/hash.js'; import { srcFiles } from '../setup/index.js'; import { logger } from '../classes/Logger.js'; -// import { password } from '../externals.js'; -const password = 'test'; +import { password } from '../externals.js'; import { noopMiddleware } from './noop.js'; export const hasPassword = Boolean(password); @@ -19,7 +18,13 @@ const hashedPassword = hasPassword ? hashPassword(password) : ''; * List of public assets that can be accessed without authentication * should match the files in client/public */ -const publicAssets = ['/favicon.ico', '/manifest.json', '/ontime-logo.png', '/robots.txt', '/site.webmanifest']; +const publicAssets = new Set([ + '/favicon.ico', + '/manifest.json', + '/ontime-logo.png', + '/robots.txt', + '/site.webmanifest', +]); export const loginRouter = express.Router(); @@ -31,23 +36,8 @@ loginRouter.post('/', (req, res) => { res.clearCookie('token'); const { password: reqPassword, redirect } = req.body; - if (!hasPassword) { - res.redirect(redirect || '/'); - return; - } - - if (!reqPassword) { - res.status(401).send('Unauthorized'); - return; - } - if (hashPassword(reqPassword) === hashedPassword) { - res.cookie('token', hashedPassword, { - httpOnly: false, // allow websocket to access cookie - secure: true, - path: '/', // allow cookie to be accessed from any path - sameSite: 'strict', - }); + setSessionCookie(res, hashedPassword); res.redirect(redirect || '/'); return; } @@ -76,12 +66,12 @@ export function makeAuthenticateMiddleware(prefix: string) { function authenticateAndRedirect(req: Request, res: Response, next: NextFunction) { // Allow access to specific public assets without authentication - if (publicAssets.includes(req.originalUrl)) { + if (publicAssets.has(req.originalUrl)) { return next(); } + // we shouldnt be here in the login route if (req.originalUrl.startsWith('/login')) { - // cannot authenticate the login route return next(); } @@ -93,20 +83,11 @@ export function makeAuthenticateMiddleware(prefix: string) { // we use query params for generating authenticated URLs and for clients like the companion module // if the user gives is a token in the query params, we set the cookie to be used in further requests if (req.query.token === hashedPassword) { - res.cookie('token', hashedPassword, { - httpOnly: false, // allow websocket to access cookie - secure: true, - path: '/', // allow cookie to be accessed from any path - sameSite: 'strict', - }); + setSessionCookie(res, hashedPassword); return next(); } - const redirect = req.originalUrl.startsWith('/login') - ? `${prefix}/login` - : `${prefix}/login?redirect=${req.originalUrl}`; - - res.redirect(redirect); + res.redirect(`${prefix}/login?redirect=${req.originalUrl}`); } return { authenticate, authenticateAndRedirect }; @@ -139,3 +120,12 @@ export function authenticateSocket(_ws: WebSocket, req: IncomingMessage, next: ( logger.warning(LogOrigin.Client, 'Unauthorized WebSocket connection attempt'); return next(new Error('Unauthorized')); } + +function setSessionCookie(res: Response, token: string) { + res.cookie('token', token, { + httpOnly: false, // allow websocket to access cookie + secure: true, + path: '/', // allow cookie to be accessed from any path + sameSite: 'strict', + }); +}