const modulename = 'WebServer:AuthMws'; import consoleFactory from '@lib/console'; import { checkRequestAuth } from "../authLogic"; import { ApiAuthErrorResp, ApiToastResp, GenericApiErrorResp } from "@shared/genericApiTypes"; import { InitializedCtx } from '../ctxTypes'; import { txHostConfig } from '@core/globalData'; const console = consoleFactory(modulename); const webLogoutPage = `

User logged out.
Redirecting to login page...

`; /** * For the hosting provider routes */ export const hostAuthMw = async (ctx: InitializedCtx, next: Function) => { const docs = 'https://aka.cfx.re/txadmin-env-config'; //Token disabled if (txHostConfig.hostApiToken === 'disabled') { return await next(); } //Token undefined if (!txHostConfig.hostApiToken) { return ctx.send({ error: 'token not configured', desc: 'need to configure the TXHOST_API_TOKEN environment variable to be able to use the status endpoint', docs, }); } //Token available let tokenProvided: string | undefined; const headerToken = ctx.headers['x-txadmin-envtoken']; if (typeof headerToken === 'string' && headerToken) { tokenProvided = headerToken; } const paramsToken = ctx.query.envtoken; if (typeof paramsToken === 'string' && paramsToken) { tokenProvided = paramsToken; } if (headerToken && paramsToken) { return ctx.send({ error: 'token conflict', desc: 'cannot use both header and query token', docs, }); } if (!tokenProvided) { return ctx.send({ error: 'token missing', desc: 'a token needs to be provided in the header or query string', docs, }); } if (tokenProvided !== txHostConfig.hostApiToken) { return ctx.send({ error: 'invalid token', desc: 'the token provided does not match the TXHOST_API_TOKEN environment variable', docs, }); } return await next(); }; /** * Intercom auth middleware * This does not set ctx.admin and does not use session/cookies whatsoever. * FIXME: add isLocalAddress check? */ export const intercomAuthMw = async (ctx: InitializedCtx, next: Function) => { if ( typeof ctx.request.body?.txAdminToken !== 'string' || ctx.request.body.txAdminToken !== txCore.webServer.luaComToken ) { return ctx.send({ error: 'invalid token' }); } await next(); }; /** * Used for the legacy web interface. */ export const webAuthMw = async (ctx: InitializedCtx, next: Function) => { //Check auth const authResult = checkRequestAuth( ctx.request.headers, ctx.ip, ctx.txVars.isLocalRequest, ctx.sessTools ); if (!authResult.success) { ctx.sessTools.destroy(); if (authResult.rejectReason) { console.verbose.warn(`Invalid session auth: ${authResult.rejectReason}`); } return ctx.send(webLogoutPage); } //Adding the admin to the context ctx.admin = authResult.admin; await next(); }; /** * API Authentication Middleware */ export const apiAuthMw = async (ctx: InitializedCtx, next: Function) => { const sendTypedResp = (data: ApiAuthErrorResp | (ApiToastResp & GenericApiErrorResp)) => ctx.send(data); //Check auth const authResult = checkRequestAuth( ctx.request.headers, ctx.ip, ctx.txVars.isLocalRequest, ctx.sessTools ); if (!authResult.success) { ctx.sessTools.destroy(); if (authResult.rejectReason && (authResult.rejectReason !== 'nui_admin_not_found' || console.isVerbose)) { console.verbose.warn(`Invalid session auth: ${authResult.rejectReason}`); } return sendTypedResp({ logout: true, reason: authResult.rejectReason ?? 'no session' }); } //For web routes, we need to check the CSRF token //For nui routes, we need to check the luaComToken, which is already done in nuiAuthLogic above if (ctx.txVars.isWebInterface) { const sessToken = authResult.admin?.csrfToken; //it should exist for nui because of authLogic const headerToken = ctx.headers['x-txadmin-csrftoken']; if (!sessToken || !headerToken || sessToken !== headerToken) { console.verbose.warn(`Invalid CSRF token: ${ctx.path}`); const msg = (headerToken) ? 'Error: Invalid CSRF token, please refresh the page or try to login again.' : 'Error: Missing HTTP header \'x-txadmin-csrftoken\'. This likely means your files are not updated or you are using some reverse proxy that is removing this header from the HTTP request.'; //Doing ApiAuthErrorResp & GenericApiErrorResp to maintain compatibility with all routes //"error" is used by diagnostic, masterActions, playerlist, whitelist and possibly more return sendTypedResp({ type: 'error', msg: msg, error: msg }); } } //Adding the admin to the context ctx.admin = authResult.admin; await next(); };