83 lines
2.4 KiB
TypeScript
83 lines
2.4 KiB
TypeScript
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
|
|
import { redirect } from "remix-typedjson";
|
|
import { $replica } from "~/db.server";
|
|
import { clearImpersonation, redirectWithImpersonation } from "~/models/admin.server";
|
|
import { env } from "~/env.server";
|
|
import { logger } from "~/services/logger.server";
|
|
import { requireUser } from "~/services/session.server";
|
|
import { isSameOriginNavigation } from "~/utils/sameOriginNavigation";
|
|
|
|
export async function loader({ request, params }: LoaderFunctionArgs) {
|
|
const user = await requireUser(request);
|
|
|
|
// If already impersonating, we need to clear the impersonation
|
|
if (user.isImpersonating) {
|
|
const url = new URL(request.url);
|
|
return clearImpersonation(request, url.pathname);
|
|
}
|
|
|
|
// Only admins can impersonate
|
|
if (!user.admin) {
|
|
return redirect("/");
|
|
}
|
|
|
|
const path = params["*"];
|
|
const organizationSlug = params.organizationSlug;
|
|
|
|
logger.debug("Impersonating user", { path, organizationSlug });
|
|
|
|
if (!organizationSlug) {
|
|
logger.debug("Exiting impersonation mode");
|
|
return clearImpersonation(request, "/admin");
|
|
}
|
|
|
|
// CSRF gate for the SET-impersonation path. Clearing impersonation
|
|
// above is benign and stays reachable without the check.
|
|
if (!isSameOriginNavigation(request, env.LOGIN_ORIGIN)) {
|
|
logger.warn("Refusing cross-site impersonation entry", {
|
|
userId: user.id,
|
|
organizationSlug,
|
|
referer: request.headers.get("referer"),
|
|
secFetchSite: request.headers.get("sec-fetch-site"),
|
|
});
|
|
return redirect("/admin");
|
|
}
|
|
|
|
const org = await $replica.organization.findFirst({
|
|
where: {
|
|
slug: organizationSlug,
|
|
deletedAt: null,
|
|
},
|
|
select: {
|
|
members: {
|
|
select: {
|
|
user: {
|
|
select: {
|
|
id: true,
|
|
confirmedBasicDetails: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!org) {
|
|
logger.debug("Organization not found", { organizationSlug });
|
|
return clearImpersonation(request, "/admin");
|
|
}
|
|
|
|
const firstValidMember = org.members.find((m) => m.user.confirmedBasicDetails);
|
|
|
|
if (!firstValidMember) {
|
|
logger.debug("No valid members found", { organizationSlug });
|
|
return clearImpersonation(request, "/admin");
|
|
}
|
|
|
|
return redirectWithImpersonation(
|
|
request,
|
|
firstValidMember.user.id,
|
|
`/orgs/${organizationSlug}/${path}`
|
|
);
|
|
}
|