-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
6a05f6f
commit 02e2356
Showing
12 changed files
with
348 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
"use client"; | ||
|
||
import { ConnectWalletButton } from "@/components/ConnectWalletButton"; | ||
import { useAddressVerificationStatus } from "@/hooks/useAddressVerificationStatus"; | ||
import { useCreateChallenge } from "@/hooks/useCreateChallenge"; | ||
import { useSolveChallenge } from "@/hooks/useSolveChallenge"; | ||
import { Button } from "@inkonchain/ink-kit"; | ||
import { useTranslations } from "next-intl"; | ||
import { useState } from "react"; | ||
import type { FC } from "react"; | ||
import { useAccount, useSignMessage } from "wagmi"; | ||
import { useRouter } from "next/navigation"; | ||
|
||
interface VerifyCtaProps { | ||
className?: string; | ||
} | ||
|
||
export const VerifyCta: FC<VerifyCtaProps> = ({ className }) => { | ||
const t = useTranslations("Verify"); | ||
const [isProving, setIsProving] = useState(false); | ||
const { isConnected, address, isConnecting, isReconnecting } = useAccount(); | ||
const { data: verificationStatus, isLoading: isCheckingVerification } = | ||
useAddressVerificationStatus(address); | ||
const createChallenge = useCreateChallenge(); | ||
const { signMessageAsync } = useSignMessage(); | ||
const solveChallenge = useSolveChallenge(); | ||
const router = useRouter(); | ||
|
||
const isLoading = | ||
isConnecting || | ||
isReconnecting || | ||
isCheckingVerification || | ||
isProving || | ||
createChallenge.isPending || | ||
solveChallenge.isPending; | ||
|
||
const handleProveIdentity = async () => { | ||
if (!address) return; | ||
|
||
try { | ||
setIsProving(true); | ||
|
||
// Create challenge | ||
const challenge = await createChallenge.mutateAsync({ | ||
user_address: address, | ||
}); | ||
|
||
// Sign the challenge message | ||
const signature = await signMessageAsync({ | ||
message: challenge.message, | ||
}); | ||
|
||
// Solve challenge | ||
const solution = await solveChallenge.mutateAsync({ | ||
challenge_id: challenge.id, | ||
challenge_signature: signature, | ||
}); | ||
|
||
// Redirect to Kraken OAuth if challenge was solved | ||
if (solution.solved) { | ||
router.push(solution.oauth_url); | ||
} | ||
} catch (error) { | ||
console.error("Error during verification:", error); | ||
setIsProving(false); | ||
} | ||
}; | ||
|
||
// Loading skeleton with same height as final content | ||
if (isLoading) { | ||
return ( | ||
<div className={`relative w-80 ${className ?? ""}`}> | ||
<div className="h-12 animate-pulse rounded-full bg-whiteMagic dark:bg-gray-800" /> | ||
</div> | ||
); | ||
} | ||
|
||
// Show verified state | ||
if (verificationStatus?.isVerified) { | ||
return ( | ||
<p className="text-center text-lg font-medium text-green-600"> | ||
✓ Your address is verified | ||
</p> | ||
); | ||
} | ||
|
||
// Show action buttons | ||
return ( | ||
<div className={`relative w-full ${className ?? ""}`}> | ||
{isConnected ? ( | ||
<Button | ||
size="lg" | ||
variant="primary" | ||
onClick={handleProveIdentity} | ||
disabled={isLoading} | ||
> | ||
{t("proveIdentity")} | ||
</Button> | ||
) : ( | ||
<ConnectWalletButton connectLabel={t("cta")} size="lg" /> | ||
)} | ||
</div> | ||
); | ||
}; | ||
|
||
VerifyCta.displayName = "VerifyCta"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { env } from "@/env"; | ||
import { NextResponse } from "next/server"; | ||
|
||
export async function GET(request: Request) { | ||
try { | ||
const targetUrl = new URL( | ||
`${env.INK_VERIFY_API_BASE_URL}/v1/auth/callback/kraken` | ||
); | ||
targetUrl.search = new URL(request.url).searchParams.toString(); | ||
|
||
await fetch(targetUrl); | ||
} catch (error) { | ||
console.error("OAuth callback error:", error); | ||
} | ||
|
||
return NextResponse.redirect(new URL("/verify?verifyPage=true", request.url)); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { env } from "@/env"; | ||
import { NextResponse } from "next/server"; | ||
|
||
export async function POST(request: Request) { | ||
try { | ||
const body = await request.json(); | ||
|
||
const response = await fetch( | ||
`${env.INK_VERIFY_API_BASE_URL}/v1/auth/challenge/new`, | ||
{ | ||
method: request.method, | ||
headers: request.headers, | ||
body: JSON.stringify(body), | ||
} | ||
); | ||
|
||
return new NextResponse(response.body, { | ||
status: response.status, | ||
statusText: response.statusText, | ||
headers: response.headers, | ||
}); | ||
} catch (error) { | ||
console.error("Proxy error:", error); | ||
return NextResponse.json( | ||
{ error: "Failed to proxy request" }, | ||
{ status: 500 } | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { env } from "@/env"; | ||
import { NextResponse } from "next/server"; | ||
|
||
export async function POST(request: Request) { | ||
try { | ||
const body = await request.json(); | ||
|
||
const response = await fetch( | ||
`${env.INK_VERIFY_API_BASE_URL}/v1/auth/challenge/solve`, | ||
{ | ||
method: request.method, | ||
headers: request.headers, | ||
body: JSON.stringify(body), | ||
} | ||
); | ||
|
||
return new NextResponse(response.body, { | ||
status: response.status, | ||
statusText: response.statusText, | ||
headers: response.headers, | ||
}); | ||
} catch (error) { | ||
console.error("Proxy error:", error); | ||
return NextResponse.json( | ||
{ error: "Failed to proxy request" }, | ||
{ status: 500 } | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { env } from "@/env"; | ||
import { NextResponse } from "next/server"; | ||
import { z } from "zod"; | ||
|
||
// We keep minimal validation just to ensure the address parameter exists | ||
const RequestParamsSchema = z.object({ | ||
address: z.string().min(1), | ||
}); | ||
|
||
export async function GET( | ||
request: Request, | ||
{ params }: { params: Promise<{ address: string }> } | ||
) { | ||
try { | ||
// Minimal validation of the address parameter | ||
const { address } = RequestParamsSchema.parse(await params); | ||
|
||
// Construct the target URL | ||
const targetUrl = `${env.INK_VERIFY_API_BASE_URL}/v1/auth/verifications/${address}`; | ||
|
||
// Forward the request with all its original headers | ||
const response = await fetch(targetUrl, { | ||
method: request.method, | ||
headers: request.headers, | ||
}); | ||
|
||
// Return the response as-is | ||
return new NextResponse(response.body, { | ||
status: response.status, | ||
statusText: response.statusText, | ||
headers: response.headers, | ||
}); | ||
} catch (error) { | ||
console.error("Proxy error:", error); | ||
return NextResponse.json( | ||
{ error: "Failed to proxy request" }, | ||
{ status: 500 } | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import { useQuery } from "@tanstack/react-query"; | ||
import { z } from "zod"; | ||
|
||
const ResponseSchema = z.object({ | ||
is_verified: z.boolean(), | ||
}); | ||
|
||
type VerificationResponse = { | ||
isVerified: boolean; | ||
}; | ||
|
||
export function useAddressVerificationStatus(address: string | undefined) { | ||
return useQuery<VerificationResponse, Error>({ | ||
queryKey: ["verification-status", address], | ||
queryFn: async () => { | ||
if (!address) { | ||
throw new Error("No address provided"); | ||
} | ||
|
||
const response = await fetch(`/api/auth/verification/${address}`); | ||
|
||
if (!response.ok) { | ||
throw new Error( | ||
`Verification check failed: ${response.status} ${response.statusText}` | ||
); | ||
} | ||
|
||
const data = await response.json(); | ||
|
||
// Validate and transform the response | ||
const validated = ResponseSchema.parse(data); | ||
return { | ||
isVerified: validated.is_verified, | ||
}; | ||
}, | ||
enabled: Boolean(address), | ||
staleTime: 60 * 1000, | ||
retry: 3, | ||
refetchOnWindowFocus: true, | ||
refetchOnMount: true, | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { useMutation } from "@tanstack/react-query"; | ||
import { z } from "zod"; | ||
|
||
export const ChallengeResponseSchema = z.object({ | ||
id: z.string(), | ||
user_address: z.string(), | ||
message: z.string(), | ||
}); | ||
|
||
export type Challenge = z.infer<typeof ChallengeResponseSchema>; | ||
|
||
export const useCreateChallenge = () => { | ||
return useMutation<Challenge, Error, { user_address: string }>({ | ||
mutationFn: async (variables) => { | ||
const response = await fetch("/api/auth/challenge/new", { | ||
method: "POST", | ||
headers: { | ||
"Content-Type": "application/json", | ||
}, | ||
body: JSON.stringify(variables), | ||
}); | ||
|
||
if (!response.ok) { | ||
throw new Error("Failed to create challenge"); | ||
} | ||
|
||
const data = await response.json(); | ||
return ChallengeResponseSchema.parse(data); | ||
}, | ||
}); | ||
}; |
Oops, something went wrong.