Skip to content

Commit

Permalink
Merge branch 'main' into app-install-flow-followup
Browse files Browse the repository at this point in the history
  • Loading branch information
SomayChauhan committed Jun 21, 2024
2 parents 216a9fc + c958b60 commit 621b82a
Show file tree
Hide file tree
Showing 138 changed files with 3,038 additions and 1,361 deletions.
5 changes: 0 additions & 5 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,6 @@
# instructions. This environment variable is deprecated although still supported for backward compatibility.
# @see https://console.cal.com
CALCOM_LICENSE_KEY=
# Signature token for the Cal.com License API (used for self-hosted integrations)
# We will give you a token when we provide you with a license key this ensure you and only you can communicate with the Cal.com License API for your license key
CAL_SIGNATURE_TOKEN=
# The route to the Cal.com License API
CALCOM_PRIVATE_API_ROUTE=
# ***********************************************************************************************************

# - DATABASE ************************************************************************************************
Expand Down
9 changes: 0 additions & 9 deletions .github/workflows/semantic-pull-requests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,3 @@ jobs:
```
${{ steps.lint_pr_title.outputs.error_message }}
```
# Delete a previous comment when the issue has been resolved
- if: ${{ steps.lint_pr_title.outputs.error_message == null }}
uses: marocchino/sticky-pull-request-comment@v2
with:
header: pr-title-lint-error
message: |
Thank you for following the naming conventions! 🙏 Feel free to join our [discord](https://go.cal.com/discord) and post your PR link.
17 changes: 17 additions & 0 deletions apps/api/v1/instrumentation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import * as Sentry from "@sentry/nextjs";

export function register() {
if (process.env.NEXT_RUNTIME === "nodejs") {
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
// reduce sample rate to 10% on production
tracesSampleRate: process.env.NODE_ENV !== "production" ? 1.0 : 0.1,
});
}

if (process.env.NEXT_RUNTIME === "edge") {
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
});
}
}
90 changes: 90 additions & 0 deletions apps/api/v1/lib/helpers/rateLimitApiKey.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import type { Request, Response } from "express";
import type { NextApiResponse, NextApiRequest } from "next";
import { createMocks } from "node-mocks-http";
import { describe, it, expect, vi } from "vitest";

import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";

import { rateLimitApiKey } from "~/lib/helpers/rateLimitApiKey";

type CustomNextApiRequest = NextApiRequest & Request;
type CustomNextApiResponse = NextApiResponse & Response;

vi.mock("@calcom/lib/checkRateLimitAndThrowError", () => ({
checkRateLimitAndThrowError: vi.fn(),
}));

describe("rateLimitApiKey middleware", () => {
it("should return 401 if no apiKey is provided", async () => {
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
method: "GET",
query: {},
});

await rateLimitApiKey(req, res, vi.fn() as any);

expect(res._getStatusCode()).toBe(401);
expect(res._getJSONData()).toEqual({ message: "No apiKey provided" });
});

it("should call checkRateLimitAndThrowError with correct parameters", async () => {
const { req, res } = createMocks({
method: "GET",
query: { apiKey: "test-key" },
});

(checkRateLimitAndThrowError as any).mockResolvedValueOnce({
limit: 100,
remaining: 99,
reset: Date.now(),
});

// @ts-expect-error weird typing between middleware and createMocks
await rateLimitApiKey(req, res, vi.fn() as any);

expect(checkRateLimitAndThrowError).toHaveBeenCalledWith({
identifier: "test-key",
rateLimitingType: "api",
onRateLimiterResponse: expect.any(Function),
});
});

it("should set rate limit headers correctly", async () => {
const { req, res } = createMocks({
method: "GET",
query: { apiKey: "test-key" },
});

const rateLimiterResponse = {
limit: 100,
remaining: 99,
reset: Date.now(),
};

(checkRateLimitAndThrowError as any).mockImplementationOnce(({ onRateLimiterResponse }) => {
onRateLimiterResponse(rateLimiterResponse);
});

// @ts-expect-error weird typing between middleware and createMocks
await rateLimitApiKey(req, res, vi.fn() as any);

expect(res.getHeader("X-RateLimit-Limit")).toBe(rateLimiterResponse.limit);
expect(res.getHeader("X-RateLimit-Remaining")).toBe(rateLimiterResponse.remaining);
expect(res.getHeader("X-RateLimit-Reset")).toBe(rateLimiterResponse.reset);
});

