diff --git a/src/app/AppLayout.jsx b/src/app/AppLayout.jsx index 91ddeadd0..114332f9b 100644 --- a/src/app/AppLayout.jsx +++ b/src/app/AppLayout.jsx @@ -47,7 +47,7 @@ export function Layout({ children = null }) { (state) => state.user.currentAccessibleModules, ); const pluggedNavigation = useSelector( - (state) => state.user.pluggedNavigation, + (state) => state.user.currentPluggedNavigation, ); const unreadCount = useSelector((state) => state.notification.unreadCount); const profilePhoto = useSelector((state) => state.user.profilePhoto); diff --git a/src/helper/validateauth.jsx b/src/helper/validateauth.jsx index 34f01652e..6b2978b6e 100644 --- a/src/helper/validateauth.jsx +++ b/src/helper/validateauth.jsx @@ -49,7 +49,7 @@ function ValidateAuth() { last_selected_role, roll_no, must_complete_profile = false, - plugged_navigation = [], + plugged_navigation = {}, } = data; dispatch(setUserName(name)); diff --git a/src/redux/userslice.jsx b/src/redux/userslice.jsx index d8f8259a4..b0d2c02ea 100644 --- a/src/redux/userslice.jsx +++ b/src/redux/userslice.jsx @@ -15,7 +15,8 @@ const userSlice = createSlice({ authChecked: false, // /api/auth/me has resolved (gates route rendering) programmeType: null, // UG | PG | PHD — decides which academic pages exist profilePhoto: "", // student's own photo, shown on the sidebar avatar - pluggedNavigation: [], // menus contributed by modules another service serves + pluggedNavigation: {}, // Format---> {role: [section]}, from other services + currentPluggedNavigation: [], // the acting role's share of the above }, reducers: { setProgrammeType: (state, action) => { @@ -43,7 +44,7 @@ const userSlice = createSlice({ state.role = action.payload; }, setPluggedNavigation: (state, action) => { - state.pluggedNavigation = action.payload ?? []; + state.pluggedNavigation = action.payload ?? {}; }, setAccessibleModules: (state, action) => { state.accessibleModules = action.payload; @@ -51,6 +52,8 @@ const userSlice = createSlice({ setCurrentAccessibleModules: (state) => { state.currentAccessibleModules = state.accessibleModules[state.role] || {}; + state.currentPluggedNavigation = + state.pluggedNavigation[state.role] || []; }, clearUserName: (state) => { state.username = "User"; diff --git a/src/redux/userslice.test.jsx b/src/redux/userslice.test.jsx new file mode 100644 index 000000000..9f07f4ddf --- /dev/null +++ b/src/redux/userslice.test.jsx @@ -0,0 +1,36 @@ +import { describe, expect, it } from "vitest"; + +import reducer, { + setAccessibleModules, + setCurrentAccessibleModules, + setPluggedNavigation, + setRole, +} from "./userslice"; + +const placement = [ + { section: "Placement", items: [{ code: "placement_cell", links: [] }] }, +]; +const leave = [{ section: "Leave", items: [{ code: "leave", links: [] }] }]; + +const stateFor = (role) => + [ + setAccessibleModules({ + student: { placement_cell: true }, + acadadmin: { leave: true }, + }), + setPluggedNavigation({ student: placement, acadadmin: leave }), + setRole(role), + setCurrentAccessibleModules(), + ].reduce(reducer, undefined); + +describe("the acting role's own sidebar", () => { + it("gives a role only the menus granted to it", () => { + expect(stateFor("student").currentPluggedNavigation).toEqual(placement); + expect(stateFor("acadadmin").currentPluggedNavigation).toEqual(leave); + }); + + it("draws nothing for a role that was granted nothing", () => { + expect(stateFor("faculty").currentPluggedNavigation).toEqual([]); + expect(stateFor("faculty").currentAccessibleModules).toEqual({}); + }); +}); diff --git a/src/ui/routing/PluggedModule.jsx b/src/ui/routing/PluggedModule.jsx index b4e6834c4..f2880ae91 100644 --- a/src/ui/routing/PluggedModule.jsx +++ b/src/ui/routing/PluggedModule.jsx @@ -26,7 +26,10 @@ export function PluggedModule({ title }) { className={classes.frame} src={src} title={title} - // Same institute, same session; it needs its cookie and nothing beyond. + // Not a boundary: same-origin plus scripts lets the frame drop its own + // sandbox, and it needs both. The frame is trusted because it is ours and + // the origin is ours; what keeps that true is that nothing else — an + // upload above all — is ever served from this origin as a page. sandbox="allow-same-origin allow-scripts allow-forms allow-popups allow-downloads" /> );