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(user: AuthUserFragment) { return { ...user, roles: user.roles.nodes.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>(); // Refresh auth token by calling remote GraphQL endpoint directly export async function refreshAuthToken(refreshToken: string): Promise { // 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); } }); }