Skip to content

Commit

Permalink
wip: force ui authData resync on admins update
Browse files Browse the repository at this point in the history
  • Loading branch information
tabarra committed Dec 29, 2023
1 parent 0618315 commit ae56d05
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 18 deletions.
7 changes: 5 additions & 2 deletions core/components/AdminVault/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -567,8 +567,11 @@ export default class AdminVault {
* Notify game server about admin changes
*/
async refreshOnlineAdmins() {
if (globals.playerlistManager === null) return;
//Refresh auth of all admins connected to socket.io
globals.webServer.webSocket.reCheckAdminAuths().catch((e) => { });

//Refresh in-game auth of all admins connected to the server
if (globals.playerlistManager === null) return;
try {
//Getting all admin identifiers
const adminIDs = this.admins.reduce((ids, adm) => {
Expand All @@ -582,7 +585,7 @@ export default class AdminVault {
return p.ids.some((i) => adminIDs.includes(i));
}).map((p) => p.netid);

return globals.fxRunner.sendEvent('adminsUpdated', onlineIDs);
globals.fxRunner.sendEvent('adminsUpdated', onlineIDs);
} catch (error) {
console.verbose.error('Failed to refreshOnlineAdmins() with error:');
console.verbose.dir(error);
Expand Down
1 change: 0 additions & 1 deletion core/components/WebServer/authLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { convars } from '@core/globalData';
import consoleFactory from '@extras/console';
import TxAdmin from "@core/txAdmin";
import { SessToolsType } from "./middlewares/sessionMws";
import { isIpAddressLocal } from "@extras/isIpAddressLocal";
import { ReactAuthDataType } from "@shared/authApiTypes";
const console = consoleFactory(modulename);

Expand Down
58 changes: 51 additions & 7 deletions core/components/WebServer/webSocket.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const modulename = 'WebSocket';
import { Server as SocketIO, Socket } from 'socket.io';
import { Server as SocketIO, Socket, RemoteSocket } from 'socket.io';
import consoleFactory from '@extras/console';
import statusRoom from './wsRooms/status';
import playerlistRoom from './wsRooms/playerlist';
Expand Down Expand Up @@ -71,6 +71,46 @@ export default class WebSocket {
setInterval(this.flushBuffers.bind(this), 250);
}

/**
* Refreshes the auth data for all connected admins
* If an admin is not authed anymore, they will be disconnected
* If an admin lost permission to a room, they will be kicked out of it
* This is called from AdminVault.refreshOnlineAdmins()
*/
async reCheckAdminAuths() {
const sockets = await this.#io.fetchSockets();
console.verbose.warn(`SocketIO`, `AdminVault changed, refreshing auth for ${sockets.length} sockets.`);
for (const socket of sockets) {
//@ts-ignore
const reqIp = getIP(socket);
const authResult = checkRequestAuth(
this.#txAdmin,
socket.handshake.headers,
reqIp,
isIpAddressLocal(reqIp),
//@ts-ignore
socket.sessTools
);
if (!authResult.success) {
//@ts-ignore
return terminateSession(socket, 'session invalidated by websocket.reCheckAdminAuths()', true);
}

//Sending auth data update - even if nothing changed
const { admin: authedAdmin } = authResult;
socket.emit('updateAuthData', authedAdmin.getAuthData());

//Checking permission of all joined rooms
for (const roomName of socket.rooms) {
if (roomName === socket.id) continue;
const roomData = this.#rooms[roomName as RoomNames];
if (roomData.permission !== true && !authedAdmin.hasPermission(roomData.permission)) {
socket.leave(roomName);
}
}
}
}


/**
* Handles incoming connection requests,
Expand All @@ -81,13 +121,13 @@ export default class WebSocket {
if (socket.handshake.query.uiVersion && socket.handshake.query.uiVersion !== txEnv.txAdminVersion) {
return forceUiReload(socket);
}

try {
//Checking for session auth
const reqIp = getIP(socket);
const authResult = checkRequestAuth(
this.#txAdmin,
socket.request.headers,
socket.handshake.headers,
reqIp,
isIpAddressLocal(reqIp),
socket.sessTools
Expand Down Expand Up @@ -122,12 +162,16 @@ export default class WebSocket {
}

//Setting up event handlers
//NOTE: if the admin permissions is removed after connection, he will
// still have access to the command, only refreshing the entire connection
// would solve it, since socket.session (therefore authedAdmin) is not auto updated
for (const [commandName, commandData] of Object.entries(room.commands ?? [])) {
if (commandData.permission === true || authedAdmin.hasPermission(commandData.permission)) {
socket.on(commandName, commandData.handler.bind(null, authedAdmin));
socket.on(commandName, (...args) => {
//Checking if admin is still in the room - perms change can make them be kicked out of room
if (socket.rooms.has(requestedRoomName)) {
commandData.handler(authedAdmin, ...args);
} else {
console.verbose.debug('SocketIO', `Command '${requestedRoomName}#${commandName}' was ignored due to admin not being in the room.`);
}
});
}
}

Expand Down
5 changes: 3 additions & 2 deletions docs/dev_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ setTimeout(() => {
- [x] make sure it is responsive
- [x] check behavior on error (invalid player, 500, etc)
- [x] clean legacy modal and playerlist code
- [ ][5d] fauth stuff
- [x][5d] auth stuff
- [x] password login
- [x] cfx.re login
- [x] error page
Expand All @@ -192,7 +192,7 @@ setTimeout(() => {
- [x] flow to refresh the page if invalidated auth
- [x] when logging out, create an effect to close all sheets and dialogs
- [x] disable menu links based on permissions
- [ ] flow to refresh the permissions on the client side
- [x] flow to refresh the permissions on the client side
- [ ][2d] full setup flow (legacy) - fazer isso no mainrouter?
- [ ][1d] full deployer flow (legacy)
- [ ][1d] add the new logos to shell+auth pages
Expand All @@ -218,6 +218,7 @@ Quickies
- [ ] check if strict mode is indeed disabled in prod build
> BETA RELEASE
- [ ] do i need to add a input type hidden with the username in the add master and account modal so vaults can save it both?
- [ ] put in server name in the login page, to help lost admins notice they are in the wrong txAdmin
- [ ] talk to r* and make sure the new build process wipes the old cache
- [ ] make sure some user input based fields are truncated (server name, player name)
Expand Down
9 changes: 7 additions & 2 deletions panel/src/hooks/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ export const useCsrfToken = () => {
return useAtomValue(csrfTokenAtom);
};

//Simple setter for auth data
export const useSetAuthData = () => {
return useSetAtom(authDataAtom)
};

//Admin permissions hook, only re-renders on perms change or login/logout
//Perms logic from core/components/WebServer/authLogic.ts
export const useAdminPerms = () => {
Expand Down Expand Up @@ -74,8 +79,8 @@ export const useAdminPerms = () => {
//Since this is triggered by a logout notice, we don't need to bother doing a POST /auth/logout
export const useExpireAuthData = () => {
const setAuthData = useSetAtom(authDataAtom);
return (src = 'unknown') => {
console.log(`Logout notice received from '${src}'. Wiping auth data.`);
return (src = 'unknown', reason = 'unknown') => {
console.log(`Logout notice received from '${src}' for reason '${reason}'. Wiping auth data.`);
setAuthData(false);
window.history.replaceState(null, '', '/login#expired');
}
Expand Down
13 changes: 9 additions & 4 deletions panel/src/layout/MainShell.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useEventListener } from 'usehooks-ts';
import MainRouter from "./MainRouter";
import { useExpireAuthData } from '../hooks/auth';
import { useExpireAuthData, useSetAuthData } from '../hooks/auth';
import { Header } from './Header';
import { ServerSidebar } from './serverSidebar/ServerSidebar';
import { PlayerlistSidebar } from './playerlistSidebar/PlayerlistSidebar';
Expand All @@ -24,10 +24,11 @@ export default function MainShell() {
const expireSession = useExpireAuthData();
const openAccountModal = useOpenAccountModal();
const openPlayerModal = useOpenPlayerModal();
const setAuthData = useSetAuthData();

useEventListener('message', (e: MessageEventFromIframe) => {
if (e.data.type === 'logoutNotice') {
expireSession('child iframe');
expireSession('child iframe', 'got logoutNotice');
} else if (e.data.type === 'openAccountModal') {
openAccountModal();
} else if (e.data.type === 'openPlayerModal') {
Expand Down Expand Up @@ -67,8 +68,8 @@ export default function MainShell() {
socket.on('error', (error) => {
console.log('Main Socket.IO', error);
});
socket.on('logout', function () {
expireSession('main socketio');
socket.on('logout', function (reason) {
expireSession('main socketio', reason);
});
socket.on('refreshToUpdate', function () {
window.location.href = '/login#updated';
Expand All @@ -83,6 +84,10 @@ export default function MainShell() {
socket.on('updateAvailable', function (data) {
processUpdateAvailableEvent(data);
});
socket.on('updateAuthData', function (authData) {
console.warn('Got updateAuthData from websocket', authData);
setAuthData(authData);
});

return () => {
socket.removeAllListeners();
Expand Down

0 comments on commit ae56d05

Please sign in to comment.