Repository navigation
Show upstream display name on consent page - #6929
tgrunnagle wants to merge 6 commits into
Conversation
The consent page's "You will sign in with" line showed the upstream's Name, a routing and storage key restricted to DNS-label syntax. An embedder keying upstreams by an opaque id showed that id to users. Add an optional, display-only DisplayName to UpstreamRunConfig, UpstreamConfig and NamedUpstream (closes #6922): - Copy it through buildUpstreamConfig and buildUpstreams - Resolve it at render time from the current upstream list, sanitised like client names, trimmed and capped; fall back to Name - Keep Name in Technical details and as every storage key - Never reject it in validation - Document the trust obligation and regenerate API docs Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixed issues from code review: - MEDIUM: Move display-name validation test above the UpstreamFilter test's doc comment Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #6929 +/- ##
==========================================
+ Coverage 80.00% 80.01% +0.01%
==========================================
Files 837 837
Lines 85205 85229 +24
==========================================
+ Hits 68172 68200 +28
+ Misses 17028 17024 -4
Partials 5 5 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
tgrunnagle
left a comment
There was a problem hiding this comment.
Multi-Agent Consensus Review
Agents consulted: security, test-coverage, architecture/API/docs, general-quality
Consensus Summary
| # | Finding | Consensus | Severity | Action |
|---|---|---|---|---|
| 1 | Pin empty DisplayName on a configured upstream |
10/10 | LOW | Fix |
| 2 | No YAML/JSON round-trip test for display_name |
10/10 | MEDIUM | Fix |
| 3 | No authorize→consent or multi-upstream display-name test | 10/10 | LOW | Fix |
| 4 | AC1.3 storage test is largely tautological | 10/10 | LOW | Discuss |
| 5 | RunConfig half of the validation test is vacuous |
10/10 | LOW | Fix |
| 6 | Invisible filler runes survive sanitising | 10/10 | LOW | Discuss |
| 7 | Truncation edge cases and comment accuracy | 10/10 | LOW | Fix |
| 8 | Sanitisation and truncation test gaps | 10/10 | LOW | Fix |
| 9 | "Swapped upstream set" wording overstates | 10/10 | LOW | Fix |
| 10 | maxConsentClientNameRunes name is client-only |
10/10 | LOW | Fix |
| 11 | upstreamDisplayName loop uses continue/break |
10/10 | LOW | Fix |
| 12 | British "sanitised" in public API and SDK docs | 10/10 | LOW | Discuss |
| 13 | Dense arch-doc paragraph; hard-coded 80 | 10/10 | LOW | Fix |
| 14 | Test structure and a weaker fail-closed assertion | 10/10 | LOW | Fix |
Overall
This adds an optional, display-only DisplayName for upstreams and shows it on the consent page's "You will sign in with … next." line. The approach is sound and matches #6922. Name stays the key everywhere, the label only reaches an escaped text node, approval still fails closed, and the field is additive, so older binaries and existing configs are unaffected. Each acceptance criterion has a named test, the generated swagger, OpenAPI and SDK artifacts agree with each other, and out-of-scope items are left alone.
Nothing here blocks the PR. The one MEDIUM item is that no test round-trips the display_name tag through YAML or JSON, so a tag typo would pass every new test. The rest is polish: a few sanitiser and truncation edge cases, wording that implies the handler hot-swaps upstreams, and test structure.
Documentation
docs/arch/11-auth-server-storage.md:240: "a swapped upstream set shows the current value" implies in-place mutation, buth.upstreamsis set once inNewHandler. The paragraph is also one dense block with a hard-coded "80 runes", and could note the field isn't yet settable from theMCPExternalAuthConfigCRD.pkg/authserver/config.go:766: "sanitised" is copied into swagger, the SDK OpenAPI files and the ogen docs. The repo overwhelmingly uses "sanitize".
Generated with Claude Code
Addresses #6929 review comments: - LOW consent_test.go (4360871334): pin empty DisplayName on a configured upstream - LOW consent_test.go (4360871388): add authorize-path and multi-upstream test - LOW consent_test.go (4360871393): narrow AC1.3 test to the lookups it guards - LOW consent.go (4360871419): trim space before the ellipsis, fix cap comment - LOW consent_test.go (4360871425): add cap, ampersand and quote rows - LOW consent.go (4360871438): reword "swapped upstream set" comment - LOW consent.go (4360871447): rename cap to maxConsentTextRunes - LOW consent.go (4360871456): return from inside the upstream loop - LOW consent_test.go (4360871486): assert pending record consumed, add helper Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Addresses #6929 review comments: - MEDIUM config_test.go (4360871381): add YAML/JSON round-trip test for display_name - LOW config_test.go (4360871399): replace vacuous RunConfig subtests with a runner-side validation test, and check Name validation once Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Addresses #6929 review comments: - LOW consent.go (4360871409): document that sanitizing drops only control and format runes, so invisible fillers can render a blank label - LOW docs/arch/11-auth-server-storage.md (4360871438): reword "swapped upstream set" as a rebuilt handler - LOW config.go (4360871466): use "sanitized" and regenerate API docs and SDK - LOW docs/arch/11-auth-server-storage.md (4360871479): split the paragraph, name maxConsentTextRunes, note the CRD gap and discovery-derived values Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Rechecked the earlier review against Open: the runner-side AC1.2 test doesn't exercise validation (LOW, The test comment and the thread reply say this goes through Either drive the same cases through PR description is stale after the renames (LOW)
Everything else checks out against the code: the Generated with Claude Code |
Addresses #6929 review comments: - LOW embeddedauthserver_test.go (issuecomment-6368251390): call buildUpstreamConfigs so validateUpstreamRunConfig runs on each label Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks for the recheck.
|
Summary
The embedded auth server's consent page says "You will sign in with {{.Upstream}} next.", where the value is the upstream's
Name.Nameis a routing and storage key restricted to DNS-label characters, so an embedder that keys upstreams by an opaque id (for example a UUID provider id) shows that id to every end user, and even hand-written configs cannot say something like "Acme Okta".DisplayName(display_name) toauthserver.UpstreamRunConfig,authserver.UpstreamConfigandhandlers.NamedUpstream, copied throughbuildUpstreamConfig(bothoidcandoauth2) andbuildUpstreams. It is never compared, persisted, or used as a key, and validation never rejects it.DisplayName, resolved from the handler's current upstream list at render time. It falls back toNamewhen the label is unset, empty after sanitizing, or the pending authorization's upstream is no longer configured. Technical details keep showingNameas the internal name.displayClientName, whitespace is trimmed, it is capped atmaxConsentTextRunes(renamed frommaxConsentClientNameRunes, since it now covers both) with a trailing ellipsis via a new sharedtruncateConsentTexthelper, and the template HTML-escapes it. The helper now also trims whitespace left before the ellipsis, which applies to client names too (see reviewer notes).docs/arch/11-auth-server-storage.md; regenerate swagger and the Go SDK OpenAPI client.Closes #6922
Type of change
Test plan
task test)New tests, mapped to the issue's acceptance criteria:
TestBuildUpstreamConfigs_CopiesDisplayNameandTestBuildUpstreams_CopiesDisplayName(AC1.1, AC1.2): the field is copied unchanged foroidcandoauth2through both conversion paths. The runner test goes throughbuildUpstreamConfigs, sovalidateUpstreamRunConfigruns on each malformed label. A temporary display-name rejection in the validator makes it fail.TestUpstreamConfig_DisplayNameNeverFailsValidation(AC1.2):Config.Validateaccepts any display name, including empty or malformed, and still rejects an invalidName.TestRunConfig_DisplayNameRoundTrip:display_namedecodes from YAML and JSON, survives re-encoding, and is omitted when empty.TestDisplayNameChangeKeepsStoredRecordsValid(AC1.3): after a display name change, the pending authorization, upstream tokens and remembered consent still load and match byName, for both memory and Redis backends.TestConsentPage_UpstreamDisplayName(AC2.1): page reads "You will sign in with Acme Okta next." for an upstream namedffffffff-ffff-ffff-ffff-ffffffffffff, with the UUID still in Technical details.TestConsentPage_UpstreamDisplayNameViaAuthorize(AC2.1): the label reaches the page through a realGET /oauth/authorize. With two upstreams, only the first one's label appears.TestConsentPage_UpstreamDisplayNameSanitized(AC2.2, AC2.3, AC2.5): bidi overrides, zero-width characters and newlines are stripped; HTML,&and quotes are escaped; a label exactly at the cap is kept whole; longer labels are cut with…and no space before it; empty and whitespace-only labels fall back toName.TestConsentPage_MissingUpstreamFallsBackToName(AC2.4): falls back to the pendingUpstreamProviderNamewhen the upstream is gone.TestConsentPage_LayoutAndNameTruncationpasses unchanged, so the page is unchanged whenDisplayNameis empty.Changes
pkg/authserver/config.goDisplayNametoUpstreamRunConfigandUpstreamConfigpkg/authserver/runner/embeddedauthserver.goDisplayNameinbuildUpstreamConfigfor both provider typespkg/authserver/server_impl.goDisplayNameontohandlers.NamedUpstreampkg/authserver/server/handlers/handler.goDisplayNametoNamedUpstreampkg/authserver/server/handlers/consent.goUpstreamDisplayto page data; resolve and sanitize the label at render; extracttruncateConsentText; rename the cap tomaxConsentTextRunesdocs/arch/11-auth-server-storage.mddocs/server/*,sdk/go/*display_nameDoes this introduce a user-facing change?
Yes. Before: the consent page always said "You will sign in with
<upstream name>next." After: when an upstream setsdisplay_name, that line shows the sanitized display name instead (for example "You will sign in with Acme Okta next."), and Technical details still show the internal name. Configs withoutdisplay_namerender the same page as before.Special notes for reviewers
DisplayNameonly from operator-controlled values.TestConsentHandler_ChangedUpstreamFailsClosed).truncateConsentTextis shared with the client name, so a client name cut at 80 runes whose last kept rune is whitespace now showsname…instead ofname …. This is cosmetic, and the change was intended so both values follow one rule.Name. The arch doc states this. The cleaner is shared with client names, and the field is operator-controlled.displayNamefield on the operator'sMCPExternalAuthConfigCRD, defaulting the label to the issuer host, and showing the label on the device verification page or in logs.🤖 Generated with Claude Code