- Updated Kiro OAuth configuration

This commit is contained in:
decolua
2026-01-27 11:35:28 +07:00
parent 79342c0c3e
commit 31cc79ab23
29 changed files with 1165 additions and 26 deletions
@@ -195,7 +195,7 @@ export default function ClaudeToolCard({
<div className="flex items-center justify-between hover:cursor-pointer" onClick={onToggle}>
<div className="flex items-center gap-4">
<div className="size-12 flex items-center justify-center">
<Image src="/providers/claude.png" alt={tool.name} width={40} height={40} className="size-12 object-contain rounded-xl" style={{ width: "auto", height: "auto" }} onError={(e) => { e.target.style.display = "none"; }} />
<Image src="/providers/claude.png" alt={tool.name} width={40} height={40} className="size-12 object-contain rounded-xl max-w-[48px] max-h-[48px]" sizes="48px" onError={(e) => { e.target.style.display = "none"; }} />
</div>
<div>
<div className="flex items-center gap-2">
@@ -168,7 +168,7 @@ wire_api = "responses"
<div className="flex items-center justify-between hover:cursor-pointer" onClick={onToggle}>
<div className="flex items-center gap-4">
<div className="size-12 flex items-center justify-center">
<Image src="/providers/codex.png" alt={tool.name} width={40} height={40} className="size-12 object-contain rounded-xl" style={{ width: "auto", height: "auto" }} onError={(e) => { e.target.style.display = "none"; }} />
<Image src="/providers/codex.png" alt={tool.name} width={40} height={40} className="size-12 object-contain rounded-xl max-w-[48px] max-h-[48px]" sizes="48px" onError={(e) => { e.target.style.display = "none"; }} />
</div>
<div>
<div className="flex items-center gap-2">
@@ -235,8 +235,8 @@ export default function DefaultToolCard({ toolId, tool, isExpanded, onToggle, ba
alt={tool.name}
width={40}
height={40}
className="size-12 object-contain rounded-xl bg-gray-500"
style={{ width: "auto", height: "auto" }}
className="size-12 object-contain rounded-xl bg-gray-500 max-w-[48px] max-h-[48px]"
sizes="48px"
onError={(e) => { e.target.style.display = "none"; }}
/>
);
@@ -250,8 +250,8 @@ export default function DefaultToolCard({ toolId, tool, isExpanded, onToggle, ba
alt={tool.name}
width={40}
height={40}
className="size-10 object-contain rounded-xl"
style={{ width: "auto", height: "auto" }}
className="size-10 object-contain rounded-xl max-w-[40px] max-h-[40px]"
sizes="40px"
onError={(e) => { e.target.style.display = "none"; }}
/>
);
@@ -5,7 +5,7 @@ import PropTypes from "prop-types";
import { useParams } from "next/navigation";
import Link from "next/link";
import Image from "next/image";
import { Card, Button, Badge, Input, Modal, CardSkeleton, OAuthModal, Toggle } from "@/shared/components";
import { Card, Button, Badge, Input, Modal, CardSkeleton, OAuthModal, KiroOAuthWrapper, Toggle } from "@/shared/components";
import { OAUTH_PROVIDERS, APIKEY_PROVIDERS, getProviderAlias } from "@/shared/constants/providers";
import { getModelsByProviderId } from "@/shared/constants/models";
import { useCopyToClipboard } from "@/shared/hooks/useCopyToClipboard";
@@ -275,8 +275,8 @@ export default function ProviderDetailPage() {
alt={providerInfo.name}
width={48}
height={48}
className="object-contain rounded-lg"
style={{ width: "auto", height: "auto" }}
className="object-contain rounded-lg max-w-[48px] max-h-[48px]"
sizes="48px"
onError={(e) => { e.currentTarget.style.display = "none"; }}
/>
</div>
@@ -344,13 +344,22 @@ export default function ProviderDetailPage() {
</Card>
{/* Modals */}
<OAuthModal
isOpen={showOAuthModal}
provider={providerId}
providerInfo={providerInfo}
onSuccess={handleOAuthSuccess}
onClose={() => setShowOAuthModal(false)}
/>
{providerId === "kiro" ? (
<KiroOAuthWrapper
isOpen={showOAuthModal}
providerInfo={providerInfo}
onSuccess={handleOAuthSuccess}
onClose={() => setShowOAuthModal(false)}
/>
) : (
<OAuthModal
isOpen={showOAuthModal}
provider={providerId}
providerInfo={providerInfo}
onSuccess={handleOAuthSuccess}
onClose={() => setShowOAuthModal(false)}
/>
)}
<AddApiKeyModal
isOpen={showAddApiKeyModal}
provider={providerId}
@@ -155,8 +155,8 @@ function ProviderCard({ providerId, provider, stats }) {
alt={provider.name}
width={40}
height={40}
className="object-contain rounded-lg"
style={{ width: "auto", height: "auto" }}
className="object-contain rounded-lg max-w-[40px] max-h-[40px]"
sizes="40px"
onError={() => setImgError(true)}
/>
)}
+76
View File
@@ -0,0 +1,76 @@
import { NextResponse } from "next/server";
import { KiroService } from "@/lib/oauth/services/kiro";
import { createProviderConnection, isCloudEnabled } from "@/models";
import { getConsistentMachineId } from "@/shared/utils/machineId";
import { syncToCloud } from "@/app/api/sync/cloud/route";
/**
* POST /api/oauth/kiro/import
* Import and validate refresh token from Kiro IDE
*/
export async function POST(request) {
try {
const { refreshToken } = await request.json();
if (!refreshToken || typeof refreshToken !== "string") {
return NextResponse.json(
{ error: "Refresh token is required" },
{ status: 400 }
);
}
const kiroService = new KiroService();
// Validate and refresh token
const tokenData = await kiroService.validateImportToken(refreshToken.trim());
// Extract email from JWT if available
const email = kiroService.extractEmailFromJWT(tokenData.accessToken);
// Save to database
const connection = await createProviderConnection({
provider: "kiro",
authType: "oauth",
accessToken: tokenData.accessToken,
refreshToken: tokenData.refreshToken,
expiresAt: new Date(Date.now() + tokenData.expiresIn * 1000).toISOString(),
email: email || null,
providerSpecificData: {
profileArn: tokenData.profileArn,
authMethod: "imported",
provider: "Imported",
},
testStatus: "active",
});
// Auto sync to Cloud if enabled
await syncToCloudIfEnabled();
return NextResponse.json({
success: true,
connection: {
id: connection.id,
provider: connection.provider,
email: connection.email,
},
});
} catch (error) {
console.log("Kiro import token error:", error);
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
/**
* Sync to Cloud if enabled
*/
async function syncToCloudIfEnabled() {
try {
const cloudEnabled = await isCloudEnabled();
if (!cloudEnabled) return;
const machineId = await getConsistentMachineId();
await syncToCloud(machineId);
} catch (error) {
console.log("Error syncing to cloud after Kiro import:", error);
}
}
@@ -0,0 +1,43 @@
import { NextResponse } from "next/server";
import { generatePKCE } from "@/lib/oauth/utils/pkce";
import { KiroService } from "@/lib/oauth/services/kiro";
/**
* GET /api/oauth/kiro/social-authorize
* Generate Google/GitHub social login URL for manual callback flow
* Uses kiro:// custom protocol as required by AWS Cognito
*/
export async function GET(request) {
try {
const { searchParams } = new URL(request.url);
const provider = searchParams.get("provider"); // "google" or "github"
if (!provider || !["google", "github"].includes(provider)) {
return NextResponse.json(
{ error: "Invalid provider. Use 'google' or 'github'" },
{ status: 400 }
);
}
// Generate PKCE for social auth
const { codeVerifier, codeChallenge, state } = generatePKCE();
const kiroService = new KiroService();
const authUrl = kiroService.buildSocialLoginUrl(
provider,
codeChallenge,
state
);
return NextResponse.json({
authUrl,
state,
codeVerifier,
codeChallenge,
provider,
});
} catch (error) {
console.log("Kiro social authorize error:", error);
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
@@ -0,0 +1,87 @@
import { NextResponse } from "next/server";
import { KiroService } from "@/lib/oauth/services/kiro";
import { createProviderConnection, isCloudEnabled } from "@/models";
import { getConsistentMachineId } from "@/shared/utils/machineId";
import { syncToCloud } from "@/app/api/sync/cloud/route";
/**
* POST /api/oauth/kiro/social-exchange
* Exchange authorization code for tokens (Google/GitHub social login)
* Callback URL will be in format: kiro://kiro.kiroAgent/authenticate-success?code=XXX&state=YYY
*/
export async function POST(request) {
try {
const { code, codeVerifier, provider } = await request.json();
if (!code || !codeVerifier) {
return NextResponse.json(
{ error: "Missing required fields" },
{ status: 400 }
);
}
if (!provider || !["google", "github"].includes(provider)) {
return NextResponse.json(
{ error: "Invalid provider" },
{ status: 400 }
);
}
const kiroService = new KiroService();
// Exchange code for tokens (redirect_uri handled internally)
const tokenData = await kiroService.exchangeSocialCode(
code,
codeVerifier
);
// Extract email from JWT if available
const email = kiroService.extractEmailFromJWT(tokenData.accessToken);
// Save to database
const connection = await createProviderConnection({
provider: "kiro",
authType: "oauth",
accessToken: tokenData.accessToken,
refreshToken: tokenData.refreshToken,
expiresAt: new Date(Date.now() + tokenData.expiresIn * 1000).toISOString(),
email: email || null,
providerSpecificData: {
profileArn: tokenData.profileArn,
authMethod: provider, // "google" or "github"
provider: provider.charAt(0).toUpperCase() + provider.slice(1),
},
testStatus: "active",
});
// Auto sync to Cloud if enabled
await syncToCloudIfEnabled();
return NextResponse.json({
success: true,
connection: {
id: connection.id,
provider: connection.provider,
email: connection.email,
},
});
} catch (error) {
console.log("Kiro social exchange error:", error);
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
/**
* Sync to Cloud if enabled
*/
async function syncToCloudIfEnabled() {
try {
const cloudEnabled = await isCloudEnabled();
if (!cloudEnabled) return;
const machineId = await getConsistentMachineId();
await syncToCloud(machineId);
} catch (error) {
console.log("Error syncing to cloud after Kiro OAuth:", error);
}
}
+2 -1
View File
@@ -48,7 +48,8 @@ export default function FlowAnimation() {
alt={tool.name}
width={48}
height={48}
className="object-contain rounded-xl"
className="object-contain rounded-xl max-w-[48px] max-h-[48px]"
sizes="48px"
/>
</div>
</div>