Files
cameleer-server/docs/superpowers/specs/2026-04-14-claim-mapping-rules-editor-design.md
hsiegeln 58e802e2d4
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m24s
CI / docker (push) Successful in 1m10s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 39s
feat: close modal on successful apply, update design spec
Modal auto-closes after Apply succeeds. Design spec updated to reflect
implemented behavior: local-edit-then-apply pattern, target select
dropdowns, amber pill for add-to-group, close-on-success.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 17:12:39 +02:00

7.3 KiB

Claim Mapping Rules Editor

Problem

The server has a claim mapping rules engine (ClaimMappingService) that maps arbitrary JWT claims to Cameleer roles and groups during OIDC login. The backend API exists (/api/v1/admin/claim-mappings CRUD), but there is no UI for managing these rules. Admins must use curl or Swagger to create, edit, and delete rules.

Solution

Add a rules editor modal to the existing OIDC config page (/admin/oidc), triggered from the Claim Mapping section. The modal contains a compact rules table with inline CRUD and a collapsible server-side test panel.

Design

Trigger

An "Edit Rules" button added to the bottom of the existing Claim Mapping section on the OIDC config page. Displays a badge with the active rule count (e.g. "3 active rules"). Visible in both read and edit mode of the OIDC form — the rules modal manages its own state independently of the OIDC form's edit/save lifecycle.

Modal — Rules Table

A full-width modal dialog with the title "Claim Mapping Rules" and a subtitle explaining their purpose.

Table columns:

Column Content Width
# Priority number (display only, derived from order) 30px
Claim Claim name in monospace code style auto
Match Match type pill: equals, contains, regex auto
Value Match value in monospace auto
Action Action pill: assign role (green/success), add to group (amber) auto
Target Role name or group name auto
Actions Reorder arrows + edit pencil + delete x ~80px

Action icons per row:

  • Up/down arrow buttons for reordering. Up hidden on first row, down hidden on last row. Moving a rule swaps priorities locally (saved to server on Apply).
  • Pencil icon switches the row to inline edit mode (same field layout as the add row).
  • X icon deletes the rule locally after a ConfirmDialog confirmation (saved to server on Apply).

Inline add form:

A row at the bottom of the table with input fields matching the table columns:

  • Input — claim name (text, placeholder "Claim name")
  • Select — match type (equals / contains / regex)
  • Input — match value (text, placeholder "Match value")
  • Select — action (assign role / add to group)
  • Select — target, populated from existing roles (when action is "assign role") or existing groups (when action is "add to group"). Includes a placeholder option ("Select role..." / "Select group..."). Switching action resets the target selection.
  • Button — "+ Add" (disabled until claim, value, and target are filled)

New rules are assigned a priority one higher than the current maximum.

Inline edit mode:

When the pencil icon is clicked, the row's static cells become editable inputs (same controls as the add form, including the target Select dropdown). The action icons change to a checkmark (save) and x (cancel). Saving updates local state only — persisted to server on Apply.

Empty state:

When no rules exist, show a centered message: "No claim mapping rules configured. Rules let you assign roles and groups based on JWT claims." with the add form below.

Modal — Test Panel

A collapsible section at the bottom of the modal, separated by a heavier border. Collapsed by default. Toggle via a clickable header row showing "Test Rules — Paste a decoded JWT to preview which rules would fire".

Layout when expanded:

Side-by-side split:

  • Left: textarea for pasting decoded JWT claims as JSON. Placeholder shows a sample JSON object.
  • Right: Results panel with:
    • Each matched rule listed: rule number, claim name, match description, arrow, action and target
    • "Effective" summary line: combined roles and groups
    • If no rules matched: "No rules matched — would fall back to default roles: [roles]"

"Test" button below the textarea triggers POST /api/v1/admin/claim-mappings/test. The evaluation runs server-side using ClaimMappingService.evaluate() — the same code path as the production OIDC login flow in OidcAuthController.applyClaimMappings().

Visual feedback: While test results are displayed, matched rows in the table above get a subtle green background tint and a checkmark in place of their action icons.

Local Edit + Apply Pattern

All changes (add, edit, delete, reorder) modify local state only. No API calls are made until the admin clicks Apply.

  • On modal open: server rules are cloned into local state.
  • Cancel: discards all local changes and closes the modal.
  • Apply: diffs local state against server state — creates new rules (temp IDs), updates changed rules, deletes removed rules. On success, shows a toast and closes the modal. On failure, shows an error toast and keeps the modal open.

Reordering

Up/down arrow buttons on each row. Clicking an arrow swaps the priority values of two adjacent rules in local state.

Priority is a server-side integer. The UI displays it as the row number (# column) — admins never edit the number directly.

New Backend Endpoint

POST /api/v1/admin/claim-mappings/test
Content-Type: application/json
Authorization: Bearer <admin-token>

{
  "sub": "user-42",
  "email": "jane@acme.com",
  "department": "engineering",
  "groups": ["frontend", "design"]
}

Response 200:
{
  "matchedRules": [
    {
      "ruleId": "uuid",
      "priority": 1,
      "claim": "email",
      "matchType": "regex",
      "matchValue": ".*@acme\\.com$",
      "action": "assignRole",
      "target": "OPERATOR"
    }
  ],
  "effectiveRoles": ["OPERATOR"],
  "effectiveGroups": [],
  "fallback": false
}

The endpoint:

  • Requires ADMIN role (same as other claim mapping endpoints)
  • Loads all rules from the database
  • Calls ClaimMappingService.evaluate(rules, claims) — production code path
  • Maps MappingResult list to the response DTO
  • Returns fallback: true when no rules matched (UI shows default roles message)
  • Read-only — no side effects, no user creation, no role assignment

Error Handling

  • Invalid JSON in test textarea: Show inline validation error, disable Test button
  • API errors on CRUD: Toast notifications (consistent with rest of admin UI)
  • ConfirmDialog on delete: Type-to-confirm not needed for rules (they're low-risk, easily recreated). Simple "Delete this rule?" confirm/cancel.

Files to Create/Modify

Backend

  • ClaimMappingAdminController.java — add POST /test endpoint
  • New DTO for test response (inline record or separate class)

Frontend

  • New component: ClaimMappingRulesModal.tsx — the modal with table, inline CRUD, and test panel
  • New CSS module: ClaimMappingRulesModal.module.css
  • New API hooks: useClaimMappingRules(), useCreateRule(), useUpdateRule(), useDeleteRule(), useTestRules() — React Query hooks for the claim mapping API
  • Modify: OidcConfigPage.tsx — add "Edit Rules" button in the Claim Mapping section, render the modal

Not in Scope

  • Drag-and-drop reordering (up/down arrows are sufficient for 1-10 rules)
  • Groups Claim field (rules can already match any claim including group claims)
  • Bulk import/export of rules
  • Rule templates or presets

Visual Reference

Mockups available in .superpowers/brainstorm/172364-1776174996/content/:

  • claim-rules-modal.html — initial layout (trigger button + table)
  • claim-rules-modal-v2.html — full modal with test panel expanded and match highlighting