Skip to content

Commit

Permalink
Add Rcon command fields to match creation page
Browse files Browse the repository at this point in the history
  • Loading branch information
JensForstmann committed Oct 20, 2023
1 parent 3ff3df4 commit bed4208
Show file tree
Hide file tree
Showing 7 changed files with 134 additions and 39 deletions.
16 changes: 14 additions & 2 deletions backend/src/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ export interface IAuthResponse {
comment?: string;
}

export type IAuthResponseOptional =
| IAuthResponse
| {
type: 'UNAUTHORIZED';
};

interface ITokenContent {
comment?: string;
}
Expand Down Expand Up @@ -79,15 +85,21 @@ export const expressAuthentication = async (
req: Request,
securityName: string,
scopes?: string[]
): Promise<IAuthResponse> => {
if (securityName === 'bearer_token') {
): Promise<IAuthResponse | IAuthResponseOptional> => {
if (securityName === 'bearer_token' || securityName === 'bearer_token_optional') {
const bearerToken = req.get('Authorization');
const result = await isAuthorized(bearerToken, req.params['id']);
if (result) {
return Promise.resolve(result);
}
}

if (securityName === 'bearer_token_optional') {
return Promise.resolve({
type: 'UNAUTHORIZED',
});
}

return Promise.reject({});
};

Expand Down
13 changes: 12 additions & 1 deletion backend/src/matchService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,20 @@ const loadMatchFromStorage = async (matchData: IMatch) => {
}
};

