Compare commits

..
12 changed files with 162 additions and 27 deletions

View file

@ -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 (
<div>
{children}
</div>
)
return <ProtectedRoute>{children}</ProtectedRoute>;
}

View file

@ -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 <UnderConstruction />;
}

View file

@ -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 (
<div>
<form action={createAccount ? login : signup}>
<div className="flex h-full w-full justify-center py-10">
<Form
action={createAccount ? login : signup}
submitText={createAccount ? "Login" : "Signup"}
>
<input type="text" name="username" placeholder="Username" required />
<input
type="password"
@ -15,11 +19,12 @@ export default function Login() {
placeholder="Password"
required
/>
<button type="submit">{createAccount ? "Login" : "Signup"}</button>
</form>
<button onClick={() => setCreateAccount(!createAccount)}>
{createAccount ? "Don't have an account?" : "Already have an account?"}
{createAccount
? "Don't have an account?"
: "Already have an account?"}
</button>
</Form>
</div>
);
}

View file

@ -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();

View file

@ -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();

View file

@ -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", {

25
components/ui/Form.tsx Normal file
View file

@ -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<void>);
children: React.ReactNode;
submitText: string;
}) {
return (
<div>
<form action={action}>
<RoundedContainer slim={false}>
{children}
<button type="submit">{submitText}</button>
</RoundedContainer>
</form>
</div>
);
}

View file

@ -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 <div>{children}</div>;
}

View file

@ -14,3 +14,18 @@ export async function cmFetch<T>(
return response.json();
}
export async function cmFetchRaw(
bffUrl: string | URL,
options?: RequestInit,
ignoreStatuses?: number[],
): Promise<Response> {
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;
}

View file

@ -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<User[]>(`/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<User>(`/api/users/${id}`);
}
export async function loginUser(user: FormData) {
const body = {
username: user.get("username"),
password: user.get("password"),
};
return cmFetch<Token>(`/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<Token>(`/api/users`, {
method: "POST",
body: user,
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
}

View file

@ -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,
};
});

View file

@ -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;
}