it("should return 429 if rate limit is exceeded", async () => {
const { req, res } = createMocks({
method: "GET",
query: { apiKey: "test-key" },
});

(checkRateLimitAndThrowError as any).mockRejectedValue(new Error("Rate limit exceeded"));

// @ts-expect-error weird typing between middleware and createMocks
await rateLimitApiKey(req, res, vi.fn() as any);

expect(res._getStatusCode()).toBe(429);
expect(res._getJSONData()).toEqual({ message: "Rate limit exceeded" });
});
});
22 changes: 13 additions & 9 deletions apps/api/v1/lib/helpers/rateLimitApiKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,19 @@ export const rateLimitApiKey: NextMiddleware = async (req, res, next) => {
if (!req.query.apiKey) return res.status(401).json({ message: "No apiKey provided" });

// TODO: Add a way to add trusted api keys
await checkRateLimitAndThrowError({
identifier: req.query.apiKey as string,
rateLimitingType: "api",
onRateLimiterResponse: (response) => {
res.setHeader("X-RateLimit-Limit", response.limit);
res.setHeader("X-RateLimit-Remaining", response.remaining);
res.setHeader("X-RateLimit-Reset", response.reset);
},
});
try {
await checkRateLimitAndThrowError({
identifier: req.query.apiKey as string,
rateLimitingType: "api",
onRateLimiterResponse: (response) => {
res.setHeader("X-RateLimit-Limit", response.limit);
res.setHeader("X-RateLimit-Remaining", response.remaining);
res.setHeader("X-RateLimit-Reset", response.reset);
},
});
} catch (error) {
res.status(429).json({ message: "Rate limit exceeded" });
}

await next();
};
17 changes: 11 additions & 6 deletions apps/api/v1/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ const { withAxiom } = require("next-axiom");
const { withSentryConfig } = require("@sentry/nextjs");

const plugins = [withAxiom];