export const create = async (dto: IMatchCreateDto) => {
export const create = async (dto: IMatchCreateDto, isLoggedIn: boolean) => {
const id = shortUuid();
try {
if (
isLoggedIn === false &&
[
...(dto.rconCommands?.init ?? []),
...(dto.rconCommands?.knife ?? []),
...(dto.rconCommands?.match ?? []),
...(dto.rconCommands?.end ?? []),
].find((cmd) => cmd.toLowerCase().includes('password'))
) {
throw 'not allowed to set passwords (text "password" found in rcon commands)';
}
const logSecret = shortUuid();
startingMatches.add(id);
const match = await Match.createFromCreateDto(dto, id, logSecret);
Expand Down
11 changes: 7 additions & 4 deletions backend/src/matchesController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
IMatchResponse,
IMatchUpdateDto,
} from '../../common';
import { IAuthResponse } from './auth';
import { IAuthResponse, IAuthResponseOptional } from './auth';
import * as Events from './events';
import * as Match from './match';
import * as MatchMap from './matchMap';
Expand All @@ -31,9 +31,12 @@ import * as MatchService from './matchService';
export class MatchesController extends Controller {
@Post()
@SuccessResponse(201)
@NoSecurity()
async createMatch(@Body() requestBody: IMatchCreateDto): Promise<IMatch> {
const match = await MatchService.create(requestBody);
@Security('bearer_token_optional')
async createMatch(
@Body() requestBody: IMatchCreateDto,
@Request() { user }: { user: IAuthResponseOptional }
): Promise<IMatch> {
const match = await MatchService.create(requestBody, user.type === 'GLOBAL');
this.setHeader('Location', `/api/matches/${match.data.id}`);
this.setStatus(201);
return match.data;
Expand Down
2 changes: 2 additions & 0 deletions backend/src/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1156,6 +1156,7 @@ export function RegisterRoutes(app: Router) {
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.post(
'/api/matches',
authenticateMiddleware([{ bearer_token_optional: [] }]),
...fetchMiddlewares<RequestHandler>(MatchesController),
...fetchMiddlewares<RequestHandler>(MatchesController.prototype.createMatch),

Expand All @@ -1167,6 +1168,7 @@ export function RegisterRoutes(app: Router) {
required: true,
ref: 'IMatchCreateDto',
},
undefined: { in: 'request', required: true, dataType: 'object' },
};

// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
Expand Down
6 changes: 5 additions & 1 deletion backend/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -2166,7 +2166,11 @@
}
}
},
"security": [],
"security": [
{
"bearer_token_optional": []
}
],
"parameters": [],
"requestBody": {
"required": true,
Expand Down
10 changes: 5 additions & 5 deletions frontend/src/components/Inputs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const ToggleInput: Component<
const [local, others] = splitProps(props, ['label', 'class', 'labelTopRight']);
return (
<div class="form-control">
<Show when={local.label !== undefined}>
<Show when={local.label || local.labelTopRight}>
<label class="label">
<span class="label-text">{local.label}</span>
<span class="label-text-alt">{local.labelTopRight}</span>
Expand Down Expand Up @@ -37,14 +37,14 @@ export const TextInput: Component<
]);
return (
<div class={(local.containerClass ?? '') + ' form-control'}>
<Show when={local.label !== undefined}>
<Show when={local.label || local.labelTopRight}>
<label class="label">
<span class="label-text">{local.label}</span>
<span class="label-text-alt">{local.labelTopRight}</span>
</label>
</Show>
<input class={(local.class ?? '') + ' input w-full'} type="text" {...others} />
<Show when={local.labelBottomLeft !== undefined}>
<Show when={local.labelBottomLeft}>
<label class="label">
<span class="label-text-alt">{local.labelBottomLeft}</span>
</label>
Expand All @@ -62,7 +62,7 @@ export const TextArea: Component<
const [local, others] = splitProps(props, ['label', 'class', 'labelTopRight']);
return (
<div class="form-control">
<Show when={local.label !== undefined || local.labelTopRight !== undefined}>
<Show when={local.label || local.labelTopRight}>
<label class="label">
<span class="label-text">{local.label}</span>
<span class="label-text-alt">{local.labelTopRight}</span>
Expand Down Expand Up @@ -99,7 +99,7 @@ export const SelectInput: Component<
<select class="select w-full" {...others}>
{local.children}
</select>
<Show when={local.labelBottomLeft !== undefined}>
<Show when={local.labelBottomLeft}>
<label class="label">
<span class="label-text-alt">{local.labelBottomLeft}</span>
</label>
Expand Down
115 changes: 89 additions & 26 deletions frontend/src/pages/create.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,30 @@
import autoAnimate from '@formkit/auto-animate';
import { useNavigate } from '@solidjs/router';
import { Component, createEffect, createSignal, For, Index, onMount, Show } from 'solid-js';
import { Component, createEffect, createSignal, For, onMount, Show } from 'solid-js';
import { createStore } from 'solid-js/store';
import {
getOtherTeamAB,
IElectionStep,
IMatch,
IMatchCreateDto,
TMatchEndAction,
TMatchMode,
TTeamAB,
} from '../../../common';
import { createFetcher, isLoggedIn } from '../utils/fetcher';
import { t } from '../utils/locale';
import { Card } from '../components/Card';
import { SelectInput, TextArea, TextInput, ToggleInput } from '../components/Inputs';
import { AddElectionStep, getElectionStepString } from '../components/ElectionStep';
import { Modal } from '../components/Modal';
import { createStore } from 'solid-js/store';
import { IPreset, IPresetCreateDto } from '../../../common/types/preset';
import {
SvgAdd,
SvgDelete,
SvgKeyboardArrowDown,
SvgKeyboardArrowUp,
SvgSave,
} from '../assets/Icons';
import { IPreset, IPresetCreateDto } from '../../../common/types/preset';
import { Card } from '../components/Card';
import { AddElectionStep, getElectionStepString } from '../components/ElectionStep';
import { SelectInput, TextArea, TextInput, ToggleInput } from '../components/Inputs';
import { Modal } from '../components/Modal';
import { createFetcher, isLoggedIn } from '../utils/fetcher';
import { t } from '../utils/locale';

const DEFAULT_MAPS = [
'de_ancient',
Expand All @@ -35,6 +36,21 @@ const DEFAULT_MAPS = [
'de_vertigo',
];

const DEFAULT_RCON_INIT = [
'game_type 0; game_mode 1; sv_game_mode_flags 0; sv_skirmish_id 0',
'say > RCON INIT LOADED <',
];
const DEFAULT_RCON_KNIFE = [
'mp_give_player_c4 0; mp_startmoney 0; mp_ct_default_secondary ""; mp_t_default_secondary ""',
'say > SPECIAL KNIFE CONFIG LOADED <',
];
const DEFAULT_RCON_MATCH = [
'mp_give_player_c4 1; mp_startmoney 800; mp_ct_default_secondary "weapon_hkp2000"; mp_t_default_secondary "weapon_glock"',
'mp_overtime_enable 1',
'say > MATCH CONFIG LOADED <',
];
const DEFAULT_RCON_END = ['say > MATCH END RCON LOADED <'];

const getElectionStepsFromPreset = (preset: 'BO1' | 'BO3', mapPool: string[]): IElectionStep[] => {
const electionSteps: IElectionStep[] = [];
const mapPoolCount = mapPool.length;
Expand Down Expand Up @@ -167,16 +183,15 @@ export const CreatePage: Component = () => {
mapPool: DEFAULT_MAPS,
electionSteps: getElectionStepsFromPreset('BO1', DEFAULT_MAPS),
rconCommands: {
// TODO
init: [],
knife: [],
match: [],
end: [],
init: DEFAULT_RCON_INIT,
knife: DEFAULT_RCON_KNIFE,
match: DEFAULT_RCON_MATCH,
end: DEFAULT_RCON_END,
},
matchEndAction: 'NONE', // TODO
matchEndAction: 'NONE',
mode: 'SINGLE',
tmtLogAddress: window.location.protocol + '//' + window.location.host,
canClinch: true, // TODO,
canClinch: true,
});

const createMatch = async () => {
Expand Down Expand Up @@ -475,22 +490,70 @@ export const CreatePage: Component = () => {
</Modal>

<div class="prose pt-4">
<h2>{t('Advanced Settings')}</h2>
<h2>{t('Rcon Commands')}</h2>
</div>
<TextArea
label={t('Init')}
labelTopRight={t('Executed only once: when the match is created')}
rows="4"
value={DEFAULT_RCON_INIT.join('\n')}
onInput={(e) =>
setMatchCreateDto('rconCommands', 'init', e.currentTarget.value.split('\n'))
}
class="font-mono"
/>
<TextArea
label={t('Knife')}
labelTopRight={t('Executed at the start of a knife round')}
rows="4"
value={DEFAULT_RCON_KNIFE.join('\n')}
onInput={(e) =>
setMatchCreateDto('rconCommands', 'knife', e.currentTarget.value.split('\n'))
}
class="font-mono"
/>
<TextArea
label={t('Match')}
labelTopRight={t('Executed at the start of each match map')}
rows="4"
value={DEFAULT_RCON_MATCH.join('\n')}
onInput={(e) =>
setMatchCreateDto('rconCommands', 'match', e.currentTarget.value.split('\n'))
}
class="font-mono"
/>
<TextArea
label={t('End')}
labelTopRight={t('Executed only once: after the end of the last map')}
rows="4"
value={DEFAULT_RCON_END.join('\n')}
onInput={(e) =>
setMatchCreateDto('rconCommands', 'end', e.currentTarget.value.split('\n'))
}
class="font-mono"
/>

<div class="prose pt-4">
<h2>{t('Advanced Settings')}</h2>
</div>
<SelectInput
label={t('Mode')}
labelBottomLeft={
matchCreateDto.mode === 'SINGLE'
? t('Single mode: stops when match is finished')
: matchCreateDto.mode === 'LOOP'
? t('Loop mode: starts again after match is finished')
: false
}
onInput={(e) => setMatchCreateDto('mode', e.currentTarget.value as TMatchMode)}
>
<option value="SINGLE">{t('Single')}</option>
<option value="LOOP">{t('Loop')}</option>
<option value="SINGLE">{t('Single match (stops when match is finished)')}</option>
<option value="LOOP">
{t('Loop mode (starts again after match is finished)')}
</option>
</SelectInput>
<SelectInput
label={t('Match End Action')}
onInput={(e) =>
setMatchCreateDto('matchEndAction', e.currentTarget.value as TMatchEndAction)
}
>
<option value="NONE">{t('None')}</option>
<option value="KICK_ALL">{t('Kick all players after match end')}</option>
<option value="QUIT_SERVER">{t('Quit server via Rcon after match end')}</option>
</SelectInput>

<TextArea
Expand Down

0 comments on commit bed4208

Please sign in to comment.