* feat(kiro): wire aws identity center device flow into provider oauth (#587)

* feat(kiro): wire aws identity center device flow into provider oauth

Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
This commit is contained in:
Dang Dinh Quan
2026-04-15 11:44:46 +07:00
committed by GitHub
co-authored by Copilot
parent c3a2bd01b7
commit b1288c5064
7 changed files with 243 additions and 164 deletions
+2 -2
View File
@@ -126,10 +126,10 @@ export default function KiroAuthModal({ isOpen, onMethodSelect, onClose }) {
</div>
</button>
{/* AWS IAM Identity Center (IDC) - HIDDEN */}
{/* AWS IAM Identity Center (IDC) */}
<button
onClick={() => handleMethodSelect("idc")}
className="hidden w-full p-4 text-left border border-border rounded-lg hover:bg-sidebar transition-colors"
className="w-full p-4 text-left border border-border rounded-lg hover:bg-sidebar transition-colors"
>
<div className="flex items-start gap-3">
<span className="material-symbols-outlined text-primary mt-0.5">business</span>
+41 -13
View File
@@ -10,7 +10,7 @@ import { useCopyToClipboard } from "@/shared/hooks/useCopyToClipboard";
* - Localhost: Auto callback via popup message
* - Remote: Manual paste callback URL
*/
export default function OAuthModal({ isOpen, provider, providerInfo, onSuccess, onClose, oauthMeta }) {
export default function OAuthModal({ isOpen, provider, providerInfo, onSuccess, onClose, oauthMeta, idcConfig }) {
const [step, setStep] = useState("waiting"); // waiting | input | success | error
const [authData, setAuthData] = useState(null);
const [callbackUrl, setCallbackUrl] = useState("");
@@ -138,18 +138,30 @@ export default function OAuthModal({ isOpen, provider, providerInfo, onSuccess,
setIsDeviceCode(true);
setStep("waiting");
const res = await fetch(`/api/oauth/${provider}/device-code`);
const deviceCodeUrl = new URL(`/api/oauth/${provider}/device-code`, window.location.origin);
if (provider === "kiro" && idcConfig?.startUrl) {
deviceCodeUrl.searchParams.set("start_url", idcConfig.startUrl);
if (idcConfig.region) {
deviceCodeUrl.searchParams.set("region", idcConfig.region);
}
deviceCodeUrl.searchParams.set("auth_method", "idc");
}
const res = await fetch(deviceCodeUrl.toString());
const data = await res.json();
if (!res.ok) throw new Error(data.error);
setDeviceData(data);
// Open verification URL
const verifyUrl = data.verification_uri_complete || data.verification_uri;
if (verifyUrl) window.open(verifyUrl, "_blank");
// Pass extraData for Kiro (contains _clientId, _clientSecret)
const extraData = provider === "kiro" ? { _clientId: data._clientId, _clientSecret: data._clientSecret } : null;
const extraData = provider === "kiro"
? {
_clientId: data._clientId,
_clientSecret: data._clientSecret,
_region: data._region,
_authMethod: data._authMethod,
_startUrl: data._startUrl,
}
: null;
startPolling(data.device_code, data.codeVerifier, data.interval || 5, extraData);
return;
}
@@ -209,7 +221,7 @@ export default function OAuthModal({ isOpen, provider, providerInfo, onSuccess,
setError(err.message);
setStep("error");
}
}, [provider, isLocalhost, startPolling]);
}, [provider, isLocalhost, startPolling, oauthMeta, idcConfig]);
// Reset state and start OAuth when modal opens
useEffect(() => {
@@ -345,6 +357,7 @@ export default function OAuthModal({ isOpen, provider, providerInfo, onSuccess,
}, [onClose, provider]);
if (!provider || !providerInfo) return null;
const deviceLoginUrl = deviceData?.verification_uri_complete || deviceData?.verification_uri || "";
return (
<Modal isOpen={isOpen} title={`Connect ${providerInfo.name}`} onClose={handleClose} size="lg">
@@ -372,18 +385,28 @@ export default function OAuthModal({ isOpen, provider, providerInfo, onSuccess,
<>
<div className="text-center py-4">
<p className="text-sm text-text-muted mb-4">
Visit the URL below and enter the code:
Visit the login URL below and authorize:
</p>
<div className="bg-sidebar p-4 rounded-lg mb-4">
<p className="text-xs text-text-muted mb-1">Verification URL</p>
<p className="text-xs text-text-muted mb-1">Login URL</p>
<div className="flex items-center gap-2">
<code className="flex-1 text-sm break-all">{deviceData.verification_uri}</code>
<code className="flex-1 text-sm break-all">{deviceLoginUrl}</code>
<Button
size="sm"
variant="ghost"
icon={copied === "verify_url" ? "check" : "content_copy"}
onClick={() => copy(deviceData.verification_uri, "verify_url")}
icon={copied === "login_url" ? "check" : "content_copy"}
onClick={() => copy(deviceLoginUrl, "login_url")}
disabled={!deviceLoginUrl}
/>
<Button
size="sm"
variant="ghost"
icon="open_in_new"
onClick={() => window.open(deviceLoginUrl, "_blank", "noopener,noreferrer")}
disabled={!deviceLoginUrl}
>
Open
</Button>
</div>
</div>
<div className="bg-primary/10 p-4 rounded-lg">
@@ -494,4 +517,9 @@ OAuthModal.propTypes = {
onClose: PropTypes.func.isRequired,
/** Extra metadata passed to /authorize and /exchange (e.g. gitlab clientId/baseUrl) */
oauthMeta: PropTypes.object,
/** Optional Kiro IDC config for AWS IAM Identity Center device flow */
idcConfig: PropTypes.shape({
startUrl: PropTypes.string,
region: PropTypes.string,
}),
};