diff --git a/app/forbidden.tsx b/app/forbidden.tsx
new file mode 100644
index 0000000..b6e9514
--- /dev/null
+++ b/app/forbidden.tsx
@@ -0,0 +1,10 @@
+import ForbiddenLayout from "@/components/forbidden-layout/page";
+import type { Metadata } from "next";
+
+export const metadata: Metadata = {
+ title: "帳號尚未開通",
+};
+
+export default function ForbiddenPage() {
+ return ;
+}
diff --git a/app/unauthorized.tsx b/app/unauthorized.tsx
new file mode 100644
index 0000000..b3f0f28
--- /dev/null
+++ b/app/unauthorized.tsx
@@ -0,0 +1,5 @@
+import { redirect } from "next/navigation";
+
+export default async function UnauthorizedPage() {
+ redirect("/login");
+}
diff --git a/components/forbidden-layout/page.tsx b/components/forbidden-layout/page.tsx
new file mode 100644
index 0000000..a3358ba
--- /dev/null
+++ b/components/forbidden-layout/page.tsx
@@ -0,0 +1,58 @@
+import { Logo } from "@/components/logo";
+import { Button } from "@/components/ui/button";
+import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
+import AuthorizedApolloWrapper from "@/providers/use-apollo.rsc";
+import { AlertTriangle } from "lucide-react";
+import Link from "next/link";
+import { Suspense } from "react";
+import { UserInfo } from "./user-info";
+
+export default async function ForbiddenLayout() {
+ return (
+
+
+
+
+
+ 資料庫練功坊
+
+
+
+
+ 無權開啟此頁面
+
+ 您的帳號尚未開通,無法使用系統。請聯絡管理員開通帳號。
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/components/forbidden-layout/user-info.tsx b/components/forbidden-layout/user-info.tsx
new file mode 100644
index 0000000..82b5915
--- /dev/null
+++ b/components/forbidden-layout/user-info.tsx
@@ -0,0 +1,16 @@
+"use client";
+
+import useUser from "@/hooks/use-user";
+
+export function UserInfo() {
+ const { user } = useUser();
+
+ return (
+
+
+ 您目前登入的帳號是:{user?.name} ({user?.email})
+
+ 如果這不是您想登入的帳號,請切換 Google 帳號後重新登入
+
+ );
+}
diff --git a/lib/auth.ts b/lib/auth.ts
index 4a02159..8836860 100644
--- a/lib/auth.ts
+++ b/lib/auth.ts
@@ -250,12 +250,5 @@ export async function getAuthStatus(token: string): Promise {
};
}
- if (parsedData.data.scope.includes("*")) {
- return {
- loggedIn: true,
- introspectResult: parsedData.data,
- };
- }
-
return { loggedIn: true, introspectResult: parsedData.data };
}
diff --git a/next.config.ts b/next.config.ts
index 4293b29..0b69c3e 100644
--- a/next.config.ts
+++ b/next.config.ts
@@ -13,6 +13,7 @@ const nextConfig: NextConfig = {
],
],
ppr: "incremental",
+ authInterrupts: true,
},
async rewrites() {
return [
diff --git a/providers/use-protected-route.tsx b/providers/use-protected-route.tsx
index 9b6489a..d1eef61 100644
--- a/providers/use-protected-route.tsx
+++ b/providers/use-protected-route.tsx
@@ -1,18 +1,23 @@
"use server";
import { getAuthStatus, getAuthToken } from "@/lib/auth";
-import { redirect, unauthorized } from "next/navigation";
+import { forbidden, unauthorized } from "next/navigation";
export default async function ProtectedRoute({ children }: { children: React.ReactNode }) {
const token = await getAuthToken();
if (!token) {
- redirect("/login");
+ unauthorized();
}
- const { loggedIn } = await getAuthStatus(token);
- if (!loggedIn) {
+ const { loggedIn, introspectResult } = await getAuthStatus(token);
+ if (!loggedIn || !introspectResult?.active) {
unauthorized();
}
+ // requires for verification
+ if (introspectResult.scope.includes("unverified")) {
+ forbidden();
+ }
+
return children;
}