cal.pub0.org/pages/api/auth/[...nextauth].tsx

113 lines
3.4 KiB
TypeScript

import NextAuth, { Session } from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import { authenticator } from "otplib";
import { ErrorCode, verifyPassword } from "@lib/auth";
import { symmetricDecrypt } from "@lib/crypto";
import prisma from "@lib/prisma";
export default NextAuth({
session: {
strategy: "jwt",
},
secret: process.env.JWT_SECRET,
pages: {
signIn: "/auth/login",
signOut: "/auth/logout",
error: "/auth/error", // Error code passed in query string as ?error=
},
providers: [
CredentialsProvider({
id: "credentials",
name: "Cal.com",
type: "credentials",
credentials: {
email: { label: "Email Address", type: "email", placeholder: "john.doe@example.com" },
password: { label: "Password", type: "password", placeholder: "Your super secure password" },
totpCode: { label: "Two-factor Code", type: "input", placeholder: "Code from authenticator app" },
},
async authorize(credentials) {
if (!credentials) {
console.error(`For some reason credentials are missing`);
throw new Error(ErrorCode.InternalServerError);
}
const user = await prisma.user.findUnique({
where: {
email: credentials.email.toLowerCase(),
},
});
if (!user) {
throw new Error(ErrorCode.UserNotFound);
}
if (!user.password) {
throw new Error(ErrorCode.UserMissingPassword);
}
const isCorrectPassword = await verifyPassword(credentials.password, user.password);
if (!isCorrectPassword) {
throw new Error(ErrorCode.IncorrectPassword);
}
if (user.twoFactorEnabled) {
if (!credentials.totpCode) {
throw new Error(ErrorCode.SecondFactorRequired);
}
if (!user.twoFactorSecret) {
console.error(`Two factor is enabled for user ${user.id} but they have no secret`);
throw new Error(ErrorCode.InternalServerError);
}
if (!process.env.CALENDSO_ENCRYPTION_KEY) {
console.error(`"Missing encryption key; cannot proceed with two factor login."`);
throw new Error(ErrorCode.InternalServerError);
}
const secret = symmetricDecrypt(user.twoFactorSecret, process.env.CALENDSO_ENCRYPTION_KEY);
if (secret.length !== 32) {
console.error(
`Two factor secret decryption failed. Expected key with length 32 but got ${secret.length}`
);
throw new Error(ErrorCode.InternalServerError);
}
const isValidToken = authenticator.check(credentials.totpCode, secret);
if (!isValidToken) {
throw new Error(ErrorCode.IncorrectTwoFactorCode);
}
}
return {
id: user.id,
username: user.username,
email: user.email,
name: user.name,
};
},
}),
],
callbacks: {
async jwt({ token, user }) {
if (user) {
token.id = user.id;
token.username = user.username;
}
return token;
},
async session({ session, token }) {
const calendsoSession: Session = {
...session,
user: {
...session.user,
id: token.id as number,
username: token.username as string,
},
};
return calendsoSession;
},
},
});