diff --git a/core/components/WebServer/requestAuthenticator.js b/core/components/WebServer/requestAuthenticator.js index 421215a81..18883a18c 100644 --- a/core/components/WebServer/requestAuthenticator.js +++ b/core/components/WebServer/requestAuthenticator.js @@ -29,7 +29,7 @@ export const requestAuth = (epType) => { if (epType === 'api') { const sessToken = ctx.session?.auth?.csrfToken; const headerToken = ctx.headers['x-txadmin-csrftoken']; - if(sessToken && (sessToken !== headerToken)){ + if (sessToken && (sessToken !== headerToken)) { //DEBUG // ogConsole.dir({ // route: `${ctx.method} ${ctx.path}`, @@ -51,7 +51,11 @@ export const requestAuth = (epType) => { if (verbose) logWarn(`Invalid session auth: ${ctx.path}`, epType); ctx.session.auth = {}; if (epType === 'web') { - return ctx.response.redirect('/auth?logout'); + if (ctx.method === 'GET' && ctx.path !== '/') { + return ctx.response.redirect(`/auth?logout&r=${encodeURIComponent(ctx.path)}`); + } else { + return ctx.response.redirect(`/auth?logout`); + } } else if (epType === 'api') { return ctx.send({ logout: true }); } else if (epType === 'nuiStart') { diff --git a/core/extras/helpers.ts b/core/extras/helpers.ts index 62ab87088..8f1172b94 100644 --- a/core/extras/helpers.ts +++ b/core/extras/helpers.ts @@ -167,3 +167,11 @@ export type PlayerIdsObjectType = { steam: string | null; xbl: string | null; }; + + +/** + * Validates if a redirect path is valid or not. + * To prevent open redirect, we need to make sure the first char is / and the second is not, + * otherwise //example.com would be a valid redirect to ://example.com + */ +export const isValidRedirectPath = (redirPath: unknown) => typeof redirPath === 'string' && /^\/\w/.test(redirPath); diff --git a/core/webroutes/authentication/providerCallback.js b/core/webroutes/authentication/providerCallback.js index 1151ff899..99ad8918b 100644 --- a/core/webroutes/authentication/providerCallback.js +++ b/core/webroutes/authentication/providerCallback.js @@ -2,6 +2,7 @@ const modulename = 'WebServer:ProviderCallback'; import crypto from 'node:crypto'; import logger, { ogConsole } from '@core/extras/console.js'; import { verbose } from '@core/globalData'; +import { isValidRedirectPath } from '@core/extras/helpers'; const { dir, log, logOk, logWarn, logError } = logger(modulename); //Helper functions @@ -122,7 +123,8 @@ export default async function ProviderCallback(ctx) { ctx.utils.logAction(`logged in from ${ctx.ip} via citizenfx`); globals.databus.txStatsData.login.origins[ctx.txVars.hostType]++; globals.databus.txStatsData.login.methods.citizenfx++; - return ctx.response.redirect('/'); + const redirectPath = (isValidRedirectPath(ctx.session?.socialLoginRedirect)) ? ctx.session.socialLoginRedirect : '/'; + return ctx.response.redirect(redirectPath); } catch (error) { ctx.session.auth = {}; if (verbose) logError(`Failed to login: ${error.message}`); diff --git a/core/webroutes/authentication/providerRedirect.js b/core/webroutes/authentication/providerRedirect.js index 9f5ff7b59..51b444ac0 100644 --- a/core/webroutes/authentication/providerRedirect.js +++ b/core/webroutes/authentication/providerRedirect.js @@ -1,13 +1,11 @@ const modulename = 'WebServer:ProviderRedirect'; import logger from '@core/extras/console.js'; +import { isValidRedirectPath } from '@core/extras/helpers'; import { verbose } from '@core/globalData'; const { dir, log, logOk, logWarn, logError } = logger(modulename); //Helper functions const isUndefined = (x) => { return (typeof x === 'undefined'); }; -const genCallbackURL = (ctx, provider) => { - return ctx.protocol + '://' + ctx.get('host') + `/auth/${provider}/callback`; -}; const returnJustMessage = (ctx, errorTitle, errorMessage) => { return ctx.utils.render('login', { template: 'justMessage', errorTitle, errorMessage }); }; @@ -30,10 +28,17 @@ export default async function ProviderRedirect(ctx) { //Make sure the session is initialized ctx.session.startedSocialLogin = Date.now(); - //Generatte CitizenFX provider Auth URL + //Save redirection path in session, if any + //NOTE: technically we don't need to regex validate here, as that will be done on providerCallback + if(isValidRedirectPath(ctx.query?.r)){ + ctx.session.socialLoginRedirect = ctx.query.r; + } + + //Generate CitizenFX provider Auth URL + const callbackUrl = ctx.protocol + '://' + ctx.get('host') + `/auth/citizenfx/callback`; try { const urlCitizenFX = await globals.adminVault.providers.citizenfx.getAuthURL( - genCallbackURL(ctx, 'citizenfx'), + callbackUrl, ctx.session._sessCtx.externalKey, ); return ctx.response.redirect(urlCitizenFX); diff --git a/core/webroutes/authentication/verifyPassword.js b/core/webroutes/authentication/verifyPassword.js index c5710c68e..50bacd335 100644 --- a/core/webroutes/authentication/verifyPassword.js +++ b/core/webroutes/authentication/verifyPassword.js @@ -1,5 +1,6 @@ const modulename = 'WebServer:AuthVerify'; import logger from '@core/extras/console.js'; +import { isValidRedirectPath } from '@core/extras/helpers'; import { verbose } from '@core/globalData'; const { dir, log, logOk, logWarn, logError } = logger(modulename); @@ -54,5 +55,6 @@ export default async function AuthVerify(ctx) { return ctx.utils.render('login', renderData); } - return ctx.response.redirect('/'); + const redirectPath = (isValidRedirectPath(ctx.query?.r)) ? ctx.query.r : '/'; + return ctx.response.redirect(redirectPath); }; diff --git a/docs/dev_notes.md b/docs/dev_notes.md index ea11e7144..79d6d1575 100644 --- a/docs/dev_notes.md +++ b/docs/dev_notes.md @@ -1,54 +1,42 @@ # TODO: -- [x] rename txAdmin Logs to System Logs (check chungus commands as well) -- [x] Finish diagnostics report function -- [x] Make cyclical exec in cfg file block the server start -- [x] change nui player card default tab back to actions -- [x] upgrade packages -- [x] bot: upgrade discord.js -- [x] bot: convert into slash commands -- [x] bot: add NEW tag to settings menu and discord tab -- [x] bot: add dynamic activity ("watching xx/yy players") -- [x] bot: add persistent /status message -- [x] bot: embed/config editor on settings page -- [x] bot: embed/config docs file -- [x] bot: fix resolveMember() -- [x] bot: add /whitelist command -- [x] bot: add /info command -- [x] add new whitelist modes - - [x] admin-only (#516) - - [x] guild membership (#450) - - [x] guild roles -- [x] bot: update AGAIN to djs v14 -- [x] fix `Restarting the fxserver with delay override 0.` -- [x] add cap to `stats_heatmapData_v1.json` (StatsCollector.hardConfigs.performance.lengthCap) -- [x] chore(core): move admin action log() to logger -- [x] Improve the message `[txAdmin] You do not have at least 1 valid identifier [...]` -- [x] CFG Editor: add hotkeys for search, comment, and restart sv -- [x] add a `Wait(0)` on `sv_main.lua` kick/ban handlers? (Issue #639) -- [x] merge translations -- [x] remove `discord.*` from locale files -- [x] adjust the message that shows when deployer step 3 has no server.cfg to read -- [x] set nui/vite.config.ts > target > chrome103 -- [x] checkJoin: add messages to locale files -- [x] checkJoin: customMessage `\n` to `
` -- [x] fix(core): cfx.re login match by admin id instead of name -- [x] finish txdiagnostics backend, test e2e one last time -- [x] bot: change settings page description - - - - - -# Next up: +- [x] QoL: add redirect post login if invalid sess +- [ ] Improve discord embed UX: + - [x] embed placeholder + - [x] check for the emoji + - [x] check for the url fields + - [ ] discord auth not admin response + - [ ] bot save: intent message + - [ ] bot save: could not resolve guild id = was the bot invited? + - [ ] embed jsons reset buttons +- [ ] status embed every 30 seconds or reactive to status changes - [ ] add superjump - [ ] the PR about hiding notifications - [ ] wav for announcements +- [ ] create events for dynamic scheduled restarts +- [ ] create new whitelist events + - [ ] whitelistPlayer: + - license: xxxxx + - author: admin name + - status: true/false + - [ ] whitelistPreApproval: + - action: added/removed + - identifier: `discord:xxxxxx` / `license:xxxxx` + - author: admin name + - [ ] whitelistRequest: + - action: requested/approved/denied + - author: either player name, or admin name + - requestId: Rxxxx + - license: xxxxxx +- [ ] At the schedule restart input prompt, add a note saying what is the current server time + + +## Optional - [ ] bot: fix http agent options for localAddress - [ ] bot: add rate limit events to diagnostics page - [ ] change dashboard median player message - top 1000: "your server seems to be in top 1000, join and type /server to track your progress" - top 500: "you might be in top 500, join discord and see if you are eligible for the role" -- [ ] update readme with new features +- [ ] update readme with new features and contributing warning - [ ] stats: - [ ] ???? - [ ] jwe @@ -65,12 +53,17 @@ CreateThread(function() end) ``` + +# Next up: +- [ ] xxxxxx + =================== ### MUI update 5.10.17 ok 5.11.0 broken To test it, remove the `^` rm -rf node_modules/; npm i; npm list @mui/material; npm run dev:menu:game +https://github.com/mui/material-ui/blob/master/CHANGELOG.md =================== @@ -97,23 +90,7 @@ teste: - [ ] no duplicated id type in bans? preparing for the new db migration - [ ] reorder `sv_main.lua` and add `local` prefix to most if not all functions -- [ ] create events for dynamic scheduled restarts -- [ ] create new whitelist events - - [ ] whitelistPlayer: - - license: xxxxx - - author: admin name - - status: true/false - - [ ] whitelistPreApproval: - - action: added/removed - - identifier: `discord:xxxxxx` / `license:xxxxx` - - author: admin name - - [ ] whitelistRequest: - - action: requested/approved/denied - - author: either player name, or admin name - - requestId: Rxxxx - - license: xxxxxx - [ ] mock out insights page (assets + http reqs) -- [ ] At the schedule restart input prompt, add a note saying what is the current server time - [ ] `cfg cyclical 'exec' command detected to file` should be blocking instead of warning. Behare that this is not trivial without also turning missing exec target read error also being error - [ ] maybe some sort of lockfile to admins.json file which would disable admin manager? @@ -513,7 +490,7 @@ To check of admin perm, just do `IsPlayerAceAllowed(src, 'txadmin.xxxxxx')` ### txPointing (old txBanana) -- code prototype with ItsANoBrainer#1337 +- code prototype with ItsANoBrainer#1337 (https://github.com/tabarra/txBanana) - keybind to toggle gun (grab or put away) - when you point at player, show above head some info - when you "shoot" it will open the player menu and hopefully fire a laser or something diff --git a/web/public/js/txadmin/base.js b/web/public/js/txadmin/base.js index 15fbf4750..ad3e253f4 100644 --- a/web/public/js/txadmin/base.js +++ b/web/public/js/txadmin/base.js @@ -81,7 +81,7 @@ for (let pfp of pfpList) { //================================================================ const checkApiLogoutRefresh = (data) => { if (data.logout === true) { - window.location = '/auth?logout'; + window.location = `/auth?logout&r=${encodeURIComponent(window.location.pathname)}`; return true; } else if (data.refresh === true) { window.location.reload(true); diff --git a/web/standalone/login.ejs b/web/standalone/login.ejs index aa01f1b74..90c95cba3 100644 --- a/web/standalone/login.ejs +++ b/web/standalone/login.ejs @@ -282,7 +282,7 @@

Login

<% if (isWebInterface) { %> + role="button" href="auth/citizenfx/redirect" id="btn-cfxredirect"> <% } else { %> @@ -293,7 +293,7 @@
OR
-
+
@@ -387,6 +387,18 @@