Skip to content

Commit 5da9ba3

Browse files
committed
RBAC tests: dashboard session auth for admin pages (TRI-8742)
Validates the dashboardLoader({ authorization: { requireSuper: true } }) gate that the 14 admin pages were migrated to in TRI-8717 (and the dashboardBuilder split fix landed earlier on this branch). Coverage: - "Admin pages — requireSuper gate": three representative routes (/admin, /admin/concurrency, /admin/back-office) crossed with the three auth states: - No session → 302 to /login?redirectTo=<original-path>. - Non-admin session → 302 to / (no path leakage in redirectTo — a non-admin re-auth shouldn't bounce them back to /admin). - Admin session → handler runs (status < 300). All 14 admin routes share the same dashboardLoader config, so testing every file would just confirm the wrapper works (the harness already proves that). If config drifts per-route in the future, add targeted tests for the divergent ones. - "Admin action — requireSuper gate (admin.feature-flags POST)": locks in the behaviour change from the TRI-8717 migration. The legacy admin actions returned 403 Unauthorized; dashboardAction's unauthorizedRedirect is "/", so non-admins now get a 302 to "/" instead. Any XHR client branching on 403 needs updating — the test makes a silent regression loud. Reuses seedTestSession + seedTestUser from helpers/seedTestSession.ts (the helper that ships with the shared-container harness from TRI-8732). No new helpers needed. Verification: typecheck clean. Test execution deferred to your normal e2e run; CI will catch any false expectations.
1 parent 923a9bf commit 5da9ba3

1 file changed

Lines changed: 108 additions & 1 deletion

File tree

apps/webapp/test/auth-dashboard.e2e.full.test.ts

Lines changed: 108 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,119 @@
44

55
import { describe, expect, it } from "vitest";
66
import { getTestServer } from "./helpers/sharedTestServer";
7+
import { seedTestSession, seedTestUser } from "./helpers/seedTestSession";
78

89
describe("Dashboard", () => {
9-
// Placeholder until TRI-8742+ adds the actual matrix.
1010
it("shared webapp container redirects /admin/concurrency to /login when unauthenticated", async () => {
1111
const server = getTestServer();
1212
const res = await server.webapp.fetch("/admin/concurrency", { redirect: "manual" });
1313
expect(res.status).toBe(302);
1414
});
15+
16+
// Admin pages migrated to dashboardLoader({ authorization: { requireSuper: true } })
17+
// in TRI-8717. The dashboardLoader resolves auth in three stages:
18+
// 1. No session → redirect to /login?redirectTo=<path>.
19+
// 2. Session, user.admin === false → redirect to / (no path leakage).
20+
// 3. Session, user.admin === true → run the loader handler.
21+
//
22+
// Coverage strategy: pick three representative routes (the index, a
23+
// tabbed sub-page, and the back-office tree) rather than all 14 —
24+
// they all share the same dashboardLoader config so testing every
25+
// file would just confirm the wrapper works, which the harness
26+
// already proves. If the wrapper config drifts per-route in the
27+
// future, add targeted tests for the divergent ones.
28+
describe("Admin pages — requireSuper gate", () => {
29+
const adminRoutes = [
30+
"/admin",
31+
"/admin/concurrency",
32+
"/admin/back-office",
33+
];
34+
35+
for (const path of adminRoutes) {
36+
describe(`GET ${path}`, () => {
37+
it("no session: redirects to /login?redirectTo=<path>", async () => {
38+
const server = getTestServer();
39+
const res = await server.webapp.fetch(path, { redirect: "manual" });
40+
expect(res.status).toBe(302);
41+
const location = res.headers.get("location") ?? "";
42+
expect(location).toContain("/login");
43+
// Path leaks deliberately so a successful login bounces the
44+
// user back to where they were headed.
45+
expect(location).toContain(`redirectTo=${encodeURIComponent(path)}`);
46+
});
47+
48+
it("session for non-admin user: redirects to / (no path leakage)", async () => {
49+
const server = getTestServer();
50+
const user = await seedTestUser(server.prisma, { admin: false });
51+
const cookie = await seedTestSession({ userId: user.id });
52+
const res = await server.webapp.fetch(path, {
53+
redirect: "manual",
54+
headers: { Cookie: cookie },
55+
});
56+
expect(res.status).toBe(302);
57+
const location = res.headers.get("location") ?? "";
58+
// unauthorizedRedirect default in dashboardBuilder is "/".
59+
// A non-admin landing on /admin shouldn't get redirectTo
60+
// back to /admin once they upgrade — they're not getting in
61+
// by re-auth.
62+
expect(new URL(location, "http://localhost").pathname).toBe("/");
63+
});
64+
65+
it("session for admin user: 2xx", async () => {
66+
const server = getTestServer();
67+
const user = await seedTestUser(server.prisma, { admin: true });
68+
const cookie = await seedTestSession({ userId: user.id });
69+
const res = await server.webapp.fetch(path, {
70+
redirect: "manual",
71+
headers: { Cookie: cookie },
72+
});
73+
// Loader handler ran — could be 200 (HTML) or 204 (Remix
74+
// _data fetch). Either way, NOT a redirect.
75+
expect(res.status).toBeLessThan(300);
76+
});
77+
});
78+
}
79+
});
80+
81+
// Action handlers behind requireSuper used to return 403 Unauthorized
82+
// pre-RBAC — now they redirect to / via dashboardAction's
83+
// unauthorizedRedirect. The ticket flagged this as a behaviour
84+
// change worth locking in (any XHR fetcher that branched on 403
85+
// would have regressed silently). Use admin.feature-flags POST as
86+
// the canary — it's the simplest action of the bunch.
87+
describe("Admin action — requireSuper gate (admin.feature-flags POST)", () => {
88+
const path = "/admin/feature-flags";
89+
90+
it("no session: redirects to /login (POST)", async () => {
91+
const server = getTestServer();
92+
const res = await server.webapp.fetch(path, {
93+
method: "POST",
94+
body: JSON.stringify({}),
95+
headers: { "Content-Type": "application/json" },
96+
redirect: "manual",
97+
});
98+
expect(res.status).toBe(302);
99+
const location = res.headers.get("location") ?? "";
100+
expect(location).toContain("/login");
101+
});
102+
103+
it("session for non-admin user: redirects to / (was 403 pre-RBAC)", async () => {
104+
const server = getTestServer();
105+
const user = await seedTestUser(server.prisma, { admin: false });
106+
const cookie = await seedTestSession({ userId: user.id });
107+
const res = await server.webapp.fetch(path, {
108+
method: "POST",
109+
body: JSON.stringify({}),
110+
headers: { "Content-Type": "application/json", Cookie: cookie },
111+
redirect: "manual",
112+
});
113+
// Behaviour change from the TRI-8717 migration: the legacy
114+
// path returned 403 Unauthorized; dashboardAction returns a
115+
// 302 to "/" instead. Any client code branching on 403 needs
116+
// updating — locking this in so a silent regression is loud.
117+
expect(res.status).toBe(302);
118+
const location = res.headers.get("location") ?? "";
119+
expect(new URL(location, "http://localhost").pathname).toBe("/");
120+
});
121+
});
15122
});

0 commit comments

Comments
 (0)