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) { | ||||
|     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; | ||||
| } | ||||
| export async function verifyPassword(password: string, hashedPassword: string) { | ||||
|   const isValid = await compare(password, hashedPassword); | ||||
|   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" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@types/bcryptjs": "^2.4.2", | ||||
|     "@types/jest": "^27.0.1", | ||||
|     "@types/node": "^16.6.1", | ||||
|     "@types/nodemailer": "^6.4.4", | ||||
|  |  | |||
|  | @ -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; | ||||
|     }, | ||||
|   }, | ||||
| }); | ||||
|  |  | |||
|  | @ -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}); | ||||
|  |  | |||
|  | @ -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}); | ||||
|  |  | |||
|  | @ -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}); | ||||
|  |  | |||
|  | @ -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') { | ||||
|  |  | |||
|  | @ -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']; | ||||
|  |  | |||
|  | @ -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; | ||||
| 
 | ||||
|  |  | |||
|  | @ -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']; | ||||
| 
 | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -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; | ||||
| 
 | ||||
|  |  | |||
|  | @ -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" | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue
	
	 Alex Johansson
						Alex Johansson