100 lines
3.3 KiB
TypeScript
100 lines
3.3 KiB
TypeScript
// src/hooks.server.ts
|
|
import { optima } from "$lib";
|
|
import { redirect, type Handle } from "@sveltejs/kit";
|
|
|
|
export const handle: Handle = async ({ event, resolve }) => {
|
|
const accessToken = event.cookies.get("accessToken") || null;
|
|
const refreshToken = event.cookies.get("refreshToken") || null;
|
|
|
|
if (event.url.pathname === "/logout") {
|
|
event.cookies.delete("accessToken", { path: "/" });
|
|
event.cookies.delete("refreshToken", { path: "/" });
|
|
|
|
return redirect(303, "/login");
|
|
}
|
|
|
|
if (event.url.pathname.startsWith("/login") && optima.user.isLoggedIn()) {
|
|
return redirect(303, "/");
|
|
}
|
|
|
|
if (event.url.pathname.startsWith("/login")) {
|
|
return await resolve(event);
|
|
}
|
|
|
|
if (!accessToken && !refreshToken) {
|
|
optima.user.logout(event);
|
|
redirect(303, "/login");
|
|
}
|
|
|
|
// Check if the access token is expired or near expiry and refresh if needed
|
|
let currentAccessToken = accessToken;
|
|
let currentRefreshToken = refreshToken;
|
|
|
|
if (currentAccessToken) {
|
|
try {
|
|
const [, payload] = currentAccessToken.split(".");
|
|
const decoded = JSON.parse(
|
|
Buffer.from(payload, "base64url").toString("utf8"),
|
|
);
|
|
const nowSec = Math.floor(Date.now() / 1000);
|
|
const thresholdSec = 60; // refresh if < 60s remaining
|
|
|
|
if (!decoded?.exp || decoded.exp - nowSec < thresholdSec) {
|
|
// Token is expired or about to expire — try to refresh
|
|
if (currentRefreshToken) {
|
|
const refreshed =
|
|
await optima.user.refreshSession(currentRefreshToken);
|
|
currentAccessToken = refreshed.accessToken;
|
|
currentRefreshToken = refreshed.refreshToken ?? currentRefreshToken;
|
|
} else {
|
|
// No refresh token available, force re-login
|
|
optima.user.logout(event);
|
|
return redirect(303, "/login");
|
|
}
|
|
}
|
|
} catch {
|
|
// Token is malformed or refresh failed — try refresh as fallback
|
|
if (currentRefreshToken) {
|
|
try {
|
|
const refreshed =
|
|
await optima.user.refreshSession(currentRefreshToken);
|
|
currentAccessToken = refreshed.accessToken;
|
|
currentRefreshToken = refreshed.refreshToken ?? currentRefreshToken;
|
|
} catch {
|
|
// Refresh also failed, force re-login
|
|
optima.user.logout(event);
|
|
return redirect(303, "/login");
|
|
}
|
|
} else {
|
|
optima.user.logout(event);
|
|
return redirect(303, "/login");
|
|
}
|
|
}
|
|
} else if (currentRefreshToken) {
|
|
// No access token but have a refresh token — try to get a new one
|
|
try {
|
|
const refreshed = await optima.user.refreshSession(currentRefreshToken);
|
|
currentAccessToken = refreshed.accessToken;
|
|
currentRefreshToken = refreshed.refreshToken ?? currentRefreshToken;
|
|
} catch {
|
|
optima.user.logout(event);
|
|
return redirect(303, "/login");
|
|
}
|
|
}
|
|
|
|
const setTokens = async (accessToken: string, refreshToken: string) => {
|
|
event.cookies.set("accessToken", accessToken, { path: "/" });
|
|
event.cookies.set("refreshToken", refreshToken, { path: "/" });
|
|
|
|
event.locals.session = { accessToken, refreshToken, set: setTokens };
|
|
|
|
return;
|
|
};
|
|
|
|
// Persist any refreshed tokens into cookies
|
|
await setTokens(currentAccessToken!, currentRefreshToken!);
|
|
|
|
const response = await resolve(event);
|
|
return response;
|
|
};
|