diff --git a/app/Admin/layout.tsx b/app/Admin/layout.tsx index 696a445..a59f214 100644 --- a/app/Admin/layout.tsx +++ b/app/Admin/layout.tsx @@ -1,11 +1,13 @@ import React from "react"; -export default function AdminLayout({children}: {children: React.ReactNode}) { +import ProtectedRoute from "@/components/wrappers/ProtectedRoute"; + +export default async function AdminLayout({ + children, +}: { + children: React.ReactNode; +}) { // TODO: Put user state management here //redirect("wherever state says") <= stateRouteManager() - return ( -
- {children} -
- ) - } \ No newline at end of file + return {children}; +} diff --git a/app/Admin/page.tsx b/app/Admin/page.tsx index c19c6e1..05169a4 100644 --- a/app/Admin/page.tsx +++ b/app/Admin/page.tsx @@ -1,6 +1,5 @@ -import { redirect } from "next/navigation"; +import UnderConstruction from "@/components/static_pages/UnderConstruction"; export default function Admin() { - // TODO: Replace with Layout State Manager - redirect("/Admin/Login"); + return ; } diff --git a/app/Admin/Login/page.tsx b/app/Authentication/Login/page.tsx similarity index 50% rename from app/Admin/Login/page.tsx rename to app/Authentication/Login/page.tsx index c176301..89264a1 100644 --- a/app/Admin/Login/page.tsx +++ b/app/Authentication/Login/page.tsx @@ -2,12 +2,16 @@ import { useState } from "react"; import { login, signup } from "@/lib/server_functions/userAuth"; +import Form from "@/components/ui/Form"; export default function Login() { const [createAccount, setCreateAccount] = useState(false); return ( -
-
+
+ - - - + +
); } diff --git a/app/api/users/login/route.ts b/app/api/users/login/route.ts index 4270a0b..99226cf 100644 --- a/app/api/users/login/route.ts +++ b/app/api/users/login/route.ts @@ -1,7 +1,12 @@ const BACKEND_URL = process.env.BACKEND_URL; export async function POST(request: Request) { - const response = await fetch(`${BACKEND_URL}/users/auth`, request); + const response = await fetch(`${BACKEND_URL}/users/auth`, { + method: "POST", + headers: request.headers, + body: request.body, + duplex: "half", + }); const data = await response.json(); diff --git a/app/api/users/route.ts b/app/api/users/route.ts index b961291..399e135 100644 --- a/app/api/users/route.ts +++ b/app/api/users/route.ts @@ -15,7 +15,12 @@ export async function GET() { } export async function POST(request: Request) { - const response = await fetch(`${BACKEND_URL}/users`, request); + const response = await fetch(`${BACKEND_URL}/users`, { + method: "POST", + headers: request.headers, + body: request.body, + duplex: "half", + }); const data = await response.json(); diff --git a/app/api/users/user/route.ts b/app/api/users/user/route.ts index 9f0e902..6969a22 100644 --- a/app/api/users/user/route.ts +++ b/app/api/users/user/route.ts @@ -1,7 +1,13 @@ const BACKEND_URL = process.env.BACKEND_URL; -export async function GET() { - const response = await fetch(`${BACKEND_URL}/users/user`); +export async function GET(request: Request) { + const authorization = request.headers.get("authorization"); + + const response = await fetch(`${BACKEND_URL}/users/user`, { + headers: { + Authorization: authorization ?? "", + }, + }); if (!response.ok) { return new Response("Request failed in backend", { diff --git a/components/ui/Form.tsx b/components/ui/Form.tsx new file mode 100644 index 0000000..7f0e323 --- /dev/null +++ b/components/ui/Form.tsx @@ -0,0 +1,25 @@ +import React from "react"; + +import RoundedContainer from "./RoundedContainer"; + +export default function Form({ + action, + children, + submitText, +}: { + // eslint-disable-next-line no-unused-vars + action?: string | ((formData: FormData) => void | Promise); + children: React.ReactNode; + submitText: string; +}) { + return ( +
+
+ + {children} + + +
+
+ ); +} diff --git a/components/wrappers/ProtectedRoute.tsx b/components/wrappers/ProtectedRoute.tsx new file mode 100644 index 0000000..2c75bbc --- /dev/null +++ b/components/wrappers/ProtectedRoute.tsx @@ -0,0 +1,17 @@ +import React from "react"; +import { redirect } from "next/navigation"; + +import { verifySession } from "@/lib/server_functions/dataAccessLayer"; + +export default async function ProtectedRoute({ + children, +}: { + children: React.ReactNode; +}) { + const session = await verifySession(); + if (!session.isAuthed) { + redirect("/Authentication/Login"); + } + + return
{children}
; +} diff --git a/lib/api/cmFetch.ts b/lib/api/cmFetch.ts index 544b23d..79446c4 100644 --- a/lib/api/cmFetch.ts +++ b/lib/api/cmFetch.ts @@ -14,3 +14,18 @@ export async function cmFetch( return response.json(); } + +export async function cmFetchRaw( + bffUrl: string | URL, + options?: RequestInit, + ignoreStatuses?: number[], +): Promise { + const response = await fetch(`${FRONTEND_URL}/${bffUrl}`, options); + + if (!response.ok && !ignoreStatuses?.includes(response.status)) { + throw new Error("Request failed"); + } + console.log(response); + + return response; +} diff --git a/lib/api/helpers/users.ts b/lib/api/helpers/users.ts index 1c8dc9d..fc925f4 100644 --- a/lib/api/helpers/users.ts +++ b/lib/api/helpers/users.ts @@ -1,24 +1,49 @@ -import { cmFetch } from "../cmFetch"; +import { getSessionToken } from "@/lib/server_functions/userAuth"; + +import { cmFetch, cmFetchRaw } from "../cmFetch"; import { Token, User } from "../types"; export async function getUsers() { return cmFetch(`/api/users`); } +export async function getCurrentUser() { + return cmFetchRaw( + `/api/users/user`, + { headers: { Authorization: `Bearer ${await getSessionToken()}` } }, + [401], + ); +} + export async function getUser(id: number) { return cmFetch(`/api/users/${id}`); } export async function loginUser(user: FormData) { + const body = { + username: user.get("username"), + password: user.get("password"), + }; return cmFetch(`/api/users/login`, { method: "POST", - body: user, + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(body), }); } export async function createUser(user: FormData) { + const body = { + username: user.get("username"), + password: user.get("password"), + authority: 0, + }; return cmFetch(`/api/users`, { method: "POST", - body: user, + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(body), }); } diff --git a/lib/server_functions/dataAccessLayer.ts b/lib/server_functions/dataAccessLayer.ts new file mode 100644 index 0000000..98af205 --- /dev/null +++ b/lib/server_functions/dataAccessLayer.ts @@ -0,0 +1,23 @@ +import { cookies } from "next/headers"; +import { cache } from "react"; + +import "server-only"; +import { getCurrentUser } from "../api/helpers/users"; +import { User } from "../api/types"; + +export const verifySession = cache(async () => { + const cookie = (await cookies()).get("access_token")?.value; + if (!cookie) { + return { isAuthed: false }; + } + const response = await getCurrentUser(); + if (response.status == 401) { + return { isAuthed: false }; + } + const user = (await response.json()) as User; + return { + isAuthed: true, + surface_auth_level: user.authority, + username: user.username, + }; +}); diff --git a/lib/server_functions/userAuth.ts b/lib/server_functions/userAuth.ts index 3871280..538c665 100644 --- a/lib/server_functions/userAuth.ts +++ b/lib/server_functions/userAuth.ts @@ -9,7 +9,7 @@ export async function login(formData: FormData) { "use server"; const token = await loginUser(formData); await createSession(token); - redirect("/admin/overview"); + redirect("/Admin/Overview"); } export async function signup(formData: FormData) { @@ -17,7 +17,7 @@ export async function signup(formData: FormData) { "use server"; const token = await createUser(formData); await createSession(token); - redirect("/admin/overview"); + redirect("/Admin/Overview"); } export async function createSession(token: Token) { @@ -26,8 +26,16 @@ export async function createSession(token: Token) { cookieStore.set("access_token", access_token, { httpOnly: true, - secure: true, + secure: process.env.NODE_ENV === "production", sameSite: "lax", path: "/", }); } + +export async function getSessionToken() { + const cookieStore = await cookies(); + console.log(cookieStore.getAll()); + const token = cookieStore.get("access_token")?.value; + + return token ? token : null; +}