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

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

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"