Removes the getUser logic from createContext, instead use isAuthed (#7902)
* Removes the getUser logic from createContext, instead use isAuthed * Fix types :party
This commit is contained in:
@@ -29,7 +29,6 @@ export async function ssgInit<TParams extends { locale?: string }>(opts: GetStat
|
||||
ctx: {
|
||||
prisma,
|
||||
session: null,
|
||||
user: null,
|
||||
locale,
|
||||
i18n: _i18n,
|
||||
},
|
||||
|
||||
@@ -3,108 +3,32 @@ import type { Session } from "next-auth";
|
||||
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
|
||||
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { defaultAvatarSrc } from "@calcom/lib/defaultAvatarImage";
|
||||
import { getLocaleFromHeaders } from "@calcom/lib/i18n";
|
||||
import prisma from "@calcom/prisma";
|
||||
import type { SelectedCalendar, User as PrismaUser, Credential } from "@calcom/prisma/client";
|
||||
|
||||
import type { Maybe } from "@trpc/server";
|
||||
import type { CreateNextContextOptions } from "@trpc/server/adapters/next";
|
||||
|
||||
type CreateContextOptions = CreateNextContextOptions | GetServerSidePropsContext;
|
||||
|
||||
async function getUserFromSession({
|
||||
session,
|
||||
req,
|
||||
}: {
|
||||
session: Maybe<Session>;
|
||||
req: CreateContextOptions["req"];
|
||||
}) {
|
||||
if (!session?.user?.id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
where: {
|
||||
id: session.user.id,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
name: true,
|
||||
email: true,
|
||||
bio: true,
|
||||
timeZone: true,
|
||||
weekStart: true,
|
||||
startTime: true,
|
||||
endTime: true,
|
||||
defaultScheduleId: true,
|
||||
bufferTime: true,
|
||||
theme: true,
|
||||
createdDate: true,
|
||||
hideBranding: true,
|
||||
avatar: true,
|
||||
twoFactorEnabled: true,
|
||||
disableImpersonation: true,
|
||||
identityProvider: true,
|
||||
brandColor: true,
|
||||
darkBrandColor: true,
|
||||
away: true,
|
||||
credentials: {
|
||||
select: {
|
||||
id: true,
|
||||
type: true,
|
||||
key: true,
|
||||
userId: true,
|
||||
appId: true,
|
||||
invalid: true,
|
||||
},
|
||||
orderBy: {
|
||||
id: "asc",
|
||||
},
|
||||
},
|
||||
selectedCalendars: {
|
||||
select: {
|
||||
externalId: true,
|
||||
integration: true,
|
||||
},
|
||||
},
|
||||
completedOnboarding: true,
|
||||
destinationCalendar: true,
|
||||
locale: true,
|
||||
timeFormat: true,
|
||||
trialEndsAt: true,
|
||||
metadata: true,
|
||||
role: true,
|
||||
},
|
||||
});
|
||||
|
||||
// some hacks to make sure `username` and `email` are never inferred as `null`
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
const { email, username } = user;
|
||||
if (!email) {
|
||||
return null;
|
||||
}
|
||||
const rawAvatar = user.avatar;
|
||||
// This helps to prevent reaching the 4MB payload limit by avoiding base64 and instead passing the avatar url
|
||||
user.avatar = rawAvatar ? `${WEBAPP_URL}/${user.username}/avatar.png` : defaultAvatarSrc({ email });
|
||||
|
||||
const locale = user.locale || getLocaleFromHeaders(req);
|
||||
return {
|
||||
...user,
|
||||
rawAvatar,
|
||||
email,
|
||||
username,
|
||||
locale,
|
||||
};
|
||||
}
|
||||
|
||||
type CreateInnerContextOptions = {
|
||||
session: Session | null;
|
||||
locale: string;
|
||||
user: Awaited<ReturnType<typeof getUserFromSession>>;
|
||||
user?: Omit<
|
||||
PrismaUser,
|
||||
| "locale"
|
||||
| "twoFactorSecret"
|
||||
| "emailVerified"
|
||||
| "password"
|
||||
| "identityProviderId"
|
||||
| "invitedTo"
|
||||
| "allowDynamicBooking"
|
||||
| "verified"
|
||||
> & {
|
||||
locale: NonNullable<PrismaUser["locale"]>;
|
||||
credentials?: Credential[];
|
||||
selectedCalendars?: Partial<SelectedCalendar>[];
|
||||
};
|
||||
i18n: Awaited<ReturnType<typeof serverSideTranslations>>;
|
||||
} & Partial<CreateContextOptions>;
|
||||
|
||||
@@ -144,11 +68,9 @@ export const createContext = async (
|
||||
// for API-response caching see https://trpc.io/docs/caching
|
||||
const session = await sessionGetter({ req, res });
|
||||
|
||||
const user = await getUserFromSession({ session, req });
|
||||
const locale = user?.locale ?? getLocaleFromHeaders(req);
|
||||
const i18n = await serverSideTranslations(locale, ["common", "vital"]);
|
||||
|
||||
const contextInner = await createContextInner({ session, i18n, locale, user });
|
||||
const locale = getLocaleFromHeaders(req);
|
||||
const i18n = await serverSideTranslations(getLocaleFromHeaders(req), ["common", "vital"]);
|
||||
const contextInner = await createContextInner({ session, i18n, locale });
|
||||
return {
|
||||
...contextInner,
|
||||
req,
|
||||
|
||||
+102
-38
@@ -1,11 +1,96 @@
|
||||
import type { Session } from "next-auth";
|
||||
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
|
||||
import superjson from "superjson";
|
||||
|
||||
import rateLimit from "@calcom/lib/rateLimit";
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { defaultAvatarSrc } from "@calcom/lib/defaultAvatarImage";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
import type { Maybe } from "@trpc/server";
|
||||
import { initTRPC, TRPCError } from "@trpc/server";
|
||||
|
||||
import type { createContextInner } from "./createContext";
|
||||
|
||||
async function getUserFromSession({ session }: { session: Maybe<Session> }) {
|
||||
if (!session?.user?.id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
where: {
|
||||
id: session.user.id,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
name: true,
|
||||
email: true,
|
||||
bio: true,
|
||||
timeZone: true,
|
||||
weekStart: true,
|
||||
startTime: true,
|
||||
endTime: true,
|
||||
defaultScheduleId: true,
|
||||
bufferTime: true,
|
||||
theme: true,
|
||||
createdDate: true,
|
||||
hideBranding: true,
|
||||
avatar: true,
|
||||
twoFactorEnabled: true,
|
||||
disableImpersonation: true,
|
||||
identityProvider: true,
|
||||
brandColor: true,
|
||||
darkBrandColor: true,
|
||||
away: true,
|
||||
credentials: {
|
||||
select: {
|
||||
id: true,
|
||||
type: true,
|
||||
key: true,
|
||||
userId: true,
|
||||
appId: true,
|
||||
invalid: true,
|
||||
},
|
||||
orderBy: {
|
||||
id: "asc",
|
||||
},
|
||||
},
|
||||
selectedCalendars: {
|
||||
select: {
|
||||
externalId: true,
|
||||
integration: true,
|
||||
},
|
||||
},
|
||||
completedOnboarding: true,
|
||||
destinationCalendar: true,
|
||||
locale: true,
|
||||
timeFormat: true,
|
||||
trialEndsAt: true,
|
||||
metadata: true,
|
||||
role: true,
|
||||
},
|
||||
});
|
||||
|
||||
// some hacks to make sure `username` and `email` are never inferred as `null`
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
const { email, username } = user;
|
||||
if (!email) {
|
||||
return null;
|
||||
}
|
||||
const rawAvatar = user.avatar;
|
||||
// This helps to prevent reaching the 4MB payload limit by avoiding base64 and instead passing the avatar url
|
||||
user.avatar = rawAvatar ? `${WEBAPP_URL}/${user.username}/avatar.png` : defaultAvatarSrc({ email });
|
||||
|
||||
return {
|
||||
...user,
|
||||
rawAvatar,
|
||||
email,
|
||||
username,
|
||||
};
|
||||
}
|
||||
|
||||
const t = initTRPC.context<typeof createContextInner>().create({
|
||||
transformer: superjson,
|
||||
});
|
||||
@@ -18,20 +103,30 @@ const perfMiddleware = t.middleware(async ({ path, type, next }) => {
|
||||
return result;
|
||||
});
|
||||
|
||||
const isAuthedMiddleware = t.middleware(({ ctx, next }) => {
|
||||
if (!ctx.user || !ctx.session) {
|
||||
const isAuthed = t.middleware(async ({ ctx: { session, locale, ...ctx }, next }) => {
|
||||
const user = await getUserFromSession({ session });
|
||||
if (!user || !session) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
}
|
||||
const i18n =
|
||||
user.locale && user.locale !== locale
|
||||
? await serverSideTranslations(user.locale, ["common", "vital"])
|
||||
: ctx.i18n;
|
||||
locale = user.locale || locale;
|
||||
return next({
|
||||
ctx: {
|
||||
i18n,
|
||||
// infers that `user` and `session` are non-nullable to downstream procedures
|
||||
session: ctx.session,
|
||||
user: ctx.user,
|
||||
session,
|
||||
user: {
|
||||
...user,
|
||||
locale,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const isAdminMiddleware = isAuthedMiddleware.unstable_pipe(({ ctx, next }) => {
|
||||
const isAdminMiddleware = isAuthed.unstable_pipe(({ ctx, next }) => {
|
||||
if (ctx.user.role !== "ADMIN") {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
}
|
||||
@@ -40,41 +135,10 @@ const isAdminMiddleware = isAuthedMiddleware.unstable_pipe(({ ctx, next }) => {
|
||||
});
|
||||
});
|
||||
|
||||
interface IRateLimitOptions {
|
||||
intervalInMs: number;
|
||||
limit: number;
|
||||
}
|
||||
const isRateLimitedByUserIdMiddleware = ({ intervalInMs, limit }: IRateLimitOptions) =>
|
||||
t.middleware(({ ctx, next }) => {
|
||||
// validate user exists
|
||||
if (!ctx.user) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
}
|
||||
|
||||
const { isRateLimited } = rateLimit({ intervalInMs }).check(limit, ctx.user.id.toString());
|
||||
|
||||
if (isRateLimited) {
|
||||
throw new TRPCError({ code: "TOO_MANY_REQUESTS" });
|
||||
}
|
||||
|
||||
return next({
|
||||
ctx: {
|
||||
// infers that `user` and `session` are non-nullable to downstream procedures
|
||||
session: ctx.session,
|
||||
user: ctx.user,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
export const router = t.router;
|
||||
export const mergeRouters = t.mergeRouters;
|
||||
export const middleware = t.middleware;
|
||||
export const publicProcedure = t.procedure.use(perfMiddleware);
|
||||
export const authedProcedure = t.procedure.use(perfMiddleware).use(isAuthedMiddleware);
|
||||
export const authedRateLimitedProcedure = ({ intervalInMs, limit }: IRateLimitOptions) =>
|
||||
t.procedure
|
||||
.use(perfMiddleware)
|
||||
.use(isAuthedMiddleware)
|
||||
.use(isRateLimitedByUserIdMiddleware({ intervalInMs, limit }));
|
||||
export const authedProcedure = t.procedure.use(perfMiddleware).use(isAuthed);
|
||||
|
||||
export const authedAdminProcedure = t.procedure.use(perfMiddleware).use(isAdminMiddleware);
|
||||
|
||||
Reference in New Issue
Block a user