Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DWD with calendar-cache support #18619

Draft
wants to merge 75 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
9cc1fc9
Add endpoints for testing the flow
hariombalhara Sep 11, 2024
cf74351
Add MVP
hariombalhara Sep 13, 2024
18a144d
new route
hariombalhara Sep 15, 2024
9a26de9
Merge remote-tracking branch 'origin/main' into domain-wide-delegatio…
hariombalhara Sep 15, 2024
ddade88
Fixes
hariombalhara Sep 17, 2024
d1d5395
Merge remote-tracking branch 'origin/main' into domain-wide-delegatio…
hariombalhara Sep 20, 2024
77fba87
Fixes
hariombalhara Sep 20, 2024
c29e729
Remove enable toggle support from domainWideDelegation
hariombalhara Sep 21, 2024
3ece0d5
Fixes
hariombalhara Sep 23, 2024
cc0b5ea
Revert "Remove enable toggle support from domainWideDelegation"
hariombalhara Sep 23, 2024
6a38c56
Revert yarn.lock
hariombalhara Sep 23, 2024
5ffe8a2
More fixes
hariombalhara Sep 23, 2024
0bca65c
Merge branch 'main' into domain-wide-delegation-google-calendar
alishaz-polymath Oct 18, 2024
f28a5bc
Merge remote-tracking branch 'origin/main' into domain-wide-delegatio…
hariombalhara Oct 23, 2024
119ab4b
Merge remote-tracking branch 'origin/domain-wide-delegation-google-ca…
hariombalhara Oct 23, 2024
5c825c4
Fix new workspace platform add
hariombalhara Oct 23, 2024
ddabdad
Merge branch 'main' into domain-wide-delegation-google-calendar
Udit-takkar Nov 5, 2024
39efab2
Merge branch 'main' into domain-wide-delegation-google-calendar
Udit-takkar Nov 19, 2024
78c3e67
refactor: improvements
Udit-takkar Nov 21, 2024
94eb887
refactor: bug fixes and improvements
Udit-takkar Nov 22, 2024
571d19b
fix: type errors
Udit-takkar Nov 22, 2024
6a12da9
Merge branch 'main' into domain-wide-delegation-google-calendar
Udit-takkar Nov 22, 2024
f241adc
fix: conflicts
Udit-takkar Nov 22, 2024
51f3344
chore: update test
Udit-takkar Nov 22, 2024
64a4ff7
fix: logic
Udit-takkar Nov 22, 2024
dd29641
chore: improvements
Udit-takkar Nov 22, 2024
d30c1ba
fix: toglle
Udit-takkar Nov 22, 2024
3ce6322
fix: bugs
Udit-takkar Nov 22, 2024
c62ad22
fix: type err
Udit-takkar Nov 22, 2024
002874d
Merge branch 'main' into domain-wide-delegation-google-calendar
Udit-takkar Nov 26, 2024
63187ea
chore: check number type
Udit-takkar Nov 22, 2024
e4d5421
fix: after conflicts
Udit-takkar Nov 26, 2024
2fef7cf
chore: fix type err
Udit-takkar Nov 26, 2024
1b42a1f
fix: type errors and tests
Udit-takkar Nov 26, 2024
e306763
fix: tets
Udit-takkar Nov 26, 2024
b1a01bc
test
Udit-takkar Nov 26, 2024
425ead8
chore: remove unused
Udit-takkar Nov 27, 2024
e1e53a2
fix: google meet url on booking page and secondary calendar
Udit-takkar Nov 27, 2024
d394fe2
fix: add property
Udit-takkar Nov 28, 2024
5ce0627
fix: type err
Udit-takkar Nov 28, 2024
0b7b9aa
fix: re assingment bug
Udit-takkar Nov 29, 2024
0ade51f
fix: use getAllCredentials
Udit-takkar Dec 1, 2024
8193c62
chore: fix import
Udit-takkar Dec 1, 2024
7f284e8
fix: installed count
Udit-takkar Dec 2, 2024
1169cde
fix: pass event type
Udit-takkar Dec 2, 2024
b4b64f2
Merge remote-tracking branch 'origin/main' into domain-wide-delegatio…
hariombalhara Dec 3, 2024
e43500f
fix: import
Udit-takkar Dec 3, 2024
2a1e166
fix: [Stacked PR] Review fixes (#17958)
hariombalhara Dec 3, 2024
260496a
refactor: use repository
Udit-takkar Dec 3, 2024
6af4f99
chore: remove duplicate
Udit-takkar Dec 3, 2024
08fb6e7
fix: More review fixes for domain wide delegation (#17969)
hariombalhara Dec 3, 2024
46f7afd
fix ts error
hariombalhara Dec 4, 2024
74d55c8
Fix getSchedule not using dwd credentials (#17995)
hariombalhara Dec 4, 2024
a691530
fix: Remove direct DWD table access from google-calendar and remove d…
hariombalhara Dec 5, 2024
164a6b3
Remove fn rename to reduce number of files changed
hariombalhara Dec 5, 2024
460fbbf
Merge remote-tracking branch 'origin/main' into domain-wide-delegatio…
hariombalhara Dec 5, 2024
0cf9e3d
Merge remote-tracking branch 'origin/main' into domain-wide-delegatio…
hariombalhara Dec 9, 2024
3a2956e
chore: check feature globally
Udit-takkar Dec 10, 2024
a137078
test: add unit tests
Udit-takkar Dec 11, 2024
19504b9
chore: move function
Udit-takkar Dec 11, 2024
2f939a4
test: add booking test
Udit-takkar Dec 11, 2024
934ffb1
wip
hariombalhara Jan 8, 2025
940d669
Merge remote-tracking branch 'origin/main' into domain-wide-delegatio…
hariombalhara Jan 8, 2025
8656758
Remove domain-wide-delegation team feature flag
hariombalhara Jan 8, 2025
2b14aea
Make sure duplicate calendars are not shown due to DWD. Show DWD when…
hariombalhara Jan 8, 2025
bad611f
Fix tests and their ts errors
hariombalhara Jan 8, 2025
06084b0
Fix more tests
hariombalhara Jan 8, 2025
4cb96a9
Move findUsersForAvailabilityCheck to separate file as it has AppStor…
hariombalhara Jan 8, 2025
ebc44d8
Merge remote-tracking branch 'origin/main' into domain-wide-delegatio…
hariombalhara Jan 10, 2025
7dd34c8
fix: Multiple calendar connections from Google not showing up in apps…
hariombalhara Jan 10, 2025
0e5293f
DestinationCalendar must have either credentialId or domainWideDeelga…
hariombalhara Jan 10, 2025
560b3a0
Disable deletetion of DWD as it is destructive and prefer disabling i…
hariombalhara Jan 10, 2025
6c5a379
Show DWD credential calendars at the top
hariombalhara Jan 10, 2025
feca132
Fixed tests
hariombalhara Jan 11, 2025
009f236
Calendar Cache DWD support
hariombalhara Jan 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/api/v1/pages/api/credential-sync/_post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ async function handler(req: NextApiRequest) {
// ^ Workaround for the select in `create` not working

if (createCalendarResources) {
const calendar = await getCalendar(credential);
const calendar = await getCalendar({ ...credential, delegatedTo: null });
if (!calendar) throw new HttpError({ message: "Calendar missing for credential", statusCode: 500 });
const calendars = await calendar.listCalendars();
const calendarToCreate = calendars.find((calendar) => calendar.primary) || calendars[0];
Expand Down
3 changes: 2 additions & 1 deletion apps/api/v1/pages/api/destination-calendars/[id]/_patch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ type UserCredentialType = {
teamId: number | null;
key: Prisma.JsonValue;
invalid: boolean | null;
domainWideDelegationCredentialId?: string | null;
};

export async function patchHandler(req: NextApiRequest) {
Expand Down Expand Up @@ -185,7 +186,7 @@ async function verifyCredentialsAndGetId({
currentCredentialId: number | null;
}) {
if (parsedBody.integration && parsedBody.externalId) {
const calendarCredentials = getCalendarCredentials(userCredentials);
const calendarCredentials = await getCalendarCredentials(userCredentials);

const { connectedCalendars } = await getConnectedCalendars(
calendarCredentials,
Expand Down
2 changes: 1 addition & 1 deletion apps/api/v1/pages/api/destination-calendars/_post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ async function postHandler(req: NextApiRequest) {
message: "Bad request, credential id invalid",
});

const calendarCredentials = getCalendarCredentials(userCredentials);
const calendarCredentials = await getCalendarCredentials(userCredentials);

const { connectedCalendars } = await getConnectedCalendars(calendarCredentials, [], parsedBody.externalId);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
## Version 1.0
### Release Plan
- Read the document(domain-wide-delegation.md) and acknowledge it.
- Deploy
1. Follow "Setting up Domain-Wide Delegation for Google Calendar API" in domain-wide-delegation.md to create Service Account and create a workspace.
2. Merge PR and then deploy.
- Enabling for i.cal.com
- 1. Enable DWD for i.cal.com first and then test there
- 2. Wait for 1-2 days and keep monitoring the errors in Sentry and Axiom.
- Enable for a big customer
- 1. Wait for a week and keep monitoring the errors in Sentry and Axiom.
- Followup with sorting the credentials with DWD credentials first
- Monitor the errors in Sentry and Axiom.

### Important
- Bugs
- [ ] Duplicate Calendar Events in Google Calendar when choosing non-primary calendar as destination. No idea why this is happening.
- [x] Duplicate Calendar connections in 'apps/installed' if a user already had connected calendar and DWD is enabled.
- [x] Calendar Cache has credentialId column which isn't applicable for DWD(Solution: Added userId there)
- Manual Testing
- [ ] Test with Multiple DWD entries for different organizations. Verify that wrong DWD entry isn't used.
- [ ] Location Change of a booking to Google Meet(from Cal Video)
- [ ] RR Team Event
- Booking
- Unavailable slot isn't available for booking. Unavailable user isn't used.
- Reroute
- Reassign
- [ ] Calendar Cache
- [x] Troubleshooter
- [ ] Shows busy times from Claendar
- [x] If a user has connected a calendar, and then DWD is enabled.
- Tested various scenarios for it
- [x] Inviting a new user.
- Verified that Google Calendar is shown pre-installed.
- How about Google Meet(which depends on Google Calendar) - Correctly shows up as installed.
- TODO:
- [x] Troubleshooter
- [x] Google CalendarService unit tests to verify that if DWD credential is provided it uses impersonation to access API otherwise it uses regular user credential API.
- [x] setDestinationCalendar.handler.ts tests to verify that when DWD is enabled it still correctly sets the destination calendar.
- [x] getConnectedDestinationCalendars tests.
- [x] Creating DWD shouldn't immediately enable it. Enabling has separate check to confirm if it is actually configured in google workspace
- [x] Added check to avoid adding same domain for a workspace platform in another organization if it is already enabled in some other organization
- [x] Don't show dwd in menu for non-org-admin users - It errors with something_went_wrong right now
- [x] Don't allow disabled platform to be selected in the UI for creation.
- We have disabled coming the disabled platform to be coming into the list that effectively disables edit of existing dwd and creation of new dwd for that platform.
- [x] Where should we show the user the client ID to enable domain wide delegation?
- [x] It must be shown to the organization owner/admin only
- [x] There could be multiple checkboxes per domain to enable domain wide delegation for a domain
- [x] Which domain to allow
- Any domain can be added by a user
- [x] Support multiple domains in DomainWideDelegation schema for an organization
- [x] Use the domain as well to identify if the domain wide delegation is enabled
- [x] Before enabling Domain-wide delegation, there should be a check to ensure that the clientID has been added to the Workspace Platform
- [x] We should allow setting default conferencing app during onboarding

### Follow-up release
- [ ] Confirmation for DwD deletion and disabling
- [ ] If DWD is enabled and the org member doesn't exist in Google Workspace, and the user has connected personal account, should we correctly use the personal account?

### Security
- [x] We don't let any one user see the added service account key from UI.
- [ ] We intend to implement Workload Identity Federation in the future.

### Documentation
- After enabling domain-wide delegation, the credential is shown pre-installed and the connection can't be removed(or the app can't be uninstalled by user)
- Steps
- App admin will first create a Workspace Platform and then organization owner/admin can enable domain-wide delegation for a domain
- As soon as domain-wide delegation is created, it would start taking preference over the personal credentials of the organization members and it would be used for that.

Version-2.0
- Workload Identity Federation to ensure that the service account key is never stored in DB.



Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
## Setting up Domain-Wide Delegation for Google Calendar API

Step 1: Create a Google Cloud Project

Before you can create a service account, you'll need to set up a Google Cloud project.

1. Create a Google Cloud Project:
- Go to the Google Cloud Console
- Select Create Project
- Give your project a name and select your billing account (if applicable)
- Click Create
2. Enable the Google Calendar API:
1. Go to the Google Cloud Console
2. Select API & Services → Library
3. Search for "Google Calendar API"
4. Click Enable

Step 2: Create a Service Account

A service account is needed to act on behalf of users

1. Navigate to the Service Accounts page:
- In the Google Cloud Console, go to IAM & Admin → Service Accounts
2. Create a New Service Account:
- Click on Create Service Account
- Give your service account a name and description
- Click Create and Continue

Step 3: To Be taken by Cal.com instance admin:
- Create a Workspace Platform with slug="google". Slug has to be exactly this. This is how we know we need to use Google Calendar and Google Meet.

Last Step (To Be Taken By Cal.com organization Owner/Admin): Assign Specific API Permissions via OAuth Scopes:
- Create DWD with workspace platform "google"
- User must be a member of the Google Workspace to be able to enable DWD as there is a validation if the user's calendar can be accessed through the service account
- Get the Client ID from there
- Go to your Google Admin Console (admin-google-com)
- Navigate to Security → Access and Data Controls -> API controls -> Manage Domain-Wide Delegation
- Here, you'll authorize the Client ID(Unique ID) to access the Google Calendar API
- Add the necessary API scopes for Google Calendar(Full access to Google Calendar)
https://www.googleapis.com/auth/calendar


## Restrictions after enabling DWD
- Enabling DWD for a particular workspace in Cal.com(only google supported at the moment) disables the user from disconnecting that credential.

## Developer Notes
### How DWD works
- We use the Cal.com user's email to impersonate that user using DWD Credential(which is just a service account key at the moment)
- That gives us read/write permission to get availability of the user and create new events in their calendar.

### What is a DWD Credential?
- A DWD service account key along with user's email becomes the DWD Credential which is an alternative to regular Credential in DB.
- DWD doesn't completely replace the regular credentials. DWD Credential gives access to the cal.com user's email in Google Calendar. So, if the user needs to connect to some other email's calendar, we need to use the regular credentials.

### Important Points
- No Credential table entry is created when enabling DWD. The workspace platform's related apps will be considered as "installed" for the users with email matching dwd domain. An in-memory credential like object is created for this purpose. It allows avoiding creation of thousands of records for all the members of the organization when dwd is enabled.
- DWD Credential is applicable to Users only.
- For team, we don't use dwd credential as you can impersonate a user and not team through Dwd credential. Currently supported apps(Google Calendar and Google Meet) don't support team installation, so we could simply allow enabling DWD without any issues.
- Disabling a workspace platform stops it from being used for any new organizations and also disables any DWD using the workspace platform from being edited.
- It still all existing DWDs to keep on working
- Adding any number of DWDs for a particular workspace always gives the same Client ID as DWD uses the workspace's default Service Account.
- We should disable DWD and not delete it when we want to stop using it temporarily. Deleting DWD also removes all the seletedCalendar entries connected to it.

### How apps/installed loads the credentials
1. Identify the logged in user's email
2. Identify the domainWideDelegations for that email's domain
3. Build in-memory credentials for the domainWideDelegations and use them along with the actual credentials(that user might have connected) of the user
4. We don't show the non-dwd connected calendar(if there is a corresponding dwd connected calendar). Though we use the non-dwd credentials to identify the selected calendars, for the dwd connected calendar.


## Impact on existing users booking flow
- There should be no impact on availability on enabling DWD because we keep on using the existing credentials along with new DWD credential.
- When booking the event, we sort the credentials with DWD credentials last, so there should be no impact on creating calendar events.
- NOTE: We will followup with sorting the credentials with DWD credentials first in a followup PR(They are preferred because they don't expire)

## Impact on APIs - [ To Verify ]
- We don't support DWD through APIs yet. So, all existing APIs would continue to work with non-dwd credentials only.

## Performance Issues
- There could be 100s of users in an organization with already connected calendars. Enabling DWD adds a duplicate credential for each of them.
- Because a credential isn't aware of which email it is for(without connecting with Google Calendar API itself), we can't deduplicate them.

## Notes when testing locally
- You need to enable the feature through feature flag.
- You could use Acme org and login as owner1-acme@example.com
- Make sure to change the email of the user above to your workspace owner's email(other member's email might also work). This is necessary otherwise you won't be able to enable DWD for the organization.
- Note: After changing the email, you would have to logout and login again as required by NextAuth
7 changes: 5 additions & 2 deletions apps/web/components/apps/AppPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,14 @@ export const AppPage = ({
const searchParams = useCompatSearchParams();

const hasDescriptionItems = descriptionItems && descriptionItems.length > 0;
const utils = trpc.useUtils();

const mutation = useAddAppMutation(null, {
onSuccess: (data) => {
onSuccess: async (data) => {
if (data?.setupPending) return;
setIsLoading(false);
showToast(t("app_successfully_installed"), "success");
showToast(data?.message || t("app_successfully_installed"), "success");
await utils.viewer.appCredentialsByType.invalidate({ appType: type });
},
onError: (error) => {
if (error instanceof Error) showToast(error.message || t("app_could_not_be_installed"), "error");
Expand Down Expand Up @@ -161,6 +163,7 @@ export const AppPage = ({

// variant not other allows, an app to be shown in calendar category without requiring an actual calendar connection e.g. vimcal
// Such apps, can only be installed once.

const allowedMultipleInstalls = categories.indexOf("calendar") > -1 && variant !== "other";
useEffect(() => {
if (searchParams?.get("defaultInstall") === "true") {
Expand Down
Loading
Loading