Skip to content

Commit 8af73ae

Browse files
authored
Merge pull request #240 from codeunia-dev/fix/security-warnings
Fix/security warnings
2 parents 63014f9 + e72d3f2 commit 8af73ae

File tree

9 files changed

+99
-34
lines changed

9 files changed

+99
-34
lines changed

app/[username]/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export default async function UsernamePage({ params }: UsernamePageProps) {
4141
if (error || !profile) {
4242
notFound();
4343
}
44-
} catch (error) {
44+
} catch {
4545
// If there's any error, show 404
4646
notFound();
4747
}

app/admin/forms/core-team/page.tsx

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { Input } from "@/components/ui/input";
88
import { Label } from "@/components/ui/label";
99
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
1010
import { apiFetch } from "@/lib/api-fetch";
11+
import { useAuth } from "@/lib/hooks/useAuth";
1112
import {
1213
Dialog,
1314
DialogContent,
@@ -61,6 +62,7 @@ interface CoreTeamApplication {
6162
}
6263

6364
export default function AdminCoreTeamPage() {
65+
const { loading: authLoading, is_admin } = useAuth();
6466
const [applications, setApplications] = useState<CoreTeamApplication[]>([]);
6567
const [searchTerm, setSearchTerm] = useState("");
6668
const [statusFilter, setStatusFilter] = useState("all");
@@ -124,6 +126,14 @@ export default function AdminCoreTeamPage() {
124126
return Array.from(new Set(roles)).sort();
125127
}, [applications]);
126128

129+
// Admin access check (after all hooks)
130+
if (authLoading) {
131+
return <div className="flex items-center justify-center min-h-[200px]">Loading...</div>;
132+
}
133+
if (!is_admin) {
134+
return <div className="text-center text-sm text-muted-foreground py-12">Forbidden: admin access required.</div>;
135+
}
136+
127137
// Handle status update
128138
const handleStatusUpdate = async (id: number, newStatus: string) => {
129139
try {
@@ -161,18 +171,19 @@ export default function AdminCoreTeamPage() {
161171

162172
// Export to CSV
163173
const handleExportCSV = () => {
174+
const sanitize = (v: string) => (/^[-+=@]/.test(v) ? `'${v}` : v);
164175
const csvContent = [
165176
["Name", "Email", "Phone", "Location", "Occupation", "Company", "Preferred Role", "Status", "Applied Date"],
166177
...filteredApplications.map(app => [
167-
`${app.first_name} ${app.last_name}`,
168-
app.email,
169-
app.phone || "",
170-
app.location,
171-
app.occupation,
172-
app.company || "",
173-
app.preferred_role,
174-
app.status,
175-
new Date(app.created_at).toLocaleDateString()
178+
sanitize(`${app.first_name} ${app.last_name}`),
179+
sanitize(app.email),
180+
sanitize(app.phone || ""),
181+
sanitize(app.location),
182+
sanitize(app.occupation),
183+
sanitize(app.company || ""),
184+
sanitize(app.preferred_role),
185+
sanitize(app.status),
186+
sanitize(new Date(app.created_at).toLocaleDateString())
176187
])
177188
].map(row => row.map(cell => `"${cell}"`).join(",")).join("\n");
178189

app/api/admin-core-team/route.ts

Lines changed: 60 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,61 @@
11
import { NextResponse } from 'next/server';
22
import { createClient } from '@supabase/supabase-js';
3+
import { createServerClient } from '@supabase/ssr';
4+
import { cookies } from 'next/headers';
35

4-
function getSupabaseClient() {
5-
return createClient(
6-
process.env.NEXT_PUBLIC_SUPABASE_URL!,
7-
process.env.SUPABASE_SERVICE_ROLE_KEY!
8-
);
6+
// Server-side clients
7+
function getServiceClient() {
8+
return createClient(
9+
process.env.NEXT_PUBLIC_SUPABASE_URL!,
10+
process.env.SUPABASE_SERVICE_ROLE_KEY!
11+
);
12+
}
13+
14+
async function getServerClient() {
15+
const cookieStore = await cookies();
16+
return createServerClient(
17+
process.env.NEXT_PUBLIC_SUPABASE_URL!,
18+
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
19+
{
20+
cookies: {
21+
getAll() {
22+
return cookieStore.getAll();
23+
},
24+
setAll(cookiesToSet) {
25+
cookiesToSet.forEach(({ name, value, options }) => {
26+
cookieStore.set(name, value, options);
27+
});
28+
},
29+
},
30+
}
31+
);
32+
}
33+
34+
async function requireAdmin() {
35+
const supa = await getServerClient();
36+
const { data: { user }, error } = await supa.auth.getUser();
37+
if (error || !user) {
38+
return { ok: false, resp: NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) };
39+
}
40+
// Check admin flag from profiles (service client to bypass RLS for lookup only)
41+
const svc = getServiceClient();
42+
const { data: profile, error: pErr } = await svc.from('profiles').select('is_admin').eq('id', user.id).single();
43+
if (pErr || !profile?.is_admin) {
44+
return { ok: false, resp: NextResponse.json({ error: 'Forbidden' }, { status: 403 }) };
45+
}
46+
return { ok: true };
947
}
1048

1149
export async function GET() {
1250
try {
13-
const supabase = getSupabaseClient();
51+
const auth = await requireAdmin();
52+
if (!auth.ok) return auth.resp;
53+
54+
const supabase = getServiceClient();
1455

1556
const { data, error } = await supabase
1657
.from('core_team_applications')
17-
.select('*')
58+
.select('id,first_name,last_name,email,phone,location,occupation,company,experience,skills,portfolio,preferred_role,availability,commitment,motivation,vision,previous_experience,social_media,references_info,additional_info,status,user_id,created_at,updated_at')
1859
.order('created_at', { ascending: false });
1960

2061
if (error) {
@@ -31,14 +72,18 @@ export async function GET() {
3172

3273
export async function POST(req: Request) {
3374
try {
75+
const auth = await requireAdmin();
76+
if (!auth.ok) return auth.resp;
77+
3478
const body = await req.json();
35-
const { id, status, notes } = body;
79+
const { id, status, notes } = body as { id?: number; status?: string; notes?: string };
3680

37-
if (!id || !status) {
38-
return NextResponse.json({ error: 'Missing required fields' }, { status: 400 });
81+
const ALLOWED_STATUSES = new Set(['pending','approved','rejected']);
82+
if (!id || !status || !ALLOWED_STATUSES.has(status)) {
83+
return NextResponse.json({ error: 'Missing required fields or invalid status' }, { status: 400 });
3984
}
4085

41-
const supabase = getSupabaseClient();
86+
const supabase = getServiceClient();
4287

4388
const { data, error } = await supabase
4489
.from('core_team_applications')
@@ -65,14 +110,17 @@ export async function POST(req: Request) {
65110

66111
export async function PATCH(req: Request) {
67112
try {
113+
const auth = await requireAdmin();
114+
if (!auth.ok) return auth.resp;
115+
68116
const body = await req.json();
69117
const { id, ...updates } = body;
70118

71119
if (!id) {
72120
return NextResponse.json({ error: 'Missing application ID' }, { status: 400 });
73121
}
74122

75-
const supabase = getSupabaseClient();
123+
const supabase = getServiceClient();
76124

77125
const { data, error } = await supabase
78126
.from('core_team_applications')

components/forms/collaboration-form.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { Building2, FileText, Loader2 } from "lucide-react";
1111
import { createBrowserClient } from "@supabase/ssr";
1212
import { toast } from "sonner";
1313
import { useAuth } from "@/lib/hooks/useAuth";
14+
import Link from "next/link";
1415

1516
export function CollaborationForm() {
1617
const { user, loading: authLoading } = useAuth();
@@ -143,9 +144,9 @@ export function CollaborationForm() {
143144
</p>
144145
<Button
145146
className="bg-gradient-to-r from-blue-500 to-cyan-500 hover:from-blue-600 hover:to-cyan-600 text-white"
146-
onClick={() => window.location.href = '/auth/signin'}
147+
asChild
147148
>
148-
Sign In to Continue
149+
<Link href="/auth/signin">Sign In to Continue</Link>
149150
</Button>
150151
</div>
151152
</CardContent>

components/forms/core-team-form.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { Crown, Send, Users, Code2, Camera, PenTool, Target, Zap } from "lucide-
1212
import { createBrowserClient } from "@supabase/ssr";
1313
import { toast } from "sonner";
1414
import { useAuth } from "@/lib/hooks/useAuth";
15+
import Link from "next/link";
1516

1617
export function CoreTeamForm() {
1718
const { user, loading: authLoading } = useAuth();
@@ -200,9 +201,9 @@ export function CoreTeamForm() {
200201
</p>
201202
<Button
202203
className="bg-gradient-to-r from-amber-500 to-yellow-500 hover:from-amber-600 hover:to-yellow-600 text-white"
203-
onClick={() => window.location.href = '/auth/signin'}
204+
asChild
204205
>
205-
Sign In to Continue
206+
<Link href="/auth/signin">Sign In to Continue</Link>
206207
</Button>
207208
</div>
208209
</CardContent>

components/forms/judges-form.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { Award, Send, Code2, Users, Globe } from "lucide-react";
1212
import { createBrowserClient } from "@supabase/ssr";
1313
import { toast } from "sonner";
1414
import { useAuth } from "@/lib/hooks/useAuth";
15+
import Link from "next/link";
1516

1617
export function JudgesForm() {
1718
const { user, loading: authLoading } = useAuth();
@@ -184,9 +185,9 @@ export function JudgesForm() {
184185
</p>
185186
<Button
186187
className="bg-gradient-to-r from-orange-500 to-red-500 hover:from-orange-600 hover:to-red-600 text-white"
187-
onClick={() => window.location.href = '/auth/signin'}
188+
asChild
188189
>
189-
Sign In to Continue
190+
<Link href="/auth/signin">Sign In to Continue</Link>
190191
</Button>
191192
</div>
192193
);

components/forms/mentor-form.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { Lightbulb, Send, Code2, Users, GraduationCap, MessageSquare } from "luc
1212
import { createBrowserClient } from "@supabase/ssr";
1313
import { toast } from "sonner";
1414
import { useAuth } from "@/lib/hooks/useAuth";
15+
import Link from "next/link";
1516

1617
export function MentorForm() {
1718
const { user, loading: authLoading } = useAuth();
@@ -229,9 +230,9 @@ export function MentorForm() {
229230
</p>
230231
<Button
231232
className="bg-gradient-to-r from-purple-500 to-indigo-500 hover:from-purple-600 hover:to-indigo-600 text-white"
232-
onClick={() => window.location.href = '/auth/signin'}
233+
asChild
233234
>
234-
Sign In to Continue
235+
<Link href="/auth/signin">Sign In to Continue</Link>
235236
</Button>
236237
</div>
237238
</CardContent>

components/forms/sponsorship-form.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { Trophy, Send, Building2 } from "lucide-react";
1212
import { createBrowserClient } from "@supabase/ssr";
1313
import { toast } from "sonner";
1414
import { useAuth } from "@/lib/hooks/useAuth";
15+
import Link from "next/link";
1516

1617
export function SponsorshipForm() {
1718
const { user, loading: authLoading } = useAuth();
@@ -188,9 +189,9 @@ export function SponsorshipForm() {
188189
</p>
189190
<Button
190191
className="bg-gradient-to-r from-green-500 to-emerald-500 hover:from-green-600 hover:to-emerald-600 text-white"
191-
onClick={() => window.location.href = '/auth/signin'}
192+
asChild
192193
>
193-
Sign In to Continue
194+
<Link href="/auth/signin">Sign In to Continue</Link>
194195
</Button>
195196
</div>
196197
);

components/forms/volunteer-form.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { HandHeart, Send, Calendar, MapPin, Users, Code2, Heart } from "lucide-r
1212
import { createBrowserClient } from "@supabase/ssr";
1313
import { toast } from "sonner";
1414
import { useAuth } from "@/lib/hooks/useAuth";
15+
import Link from "next/link";
1516

1617
export function VolunteerForm() {
1718
const { user, loading: authLoading } = useAuth();
@@ -176,9 +177,9 @@ export function VolunteerForm() {
176177
</p>
177178
<Button
178179
className="bg-gradient-to-r from-pink-500 to-rose-500 hover:from-pink-600 hover:to-rose-600 text-white"
179-
onClick={() => window.location.href = '/auth/signin'}
180+
asChild
180181
>
181-
Sign In to Continue
182+
<Link href="/auth/signin">Sign In to Continue</Link>
182183
</Button>
183184
</div>
184185
</CardContent>

0 commit comments

Comments
 (0)