Extract more utils

pull/11421/merge^2
Sean Brydon 2023-09-05 14:10:08 +01:00
parent 79e00728ee
commit b5918c0317
3 changed files with 179 additions and 119 deletions

View File

@ -1,6 +1,5 @@
import type { NextApiResponse } from "next";
import dayjs from "@calcom/dayjs";
import {
createUser,
findExistingUser,
@ -10,7 +9,16 @@ import {
sendVerificationEmail,
syncServicesCreateUser,
throwIfSignupIsDisabled,
createStripeCustomer,
} from "@calcom/feature-auth/lib/signup/signupUtils";
import {
checkIfTokenExistsAndValid,
acceptAllInvitesWithTeamId,
findTeam,
upsertUsersPasswordAndVerify,
joinOrgAndAcceptChildInivtes,
cleanUpInviteToken,
} from "@calcom/feature-auth/lib/signup/teamInviteUtils";
import { hashPassword } from "@calcom/features/auth/lib/hashPassword";
import { IS_CALCOM } from "@calcom/lib/constants";
import { getLocaleFromRequest } from "@calcom/lib/getLocaleFromRequest";
@ -18,8 +26,6 @@ import { HttpError } from "@calcom/lib/http-error";
import type { RequestWithUsernameStatus } from "@calcom/lib/server/username";
import { closeComUpsertTeamUser } from "@calcom/lib/sync/SyncServiceManager";
import { validateUsernameInTeam } from "@calcom/lib/validateUsername";
import { IdentityProvider } from "@calcom/prisma/enums";
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
export default async function handler(req: RequestWithUsernameStatus, res: NextApiResponse) {
try {
@ -28,9 +34,14 @@ export default async function handler(req: RequestWithUsernameStatus, res: NextA
const { email, password, language, token, username } = parseSignupData(req.body);
await findExistingUser(username, email);
const hashedPassword = await hashPassword(password);
const premiumUsernameMetadata = await handlePremiumUsernameFlow({
const customer = await createStripeCustomer({
email,
username,
});
const premiumUsernameMetadata = await handlePremiumUsernameFlow({
customer,
premiumUsernameStatusCode: req.usernameStatus.statusCode,
});
@ -48,56 +59,19 @@ export default async function handler(req: RequestWithUsernameStatus, res: NextA
username: username || "",
});
await syncServicesCreateUser(user);
}
{
const foundToken = await prisma.verificationToken.findFirst({
where: {
token,
},
select: {
id: true,
expires: true,
teamId: true,
},
});
if (!foundToken) {
return res.status(401).json({ message: "Invalid Token" });
}
if (dayjs(foundToken?.expires).isBefore(dayjs())) {
return res.status(401).json({ message: "Token expired" });
}
} else {
const foundToken = await checkIfTokenExistsAndValid(token);
if (foundToken?.teamId) {
const teamUserValidation = await validateUsernameInTeam(username, email, foundToken?.teamId);
if (!teamUserValidation.isValid) {
return res.status(409).json({ message: "Username or email is already taken" });
}
const team = await prisma.team.findUnique({
where: {
id: foundToken.teamId,
},
});
const team = await findTeam(foundToken.teamId);
if (team) {
const teamMetadata = teamMetadataSchema.parse(team?.metadata);
const user = await prisma.user.upsert({
where: { email },
update: {
username,
password: hashedPassword,
emailVerified: new Date(Date.now()),
identityProvider: IdentityProvider.CAL,
},
create: {
username,
email: email,
password: hashedPassword,
identityProvider: IdentityProvider.CAL,
},
});
const teamMetadata = team.metadata;
const user = await upsertUsersPasswordAndVerify(email, username, hashedPassword);
if (teamMetadata?.isOrganization) {
await prisma.user.update({
where: {
@ -108,64 +82,12 @@ export default async function handler(req: RequestWithUsernameStatus, res: NextA
},
});
}
const membership = await prisma.membership.update({
where: {
userId_teamId: { userId: user.id, teamId: team.id },
},
data: {
accepted: true,
},
});
const membership = await acceptAllInvitesWithTeamId(user.id, team.id);
closeComUpsertTeamUser(team, user, membership.role);
// Accept any child team invites for orgs.
if (team.parentId) {
// Join ORG
await prisma.user.update({
where: {
id: user.id,
},
data: {
organizationId: team.parentId,
},
});
/** We do a membership update twice so we can join the ORG invite if the user is invited to a team witin a ORG. */
await prisma.membership.updateMany({
where: {
userId: user.id,
team: {
id: team.parentId,
},
accepted: false,
},
data: {
accepted: true,
},
});
// Join any other invites
await prisma.membership.updateMany({
where: {
userId: user.id,
team: {
parentId: team.parentId,
},
accepted: false,
},
data: {
accepted: true,
},
});
await joinOrgAndAcceptChildInivtes(user.id, team.parentId);
}
// Cleanup token after use
await prisma.verificationToken.delete({
where: {
id: foundToken.id,
},
});
await cleanUpInviteToken(foundToken.id);
}
}
}
@ -184,9 +106,11 @@ export default async function handler(req: RequestWithUsernameStatus, res: NextA
return res.status(201).json({ message: "Created user" });
} catch (e) {
console.log(e);
if (e instanceof HttpError) {
return res.status(e.statusCode).json({ message: e.message });
}
return res.status(500).json({ message: "Internal server error" });
}

View File

@ -1,4 +1,4 @@
import { sendEmailVerification } from "auth/lib/verifyEmail";
import type Stripe from "stripe";
import stripe from "@calcom/app-store/stripepayment/lib/server";
import { getPremiumMonthlyPlanPriceId } from "@calcom/app-store/stripepayment/lib/utils";
@ -9,6 +9,8 @@ import slugify from "@calcom/lib/slugify";
import { createWebUser as syncServicesCreateWebUser } from "@calcom/lib/sync/SyncServiceManager";
import { signupSchema } from "@calcom/prisma/zod-utils";
import { sendEmailVerification } from "../verifyEmail";
export async function findExistingUser(username: string, email: string) {
const user = await prisma.user.findFirst({
where: {
@ -62,22 +64,7 @@ export function parseSignupData(data: unknown) {
username: slugify(parsedSchema.data.username),
};
}
export async function handlePremiumUsernameFlow({
email,
username,
premiumUsernameStatusCode,
}: {
email: string;
username: string;
premiumUsernameStatusCode: number;
}) {
if (!IS_CALCOM) return;
const metadata: {
stripeCustomerId?: string;
checkoutSessionId?: string;
} = {};
export async function createStripeCustomer({ email, username }: { email: string; username: string }) {
// Create the customer in Stripe
const customer = await stripe.customers.create({
email,
@ -87,6 +74,25 @@ export async function handlePremiumUsernameFlow({
},
});
return customer;
}
export async function handlePremiumUsernameFlow({
customer,
premiumUsernameStatusCode,
}: {
premiumUsernameStatusCode: number;
customer?: Stripe.Customer;
}) {
if (!IS_CALCOM) return;
if (!customer) {
throw new HttpError({
statusCode: 500,
message: "Missing customer",
});
}
const returnUrl = `${WEBAPP_URL}/api/integrations/stripepayment/paymentCallback?checkoutSessionId={CHECKOUT_SESSION_ID}&callbackUrl=/auth/verify?sessionId={CHECKOUT_SESSION_ID}`;
if (premiumUsernameStatusCode === 402) {
@ -150,7 +156,6 @@ export function sendVerificationEmail({
language: string;
username: string;
}) {
if (!IS_CALCOM) return;
return sendEmailVerification({
email,
language,

View File

@ -0,0 +1,131 @@
import dayjs from "@calcom/dayjs";
import { HttpError } from "@calcom/lib/http-error";
import { IdentityProvider } from "@calcom/prisma/enums";
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
export async function checkIfTokenExistsAndValid(token: string) {
const foundToken = await prisma.verificationToken.findFirst({
where: {
token,
},
select: {
id: true,
expires: true,
teamId: true,
},
});
if (!foundToken) {
throw new HttpError({
statusCode: 401,
message: "Invalid Token",
});
}
if (dayjs(foundToken?.expires).isBefore(dayjs())) {
throw new HttpError({
statusCode: 401,
message: "Token expired",
});
}
return foundToken;
}
export async function findTeam(teamId: number) {
const team = await prisma.team.findUnique({
where: {
id: teamId,
},
});
if (!team) {
throw new HttpError({
statusCode: 404,
message: "Team not found",
});
}
return {
...team,
metadata: teamMetadataSchema.parse(team.metadata),
};
}
export async function upsertUsersPasswordAndVerify(email: string, username: string, hashedPassword: string) {
return await prisma.user.upsert({
where: { email },
update: {
username,
password: hashedPassword,
emailVerified: new Date(Date.now()),
identityProvider: IdentityProvider.CAL,
},
create: {
username,
email: email,
password: hashedPassword,
identityProvider: IdentityProvider.CAL,
},
});
}
export async function acceptAllInvitesWithTeamId(userId: number, teamId: number) {
const membership = await prisma.membership.update({
where: {
userId_teamId: { userId: userId, teamId: teamId },
},
data: {
accepted: true,
},
});
return membership;
}
export async function joinOrgAndAcceptChildInivtes(userId: number, orgId: number) {
// Join ORG
await prisma.user.update({
where: {
id: userId,
},
data: {
organizationId: orgId,
},
});
/** We do a membership update twice so we can join the ORG invite if the user is invited to a team witin a ORG. */
await prisma.membership.updateMany({
where: {
userId: userId,
team: {
id: orgId,
},
accepted: false,
},
data: {
accepted: true,
},
});
// Join any other invites
await prisma.membership.updateMany({
where: {
userId: userId,
team: {
parentId: orgId,
},
accepted: false,
},
data: {
accepted: true,
},
});
}
export async function cleanUpInviteToken(tokenId: number) {
await prisma.verificationToken.delete({
where: {
id: tokenId,
},
});
}