perf: use getSlimServerSession for session retrieval (#8308)
* perf: use getSlimServerSession for session retrieval Creates a new `getSlimServerSession` method and replaces all calls to `getServerSession`. The new method is much faster than `getServerSession` with it not requiring the NextAuth options object which includes a number of additional packages. Additionally introduces a primitive in-memory cache for slim sessions. * fix: account for seconds in token.exp * Reverts diffs * Replaces getServerSession with slimmer version * Update disable.ts --------- Co-authored-by: Peer Richelsen <peeroke@gmail.com> Co-authored-by: Keith Williams <keithwillcode@gmail.com> Co-authored-by: zomars <zomars@me.com>pull/8339/head
parent
667d42d5da
commit
e5eb7c3906
|
@ -1,18 +1,85 @@
|
|||
import type { GetServerSidePropsContext, NextApiRequest, NextApiResponse } from "next";
|
||||
import type { AuthOptions, Session } from "next-auth";
|
||||
import { getServerSession as getServerSessionInner } from "next-auth/next";
|
||||
import { getToken } from "next-auth/jwt";
|
||||
|
||||
import { AUTH_OPTIONS } from "./next-auth-options";
|
||||
import checkLicense from "@calcom/features/ee/common/server/checkLicense";
|
||||
import { CAL_URL } from "@calcom/lib/constants";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
/**
|
||||
* Stores the session in memory using the stringified token as the key.
|
||||
*
|
||||
* This is fine for production as each lambda will be recycled before this
|
||||
* becomes large enough to cause issues.
|
||||
*
|
||||
* If we want to get extra spicy we could store this in edge config or similar
|
||||
* so we can TTL things and benefit from faster retrievals than prisma.
|
||||
*/
|
||||
const UNSTABLE_SESSION_CACHE = new Map<string, Session>();
|
||||
|
||||
/**
|
||||
* This is a slimmed down version of the `getServerSession` function from
|
||||
* `next-auth`.
|
||||
*
|
||||
* Instead of requiring the entire options object for NextAuth, we create
|
||||
* a compatible session using information from the incoming token.
|
||||
*
|
||||
* The downside to this is that we won't refresh sessions if the users
|
||||
* token has expired (30 days). This should be fine as we call `/auth/session`
|
||||
* frequently enough on the client-side to keep the session alive.
|
||||
*/
|
||||
export async function getServerSession(options: {
|
||||
req: NextApiRequest | GetServerSidePropsContext["req"];
|
||||
res: NextApiResponse | GetServerSidePropsContext["res"];
|
||||
res?: NextApiResponse | GetServerSidePropsContext["res"];
|
||||
authOptions?: AuthOptions;
|
||||
}) {
|
||||
const { req, res, authOptions = AUTH_OPTIONS } = options;
|
||||
const { req, authOptions: { secret } = {} } = options;
|
||||
|
||||
const session = await getServerSessionInner(req, res, authOptions);
|
||||
const token = await getToken({
|
||||
req,
|
||||
secret,
|
||||
});
|
||||
|
||||
// that these are equal are ensured in `[...nextauth]`'s callback
|
||||
return session as Session | null;
|
||||
if (!token || !token.email || !token.sub) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const cachedSession = UNSTABLE_SESSION_CACHE.get(JSON.stringify(token));
|
||||
|
||||
if (cachedSession) {
|
||||
return cachedSession;
|
||||
}
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
where: {
|
||||
email: token.email.toLowerCase(),
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const hasValidLicense = await checkLicense(prisma);
|
||||
|
||||
const session: Session = {
|
||||
hasValidLicense,
|
||||
expires: new Date(typeof token.exp === "number" ? token.exp * 1000 : Date.now()).toISOString(),
|
||||
user: {
|
||||
id: user.id,
|
||||
name: user.name,
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
emailVerified: user.emailVerified,
|
||||
email_verified: user.emailVerified !== null,
|
||||
role: user.role,
|
||||
image: `${CAL_URL}/${user.username}/avatar.png`,
|
||||
impersonatedByUID: token.impersonatedByUID ?? undefined,
|
||||
belongsToActiveTeam: token.belongsToActiveTeam,
|
||||
},
|
||||
};
|
||||
|
||||
UNSTABLE_SESSION_CACHE.set(JSON.stringify(token), session);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
|
|
@ -42,13 +42,16 @@ type UserTeams = {
|
|||
})[];
|
||||
};
|
||||
|
||||
const checkIfUserBelongsToActiveTeam = <T extends UserTeams>(user: T): boolean =>
|
||||
user.teams.filter((m: { team: { metadata: unknown } }) => {
|
||||
if (!IS_TEAM_BILLING_ENABLED) return true;
|
||||
export const checkIfUserBelongsToActiveTeam = <T extends UserTeams>(user: T) =>
|
||||
user.teams.some((m: { team: { metadata: unknown } }) => {
|
||||
if (!IS_TEAM_BILLING_ENABLED) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const metadata = teamMetadataSchema.safeParse(m.team.metadata);
|
||||
if (metadata.success && metadata.data?.subscriptionId) return true;
|
||||
return false;
|
||||
}).length > 0;
|
||||
|
||||
return metadata.success && metadata.data?.subscriptionId;
|
||||
});
|
||||
|
||||
const providers: Provider[] = [
|
||||
CredentialsProvider({
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type { User as PrismaUser } from "@prisma/client";
|
||||
import type { User as PrismaUser, UserPermissionRole } from "@prisma/client";
|
||||
import type { DefaultUser } from "next-auth";
|
||||
|
||||
declare module "next-auth" {
|
||||
|
@ -9,6 +9,7 @@ declare module "next-auth" {
|
|||
hasValidLicense: boolean;
|
||||
user: User;
|
||||
}
|
||||
|
||||
interface User extends Omit<DefaultUser, "id"> {
|
||||
id: PrismaUser["id"];
|
||||
emailVerified?: PrismaUser["emailVerified"];
|
||||
|
@ -19,3 +20,15 @@ declare module "next-auth" {
|
|||
role?: PrismaUser["role"] | "INACTIVE_ADMIN";
|
||||
}
|
||||
}
|
||||
|
||||
declare module "next-auth/jwt" {
|
||||
interface JWT {
|
||||
id?: string | number;
|
||||
name?: string | null;
|
||||
username?: string | null;
|
||||
email?: string | null;
|
||||
role?: UserPermissionRole | "INACTIVE_ADMIN" | null;
|
||||
impersonatedByUID?: number | null;
|
||||
belongsToActiveTeam?: boolean;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue