Files
headless-2026-03/wp-content/themes/headless/server/utils/auth.ts
2026-03-27 08:18:18 -04:00

121 lines
3.7 KiB
TypeScript

import { jwtDecode } from "jwt-decode";
import type { AuthPayloadFragment, AuthUserFragment } from "#graphql/types";
import type { H3Event } from "h3";
/**
* Handle user login by setting the session data with the provided authentication information.
*
* @param event The H3 event object.
* @param payload The authentication payload containing user and token information.
* @return A promise that resolves to true if the login was successful, or false if there was an error.
*/
export async function handleLogin(
event: H3Event,
{ user, authToken, refreshToken }: AuthPayloadFragment,
) {
if (!user || !authToken || !refreshToken) {
return false;
}
await setUserSession(event, {
user: getAuthUser(user),
secure: { authToken, refreshToken },
loggedInAt: new Date().toISOString(),
});
return true;
}
/**
* Handle user logout by clearing the session data.
*
* @param event The H3 event object.
* @returns A promise that resolves when the session has been cleared.
*/
export async function handleLogout(event: H3Event) {
await clearUserSession(event);
}
/**
* Convert the AuthUserFragment to a User object expected by nuxt-auth-utils
*
* @param user The AuthUserFragment containing user data from the GraphQL response
* @returns A User object with the expected structure for nuxt-auth-utils, including an array of role names
*/
function getAuthUser({ roles, ...user }: AuthUserFragment) {
return {
...user,
roles: extractNodes(roles).map(({ name }) => name),
};
}
/**
* Retrieve the authentication token from the user's session, checking for expiration and handling token refresh if necessary.
*
* @param event The H3 event object, used to access the user's session data.
* @returns A promise that resolves to the authentication token if it is valid, or undefined if there is no valid token or if the user is not authenticated.
*/
export async function getAuthToken(event: H3Event) {
// Retrieve user session, return if none
const session = await getUserSession(event);
if (!session.secure) {
return;
}
// Extract tokens and check expiration
const decoded = jwtDecode<{ exp: number }>(session.secure.authToken);
const isExpired = decoded.exp * 1000 < Date.now();
if (isExpired) {
try {
const newAuthToken = await refreshAuthToken(session.secure.refreshToken);
if (!newAuthToken) {
throw new Error("Impossible de rafraîchir le jeton d'authentification.");
}
session.secure.authToken = newAuthToken;
await setUserSession(event, session);
} catch {
await clearUserSession(event);
return;
}
}
return session.secure.authToken;
}
// Track in-flight refreshAuthToken calls to prevent duplicate requests
const refreshTokenPromises = new Map<string, Promise<string | undefined>>();
// Refresh auth token by calling remote GraphQL endpoint directly
export async function refreshAuthToken(refreshToken: string): Promise<string | undefined> {
// Return existing in-flight promise if available
const inFlight = refreshTokenPromises.get(refreshToken);
if (inFlight) {
return inFlight;
}
const refreshPromise = (async () => {
const { wpUrl } = useRuntimeConfig();
const endpoint = `${wpUrl}/graphql`;
const { data } = await executeHttpOperation(
{
operationName: "AuthRefreshToken",
variables: { refreshToken },
},
{ endpoint, headers: { Authorization: null } },
);
return data?.refreshToken?.authToken || undefined;
})();
refreshTokenPromises.set(refreshToken, refreshPromise);
return refreshPromise.finally(() => {
const current = refreshTokenPromises.get(refreshToken);
if (current === refreshPromise) {
refreshTokenPromises.delete(refreshToken);
}
});
}