From e2e397fc6feec2d9c1f202cdf8da983500a6bf45 Mon Sep 17 00:00:00 2001 From: Joe Date: Thu, 6 Feb 2025 15:25:40 -0500 Subject: [PATCH 1/7] Add `SELF_SERVE_ADMIN` to `CreationSource` --- packages/prisma/schema.prisma | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma index e69f513399920b..38d02202f29d07 100644 --- a/packages/prisma/schema.prisma +++ b/packages/prisma/schema.prisma @@ -43,9 +43,10 @@ enum PeriodType { } enum CreationSource { - API_V1 @map("api_v1") - API_V2 @map("api_v2") - WEBAPP @map("webapp") + API_V1 @map("api_v1") + API_V2 @map("api_v2") + WEBAPP @map("webapp") + SELF_SERVE_ADMIN @map("self_serve_admin") } model Host { From 01d1799e5967d7bea34c27202c89d1548c5a1b38 Mon Sep 17 00:00:00 2001 From: Joe Date: Sat, 8 Feb 2025 22:01:51 -0500 Subject: [PATCH 2/7] Add migration to add `SELF_SERVE_ADMIN` to `CreationSource` --- .../migration.sql | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 packages/prisma/migrations/20250209030127_add_self_serve_admin_to_creation_source/migration.sql diff --git a/packages/prisma/migrations/20250209030127_add_self_serve_admin_to_creation_source/migration.sql b/packages/prisma/migrations/20250209030127_add_self_serve_admin_to_creation_source/migration.sql new file mode 100644 index 00000000000000..e495cd8516612d --- /dev/null +++ b/packages/prisma/migrations/20250209030127_add_self_serve_admin_to_creation_source/migration.sql @@ -0,0 +1,2 @@ +-- AlterEnum +ALTER TYPE "CreationSource" ADD VALUE 'self_serve_admin'; From 6ed93e4123e3253a4b58a96cc38640186cdd9f43 Mon Sep 17 00:00:00 2001 From: Joe Date: Sat, 8 Feb 2025 22:02:21 -0500 Subject: [PATCH 3/7] Use `SELF_SERVE_ADMIN` in setup endpoint --- apps/web/pages/api/auth/setup.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/pages/api/auth/setup.ts b/apps/web/pages/api/auth/setup.ts index f6a6222ed5b532..673da337034b14 100644 --- a/apps/web/pages/api/auth/setup.ts +++ b/apps/web/pages/api/auth/setup.ts @@ -49,7 +49,7 @@ async function handler(req: NextApiRequest) { emailVerified: new Date(), locale: "en", // TODO: We should revisit this identityProvider: IdentityProvider.CAL, - creationSource: CreationSource.WEBAPP, + creationSource: CreationSource.SELF_SERVE_ADMIN, }, }); From d1ad56bfe6b1f58f18408b6b7ecc9f2cbce8ce02 Mon Sep 17 00:00:00 2001 From: Joe Date: Thu, 6 Feb 2025 15:26:38 -0500 Subject: [PATCH 4/7] Self serve setup use `UserCreationService` --- apps/web/pages/api/auth/setup.ts | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/apps/web/pages/api/auth/setup.ts b/apps/web/pages/api/auth/setup.ts index 673da337034b14..ab4241a9f1c9bc 100644 --- a/apps/web/pages/api/auth/setup.ts +++ b/apps/web/pages/api/auth/setup.ts @@ -1,12 +1,11 @@ import type { NextApiRequest } from "next"; import z from "zod"; -import { hashPassword } from "@calcom/features/auth/lib/hashPassword"; import { isPasswordValid } from "@calcom/features/auth/lib/isPasswordValid"; import { emailRegex } from "@calcom/lib/emailSchema"; import { HttpError } from "@calcom/lib/http-error"; import { defaultHandler, defaultResponder } from "@calcom/lib/server"; -import slugify from "@calcom/lib/slugify"; +import { UserCreationService } from "@calcom/lib/server/service/userCreationService"; import prisma from "@calcom/prisma"; import { IdentityProvider } from "@calcom/prisma/enums"; import { CreationSource } from "@calcom/prisma/enums"; @@ -34,23 +33,18 @@ async function handler(req: NextApiRequest) { throw new HttpError({ statusCode: 422, message: parsedQuery.error.message }); } - const username = slugify(parsedQuery.data.username.trim()); const userEmail = parsedQuery.data.email_address.toLowerCase(); - const hashedPassword = await hashPassword(parsedQuery.data.password); - - await prisma.user.create({ - data: { - username, - email: userEmail, - password: { create: { hash: hashedPassword } }, - role: "ADMIN", - name: parsedQuery.data.full_name, - emailVerified: new Date(), - locale: "en", // TODO: We should revisit this - identityProvider: IdentityProvider.CAL, - creationSource: CreationSource.SELF_SERVE_ADMIN, - }, + await UserCreationService.createUser({ + username: parsedQuery.data.username.trim(), + email: userEmail, + password: parsedQuery.data.password, + role: "ADMIN", + name: parsedQuery.data.full_name, + emailVerified: new Date(), + locale: "en", // TODO: We should revisit this + identityProvider: IdentityProvider.CAL, + creationSource: CreationSource.SELF_SERVE_ADMIN, }); return { message: "First admin user created successfully." }; From 4c70911dfbbf9e533bfd6863372889dcf9ca092d Mon Sep 17 00:00:00 2001 From: Joe Date: Sat, 8 Feb 2025 22:07:48 -0500 Subject: [PATCH 5/7] Type fix --- apps/web/pages/api/auth/setup.ts | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/apps/web/pages/api/auth/setup.ts b/apps/web/pages/api/auth/setup.ts index ab4241a9f1c9bc..c3223cb7a3ba13 100644 --- a/apps/web/pages/api/auth/setup.ts +++ b/apps/web/pages/api/auth/setup.ts @@ -36,15 +36,17 @@ async function handler(req: NextApiRequest) { const userEmail = parsedQuery.data.email_address.toLowerCase(); await UserCreationService.createUser({ - username: parsedQuery.data.username.trim(), - email: userEmail, - password: parsedQuery.data.password, - role: "ADMIN", - name: parsedQuery.data.full_name, - emailVerified: new Date(), - locale: "en", // TODO: We should revisit this - identityProvider: IdentityProvider.CAL, - creationSource: CreationSource.SELF_SERVE_ADMIN, + data: { + username: parsedQuery.data.username.trim(), + email: userEmail, + password: parsedQuery.data.password, + role: "ADMIN", + name: parsedQuery.data.full_name, + emailVerified: new Date(), + locale: "en", // TODO: We should revisit this + identityProvider: IdentityProvider.CAL, + creationSource: CreationSource.SELF_SERVE_ADMIN, + }, }); return { message: "First admin user created successfully." }; From 04657103302c13a739cafd8390203fa486439d28 Mon Sep 17 00:00:00 2001 From: Joe Au-Yeung Date: Sun, 9 Feb 2025 17:09:59 -0500 Subject: [PATCH 6/7] Add tests --- apps/web/pages/api/auth/setup.test.ts | 70 +++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 apps/web/pages/api/auth/setup.test.ts diff --git a/apps/web/pages/api/auth/setup.test.ts b/apps/web/pages/api/auth/setup.test.ts new file mode 100644 index 00000000000000..bd9edeb1bcf13d --- /dev/null +++ b/apps/web/pages/api/auth/setup.test.ts @@ -0,0 +1,70 @@ +import prismock from "../../../../../tests/libs/__mocks__/prisma"; + +import type { Request, Response } from "express"; +import type { NextApiRequest, NextApiResponse } from "next"; +import { createMocks } from "node-mocks-http"; +import { describe, test, expect, vi } from "vitest"; + +import handler from "./setup"; + +type CustomNextApiRequest = NextApiRequest & Request; +type CustomNextApiResponse = NextApiResponse & Response; + +vi.mock("@calcom/lib/server/i18n", () => { + return { + getTranslation: (key: string) => { + return () => key; + }, + }; +}); + +describe("setup", () => { + test("should return a 400 error if users already exists", async () => { + const { req, res } = createMocks({ + method: "POST", + body: { + email: "test@example.com", + username: "test", + }, + }); + + await prismock.user.create({ + data: { + email: "test@example.com", + }, + }); + + await handler(req, res); + + expect(res.statusCode).toBe(400); + }); + + test("should return 422 if request body is invalid", async () => { + const { req, res } = createMocks({ + method: "POST", + body: { + email: "test@example.com", + }, + }); + + await handler(req, res); + + expect(res.statusCode).toBe(422); + }); + + test("should create a new admin user", async () => { + const { req, res } = createMocks({ + method: "POST", + body: { + username: "test", + full_name: "Test User", + email_address: "test@example.com", + password: "ADMINtestingpassword123!", + }, + }); + + await handler(req, res); + + expect(res.statusCode).toBe(200); + }); +}); From 53e4bcee2b817d8ff927f27bf5bf3747c855615b Mon Sep 17 00:00:00 2001 From: Joe Au-Yeung Date: Sun, 9 Feb 2025 17:13:40 -0500 Subject: [PATCH 7/7] Make test more robust --- apps/web/pages/api/auth/setup.test.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/apps/web/pages/api/auth/setup.test.ts b/apps/web/pages/api/auth/setup.test.ts index bd9edeb1bcf13d..bd0a1ad43391fd 100644 --- a/apps/web/pages/api/auth/setup.test.ts +++ b/apps/web/pages/api/auth/setup.test.ts @@ -5,6 +5,8 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { createMocks } from "node-mocks-http"; import { describe, test, expect, vi } from "vitest"; +import { UserPermissionRole, CreationSource, IdentityProvider } from "@calcom/prisma/enums"; + import handler from "./setup"; type CustomNextApiRequest = NextApiRequest & Request; @@ -66,5 +68,23 @@ describe("setup", () => { await handler(req, res); expect(res.statusCode).toBe(200); + + const user = await prismock.user.findFirst({ + where: { + email: "test@example.com", + }, + }); + + expect(user).toEqual( + expect.objectContaining({ + email: "test@example.com", + username: "test", + locked: false, + organizationId: null, + identityProvider: IdentityProvider.CAL, + role: UserPermissionRole.ADMIN, + creationSource: CreationSource.SELF_SERVE_ADMIN, + }) + ); }); });