From 1af7fb3dfcedc85e4b761d6aa8091f1fa0f0feba Mon Sep 17 00:00:00 2001 From: Jared Palmer Date: Mon, 22 May 2023 10:32:06 -0400 Subject: [PATCH] Remove vercel auth --- app/actions.ts | 4 +- app/api/auth/callback/route.ts | 62 ---------------- app/api/auth/login/route.ts | 29 -------- app/api/auth/logout/route.ts | 25 ------- lib/cookies/decrypt-jwe.ts | 25 ------- lib/cookies/encrypt-jwe.ts | 16 ----- lib/session/constants.ts | 1 - lib/session/create.ts | 116 ------------------------------ lib/session/get-server-session.ts | 8 --- lib/session/server.ts | 23 ------ lib/session/types.ts | 13 ---- 11 files changed, 2 insertions(+), 320 deletions(-) delete mode 100644 app/api/auth/callback/route.ts delete mode 100644 app/api/auth/login/route.ts delete mode 100644 app/api/auth/logout/route.ts delete mode 100644 lib/cookies/decrypt-jwe.ts delete mode 100644 lib/cookies/encrypt-jwe.ts delete mode 100644 lib/session/constants.ts delete mode 100644 lib/session/create.ts delete mode 100644 lib/session/get-server-session.ts delete mode 100644 lib/session/server.ts delete mode 100644 lib/session/types.ts diff --git a/app/actions.ts b/app/actions.ts index 3c6967c..cab6259 100644 --- a/app/actions.ts +++ b/app/actions.ts @@ -1,11 +1,11 @@ "use server"; +import { auth } from "@/auth"; import { prisma } from "@/lib/prisma"; -import { getServerSession } from "@/lib/session/get-server-session"; import { revalidatePath } from "next/cache"; export async function removeChat({ id, path }: { id: string; path: string }) { - const session = await getServerSession(); + const session = await auth(); const userId = session?.user?.email; if (!userId || !id) { diff --git a/app/api/auth/callback/route.ts b/app/api/auth/callback/route.ts deleted file mode 100644 index a235634..0000000 --- a/app/api/auth/callback/route.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { NextRequest, NextResponse } from 'next/server'; -import { serialize } from 'cookie'; -import { createSession } from '@/lib/session/create'; -import { encryptJWECookie } from '@/lib/cookies/encrypt-jwe'; -import ms from 'ms'; -import { userSessionCookieName } from '@/lib/session/constants'; - -export const runtime = 'edge'; - -const sharedTokenSecret = process.env.SHARE_TOKEN_ENDPOINT_SECRET; -const cookieTTL = ms('1y'); - -export async function GET(req: NextRequest) { - const { origin, searchParams } = req.nextUrl; - const shareableToken = searchParams.get('shareableToken'); - - if (!sharedTokenSecret || !shareableToken) { - // TODO: Make an error page - return NextResponse.json({ error: 'Invalid request' }, { status: 500 }); - } - - const query = new URLSearchParams({ - sharedTokenSecret, - }); - - const response = await fetch( - `https://api.vercel.com/user/tokens/shared/${shareableToken}?${query.toString()}` - ); - - const { token } = (await response.json()) as { token: string }; - - const session = await createSession(token); - - const cookieValue = await encryptJWECookie(session, '1y'); - const next = req.cookies.get('auth-next')?.value; - - return NextResponse.redirect(new URL(next ?? '/', origin), { - headers: [ - [ - 'set-cookie', - serialize(userSessionCookieName, cookieValue, { - path: '/', - maxAge: cookieTTL / 1000, - expires: new Date(Date.now() + cookieTTL), - httpOnly: true, - secure: process.env.NODE_ENV !== 'development', - }), - ], - // There's currently an issue with adding more than one cookie to a response: https://github.com/vercel/next.js/issues/46579 - // the `auth-next` cookie will expire in 10 min anyway - // [ - // 'set-cookie', - // serialize('auth-next', '', { - // path: '/api/auth/callback', - // maxAge: 0, - // expires: new Date(), - // httpOnly: true, - // }) - // ] - ], - }); -} diff --git a/app/api/auth/login/route.ts b/app/api/auth/login/route.ts deleted file mode 100644 index 1bd23cc..0000000 --- a/app/api/auth/login/route.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { NextRequest, NextResponse } from 'next/server'; -import { serialize } from 'cookie'; - -export const runtime = 'edge'; - -export async function GET(req: NextRequest) { - const { origin } = req.nextUrl; - - const next = req.nextUrl.searchParams.get('next'); - - const params = new URLSearchParams({ - redirectUrl: new URL('/api/auth/callback', origin).toString(), - }); - - return NextResponse.redirect( - `https://vercel.com/api/vercel-auth?${params.toString()}`, - { - headers: next - ? { - 'Set-Cookie': serialize('auth-next', next, { - path: '/api/auth/callback', - maxAge: 60 * 60 * 10, - httpOnly: true, - }), - } - : undefined, - } - ); -} diff --git a/app/api/auth/logout/route.ts b/app/api/auth/logout/route.ts deleted file mode 100644 index a8b2768..0000000 --- a/app/api/auth/logout/route.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { userSessionCookieName } from '@/lib/session/constants'; -import { serialize } from 'cookie'; -import { NextRequest, NextResponse } from 'next/server'; - -export const runtime = 'edge'; - -export async function GET(req: NextRequest) { - const { origin, searchParams } = req.nextUrl; - const next = searchParams.get('next'); - - return NextResponse.redirect(new URL(next ?? '/', origin), { - headers: [ - [ - 'set-cookie', - serialize(userSessionCookieName, '', { - path: '/', - maxAge: 0, - expires: new Date(), - httpOnly: true, - secure: process.env.NODE_ENV !== 'development', - }), - ], - ], - }); -} diff --git a/lib/cookies/decrypt-jwe.ts b/lib/cookies/decrypt-jwe.ts deleted file mode 100644 index 70e05bf..0000000 --- a/lib/cookies/decrypt-jwe.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { jwtDecrypt, base64url } from 'jose'; - -export async function decryptJWECookie( - cookie: string, - secret: string | undefined = process.env.JWE_SECRET -): Promise { - if (!secret) { - throw new Error('Missing JWE secret'); - } - - if (typeof cookie !== 'string') return; - - try { - const { payload } = await jwtDecrypt(cookie, base64url.decode(secret)); - const decoded = payload as T; - - // @ts-expect-error jwt field - delete decoded.iat; - // @ts-expect-error jwt field - delete decoded.exp; - return decoded; - } catch { - // Do nothing - } -} diff --git a/lib/cookies/encrypt-jwe.ts b/lib/cookies/encrypt-jwe.ts deleted file mode 100644 index d51bb71..0000000 --- a/lib/cookies/encrypt-jwe.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { EncryptJWT, base64url } from 'jose'; - -export async function encryptJWECookie( - payload: T, - expirationTime: string, - secret: string | undefined = process.env.JWE_SECRET -): Promise { - if (!secret) { - throw new Error('Missing JWE secret'); - } - - return new EncryptJWT(payload as any) - .setExpirationTime(expirationTime) - .setProtectedHeader({ alg: 'dir', enc: 'A256GCM' }) - .encrypt(base64url.decode(secret)); -} diff --git a/lib/session/constants.ts b/lib/session/constants.ts deleted file mode 100644 index 9fa6e88..0000000 --- a/lib/session/constants.ts +++ /dev/null @@ -1 +0,0 @@ -export const userSessionCookieName = 'user_session'; diff --git a/lib/session/create.ts b/lib/session/create.ts deleted file mode 100644 index 3583f31..0000000 --- a/lib/session/create.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { type Session } from './types'; - -export async function createSession(vercelToken: string): Promise { - const [user, teams] = await Promise.all([ - fetchUser(vercelToken), - fetchTeams(vercelToken), - ]); - const plan = getHighestAccountLevel(teams); - return { - user: { - id: user.uid, - name: user.name, - username: user.username, - email: user.email, - avatar: `https://vercel.com/api/www/avatar/?u=${user.username}`, - plan, - }, - vercelToken, - }; -} - -interface VercelUser { - uid: string; - username: string; - email: string; - name: string; - avatar: string; -} - -type BillingStatus = 'active' | 'trialing' | 'overdue' | 'canceled' | 'expired'; - -export const billingPlans = ['hobby', 'pro', 'enterprise'] as const; - -export type BillingPlan = (typeof billingPlans)[number]; - -export interface Billing { - addons?: string | null; - language?: string | null; - plan: BillingPlan; - // TODO: Make this required once the migration has completed - status?: BillingStatus; - name?: string | null; - overdue?: boolean | null; - period: { start: number; end: number } | null; - purchaseOrder?: string | null; - platform?: 'stripe' | 'stripeTestMode'; - email?: string; - trial: { start: number; end: number } | null; -} -export interface VercelTeam { - id: string; - slug: string; - name: string; - avatar?: string; - billing?: Billing; - created?: string; -} -async function fetchUser(token: string) { - const response = await fetch('https://vercel.com/api/user', { - headers: { - Authorization: `Bearer ${token}`, - }, - }); - - const { user } = (await response.json()) as { user: VercelUser }; - - return user; -} -export async function fetchTeams(token: string) { - const response = await fetch('https://vercel.com/api/teams', { - headers: { - Authorization: `Bearer ${token}`, - }, - }); - - const { teams } = (await response.json()) as { teams: VercelTeam[] }; - return teams; -} - -export function hasActiveProOrEnterpriseAccount(teams: VercelTeam[]) { - return teams.some( - (team) => - (team.billing?.plan === 'pro' && team.billing.status === 'active') || - (team.billing?.plan === 'enterprise' && team.billing.status === 'active') - ); -} - -export function hasActiveProAccount(teams: VercelTeam[]) { - return teams.some( - (team) => team.billing?.plan === 'pro' && team.billing.status === 'active' - ); -} - -export function isInTrial(teams: VercelTeam[]) { - return ( - !hasActiveProOrEnterpriseAccount(teams) && - teams.some( - (team) => - team.billing?.plan === 'pro' && team.billing.status === 'trialing' - ) - ); -} - -export function getHighestAccountLevel(teams: VercelTeam[]): BillingPlan { - const plans = teams - .filter( - (team) => team.billing?.plan && team.billing.status === 'active' - // (team.billing?.plan && team.billing.status === 'trialing') - ) - .map((team) => team.billing?.plan); - return plans.includes('enterprise') - ? 'enterprise' - : plans.includes('pro') - ? 'pro' - : 'hobby'; -} diff --git a/lib/session/get-server-session.ts b/lib/session/get-server-session.ts deleted file mode 100644 index 8209abd..0000000 --- a/lib/session/get-server-session.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { cookies } from 'next/headers'; -import { userSessionCookieName } from './constants'; -import { getSessionFromCookie } from './server'; - -export async function getServerSession() { - const cookieValue = cookies().get(userSessionCookieName)?.value; - return getSessionFromCookie(cookieValue); -} diff --git a/lib/session/server.ts b/lib/session/server.ts deleted file mode 100644 index 1d79ce0..0000000 --- a/lib/session/server.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { type NextApiRequest } from 'next'; -import { type NextRequest } from 'next/server'; -import { userSessionCookieName } from './constants'; -import { decryptJWECookie } from '../cookies/decrypt-jwe'; -import { type Session } from './types'; - -export async function getSessionFromCookie(cookieValue?: string) { - if (!cookieValue) return; - - const session = await decryptJWECookie(cookieValue); - - return session; -} - -export async function getSessionFromReq( - req: NextApiRequest | NextRequest -): Promise { - const cookieValue = - 'get' in req.cookies && typeof req.cookies.get === 'function' - ? req.cookies.get(userSessionCookieName)?.value - : (req.cookies as Record)[userSessionCookieName]; - return getSessionFromCookie(cookieValue); -} diff --git a/lib/session/types.ts b/lib/session/types.ts deleted file mode 100644 index 8d62689..0000000 --- a/lib/session/types.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { BillingPlan } from './create'; - -export interface Session { - vercelToken: string; - user: { - id: string; - username: string; - email: string; - avatar: string; - name?: string; - plan: BillingPlan; - }; -}