fix: Add new route to create team routing-form response again(after revert earlier) (#22407)
* Revert "revert: "fix: Add new route to create team routing-form response (#22347)" (#22399)"
This reverts commit e6c36af3b4.
* Update the documentation
* Remove versioning
This commit is contained in:
@@ -0,0 +1 @@
|
||||
export { Or } from "./or.guard";
|
||||
@@ -0,0 +1,104 @@
|
||||
import { ExecutionContext, CanActivate } from "@nestjs/common";
|
||||
|
||||
import { Or } from "./or.guard";
|
||||
|
||||
// Mock guards for testing
|
||||
class MockGuard1 implements CanActivate {
|
||||
constructor(private shouldPass: boolean) {}
|
||||
|
||||
async canActivate(): Promise<boolean> {
|
||||
return this.shouldPass;
|
||||
}
|
||||
}
|
||||
|
||||
class MockGuard2 implements CanActivate {
|
||||
constructor(private shouldPass: boolean) {}
|
||||
|
||||
async canActivate(): Promise<boolean> {
|
||||
return this.shouldPass;
|
||||
}
|
||||
}
|
||||
|
||||
class MockGuard3 implements CanActivate {
|
||||
constructor(private shouldThrow: boolean = false) {}
|
||||
|
||||
async canActivate(): Promise<boolean> {
|
||||
if (this.shouldThrow) {
|
||||
throw new Error("Guard failed");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
describe("OrGuard", () => {
|
||||
let guard: InstanceType<ReturnType<typeof Or>>;
|
||||
let mockExecutionContext: ExecutionContext;
|
||||
let mockModuleRef: any;
|
||||
|
||||
beforeEach(() => {
|
||||
mockModuleRef = {
|
||||
get: jest.fn(),
|
||||
};
|
||||
|
||||
const OrGuardClass = Or([MockGuard1, MockGuard2]);
|
||||
guard = new OrGuardClass(mockModuleRef);
|
||||
mockExecutionContext = {} as ExecutionContext;
|
||||
});
|
||||
|
||||
it("should be defined", () => {
|
||||
expect(guard).toBeDefined();
|
||||
});
|
||||
|
||||
it("should grant access when first guard passes", async () => {
|
||||
const mockGuard1 = new MockGuard1(true);
|
||||
const mockGuard2 = new MockGuard2(false);
|
||||
|
||||
mockModuleRef.get.mockReturnValueOnce(mockGuard1).mockReturnValueOnce(mockGuard2);
|
||||
|
||||
const result = await guard.canActivate(mockExecutionContext);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it("should grant access when second guard passes", async () => {
|
||||
const mockGuard1 = new MockGuard1(false);
|
||||
const mockGuard2 = new MockGuard2(true);
|
||||
|
||||
mockModuleRef.get.mockReturnValueOnce(mockGuard1).mockReturnValueOnce(mockGuard2);
|
||||
|
||||
const result = await guard.canActivate(mockExecutionContext);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it("should deny access when all guards fail", async () => {
|
||||
const mockGuard1 = new MockGuard1(false);
|
||||
const mockGuard2 = new MockGuard2(false);
|
||||
|
||||
mockModuleRef.get.mockReturnValueOnce(mockGuard1).mockReturnValueOnce(mockGuard2);
|
||||
|
||||
const result = await guard.canActivate(mockExecutionContext);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it("should continue checking other guards when one throws an error", async () => {
|
||||
const mockGuard1 = new MockGuard3(true); // throws error
|
||||
const mockGuard2 = new MockGuard2(true); // passes
|
||||
|
||||
mockModuleRef.get.mockReturnValueOnce(mockGuard1).mockReturnValueOnce(mockGuard2);
|
||||
|
||||
const result = await guard.canActivate(mockExecutionContext);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Or decorator", () => {
|
||||
it("should create a guard class with the specified guards", () => {
|
||||
const OrGuardClass = Or([MockGuard1, MockGuard2]);
|
||||
|
||||
expect(OrGuardClass).toBeDefined();
|
||||
expect(typeof OrGuardClass).toBe("function");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
import { Injectable, CanActivate, ExecutionContext, Type, Logger } from "@nestjs/common";
|
||||
import { ModuleRef } from "@nestjs/core";
|
||||
|
||||
/**
|
||||
* Decorator function that creates an Or guard with the specified guards
|
||||
* @param guards Array of guard classes to evaluate with OR logic
|
||||
* @returns A guard class that grants access if ANY of the provided guards return true
|
||||
*/
|
||||
export function Or(guards: Type<CanActivate>[]) {
|
||||
@Injectable()
|
||||
class OrGuard implements CanActivate {
|
||||
public readonly logger = new Logger("OrGuard");
|
||||
|
||||
constructor(public readonly moduleRef: ModuleRef) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
let lastError: unknown | null = null;
|
||||
for (const Guard of guards) {
|
||||
try {
|
||||
const guardInstance = this.moduleRef.get(Guard, { strict: false });
|
||||
const result = await Promise.resolve(guardInstance.canActivate(context));
|
||||
|
||||
if (result === true) {
|
||||
this.logger.log(`OrGuard - Guard ${Guard.name} granted access`);
|
||||
return true; // Access granted if any guard returns true
|
||||
}
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
// If a guard throws an exception, it implies failure for that specific guard.
|
||||
// We catch it and continue checking other guards in the OR chain.
|
||||
// If an exception should stop the entire chain immediately, re-throw it here.
|
||||
this.logger.log(
|
||||
`OrGuard - Guard ${Guard.name} failed: ${
|
||||
error instanceof Error ? error.message : "Unknown error"
|
||||
}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.log("OrGuard - All guards failed, access denied");
|
||||
if (lastError) {
|
||||
throw lastError;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return OrGuard;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { ApiAuthGuardUser } from "@/modules/auth/strategies/api-auth/api-auth.strategy";
|
||||
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
|
||||
import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from "@nestjs/common";
|
||||
import { Request } from "express";
|
||||
|
||||
import { Team } from "@calcom/prisma/client";
|
||||
|
||||
@Injectable()
|
||||
export class IsUserRoutingForm implements CanActivate {
|
||||
constructor(private readonly dbRead: PrismaReadService) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const request = context.switchToHttp().getRequest<Request & { organization: Team }>();
|
||||
const routingFormId: string = request.params.routingFormId;
|
||||
const user = request.user as ApiAuthGuardUser;
|
||||
if (!routingFormId) {
|
||||
throw new ForbiddenException("IsUserRoutingForm - No routing form id found in request params.");
|
||||
}
|
||||
|
||||
const userRoutingForm = await this.dbRead.prisma.app_RoutingForms_Form.findFirst({
|
||||
where: {
|
||||
id: routingFormId,
|
||||
userId: Number(user.id),
|
||||
teamId: null,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!userRoutingForm) {
|
||||
throw new ForbiddenException(
|
||||
`Routing Form with id=${routingFormId} is not a user Routing Form owned by user with id=${user.id}.`
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user