Skip to content

Commit bba76c6

Browse files
committed
Prevent potential remote code execution vulnerability
1 parent 09b244e commit bba76c6

4 files changed

Lines changed: 58 additions & 34 deletions

File tree

src/lib/components/Table/ColumnUtils.ts

Lines changed: 43 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { durationToString, elapsedToString } from '$lib/utils/time';
66
import type { SemVer } from 'semver';
77
import type { TwTextColor } from '$lib/types/Tailwind';
88
import { getReadableUserAgentName } from '$lib/utils/userAgent';
9+
import { escapeHtml } from '$lib/utils/encoding';
910

1011
export function CreateSortHeader<TData>(name: string): StringOrTemplateHeader<TData, unknown> {
1112
return ({ column }) =>
@@ -31,58 +32,75 @@ export function CreateSimpleCellSnippet<TData extends object, K extends keyof TD
3132
};
3233
}
3334

34-
type TableCell =
35+
export type TableCell =
3536
`<div class="px-4 font-medium${'' | ` ${TwTextColor}`}"${'' | ` title="${string}"`}>${string}</div>`;
36-
const CellNA: TableCell = '<div class="px-4 font-medium" title="N/A">N/A</div>';
37-
const CellOrangeNever: TableCell = '<div class="px-4 font-medium text-orange-500">Never</div>';
38-
const CellRedUnknown: TableCell = '<div class="px-4 font-medium text-red-500">Unknown</div>';
39-
const CellRedUnavailable: TableCell =
40-
'<div class="px-4 font-medium text-red-500">Unavailable</div>';
37+
export const CellNotApplicable =
38+
'<div class="px-4 font-medium" title="N/A">N/A</div>' as const satisfies TableCell;
39+
export const CellOrangeNever =
40+
'<div class="px-4 font-medium text-orange-500">Never</div>' as const satisfies TableCell;
41+
export const CellRedUnknown =
42+
'<div class="px-4 font-medium text-red-500">Unknown</div>' as const satisfies TableCell;
43+
export const CellRedUnavailable =
44+
'<div class="px-4 font-medium text-red-500">Unavailable</div>' as const satisfies TableCell;
45+
export const CellGreenTrue =
46+
'<div class="px-4 font-medium text-green-500">true</div>' as const satisfies TableCell;
47+
export const CellRedFalse =
48+
'<div class="px-4 font-medium text-red-500">false</div>' as const satisfies TableCell;
49+
export const CellGreenOnline =
50+
'<div class="px-4 font-medium text-green-500">online</div>' as const satisfies TableCell;
51+
export const CellRedOffline =
52+
'<div class="px-4 font-medium text-red-500">offline</div>' as const satisfies TableCell;
53+
54+
export const RenderCell = (content: string): TableCell =>
55+
`<div class="px-4 font-medium">${escapeHtml(content)}</div>`;
56+
export const RenderRedCell = (content: string): TableCell =>
57+
`<div class="px-4 font-medium text-red-500">${escapeHtml(content)}</div>`;
58+
export const RenderGreenCell = (content: string): TableCell =>
59+
`<div class="px-4 font-medium text-green-500">${escapeHtml(content)}</div>`;
60+
export const RenderOrangeCell = (content: string): TableCell =>
61+
`<div class="px-4 font-medium text-orange-500">${escapeHtml(content)}</div>`;
62+
export const RenderBlueCell = (content: string): TableCell =>
63+
`<div class="px-4 font-medium text-blue-500">${escapeHtml(content)}</div>`;
64+
export const RenderCellWithTooltip = (content: string, tooltip: string): TableCell =>
65+
`<div class="px-4 font-medium" title="${escapeHtml(tooltip)}">${escapeHtml(content)}</div>`;
4166

4267
export const LocaleDateRenderer = (date: Date): TableCell =>
43-
`<div class="px-4 font-medium" title="${date}">${date.toLocaleDateString()}</div>`;
68+
RenderCellWithTooltip(date.toLocaleDateString(), date.toString());
4469

4570
export const LocaleDateTimeRenderer = (date: Date): TableCell =>
46-
`<div class="px-4 font-medium" title="${date}">${date.toLocaleString()}</div>`;
71+
RenderCellWithTooltip(date.toLocaleString(), date.toString());
4772

4873
export const TimeSinceDurationRenderer = (date: Date): TableCell =>
49-
`<div class="px-4 font-medium" title="${date}">${durationToString(Date.now() - date.getTime())}</div>`;
74+
RenderCellWithTooltip(durationToString(Date.now() - date.getTime()), date.toString());
5075

5176
export const TimeSinceRelativeRenderer = (date: Date): TableCell =>
52-
`<div class="px-4 font-medium" title="${date}">${elapsedToString(date.getTime() - Date.now())}</div>`;
77+
RenderCellWithTooltip(elapsedToString(date.getTime() - Date.now()), date.toString());
5378

54-
export const TimeSinceRelativeOrNeverRenderer = (date: Date | null | undefined): TableCell => {
55-
if (!date) return CellOrangeNever;
56-
return TimeSinceRelativeRenderer(date);
57-
};
79+
export const TimeSinceRelativeOrNeverRenderer = (date: Date | null | undefined): TableCell =>
80+
date ? TimeSinceRelativeRenderer(date) : CellOrangeNever;
5881

5982
export const NumberRenderer = (number: number | null): TableCell =>
60-
number ? `<div class="px-4 font-medium" title="${number}">${number}</div>` : CellNA;
83+
number ? RenderCell(number.toString()) : CellNotApplicable;
6184

