Added implementation for base page and log in log out

This commit is contained in:
fedyna-k
2025-01-14 23:34:40 +01:00
parent 9a97591bda
commit ccad788e19
27 changed files with 374 additions and 479 deletions
View File
+1 -16
View File
@@ -6,22 +6,7 @@ export default function Error404() {
<Head>
<title>404 - Page not found</title>
</Head>
<div class="px-4 py-8 mx-auto bg-[#86efac]">
<div class="max-w-screen-md mx-auto flex flex-col items-center justify-center">
<img
class="my-6"
src="/logo.svg"
width="128"
height="128"
alt="the Fresh logo: a sliced lemon dripping with juice"
/>
<h1 class="text-4xl font-bold">404 - Page not found</h1>
<p class="my-4">
The page you were looking for doesn't exist.
</p>
<a href="/" class="underline">Go back home</a>
</div>
</div>
<p>404</p>
</>
);
}
+24 -7
View File
@@ -1,16 +1,33 @@
import { type PageProps } from "$fresh/server.ts";
export default function App({ Component }: PageProps) {
import { FreshContext } from "$fresh/server.ts";
import { Partial } from "$fresh/runtime.ts";
export default async function App(request: Request, context: FreshContext) {
const link = context.state.isAuthenticated ? "out" : "in";
return (
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>polympr</title>
<link rel="stylesheet" href="/styles.css" />
<title>PolyMPR</title>
<link rel="stylesheet" href="/styles/main.css" />
</head>
<body>
<Component />
<body f-client-nav>
<header>
<h1>PolyMPR</h1>
<nav>
<a href="/modules" f-partial="/partials/modules">Modules</a>
<a href={`/log${link}`} f-client-nav={false}>Log {link}</a>
</nav>
</header>
<Partial name="body">
<context.Component />
</Partial>
<footer>
<p>&copy; 2025 PolyMPR - <a href="/about" f-partial="/partials/about">About</a></p>
</footer>
</body>
</html>
);
}
}
+39
View File
@@ -0,0 +1,39 @@
import { MiddlewareHandlerContext } from "$fresh/server.ts";
import { getCookies } from "$std/http/cookie.ts";
import { isJwtValid } from "@popov/jwt";
const PUBLIC_ROUTES = ["/", "/login", "/logout", "/about", "/contact"];
interface State {
isAuthenticated: boolean;
}
function isRoutePublic(route: string) {
return PUBLIC_ROUTES.includes(route) || route.match(/\..+$/);
}
export const handler = [
async function checkAuthentication(request: Request, context: MiddlewareHandlerContext<State>) {
const cookies = getCookies(request.headers);
context.state.isAuthenticated = await isJwtValid(cookies["sessionToken"] ?? "", "NEED TO CHANGE THIS KEY FURTHER IN DEV");
return context.next();
},
async function ensureAuthentication(request: Request, context: MiddlewareHandlerContext<State>) {
const url = new URL(request.url);
if (!isRoutePublic(url.pathname) && !context.state.isAuthenticated) {
return new Response(null, {
status: 302,
headers: {
Location: "/login"
}
});
}
return context.next();
}
];
-21
View File
@@ -1,21 +0,0 @@
import { FreshContext } from "$fresh/server.ts";
// Jokes courtesy of https://punsandoneliners.com/randomness/programmer-jokes/
const JOKES = [
"Why do Java developers often wear glasses? They can't C#.",
"A SQL query walks into a bar, goes up to two tables and says “can I join you?”",
"Wasn't hard to crack Forrest Gump's password. 1forrest1.",
"I love pressing the F5 key. It's refreshing.",
"Called IT support and a chap from Australia came to fix my network connection. I asked “Do you come from a LAN down under?”",
"There are 10 types of people in the world. Those who understand binary and those who don't.",
"Why are assembly programmers often wet? They work below C level.",
"My favourite computer based band is the Black IPs.",
"What programme do you use to predict the music tastes of former US presidential candidates? An Al Gore Rhythm.",
"An SEO expert walked into a bar, pub, inn, tavern, hostelry, public house.",
];
export const handler = (_req: Request, _ctx: FreshContext): Response => {
const randomIndex = Math.floor(Math.random() * JOKES.length);
const body = JOKES[randomIndex];
return new Response(body);
};
-5
View File
@@ -1,5 +0,0 @@
import { PageProps } from "$fresh/server.ts";
export default function Greet(props: PageProps) {
return <div>Hello {props.params.name}</div>;
}
+1 -18
View File
@@ -2,24 +2,7 @@ import { useSignal } from "@preact/signals";
import Counter from "../islands/Counter.tsx";
export default function Home() {
const count = useSignal(3);
return (
<div class="px-4 py-8 mx-auto bg-[#86efac]">
<div class="max-w-screen-md mx-auto flex flex-col items-center justify-center">
<img
class="my-6"
src="/logo.svg"
width="128"
height="128"
alt="the Fresh logo: a sliced lemon dripping with juice"
/>
<h1 class="text-4xl font-bold">Welcome to Fresh</h1>
<p class="my-4">
Try updating this message in the
<code class="mx-2">./routes/index.tsx</code> file, and refresh.
</p>
<Counter count={count} />
</div>
</div>
<h1>PolyMPR</h1>
);
}
+102
View File
@@ -0,0 +1,102 @@
import { Handlers } from "$fresh/server.ts";
import { State } from "./_middleware.ts";
import { parse, type XmlNode } from "@melvdouc/xml-parser";
import { createJwt } from "@popov/jwt";
import { setCookie } from "$std/http/cookie.ts";
const SERVICE = "https://localhost/login";
const CAS = "https://ident.univ-amu.fr/cas";
function getTag(tag: XmlNode): [string, string] {
return [
tag.tagName.replace("cas:", ""),
tag.children[0].value
];
}
async function createUserJWT(casResponse: XmlNode): Promise<string> {
const nodes = casResponse.children[1].children.map(getTag);
const fullUserInfos = {};
nodes.forEach(([key, value]) => {
if (fullUserInfos[key] && Array.isArray(fullUserInfos[key])) {
fullUserInfos[key].push(value);
}
else if (fullUserInfos[key]) {
fullUserInfos[key] = [fullUserInfos[key], value];
}
else {
fullUserInfos[key] = value;
}
});
const now = Math.floor(Date.now() / 1000);
const oneHour = 60 * 60;
const payload = {
iss: "PolyMPR",
iat: now,
exp: now + oneHour,
aud: "PolyMPR",
user: fullUserInfos
};
const key = "NEED TO CHANGE THIS KEY FURTHER IN DEV";
return createJwt(payload, key);
}
export const handler: Handlers<any, State> = {
async GET(request, context) {
const url = new URL(request.url);
const ticket = url.searchParams.get("ticket");
if (ticket) {
const response = await fetch(`${CAS}/serviceValidate?service=${SERVICE}&ticket=${ticket}`);
const body = parse(await response.text(), "application/xml");
const casResponse = body[0].children[0];
if (casResponse.tagName != "cas:authenticationSuccess") {
return new Response(null, {
status: 302,
headers: {
Location: `${CAS}/login?service=${SERVICE}`
}
});
}
const headers = new Headers();
setCookie(headers, {
name: "sessionToken",
value: await createUserJWT(casResponse)
});
headers.set("Location", "/apps");
return new Response(null, {
status: 302,
headers
});
}
if (context.state.isAuthenticated) {
return new Response(null, {
status: 302,
headers: {
Location: "/apps"
}
});
}
else {
return new Response(null, {
status: 302,
headers: {
Location: `${CAS}/login?service=${SERVICE}`
}
});
}
}
};
+32
View File
@@ -0,0 +1,32 @@
import { Handlers } from "$fresh/server.ts";
import { State } from "./_middleware.ts";
import { deleteCookie } from "$std/http/cookie.ts";
const SERVICE = "https://localhost/";
const CAS = "https://ident.univ-amu.fr/cas";
export const handler: Handlers<any, State> = {
async GET(request, context) {
if (context.state.isAuthenticated) {
const headers = new Headers();
deleteCookie(headers, "sessionToken", { path: "/" });
headers.set("Location", `${CAS}/logout?service=${SERVICE}`);
return new Response(null, {
status: 302,
headers
});
}
else {
return new Response(null, {
status: 302,
headers: {
Location: "/"
}
});
}
}
};
+15
View File
@@ -0,0 +1,15 @@
import { RouteConfig } from "$fresh/server.ts";
import { Partial } from "$fresh/runtime.ts";
export const config: RouteConfig = {
skipAppWrapper: true,
skipInheritedLayouts: true,
};
export default async function About(request, context) {
return (
<Partial name="body">
<p>C'est nous wsh</p>
</Partial>
);
};
View File