Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Many small bug fixes #575

Merged
merged 14 commits into from
Jul 22, 2024
Merged
1 change: 1 addition & 0 deletions docker/docker-compose-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ services:
POSTGRES_USER: gbans
POSTGRES_DB: gbans
POSTGRES_PASSWORD: gbans
POSTGRES_HOST_AUTH_METHOD: md5
networks:
- prod_network
healthcheck:
Expand Down
1 change: 1 addition & 0 deletions docker/docker-compose-prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ services:
POSTGRES_USER: gbans
POSTGRES_DB: gbans
POSTGRES_PASSWORD: gbans
POSTGRES_HOST_AUTH_METHOD: md5
networks:
- prod_network
healthcheck:
Expand Down
27 changes: 25 additions & 2 deletions docs/docs/guide/sourcemod.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,33 @@ This covers functionality available through the gbans plugin as well as some rec
| gb_stv_path | stv_demos/active | Path to store currently recording demos |
| gb_stv_path_complete | stv_demos/complete | Path to store complete demos |

## Creating a sourcemod database user
## Creating a sourcemod database user for clientprefs

It's recommended, but not required, to create a secondary less-privileged user, especially when using servers remote to the
gbans instance. Below is an example of creating a restricted user that only has access to the tables, and functions, required
for operation.

If you are using the official docker images, you must ensure that you enable the md5 authentication method when creating
the image. This can be achieved by defining the env var `POSTGRES_HOST_AUTH_METHOD=md5` when building your image. This
will automatically apply the following values under your `pg_hba.conf`.

```
host all all all md5
```

If you are not using docker, you can of course just manually edit this file. Its usually located at `/var/lib/postgresql/data/pg_hba.conf`.

Note that this is only the default method and you should consider a more restrictive set of parameters, such as limiting the database, role and hosts
that are allowed to connect. For example, we could restrict access like so:

```
host gbans sourcemod 10.20.30.0/24 md5
```

