Skip to content

Commit 7189639

Browse files
committed
feat: Implement Firestore security rules, add security headers, and update Next.js/React dependencies.
1 parent fc02809 commit 7189639

7 files changed

Lines changed: 107 additions & 9 deletions

File tree

app/globals.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
@import "tailwindcss";
21
@import url("https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.2.0/github-markdown-dark.min.css");
2+
@import "tailwindcss";
33

44
iframe[aria-hidden="true"] {
55
display: none !important;

bun.lockb

100644100755
5.33 KB
Binary file not shown.

firestore.rules

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
rules_version = '2';
2+
3+
service cloud.firestore {
4+
match /databases/{database}/documents {
5+
// Helper function to check if user is authenticated
6+
function isAuthenticated() {
7+
return request.auth != null;
8+
}
9+
10+
// Helper function to check if user has admin/super role
11+
// NOTE: This assumes custom claims are set OR we check a user document.
12+
// Since custom claims are better for security but require backend setup,
13+
// we'll check the 'authors' collection for role as per the app's structure.
14+
function isAdmin() {
15+
return isAuthenticated() &&
16+
exists(/databases/$(database)/documents/authors/$(request.auth.uid)) &&
17+
(get(/databases/$(database)/documents/authors/$(request.auth.uid)).data.role in ['admin', 'super']);
18+
}
19+
20+
// Default: Deny all
21+
match /{document=**} {
22+
allow read, write: if false;
23+
}
24+
25+
// Articles: Public read, Admin write
26+
match /articles/{articleId} {
27+
allow read: if true;
28+
allow write: if isAdmin();
29+
}
30+
31+
// News: Public read, Admin write
32+
match /news/{newsId} {
33+
allow read: if true;
34+
allow write: if isAdmin();
35+
}
36+
37+
// Authors/Team: Public read, Admin write (or self update - optional)
38+
match /authors/{userId} {
39+
allow read: if true;
40+
allow write: if isAdmin() || (isAuthenticated() && request.auth.uid == userId);
41+
}
42+
}
43+
}

lib/auth-context.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
3535
try {
3636
const { userRole, lastAuthenticated } = JSON.parse(storedAuth)
3737
// Only use stored data if it's less than 1 hour old
38+
// SECURITY NOTE: This is for UI responsiveness only. Backend rules must always verify auth token/claims.
39+
// A knowledgeable user can modify localStorage to spoof 'admin' role in the Client UI, but
40+
// they will fail at the database level if rules are correct.
3841
if (lastAuthenticated && Date.now() - lastAuthenticated < 60 * 60 * 1000) {
3942
setAuthState((prev) => ({ ...prev, userRole }))
4043
}

next.config.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,45 @@
11
import type { NextConfig } from "next";
22

3+
34
const nextConfig: NextConfig = {
45
/* config options here */
6+
async headers() {
7+
return [
8+
{
9+
source: '/:path*',
10+
headers: [
11+
{
12+
key: 'X-DNS-Prefetch-Control',
13+
value: 'on'
14+
},
15+
{
16+
key: 'Strict-Transport-Security',
17+
value: 'max-age=63072000; includeSubDomains; preload'
18+
},
19+
{
20+
key: 'X-XSS-Protection',
21+
value: '1; mode=block'
22+
},
23+
{
24+
key: 'X-Frame-Options',
25+
value: 'DENY' // Preventing clickjacking
26+
},
27+
{
28+
key: 'X-Content-Type-Options',
29+
value: 'nosniff'
30+
},
31+
{
32+
key: 'Referrer-Policy',
33+
value: 'strict-origin-when-cross-origin'
34+
},
35+
{
36+
key: 'Permissions-Policy',
37+
value: 'camera=(), microphone=(), geolocation=(), interest-cohort=()'
38+
}
39+
],
40+
},
41+
]
42+
},
543
};
644

745
export default nextConfig;

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,11 @@
4848
"markdown-to-jsx": "^7.7.4",
4949
"marked": "^15.0.7",
5050
"msw": "^2.7.3",
51-
"next": "15.2.1",
51+
"next": "^16.1.1",
5252
"next-themes": "^0.4.6",
53-
"react": "^19.0.0",
53+
"react": "^19.2.3",
5454
"react-day-picker": "^9.6.5",
55-
"react-dom": "^19.0.0",
55+
"react-dom": "^19.2.3",
5656
"react-hook-form": "^7.55.0",
5757
"react-hot-toast": "^2.5.2",
5858
"react-icons": "^5.5.0",

tsconfig.json

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
{
22
"compilerOptions": {
33
"target": "ES2017",
4-
"lib": ["dom", "dom.iterable", "esnext"],
4+
"lib": [
5+
"dom",
6+
"dom.iterable",
7+
"esnext"
8+
],
59
"allowJs": true,
610
"skipLibCheck": true,
711
"strict": true,
@@ -11,17 +15,27 @@
1115
"moduleResolution": "bundler",
1216
"resolveJsonModule": true,
1317
"isolatedModules": true,
14-
"jsx": "preserve",
18+
"jsx": "react-jsx",
1519
"incremental": true,
1620
"plugins": [
1721
{
1822
"name": "next"
1923
}
2024
],
2125
"paths": {
22-
"@/*": ["./*"]
26+
"@/*": [
27+
"./*"
28+
]
2329
}
2430
},
25-
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26-
"exclude": ["node_modules"]
31+
"include": [
32+
"next-env.d.ts",
33+
"**/*.ts",
34+
"**/*.tsx",
35+
".next/types/**/*.ts",
36+
".next/dev/types/**/*.ts"
37+
],
38+
"exclude": [
39+
"node_modules"
40+
]
2741
}

0 commit comments

Comments
 (0)