Skip to content

Commit e5e910b

Browse files
committed
fix(webapp): gate the impersonate route on canSuper, not the admin column
Opting out of the admin layout also opted out of its requireSuper check, leaving this the only admin entry point gated on the raw User.admin column. canSuper() equals that column in the OSS fallback, but an RBAC plugin is free to be stricter. The ability is now built explicitly for the real admin's id and canSuper() is checked directly. dashboardLoader can't be used: it resolves its subject with getUserId, which is the impersonated id while impersonating — the bug this route exists to fix.
1 parent 7be4c3b commit e5e910b

1 file changed

Lines changed: 13 additions & 1 deletion

File tree

apps/webapp/app/routes/admin_.impersonate.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
import { z } from "zod";
77
import { redirectWithImpersonation } from "~/models/admin.server";
88
import { authenticator } from "~/services/auth.server";
9+
import { rbac } from "~/services/rbac.server";
910
import { getRealUser } from "~/services/session.server";
1011
import { validateAndConsumeImpersonationToken } from "~/services/impersonation.server";
1112
import { logger } from "~/services/logger.server";
@@ -45,7 +46,18 @@ async function requireRealAdmin(request: Request) {
4546
}
4647

4748
const admin = await getRealUser(request);
48-
return admin?.admin ? admin : null;
49+
if (!admin) return null;
50+
51+
// Same gate `dashboardLoader({ authorization: { requireSuper: true } })` applies, evaluated
52+
// against the real admin. It can't be reached through the builder here, because the builder
53+
// resolves its subject with `getUserId` — the impersonated id while impersonating, which is the
54+
// bug this route exists to fix. So the ability is built explicitly for `admin.id` instead of
55+
// trusting the raw `User.admin` column: `canSuper()` is only equal to that column in the OSS
56+
// fallback, and a plugin is free to be stricter. requireSuper needs no org/project scope.
57+
const auth = await rbac.authenticateSession(request, { userId: admin.id });
58+
if (!auth.ok || !auth.ability.canSuper()) return null;
59+
60+
return admin;
4961
}
5062

5163
async function handleImpersonationRequest(request: Request, userId: string): Promise<Response> {

0 commit comments

Comments
 (0)