6285
export const UserAgentRenderer = (userAgent: string | null): TableCell => {
6386
if (!userAgent) return CellRedUnknown;
6487

6588
const readableName = getReadableUserAgentName(userAgent);
66-
if (!readableName)
67-
return `<div class="px-4 font-medium text-orange-500" title="${userAgent}">${userAgent}</div>`;
89+
if (!readableName) return RenderOrangeCell(userAgent);
6890

69-
return `<div class="px-4 font-medium" title="${userAgent}">${readableName}</div>`;
91+
return RenderCellWithTooltip(readableName, userAgent);
7092
};
7193

7294
export const FirmwareVersionRenderer = (firmwareVersion: SemVer | null): TableCell => {
7395
if (!firmwareVersion) return CellRedUnavailable;
7496

7597
let firmwareVersionString = firmwareVersion.toString();
7698

77-
let color: TwTextColor;
7899
if (firmwareVersionString.length <= 0) {
79-
firmwareVersionString = 'Invalid';
80-
color = 'text-red-500';
100+
return RenderRedCell('Invalid');
81101
} else if (firmwareVersionString === '0.0.0-local') {
82-
color = 'text-orange-500';
83-
} else {
84-
color = 'text-white';
102+
return RenderOrangeCell(firmwareVersionString);
85103
}
86104

87-
return `<div class="px-4 font-medium ${color}" title="${firmwareVersionString}">${firmwareVersionString}</div>`;
105+
return RenderCell(firmwareVersionString);
88106
};

src/routes/(authenticated)/admin/online-hubs/columns.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
CreateSortHeader,
88
FirmwareVersionRenderer,
99
NumberRenderer,
10+
RenderCellWithTooltip,
1011
TimeSinceDurationRenderer,
1112
UserAgentRenderer,
1213
} from '$lib/components/Table/ColumnUtils';
@@ -29,8 +30,7 @@ export type OnlineHub = {
2930
rssi: number | null;
3031
};
3132

32-
const OwnerRenderer = (owner: OnlineHubOwner) =>
33-
`<div class="px-4 font-medium" title="${owner.id}">${owner.name}</div>`;
33+
const OwnerRenderer = (owner: OnlineHubOwner) => RenderCellWithTooltip(owner.name, owner.id);
3434

3535
export const columns: ColumnDef<OnlineHub>[] = [
3636
{

src/routes/(authenticated)/admin/users/columns.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,14 @@ import { renderComponent } from '$lib/components/ui/data-table';
33
import { PasswordHashingAlgorithm, RoleType } from '$lib/api/internal/v1';
44
import DataTableActions from './data-table-actions.svelte';
55
import {
6+
CellGreenTrue,
7+
CellRedFalse,
68
CreateSimpleCellSnippet,
79
CreateSortHeader,
810
LocaleDateTimeRenderer,
11+
RenderBlueCell,
12+
RenderCell,
13+
RenderOrangeCell,
914
} from '$lib/components/Table/ColumnUtils';
1015

1116
export type User = {
@@ -19,16 +24,16 @@ export type User = {
1924
};
2025

2126
const PasswordHashTypeRenderer = (passwordHashType: PasswordHashingAlgorithm) => {
22-
const isLegacy = passwordHashType !== PasswordHashingAlgorithm.BCrypt;
23-
return `<div class="px-4 font-medium ${isLegacy ? 'text-orange-500' : ''}">${passwordHashType}</div>`;
27+
if (passwordHashType !== PasswordHashingAlgorithm.BCrypt)
28+
return RenderOrangeCell(passwordHashType);
29+
return RenderCell(passwordHashType);
2430
};
2531

2632
const IsEmailActivatedRenderer = (emailActivated: boolean) =>
27-
`<div class="px-4 font-medium ${emailActivated ? 'text-green-500' : 'text-red-500'}">${emailActivated}</div>`;
28-
33+
emailActivated ? CellGreenTrue : CellRedFalse;
2934
const UserRolesRenderer = (roles: RoleType[]) => {
3035
const isPrivileged = [RoleType.Admin, RoleType.System].some((role) => roles.includes(role));
31-
return `<div class="px-4 font-medium ${isPrivileged ? 'text-blue-500' : ''}">${roles}</div>`;
36+
return isPrivileged ? RenderBlueCell(roles.toString()) : RenderCell(roles.toString());
3237
};
3338

3439
export const columns: ColumnDef<User>[] = [

src/routes/(authenticated)/hubs/columns.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { ShockerModelType } from '$lib/api/internal/v1';
44
import DataTableActions from './data-table-actions.svelte';
55
import type { SemVer } from 'semver';
66
import {
7+
CellGreenOnline,
8+
CellRedOffline,
79
CreateSimpleCellSnippet,
810
CreateSortHeader,
911
FirmwareVersionRenderer,
@@ -27,8 +29,7 @@ export type Hub = {
2729
created_at: Date;
2830
};
2931

30-
const IsOnlineRenderer = (isOnline: boolean) =>
31-
`<div class="px-4 font-medium ${isOnline ? 'text-green-500' : 'text-red-500'}">${isOnline ? 'Online' : 'Offline'}</div>`;
32+
const IsOnlineRenderer = (isOnline: boolean) => (isOnline ? CellGreenOnline : CellRedOffline);
3233

3334
export const columns: ColumnDef<Hub>[] = [
3435
{

0 commit comments

Comments
 (0)