calcom/pages/api/auth/[...nextauth].tsx
Omar López 7bc7b241ac
Zomars/cal 794 normalize emails in db (#1361)
* Email input UX improvements

* Makes email queries case insensitive

* Lowercases all emails

* Type fixes

* Re adds lowercase email to login

* Removes citext dependency

* Updates schema

* Migration fixes

* Added failsafes to team invites

* Team invite improvements

* Deleting the index, lowercasing 

```
calendso=> UPDATE users SET email=LOWER(email);
ERROR:  duplicate key value violates unique constraint "users.email_unique"
DETAIL:  Key (email)=(free@example.com) already exists.
```

vs.

```
calendso=> CREATE UNIQUE INDEX "users.email_unique" ON "users" (email);
ERROR:  could not create unique index "users.email_unique"
DETAIL:  Key (email)=(Free@example.com) is duplicated.
```

I think it'll be easier to rectify for users if they try to run the migrations if the index stays in place.

Co-authored-by: Alex van Andel <me@alexvanandel.com>
2021-12-21 00:59:06 +00:00

107 lines
3.2 KiB
TypeScript

import NextAuth from "next-auth";
import Providers from "next-auth/providers";
import { authenticator } from "otplib";
import { ErrorCode, Session, verifyPassword } from "@lib/auth";
import { symmetricDecrypt } from "@lib/crypto";
import prisma from "@lib/prisma";
export default NextAuth({
session: {
jwt: true,
},
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: [
Providers.Credentials({
name: "Cal.com",
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) {
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;
},
},
});