Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions app/forbidden.tsx
Original file line number Diff line number Diff line change
@@ -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 <ForbiddenLayout />;
}
5 changes: 5 additions & 0 deletions app/unauthorized.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { redirect } from "next/navigation";

export default async function UnauthorizedPage() {
redirect("/login");
}
58 changes: 58 additions & 0 deletions components/forbidden-layout/page.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div
className={`
flex min-h-svh flex-col items-center justify-center gap-6
bg-gradient-to-br from-red-50 via-white to-red-100 p-6
md:p-10
`}
>
<Link
href="/"
className={`flex items-center gap-2 self-center font-medium`}
>
<div
className={`
flex size-6 items-center justify-center rounded-md
text-primary-foreground
`}
>
<Logo />
</div>
資料庫練功坊
</Link>
<Card className="min-w-md">
<CardHeader className="flex w-full flex-col items-center text-center">
<AlertTriangle className="mb-2 size-7 text-red-500" />
<CardTitle className="text-xl">無權開啟此頁面</CardTitle>
<CardDescription>
您的帳號尚未開通,無法使用系統。請聯絡管理員開通帳號。
</CardDescription>
</CardHeader>
<CardContent className="flex flex-col items-center gap-4">
<Button asChild variant="outline">
<Link href="/login">重新登入</Link>
</Button>
</CardContent>
<CardFooter
className={`justify-center text-center text-xs text-muted-foreground`}
>
<Suspense>
<AuthorizedApolloWrapper>
<UserInfo />
</AuthorizedApolloWrapper>
</Suspense>
</CardFooter>
</Card>
</div>
);
}
16 changes: 16 additions & 0 deletions components/forbidden-layout/user-info.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"use client";

import useUser from "@/hooks/use-user";

export function UserInfo() {
const { user } = useUser();

return (
<section className="flex flex-col items-center gap-1">
<p>
您目前登入的帳號是:{user?.name} ({user?.email})
</p>
<p>如果這不是您想登入的帳號,請切換 Google 帳號後重新登入</p>
</section>
);
}
7 changes: 0 additions & 7 deletions lib/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,12 +250,5 @@ export async function getAuthStatus(token: string): Promise<AuthStatus> {
};
}

if (parsedData.data.scope.includes("*")) {
return {
loggedIn: true,
introspectResult: parsedData.data,
};
}

return { loggedIn: true, introspectResult: parsedData.data };
}
1 change: 1 addition & 0 deletions next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const nextConfig: NextConfig = {
],
],
ppr: "incremental",
authInterrupts: true,
},
async rewrites() {
return [
Expand Down
13 changes: 9 additions & 4 deletions providers/use-protected-route.tsx
Original file line number Diff line number Diff line change
@@ -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;
}