From f450f8ee03dfd20824b444e739836e6fe08297b3 Mon Sep 17 00:00:00 2001 From: streaper2 Date: Fri, 1 May 2026 21:48:00 +0200 Subject: [PATCH] =?UTF-8?q?correction=20faille=20de=20securit=C3=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/annuaire/[id]/page.tsx | 13 ++++++++++--- app/annuaire/page.tsx | 11 +++++++++-- app/api/businesses/[id]/route.ts | 17 ++++++++++++++--- app/api/businesses/route.ts | 21 ++++++++++++++++++--- app/api/favorites/route.ts | 21 +++++++++++++++++++-- app/forgot-password/page.tsx | 2 +- app/login/page.tsx | 2 +- app/page.tsx | 6 ++++-- app/register/page.tsx | 2 +- app/reset-password/page.tsx | 2 +- 10 files changed, 78 insertions(+), 19 deletions(-) diff --git a/app/annuaire/[id]/page.tsx b/app/annuaire/[id]/page.tsx index 10c487e..e1de6ff 100644 --- a/app/annuaire/[id]/page.tsx +++ b/app/annuaire/[id]/page.tsx @@ -77,7 +77,9 @@ const BusinessDetailPage = () => { // 2. Try API (Database) try { - const res = await fetch(`/api/businesses/${id}`); + const res = await fetch(`/api/businesses/${id}`, { + headers: user ? { 'x-user-id': user.id } : {} + }); if (res.ok) { const data = await res.json(); setBusiness(data); @@ -104,7 +106,10 @@ const BusinessDetailPage = () => { } // Also increment views count in DB (Legacy support) - fetch(`/api/businesses/${id}/view`, { method: 'POST' }).catch(console.error); + fetch(`/api/businesses/${id}/view`, { + method: 'POST', + headers: user ? { 'x-user-id': user.id } : {} + }).catch(console.error); } else { setBusiness(null); } @@ -135,7 +140,9 @@ const BusinessDetailPage = () => { // 5. Fetch all ratings try { setLoadingRatings(true); - const ratingsRes = await fetch(`/api/businesses/${id}/ratings`); + const ratingsRes = await fetch(`/api/businesses/${id}/ratings`, { + headers: user ? { 'x-user-id': user.id } : {} + }); if (ratingsRes.ok) { const ratingsData = await ratingsRes.json(); setRatings(ratingsData); diff --git a/app/annuaire/page.tsx b/app/annuaire/page.tsx index 3ecdc38..215a089 100644 --- a/app/annuaire/page.tsx +++ b/app/annuaire/page.tsx @@ -7,8 +7,10 @@ import { Search, Loader2 } from 'lucide-react'; import { Business, Country } from '../../types'; import BusinessCard from '../../components/BusinessCard'; import AnnuaireHero from '../../components/AnnuaireHero'; +import { useUser } from '../../components/UserProvider'; const AnnuairePageContent = () => { + const { user } = useUser(); const [businesses, setBusinesses] = useState([]); const [featuredBusiness, setFeaturedBusiness] = useState(null); const [countries, setCountries] = useState([]); @@ -41,7 +43,10 @@ const AnnuairePageContent = () => { const fetchHeadliner = async () => { try { // Disable caching to ensure we get fresh data and more variety - const res = await fetch('/api/businesses?featured=true', { cache: 'no-store' }); + const res = await fetch('/api/businesses?featured=true', { + cache: 'no-store', + headers: user ? { 'x-user-id': user.id } : {} + }); const data = await res.json(); if (Array.isArray(data) && data.length > 0) { // Truly random selection from the pool of featured businesses @@ -65,7 +70,9 @@ const AnnuairePageContent = () => { if (filterCountry !== 'All') params.append('countryId', filterCountry); if (searchQuery) params.append('q', searchQuery); - const res = await fetch(`/api/businesses?${params.toString()}`); + const res = await fetch(`/api/businesses?${params.toString()}`, { + headers: user ? { 'x-user-id': user.id } : {} + }); const data = await res.json(); if (Array.isArray(data)) { setBusinesses(data); diff --git a/app/api/businesses/[id]/route.ts b/app/api/businesses/[id]/route.ts index b2b94a6..44ae472 100644 --- a/app/api/businesses/[id]/route.ts +++ b/app/api/businesses/[id]/route.ts @@ -1,15 +1,20 @@ import { NextRequest, NextResponse } from 'next/server' import prisma from '@/lib/prisma' -import { USER_SAFE_SELECT } from '@/lib/auth-utils' +import { USER_SAFE_SELECT, getAuthUser } from '@/lib/auth-utils' type Params = { params: Promise<{ id: string }> } // GET /api/businesses/[id] export async function GET( - _request: NextRequest, + request: NextRequest, context: { params: Promise<{ id: string }> } ): Promise { try { + const user = await getAuthUser(request) + if (!user) { + return NextResponse.json({ error: 'Authentification requise' }, { status: 401 }) + } + const { id } = await context.params const business = await prisma.business.findFirst({ where: { @@ -26,7 +31,13 @@ export async function GET( }, }) if (!business) return NextResponse.json({ error: 'Non trouvé' }, { status: 404 }) - return NextResponse.json(business) + + // Mask technical IDs + const { id: bizId, ownerId, ...rest } = business + return NextResponse.json({ + ...rest, + id: business.slug || bizId + }) } catch (error) { console.error('GET /api/businesses/[id] error:', error) return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 }) diff --git a/app/api/businesses/route.ts b/app/api/businesses/route.ts index 90b90d8..aa5e5dc 100644 --- a/app/api/businesses/route.ts +++ b/app/api/businesses/route.ts @@ -1,10 +1,15 @@ import { NextRequest, NextResponse } from 'next/server' import prisma from '@/lib/prisma' -import { USER_SAFE_SELECT } from '@/lib/auth-utils' +import { USER_SAFE_SELECT, getAuthUser } from '@/lib/auth-utils' // GET /api/businesses — List all businesses (Mock + DB) export async function GET(request: NextRequest) { try { + const user = await getAuthUser(request) + if (!user) { + return NextResponse.json({ error: 'Authentification requise' }, { status: 401 }) + } + const { searchParams } = new URL(request.url) const category = searchParams.get('category') const q = searchParams.get('q')?.toLowerCase() @@ -52,8 +57,18 @@ export async function GET(request: NextRequest) { orderBy: { createdAt: 'desc' }, }) - // 2. Return DB results - return NextResponse.json(dbBusinesses) + // 2. Map results to mask technical IDs + const safeBusinesses = dbBusinesses.map(biz => { + const { id, ownerId, ...rest } = biz + return { + ...rest, + // Return slug as the primary identifier if available, + // otherwise return a masked version or keep it but it's now "safe" + id: biz.slug || id + } + }) + + return NextResponse.json(safeBusinesses) } catch (error) { console.error('GET /api/businesses error:', error) return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 }) diff --git a/app/api/favorites/route.ts b/app/api/favorites/route.ts index 8487c19..8350150 100644 --- a/app/api/favorites/route.ts +++ b/app/api/favorites/route.ts @@ -43,12 +43,29 @@ export async function POST(request: NextRequest) { return NextResponse.json({ error: 'businessId requis' }, { status: 400 }); } + // Resolve business ID (in case slug was passed) + const business = await prisma.business.findFirst({ + where: { + OR: [ + { id: businessId }, + { slug: businessId } + ] + }, + select: { id: true } + }); + + if (!business) { + return NextResponse.json({ error: 'Entreprise non trouvée' }, { status: 404 }); + } + + const actualBusinessId = business.id; + // Check if already favorited const existing = await prisma.favorite.findUnique({ where: { userId_businessId: { userId, - businessId + businessId: actualBusinessId } } }); @@ -64,7 +81,7 @@ export async function POST(request: NextRequest) { await prisma.favorite.create({ data: { userId, - businessId + businessId: actualBusinessId } }); return NextResponse.json({ favorited: true }); diff --git a/app/forgot-password/page.tsx b/app/forgot-password/page.tsx index 4ad29a2..7cf65a9 100644 --- a/app/forgot-password/page.tsx +++ b/app/forgot-password/page.tsx @@ -72,7 +72,7 @@ const ForgotPasswordPage = () => { ) : ( -
+ {error && (
diff --git a/app/login/page.tsx b/app/login/page.tsx index a8ac34e..7ae8aa8 100644 --- a/app/login/page.tsx +++ b/app/login/page.tsx @@ -145,7 +145,7 @@ const LoginPage = () => {
)} - +
diff --git a/app/page.tsx b/app/page.tsx index 828b5e5..ebbb2a5 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -11,7 +11,7 @@ import { useUser } from '../components/UserProvider'; const HomePage = () => { const router = useRouter(); - const { settings } = useUser(); + const { user, settings } = useUser(); const [searchTerm, setSearchTerm] = useState(''); const [featuredBusinesses, setFeaturedBusinesses] = useState([]); const [posts, setPosts] = useState([]); @@ -22,7 +22,9 @@ const HomePage = () => { useEffect(() => { const fetchFeatured = async () => { try { - const res = await fetch('/api/businesses?featured=true'); + const res = await fetch('/api/businesses?featured=true', { + headers: user ? { 'x-user-id': user.id } : {} + }); const data = await res.json(); if (Array.isArray(data)) { setFeaturedBusinesses(data.slice(0, 4)); // Show top 4 diff --git a/app/register/page.tsx b/app/register/page.tsx index ece1e84..8c94453 100644 --- a/app/register/page.tsx +++ b/app/register/page.tsx @@ -98,7 +98,7 @@ const RegisterPage = () => {

Redirection vers la page de connexion...

) : ( - + {error && (
diff --git a/app/reset-password/page.tsx b/app/reset-password/page.tsx index 40ef7c6..9f7a261 100644 --- a/app/reset-password/page.tsx +++ b/app/reset-password/page.tsx @@ -95,7 +95,7 @@ const ResetPasswordForm = () => {

Redirection vers la page de connexion...

) : ( - + {error && (