mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-24 12:42:38 +00:00
fix(web): rule pages tell non-admins that contributing is admin-only instead of failing with 403
The analyze and gather rule pages loaded the community submissions list for every role. rules/submissions is TenantAdminOrGA, so an Operator or Viewer got a 403 and a red "Failed to load your community submissions" banner on every page visit (16 denials in 24 h from two operators of one tenant). The pages now skip the list for callers who cannot read it and the contribution box says that only tenant admins can contribute and follow the tenant's submissions. Tenant admins, Global Admins and a global-scope override are unchanged; the backend policy stays as it is. Bumps the internal and .claude/commands gitlinks (platform-health marker catalog, backlog). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
+1
-1
Submodule .claude/commands updated: 95b417d3c7...315ad0e736
+1
-1
Submodule internal updated: 148e99f2e6...7cb78f47ea
@@ -112,6 +112,9 @@ export default function AnalyzeRulesPage() {
|
||||
// A read-only Global Reader — and an own-tenant admin viewing a FOREIGN tenant (cross-tenant override) —
|
||||
// is read-only. Backend also enforces (rules write is TenantAdminOrGA, cross-tenant blocked for non-GA).
|
||||
const isReadOnly = !(user?.isGlobalAdmin || (user?.isTenantAdmin && !isGlobalOverride));
|
||||
// Community submissions are TenantAdminOrGA on the own tenant; only a global-scope override reads them through
|
||||
// the operator list. Everyone else (Operator, Viewer) gets a hint instead of a 403 on the list call.
|
||||
const submissionsAdminOnly = isReadOnly && !isGlobalOverride;
|
||||
|
||||
// Cross-tenant write routing: in a Global Admin override the JWT-scoped rules/analyze/{id} route
|
||||
// resolves the tenant from the caller's token and would silently upsert into the GA's OWN tenant
|
||||
@@ -610,15 +613,17 @@ export default function AnalyzeRulesPage() {
|
||||
) : (
|
||||
<div className="space-y-6">
|
||||
{/* Community contribution: submit own custom rules for the shared pool; the list shows their status. */}
|
||||
<CommunityContributionBox onContribute={isReadOnly ? undefined : () => setShowSubmitModal(true)} />
|
||||
<MySubmissionsList
|
||||
kind="analyze"
|
||||
getAccessToken={getAccessToken}
|
||||
refreshKey={submissionsRefreshKey}
|
||||
overrideTenantId={isGlobalOverride ? effectiveTenantId : undefined}
|
||||
readOnly={isReadOnly}
|
||||
onError={showError}
|
||||
/>
|
||||
<CommunityContributionBox onContribute={isReadOnly ? undefined : () => setShowSubmitModal(true)} adminOnly={submissionsAdminOnly} />
|
||||
{!submissionsAdminOnly && (
|
||||
<MySubmissionsList
|
||||
kind="analyze"
|
||||
getAccessToken={getAccessToken}
|
||||
refreshKey={submissionsRefreshKey}
|
||||
overrideTenantId={isGlobalOverride ? effectiveTenantId : undefined}
|
||||
readOnly={isReadOnly}
|
||||
onError={showError}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Summary Stats */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-6 gap-4">
|
||||
|
||||
@@ -83,6 +83,9 @@ export default function GatherRulesPage() {
|
||||
// A read-only Global Reader — and an own-tenant admin viewing a FOREIGN tenant (cross-tenant override) —
|
||||
// is read-only. Backend also enforces (rules write is TenantAdminOrGA, cross-tenant blocked for non-GA).
|
||||
const isReadOnly = !(user?.isGlobalAdmin || (user?.isTenantAdmin && !isGlobalOverride));
|
||||
// Community submissions are TenantAdminOrGA on the own tenant; only a global-scope override reads them through
|
||||
// the operator list. Everyone else (Operator, Viewer) gets a hint instead of a 403 on the list call.
|
||||
const submissionsAdminOnly = isReadOnly && !isGlobalOverride;
|
||||
|
||||
// Cross-tenant write routing: in a Global Admin override the JWT-scoped rules/gather/{id} route
|
||||
// resolves the tenant from the caller's token and would silently upsert into the GA's OWN tenant
|
||||
@@ -444,15 +447,17 @@ export default function GatherRulesPage() {
|
||||
)}
|
||||
|
||||
{/* Community contribution: submit own custom rules for the shared pool; the list shows their status. */}
|
||||
<CommunityContributionBox onContribute={isReadOnly ? undefined : () => setShowSubmitModal(true)} />
|
||||
<MySubmissionsList
|
||||
kind="gather"
|
||||
getAccessToken={getAccessToken}
|
||||
refreshKey={submissionsRefreshKey}
|
||||
overrideTenantId={isGlobalOverride ? effectiveTenantId : undefined}
|
||||
readOnly={isReadOnly}
|
||||
onError={showError}
|
||||
/>
|
||||
<CommunityContributionBox onContribute={isReadOnly ? undefined : () => setShowSubmitModal(true)} adminOnly={submissionsAdminOnly} />
|
||||
{!submissionsAdminOnly && (
|
||||
<MySubmissionsList
|
||||
kind="gather"
|
||||
getAccessToken={getAccessToken}
|
||||
refreshKey={submissionsRefreshKey}
|
||||
overrideTenantId={isGlobalOverride ? effectiveTenantId : undefined}
|
||||
readOnly={isReadOnly}
|
||||
onError={showError}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Summary Stats */}
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-4">
|
||||
|
||||
@@ -6,10 +6,12 @@ import { DOCS_PATHS } from "@/lib/docsPaths";
|
||||
interface CommunityContributionBoxProps {
|
||||
/** Undefined hides the button (read-only roles, no custom rules yet). */
|
||||
onContribute?: () => void;
|
||||
/** The caller's role cannot contribute at all (contributing is tenant-admin only): say so instead of just hiding the button. */
|
||||
adminOnly?: boolean;
|
||||
}
|
||||
|
||||
/** The community call-to-action on the rule pages: contribute a rule here, report bugs on GitHub. */
|
||||
export function CommunityContributionBox({ onContribute }: CommunityContributionBoxProps) {
|
||||
export function CommunityContributionBox({ onContribute, adminOnly }: CommunityContributionBoxProps) {
|
||||
return (
|
||||
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4 flex flex-col sm:flex-row sm:items-center gap-3">
|
||||
<div className="flex items-start space-x-3 flex-1">
|
||||
@@ -22,6 +24,9 @@ export function CommunityContributionBox({ onContribute }: CommunityContribution
|
||||
<a href="https://github.com/okieselbach/AutopilotMonitor/issues" target="_blank" rel="noopener noreferrer" className="font-medium underline hover:text-blue-900">
|
||||
open a GitHub issue
|
||||
</a>.
|
||||
{adminOnly && (
|
||||
<span className="block mt-1 text-blue-700">Only tenant admins can contribute rules and follow the tenant's submissions — ask an admin of your tenant to submit it.</span>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
{onContribute && (
|
||||
|
||||
Reference in New Issue
Block a user