Skip to content

Commit 0b33cdd

Browse files
committed
feat: harden security configuration
- explicit auth config (token expiry, login attempt lockout, secure cookies) - CORS/CSRF whitelist + serverURL scoped to NEXT_PUBLIC_BASE_URL - disable unused GraphQL API and remove its routes - restrict Media uploads to images, cap file size at 5 MB
1 parent a4fd786 commit 0b33cdd

7 files changed

Lines changed: 48 additions & 18 deletions

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'training-app': minor
3+
---
4+
5+
Harden security configuration (no DB migration required):
6+
7+
- Explicit `auth` config on `users` and `clients` (2h token expiration, 5 max login attempts, 10 min lockout, `secure` cookies in production, `sameSite: Lax`)
8+
- Add `serverURL`, CORS and CSRF whitelists scoped to `NEXT_PUBLIC_BASE_URL`
9+
- Disable the unused GraphQL API and remove its routes to shrink the public API surface
10+
- Restrict Media uploads to images and cap file size at 5 MB

src/app/(payload)/api/graphql-playground/route.ts

Lines changed: 0 additions & 7 deletions
This file was deleted.

src/app/(payload)/api/graphql/route.ts

Lines changed: 0 additions & 8 deletions
This file was deleted.

src/collections/clients/index.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,15 @@ import { isAdmin, adminOrSelf, isAdminField } from '../../access'
33

44
export const Clients: CollectionConfig = {
55
slug: 'clients',
6-
auth: true,
6+
auth: {
7+
tokenExpiration: 60 * 60 * 2, // 2h session
8+
maxLoginAttempts: 5,
9+
lockTime: 1000 * 60 * 10, // 10 min lockout after too many attempts
10+
cookies: {
11+
secure: process.env.NODE_ENV === 'production', // HTTPS-only cookie in prod
12+
sameSite: 'Lax',
13+
},
14+
},
715
admin: {
816
useAsTitle: 'email',
917
defaultColumns: ['name', 'email'],

src/collections/media/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,7 @@ export const Media: CollectionConfig = {
1111
type: 'text',
1212
},
1313
],
14-
upload: true,
14+
upload: {
15+
mimeTypes: ['image/*'], // only allow image uploads
16+
},
1517
}

src/collections/users/index.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,15 @@ export const Users: CollectionConfig = {
55
admin: {
66
useAsTitle: 'email',
77
},
8-
auth: true,
8+
auth: {
9+
tokenExpiration: 60 * 60 * 2, // 2h session
10+
maxLoginAttempts: 5,
11+
lockTime: 1000 * 60 * 10, // 10 min lockout after too many attempts
12+
cookies: {
13+
secure: process.env.NODE_ENV === 'production', // HTTPS-only cookie in prod
14+
sameSite: 'Lax',
15+
},
16+
},
917
fields: [],
1018
versions: false,
1119
}

src/payload.config.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,24 @@ import {
2424
const filename = fileURLToPath(import.meta.url)
2525
const dirname = path.dirname(filename)
2626

27+
const serverURL = process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost:3000'
28+
2729
export default buildConfig({
30+
serverURL,
31+
// Restrict cross-origin requests and CSRF-trusted origins to our own domain.
32+
cors: [serverURL],
33+
csrf: [serverURL],
34+
// GraphQL is not used by the app; disable it to shrink the public API surface.
35+
graphQL: {
36+
disable: true,
37+
},
38+
// Cap upload size at the multipart parser to reject oversized files early.
39+
upload: {
40+
limits: {
41+
fileSize: 5_000_000, // 5 MB
42+
},
43+
abortOnLimit: true,
44+
},
2845
admin: {
2946
user: Users.slug,
3047
importMap: {

0 commit comments

Comments
 (0)