feat: wire Builder ID and company SSO into KAS-mode sign-in - #6906
Conversation
Design Review (Fable 5) — ✅ PASSDesign-level review of Design-Verdict: PASS Additive wiring that reuses the existing device-flow/pending-login machinery; every failure path is loud and coded rather than persisting an unusable credential. Suggestions
[DESIGN-REVIEWED] 47e46b2 |
GPT 5.6 Review — ✅ no blocking findingsGPT 5.6 completed its review of This comment is updated in place on each push. Review detailsFINDING -- src/kiro_crew/auth/service.py:333 -- Profile-resolution failures return only False positive or not applicable? A repository writer can comment: |
UX Review (Fable 5) — 🟡 CONCERNSUX-level review of UX-Verdict: CONCERNS Solid inline SSO form, but every IdC failure after the user approves collapses to a detail-less dead end, and retries re-ask for the portal URL. Watch
Suggestions
[UX-REVIEWED] 47e46b2 |
First Principles Review (Fable 5) — 🟡 CONCERNSPremise-level review of All evidence gathered. Here is the review. First-Principles-Verdict: CONCERNS Every added capability earns its place, but the old blocking What this change shipsIntent: let a person sign in to KAS mode with AWS Builder ID or their company's IAM Identity Center portal, not only Google/GitHub — an ADDITION.
Watch
Subtractions
[FIRST-PRINCIPLES-REVIEWED] 47e46b2 |
Opus 4.8 Review — ✅ no blocking findingsReviewed Review detailsBoth candidates fail the falsification bar. Candidate 1 (stray No findings. [OPUS-REVIEWED] 47e46b2 Verdict parsed from the review's SHA-scoped output markers for commit False positive or not applicable? A repository writer can comment: |
8c5624e to
4eda778
Compare
4eda778 to
6885fcd
Compare
6885fcd to
89c3401
Compare
89c3401 to
5ce0981
Compare
5ce0981 to
a355320
Compare
The KAS login gate shipped with only the two Kiro-proxied social providers wired; the Builder ID / IdC SSO-OIDC device flow existed in auth/login/builder_id.py but nothing routed to it, so the chooser hid those buttons. Backend: begin_device now dispatches builder_id and idc to the SSO-OIDC device flow (dynamic RegisterClient + StartDeviceAuthorization), with a non-blocking poll_token_once matching the dashboard-driven poll cadence. An IdC token is unusable without a profile ARN (the store drops it), so an authorized IdC poll resolves one via the new control_plane module (ListAvailableProfiles, contract mirrored from the KAS bundle's vendored client); resolution failures end the login with a coded error instead of persisting an unusable credential. idc requires the company start URL (coded 400 when missing) and takes an optional region. Frontend: the chooser gains a Builder ID button and a company-SSO choice that expands an inline start-URL + region form (submit disabled until a URL is present). 8 new catalog keys across 12 locales + en-XA. Verified: Builder ID begin + unapproved poll live against the real SSO-OIDC endpoint (RegisterClient accepted, real user code issued, poll returned pending). IdC profile resolution is contract-mirrored but not yet verified against a live enterprise tenant; multi-profile selection picks the first profile and is a documented follow-up.
a355320 to
47e46b2
Compare
|
Disposition — GPT advisory (service.py profile-resolution error returns): declined, with rationale. The poll response's |
bolichen97
left a comment
There was a problem hiding this comment.
Full-diff maintainer review passed: change matches its stated scope, no regressions or trust-boundary weakening found, checks green and no outstanding change requests. Approving.
| body = await resp.text() | ||
| raise ControlPlaneError( | ||
| f"ListAvailableProfiles failed: HTTP {resp.status} {body[:500]}" | ||
| ) |
There was a problem hiding this comment.
🟡 [security] The error body is logged at body[:500] truncation when ListAvailableProfiles fails.
raise ControlPlaneError(
f"ListAvailableProfiles failed: HTTP {resp.status} {body[:500]}"
)Consequence chain: cause (error includes body in exception message) → mechanism (body might contain echoed request details or sensitive error context) → consequence (if AWS ever changes its error format to include request context, bearer token could leak to logs/exception handlers).
This is low probability (AWS services typically do not echo auth headers in error bodies), but the pattern is fragile. The social poll and OIDC token poll both avoid logging bodies that could contain tokens.
Suggestion: Truncate or redact the body before raising, or at minimum note in a comment why this is safe (AWS JSON 1.0 errors do not echo auth headers).
[code-review-sage]
| raise BuilderIdAuthError("CreateToken returned a non-object body") | ||
| return _token_from_create(data, client, region, identity, provider) | ||
| err = data.get("error", "") if isinstance(data, dict) else "" | ||
| if err in ("authorization_pending", "slow_down"): |
There was a problem hiding this comment.
🟡 [observability] The undecodable-body fallback silently returns None with no log.
except (aiohttp.ClientError, ValueError):
# Malformed/empty body ...
return NoneConsequence chain: cause (transient LB/proxy error returns HTML) → mechanism (silently treated as pending, no diagnostic) → consequence (on-call debugging a stuck login sees only repeated pending with no signal that the token endpoint is misbehaving).
The social poll has the same pattern, but adding a logger.debug here would cost nothing and help distinguish "user has not approved yet" from "the endpoint is serving garbage".
Suggestion: Add logger.debug("poll_token_once: transient body parse failure", exc_info=True).
[code-review-sage]
Summary
The KAS-mode sign-in gate offered only Google and GitHub; the Builder ID / IAM Identity Center SSO-OIDC device flow existed in
auth/login/builder_id.pybut nothing routed to it, so the chooser hid those buttons. This wires both providers end to end:begin_devicedispatchesbuilder_idandidcto the SSO-OIDC device flow (runtimeRegisterClient+StartDeviceAuthorization, no Kiro-controlled gate), with a new non-blockingpoll_token_oncematching the dashboard-driven poll cadence. An IdC token is unusable without a profile ARN (the store itself drops it), so an authorized IdC poll resolves one via the newcontrol_planemodule (ListAvailableProfiles); any resolution failure ends the login with a coded error instead of persisting an unusable credential.idcrequires the company start URL (coded 400 when missing) and takes an optional region.Screenshots
Testing
test_kas_login_service.py+test_kas_login_api.py), including 9 new tests covering builder_id/idc begin + poll, profile-ARN resolution, no-profile and control-plane-failure error paths, expiry, and multi-profile first-pick.tscclean, ESLint 0 warnings,KasLoginGate.test.tsx12 passed (4 new interaction tests), i18n suite 645 passed, production build green.mypy --platform linuxclean on changed files.oidc.us-east-1.amazonaws.com:RegisterClientaccepted our dynamic registration, a real user code was issued, and the unapproved single poll correctly returned pending. No credential was created.Known limitations (deliberate)
ListAvailableProfileswire shape (AWS JSON 1.0,X-Amz-Target: KiroControlPlaneBearerService.ListAvailableProfiles, bearer auth) is taken from the KAS bundle's vendored@amzn/kiro-control-plane-bearer-client, but we have no enterprise IdC tenant to run the full flow against. Every failure path surfaces as a coded error rather than a stored-but-unusable credential.