Files
agentlens/apps/web/src/auth.ts
Vectry 539d35b649 feat: password reset flow and email verification
- Add forgot-password and reset-password pages and API routes
- Add email verification with token generation on registration
- Add resend-verification endpoint with 60s rate limit
- Add shared email utility (nodemailer, Migadu SMTP)
- Add VerificationBanner in dashboard layout
- Add PasswordResetToken and EmailVerificationToken models
- Add emailVerified field to User model
- Extend NextAuth session with isEmailVerified
- Add forgot-password link to login page
- Wire EMAIL_PASSWORD env var in docker-compose
2026-02-10 16:47:06 +00:00

85 lines
2.1 KiB
TypeScript

import NextAuth from "next-auth";
import Credentials from "next-auth/providers/credentials";
import { compare } from "bcryptjs";
import { z } from "zod";
import { prisma } from "@/lib/prisma";
import authConfig from "./auth.config";
declare module "next-auth" {
interface Session {
user: {
id: string;
email: string;
name?: string | null;
image?: string | null;
isEmailVerified: boolean;
};
}
}
declare module "@auth/core/jwt" {
interface JWT {
id: string;
isEmailVerified: boolean;
}
}
const loginSchema = z.object({
email: z.email("Invalid email address"),
password: z.string().min(8, "Password must be at least 8 characters"),
});
export const { handlers, auth, signIn, signOut } = NextAuth({
...authConfig,
providers: [
Credentials({
credentials: {
email: { label: "Email", type: "email" },
password: { label: "Password", type: "password" },
},
async authorize(credentials) {
const parsed = loginSchema.safeParse(credentials);
if (!parsed.success) return null;
const { email, password } = parsed.data;
const user = await prisma.user.findUnique({
where: { email: email.toLowerCase() },
});
if (!user) return null;
const isValid = await compare(password, user.passwordHash);
if (!isValid) return null;
return {
id: user.id,
email: user.email,
name: user.name,
};
},
}),
],
callbacks: {
async jwt({ token, user, trigger }) {
if (user) {
token.id = user.id as string;
}
if (trigger === "update" || user) {
const dbUser = await prisma.user.findUnique({
where: { id: token.id },
select: { emailVerified: true },
});
if (dbUser) {
token.isEmailVerified = dbUser.emailVerified;
}
}
return token;
},
session({ session, token }) {
session.user.id = token.id;
session.user.isEmailVerified = token.isEmailVerified;
return session;
},
},
});