Skip to content

feat: wire Builder ID and company SSO into KAS-mode sign-in - #6906

Merged
bolichen97 merged 1 commit into
mainfrom
feat/kas-login-builder-id-idc
Aug 31, 2026
Merged

feat: wire Builder ID and company SSO into KAS-mode sign-in#6906
bolichen97 merged 1 commit into
mainfrom
feat/kas-login-builder-id-idc

Conversation

@CrysisDeu

@CrysisDeu CrysisDeu commented Aug 30, 2026

Copy link
Copy Markdown
Collaborator

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.py but nothing routed to it, so the chooser hid those buttons. This wires both providers end to end:

  • Backendbegin_device dispatches builder_id and idc to the SSO-OIDC device flow (runtime RegisterClient + StartDeviceAuthorization, no Kiro-controlled gate), with a new non-blocking poll_token_once matching 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 new control_plane module (ListAvailableProfiles); any resolution failure ends 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 Continue with AWS Builder ID and Continue with company SSO; the SSO choice expands an inline start-URL + region form whose submit stays disabled until a URL is present (no dead-end 400 round-trip). 8 new catalog keys across 12 locales + regenerated en-XA.

Screenshots

Chooser (en) Company-SSO form (en)
chooser sso form
Chooser (zh-CN) Company-SSO form (zh-CN)
chooser zh sso form zh

