Files
PolyMPR/routes/_middleware.ts
T
anys dc0af96470 Refactor AuthenticatedState to store displayName and uid
The AuthenticatedState interface was updated to directly store the
`displayName` and `uid` properties. Previously, it stored the entire
`CasContent` object, which contained these properties along with others
that were not consistently used. This change simplifies the interface
and reduces redundancy.
2026-01-08 20:04:17 +01:00

133 lines
3.8 KiB
TypeScript

import { FreshContext, MiddlewareHandler } from "$fresh/server.ts";
import { getCookies } from "$std/http/cookie.ts";
import { getJwtPayload, isJwtValid } from "@popov/jwt";
import { CasContent, LoginJWT, State } from "$root/defaults/interfaces.ts";
const PUBLIC_ROUTES = [
"/",
"/login",
"/logout",
"/about",
"/partials/about",
"/contact",
];
const jwtKeyCache: Record<string, string> = {};
const deleteKey = (user: string) => delete jwtKeyCache[user];
/**
* Checks if the given route is public.
* @param route The route to check.
* @returns `true` if the route is public, `false` otherwise.
*/
function isRoutePublic(route: string): boolean {
return PUBLIC_ROUTES.includes(route) ||
!!(route.match(/\..+$/)?.[0] ?? false);
}
/**
* Get the given user's key, creating it if not already existing.
* @param user The key's user.
* @returns The user's key.
*/
export function getKey(user: string): string {
if (!jwtKeyCache[user]) {
const keyBuffer = new Uint8Array(32);
crypto.getRandomValues(keyBuffer);
jwtKeyCache[user] = new TextDecoder().decode(keyBuffer);
setTimeout(deleteKey, 0x75300, user);
}
return jwtKeyCache[user];
}
export const handler: MiddlewareHandler<State>[] = [
/**
* Check if user is authenticated and add session to context accordingly.
* Only authenticated users who are members of Polytech are allowed.
* @param request The HTTP incomming request.
* @param context The Fresh context object with custom `State`.
* @returns The response from the next middleware.
*/
async function checkAuthentication(
request: Request,
context: FreshContext<State>,
): Promise<Response> {
const cookies = getCookies(request.headers);
if (!cookies["sessionToken"]) {
context.state.isAuthenticated = false;
context.state.isFromPolytech = false;
return await context.next();
}
const content = getJwtPayload(cookies["sessionToken"]) as LoginJWT;
const key = getKey(content.user.uid);
context.state.isAuthenticated = await isJwtValid(
cookies["sessionToken"],
key,
);
if (context.state.isAuthenticated) {
const session: CasContent =
(getJwtPayload(cookies["sessionToken"]) as LoginJWT).user;
const isFromPolytech = session.amuComposante.includes("polytech");
context.state.isFromPolytech = isFromPolytech;
if (isFromPolytech) {
context.state.displayName = session.displayName;
context.state.uid = session.uid;
if (session.eduPersonPrimaryAffiliation == "faculty") {
context.state.role = "professeur"
} else if (session.eduPersonPrimaryAffiliation == "employee") {
context.state.role = "administration"
} else if (session.eduPersonPrimaryAffiliation == "student") {
context.state.role = "etudiant";
} else {
context.state.role = "autre";
}
}
}
return await context.next();
},
/**
* Check if page can be accessed with or without authentication.
* Redirect if the page is private and the user isn't authenticated.
* @param request The HTTP incomming request.
* @param context The Fresh context object with `State` set up.
* @returns The response from the next middleware or from the page.
*/
async function ensureAuthentication(
request: Request,
context: FreshContext<State>,
): Promise<Response> {
const url = new URL(request.url);
if (!isRoutePublic(url.pathname)) {
if (!context.state.isAuthenticated) {
return new Response(null, {
status: 302,
headers: {
Location: "/login",
},
});
}
if (!context.state.isFromPolytech) {
return new Response(null, {
status: 403,
headers: {
Location: "/403",
},
});
}
}
return await context.next();
},
];