148 lines
4.0 KiB
TypeScript
148 lines
4.0 KiB
TypeScript
import { redirect } from "@remix-run/node";
|
|
import {
|
|
isAccessTokenExpired,
|
|
isRefreshTokenExpired,
|
|
sessionStorage,
|
|
} from "~/sessions.server";
|
|
import { ApiApi, Configuration } from "../../src/api/types";
|
|
import { getInternalApiUrl } from "~/utils/api-config";
|
|
|
|
export interface User {
|
|
access_token: string;
|
|
refresh_token: string;
|
|
access_token_timestamp: number;
|
|
refresh_token_timestamp: number;
|
|
}
|
|
/**
|
|
* Token manager for handling access and refresh tokens
|
|
* - access_token expires after 1 week
|
|
* - refresh_token expires after 1 month
|
|
*/
|
|
const protectedRoutes = [
|
|
"/profile/bookmarks",
|
|
"/profile/following",
|
|
"/profile/threads",
|
|
"/profile/wallet",
|
|
"/profile/location",
|
|
"/profile/location/add",
|
|
"/profile/location/edit",
|
|
"/profile/orders",
|
|
"/profile/setting",
|
|
"/cart",
|
|
"/store",
|
|
];
|
|
export async function validateTokens(request: Request) {
|
|
const session = await sessionStorage.getSession(
|
|
request.headers.get("cookie"),
|
|
);
|
|
const user: User | null = session.get("user");
|
|
const isProtectedRoute = protectedRoutes.some((route) =>
|
|
request.url.includes(route),
|
|
);
|
|
// If no user data found, redirect to login
|
|
if (!user || !user.access_token) {
|
|
if (isProtectedRoute) {
|
|
throw redirect("/login", {
|
|
headers: {
|
|
"Set-Cookie": await sessionStorage.destroySession(session),
|
|
},
|
|
});
|
|
} else return { user: null };
|
|
}
|
|
// Check if refresh_token is expired
|
|
if (
|
|
user &&
|
|
user.refresh_token_timestamp &&
|
|
isRefreshTokenExpired(user.refresh_token_timestamp)
|
|
) {
|
|
// If refresh token is expired, clear session and redirect to login
|
|
if (isProtectedRoute) {
|
|
throw redirect("/login", {
|
|
headers: {
|
|
"Set-Cookie": await sessionStorage.destroySession(session),
|
|
},
|
|
});
|
|
} else return { user: null };
|
|
}
|
|
|
|
// Check if access_token is expired but refresh_token is still valid
|
|
if (
|
|
user &&
|
|
user.access_token_timestamp &&
|
|
isAccessTokenExpired(user.access_token_timestamp)
|
|
) {
|
|
try {
|
|
// Try to refresh the access token using the refresh token
|
|
const newTokens = await refreshAccessToken(user.refresh_token);
|
|
// Update session with new tokens and new timestamps
|
|
const currentTime = Date.now();
|
|
session.set("user", {
|
|
...user,
|
|
access_token: newTokens.access,
|
|
refresh_token: newTokens.refresh,
|
|
access_token_timestamp: currentTime,
|
|
refresh_token_timestamp: currentTime,
|
|
});
|
|
|
|
// Return the updated session and new tokens
|
|
return {
|
|
user: session.get("user"),
|
|
headers: {
|
|
"Set-Cookie": await sessionStorage.commitSession(session, {
|
|
expires: new Date(new Date().setMonth(new Date().getMonth() + 1)),
|
|
}),
|
|
},
|
|
};
|
|
} catch (error) {
|
|
// If refresh fails, redirect to login
|
|
console.error("Token refresh failed:", error);
|
|
if (isProtectedRoute) {
|
|
throw redirect("/login", {
|
|
headers: {
|
|
"Set-Cookie": await sessionStorage.destroySession(session),
|
|
},
|
|
});
|
|
} else return { user: null };
|
|
}
|
|
}
|
|
if (request.url.includes("/login")) throw redirect("/");
|
|
|
|
// If tokens are valid, return user data
|
|
return { user };
|
|
}
|
|
|
|
/**
|
|
* Function to refresh the access token using the refresh token
|
|
* This calls the API endpoint that handles token refresh
|
|
*/
|
|
async function refreshAccessToken(refreshToken: string) {
|
|
try {
|
|
const basePath = getInternalApiUrl();
|
|
const config = new Configuration({
|
|
basePath,
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
accept: "application/json",
|
|
},
|
|
});
|
|
|
|
const apiClient = new ApiApi(config);
|
|
|
|
const response = await apiClient.apiUserV1TokenRefreshCreate({
|
|
data: {
|
|
refresh: refreshToken,
|
|
},
|
|
});
|
|
|
|
// Validate the response data
|
|
if (!response.access || !response.refresh) {
|
|
throw new Error("Invalid refresh token response");
|
|
}
|
|
|
|
return response;
|
|
} catch (error) {
|
|
console.error("Error refreshing token:", error);
|
|
throw error;
|
|
}
|
|
}
|