Merge pull request #28 from damjan1996/auto-claude/044-implement-server-side-route-protection-for-dashboa
auto-claude: 044-implement-server-side-route-protection-for-dashboa
This commit is contained in:
@@ -1,6 +1,12 @@
|
|||||||
|
import { connection } from 'next/server';
|
||||||
import { setRequestLocale } from 'next-intl/server';
|
import { setRequestLocale } from 'next-intl/server';
|
||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
|
import { redirect } from 'next/navigation';
|
||||||
import DashboardContent from '@/components/auth/DashboardContent';
|
import DashboardContent from '@/components/auth/DashboardContent';
|
||||||
|
import { createClient } from '@/lib/supabase/server';
|
||||||
|
|
||||||
|
// Force dynamic rendering to ensure auth checks run on every request
|
||||||
|
export const dynamic = 'force-dynamic';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
params: Promise<{ locale: string }>;
|
params: Promise<{ locale: string }>;
|
||||||
@@ -29,8 +35,19 @@ export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default async function DashboardPage({ params }: Props) {
|
export default async function DashboardPage({ params }: Props) {
|
||||||
|
// CRITICAL: Use connection() API to guarantee dynamic rendering in Next.js 15
|
||||||
|
await connection();
|
||||||
|
|
||||||
const { locale } = await params;
|
const { locale } = await params;
|
||||||
setRequestLocale(locale);
|
setRequestLocale(locale);
|
||||||
|
|
||||||
|
// Server-side auth check (defense-in-depth)
|
||||||
|
const supabase = await createClient();
|
||||||
|
const { data: { user } } = await supabase.auth.getUser();
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
redirect(`/${locale}/login`);
|
||||||
|
}
|
||||||
|
|
||||||
return <DashboardContent />;
|
return <DashboardContent />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { Loader2, BarChart2, Users, Eye, Clock, TrendingUp, TrendingDown, LogOut } from 'lucide-react';
|
import { BarChart2, Users, Eye, Clock, TrendingUp, TrendingDown, LogOut } from 'lucide-react';
|
||||||
import { useTranslations, useLocale } from 'next-intl';
|
import { useTranslations, useLocale } from 'next-intl';
|
||||||
import { createClient } from '@/lib/supabase/client';
|
import { createClient } from '@/lib/supabase/client';
|
||||||
import type { User } from '@supabase/supabase-js';
|
|
||||||
|
|
||||||
interface MetricCard {
|
interface MetricCard {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -20,6 +18,8 @@ export default function DashboardContent() {
|
|||||||
const t = useTranslations('dashboard');
|
const t = useTranslations('dashboard');
|
||||||
const locale = useLocale();
|
const locale = useLocale();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
<<<<<<< HEAD
|
||||||
|
=======
|
||||||
const [user, setUser] = useState<User | null>(null);
|
const [user, setUser] = useState<User | null>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
@@ -34,6 +34,7 @@ export default function DashboardContent() {
|
|||||||
|
|
||||||
fetchUser();
|
fetchUser();
|
||||||
}, []);
|
}, []);
|
||||||
|
>>>>>>> origin/master
|
||||||
|
|
||||||
const handleLogout = async () => {
|
const handleLogout = async () => {
|
||||||
const supabase = createClient();
|
const supabase = createClient();
|
||||||
@@ -41,25 +42,6 @@ export default function DashboardContent() {
|
|||||||
router.push(`/${locale}`);
|
router.push(`/${locale}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return (
|
|
||||||
<div className="min-h-screen flex items-center justify-center">
|
|
||||||
<div className="flex flex-col items-center space-y-4">
|
|
||||||
<Loader2 className="h-8 w-8 text-zinc-400 animate-spin" />
|
|
||||||
<p className="text-zinc-400 text-sm">{t('status.loading')}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
return (
|
|
||||||
<div className="min-h-screen flex items-center justify-center">
|
|
||||||
<p className="text-zinc-400">{t('status.notAuthenticated')}</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const metrics: MetricCard[] = [
|
const metrics: MetricCard[] = [
|
||||||
{
|
{
|
||||||
title: t('metrics.pageViews.title'),
|
title: t('metrics.pageViews.title'),
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import { createServerClient } from '@supabase/ssr';
|
import { createServerClient } from '@supabase/ssr';
|
||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
|
export function createClient(request: NextRequest, response: NextResponse) {
|
||||||
|
return createServerClient(
|
||||||
|
=======
|
||||||
export async function createClient(request: NextRequest) {
|
export async function createClient(request: NextRequest) {
|
||||||
let response = NextResponse.next({
|
let response = NextResponse.next({
|
||||||
request: {
|
request: {
|
||||||
@@ -9,6 +13,7 @@ export async function createClient(request: NextRequest) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const supabase = createServerClient(
|
const supabase = createServerClient(
|
||||||
|
>>>>>>> origin/master
|
||||||
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
||||||
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
|
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
|
||||||
{
|
{
|
||||||
@@ -17,6 +22,15 @@ export async function createClient(request: NextRequest) {
|
|||||||
return request.cookies.getAll();
|
return request.cookies.getAll();
|
||||||
},
|
},
|
||||||
setAll(cookiesToSet) {
|
setAll(cookiesToSet) {
|
||||||
|
<<<<<<< HEAD
|
||||||
|
try {
|
||||||
|
cookiesToSet.forEach(({ name, value, options }) => {
|
||||||
|
response.cookies.set(name, value, options);
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
// Handle middleware context
|
||||||
|
}
|
||||||
|
=======
|
||||||
cookiesToSet.forEach(({ name, value, options }) =>
|
cookiesToSet.forEach(({ name, value, options }) =>
|
||||||
request.cookies.set(name, value)
|
request.cookies.set(name, value)
|
||||||
);
|
);
|
||||||
@@ -28,10 +42,14 @@ export async function createClient(request: NextRequest) {
|
|||||||
cookiesToSet.forEach(({ name, value, options }) =>
|
cookiesToSet.forEach(({ name, value, options }) =>
|
||||||
response.cookies.set(name, value, options)
|
response.cookies.set(name, value, options)
|
||||||
);
|
);
|
||||||
|
>>>>>>> origin/master
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
<<<<<<< HEAD
|
||||||
|
=======
|
||||||
|
|
||||||
return { supabase, response };
|
return { supabase, response };
|
||||||
|
>>>>>>> origin/master
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user