diff --git a/lib/auth.ts b/lib/auth.ts index 2c20bb0f1b..8cd2a341c5 100644 --- a/lib/auth.ts +++ b/lib/auth.ts @@ -1,11 +1,29 @@ -import { hash, compare } from 'bcryptjs'; +import { compare, hash } from "bcryptjs"; +import { DefaultSession } from "next-auth"; +import { getSession as getSessionInner, GetSessionOptions } from "next-auth/client"; -export async function hashPassword(password) { - const hashedPassword = await hash(password, 12); - return hashedPassword; +export async function hashPassword(password: string) { + const hashedPassword = await hash(password, 12); + return hashedPassword; } -export async function verifyPassword(password, hashedPassword) { - const isValid = await compare(password, hashedPassword); - return isValid; -} \ No newline at end of file +export async function verifyPassword(password: string, hashedPassword: string) { + const isValid = await compare(password, hashedPassword); + return isValid; +} + +type DefaultSessionUser = NonNullable; +type CalendsoSessionUser = DefaultSessionUser & { + id: number; + username: string; +}; +interface Session extends DefaultSession { + user?: CalendsoSessionUser; +} + +export async function getSession(options: GetSessionOptions): Promise { + const session = await getSessionInner(options); + + // that these are equal are ensured in `[...nextauth]`'s callback + return session as CalendsoSession; +} diff --git a/package.json b/package.json index ae9122f1d3..0b2d3da8eb 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "uuid": "^8.3.2" }, "devDependencies": { + "@types/bcryptjs": "^2.4.2", "@types/jest": "^27.0.1", "@types/node": "^16.6.1", "@types/nodemailer": "^6.4.4", diff --git a/pages/api/auth/[...nextauth].tsx b/pages/api/auth/[...nextauth].tsx index ad65f66f05..3e5ec5fc05 100644 --- a/pages/api/auth/[...nextauth].tsx +++ b/pages/api/auth/[...nextauth].tsx @@ -1,59 +1,73 @@ -import NextAuth from 'next-auth'; -import Providers from 'next-auth/providers'; -import prisma from '../../../lib/prisma'; -import {verifyPassword} from "../../../lib/auth"; +import NextAuth from "next-auth"; +import Providers from "next-auth/providers"; +import prisma from "../../../lib/prisma"; +import { CalendsoSession, verifyPassword } from "../../../lib/auth"; export default NextAuth({ - session: { - jwt: true + session: { + jwt: true, + }, + pages: { + signIn: "/auth/login", + signOut: "/auth/logout", + error: "/auth/error", // Error code passed in query string as ?error= + }, + providers: [ + Providers.Credentials({ + name: "Calendso", + credentials: { + email: { label: "Email Address", type: "email", placeholder: "john.doe@example.com" }, + password: { label: "Password", type: "password", placeholder: "Your super secure password" }, + }, + async authorize(credentials) { + const user = await prisma.user.findFirst({ + where: { + email: credentials.email, + }, + }); + + if (!user) { + throw new Error("No user found"); + } + if (!user.password) { + throw new Error("Incorrect password"); + } + + const isValid = await verifyPassword(credentials.password, user.password); + + if (!isValid) { + throw new Error("Incorrect password"); + } + + return { + id: user.id, + username: user.username, + email: user.email, + name: user.name, + image: user.avatar, + }; + }, + }), + ], + callbacks: { + async jwt(token, user) { + // Add username to the token right after signin + if (user?.username) { + token.id = user.id; + token.username = user.username; + } + return token; }, - pages: { - signIn: '/auth/login', - signOut: '/auth/logout', - error: '/auth/error', // Error code passed in query string as ?error= - }, - providers: [ - Providers.Credentials({ - name: 'Calendso', - credentials: { - email: { label: "Email Address", type: "email", placeholder: "john.doe@example.com" }, - password: { label: "Password", type: "password", placeholder: "Your super secure password" } - }, - async authorize(credentials) { - const user = await prisma.user.findFirst({ - where: { - email: credentials.email - } - }); - - if (!user) { - throw new Error('No user found'); - } - - const isValid = await verifyPassword(credentials.password, user.password); - - if (!isValid) { - throw new Error('Incorrect password'); - } - - return {id: user.id, username: user.username, email: user.email, name: user.name, image: user.avatar}; - } - }) - ], - callbacks: { - async jwt(token, user, account, profile, isNewUser) { - // Add username to the token right after signin - if (user?.username) { - token.id = user.id; - token.username = user.username; - } - return token; - }, - async session(session, token) { - session.user = session.user || {} - session.user.id = token.id; - session.user.username = token.username; - return session; + async session(session, token) { + const calendsoSession: CalendsoSession = { + ...session, + user: { + ...session.user, + id: token.id as number, + username: token.username as string, }, + }; + return calendsoSession; }, + }, }); diff --git a/pages/api/auth/changepw.ts b/pages/api/auth/changepw.ts index fa52f1e2e6..ba4fb282f7 100644 --- a/pages/api/auth/changepw.ts +++ b/pages/api/auth/changepw.ts @@ -1,7 +1,7 @@ -import type { NextApiRequest, NextApiResponse } from 'next'; -import { hashPassword, verifyPassword } from '../../../lib/auth'; -import { getSession } from 'next-auth/client'; -import prisma from '../../../lib/prisma'; +import type { NextApiRequest, NextApiResponse } from "next"; +import { hashPassword, verifyPassword } from "../../../lib/auth"; +import { getSession } from "@lib/auth"; +import prisma from "../../../lib/prisma"; export default async function handler(req: NextApiRequest, res: NextApiResponse) { const session = await getSession({req: req}); diff --git a/pages/api/availability/calendar.ts b/pages/api/availability/calendar.ts index bebfbbbf0f..83ad9f6217 100644 --- a/pages/api/availability/calendar.ts +++ b/pages/api/availability/calendar.ts @@ -1,7 +1,7 @@ -import type {NextApiRequest, NextApiResponse} from 'next'; -import {getSession} from 'next-auth/client'; -import prisma from '../../../lib/prisma'; -import {IntegrationCalendar, listCalendars} from "../../../lib/calendarClient"; +import type { NextApiRequest, NextApiResponse } from "next"; +import { getSession } from "@lib/auth"; +import prisma from "../../../lib/prisma"; +import { IntegrationCalendar, listCalendars } from "../../../lib/calendarClient"; export default async function handler(req: NextApiRequest, res: NextApiResponse) { const session = await getSession({req: req}); diff --git a/pages/api/availability/day.ts b/pages/api/availability/day.ts index e81e0b4724..57a66998e3 100644 --- a/pages/api/availability/day.ts +++ b/pages/api/availability/day.ts @@ -1,6 +1,6 @@ -import type {NextApiRequest, NextApiResponse} from 'next'; -import {getSession} from 'next-auth/client'; -import prisma from '../../../lib/prisma'; +import type { NextApiRequest, NextApiResponse } from "next"; +import { getSession } from "@lib/auth"; +import prisma from "../../../lib/prisma"; export default async function handler(req: NextApiRequest, res: NextApiResponse) { const session = await getSession({req: req}); diff --git a/pages/api/integrations.ts b/pages/api/integrations.ts index 70bcb7d854..28aa700b73 100644 --- a/pages/api/integrations.ts +++ b/pages/api/integrations.ts @@ -1,5 +1,5 @@ -import prisma from '../../lib/prisma'; -import { getSession } from 'next-auth/client'; +import prisma from "../../lib/prisma"; +import { getSession } from "@lib/auth"; export default async function handler(req, res) { if (req.method === 'GET') { diff --git a/pages/api/integrations/googlecalendar/add.ts b/pages/api/integrations/googlecalendar/add.ts index 5d2c4ae0a3..762a9016a2 100644 --- a/pages/api/integrations/googlecalendar/add.ts +++ b/pages/api/integrations/googlecalendar/add.ts @@ -1,7 +1,7 @@ -import type { NextApiRequest, NextApiResponse } from 'next'; -import { getSession } from 'next-auth/client'; -import prisma from '../../../../lib/prisma'; -const {google} = require('googleapis'); +import type { NextApiRequest, NextApiResponse } from "next"; +import { getSession } from "@lib/auth"; +import prisma from "../../../../lib/prisma"; +const { google } = require("googleapis"); const credentials = process.env.GOOGLE_API_CREDENTIALS; const scopes = ['https://www.googleapis.com/auth/calendar.readonly', 'https://www.googleapis.com/auth/calendar.events']; diff --git a/pages/api/integrations/googlecalendar/callback.ts b/pages/api/integrations/googlecalendar/callback.ts index d3c3476bbb..86699b1983 100644 --- a/pages/api/integrations/googlecalendar/callback.ts +++ b/pages/api/integrations/googlecalendar/callback.ts @@ -1,7 +1,7 @@ -import type { NextApiRequest, NextApiResponse } from 'next'; -import { getSession } from 'next-auth/client'; -import prisma from '../../../../lib/prisma'; -const {google} = require('googleapis'); +import type { NextApiRequest, NextApiResponse } from "next"; +import { getSession } from "@lib/auth"; +import prisma from "../../../../lib/prisma"; +const { google } = require("googleapis"); const credentials = process.env.GOOGLE_API_CREDENTIALS; diff --git a/pages/api/integrations/office365calendar/add.ts b/pages/api/integrations/office365calendar/add.ts index 24f4d1d09c..7b9059ded6 100644 --- a/pages/api/integrations/office365calendar/add.ts +++ b/pages/api/integrations/office365calendar/add.ts @@ -1,6 +1,6 @@ -import type { NextApiRequest, NextApiResponse } from 'next'; -import { getSession } from 'next-auth/client'; -import prisma from '../../../../lib/prisma'; +import type { NextApiRequest, NextApiResponse } from "next"; +import { getSession } from "@lib/auth"; +import prisma from "../../../../lib/prisma"; const scopes = ['User.Read', 'Calendars.Read', 'Calendars.ReadWrite', 'offline_access']; diff --git a/pages/api/integrations/office365calendar/callback.ts b/pages/api/integrations/office365calendar/callback.ts index 71581a2df0..676dcf83d2 100644 --- a/pages/api/integrations/office365calendar/callback.ts +++ b/pages/api/integrations/office365calendar/callback.ts @@ -1,7 +1,7 @@ -import type { NextApiRequest, NextApiResponse } from 'next'; -import { getSession } from 'next-auth/client'; -import prisma from '../../../../lib/prisma'; -const scopes = ['offline_access', 'Calendars.Read', 'Calendars.ReadWrite']; +import type { NextApiRequest, NextApiResponse } from "next"; +import { getSession } from "@lib/auth"; +import prisma from "../../../../lib/prisma"; +const scopes = ["offline_access", "Calendars.Read", "Calendars.ReadWrite"]; export default async function handler(req: NextApiRequest, res: NextApiResponse) { const { code } = req.query; diff --git a/pages/api/integrations/zoomvideo/add.ts b/pages/api/integrations/zoomvideo/add.ts index a516358672..20b1cff73a 100644 --- a/pages/api/integrations/zoomvideo/add.ts +++ b/pages/api/integrations/zoomvideo/add.ts @@ -1,6 +1,6 @@ -import type {NextApiRequest, NextApiResponse} from 'next'; -import {getSession} from 'next-auth/client'; -import prisma from '../../../../lib/prisma'; +import type { NextApiRequest, NextApiResponse } from "next"; +import { getSession } from "@lib/auth"; +import prisma from "../../../../lib/prisma"; const client_id = process.env.ZOOM_CLIENT_ID; diff --git a/yarn.lock b/yarn.lock index ba4efe74b3..d7cdfc417a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1151,6 +1151,11 @@ dependencies: "@babel/types" "^7.3.0" +"@types/bcryptjs@^2.4.2": + version "2.4.2" + resolved "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.2.tgz#e3530eac9dd136bfdfb0e43df2c4c5ce1f77dfae" + integrity sha512-LiMQ6EOPob/4yUL66SZzu6Yh77cbzJFYll+ZfaPiPPFswtIlA/Fs1MzdKYA7JApHU49zQTbJGX3PDmCpIdDBRQ== + "@types/graceful-fs@^4.1.2": version "4.1.5" resolved "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15"