Auth flow fixes (#35)

This commit is contained in:
Chris Estreich 2025-05-18 19:14:52 -07:00 committed by GitHub
parent 9662d16669
commit cfc93622f8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 258 additions and 97 deletions

View file

@ -11,7 +11,7 @@ export default async function AuthenticatedLayout({
const { orgId } = await auth();
if (!orgId) {
redirect('/onboarding/select-org');
redirect('/select-org');
}
return (

View file

@ -15,7 +15,7 @@ export const Org = () => {
<OrganizationProfile
routing="path"
path="/org"
afterLeaveOrganizationUrl="/onboarding/select-org"
afterLeaveOrganizationUrl="/select-org"
>
<OrganizationProfile.Page
label={t('provider_whitelist')}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,45 @@
'use client';
import { useEffect, useState } from 'react';
import Link from 'next/link';
import { Card, CardContent, CardFooter } from '@/components/ui';
import { VSCodeLogo } from './VSCodeLogo';
import { CursorLogo } from './CursorLogo';
interface DeepLinkProps {
vsCodeUrl: string;
cursorUrl: string;
}
export const DeepLink = ({ vsCodeUrl, cursorUrl }: DeepLinkProps) => {
const [redirectAttempted, setRedirectAttempted] = useState(false);
useEffect(() => {
window.location.href = vsCodeUrl;
const timer = setTimeout(() => setRedirectAttempted(true), 2000);
return () => clearTimeout(timer);
}, [vsCodeUrl]);
return (
<Card className="max-w-md mx-auto">
<CardContent className="flex flex-col gap-4">
<div>You&apos;ve successfully authenticated.</div>
<div className="flex flex-row items-center gap-4">
<div>Open in your IDE:</div>
<Link href={vsCodeUrl}>
<VSCodeLogo />
</Link>
<Link href={cursorUrl}>
<CursorLogo />
</Link>
</div>
</CardContent>
<CardFooter className="text-sm text-muted-foreground">
{redirectAttempted
? 'You can close this window after your IDE opens.'
: 'Redirecting automatically...'}
</CardFooter>
</Card>
);
};

View file

@ -0,0 +1,140 @@
import type { SVGProps } from 'react';
type VSCodeLogoProps = SVGProps<SVGSVGElement>;
export const VSCodeLogo = ({
width = '36',
height = '36',
...props
}: VSCodeLogoProps) => (
<svg
width={width}
height={height}
viewBox="0 0 100 100"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<mask
id="mask0"
type="alpha"
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="100"
height="100"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M70.9119 99.3171C72.4869 99.9307 74.2828 99.8914 75.8725 99.1264L96.4608 89.2197C98.6242 88.1787 100 85.9892 100 83.5872V16.4133C100 14.0113 98.6243 11.8218 96.4609 10.7808L75.8725 0.873756C73.7862 -0.130129 71.3446 0.11576 69.5135 1.44695C69.252 1.63711 69.0028 1.84943 68.769 2.08341L29.3551 38.0415L12.1872 25.0096C10.589 23.7965 8.35363 23.8959 6.86933 25.2461L1.36303 30.2549C-0.452552 31.9064 -0.454633 34.7627 1.35853 36.417L16.2471 50.0001L1.35853 63.5832C-0.454633 65.2374 -0.452552 68.0938 1.36303 69.7453L6.86933 74.7541C8.35363 76.1043 10.589 76.2037 12.1872 74.9905L29.3551 61.9587L68.769 97.9167C69.3925 98.5406 70.1246 99.0104 70.9119 99.3171ZM75.0152 27.2989L45.1091 50.0001L75.0152 72.7012V27.2989Z"
fill="white"
/>
</mask>
<g mask="url(#mask0)">
<path
d="M96.4614 10.7962L75.8569 0.875542C73.4719 -0.272773 70.6217 0.211611 68.75 2.08333L1.29858 63.5832C-0.515693 65.2373 -0.513607 68.0937 1.30308 69.7452L6.81272 74.754C8.29793 76.1042 10.5347 76.2036 12.1338 74.9905L93.3609 13.3699C96.086 11.3026 100 13.2462 100 16.6667V16.4275C100 14.0265 98.6246 11.8378 96.4614 10.7962Z"
fill="#0065A9"
/>
<g filter="url(#filter0_d)">
<path
d="M96.4614 89.2038L75.8569 99.1245C73.4719 100.273 70.6217 99.7884 68.75 97.9167L1.29858 36.4169C-0.515693 34.7627 -0.513607 31.9063 1.30308 30.2548L6.81272 25.246C8.29793 23.8958 10.5347 23.7964 12.1338 25.0095L93.3609 86.6301C96.086 88.6974 100 86.7538 100 83.3334V83.5726C100 85.9735 98.6246 88.1622 96.4614 89.2038Z"
fill="#007ACC"
/>
</g>
<g filter="url(#filter1_d)">
<path
d="M75.8578 99.1263C73.4721 100.274 70.6219 99.7885 68.75 97.9166C71.0564 100.223 75 98.5895 75 95.3278V4.67213C75 1.41039 71.0564 -0.223106 68.75 2.08329C70.6219 0.211402 73.4721 -0.273666 75.8578 0.873633L96.4587 10.7807C98.6234 11.8217 100 14.0112 100 16.4132V83.5871C100 85.9891 98.6234 88.1786 96.4586 89.2196L75.8578 99.1263Z"
fill="#1F9CF0"
/>
</g>
<g style={{ mixBlendMode: 'overlay' }} opacity="0.25">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M70.8511 99.3171C72.4261 99.9306 74.2221 99.8913 75.8117 99.1264L96.4 89.2197C98.5634 88.1787 99.9392 85.9892 99.9392 83.5871V16.4133C99.9392 14.0112 98.5635 11.8217 96.4001 10.7807L75.8117 0.873695C73.7255 -0.13019 71.2838 0.115699 69.4527 1.44688C69.1912 1.63705 68.942 1.84937 68.7082 2.08335L29.2943 38.0414L12.1264 25.0096C10.5283 23.7964 8.29285 23.8959 6.80855 25.246L1.30225 30.2548C-0.513334 31.9064 -0.515415 34.7627 1.29775 36.4169L16.1863 50L1.29775 63.5832C-0.515415 65.2374 -0.513334 68.0937 1.30225 69.7452L6.80855 74.754C8.29285 76.1042 10.5283 76.2036 12.1264 74.9905L29.2943 61.9586L68.7082 97.9167C69.3317 98.5405 70.0638 99.0104 70.8511 99.3171ZM74.9544 27.2989L45.0483 50L74.9544 72.7012V27.2989Z"
fill="url(#paint0_linear)"
/>
</g>
</g>
<defs>
<filter
id="filter0_d"
x="-8.39411"
y="15.8291"
width="116.727"
height="92.2456"
filterUnits="userSpaceOnUse"
colorInterpolationFilters="sRGB"
>
<feFlood floodOpacity="0" result="BackgroundImageFix" />
<feColorMatrix
in="SourceAlpha"
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
/>
<feOffset />
<feGaussianBlur stdDeviation="4.16667" />
<feColorMatrix
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"
/>
<feBlend
mode="overlay"
in2="BackgroundImageFix"
result="effect1_dropShadow"
/>
<feBlend
mode="normal"
in="SourceGraphic"
in2="effect1_dropShadow"
result="shape"
/>
</filter>
<filter
id="filter1_d"
x="60.4167"
y="-8.07558"
width="47.9167"
height="116.151"
filterUnits="userSpaceOnUse"
colorInterpolationFilters="sRGB"
>
<feFlood floodOpacity="0" result="BackgroundImageFix" />
<feColorMatrix
in="SourceAlpha"
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
/>
<feOffset />
<feGaussianBlur stdDeviation="4.16667" />
<feColorMatrix
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"
/>
<feBlend
mode="overlay"
in2="BackgroundImageFix"
result="effect1_dropShadow"
/>
<feBlend
mode="normal"
in="SourceGraphic"
in2="effect1_dropShadow"
result="shape"
/>
</filter>
<linearGradient
id="paint0_linear"
x1="49.9392"
y1="0.257812"
x2="49.9392"
y2="99.7423"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="white" />
<stop offset="1" stopColor="white" stopOpacity="0" />
</linearGradient>
</defs>
</svg>
);

View file

@ -17,13 +17,17 @@ export default async function Page(props: Props) {
redirect(`/sign-in`);
}
const { userId } = await auth();
const { userId, orgId } = await auth();
const code = userId ? await getSignInToken(userId) : undefined;
if (!code) {
redirect(`/sign-in?state=${state}`);
}
if (!orgId) {
redirect(`/select-org?state=${state}`);
}
const searchParams = new URLSearchParams({ state, code });
const path = `/auth/clerk/callback?${searchParams.toString()}`;
const vsCodeUrl = new URL(path, Env.VSCODE_EXTENSION_BASE_URL);

View file

@ -0,0 +1,22 @@
'use client';
import { useMemo } from 'react';
import { useSearchParams } from 'next/navigation';
import { OrganizationList } from '@clerk/nextjs';
export const SelectOrg = () => {
const searchParams = useSearchParams();
const afterSelectOrganizationUrl = useMemo(() => {
const state = searchParams.get('state');
return state ? `/extension/sign-in?state=${state}` : '/dashboard';
}, [searchParams]);
return (
<OrganizationList
afterSelectOrganizationUrl={afterSelectOrganizationUrl}
afterCreateOrganizationUrl={afterSelectOrganizationUrl}
hidePersonal
/>
);
};

View file

@ -0,0 +1,5 @@
import { SelectOrg } from './SelectOrg';
export default async function Page() {
return <SelectOrg />;
}

View file

@ -2,11 +2,8 @@
import { useMemo } from 'react';
import { useSearchParams } from 'next/navigation';
import Link from 'next/link';
import { SignIn as ClerkSignIn } from '@clerk/nextjs';
import { Logo } from '@/components/layout';
export const SignIn = () => {
const searchParams = useSearchParams();
@ -15,12 +12,5 @@ export const SignIn = () => {
return state ? `/extension/sign-in?state=${state}` : undefined;
}, [searchParams]);
return (
<div className="flex flex-col gap-8 h-screen w-full items-center justify-center">
<Link href="/">
<Logo />
</Link>
<ClerkSignIn forceRedirectUrl={forceRedirectUrl} />
</div>
);
return <ClerkSignIn forceRedirectUrl={forceRedirectUrl} />;
};

View file

@ -2,11 +2,8 @@
import { useMemo } from 'react';
import { useSearchParams } from 'next/navigation';
import Link from 'next/link';
import { SignUp as ClerkSignUp } from '@clerk/nextjs';
import { Logo } from '@/components/layout';
export const SignUp = () => {
const searchParams = useSearchParams();
@ -15,12 +12,5 @@ export const SignUp = () => {
return state ? `/extension/sign-in?state=${state}` : undefined;
}, [searchParams]);
return (
<div className="flex flex-col gap-8 h-screen w-full items-center justify-center">
<Link href="/">
<Logo />
</Link>
<ClerkSignUp forceRedirectUrl={forceRedirectUrl} />
</div>
);
return <ClerkSignUp forceRedirectUrl={forceRedirectUrl} />;
};

View file

@ -1,5 +0,0 @@
import { CreateOrganization } from '@clerk/nextjs';
export default async function Page() {
return <CreateOrganization afterCreateOrganizationUrl="/dashboard" />;
}

View file

@ -1,11 +0,0 @@
import { OrganizationList } from '@clerk/nextjs';
export default async function Page() {
return (
<OrganizationList
afterSelectOrganizationUrl="/dashboard"
afterCreateOrganizationUrl="/dashboard"
hidePersonal
/>
);
}

View file

@ -1,5 +0,0 @@
import { UserProfile } from '@clerk/nextjs';
export default function Page() {
return <UserProfile routing="path" path="/user" />;
}

View file

@ -1,51 +0,0 @@
'use client';
import { useEffect, useState } from 'react';
import Link from 'next/link';
import { Button, Card, CardContent, CardFooter } from '@/components/ui';
import { Logo } from '@/components/layout';
interface DeepLinkProps {
vsCodeUrl: string;
cursorUrl: string;
}
export const DeepLink = ({ vsCodeUrl, cursorUrl }: DeepLinkProps) => {
const [redirectAttempted, setRedirectAttempted] = useState(false);
useEffect(() => {
window.location.href = vsCodeUrl;
const timer = setTimeout(() => setRedirectAttempted(true), 2000);
return () => clearTimeout(timer);
}, [vsCodeUrl]);
return (
<div className="flex flex-col justify-center gap-4 max-w-md h-screen mx-auto">
<Link href="/" className="text-center">
<Logo />
</Link>
<Card>
<CardContent className="flex flex-col gap-4">
<div>
You&apos;ve successfully authenticated. We&apos;re attempting to
open VSCode with your credentials.
</div>
<div className="flex flex-row justify-between gap-4">
<Button variant="secondary" className="flex-1" asChild>
<Link href={vsCodeUrl}>Open in VSCode</Link>
</Button>
<Button variant="secondary" className="flex-1" asChild>
<Link href={cursorUrl}>Open in Cursor</Link>
</Button>
</div>
</CardContent>
<CardFooter className="text-sm text-muted-foreground">
{redirectAttempted
? 'You can close this window after VSCode opens.'
: 'Redirecting automatically...'}
</CardFooter>
</Card>
</div>
);
};

View file

@ -3,6 +3,7 @@ import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';
const isUnprotectedRoute = createRouteMatcher([
'/sign-in(.*)',
'/sign-up(.*)',
'/extension/sign-in(.*)',
'/',
]);