64 lines
1.7 KiB
TypeScript
64 lines
1.7 KiB
TypeScript
import { redirect } from "react-router";
|
|
|
|
interface AuthLoaderData {
|
|
readonly token: string | null;
|
|
}
|
|
export const loadAuthToken = async (
|
|
request: Request
|
|
): Promise<AuthLoaderData> => {
|
|
const cookie = request.headers.get("cookie");
|
|
if (!cookie) {
|
|
return { token: null };
|
|
}
|
|
const tokenUrl = new URL(
|
|
"/api/auth/convex/token",
|
|
new URL(request.url).origin
|
|
);
|
|
|
|
const headers = new Headers({ cookie });
|
|
const response = await fetch(tokenUrl, { headers });
|
|
if (response.status === 401) {
|
|
return { token: null };
|
|
}
|
|
if (!response.ok) {
|
|
throw new Response("Authentication service unavailable", { status: 503 });
|
|
}
|
|
|
|
const payload: unknown = await response.json();
|
|
if (
|
|
typeof payload !== "object" ||
|
|
payload === null ||
|
|
!("token" in payload) ||
|
|
typeof payload.token !== "string"
|
|
) {
|
|
throw new Response("Authentication service returned an invalid response", {
|
|
status: 502,
|
|
});
|
|
}
|
|
return { token: payload.token };
|
|
};
|
|
|
|
export const requireAuthToken = async (
|
|
request: Request
|
|
): Promise<AuthLoaderData> => {
|
|
const auth = await loadAuthToken(request);
|
|
if (!auth.token) {
|
|
const requestUrl = new URL(request.url);
|
|
const returnTo = `${requestUrl.pathname}${requestUrl.search}`;
|
|
throw redirect(`/login?returnTo=${encodeURIComponent(returnTo)}`);
|
|
}
|
|
return auth;
|
|
};
|
|
|
|
export const redirectAuthenticated = async (
|
|
request: Request
|
|
): Promise<AuthLoaderData> => {
|
|
const auth = await loadAuthToken(request);
|
|
if (auth.token) {
|
|
const requestUrl = new URL(request.url);
|
|
const returnTo = requestUrl.searchParams.get("returnTo");
|
|
throw redirect(returnTo?.startsWith("/") ? returnTo : "/");
|
|
}
|
|
return auth;
|
|
};
|