refactor: platform billing controller auth guards (#25559)
* refactor: platform billing controller auth guards * fixup! refactor: platform billing controller auth guards
This commit is contained in:
@@ -2,6 +2,7 @@ import { BookingsRepository_2024_08_13 } from "@/ee/bookings/2024-08-13/reposito
|
||||
import { BillingProcessor } from "@/modules/billing/billing.processor";
|
||||
import { BillingRepository } from "@/modules/billing/billing.repository";
|
||||
import { BillingController } from "@/modules/billing/controllers/billing.controller";
|
||||
import { IsUserInBillingOrg } from "@/modules/billing/guards/is-user-in-billing-org";
|
||||
import { BillingServiceCachingProxy } from "@/modules/billing/services/billing-service-caching-proxy";
|
||||
import { BillingConfigService } from "@/modules/billing/services/billing.config.service";
|
||||
import { BillingService } from "@/modules/billing/services/billing.service";
|
||||
@@ -44,6 +45,7 @@ import { Module } from "@nestjs/common";
|
||||
ManagedOrganizationsBillingService,
|
||||
OAuthClientRepository,
|
||||
BookingsRepository_2024_08_13,
|
||||
IsUserInBillingOrg,
|
||||
],
|
||||
exports: [BillingService, BillingRepository, ManagedOrganizationsBillingService],
|
||||
controllers: [BillingController],
|
||||
|
||||
@@ -17,7 +17,7 @@ import { OrganizationRepositoryFixture } from "test/fixtures/repository/organiza
|
||||
import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withNextAuth } from "test/utils/withNextAuth";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import type { Team, PlatformBilling } from "@calcom/prisma/client";
|
||||
|
||||
@@ -32,8 +32,10 @@ describe("Platform Billing Controller (e2e)", () => {
|
||||
let membershipsRepositoryFixture: MembershipRepositoryFixture;
|
||||
let platformBillingRepositoryFixture: PlatformBillingRepositoryFixture;
|
||||
let organization: Team;
|
||||
let organization2: Team;
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await withNextAuth(
|
||||
const moduleRef = await withApiAuth(
|
||||
userEmail,
|
||||
Test.createTestingModule({
|
||||
imports: [AppModule, PrismaModule, UsersModule, TokensModule],
|
||||
@@ -49,6 +51,10 @@ describe("Platform Billing Controller (e2e)", () => {
|
||||
name: `billing-organization-${randomString()}`,
|
||||
isPlatform: true,
|
||||
});
|
||||
organization2 = await organizationsRepositoryFixture.create({
|
||||
name: `billing-organization-2-${randomString()}`,
|
||||
isPlatform: true,
|
||||
});
|
||||
|
||||
user = await userRepositoryFixture.create({
|
||||
email: userEmail,
|
||||
@@ -133,7 +139,7 @@ describe("Platform Billing Controller (e2e)", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("/billing/webhook (GET) should get billing plan for org", () => {
|
||||
it("/billing/:orgId/check (GET) should check billing plan for org", () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/billing/${organization.id}/check`)
|
||||
.expect(200)
|
||||
@@ -143,6 +149,10 @@ describe("Platform Billing Controller (e2e)", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("/billing/:organizationId/check (GET) should not be able to check other org plan", () => {
|
||||
return request(app.getHttpServer()).get(`/v2/billing/${organization2.id}/check`).expect(403);
|
||||
});
|
||||
|
||||
it("/billing/webhook (POST) failed payment should set billing free plan to overdue", () => {
|
||||
jest.spyOn(StripeService.prototype, "getStripe").mockImplementation(
|
||||
() =>
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { AppConfig } from "@/config/type";
|
||||
import { API_VERSIONS_VALUES } from "@/lib/api-versions";
|
||||
import { ApiAuthGuardOnlyAllow } from "@/modules/auth/decorators/api-auth-guard-only-allow.decorator";
|
||||
import { MembershipRoles } from "@/modules/auth/decorators/roles/membership-roles.decorator";
|
||||
import { NextAuthGuard } from "@/modules/auth/guards/next-auth/next-auth.guard";
|
||||
import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
|
||||
import { OrganizationRolesGuard } from "@/modules/auth/guards/organization-roles/organization-roles.guard";
|
||||
import { SubscribeToPlanInput } from "@/modules/billing/controllers/inputs/subscribe-to-plan.input";
|
||||
import { CheckPlatformBillingResponseDto } from "@/modules/billing/controllers/outputs/CheckPlatformBillingResponse.dto";
|
||||
import { SubscribeTeamToBillingResponseDto } from "@/modules/billing/controllers/outputs/SubscribeTeamToBillingResponse.dto";
|
||||
import { IsUserInBillingOrg } from "@/modules/billing/guards/is-user-in-billing-org";
|
||||
import { IBillingService } from "@/modules/billing/interfaces/billing-service.interface";
|
||||
import { StripeService } from "@/modules/stripe/stripe.service";
|
||||
import {
|
||||
@@ -49,8 +51,9 @@ export class BillingController {
|
||||
}
|
||||
|
||||
@Get("/:teamId/check")
|
||||
@UseGuards(NextAuthGuard, OrganizationRolesGuard)
|
||||
@UseGuards(ApiAuthGuard, OrganizationRolesGuard, IsUserInBillingOrg)
|
||||
@MembershipRoles(["OWNER", "ADMIN", "MEMBER"])
|
||||
@ApiAuthGuardOnlyAllow(["NEXT_AUTH"])
|
||||
async checkTeamBilling(
|
||||
@Param("teamId", ParseIntPipe) teamId: number
|
||||
): Promise<ApiResponse<CheckPlatformBillingResponseDto>> {
|
||||
@@ -66,8 +69,9 @@ export class BillingController {
|
||||
}
|
||||
|
||||
@Post("/:teamId/subscribe")
|
||||
@UseGuards(NextAuthGuard, OrganizationRolesGuard)
|
||||
@UseGuards(ApiAuthGuard, OrganizationRolesGuard, IsUserInBillingOrg)
|
||||
@MembershipRoles(["OWNER", "ADMIN"])
|
||||
@ApiAuthGuardOnlyAllow(["NEXT_AUTH"])
|
||||
async subscribeTeamToStripe(
|
||||
@Param("teamId") teamId: number,
|
||||
@Body() input: SubscribeToPlanInput
|
||||
@@ -84,8 +88,9 @@ export class BillingController {
|
||||
}
|
||||
|
||||
@Post("/:teamId/upgrade")
|
||||
@UseGuards(NextAuthGuard, OrganizationRolesGuard)
|
||||
@UseGuards(ApiAuthGuard, OrganizationRolesGuard, IsUserInBillingOrg)
|
||||
@MembershipRoles(["OWNER", "ADMIN"])
|
||||
@ApiAuthGuardOnlyAllow(["NEXT_AUTH"])
|
||||
async upgradeTeamBillingInStripe(
|
||||
@Param("teamId") teamId: number,
|
||||
@Body() input: SubscribeToPlanInput
|
||||
@@ -100,13 +105,12 @@ export class BillingController {
|
||||
};
|
||||
}
|
||||
|
||||
@Delete("/:organizationId/unsubscribe")
|
||||
@UseGuards(NextAuthGuard, OrganizationRolesGuard)
|
||||
@Delete("/:teamId/unsubscribe")
|
||||
@UseGuards(ApiAuthGuard, OrganizationRolesGuard, IsUserInBillingOrg)
|
||||
@MembershipRoles(["OWNER", "ADMIN"])
|
||||
async cancelTeamSubscriptionInStripe(
|
||||
@Param("organizationId") organizationId: number
|
||||
): Promise<ApiResponse> {
|
||||
await this.billingService.cancelTeamSubscription(organizationId);
|
||||
@ApiAuthGuardOnlyAllow(["NEXT_AUTH"])
|
||||
async cancelTeamSubscriptionInStripe(@Param("teamId") teamId: number): Promise<ApiResponse> {
|
||||
await this.billingService.cancelTeamSubscription(teamId);
|
||||
|
||||
return {
|
||||
status: "success",
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { ApiAuthGuardUser } from "@/modules/auth/strategies/api-auth/api-auth.strategy";
|
||||
import { OrganizationsRepository } from "@/modules/organizations/index/organizations.repository";
|
||||
import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from "@nestjs/common";
|
||||
import { Request } from "express";
|
||||
|
||||
import type { Team } from "@calcom/prisma/client";
|
||||
|
||||
@Injectable()
|
||||
export class IsUserInBillingOrg implements CanActivate {
|
||||
constructor(private organizationsRepository: OrganizationsRepository) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const request = context.switchToHttp().getRequest<Request & { team: Team }>();
|
||||
const orgId: string = request.params.teamId;
|
||||
const userId = (request.user as ApiAuthGuardUser).id;
|
||||
|
||||
if (!userId) {
|
||||
throw new ForbiddenException("IsUserInBillingOrg - No user id found.");
|
||||
}
|
||||
|
||||
if (!orgId) {
|
||||
throw new ForbiddenException("IsUserInBillingOrg - No org id found in request params.");
|
||||
}
|
||||
|
||||
const user = await this.organizationsRepository.findOrgUser(Number(orgId), Number(userId));
|
||||
|
||||
if (!user) {
|
||||
throw new ForbiddenException(
|
||||
`IsUserInBillingOrg - user with id=${userId} is not part of the organization with id=${orgId}.`
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user