Skip to content

Commit 3ee7d0f

Browse files
committed
Site Map
1 parent 3f0b294 commit 3ee7d0f

8 files changed

Lines changed: 229 additions & 140 deletions

File tree

src/hooks.server.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { env } from "$env/dynamic/private";
1010
import { oauth2ProviderManager } from "$lib/oauth/providerManager";
1111
import { SessionOAuthHelper } from "$lib/oauth/sessionHelper";
1212
import { resourceDocsCache } from "$lib/stores/resourceDocsCache";
13+
import { validateSiteMapScopes } from "$lib/utils/roleChecker";
1314

1415
declare const process: { env: Record<string, string | undefined>; argv: string[] };
1516

@@ -55,6 +56,8 @@ function checkServerPort() {
5556
// Startup scripts
5657
// Check server port
5758
checkServerPort();
59+
// Validate SITE_MAP doesn't mix bankScoped and system-wide roles on any page
60+
validateSiteMapScopes();
5861

5962
// Init Redis
6063
let client: Redis;

src/lib/components/PageRoleCheck.svelte

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
return checkRoles(userEntitlements || [], required || [], currentBankId);
2121
});
2222
23-
let firstMissingRequired = $derived(requiredCheck.missingRoles[0] || null);
2423
let showContent = $derived(requiredCheck.hasAllRoles);
2524
2625
// Check optional roles (informational — content still renders)
@@ -36,13 +35,15 @@
3635
);
3736
</script>
3837

39-
{#if firstMissingRequired}
38+
{#if requiredCheck.missingRoles.length > 0}
4039
<div class="role-alerts">
41-
<MissingRoleAlert
42-
roles={[firstMissingRequired.role]}
43-
bankId={firstMissingRequired.bankId || undefined}
44-
message={`You need the following role to access this page: ${firstMissingRequired.role}`}
45-
/>
40+
{#each requiredCheck.missingRoles as missingRole}
41+
<MissingRoleAlert
42+
roles={[missingRole.role]}
43+
bankId={missingRole.bankId || undefined}
44+
message={`You need the following role to access this page: ${missingRole.role}`}
45+
/>
46+
{/each}
4647
</div>
4748
{/if}
4849

src/lib/utils/roleChecker.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ export const SITE_MAP: Record<string, PageRoleConfig> = {
6868
{ role: "CanDeleteEntitlementRequestsAtAnyBank" },
6969
],
7070
},
71+
"/rbac/groups": {
72+
required: [{ role: "CanGetGroupsAtAllBanks" }],
73+
},
7174
"/rbac/groups/create": {
7275
required: [{ role: "CanCreateGroupAtAllBanks" }],
7376
},
@@ -132,6 +135,12 @@ export const SITE_MAP: Record<string, PageRoleConfig> = {
132135
required: [{ role: "CanManageFeaturedApiCollections" }],
133136
},
134137

138+
// ── Products ────────────────────────────────────────────
139+
"/products/bootstrap": {
140+
required: [{ role: "CanUpdateApiProduct", bankScoped: true }],
141+
optional: [{ role: "CanDeleteApiProduct", bankScoped: true }],
142+
},
143+
135144
// ── Banks ─────────────────────────────────────────────
136145
"/banks/create": {
137146
required: [{ role: "CanCreateBank" }],
@@ -152,6 +161,12 @@ export const SITE_MAP: Record<string, PageRoleConfig> = {
152161
},
153162

154163
// ── Metrics ───────────────────────────────────────────
164+
"/metrics": {
165+
required: [{ role: "CanReadMetrics" }],
166+
},
167+
"/aggregate-metrics": {
168+
required: [{ role: "CanReadAggregateMetrics" }],
169+
},
155170
"/connector-traces": {
156171
required: [{ role: "CanGetConnectorTrace" }],
157172
},
@@ -248,6 +263,30 @@ export const SITE_MAP: Record<string, PageRoleConfig> = {
248263
},
249264
};
250265

266+
/**
267+
* Validate that no SITE_MAP page mixes bankScoped and non-bankScoped roles
268+
* in its required list. Mixing scopes would show confusing role widgets
269+
* (some saying "System-wide" and others "Bank-level") on the same page.
270+
* Logs a warning at startup for any violations.
271+
*/
272+
export function validateSiteMapScopes(): void {
273+
for (const [route, config] of Object.entries(SITE_MAP)) {
274+
const roles = config.required;
275+
if (roles.length < 2) continue;
276+
277+
const hasBankScoped = roles.some((r) => r.bankScoped);
278+
const hasSystemScoped = roles.some((r) => !r.bankScoped);
279+
280+
if (hasBankScoped && hasSystemScoped) {
281+
logger.warn(
282+
`SITE_MAP "${route}" mixes bankScoped and system-wide roles in its required list: ` +
283+
roles.map((r) => `${r.role}${r.bankScoped ? " (bankScoped)" : ""}`).join(", ") +
284+
`. This will show confusing role widgets. Separate into distinct pages or unify the scope.`,
285+
);
286+
}
287+
}
288+
}
289+
251290
/**
252291
* Look up page roles by route ID (stripping /(protected) prefix if present)
253292
*/

0 commit comments

Comments
 (0)