mirror of
https://github.com/Nezumi-2711/9router.git
synced 2026-09-22 20:00:47 +00:00
* 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:
co-authored by
Copilot
parent
c3a2bd01b7
commit
b1288c5064
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
}),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user