Skip to content

Commit 2b056a0

Browse files
committed
feat: forbidden page
1 parent 68e1b5a commit 2b056a0

File tree

7 files changed

+99
-11
lines changed

7 files changed

+99
-11
lines changed

app/forbidden.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import ForbiddenLayout from "@/components/forbidden-layout/page";
2+
import type { Metadata } from "next";
3+
4+
export const metadata: Metadata = {
5+
title: "帳號尚未開通",
6+
};
7+
8+
export default function ForbiddenPage() {
9+
return <ForbiddenLayout />;
10+
}

app/unauthorized.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { redirect } from "next/navigation";
2+
3+
export default async function UnauthorizedPage() {
4+
redirect("/login");
5+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { Logo } from "@/components/logo";
2+
import { Button } from "@/components/ui/button";
3+
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
4+
import AuthorizedApolloWrapper from "@/providers/use-apollo.rsc";
5+
import { AlertTriangle } from "lucide-react";
6+
import Link from "next/link";
7+
import { Suspense } from "react";
8+
import { UserInfo } from "./user-info";
9+
10+
export default async function ForbiddenLayout() {
11+
return (
12+
<div
13+
className={`
14+
flex min-h-svh flex-col items-center justify-center gap-6
15+
bg-gradient-to-br from-red-50 via-white to-red-100 p-6
16+
md:p-10
17+
`}
18+
>
19+
<Link
20+
href="/"
21+
className={`flex items-center gap-2 self-center font-medium`}
22+
>
23+
<div
24+
className={`
25+
flex size-6 items-center justify-center rounded-md
26+
text-primary-foreground
27+
`}
28+
>
29+
<Logo />
30+
</div>
31+
資料庫練功坊
32+
</Link>
33+
<Card className="min-w-md">
34+
<CardHeader className="flex w-full flex-col items-center text-center">
35+
<AlertTriangle className="mb-2 size-7 text-red-500" />
36+
<CardTitle className="text-xl">無權開啟此頁面</CardTitle>
37+
<CardDescription>
38+
您的帳號尚未開通,無法使用系統。請聯絡管理員開通帳號。
39+
</CardDescription>
40+
</CardHeader>
41+
<CardContent className="flex flex-col items-center gap-4">
42+
<Button asChild variant="outline">
43+
<Link href="/login">重新登入</Link>
44+
</Button>
45+
</CardContent>
46+
<CardFooter
47+
className={`justify-center text-center text-xs text-muted-foreground`}
48+
>
49+
<Suspense>
50+
<AuthorizedApolloWrapper>
51+
<UserInfo />
52+
</AuthorizedApolloWrapper>
53+
</Suspense>
54+
</CardFooter>
55+
</Card>
56+
</div>
57+
);
58+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
"use client";
2+
3+
import useUser from "@/hooks/use-user";
4+
5+
export function UserInfo() {
6+
const { user } = useUser();
7+
8+
return (
9+
<section className="flex flex-col items-center gap-1">
10+
<p>
11+
您目前登入的帳號是:{user?.name} ({user?.email})
12+
</p>
13+
<p>如果這不是您想登入的帳號,請切換 Google 帳號後重新登入</p>
14+
</section>
15+
);
16+
}

lib/auth.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -250,12 +250,5 @@ export async function getAuthStatus(token: string): Promise<AuthStatus> {
250250
};
251251
}
252252

253-
if (parsedData.data.scope.includes("*")) {
254-
return {
255-
loggedIn: true,
256-
introspectResult: parsedData.data,
257-
};
258-
}
259-
260253
return { loggedIn: true, introspectResult: parsedData.data };
261254
}

next.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const nextConfig: NextConfig = {
1313
],
1414
],
1515
ppr: "incremental",
16+
authInterrupts: true,
1617
},
1718
async rewrites() {
1819
return [

providers/use-protected-route.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,23 @@
11
"use server";
22

33
import { getAuthStatus, getAuthToken } from "@/lib/auth";
4-
import { redirect, unauthorized } from "next/navigation";
4+
import { forbidden, unauthorized } from "next/navigation";
55

66
export default async function ProtectedRoute({ children }: { children: React.ReactNode }) {
77
const token = await getAuthToken();
88
if (!token) {
9-
redirect("/login");
9+
unauthorized();
1010
}
1111

12-
const { loggedIn } = await getAuthStatus(token);
13-
if (!loggedIn) {
12+
const { loggedIn, introspectResult } = await getAuthStatus(token);
13+
if (!loggedIn || !introspectResult?.active) {
1414
unauthorized();
1515
}
1616

17+
// requires for verification
18+
if (introspectResult.scope.includes("unverified")) {
19+
forbidden();
20+
}
21+
1722
return children;
1823
}

0 commit comments

Comments
 (0)