/** @type {import("next").NextConfig} */
const nextConfig = {
experimental: {
instrumentationHook: true,
},
transpilePackages: [
"@calcom/app-store",
"@calcom/core",
Expand Down Expand Up @@ -87,12 +92,12 @@ const nextConfig = {
};

if (!!process.env.NEXT_PUBLIC_SENTRY_DSN) {
nextConfig["sentry"] = {
autoInstrumentServerFunctions: true,
hideSourceMaps: true,
};

plugins.push(withSentryConfig);
plugins.push((nextConfig) =>
withSentryConfig(nextConfig, {
autoInstrumentServerFunctions: true,
hideSourceMaps: true,
})
);
}

module.exports = () => plugins.reduce((acc, next) => next(acc), nextConfig);
2 changes: 1 addition & 1 deletion apps/api/v1/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"@calcom/lib": "*",
"@calcom/prisma": "*",
"@calcom/trpc": "*",
"@sentry/nextjs": "^7.73.0",
"@sentry/nextjs": "^8.8.0",
"bcryptjs": "^2.4.3",
"memory-cache": "^0.2.0",
"next": "^13.5.4",
Expand Down
5 changes: 0 additions & 5 deletions apps/api/v1/sentry.edge.config.ts

This file was deleted.

6 changes: 0 additions & 6 deletions apps/api/v1/sentry.server.config.ts

This file was deleted.

4 changes: 4 additions & 0 deletions apps/api/v2/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,7 @@ STRIPE_API_KEY=
STRIPE_WEBHOOK_SECRET=

WEB_APP_URL=http://localhost:3000/
CALCOM_LICENSE_KEY=
API_KEY_PREFIX=cal_
GET_LICENSE_KEY_URL="https://console.cal.com/api/license"
IS_E2E=false
1 change: 1 addition & 0 deletions apps/api/v2/.prettierrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ const rootConfig = require("../../../packages/config/prettier-preset");

module.exports = {
...rootConfig,
importOrder: ["^./instrument", ...rootConfig.importOrder],
importOrderParserPlugins: ["typescript", "decorators-legacy"],
};
10 changes: 10 additions & 0 deletions apps/api/v2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@ $ yarn prisma generate

Copy `.env.example` to `.env` and fill values.

## Add license Key to deployments table in DB

id, logo theme licenseKey agreedLicenseAt
1, null, null, 'c4234812-12ab-42s6-a1e3-55bedd4a5bb7', '2023-05-15 21:39:47.611'

your CALCOM_LICENSE_KEY env var need to contain the same value

.env
CALCOM_LICENSE_KEY=c4234812-12ab-42s6-a1e3-55bedd4a5bb

## Running the app

```bash
Expand Down
3 changes: 1 addition & 2 deletions apps/api/v2/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,7 @@
"@nestjs/platform-express": "^10.0.0",
"@nestjs/swagger": "^7.3.0",
"@nestjs/throttler": "^5.1.2",
"@sentry/node": "^7.86.0",
"@sentry/tracing": "^7.86.0",
"@sentry/node": "^8.8.0",
"body-parser": "^1.20.2",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
Expand Down
18 changes: 6 additions & 12 deletions apps/api/v2/src/app.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { getEnv } from "@/env";
import "./instrument";

import { HttpExceptionFilter } from "@/filters/http-exception.filter";
import { PrismaExceptionFilter } from "@/filters/prisma-exception.filter";
import { SentryFilter } from "@/filters/sentry-exception.filter";
import { ZodExceptionFilter } from "@/filters/zod-exception.filter";
import type { ValidationError } from "@nestjs/common";
import { BadRequestException, ValidationPipe, VersioningType } from "@nestjs/common";
import { HttpAdapterHost } from "@nestjs/core";
import { BaseExceptionFilter, HttpAdapterHost } from "@nestjs/core";
import type { NestExpressApplication } from "@nestjs/platform-express";
import * as Sentry from "@sentry/node";
import * as cookieParser from "cookie-parser";
Expand All @@ -30,9 +30,7 @@ export const bootstrap = (app: NestExpressApplication): NestExpressApplication =
type: VersioningType.CUSTOM,
extractor: (request: unknown) => {
const headerVersion = (request as Request)?.headers[CAL_API_VERSION_HEADER] as string | undefined;
console.log("asap header headerVersion", headerVersion);
if (headerVersion && API_VERSIONS.includes(headerVersion as API_VERSIONS_ENUM)) {
console.log("asap return header headerVersion", headerVersion);
return headerVersion;
}
return VERSION_2024_04_15;
Expand Down Expand Up @@ -71,15 +69,11 @@ export const bootstrap = (app: NestExpressApplication): NestExpressApplication =
})
);

if (process.env.SENTRY_DSN) {
Sentry.init({
dsn: getEnv("SENTRY_DSN"),
});
}

// Exception filters, new filters go at the bottom, keep the order
const { httpAdapter } = app.get(HttpAdapterHost);
app.useGlobalFilters(new SentryFilter(httpAdapter));
if (process.env.SENTRY_DSN) {
Sentry.setupNestErrorHandler(app, new BaseExceptionFilter(httpAdapter));
}
app.useGlobalFilters(new PrismaExceptionFilter());
app.useGlobalFilters(new ZodExceptionFilter());
app.useGlobalFilters(new HttpExceptionFilter());
Expand Down
4 changes: 4 additions & 0 deletions apps/api/v2/src/config/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ const loadConfig = (): AppConfig => {
? `:${Number(getEnv("API_PORT", "5555"))}`
: ""
}/v2`,
keyPrefix: getEnv("API_KEY_PREFIX", "cal_"),
licenseKey: getEnv("CALCOM_LICENSE_KEY", ""),
licenceKeyUrl: getEnv("GET_LICENSE_KEY_URL", "https://console.cal.com/api/license"),
},
db: {
readUrl: getEnv("DATABASE_READ_URL"),
Expand All @@ -31,6 +34,7 @@ const loadConfig = (): AppConfig => {
app: {
baseUrl: getEnv("WEB_APP_URL", "https://app.cal.com"),
},
e2e: getEnv("IS_E2E", false),
};
};

Expand Down
4 changes: 4 additions & 0 deletions apps/api/v2/src/config/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ export type AppConfig = {
port: number;
path: string;
url: string;
keyPrefix: string;
licenseKey: string;
licenceKeyUrl: string;
};
db: {
readUrl: string;
Expand All @@ -22,4 +25,5 @@ export type AppConfig = {
app: {
baseUrl: string;
};
e2e: boolean;
};
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import * as request from "supertest";
import { BookingsRepositoryFixture } from "test/fixtures/repository/bookings.repository.fixture";
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
import { withAccessTokenAuth } from "test/utils/withAccessTokenAuth";
import { withApiAuth } from "test/utils/withApiAuth";

import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants";
import { handleNewBooking } from "@calcom/platform-libraries-0.0.2";
Expand All @@ -39,7 +39,7 @@ describe("Bookings Endpoints", () => {
let createdBooking: Awaited<ReturnType<typeof handleNewBooking>>;

beforeAll(async () => {
const moduleRef = await withAccessTokenAuth(
const moduleRef = await withApiAuth(
userEmail,
Test.createTestingModule({
imports: [AppModule, PrismaModule, UsersModule, SchedulesModule_2024_04_15],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { GetBookingsOutput } from "@/ee/bookings/outputs/get-bookings.output";
import { API_VERSIONS_VALUES } from "@/lib/api-versions";
import { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator";
import { Permissions } from "@/modules/auth/decorators/permissions/permissions.decorator";
import { AccessTokenGuard } from "@/modules/auth/guards/access-token/access-token.guard";
import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
import { BillingService } from "@/modules/billing/services/billing.service";
import { OAuthClientRepository } from "@/modules/oauth-clients/oauth-client.repository";
Expand Down Expand Up @@ -89,7 +89,7 @@ export class BookingsController {
) {}

@Get("/")
@UseGuards(AccessTokenGuard)
@UseGuards(ApiAuthGuard)
@Permissions([BOOKING_READ])
@ApiQuery({ name: "filters[status]", enum: Status, required: true })
@ApiQuery({ name: "limit", type: "number", required: false })
Expand Down
Loading

0 comments on commit 621b82a

Please sign in to comment.