feat: Add authentication and session management with NextAuth.js and Prisma [broken]

- Implemented API session retrieval in `lib/api-auth.ts` to manage user sessions.
- Created authentication options in `lib/auth-options.ts` using NextAuth.js with credentials provider.
- Added migration scripts to create necessary tables for authentication in `migrations/0002_create_auth_tables.sql` and `prisma/migrations/20250601033219_add_nextauth_tables/migration.sql`.
- Configured ESLint with Next.js and TypeScript support in `eslint.config.mjs`.
- Updated Next.js configuration in `next.config.ts` for Cloudflare compatibility.
- Defined Cloudflare Worker configuration in `open-next.config.ts` and `wrangler.jsonc`.
- Enhanced type definitions for authentication in `types/auth.d.ts`.
- Created a Cloudflare Worker entry point in `src/index.ts.backup` to handle API requests and responses.
This commit is contained in:
2025-06-01 16:34:54 +02:00
parent 71c8aff125
commit bde0b44ea0
53 changed files with 20841 additions and 6435 deletions

View File

@ -1,104 +0,0 @@
import NextAuth, { NextAuthOptions } from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import { prisma } from "../../../lib/prisma";
import bcrypt from "bcryptjs";
// Define the shape of the JWT token
declare module "next-auth/jwt" {
interface JWT {
companyId: string;
role: string;
}
}
// Define the shape of the session object
declare module "next-auth" {
interface Session {
user: {
id?: string;
name?: string;
email?: string;
image?: string;
companyId: string;
role: string;
};
}
interface User {
id: string;
email: string;
companyId: string;
role: string;
}
}
export const authOptions: NextAuthOptions = {
providers: [
CredentialsProvider({
name: "Credentials",
credentials: {
email: { label: "Email", type: "text" },
password: { label: "Password", type: "password" },
},
async authorize(credentials) {
if (!credentials?.email || !credentials?.password) {
return null;
}
const user = await prisma.user.findUnique({
where: { email: credentials.email },
});
if (!user) return null;
const valid = await bcrypt.compare(credentials.password, user.password);
if (!valid) return null;
return {
id: user.id,
email: user.email,
companyId: user.companyId,
role: user.role,
};
},
}),
],
session: {
strategy: "jwt",
maxAge: 30 * 24 * 60 * 60, // 30 days
},
cookies: {
sessionToken: {
name: `next-auth.session-token`,
options: {
httpOnly: true,
sameSite: "lax",
path: "/",
secure: process.env.NODE_ENV === "production",
},
},
},
callbacks: {
async jwt({ token, user }) {
if (user) {
token.companyId = user.companyId;
token.role = user.role;
}
return token;
},
async session({ session, token }) {
if (token && session.user) {
session.user.companyId = token.companyId;
session.user.role = token.role;
}
return session;
},
},
pages: {
signIn: "/login",
},
secret: process.env.NEXTAUTH_SECRET,
debug: process.env.NODE_ENV === "development",
};
export default NextAuth(authOptions);

View File

@ -1,14 +1,13 @@
// API endpoint: update company CSV URL config
import { NextApiRequest, NextApiResponse } from "next";
import { getServerSession } from "next-auth";
import { getApiSession } from "../../../lib/api-auth";
import { prisma } from "../../../lib/prisma";
import { authOptions } from "../auth/[...nextauth]";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const session = await getServerSession(req, res, authOptions);
const session = await getApiSession(req, res);
if (!session?.user) return res.status(401).json({ error: "Not logged in" });
const user = await prisma.user.findUnique({

View File

@ -1,9 +1,8 @@
// API endpoint: return metrics for current company
import { NextApiRequest, NextApiResponse } from "next";
import { getServerSession } from "next-auth";
import { getApiSession } from "../../../lib/api-auth";
import { prisma } from "../../../lib/prisma";
import { sessionMetrics } from "../../../lib/metrics";
import { authOptions } from "../auth/[...nextauth]";
import { ChatSession } from "../../../lib/types"; // Import ChatSession
interface SessionUser {
@ -19,11 +18,7 @@ export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const session = (await getServerSession(
req,
res,
authOptions
)) as SessionData | null;
const session = await getApiSession(req, res);
if (!session?.user) return res.status(401).json({ error: "Not logged in" });
const user = await prisma.user.findUnique({

View File

@ -1,6 +1,5 @@
import { NextApiRequest, NextApiResponse } from "next";
import { getServerSession } from "next-auth/next";
import { authOptions } from "../auth/[...nextauth]";
import { getApiSession } from "../../../lib/api-auth";
import { prisma } from "../../../lib/prisma";
import { SessionFilterOptions } from "../../../lib/types";
@ -14,7 +13,7 @@ export default async function handler(
return res.status(405).json({ error: "Method not allowed" });
}
const authSession = await getServerSession(req, res, authOptions);
const authSession = await getApiSession(req, res);
if (!authSession || !authSession.user?.companyId) {
return res.status(401).json({ error: "Unauthorized" });

View File

@ -1,6 +1,5 @@
import { NextApiRequest, NextApiResponse } from "next";
import { getServerSession } from "next-auth/next";
import { authOptions } from "../auth/[...nextauth]";
import { getApiSession } from "../../../lib/api-auth";
import { prisma } from "../../../lib/prisma";
import {
ChatSession,
@ -17,7 +16,7 @@ export default async function handler(
return res.status(405).json({ error: "Method not allowed" });
}
const authSession = await getServerSession(req, res, authOptions);
const authSession = await getApiSession(req, res);
if (!authSession || !authSession.user?.companyId) {
return res.status(401).json({ error: "Unauthorized" });

View File

@ -1,13 +1,12 @@
import { NextApiRequest, NextApiResponse } from "next";
import { getServerSession } from "next-auth";
import { getApiSession } from "../../../lib/api-auth";
import { prisma } from "../../../lib/prisma";
import { authOptions } from "../auth/[...nextauth]";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const session = await getServerSession(req, res, authOptions);
const session = await getApiSession(req, res);
if (!session?.user || session.user.role !== "admin")
return res.status(403).json({ error: "Forbidden" });

View File

@ -1,9 +1,8 @@
import { NextApiRequest, NextApiResponse } from "next";
import crypto from "crypto";
import { getServerSession } from "next-auth";
import { prisma } from "../../../lib/prisma";
import bcrypt from "bcryptjs";
import { authOptions } from "../auth/[...nextauth]";
import { getApiSession } from "../../../lib/api-auth";
// User type from prisma is used instead of the one in lib/types
interface UserBasicInfo {
@ -16,9 +15,18 @@ export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const session = await getServerSession(req, res, authOptions);
if (!session?.user || session.user.role !== "admin")
return res.status(403).json({ error: "Forbidden" });
const session = await getApiSession(req, res);
console.log("Session in users API:", session);
if (!session?.user) {
console.log("No session or user found");
return res.status(401).json({ error: "Not logged in" });
}
if (session.user.role !== "admin") {
console.log("User is not admin:", session.user.role);
return res.status(403).json({ error: "Admin access required" });
}
const user = await prisma.user.findUnique({
where: { email: session.user.email as string },

View File

@ -25,7 +25,7 @@ export default async function handler(
data: { resetToken: token, resetTokenExpiry: expiry },
});
const resetUrl = `${process.env.NEXTAUTH_URL || "http://localhost:3000"}/reset-password?token=${token}`;
const resetUrl = `${process.env.AUTH_URL || "http://localhost:3000"}/reset-password?token=${token}`;
await sendEmail(email, "Password Reset", `Reset your password: ${resetUrl}`);
res.status(200).end();
}