Add back vc auth temporarily to unblock things
This commit is contained in:
parent
07c2e7d36a
commit
177b2cc210
20 changed files with 350 additions and 40 deletions
|
|
@ -1,12 +1,12 @@
|
||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
import { getServerSession } from "next-auth";
|
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
|
import { getServerSession } from "@/lib/session/get-server-session";
|
||||||
import { revalidatePath } from "next/cache";
|
import { revalidatePath } from "next/cache";
|
||||||
import { authOptions } from "@/app/api/auth/[...nextauth]/route";
|
|
||||||
|
|
||||||
export async function removeChat({ id, path }: { id: string; path: string }) {
|
export async function removeChat({ id, path }: { id: string; path: string }) {
|
||||||
const session = await getServerSession(authOptions);
|
const session = await getServerSession();
|
||||||
|
|
||||||
const userId = session?.user?.email;
|
const userId = session?.user?.email;
|
||||||
if (!userId || !id) {
|
if (!userId || !id) {
|
||||||
throw new Error("Unauthorized");
|
throw new Error("Unauthorized");
|
||||||
|
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
import NextAuth, { NextAuthOptions } from "next-auth";
|
|
||||||
import { PrismaAdapter } from "@next-auth/prisma-adapter";
|
|
||||||
import { prisma } from "@/lib/prisma";
|
|
||||||
import GoogleProvider from "next-auth/providers/google";
|
|
||||||
|
|
||||||
export const authOptions: NextAuthOptions = {
|
|
||||||
adapter: PrismaAdapter(prisma),
|
|
||||||
providers: [
|
|
||||||
GoogleProvider({
|
|
||||||
clientId: process.env.GOOGLE_CLIENT_ID as string,
|
|
||||||
clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const handler = NextAuth(authOptions);
|
|
||||||
|
|
||||||
export { handler as GET, handler as POST };
|
|
||||||
62
app/api/auth/callback/route.ts
Normal file
62
app/api/auth/callback/route.ts
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
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,
|
||||||
|
// })
|
||||||
|
// ]
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
29
app/api/auth/login/route.ts
Normal file
29
app/api/auth/login/route.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
25
app/api/auth/logout/route.ts
Normal file
25
app/api/auth/logout/route.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
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',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { OpenAIStream, openai } from "@/lib/openai";
|
import { OpenAIStream, openai } from "@/lib/openai";
|
||||||
import { getServerSession } from "next-auth";
|
import { getServerSession } from "@/lib/session/get-server-session";
|
||||||
|
|
||||||
export const runtime = "edge";
|
export const runtime = "edge";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,7 @@ import { prisma } from "@/lib/prisma";
|
||||||
|
|
||||||
import { Chat } from "../../chat";
|
import { Chat } from "../../chat";
|
||||||
import { type Metadata } from "next";
|
import { type Metadata } from "next";
|
||||||
import { getServerSession } from "next-auth";
|
import { getServerSession } from "@/lib/session/get-server-session";
|
||||||
import { authOptions } from "@/app/api/auth/[...nextauth]/route";
|
|
||||||
|
|
||||||
export interface ChatPageProps {
|
export interface ChatPageProps {
|
||||||
params: {
|
params: {
|
||||||
|
|
@ -30,7 +29,7 @@ export const runtime = "nodejs"; // default
|
||||||
export const preferredRegion = "home";
|
export const preferredRegion = "home";
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
export default async function ChatPage({ params }: ChatPageProps) {
|
export default async function ChatPage({ params }: ChatPageProps) {
|
||||||
const session = await getServerSession(authOptions);
|
const session = await getServerSession();
|
||||||
const chat = await prisma.chat.findFirst({
|
const chat = await prisma.chat.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: params.id,
|
id: params.id,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { getServerSession } from "next-auth";
|
import { getServerSession } from "@/lib/session/get-server-session";
|
||||||
import { Chat } from "./chat";
|
import { Chat } from "./chat";
|
||||||
import { Sidebar } from "./sidebar";
|
import { Sidebar } from "./sidebar";
|
||||||
import { authOptions } from "@/app/api/auth/[...nextauth]/route";
|
|
||||||
|
|
||||||
// Prisma does not support Edge without the Data Proxy currently
|
// Prisma does not support Edge without the Data Proxy currently
|
||||||
export const runtime = "nodejs"; // default
|
export const runtime = "nodejs"; // default
|
||||||
|
|
@ -9,7 +8,7 @@ export const preferredRegion = "home";
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
export default async function IndexPage() {
|
export default async function IndexPage() {
|
||||||
const session = await getServerSession(authOptions);
|
const session = await getServerSession();
|
||||||
return (
|
return (
|
||||||
<div className="relative flex h-full w-full overflow-hidden">
|
<div className="relative flex h-full w-full overflow-hidden">
|
||||||
<Sidebar session={session} newChat />
|
<Sidebar session={session} newChat />
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,15 @@
|
||||||
import { Plus } from "lucide-react";
|
import { DevGPTLogo } from "@/components/ui/devgpt-logo";
|
||||||
import Link from "next/link";
|
|
||||||
import { Login } from "@/components/ui/login";
|
import { Login } from "@/components/ui/login";
|
||||||
import { UserMenu } from "@/components/ui/user-menu";
|
import { UserMenu } from "@/components/ui/user-menu";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
import { VercelLogo } from "@/components/ui/vercel-logo";
|
import { type Session } from "@/lib/session/types";
|
||||||
import { SidebarItem } from "./sidebar-item";
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { type Session } from "next-auth";
|
import { Plus } from "lucide-react";
|
||||||
import { DevGPTLogo } from "@/components/ui/devgpt-logo";
|
import Link from "next/link";
|
||||||
|
import { SidebarItem } from "./sidebar-item";
|
||||||
|
|
||||||
export interface SidebarProps {
|
export interface SidebarProps {
|
||||||
session: Session | null;
|
session?: Session;
|
||||||
newChat?: boolean;
|
newChat?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,16 +4,21 @@ import { useTheme } from "next-themes";
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Moon, Sun } from "lucide-react";
|
import { Moon, Sun } from "lucide-react";
|
||||||
|
import { useTransition } from "react";
|
||||||
|
|
||||||
export function ThemeToggle() {
|
export function ThemeToggle() {
|
||||||
const { setTheme, theme } = useTheme();
|
const { setTheme, theme } = useTheme();
|
||||||
|
const [_, startTransition] = useTransition();
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="xs"
|
size="xs"
|
||||||
className="p-0 leading-4 h-4 font-normal w-full"
|
className="p-0 leading-4 h-4 font-normal w-full"
|
||||||
onClick={() => setTheme(theme === "light" ? "dark" : "light")}
|
onClick={() => {
|
||||||
|
startTransition(() => {
|
||||||
|
setTheme(theme === "light" ? "dark" : "light");
|
||||||
|
});
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<span className="flex flex-row justify-between text-xs text-zinc-900 dark:text-zinc-400 w-full">
|
<span className="flex flex-row justify-between text-xs text-zinc-900 dark:text-zinc-400 w-full">
|
||||||
<span className="">Toggle theme</span>
|
<span className="">Toggle theme</span>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
"use client";
|
"use client";
|
||||||
import { ThemeToggle } from "@/components/theme-toggle";
|
import { ThemeToggle } from "@/components/theme-toggle";
|
||||||
import { type Session } from "next-auth";
|
import { type Session } from "@/lib/session/types";
|
||||||
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
|
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
|
||||||
|
|
@ -14,12 +14,12 @@ export function UserMenu({ session }: UserMenuProps) {
|
||||||
<DropdownMenu.Root>
|
<DropdownMenu.Root>
|
||||||
<DropdownMenu.Trigger asChild>
|
<DropdownMenu.Trigger asChild>
|
||||||
<button className="focus:outline-none">
|
<button className="focus:outline-none">
|
||||||
{session.user?.image ? (
|
{session.user?.avatar ? (
|
||||||
<Image
|
<Image
|
||||||
width={24}
|
width={24}
|
||||||
height={24}
|
height={24}
|
||||||
className="h-6 w-6 rounded-full select-none ring-zinc-100/10 ring-1 hover:opacity-80 transition-opacity duration-300"
|
className="h-6 w-6 rounded-full select-none ring-zinc-100/10 ring-1 hover:opacity-80 transition-opacity duration-300"
|
||||||
src={session.user?.image ? `${session.user.image}&s=60` : ""}
|
src={session.user?.avatar ? `${session.user.avatar}&s=60` : ""}
|
||||||
alt={session.user.name ?? "Avatar"}
|
alt={session.user.name ?? "Avatar"}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
||||||
25
lib/cookies/decrypt-jwe.ts
Normal file
25
lib/cookies/decrypt-jwe.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { jwtDecrypt, base64url } from 'jose';
|
||||||
|
|
||||||
|
export async function decryptJWECookie<T extends string | object = any>(
|
||||||
|
cookie: string,
|
||||||
|
secret: string | undefined = process.env.JWE_SECRET
|
||||||
|
): Promise<T | undefined> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
16
lib/cookies/encrypt-jwe.ts
Normal file
16
lib/cookies/encrypt-jwe.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { EncryptJWT, base64url } from 'jose';
|
||||||
|
|
||||||
|
export async function encryptJWECookie<T extends string | object = any>(
|
||||||
|
payload: T,
|
||||||
|
expirationTime: string,
|
||||||
|
secret: string | undefined = process.env.JWE_SECRET
|
||||||
|
): Promise<string> {
|
||||||
|
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));
|
||||||
|
}
|
||||||
1
lib/session/constants.ts
Normal file
1
lib/session/constants.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export const userSessionCookieName = 'user_session';
|
||||||
116
lib/session/create.ts
Normal file
116
lib/session/create.ts
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
import { type Session } from './types';
|
||||||
|
|
||||||
|
export async function createSession(vercelToken: string): Promise<Session> {
|
||||||
|
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';
|
||||||
|
}
|
||||||
8
lib/session/get-server-session.ts
Normal file
8
lib/session/get-server-session.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
23
lib/session/server.ts
Normal file
23
lib/session/server.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
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<Session>(cookieValue);
|
||||||
|
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getSessionFromReq(
|
||||||
|
req: NextApiRequest | NextRequest
|
||||||
|
): Promise<Session | undefined> {
|
||||||
|
const cookieValue =
|
||||||
|
'get' in req.cookies && typeof req.cookies.get === 'function'
|
||||||
|
? req.cookies.get(userSessionCookieName)?.value
|
||||||
|
: (req.cookies as Record<string, string>)[userSessionCookieName];
|
||||||
|
return getSessionFromCookie(cookieValue);
|
||||||
|
}
|
||||||
13
lib/session/types.ts
Normal file
13
lib/session/types.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { BillingPlan } from './create';
|
||||||
|
|
||||||
|
export interface Session {
|
||||||
|
vercelToken: string;
|
||||||
|
user: {
|
||||||
|
id: string;
|
||||||
|
username: string;
|
||||||
|
email: string;
|
||||||
|
avatar: string;
|
||||||
|
name?: string;
|
||||||
|
plan: BillingPlan;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -27,6 +27,7 @@
|
||||||
"eventsource-parser": "^1.0.0",
|
"eventsource-parser": "^1.0.0",
|
||||||
"focus-trap-react": "^10.1.1",
|
"focus-trap-react": "^10.1.1",
|
||||||
"framer-motion": "^10.12.4",
|
"framer-motion": "^10.12.4",
|
||||||
|
"jose": "^4.14.4",
|
||||||
"jsonwebtoken": "^9.0.0",
|
"jsonwebtoken": "^9.0.0",
|
||||||
"lucide-react": "0.216.0",
|
"lucide-react": "0.216.0",
|
||||||
"nanoid": "^4.0.2",
|
"nanoid": "^4.0.2",
|
||||||
|
|
|
||||||
7
pnpm-lock.yaml
generated
7
pnpm-lock.yaml
generated
|
|
@ -37,6 +37,9 @@ dependencies:
|
||||||
framer-motion:
|
framer-motion:
|
||||||
specifier: ^10.12.4
|
specifier: ^10.12.4
|
||||||
version: 10.12.9(react-dom@18.2.0)(react@18.2.0)
|
version: 10.12.9(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
jose:
|
||||||
|
specifier: ^4.14.4
|
||||||
|
version: 4.14.4
|
||||||
jsonwebtoken:
|
jsonwebtoken:
|
||||||
specifier: ^9.0.0
|
specifier: ^9.0.0
|
||||||
version: 9.0.0
|
version: 9.0.0
|
||||||
|
|
@ -2864,6 +2867,10 @@ packages:
|
||||||
'@panva/asn1.js': 1.0.0
|
'@panva/asn1.js': 1.0.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/jose@4.14.4:
|
||||||
|
resolution: {integrity: sha512-j8GhLiKmUAh+dsFXlX1aJCbt5KMibuKb+d7j1JaOJG6s2UjX1PQlW+OKB/sD4a/5ZYF4RcmYmLSndOoU3Lt/3g==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/js-sdsl@4.4.0:
|
/js-sdsl@4.4.0:
|
||||||
resolution: {integrity: sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==}
|
resolution: {integrity: sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue