Move to next-auth (#4)
This commit is contained in:
commit
f14e73ac8f
22 changed files with 114 additions and 344 deletions
|
|
@ -1,11 +1,11 @@
|
||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
|
import { auth } from "@/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";
|
||||||
|
|
||||||
export async function removeChat({ id, path }: { id: string; path: string }) {
|
export async function removeChat({ id, path }: { id: string; path: string }) {
|
||||||
const session = await getServerSession();
|
const session = await auth();
|
||||||
|
|
||||||
const userId = session?.user?.email;
|
const userId = session?.user?.email;
|
||||||
if (!userId || !id) {
|
if (!userId || !id) {
|
||||||
|
|
|
||||||
2
app/api/auth/[...nextauth]/route.ts
Normal file
2
app/api/auth/[...nextauth]/route.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
export { GET, POST } from "@/auth";
|
||||||
|
export const runtime = "edge";
|
||||||
|
|
@ -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,
|
|
||||||
// })
|
|
||||||
// ]
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
@ -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,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -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',
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
|
import { auth } from "@/auth";
|
||||||
import { OpenAIStream, openai } from "@/lib/openai";
|
import { OpenAIStream, openai } from "@/lib/openai";
|
||||||
import { getServerSession } from "@/lib/session/get-server-session";
|
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
|
|
||||||
export async function POST(req: Request) {
|
export const POST = auth(async function POST(req: Request) {
|
||||||
const json = await req.json();
|
const json = await req.json();
|
||||||
const session = await getServerSession();
|
// @ts-ignore
|
||||||
|
console.log(req.auth); // todo fix types
|
||||||
|
|
||||||
const res = await openai.createChatCompletion({
|
const res = await openai.createChatCompletion({
|
||||||
model: "gpt-3.5-turbo",
|
model: "gpt-3.5-turbo",
|
||||||
|
|
@ -41,4 +42,4 @@ export async function POST(req: Request) {
|
||||||
status: 200,
|
status: 200,
|
||||||
headers: { "Content-Type": "text/event-stream" },
|
headers: { "Content-Type": "text/event-stream" },
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
|
|
|
||||||
|
|
@ -3,7 +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 "@/lib/session/get-server-session";
|
import { auth } from "@/auth";
|
||||||
|
|
||||||
export interface ChatPageProps {
|
export interface ChatPageProps {
|
||||||
params: {
|
params: {
|
||||||
|
|
@ -29,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();
|
const session = await auth();
|
||||||
const chat = await prisma.chat.findFirst({
|
const chat = await prisma.chat.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: params.id,
|
id: params.id,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
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 { auth } from "@/auth";
|
||||||
|
|
||||||
// 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
|
||||||
|
|
@ -8,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();
|
const session = await auth();
|
||||||
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 />
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { NextChatLogo } from "@/components/ui/nextchat-logo";
|
||||||
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 { type Session } from "@/lib/session/types";
|
import { type Session } from "@auth/nextjs/types";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { Plus } from "lucide-react";
|
import { Plus } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
@ -70,7 +70,8 @@ async function SidebarList({ session }: { session?: Session }) {
|
||||||
updatedAt: "desc",
|
updatedAt: "desc",
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
[session?.user.id || ""],
|
// @ts-ignore
|
||||||
|
[session?.user?.id || ""],
|
||||||
{
|
{
|
||||||
revalidate: 3600,
|
revalidate: 3600,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
26
auth.ts
Normal file
26
auth.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
import NextAuth from "@auth/nextjs";
|
||||||
|
import GitHub from "@auth/nextjs/providers/github";
|
||||||
|
import { NextResponse } from "next/server";
|
||||||
|
export const {
|
||||||
|
handlers: { GET, POST },
|
||||||
|
auth,
|
||||||
|
CSRF_experimental,
|
||||||
|
} = NextAuth({
|
||||||
|
// @ts-ignore
|
||||||
|
providers: [GitHub],
|
||||||
|
session: { strategy: "jwt" },
|
||||||
|
async authorized({ request, auth }: any) {
|
||||||
|
const url = request.nextUrl;
|
||||||
|
|
||||||
|
if (request.method === "POST") {
|
||||||
|
const { authToken } = (await request.json()) ?? {};
|
||||||
|
// If the request has a valid auth token, it is authorized
|
||||||
|
const valid = true;
|
||||||
|
if (valid) return true;
|
||||||
|
return NextResponse.json("Invalid auth token", { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logged in users are authenticated, otherwise redirect to login page
|
||||||
|
return !!auth;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
@ -1,14 +1,13 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
import { signIn } from "@auth/nextjs/client";
|
||||||
import Link from "next/link";
|
|
||||||
|
|
||||||
export function Login() {
|
export function Login() {
|
||||||
return (
|
return (
|
||||||
<Link
|
<button
|
||||||
className="inline-flex w-full items-center justify-center rounded border border-zinc-800 bg-white h-8 px-4 -my-1.5 text-sm leading-6 tracking-tight text-zinc-900 transition-colors ease-in-out hover:bg-zinc-100 disabled:cursor-not-allowed disabled:opacity-50"
|
className="inline-flex w-full items-center justify-center rounded border border-zinc-800 bg-white h-8 px-4 -my-1.5 text-sm leading-6 tracking-tight text-zinc-900 transition-colors ease-in-out hover:bg-zinc-100 disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
href="/api/auth/login?next=/?s=1"
|
onClick={() => signIn("github")}
|
||||||
>
|
>
|
||||||
<span className="font-medium">Login</span>
|
<span className="font-medium">Login</span>
|
||||||
</Link>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
"use client";
|
"use client";
|
||||||
import { ThemeToggle } from "@/components/theme-toggle";
|
import { ThemeToggle } from "@/components/theme-toggle";
|
||||||
import { type Session } from "@/lib/session/types";
|
import { type Session } from "@auth/nextjs/types";
|
||||||
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
|
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
|
||||||
|
import { signOut } from "@auth/nextjs/client";
|
||||||
|
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
|
||||||
export interface UserMenuProps {
|
export interface UserMenuProps {
|
||||||
|
|
@ -14,12 +16,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?.avatar ? (
|
{session.user?.image ? (
|
||||||
<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?.avatar ? `${session.user.avatar}&s=60` : ""}
|
src={session.user?.image ? `${session.user.image}&s=60` : ""}
|
||||||
alt={session.user.name ?? "Avatar"}
|
alt={session.user.name ?? "Avatar"}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
|
@ -81,9 +83,7 @@ export function UserMenu({ session }: UserMenuProps) {
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
className="py-2 px-3 hover:bg-zinc-200 dark:hover:bg-zinc-800 transition-colors duration-200 cursor-pointer text-xs focus:outline-none"
|
className="py-2 px-3 hover:bg-zinc-200 dark:hover:bg-zinc-800 transition-colors duration-200 cursor-pointer text-xs focus:outline-none"
|
||||||
onClick={() => {
|
onClick={() => signOut()}
|
||||||
window.location.href = "/api/auth/logout";
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
Log Out
|
Log Out
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
|
|
@ -94,4 +94,4 @@ export function UserMenu({ session }: UserMenuProps) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
UserMenu.displayName = "Signout";
|
UserMenu.displayName = "UserMenu";
|
||||||
|
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
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 +0,0 @@
|
||||||
export const userSessionCookieName = 'user_session';
|
|
||||||
|
|
@ -1,116 +0,0 @@
|
||||||
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';
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
@ -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<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);
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
1
middleware.ts
Normal file
1
middleware.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export { auth as middleware } from "./auth";
|
||||||
|
|
@ -15,7 +15,8 @@
|
||||||
"format:check": "prettier --check \"**/*.{ts,tsx,mdx}\" --cache"
|
"format:check": "prettier --check \"**/*.{ts,tsx,mdx}\" --cache"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@next-auth/prisma-adapter": "^1.0.6",
|
"@auth/nextjs": "0.0.0-manual.030e8328",
|
||||||
|
"@next-auth/prisma-adapter": "1.0.6",
|
||||||
"@prisma/client": "^4.14.0",
|
"@prisma/client": "^4.14.0",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.0.4",
|
"@radix-ui/react-dropdown-menu": "^2.0.4",
|
||||||
"@vercel/analytics": "^1.0.0",
|
"@vercel/analytics": "^1.0.0",
|
||||||
|
|
@ -69,5 +70,10 @@
|
||||||
"prisma": "^4.14.0",
|
"prisma": "^4.14.0",
|
||||||
"tailwindcss": "^3.3.1",
|
"tailwindcss": "^3.3.1",
|
||||||
"typescript": "^4.9.3"
|
"typescript": "^4.9.3"
|
||||||
|
},
|
||||||
|
"pnpm": {
|
||||||
|
"overrides": {
|
||||||
|
"@auth/core": "0.0.0-manual.527fff6c"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
54
pnpm-lock.yaml
generated
54
pnpm-lock.yaml
generated
|
|
@ -1,8 +1,14 @@
|
||||||
lockfileVersion: '6.0'
|
lockfileVersion: '6.0'
|
||||||
|
|
||||||
|
overrides:
|
||||||
|
'@auth/core': 0.0.0-manual.527fff6c
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@auth/nextjs':
|
||||||
|
specifier: 0.0.0-manual.030e8328
|
||||||
|
version: 0.0.0-manual.030e8328(next@13.4.3-canary.3)(react@18.2.0)
|
||||||
'@next-auth/prisma-adapter':
|
'@next-auth/prisma-adapter':
|
||||||
specifier: ^1.0.6
|
specifier: 1.0.6
|
||||||
version: 1.0.6(@prisma/client@4.14.0)(next-auth@4.22.1)
|
version: 1.0.6(@prisma/client@4.14.0)(next-auth@4.22.1)
|
||||||
'@prisma/client':
|
'@prisma/client':
|
||||||
specifier: ^4.14.0
|
specifier: ^4.14.0
|
||||||
|
|
@ -173,6 +179,35 @@ packages:
|
||||||
'@jridgewell/gen-mapping': 0.3.3
|
'@jridgewell/gen-mapping': 0.3.3
|
||||||
'@jridgewell/trace-mapping': 0.3.18
|
'@jridgewell/trace-mapping': 0.3.18
|
||||||
|
|
||||||
|
/@auth/core@0.0.0-manual.527fff6c:
|
||||||
|
resolution: {integrity: sha512-NYWuH+6VAAMV05juyarbMm6rCDUGvS5vPFsE+V13NIfbvbwbEZPj3JfcHVv7vMDrUrPtJ4EhTh00Svejp1Moqg==}
|
||||||
|
peerDependencies:
|
||||||
|
nodemailer: ^6.8.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
nodemailer:
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@panva/hkdf': 1.1.1
|
||||||
|
cookie: 0.5.0
|
||||||
|
jose: 4.14.4
|
||||||
|
oauth4webapi: 2.3.0
|
||||||
|
preact: 10.11.3
|
||||||
|
preact-render-to-string: 5.2.3(preact@10.11.3)
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@auth/nextjs@0.0.0-manual.030e8328(next@13.4.3-canary.3)(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-AAtUl9EVCKqLRneDAq6KfUprWmsBRzsBoQ+8ytz/Nfgs8Uax5CWvCJs2CsnYNk04bXSB4L7eyMJmc1zTzZKaHg==}
|
||||||
|
peerDependencies:
|
||||||
|
next: ^13.4.2
|
||||||
|
react: ^18.2.0
|
||||||
|
dependencies:
|
||||||
|
'@auth/core': 0.0.0-manual.527fff6c
|
||||||
|
next: 13.4.3-canary.3(@babel/core@7.21.8)(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
react: 18.2.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- nodemailer
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@babel/code-frame@7.21.4:
|
/@babel/code-frame@7.21.4:
|
||||||
resolution: {integrity: sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==}
|
resolution: {integrity: sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
@ -3532,6 +3567,10 @@ packages:
|
||||||
path-key: 4.0.0
|
path-key: 4.0.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/oauth4webapi@2.3.0:
|
||||||
|
resolution: {integrity: sha512-JGkb5doGrwzVDuHwgrR4nHJayzN4h59VCed6EW8Tql6iHDfZIabCJvg6wtbn5q6pyB2hZruI3b77Nudvq7NmvA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/oauth@0.9.15:
|
/oauth@0.9.15:
|
||||||
resolution: {integrity: sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==}
|
resolution: {integrity: sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
@ -3824,6 +3863,15 @@ packages:
|
||||||
picocolors: 1.0.0
|
picocolors: 1.0.0
|
||||||
source-map-js: 1.0.2
|
source-map-js: 1.0.2
|
||||||
|
|
||||||
|
/preact-render-to-string@5.2.3(preact@10.11.3):
|
||||||
|
resolution: {integrity: sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==}
|
||||||
|
peerDependencies:
|
||||||
|
preact: '>=10'
|
||||||
|
dependencies:
|
||||||
|
preact: 10.11.3
|
||||||
|
pretty-format: 3.8.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/preact-render-to-string@5.2.6(preact@10.14.1):
|
/preact-render-to-string@5.2.6(preact@10.14.1):
|
||||||
resolution: {integrity: sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==}
|
resolution: {integrity: sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
|
@ -3833,6 +3881,10 @@ packages:
|
||||||
pretty-format: 3.8.0
|
pretty-format: 3.8.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/preact@10.11.3:
|
||||||
|
resolution: {integrity: sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/preact@10.14.1:
|
/preact@10.14.1:
|
||||||
resolution: {integrity: sha512-4XDSnUisk3YFBb3p9WeKeH1mKoxdFUsaXcvxs9wlpYR1wax/TWJVqhwmIWbByX0h7jMEJH6Zc5J6jqc58FKaNQ==}
|
resolution: {integrity: sha512-4XDSnUisk3YFBb3p9WeKeH1mKoxdFUsaXcvxs9wlpYR1wax/TWJVqhwmIWbByX0h7jMEJH6Zc5J6jqc58FKaNQ==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue