Files
calendar/packages/trpc/server/middlewares/sessionMiddleware.ts
T
+1 12466fb064 feat: New Organization Schema along with the ability to invite existing users to an organization (#13002)
* Add profile table

Copy to profile table

Update schema

Migrations

* wip

* Get switcher working

* Add organization seeder

* wip

* Remove dead code

* Fix unit tests

* relevantProfile -> profile

* cache bust

* One more comment

* Squash migrations

* Copy to profile table

* Update schema

* Fix existing user invite flow

* Add profile table

* Copy to profile table

* Update schema

* Format

* Add profile table

Copy to profile table

Update schema

Migrations

* Format

* wip

Get switcher working

Add organization seeder

wip

Remove dead code

Fix unit tests

relevantProfile -> profile

cache bust

One more comment

Squash migrations

Copy to profile table

Update schema

Fix existing user invite flow

Add profile table

Copy to profile table

Update schema

chore: small improvements to signup (#13227)

* removed features on mobile, better background color

* nit

* dark mode nits

New Crowdin translations by Github Action

fix: event reschedule workflow trigger (#13122)

* fix event reschedule workflow trigger

* uncomment previously failed test

* review fix

---------

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: CarinaWolli <wollencarina@gmail.com>

chore: added community support to help (#13213)

refactor: Abstract `handleSeats` from `handleNewBooking` (#12062)

* Refactor createBooking

* Type fix

* Abstract handleSeats

* Create Invitee type

* Create OrganizerUser type

* Abstract addVideoCallDataToEvt

* Abstract createLoggerWithEventDetails

* Abstract `handleAppStatus` from handler

* Create ReqAppsStatus type

* Move `deleteMeeting` and `getCalendar`

* Set parameters for `handleSeats`

* Typescript refactor

* Change function params from req

* Type fix

---------

Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
Co-authored-by: Erik <erik@erosemberg.com>

fix: Dynamic group booking  org-migration redirect (#13223)

* fix: Dynamic group booking redirect

* Fix flaky private team test

test: Check availability tab and all your functions (teste2e-availability) (#12434)

* add changes

* add changes

* add changes

* add changes

* Requested changes

* Change way to grab element

---------

Co-authored-by: gitstart-calcom <gitstart-calcom@users.noreply.github.com>
Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>

fix: Reschedule a cancelled booking (#13170)

* fix: Reschedule a cancelled booking

* canceled instead of cancelled

---------

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>

v3.7.0

test: In Team Event type when location is Organizer Default App and U… (#12939)

* test: In Team Event type when location is Organizer Default App and User Metadata is emoty

* chore: use variable

* fix: type error

test: add test for invalid cal video (#13103)

* test: add test for invalid cal video

* chore: add confirm

* chore: fix type error

* chore: update code

* test: add unit test

* chore: type error

* chore

* chore: type

* chore: fix test

* chore

---------

Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>

chore: add error message for no availability (#13230)

* chore: add error message for no default user availability

* chore: check only availability

* chore: change message

* chore: add eventType

fix: Team revert with members (#13228)

refactor: Refactor seats logic (#12905)

* Refactor createBooking

* Type fix

* Abstract handleSeats

* Create Invitee type

* Create OrganizerUser type

* Abstract addVideoCallDataToEvt

* Abstract createLoggerWithEventDetails

* Abstract `handleAppStatus` from handler

* Create ReqAppsStatus type

* Move `deleteMeeting` and `getCalendar`

* Set parameters for `handleSeats`

* Typescript refactor

* Change function params from req

* Type fix

* Move handleSeats

* Abstract lastAttendeeDeleteBooking

* Create function for rescheduling seated events

* Fix imports on reschedule seats function

* Fix imports

* Import handleSeats function

* Fix rescheduleUid type

* Refactor owner reschedule to new time slot

* Refactor combine two booking times together

* Reschedule as an attendee

* Refactor createNewSeat

* Remove old handleSeats

* Remove lastAttendeeDeleteBooking from handleNewBooking

* Test for new attendee right params are passed

* Unit test params for reschedule

* Typo fix

* Clean up

* Create new seat test

* Test when attendee already signs up for booking

* Type fix

* Test reschedule move attendee to existing booking

* On reschedule create new booking

* Test on last attendee cancel booking

* Owner reschedule to new time slot

* Owner rescheduling, merge two bookings together

* Test: when merging more than available seats, then fail

* Test: fail when event is full

* Remove duplicate E2E tests

* Clean up

* Rename `addVideoCallDataToEvt` to `addVideoCallDataToEvent`

* Refactor `calcAppsStatus`

* Assign `evt` to resutl of `addVideoCallDataToEvent`

* Use prisma.transaction when moving attendees

* Clean create seat call

* Use ErrorCode enum

* Use attendeeRescheduledSeatedBooking function

* Await function

* Prevent double triggering of workflows

* Use inviteeToAdd in createNewSeat

* Remove unused error code

* Remove old handleSeats file

* Type fix

* Type fix

* Type fix

* Type fix

* Type fix

* Type fix

* Type fix

* Type fix

---------

Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Erik <erik@erosemberg.com>

feat: Allow admins to filter bookings only by attendee emails (#13236)

* chore: Allow admins to filter bookings only by attendee emails

* Fix type error

Fixed [CAL-2930] Org branding isnt centered in shell #13225 (#13237)

Add profile table

Copy to profile table

Update schema

Migrations

Format

* wip

* Add profile relation everywhere

* Fix schema

* Fix invite handler

* Add profileId during eventTypec reation

* Get dashboard working with profile relatioon

* Add workflow create repository

* Add profileId to host

* Add profileId to credential table

* Add destinationCalendar repository

* Add profileId to membership

* Revert all profile related changes

* Add acme and dunder

* Fix membership profileId usgae

* Various fixes

* Some renaming

* Fix failing tests

* Debugging next-aut

* Consolidate migrations

* Testing fixes

* Fix unit tests

* Self review feedback

* PR feedback

* Fix orgMigration as well

* Add profile schema and copy users to it

* Remove migrations from here

* Fix type issues

* Fix type error after main merge

* Remove movedFromUserId

* Add missing migration file

* Self review addressed

* Joe's review addressed

* Fix profile switcher

---------

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
2024-02-02 21:34:13 +00:00

235 lines
6.7 KiB
TypeScript

import type { Session } from "next-auth";
import { WEBAPP_URL } from "@calcom/lib/constants";
import logger from "@calcom/lib/logger";
import { safeStringify } from "@calcom/lib/safeStringify";
import { ProfileRepository } from "@calcom/lib/server/repository/profile";
import { UserRepository } from "@calcom/lib/server/repository/user";
import { teamMetadataSchema, userMetadata } from "@calcom/prisma/zod-utils";
import { TRPCError } from "@trpc/server";
import type { TRPCContextInner } from "../createContext";
import { middleware } from "../trpc";
type Maybe<T> = T | null | undefined;
export async function getUserFromSession(ctx: TRPCContextInner, session: Maybe<Session>) {
const { prisma } = ctx;
if (!session) {
return null;
}
if (!session.user?.id) {
return null;
}
const userFromDb = await prisma.user.findUnique({
where: {
id: session.user.id,
// Locked users can't login
locked: false,
},
select: {
id: true,
username: true,
name: true,
email: true,
emailVerified: true,
bio: true,
avatarUrl: true,
timeZone: true,
weekStart: true,
startTime: true,
endTime: true,
defaultScheduleId: true,
bufferTime: true,
theme: true,
createdDate: true,
hideBranding: true,
twoFactorEnabled: true,
disableImpersonation: true,
identityProvider: true,
brandColor: true,
darkBrandColor: true,
away: true,
movedToProfileId: true,
selectedCalendars: {
select: {
externalId: true,
integration: true,
},
},
completedOnboarding: true,
destinationCalendar: true,
locale: true,
timeFormat: true,
trialEndsAt: true,
metadata: true,
role: true,
allowDynamicBooking: true,
allowSEOIndexing: true,
receiveMonthlyDigestEmail: true,
},
});
// some hacks to make sure `username` and `email` are never inferred as `null`
if (!userFromDb) {
return null;
}
const upId = session.upId;
const user = await UserRepository.enrichUserWithTheProfile({
user: userFromDb,
upId,
});
logger.debug(
`getUserFromSession: enriched user with profile - ${ctx.req?.url}`,
safeStringify({ user, userFromDb, upId })
);
const { email, username, id } = user;
if (!email || !id) {
return null;
}
const userMetaData = userMetadata.parse(user.metadata || {});
const orgMetadata = teamMetadataSchema.parse(user.profile?.organization?.metadata || {});
// This helps to prevent reaching the 4MB payload limit by avoiding base64 and instead passing the avatar url
const locale = user?.locale ?? ctx.locale;
const isOrgAdmin = !!user.profile?.organization?.members.filter(
(member) => (member.role === "ADMIN" || member.role === "OWNER") && member.userId === user.id
).length;
if (isOrgAdmin) {
logger.debug("User is an org admin", safeStringify({ userId: user.id }));
} else {
logger.debug("User is not an org admin", safeStringify({ userId: user.id }));
}
// Want to reduce the amount of data being sent
if (isOrgAdmin && user.profile?.organization?.members) {
user.profile.organization.members = [];
}
const organization = {
...user.profile?.organization,
id: user.profile?.organization?.id ?? null,
isOrgAdmin,
metadata: orgMetadata,
requestedSlug: orgMetadata?.requestedSlug ?? null,
};
return {
...user,
avatar: `${WEBAPP_URL}/${user.username}/avatar.png?${organization.id}` && `orgId=${organization.id}`,
// TODO: OrgNewSchema - later - We could consolidate the props in user.profile?.organization as organization is a profile thing now.
organization,
organizationId: organization.id,
id,
email,
username,
locale,
defaultBookerLayouts: userMetaData?.defaultBookerLayouts || null,
};
}
export type UserFromSession = Awaited<ReturnType<typeof getUserFromSession>>;
const getSession = async (ctx: TRPCContextInner) => {
const { req, res } = ctx;
const { getServerSession } = await import("@calcom/features/auth/lib/getServerSession");
return req ? await getServerSession({ req, res }) : null;
};
const getUserSession = async (ctx: TRPCContextInner) => {
/**
* It is possible that the session and user have already been added to the context by a previous middleware
* or when creating the context
*/
const session = ctx.session || (await getSession(ctx));
const user = session ? await getUserFromSession(ctx, session) : null;
let foundProfile = null;
// Check authorization for profile
if (session?.profileId && user?.id) {
foundProfile = await ProfileRepository.findByUserIdAndProfileId({
userId: user.id,
profileId: session.profileId,
});
if (!foundProfile) {
logger.error(
"Profile not found or not authorized",
safeStringify({ profileId: session.profileId, userId: user?.id })
);
// TODO: Test that logout should happen automatically
throw new TRPCError({ code: "UNAUTHORIZED", message: "Profile not found or not authorized" });
}
}
let sessionWithUpId = null;
if (session) {
let upId = session.upId;
if (!upId) {
upId = foundProfile?.upId ?? `usr-${user?.id}`;
}
if (!upId) {
throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "No upId found for session" });
}
sessionWithUpId = {
...session,
upId,
};
}
return { user, session: sessionWithUpId };
};
const sessionMiddleware = middleware(async ({ ctx, next }) => {
const middlewareStart = performance.now();
const { user, session } = await getUserSession(ctx);
const middlewareEnd = performance.now();
logger.debug("Perf:t.sessionMiddleware", middlewareEnd - middlewareStart);
return next({
ctx: { user, session },
});
});
export const isAuthed = middleware(async ({ ctx, next }) => {
const middlewareStart = performance.now();
const { user, session } = await getUserSession(ctx);
const middlewareEnd = performance.now();
logger.debug("Perf:t.isAuthed", middlewareEnd - middlewareStart);
if (!user || !session) {
throw new TRPCError({ code: "UNAUTHORIZED" });
}
return next({
ctx: { user, session },
});
});
export const isAdminMiddleware = isAuthed.unstable_pipe(({ ctx, next }) => {
const { user } = ctx;
if (user?.role !== "ADMIN") {
throw new TRPCError({ code: "UNAUTHORIZED" });
}
return next({ ctx: { user: user } });
});
// Org admins can be admins or owners
export const isOrgAdminMiddleware = isAuthed.unstable_pipe(({ ctx, next }) => {
const { user } = ctx;
if (!user?.organization?.isOrgAdmin) {
throw new TRPCError({ code: "UNAUTHORIZED" });
}
return next({ ctx: { user: user } });
});
export default sessionMiddleware;