Testing

  • Backend: 44 passed (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.
  • Frontend: tsc clean, ESLint 0 warnings, KasLoginGate.test.tsx 12 passed (4 new interaction tests), i18n suite 645 passed, production build green.
  • Gates: black / isort / flake8 / mypy --platform linux clean on changed files.
  • Live smoke — Builder ID begin against the real oidc.us-east-1.amazonaws.com: RegisterClient accepted our dynamic registration, a real user code was issued, and the unapproved single poll correctly returned pending. No credential was created.

Known limitations (deliberate)

  • IdC is contract-mirrored, not tenant-verified: the ListAvailableProfiles wire 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.
  • Multi-profile IdC picks the first profile (logged); a selection UI is a documented follow-up.

@CrysisDeu
CrysisDeu requested a review from a team August 30, 2026 03:13
@CrysisDeu
CrysisDeu requested a review from a team as a code owner August 30, 2026 03:13
@CrysisDeu
CrysisDeu requested a review from hoang-phan98 August 30, 2026 03:13
@github-actions github-actions Bot added the readiness: checking Automated validation is still running label Aug 30, 2026
@github-actions

github-actions Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Design Review (Fable 5) — ✅ PASS

Design-level review of 47e46b29f01cea5f1dd52ab9be3e38b70de180d2 — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

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

  • In _poll_oidc, treat a transport failure during list_available_profiles like ControlPlaneError (forget + coded error): today an aiohttp.ClientError there escapes as a 502 while the pending entry survives, and its single-use device code — already consumed by the successful CreateToken — then dies on the next tick as a generic token poll failed, a confusing double-failure on the exact enterprise path the PR says is unverified.
  • The blocking poll_token now has zero callers in src/ while poll_token_once reimplements the same wire exchange with different error handling; delete it or rebase it on poll_token_once so the two can't drift.

[DESIGN-REVIEWED] 47e46b2

@github-actions

github-actions Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

GPT 5.6 Review — ✅ no blocking findings

GPT 5.6 completed its review of 47e46b29f01cea5f1dd52ab9be3e38b70de180d2 and found no blocking issues.

This comment is updated in place on each push.

Review details

FINDING -- src/kiro_crew/auth/service.py:333 -- Profile-resolution failures return only {"status": "error"}, omitting the promised machine-readable code -> Fix: add distinct code values to both failure returns. (origin: validation)
[GPT-REVIEWED] 47e46b2

False positive or not applicable? A repository writer can comment:
/ai-review override gpt 47e46b29f01cea5f1dd52ab9be3e38b70de180d2: <one-sentence reason>

@github-actions

github-actions Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

UX Review (Fable 5) — 🟡 CONCERNS

UX-level review of 47e46b29f01cea5f1dd52ab9be3e38b70de180d2 — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

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

  • IdC failures reach the user as an empty "Sign-in failed". _poll_oidc returns bare {"status": "error"} for both ControlPlaneError and "no available Kiro profiles", so SignInProblem's detail (pollQuery.data?.error || '') renders nothing above "Something went wrong while waiting for approval" — a user who just successfully approved in their browser gets no cause, and "Start over" deterministically repeats the no-profile failure. Rare path × dead-end impact × every retry. Fix: return a coded error string ("Your account has no Kiro profile — contact your admin" / "Couldn't reach your company's sign-in service") in the poll response.
  • Retry wipes the start URL. Code expiry or error → "Start over" remounts Chooser (ssoOpen=false, startUrl=''), so company-SSO users re-open the form and re-paste the portal URL on every attempt. Moderate frequency (device codes expire) × friction × every time. Fix: preserve the last-submitted start_url/region across start-over.
  • Scheme-less paste hits an unlocalized browser bubble. type="url" with no noValidate: "your-company.awsapps.com/start" enables the submit (startUrlReady only checks non-empty) but native validation blocks it with the browser's own "Please enter a URL" — confusing ("I did enter a URL") and outside the 12-locale catalog. Fix: normalize by prepending https://, or validate in-app at the field.

Suggestions

  • Surface the invalid_region 400 at the region field; today "Not a valid AWS region name." lands in the page-bottom beginError block under "Could not start sign-in", detached from the input that caused it.
  • On Cancel (and add Escape) of the SSO form, return focus to the "Continue with company SSO" button — the form unmounts and keyboard focus currently drops to body.

[UX-REVIEWED] 47e46b2

@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention and removed readiness: checking Automated validation is still running labels Aug 30, 2026
@github-actions

github-actions Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

First Principles Review (Fable 5) — 🟡 CONCERNS

Premise-level review of 47e46b29f01cea5f1dd52ab9be3e38b70de180d2 — why this exists and whether the shipped surface is the smallest honest version. Updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

All evidence gathered. Here is the review.

First-Principles-Verdict: CONCERNS

Every added capability earns its place, but the old blocking poll_token is left behind at zero consumers, already diverging from its new single-shot twin.

What this change ships

Intent: 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.

  1. Chooser gains "Continue with AWS Builder ID" starting a device sign-in — justified
  2. Chooser gains "Continue with company SSO" expanding an inline URL+region form — justified
  3. SSO submit stays disabled until a start URL exists — justified (removes a guaranteed 400 dead end)
  4. Begin endpoint accepts builder_id/idc with coded 400s — justified
  5. A crafted region is rejected before hostname interpolation — undeclared; derived, but 8th sibling regex
  6. New single-shot token poll beside the retained blocking one — duplicate of builder_id.py:184
  7. IdC sign-in resolves a profile ARN, failing loudly — justified (store.py:200 drops profile-less IdC tokens)
  8. Multi-profile IdC silently takes the first, logged — declared limitation
  9. Malformed OIDC replies become coded errors, not uncoded 500s — undeclared; justified hardening
  10. 8 catalog keys × 12 locales, screenshots + capture script — justified, repo convention

Watch

  • _REGION_RE (service.py:63) is the 8th hand-rolled AWS-region matcher — grep _REGION_RE|_AWS_REGION_RE counts 7 pre-existing definitions (aws_consent.py:118, cloud/ec2.py:77, cloudwatch.py:72, instances/validation.py:68, instances/registry.py:76, detach_backend.py:77, deploy_profiles via accounts.py:238). The guard itself is derived (hostname injection); the spelling is the 8th sibling of an unfixed root cause — note which level this sits at.
  • The JSON hardening (item 9) leaves exactly one unfixed sibling: the bare await resp.json() at builder_id.py:203 — inside the function the first subtraction deletes.

Subtractions

  • Delete poll_token (builder_id.py:184) and its 4 tests in test_kas_auth_flows.py — grep poll_token(: zero production consumers (service.py calls only poll_token_once), and the two spellings have already diverged (203's unhardened resp.json()). Alternatively reimplement it as a loop over poll_token_once.
  • Drop resolve_profile from _PendingOidcLogin (service.py) — it restates identity == "identity_center"; 1 consumer (the _poll_oidc branch); derive it from identity.
  • Drop the never-used identity="builder_id"/provider="BuilderId" defaults on poll_token_once — its single caller (service.py:307) passes both explicitly.

[FIRST-PRINCIPLES-REVIEWED] 47e46b2

@github-actions

github-actions Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Opus 4.8 Review — ✅ no blocking findings

Reviewed 47e46b29f01cea5f1dd52ab9be3e38b70de180d2 — this comment is updated in place on each push.

Review details

Both candidates fail the falsification bar. Candidate 1 (stray temp-screenshots/ PNGs) is a repo-hygiene concern with no runtime call path or observable wrong outcome — it does not meet the (a)/(b)/(c) defect bar, and its own rule (no-test-side-effects) doesn't scope here. Candidate 2 (hardcoded placeholder="us-east-1") is an i18n-gate/consistency concern about an untranslatable region-code example; it's a deterministic CI category with no runtime defect. The core additions (region-grammar SSRF guard, malformed-body handling, IdC profile resolution) are correctly implemented and well-covered, and nothing in the diff weakens a guard.

No findings.

[OPUS-REVIEWED] 47e46b2

Verdict parsed from the review's SHA-scoped output markers for commit 47e46b29f01cea5f1dd52ab9be3e38b70de180d2.

False positive or not applicable? A repository writer can comment:
/ai-review override fable 47e46b29f01cea5f1dd52ab9be3e38b70de180d2: <one-sentence reason>

@CrysisDeu
CrysisDeu force-pushed the feat/kas-login-builder-id-idc branch from 8c5624e to 4eda778 Compare August 30, 2026 03:42
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: passed Eligible automated validation passed for the current revision and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Aug 30, 2026
@CrysisDeu
CrysisDeu force-pushed the feat/kas-login-builder-id-idc branch from 4eda778 to 6885fcd Compare August 30, 2026 04:16
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention readiness: passed Eligible automated validation passed for the current revision and removed readiness: passed Eligible automated validation passed for the current revision readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention labels Aug 30, 2026
@CrysisDeu
CrysisDeu force-pushed the feat/kas-login-builder-id-idc branch from 6885fcd to 89c3401 Compare August 30, 2026 05:10
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention and removed readiness: passed Eligible automated validation passed for the current revision readiness: checking Automated validation is still running labels Aug 30, 2026
@CrysisDeu
CrysisDeu force-pushed the feat/kas-login-builder-id-idc branch from 89c3401 to 5ce0981 Compare August 30, 2026 05:48
@github-actions github-actions Bot added readiness: checking Automated validation is still running and removed readiness: action required A blocking check or review needs attention labels Aug 30, 2026
@CrysisDeu
CrysisDeu force-pushed the feat/kas-login-builder-id-idc branch from 5ce0981 to a355320 Compare August 30, 2026 06:03
@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention and removed readiness: checking Automated validation is still running labels Aug 30, 2026
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.
@CrysisDeu
CrysisDeu force-pushed the feat/kas-login-builder-id-idc branch from a355320 to 47e46b2 Compare August 30, 2026 06:36
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: passed Eligible automated validation passed for the current revision and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Aug 30, 2026
@CrysisDeu

Copy link
Copy Markdown
Collaborator Author

Disposition — GPT advisory (service.py profile-resolution error returns): declined, with rationale.

The poll response's status field (pending / expired / error / authorized) is the machine-readable contract this endpoint promises, and it is uniform across every flavor: the social path's equivalent failures (_token_from_poll rejection, token-store persist failure, unrecognized poll status) all return bare {"status": "error"} today. Adding a distinct code only to the two IdC profile-resolution branches would fork the poll error contract for one provider while its siblings stay uncoded — the frontend treats all of them identically (SignInProblem screen + start over). The "coded error" commitment in the PR body refers to HTTP-level 4xx/5xx bodies (missing_start_url, invalid_region, device_authorization_failed, auth_service_unreachable), which all carry codes. Introducing per-branch poll error codes across ALL flavors is a reasonable uniform follow-up, but not a one-branch rider on this PR.

@bolichen97
bolichen97 enabled auto-merge August 30, 2026 20:28

@bolichen97 bolichen97 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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]}"
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 [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"):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 [observability] The undecodable-body fallback silently returns None with no log.

except (aiohttp.ClientError, ValueError):
    # Malformed/empty body ...
    return None

Consequence 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]

@bolichen97
bolichen97 merged commit 27cd194 into main Aug 31, 2026
75 of 77 checks passed
@bolichen97
bolichen97 deleted the feat/kas-login-builder-id-idc branch August 31, 2026 00:29
@github-actions github-actions Bot removed the readiness: passed Eligible automated validation passed for the current revision label Aug 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants