Last active
April 13, 2024 17:19
-
-
Save p6l-richard/6c2808ec0a16b0a3a736ce51cf76bb82 to your computer and use it in GitHub Desktop.
How to create a user on Cal.com with recovery flow
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Create a new user | |
/** [@calcom] 1. Create the user in cal */ | |
let calUser: Omit<CalManageUserResponse, "status">["data"] | null = | |
null; | |
const url = `${env.NEXT_PUBLIC_CAL_API_URL}/oauth-clients/${env.NEXT_PUBLIC_CAL_OAUTH_CLIENT_ID}/users`; | |
const response = await fetch(url, { | |
method: "POST", | |
headers: { | |
"Content-Type": "application/json", | |
"x-cal-secret-key": env.CAL_SECRET, | |
origin: | |
env.NODE_ENV === "development" | |
? "http://localhost:3000" | |
: // TODO: Replace this after deployment | |
"https://platform.cal.com", | |
}, | |
body: JSON.stringify({ | |
email: credentials.data.email, | |
name: signupData.data.name, | |
}), | |
}); | |
if (response.ok) { | |
const json = (await response.json()) as Omit< | |
CalManageUserResponse, | |
"status" | |
>; | |
calUser = json.data; | |
} else { | |
const text = await response.text(); | |
if (!text.includes("already exists")) { | |
throw new Error( | |
`Unable to create user '${credentials.data.email}': Invalid response from Cal after POSTing to ${url} | |
Response text: | |
${await response.text()} | |
`, | |
); | |
} | |
// [@calcom] This means that the user already exists on cal's end but we didn't have them in our db | |
// We can just look them up by email and create the user in our db: | |
// let's fetch all users and get it from there. | |
const res = await fetch(url, { | |
headers: { | |
"Content-Type": "application/json", | |
"x-cal-secret-key": env.CAL_SECRET, | |
origin: | |
env.NODE_ENV === "development" | |
? "http://localhost:3000" | |
: // TODO: Replace this after deployment | |
"https://platform.cal.com", | |
}, | |
}); | |
if (!res.ok) { | |
throw new Error( | |
`Unable to create user '${credentials.data.email}': Invalid response from Cal after GETting: ${url} | |
ℹ️ This means the user already exists in cal, but we can't fetch it to get the id. | |
Response text: | |
${await res.text()} | |
`, | |
); | |
} | |
const calUsers = (await res.json()) as Omit< | |
CalManageUserResponse, | |
"data" | |
> & { data: Array<CalManageUserResponse["data"]["user"]> }; | |
const fromCal = calUsers.data.find((calUser) => { | |
// [@calcom] the cal email adds `+<clientId>` before the @ in the email, so let's do the same four our matching: | |
const emailAsCal = credentials.data.email.replace( | |
"@", | |
`+${env.NEXT_PUBLIC_CAL_OAUTH_CLIENT_ID}@`, | |
); | |
return calUser.email === emailAsCal; | |
}); | |
if (!fromCal) { | |
throw new Error( | |
`Unable to create user '${credentials.data.email}': User not found in Cal | |
ℹ️ This means the user already exists in cal, but we couldn't reconcile it from the response. Here are the emails: | |
${calUsers.data.map((u) => u.email).join(", ")} | |
`, | |
); | |
} | |
// [@calcom] OK, we reconciled the user. Let's force-refresh their tokens so that we can store everything in our db | |
const forceRefreshUrl = `${env.NEXT_PUBLIC_CAL_API_URL}/oauth-clients/${env.NEXT_PUBLIC_CAL_OAUTH_CLIENT_ID}/users/${fromCal.id}/force-refresh`; | |
const forceRefreshResponse = await fetch(forceRefreshUrl, { | |
method: "POST", | |
headers: { | |
"Content-Type": "application/json", | |
"x-cal-secret-key": env.CAL_SECRET, | |
origin: | |
env.NODE_ENV === "development" | |
? "http://localhost:3000" | |
: // TODO: Replace this after deployment | |
"https://platform.cal.com", | |
}, | |
}); | |
if (!forceRefreshResponse.ok) { | |
throw new Error( | |
`Unable to create user '${credentials.data.email}': Invalid response from Cal after attempting to force-refresh tokens for cal user with id '${fromCal.id}' | |
Endpoint URL: ${forceRefreshUrl} | |
Response text: | |
${await forceRefreshResponse.text()} | |
`, | |
); | |
} | |
const { | |
data: { accessToken, refreshToken }, | |
} = (await forceRefreshResponse.json()) as { | |
status: string; | |
data: { accessToken: string; refreshToken: string }; | |
}; | |
// [@calcom] ✅ Now, we have successfully recovered our users tokens. Let's allocate this to our `calUser` | |
calUser = { user: fromCal, accessToken, refreshToken }; | |
} | |
const { accessToken, refreshToken, user: toCreate } = calUser; | |
/** [@calcom] 2. Create the user in our db with cal's tokens */ | |
user = await db.user.create({ | |
data: { | |
username: signupData.data.username, | |
name: signupData.data.name, | |
hashedPassword: await hash(credentials.data.password), | |
email: credentials.data.email, | |
/** [@calcom] 👇 These are the tokens necessary to make cal operations on behalf of the user */ | |
calAccount: { | |
create: { ...toCreate, accessToken, refreshToken }, | |
}, | |
/** [@calcom] 👆 */ | |
}, | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment