add type-safe getSession() (#486)
				
					
				
			* fix types for auth * implement safer to use `getSession`
This commit is contained in:
		
							parent
							
								
									a162949cf1
								
							
						
					
					
						commit
						a0a0ec86f0
					
				
					 13 changed files with 130 additions and 92 deletions
				
			
		
							
								
								
									
										34
									
								
								lib/auth.ts
									
									
									
									
									
								
							
							
						
						
									
										34
									
								
								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) { | export async function hashPassword(password: string) { | ||||||
|     const hashedPassword = await hash(password, 12); |   const hashedPassword = await hash(password, 12); | ||||||
|     return hashedPassword; |   return hashedPassword; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function verifyPassword(password, hashedPassword) { | export async function verifyPassword(password: string, hashedPassword: string) { | ||||||
|     const isValid = await compare(password, hashedPassword); |   const isValid = await compare(password, hashedPassword); | ||||||
|     return isValid; |   return isValid; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | type DefaultSessionUser = NonNullable<DefaultSession["user"]>; | ||||||
|  | type CalendsoSessionUser = DefaultSessionUser & { | ||||||
|  |   id: number; | ||||||
|  |   username: string; | ||||||
|  | }; | ||||||
|  | interface Session extends DefaultSession { | ||||||
|  |   user?: CalendsoSessionUser; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function getSession(options: GetSessionOptions): Promise<CalendsoSession | null> { | ||||||
|  |   const session = await getSessionInner(options); | ||||||
|  | 
 | ||||||
|  |   // that these are equal are ensured in `[...nextauth]`'s callback
 | ||||||
|  |   return session as CalendsoSession; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -55,6 +55,7 @@ | ||||||
|     "uuid": "^8.3.2" |     "uuid": "^8.3.2" | ||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|  |     "@types/bcryptjs": "^2.4.2", | ||||||
|     "@types/jest": "^27.0.1", |     "@types/jest": "^27.0.1", | ||||||
|     "@types/node": "^16.6.1", |     "@types/node": "^16.6.1", | ||||||
|     "@types/nodemailer": "^6.4.4", |     "@types/nodemailer": "^6.4.4", | ||||||
|  |  | ||||||
|  | @ -1,59 +1,73 @@ | ||||||
| import NextAuth from 'next-auth'; | import NextAuth from "next-auth"; | ||||||
| import Providers from 'next-auth/providers'; | import Providers from "next-auth/providers"; | ||||||
| import prisma from '../../../lib/prisma'; | import prisma from "../../../lib/prisma"; | ||||||
| import {verifyPassword} from "../../../lib/auth"; | import { CalendsoSession, verifyPassword } from "../../../lib/auth"; | ||||||
| 
 | 
 | ||||||
| export default NextAuth({ | export default NextAuth({ | ||||||
|     session: { |   session: { | ||||||
|         jwt: true |     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: { |     async session(session, token) { | ||||||
|         signIn: '/auth/login', |       const calendsoSession: CalendsoSession = { | ||||||
|         signOut: '/auth/logout', |         ...session, | ||||||
|         error: '/auth/error', // Error code passed in query string as ?error=
 |         user: { | ||||||
|     }, |           ...session.user, | ||||||
|     providers: [ |           id: token.id as number, | ||||||
|         Providers.Credentials({ |           username: token.username as string, | ||||||
|             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; |  | ||||||
|         }, |         }, | ||||||
|  |       }; | ||||||
|  |       return calendsoSession; | ||||||
|     }, |     }, | ||||||
|  |   }, | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| import type { NextApiRequest, NextApiResponse } from 'next'; | import type { NextApiRequest, NextApiResponse } from "next"; | ||||||
| import { hashPassword, verifyPassword } from '../../../lib/auth'; | import { hashPassword, verifyPassword } from "../../../lib/auth"; | ||||||
| import { getSession } from 'next-auth/client'; | import { getSession } from "@lib/auth"; | ||||||
| import prisma from '../../../lib/prisma'; | import prisma from "../../../lib/prisma"; | ||||||
| 
 | 
 | ||||||
| export default async function handler(req: NextApiRequest, res: NextApiResponse) { | export default async function handler(req: NextApiRequest, res: NextApiResponse) { | ||||||
|     const session = await getSession({req: req}); |     const session = await getSession({req: req}); | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| import type {NextApiRequest, NextApiResponse} from 'next'; | import type { NextApiRequest, NextApiResponse } from "next"; | ||||||
| import {getSession} from 'next-auth/client'; | import { getSession } from "@lib/auth"; | ||||||
| import prisma from '../../../lib/prisma'; | import prisma from "../../../lib/prisma"; | ||||||
| import {IntegrationCalendar, listCalendars} from "../../../lib/calendarClient"; | import { IntegrationCalendar, listCalendars } from "../../../lib/calendarClient"; | ||||||
| 
 | 
 | ||||||
| export default async function handler(req: NextApiRequest, res: NextApiResponse) { | export default async function handler(req: NextApiRequest, res: NextApiResponse) { | ||||||
|     const session = await getSession({req: req}); |     const session = await getSession({req: req}); | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import type {NextApiRequest, NextApiResponse} from 'next'; | import type { NextApiRequest, NextApiResponse } from "next"; | ||||||
| import {getSession} from 'next-auth/client'; | import { getSession } from "@lib/auth"; | ||||||
| import prisma from '../../../lib/prisma'; | import prisma from "../../../lib/prisma"; | ||||||
| 
 | 
 | ||||||
| export default async function handler(req: NextApiRequest, res: NextApiResponse) { | export default async function handler(req: NextApiRequest, res: NextApiResponse) { | ||||||
|     const session = await getSession({req: req}); |     const session = await getSession({req: req}); | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| import prisma from '../../lib/prisma'; | import prisma from "../../lib/prisma"; | ||||||
| import { getSession } from 'next-auth/client'; | import { getSession } from "@lib/auth"; | ||||||
| 
 | 
 | ||||||
| export default async function handler(req, res) { | export default async function handler(req, res) { | ||||||
|     if (req.method === 'GET') { |     if (req.method === 'GET') { | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| import type { NextApiRequest, NextApiResponse } from 'next'; | import type { NextApiRequest, NextApiResponse } from "next"; | ||||||
| import { getSession } from 'next-auth/client'; | import { getSession } from "@lib/auth"; | ||||||
| import prisma from '../../../../lib/prisma'; | import prisma from "../../../../lib/prisma"; | ||||||
| const {google} = require('googleapis'); | const { google } = require("googleapis"); | ||||||
| 
 | 
 | ||||||
| const credentials = process.env.GOOGLE_API_CREDENTIALS; | const credentials = process.env.GOOGLE_API_CREDENTIALS; | ||||||
| const scopes = ['https://www.googleapis.com/auth/calendar.readonly', 'https://www.googleapis.com/auth/calendar.events']; | const scopes = ['https://www.googleapis.com/auth/calendar.readonly', 'https://www.googleapis.com/auth/calendar.events']; | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| import type { NextApiRequest, NextApiResponse } from 'next'; | import type { NextApiRequest, NextApiResponse } from "next"; | ||||||
| import { getSession } from 'next-auth/client'; | import { getSession } from "@lib/auth"; | ||||||
| import prisma from '../../../../lib/prisma'; | import prisma from "../../../../lib/prisma"; | ||||||
| const {google} = require('googleapis'); | const { google } = require("googleapis"); | ||||||
| 
 | 
 | ||||||
| const credentials = process.env.GOOGLE_API_CREDENTIALS; | const credentials = process.env.GOOGLE_API_CREDENTIALS; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import type { NextApiRequest, NextApiResponse } from 'next'; | import type { NextApiRequest, NextApiResponse } from "next"; | ||||||
| import { getSession } from 'next-auth/client'; | import { getSession } from "@lib/auth"; | ||||||
| import prisma from '../../../../lib/prisma'; | import prisma from "../../../../lib/prisma"; | ||||||
| 
 | 
 | ||||||
| const scopes = ['User.Read', 'Calendars.Read', 'Calendars.ReadWrite', 'offline_access']; | const scopes = ['User.Read', 'Calendars.Read', 'Calendars.ReadWrite', 'offline_access']; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| import type { NextApiRequest, NextApiResponse } from 'next'; | import type { NextApiRequest, NextApiResponse } from "next"; | ||||||
| import { getSession } from 'next-auth/client'; | import { getSession } from "@lib/auth"; | ||||||
| import prisma from '../../../../lib/prisma'; | import prisma from "../../../../lib/prisma"; | ||||||
| const scopes = ['offline_access', 'Calendars.Read', 'Calendars.ReadWrite']; | const scopes = ["offline_access", "Calendars.Read", "Calendars.ReadWrite"]; | ||||||
| 
 | 
 | ||||||
| export default async function handler(req: NextApiRequest, res: NextApiResponse) { | export default async function handler(req: NextApiRequest, res: NextApiResponse) { | ||||||
|     const { code } = req.query; |     const { code } = req.query; | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import type {NextApiRequest, NextApiResponse} from 'next'; | import type { NextApiRequest, NextApiResponse } from "next"; | ||||||
| import {getSession} from 'next-auth/client'; | import { getSession } from "@lib/auth"; | ||||||
| import prisma from '../../../../lib/prisma'; | import prisma from "../../../../lib/prisma"; | ||||||
| 
 | 
 | ||||||
| const client_id = process.env.ZOOM_CLIENT_ID; | const client_id = process.env.ZOOM_CLIENT_ID; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1151,6 +1151,11 @@ | ||||||
|   dependencies: |   dependencies: | ||||||
|     "@babel/types" "^7.3.0" |     "@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": | "@types/graceful-fs@^4.1.2": | ||||||
|   version "4.1.5" |   version "4.1.5" | ||||||
|   resolved "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15" |   resolved "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15" | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue
	
	 Alex Johansson
						Alex Johansson