- Add server-side auth token loader (auth.server.ts) with SSR cookie forwarding - Add project-requests client for explicit/signal/existing issue dispatch - Refactor auth layout and app layout for SSR auth gating - Update auth-client and auth-provider for convex token flow - Extend project-issue primitives with request result schema - Wire projectIssues backend mutation for request handling - Update slice-one page and project-workspace hook - Adjust routes (remove unused), Caddy config, and env templates
65 lines
1.7 KiB
TypeScript
65 lines
1.7 KiB
TypeScript
import { env } from "@code/env/web";
|
|
import { redirect } from "react-router";
|
|
|
|
interface AuthLoaderData {
|
|
readonly token: string | null;
|
|
}
|
|
|
|
const tokenUrl = new URL("/api/auth/convex/token", env.VITE_AUTH_URL);
|
|
|
|
export const loadAuthToken = async (
|
|
request: Request
|
|
): Promise<AuthLoaderData> => {
|
|
const cookie = request.headers.get("cookie");
|
|
if (!cookie) {
|
|
return { token: null };
|
|
}
|
|
|
|
const headers = new Headers({ cookie });
|
|
headers.set("host", tokenUrl.host);
|
|
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;
|
|
};
|