Skip to content

Commit 5c33ae1

Browse files
committed
fix: bunch of validation fixes
1 parent b628489 commit 5c33ae1

File tree

17 files changed

+127
-85
lines changed

17 files changed

+127
-85
lines changed

src/components/pages/users/EditUserModal.tsx

Lines changed: 72 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import {
2323
import { useForm } from '@mantine/form';
2424
import { notifications } from '@mantine/notifications';
2525
import { IconPhotoMinus, IconUserCancel, IconUserEdit } from '@tabler/icons-react';
26-
import { useEffect } from 'react';
26+
import { useEffect, useMemo } from 'react';
2727
import { mutate } from 'swr';
2828

2929
export default function EditUserModal({
@@ -37,6 +37,12 @@ export default function EditUserModal({
3737
}) {
3838
const currentUser = useUserStore((state) => state.user);
3939

40+
const derivedFileType: 'BY_BYTES' | 'BY_FILES' | 'NONE' = useMemo(() => {
41+
if (user?.quota?.maxBytes != null) return 'BY_BYTES';
42+
if (user?.quota?.maxFiles != null) return 'BY_FILES';
43+
return 'NONE';
44+
}, [user]);
45+
4046
const form = useForm<{
4147
username: string;
4248
password: string;
@@ -52,10 +58,10 @@ export default function EditUserModal({
5258
password: '',
5359
role: user?.role || 'USER',
5460
avatar: null,
55-
fileType: user?.quota?.filesQuota || 'NONE',
56-
maxFiles: user?.quota?.maxFiles || 0,
57-
maxBytes: user?.quota?.maxBytes || '',
58-
maxUrls: user?.quota?.maxUrls || 0,
61+
fileType: derivedFileType,
62+
maxFiles: user?.quota?.maxFiles ?? 0,
63+
maxBytes: user?.quota?.maxBytes ?? '',
64+
maxUrls: user?.quota?.maxUrls ?? 0,
5965
},
6066
validate: {
6167
maxBytes(value, values) {
@@ -74,19 +80,33 @@ export default function EditUserModal({
7480
}),
7581
});
7682

83+
useEffect(() => {
84+
form.setValues({
85+
username: user?.username || '',
86+
password: '',
87+
role: user?.role || 'USER',
88+
avatar: null,
89+
fileType:
90+
user?.quota?.maxBytes != null ? 'BY_BYTES' : user?.quota?.maxFiles != null ? 'BY_FILES' : 'NONE',
91+
maxFiles: user?.quota?.maxFiles ?? 0,
92+
maxBytes: user?.quota?.maxBytes ?? '',
93+
maxUrls: user?.quota?.maxUrls ?? 0,
94+
});
95+
}, [user]);
96+
7797
const onSubmit = async (values: typeof form.values) => {
7898
if (!user) return;
7999

80100
let avatar64: string | null = null;
81101
if (values.avatar) {
82-
if (!values.avatar.type.startsWith('image/')) return form.setFieldError('avatar', 'Invalid file type');
102+
if (!values.avatar.type.startsWith('image/')) {
103+
return form.setFieldError('avatar', 'Invalid file type');
104+
}
83105

84106
try {
85-
const res = await readToDataURL(values.avatar);
86-
avatar64 = res;
107+
avatar64 = await readToDataURL(values.avatar);
87108
} catch (e) {
88109
console.error(e);
89-
90110
return form.setFieldError('avatar', 'Failed to read avatar file');
91111
}
92112
}
@@ -95,24 +115,28 @@ export default function EditUserModal({
95115
filesType?: 'BY_BYTES' | 'BY_FILES' | 'NONE';
96116
maxFiles?: number | null;
97117
maxBytes?: string | null;
98-
99118
maxUrls?: number | null;
100119
} = {};
101120

102121
if (values.fileType === 'NONE') {
103122
finalQuota.filesType = 'NONE';
104-
finalQuota.maxFiles = null;
105-
finalQuota.maxBytes = null;
106-
finalQuota.maxUrls = null;
107-
} else if (values.fileType === 'BY_BYTES') {
123+
}
124+
125+
if (values.fileType === 'BY_BYTES') {
108126
finalQuota.filesType = 'BY_BYTES';
109127
finalQuota.maxBytes = values.maxBytes;
110-
} else {
128+
finalQuota.maxFiles = null;
129+
}
130+
131+
if (values.fileType === 'BY_FILES') {
111132
finalQuota.filesType = 'BY_FILES';
112133
finalQuota.maxFiles = values.maxFiles;
134+
finalQuota.maxBytes = null;
113135
}
114136

115-
if (values.maxUrls) finalQuota.maxUrls = values.maxUrls > 0 ? values.maxUrls : null;
137+
if (values.maxUrls) {
138+
finalQuota.maxUrls = values.maxUrls > 0 ? values.maxUrls : null;
139+
}
116140

117141
const { data, error } = await fetchApi<Response['/api/users/[id]']>(`/api/users/${user.id}`, 'PATCH', {
118142
...(values.username !== user.username && { username: values.username }),
@@ -143,22 +167,9 @@ export default function EditUserModal({
143167
}
144168
};
145169

146-
useEffect(() => {
147-
form.setValues({
148-
username: user?.username || '',
149-
password: '',
150-
role: user?.role || 'USER',
151-
avatar: null,
152-
fileType: user?.quota?.filesQuota || 'NONE',
153-
maxFiles: user?.quota?.maxFiles || 0,
154-
maxBytes: user?.quota?.maxBytes || '',
155-
maxUrls: user?.quota?.maxUrls || 0,
156-
});
157-
}, [user]);
158-
159170
return (
160171
<Modal centered title={`Edit ${user?.username ?? ''}`} onClose={onClose} opened={opened}>
161-
<Text size='sm' c='dimmed'>
172+
<Text size='sm' mt={-5} my='sm' c='dimmed'>
162173
Any fields that are blank will be omitted, and will not be updated.
163174
</Text>
164175

@@ -171,12 +182,14 @@ export default function EditUserModal({
171182
autoComplete='username'
172183
{...form.getInputProps('username')}
173184
/>
185+
174186
<PasswordInput
175187
label='Password'
176188
placeholder='Enter a password...'
177189
autoComplete='new-password'
178190
{...form.getInputProps('password')}
179191
/>
192+
180193
<FileInput
181194
label='Avatar'
182195
placeholder='Select an avatar...'
@@ -209,42 +222,51 @@ export default function EditUserModal({
209222
/>
210223

211224
<Divider />
212-
<Title order={4}>Quota</Title>
225+
<Title order={5}>Quota</Title>
213226

214227
<Select
215228
label='File Quota Type'
216229
description='Whether to set a quota on files by total bytes or the total number of files.'
217230
data={[
218231
{ value: 'BY_BYTES', label: 'By Bytes' },
219232
{ value: 'BY_FILES', label: 'By File Count' },
220-
{ value: 'NONE', label: 'No File Quota' },
233+
{ value: 'NONE', label: 'No Files Quota' },
221234
]}
222235
{...form.getInputProps('fileType')}
223236
/>
224-
{form.values.fileType === 'BY_FILES' ? (
225-
<NumberInput
226-
label='Max Files'
227-
description='The maximum number of files the user can upload.'
228-
placeholder='Enter a number...'
229-
mx='lg'
230-
min={0}
231-
{...form.getInputProps('maxFiles')}
232-
/>
233-
) : form.values.fileType === 'BY_BYTES' ? (
234-
<TextInput
235-
label='Max Bytes'
236-
description='The maximum number of bytes the user can upload.'
237-
placeholder='Enter a human readable byte-format...'
238-
mx='lg'
239-
{...form.getInputProps('maxBytes')}
240-
/>
241-
) : null}
237+
238+
{form.values.fileType !== 'NONE' && (
239+
<>
240+
{form.values.fileType === 'BY_FILES' && (
241+
<NumberInput
242+
label='Max Files'
243+
description='The maximum number of files the user can upload.'
244+
placeholder='Enter a number...'
245+
mx='lg'
246+
min={0}
247+
{...form.getInputProps('maxFiles')}
248+
/>
249+
)}
250+
251+
{form.values.fileType === 'BY_BYTES' && (
252+
<TextInput
253+
label='Max Bytes'
254+
description='The maximum number of bytes the user can upload.'
255+
placeholder='Enter a human readable byte-format...'
256+
mx='lg'
257+
{...form.getInputProps('maxBytes')}
258+
/>
259+
)}
260+
</>
261+
)}
242262

243263
<NumberInput
244264
label='Max URLs'
245265
placeholder='Enter a number...'
266+
description='The maximum number of URLs the user can create. Leave as 0 for unlimited.'
246267
{...form.getInputProps('maxUrls')}
247268
/>
269+
<Divider />
248270

249271
<Button
250272
type='submit'

src/server/routes/api/auth/register.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { getSession, saveSession } from '@/server/session';
88
import typedPlugin from '@/server/typedPlugin';
99
import z from 'zod';
1010
import { ApiLoginResponse } from './login';
11+
import { zStringTrimmed } from '@/lib/validation';
1112

1213
export type ApiAuthRegisterResponse = ApiLoginResponse;
1314

@@ -21,8 +22,8 @@ export default typedPlugin(
2122
{
2223
schema: {
2324
body: z.object({
24-
username: z.string().min(1),
25-
password: z.string().min(1),
25+
username: zStringTrimmed,
26+
password: zStringTrimmed,
2627
code: z.string().min(1).optional(),
2728
}),
2829
},

src/server/routes/api/server/import/v4.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export default typedPlugin(
3636
export4: export4Schema.required(),
3737
config: z.object({
3838
settings: z.boolean().optional().default(false),
39-
mergeCurrentUser: z.string().nullable().optional().default(null),
39+
mergeCurrentUser: z.string().nullish().default(null),
4040
}),
4141
}),
4242
},

src/server/routes/api/user/files/[id]/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ export default typedPlugin(
4545
body: z.object({
4646
favorite: z.boolean().optional(),
4747
maxViews: z.number().min(0).optional(),
48-
password: z.string().optional().nullable(),
48+
password: z.string().nullish(),
4949
originalName: z.string().trim().min(1).optional().transform(zValidatePath),
5050
type: z.string().min(1).optional(),
5151
tags: z.array(z.string()).optional(),

src/server/routes/api/user/files/[id]/password.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { verifyPassword } from '@/lib/crypto';
22
import { prisma } from '@/lib/db';
33
import { log } from '@/lib/logger';
44
import { secondlyRatelimit } from '@/lib/ratelimits';
5+
import { zStringTrimmed } from '@/lib/validation';
56
import typedPlugin from '@/server/typedPlugin';
67
import z from 'zod';
78

@@ -19,7 +20,7 @@ export default typedPlugin(
1920
{
2021
schema: {
2122
body: z.object({
22-
password: z.string().trim().min(1),
23+
password: zStringTrimmed,
2324
}),
2425
params: z.object({
2526
id: z.string(),

src/server/routes/api/user/files/[id]/raw.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { prisma } from '@/lib/db';
66
import { sanitizeFilename } from '@/lib/fs';
77
import { log } from '@/lib/logger';
88
import { canInteract } from '@/lib/role';
9+
import { zQsBoolean } from '@/lib/validation';
910
import { userMiddleware } from '@/server/middleware/user';
1011
import typedPlugin from '@/server/typedPlugin';
1112
import z from 'zod';
@@ -24,7 +25,7 @@ export default typedPlugin(
2425
}),
2526
querystring: z.object({
2627
pw: z.string().optional(),
27-
download: z.string().optional(),
28+
download: zQsBoolean.optional(),
2829
}),
2930
},
3031
preHandler: [userMiddleware],

src/server/routes/api/user/files/index.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { prisma } from '@/lib/db';
22
import { File, cleanFiles, fileSelect } from '@/lib/db/models/file';
33
import { canInteract } from '@/lib/role';
4+
import { zQsBoolean } from '@/lib/validation';
45
import { userMiddleware } from '@/server/middleware/user';
56
import typedPlugin from '@/server/typedPlugin';
67
import z from 'zod';
@@ -28,7 +29,7 @@ export default typedPlugin(
2829
page: z.coerce.number().optional(),
2930
perpage: z.coerce.number().default(15),
3031
filter: z.enum(['dashboard', 'none', 'all']).optional().default('none'),
31-
favorite: z.enum(['true', 'false']).optional(),
32+
favorite: zQsBoolean.default(false).optional(),
3233
sortBy: z
3334
.enum([
3435
'id',
@@ -128,7 +129,7 @@ export default typedPlugin(
128129
},
129130
],
130131
}),
131-
...(favorite === 'true' &&
132+
...(favorite &&
132133
filter !== 'all' && {
133134
favorite: true,
134135
}),
@@ -198,7 +199,7 @@ export default typedPlugin(
198199
},
199200
],
200201
}),
201-
...(favorite === 'true' &&
202+
...(favorite &&
202203
filter !== 'all' && {
203204
favorite: true,
204205
}),

src/server/routes/api/user/folders/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Folder, cleanFolder, cleanFolders } from '@/lib/db/models/folder';
44
import { log } from '@/lib/logger';
55
import { secondlyRatelimit } from '@/lib/ratelimits';
66
import { canInteract } from '@/lib/role';
7+
import { zQsBoolean } from '@/lib/validation';
78
import { userMiddleware } from '@/server/middleware/user';
89
import typedPlugin from '@/server/typedPlugin';
910
import z from 'zod';
@@ -20,7 +21,7 @@ export default typedPlugin(
2021
{
2122
schema: {
2223
querystring: z.object({
23-
noincl: z.string().optional(),
24+
noincl: zQsBoolean.optional(),
2425
user: z.string().optional(),
2526
}),
2627
},

src/server/routes/api/user/index.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { prisma } from '@/lib/db';
33
import { User, userSelect } from '@/lib/db/models/user';
44
import { log } from '@/lib/logger';
55
import { secondlyRatelimit } from '@/lib/ratelimits';
6+
import { zStringTrimmed } from '@/lib/validation';
67
import { userMiddleware } from '@/server/middleware/user';
78
import { getSession, saveSession } from '@/server/session';
89
import typedPlugin from '@/server/typedPlugin';
@@ -26,17 +27,17 @@ export default typedPlugin(
2627
{
2728
schema: {
2829
body: z.object({
29-
username: z.string().min(1).optional(),
30-
password: z.string().min(1).optional(),
31-
avatar: z.string().nullable().optional(),
30+
username: zStringTrimmed.optional(),
31+
password: zStringTrimmed.optional(),
32+
avatar: z.string().nullish(),
3233
view: z
3334
.object({
34-
content: z.string().optional().nullable(),
35+
content: z.string().nullish(),
3536
embed: z.boolean().optional(),
36-
embedTitle: z.string().optional().nullable(),
37-
embedDescription: z.string().optional().nullable(),
38-
embedColor: z.string().optional().nullable(),
39-
embedSiteName: z.string().optional().nullable(),
37+
embedTitle: z.string().nullish(),
38+
embedDescription: z.string().nullish(),
39+
embedColor: z.string().nullish(),
40+
embedSiteName: z.string().nullish(),
4041
enabled: z.boolean().optional(),
4142
align: z.enum(['left', 'center', 'right']).optional(),
4243
showMimetype: z.boolean().optional(),

src/server/routes/api/user/mfa/passkey.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { log } from '@/lib/logger';
55
import { isTruthy } from '@/lib/primitive';
66
import { secondlyRatelimit } from '@/lib/ratelimits';
77
import { TimedCache } from '@/lib/timedCache';
8+
import { zStringTrimmed } from '@/lib/validation';
89
import { Prisma } from '@/prisma/client';
910
import { userMiddleware } from '@/server/middleware/user';
1011
import typedPlugin from '@/server/typedPlugin';
@@ -109,7 +110,7 @@ export default typedPlugin(
109110
schema: {
110111
body: z.object({
111112
response: z.custom<RegistrationResponseJSON>(),
112-
name: z.string().trim().min(1),
113+
name: zStringTrimmed,
113114
}),
114115
},
115116
preHandler: [userMiddleware, passkeysEnabledHandler],

0 commit comments

Comments
 (0)