add type-safe getSession() (#486)

* fix types for auth
* implement safer to use `getSession`
This commit is contained in:
Alex Johansson 2021-08-18 13:52:25 +02:00 committed by GitHub
parent a162949cf1
commit a0a0ec86f0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 130 additions and 92 deletions

View file

@ -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;
}

View file

@ -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",

View file

@ -1,47 +1,56 @@
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: { pages: {
signIn: '/auth/login', signIn: "/auth/login",
signOut: '/auth/logout', signOut: "/auth/logout",
error: '/auth/error', // Error code passed in query string as ?error= error: "/auth/error", // Error code passed in query string as ?error=
}, },
providers: [ providers: [
Providers.Credentials({ Providers.Credentials({
name: 'Calendso', name: "Calendso",
credentials: { credentials: {
email: { label: "Email Address", type: "email", placeholder: "john.doe@example.com" }, email: { label: "Email Address", type: "email", placeholder: "john.doe@example.com" },
password: { label: "Password", type: "password", placeholder: "Your super secure password" } password: { label: "Password", type: "password", placeholder: "Your super secure password" },
}, },
async authorize(credentials) { async authorize(credentials) {
const user = await prisma.user.findFirst({ const user = await prisma.user.findFirst({
where: { where: {
email: credentials.email email: credentials.email,
} },
}); });
if (!user) { if (!user) {
throw new Error('No user found'); throw new Error("No user found");
}
if (!user.password) {
throw new Error("Incorrect password");
} }
const isValid = await verifyPassword(credentials.password, user.password); const isValid = await verifyPassword(credentials.password, user.password);
if (!isValid) { if (!isValid) {
throw new Error('Incorrect password'); throw new Error("Incorrect password");
} }
return {id: user.id, username: user.username, email: user.email, name: user.name, image: user.avatar}; return {
} id: user.id,
}) username: user.username,
email: user.email,
name: user.name,
image: user.avatar,
};
},
}),
], ],
callbacks: { callbacks: {
async jwt(token, user, account, profile, isNewUser) { async jwt(token, user) {
// Add username to the token right after signin // Add username to the token right after signin
if (user?.username) { if (user?.username) {
token.id = user.id; token.id = user.id;
@ -50,10 +59,15 @@ export default NextAuth({
return token; return token;
}, },
async session(session, token) { async session(session, token) {
session.user = session.user || {} const calendsoSession: CalendsoSession = {
session.user.id = token.id; ...session,
session.user.username = token.username; user: {
return session; ...session.user,
id: token.id as number,
username: token.username as string,
},
};
return calendsoSession;
}, },
}, },
}); });

View file

@ -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});

View file

@ -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";
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) {

View file

@ -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});

View file

@ -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') {

View file

@ -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'];

View file

@ -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;

View file

@ -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'];

View file

@ -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;

View file

@ -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;

View file

@ -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"