See the postgres docs on [pg_hba.conf](https://www.postgresql.org/docs/current/auth-pg-hba-conf.html) for more details.

With md5 auth setup, you can now create a role for sourcemod:

```sql
CREATE ROLE sourcemod WITH LOGIN PASSWORD '<your-password>';
GRANT CONNECT ON DATABASE gbans TO sourcemod;
Expand All @@ -50,10 +71,12 @@ GRANT SELECT ON
steam_friends, steam_group_members,
net_asn TO sourcemod;
GRANT SELECT, INSERT, UPDATE, DELETE ON sm_cookie_cache, sm_cookies TO sourcemod;
GRANT CREATE ON SCHEMA public TO sourcemod; -- extension will bail if not set it seems :(.
GRANT USAGE, SELECT ON SEQUENCE sm_cookies_id_seq TO sourcemod;
GRANT EXECUTE ON FUNCTION check_ban TO sourcemod;
```

Now you can setup your sourcemod databases.cfg with this user.
Next you can setup your sourcemod databases.cfg with this user.

```
"Databases"
Expand Down
18 changes: 14 additions & 4 deletions frontend/src/api/bans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,16 @@ export const apiGetBansSteam = async (opts: BanSteamQueryFilter, abortController
return resp.map(transformTimeStampedDates);
};

export const apiGetBansSteamBySteamID = async (steam_id: string, abortController?: AbortController) => {
const resp = await apiCall<SteamBanRecord[], BanSteamQueryFilter>(
`/api/bans/steam_all/${steam_id}`,
'GET',
undefined,
abortController
);
return resp.map(transformTimeStampedDates);
};

export const apiGetBanBySteam = async (steamID: string, abortController?: AbortController) =>
transformTimeStampedDates(
await apiCall<SteamBanRecord>(`/api/bans/steamid/${steamID}`, 'GET', undefined, abortController)
Expand Down Expand Up @@ -352,12 +362,12 @@ export const apiGetBanMessages = async (ban_id: number) => {
return transformTimeStampedDatesList(resp);
};

export interface CreateBanMessage {
message: string;
export interface BodyMDMessage {
body_md: string;
}

export const apiCreateBanMessage = async (ban_id: number, message: string) => {
const resp = await apiCall<BanAppealMessage, CreateBanMessage>(`/api/bans/${ban_id}/messages`, 'POST', { message });
export const apiCreateBanMessage = async (ban_id: number, body_md: string) => {
const resp = await apiCall<BanAppealMessage, BodyMDMessage>(`/api/bans/${ban_id}/messages`, 'POST', { body_md });

return transformTimeStampedDates(resp);
};
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/api/report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,9 +141,9 @@ export interface CreateReportMessage {
body_md: string;
}

export const apiCreateReportMessage = async (report_id: number, message: string) =>
export const apiCreateReportMessage = async (report_id: number, body_md: string) =>
transformTimeStampedDates(
await apiCall<ReportMessage, CreateReportMessage>(`/api/report/${report_id}/messages`, 'POST', { message })
await apiCall<ReportMessage, CreateReportMessage>(`/api/report/${report_id}/messages`, 'POST', { body_md })
);

export const apiReportSetState = async (report_id: number, stateAction: ReportStatus) =>
Expand Down
1 change: 1 addition & 0 deletions frontend/src/component/MarkdownRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export const MarkDownRenderer = ({ body_md, minHeight }: { body_md: string; minH
<Box padding={2} maxWidth={'100%'} minHeight={minHeight}>
<MuiMarkdown
options={{
disableParsingRawHTML: true,
overrides: {
...getOverrides({
Highlight,
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/component/ReportViewComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { zodValidator } from '@tanstack/zod-form-adapter';
import { z } from 'zod';
import {
apiCreateReportMessage,
apiGetBansSteam,
apiGetBansSteamBySteamID,
apiGetConnections,
apiGetMessages,
PermissionLevel,
Expand Down Expand Up @@ -99,7 +99,7 @@ export const ReportViewComponent = ({ report }: { report: ReportWithAuthor }): J
const { data: bans, isLoading: isLoadingBans } = useQuery({
queryKey: ['reportBanHistory', { steamId: report.target_id }],
queryFn: async () => {
const bans = await apiGetBansSteam({ target_id: report.target_id });
const bans = await apiGetBansSteamBySteamID(report.target_id);

return bans.filter((b) => b.target_id == report.target_id);
}
Expand Down
10 changes: 4 additions & 6 deletions frontend/src/component/WikiPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const WikiPage = ({ slug = 'home', path }: { slug: string; path: '/_guest
variant={'contained'}
color={'warning'}
onClick={() => {
setEditMode((prev) => !prev);
setEditMode(true);
}}
>
Edit
Expand All @@ -70,6 +70,9 @@ export const WikiPage = ({ slug = 'home', path }: { slug: string; path: '/_guest
setEditMode(false);
mdEditorRef.current?.setMarkdown('');
sendFlash('success', `Updated ${slug} successfully. Revision: ${savedPage.revision}`);
},
onError: (error) => {
sendFlash('error', `Error trying to save: ${error.message}`);
}
});

Expand Down Expand Up @@ -124,22 +127,17 @@ export const WikiPage = ({ slug = 'home', path }: { slug: string; path: '/_guest
<Grid xs={12}>
<Field
name={'body_md'}
validators={{
onChange: z.string().min(5)
}}
children={(props) => {
return <MarkdownField {...props} label={'Region'} />;
}}
/>
</Grid>
<Grid xs={12}>
<Subscribe
key={'edit-form-buttons'}
selector={(state) => [state.canSubmit, state.isSubmitting]}
children={([canSubmit, isSubmitting]) => {
return (
<Buttons
key={'edit-buttons'}
reset={reset}
canSubmit={canSubmit}
isSubmitting={isSubmitting}
Expand Down
13 changes: 6 additions & 7 deletions frontend/src/component/field/MarkdownField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import { headingsPlugin } from '@mdxeditor/editor';
import '@mdxeditor/editor/style.css';
import Paper from '@mui/material/Paper';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { useTheme } from '@mui/material/styles';
import { apiSaveAsset, assetURL } from '../../api/media.ts';
import { useUserFlashCtx } from '../../hooks/useUserFlashCtx.ts';
Expand Down Expand Up @@ -82,7 +81,7 @@ export const MarkdownField = ({ state, handleChange }: MDBodyFieldProps) => {
<MDXEditor
className={classes}
autoFocus={true}
markdown={state.value}
markdown={state.value.trimEnd()}
plugins={[
toolbarPlugin({
toolbarContents: () => (
Expand Down Expand Up @@ -116,11 +115,11 @@ export const MarkdownField = ({ state, handleChange }: MDBodyFieldProps) => {
onChange={handleChange}
ref={mdEditorRef}
/>
{state.meta.touchedErrors.length > 0 && (
<Typography padding={1} color={theme.palette.error.main}>
{state.meta.touchedErrors}
</Typography>
)}
{/*{state.meta.touchedErrors.length > 0 && (*/}
{/* <Typography padding={1} color={theme.palette.error.main}>*/}
{/* {state.meta.touchedErrors}*/}
{/* </Typography>*/}
{/*)}*/}
</Paper>
);
};
12 changes: 7 additions & 5 deletions frontend/src/routes/_auth.report.index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ import {
banReasonsCollection,
CreateReportRequest,
PlayerProfile,
ReportStatus,
reportStatusString,
ReportWithAuthor
} from '../api';
Expand All @@ -58,14 +57,14 @@ import { Title } from '../component/Title';
import { Buttons } from '../component/field/Buttons.tsx';
import { MarkdownField, mdEditorRef } from '../component/field/MarkdownField.tsx';
import { SteamIDField } from '../component/field/SteamIDField.tsx';
import { useUserFlashCtx } from '../hooks/useUserFlashCtx.ts';
import { commonTableSearchSchema, initPagination, RowsPerPage } from '../util/table.ts';
import { makeSteamidValidators } from '../util/validator/makeSteamidValidators.ts';

const reportSchema = z.object({
...commonTableSearchSchema,
rows: z.number().optional(),
sortColumn: z.enum(['report_status', 'created_on']).optional(),
report_status: z.nativeEnum(ReportStatus).optional(),
steam_id: z.string().optional(),
demo_id: z.number({ coerce: true }).optional(),
person_message_id: z.number().optional()
Expand Down Expand Up @@ -262,6 +261,7 @@ const validationSchema = z.object({
export const ReportCreateForm = (): JSX.Element => {
const { demo_id, steam_id, person_message_id } = Route.useSearch();
const [validatedProfile, setValidatedProfile] = useState<PlayerProfile>();
const { sendFlash } = useUserFlashCtx();

const mutation = useMutation({
mutationFn: async (variables: CreateReportRequest) => {
Expand All @@ -270,6 +270,10 @@ export const ReportCreateForm = (): JSX.Element => {
onSuccess: async (data) => {
mdEditorRef.current?.setMarkdown('');
await navigate({ to: '/report/$reportId', params: { reportId: String(data.report_id) } });
sendFlash('success', 'Created report successfully');
},
onError: (error) => {
sendFlash('error', `Error trying to create report: ${error.message}`);
}
});

Expand Down Expand Up @@ -407,6 +411,7 @@ export const ReportCreateForm = (): JSX.Element => {
<Grid md={6}>
<form.Field
name={'demo_id'}
validators={{ onChange: z.number({ coerce: true }).optional() }}
children={({ state, handleChange, handleBlur }) => {
return (
<TextField
Expand Down Expand Up @@ -450,9 +455,6 @@ export const ReportCreateForm = (): JSX.Element => {
<Grid xs={12}>
<form.Field
name={'body_md'}
validators={{
onChange: z.string().min(4, 'Body must contain at least 4 characters')
}}
children={(props) => {
return <MarkdownField {...props} label={'Message (Markdown)'} />;
}}
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/routes/_guest.index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ function Index() {
</Grid>
<Grid xs={12} sm={12} md={3}>
<Stack spacing={3}>
{profile.ban_id == 0 && appInfo.servers_enabled && (
{profile && profile.ban_id == 0 && appInfo.servers_enabled && (
<Button
startIcon={<StorageIcon />}
fullWidth
Expand All @@ -49,7 +49,7 @@ function Index() {
Play Now!
</Button>
)}
{profile.ban_id != 0 && appInfo.reports_enabled && (
{profile && profile.ban_id != 0 && appInfo.reports_enabled && (
<Button
startIcon={<SupportIcon />}
fullWidth
Expand Down
6 changes: 3 additions & 3 deletions internal/appeal/appeal_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func (h *appealHandler) onAPIGetBanMessages() gin.HandlerFunc {
return func(ctx *gin.Context) {
banID, errParam := httphelper.GetInt64Param(ctx, "ban_id")
if errParam != nil {
httphelper.ResponseErr(ctx, http.StatusNotFound, domain.ErrInvalidParameter)
httphelper.ResponseAPIErr(ctx, http.StatusNotFound, domain.ErrInvalidParameter)
slog.Warn("Got invalid ban_id parameter", log.ErrAttr(errParam), log.HandlerName(2))

return
Expand Down Expand Up @@ -102,7 +102,7 @@ func (h *appealHandler) editBanMessage() gin.HandlerFunc {

msg, errSave := h.appealUsecase.EditBanMessage(ctx, curUser, reportMessageID, req.BodyMD)
if errSave != nil {
httphelper.ResponseErr(ctx, http.StatusInternalServerError, domain.ErrInternal)
httphelper.ResponseAPIErr(ctx, http.StatusInternalServerError, domain.ErrInternal)
slog.Error("Failed to save ban appeal message", log.ErrAttr(errSave), log.HandlerName(2))

return
Expand Down Expand Up @@ -143,7 +143,7 @@ func (h *appealHandler) onAPIGetAppeals() gin.HandlerFunc {

bans, errBans := h.appealUsecase.GetAppealsByActivity(ctx, req)
if errBans != nil {
httphelper.ResponseErr(ctx, http.StatusInternalServerError, domain.ErrInternal)
httphelper.ResponseAPIErr(ctx, http.StatusInternalServerError, domain.ErrInternal)
slog.Error("Failed to fetch appeals", log.ErrAttr(errBans))

return
Expand Down
10 changes: 5 additions & 5 deletions internal/asset/asset_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func (h mediaHandler) onAPISaveMedia() gin.HandlerFunc {

mediaFile, errOpen := req.File.Open()
if errOpen != nil {
httphelper.ResponseErr(ctx, http.StatusInternalServerError, domain.ErrInternal)
httphelper.ResponseAPIErr(ctx, http.StatusInternalServerError, domain.ErrInternal)
slog.Error("Failed to open form file", log.ErrAttr(errOpen), handlerName)

return
Expand All @@ -60,7 +60,7 @@ func (h mediaHandler) onAPISaveMedia() gin.HandlerFunc {

media, errMedia := h.assets.Create(ctx, httphelper.CurrentUserProfile(ctx).SteamID, "media", req.Name, mediaFile)
if errMedia != nil {
httphelper.ResponseErr(ctx, http.StatusInternalServerError, errMedia)
httphelper.ResponseAPIErr(ctx, http.StatusInternalServerError, errMedia)
slog.Error("Failed to create new asset", log.ErrAttr(errMedia), handlerName)

return
Expand All @@ -76,15 +76,15 @@ func (h mediaHandler) onGetByUUID() gin.HandlerFunc {
return func(ctx *gin.Context) {
mediaID, idErr := httphelper.GetUUIDParam(ctx, "asset_id")
if idErr != nil {
httphelper.ResponseErr(ctx, http.StatusBadRequest, domain.ErrInvalidParameter)
httphelper.ResponseAPIErr(ctx, http.StatusBadRequest, domain.ErrInvalidParameter)
slog.Error("Got invalid asset_id", handlerName)

return
}

asset, reader, errGet := h.assets.Get(ctx, mediaID)
if errGet != nil {
httphelper.ResponseErr(ctx, http.StatusInternalServerError, errGet)
httphelper.ResponseAPIErr(ctx, http.StatusInternalServerError, errGet)
slog.Error("Failed to load asset", slog.String("asset_id", mediaID.String()), handlerName)

return
Expand All @@ -93,7 +93,7 @@ func (h mediaHandler) onGetByUUID() gin.HandlerFunc {
if asset.IsPrivate {
user := httphelper.CurrentUserProfile(ctx)
if !user.SteamID.Valid() && (user.SteamID == asset.AuthorID || user.HasPermission(domain.PModerator)) {
httphelper.ResponseErr(ctx, http.StatusForbidden, domain.ErrPermissionDenied)
httphelper.ResponseAPIErr(ctx, http.StatusForbidden, domain.ErrPermissionDenied)
slog.Warn("Tried to access private asset", slog.String("asset_id", mediaID.String()), handlerName)

return
Expand Down
6 changes: 3 additions & 3 deletions internal/auth/auth_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ func (h authHandler) onAPILogout() gin.HandlerFunc {
return func(ctx *gin.Context) {
fingerprint, errCookie := ctx.Cookie(domain.FingerprintCookieName)
if errCookie != nil {
httphelper.ResponseErr(ctx, http.StatusInternalServerError, nil)
httphelper.ResponseAPIErr(ctx, http.StatusInternalServerError, nil)
slog.Warn("Failed to get fingerprint", handlerName)

return
Expand All @@ -184,14 +184,14 @@ func (h authHandler) onAPILogout() gin.HandlerFunc {

personAuth := domain.PersonAuth{}
if errGet := h.authUsecase.GetPersonAuthByRefreshToken(ctx, fingerprint, &personAuth); errGet != nil {
httphelper.ResponseErr(ctx, http.StatusInternalServerError, nil)
httphelper.ResponseAPIErr(ctx, http.StatusInternalServerError, nil)
slog.Warn("Failed to load person via fingerprint", handlerName)

return
}

if errDelete := h.authUsecase.DeletePersonAuth(ctx, personAuth.PersonAuthID); errDelete != nil {
httphelper.ResponseErr(ctx, http.StatusInternalServerError, nil)
httphelper.ResponseAPIErr(ctx, http.StatusInternalServerError, nil)
slog.Error("Failed to delete person auth on logout", log.ErrAttr(errDelete), handlerName)

return
Expand Down
Loading
Loading