fix(dashboard): de-duplicate dashboard_tags typed into the API-key create form

The form split tags with the shared ParseList and attached the result verbatim,
so "team-a, TEAM-A" persisted as two entries and the constraints column read
dashboard_tags=[team-a, TEAM-A] — one grant reported as two on the page whose
job is to show what a key was granted. Enforcement never saw it (a session holds
its tags in a case-insensitive set), which is exactly why the display was the
only place it could surface.

De-duplicates ordinal-ignore-case at the attach point only, first spelling
winning, matching ApiKeyAdminCommandLineParser.ParseDashboardTags. ParseList is
untouched: the five glob lists are matched literally, so near-duplicates there
are not necessarily the same rule and must survive verbatim — pinned by a test.

The help text claimed to mirror the CLI flag; it now claims only the shared
separators and the dedupe, since the form still drops an empty segment silently
where the CLI hard-fails. A browser form has no exit code to fail with, so that
difference stays, and Authorization.md now records it.
This commit is contained in:
Joseph Doherty
2026-08-18 05:19:02 -04:00
parent 1ea6f60ea2
commit fab600d3b0
3 changed files with 85 additions and 3 deletions
+4 -1
View File
@@ -197,7 +197,10 @@ Tags are set at key creation with
`apikey create-key --dashboard-tags team-a,team-b` (repeatable; segments are
trimmed and de-duplicated ordinal-ignore-case). The dashboard API Keys page sets
them too: its create form has a **Dashboard tags** field alongside the data-access
constraints, split on the same separators the other constraint fields use.
constraints, split on the same separators the other constraint fields use and
de-duplicated ordinal-ignore-case as the CLI does. The two differ on one point: a
stray separator drops an empty segment silently on the form rather than failing
the command, because a browser form has no exit code to fail with.
That page's constraints column names `dashboard_tags` like any other member. It
has to: `IsEmpty` counts the tags, so a key whose only recorded policy is a