Allow extensions to auth

Add an endpoint to redirect to the extension with a Clerk sign in token.
This commit is contained in:
John Richmond 2025-05-12 23:07:31 -07:00
parent 6feaf74c60
commit 6a868d7c39
4 changed files with 68 additions and 0 deletions

2
.env
View file

@ -1,5 +1,7 @@
BILLING_PLAN_ENV=dev
VSCODE_EXTENSION_BASE_URL=vscode://RooVeterinaryInc.roo-cline
# Clerk
# https://clerk.com/docs/deployments/clerk-environment-variables#sign-in-and-sign-up-redirects
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in

View file

@ -0,0 +1,25 @@
import { getSignInToken } from '@/lib/server/clerk';
import { Env } from '@/lib/server/env';
import { auth } from '@clerk/nextjs/server';
import { redirect } from 'next/navigation';
import { type NextRequest } from 'next/server';
export async function GET(request: NextRequest) {
const authObj = await auth();
const userId = authObj.userId;
const state = request.nextUrl.searchParams.get('state') || '';
if (!userId) {
throw new Error('not logged in?');
}
const signInToken = await getSignInToken(authObj.userId);
if (!signInToken) {
throw new Error("couldn't sign in");
}
const url = new URL(`${Env.VSCODE_EXTENSION_BASE_URL}/auth/clerk/callback`);
url.searchParams.append('state', state);
url.searchParams.append('code', signInToken);
redirect(url.href);
}

39
src/lib/server/clerk.ts Normal file
View file

@ -0,0 +1,39 @@
import { Env } from './env';
import { logger } from './logger';
export async function getSignInToken(
userId: string,
): Promise<string | undefined> {
const response = await fetch('https://api.clerk.com/v1/sign_in_tokens', {
method: 'POST',
headers: {
Authorization: `Bearer ${Env.CLERK_SECRET_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
user_id: userId,
// Default expiration is 30 days (2592000 seconds)
}),
});
if (!response.ok) {
const errorData = await response.json();
logger.error({
event: 'sign_in_token_creation_failed',
error: errorData,
userId,
});
return undefined; // probably just throw
}
const data = await response.json();
logger.info({
event: 'sign_in_token_created',
userId,
data, // TODO: none of this, probably
});
return data.token;
}

View file

@ -9,6 +9,7 @@ export const Env = createEnv({
STRIPE_SECRET_KEY: z.string().min(1),
STRIPE_WEBHOOK_SECRET: z.string().min(1),
BILLING_PLAN_ENV: z.enum(['dev', 'test', 'prod']),
VSCODE_EXTENSION_BASE_URL: z.string().min(1),
},
client: {
NEXT_PUBLIC_APP_URL: z.string().optional(),
@ -32,6 +33,7 @@ export const Env = createEnv({
STRIPE_SECRET_KEY: process.env.STRIPE_SECRET_KEY,
STRIPE_WEBHOOK_SECRET: process.env.STRIPE_WEBHOOK_SECRET,
BILLING_PLAN_ENV: process.env.BILLING_PLAN_ENV,
VSCODE_EXTENSION_BASE_URL: process.env.VSCODE_EXTENSION_BASE_URL,
NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL,
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY:
process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY,