Skip to content

Commit c941192

Browse files
authored
fix: access service class for editLocationHandler (calcom#25315)
* fix access service class * Move out of trpc
1 parent 44311f5 commit c941192

3 files changed

Lines changed: 467 additions & 1 deletion

File tree

Lines changed: 320 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
1+
import { describe, expect, test, vi, beforeEach } from "vitest";
2+
3+
import { CredentialRepository } from "@calcom/features/credentials/repositories/CredentialRepository";
4+
import { UserRepository } from "@calcom/features/users/repositories/UserRepository";
5+
import { HttpError } from "@calcom/lib/http-error";
6+
import { prisma } from "@calcom/prisma";
7+
8+
import { CredentialAccessService } from "./CredentialAccessService";
9+
10+
vi.mock("@calcom/prisma", () => {
11+
return {
12+
prisma: {
13+
user: {
14+
findUnique: vi.fn(),
15+
},
16+
},
17+
};
18+
});
19+
20+
vi.mock("@calcom/features/credentials/repositories/CredentialRepository", () => {
21+
return {
22+
CredentialRepository: {
23+
findFirstByIdWithKeyAndUser: vi.fn(),
24+
},
25+
};
26+
});
27+
28+
vi.mock("@calcom/features/users/repositories/UserRepository", () => {
29+
return {
30+
UserRepository: vi.fn().mockImplementation(() => ({
31+
getUserOrganizationAndTeams: vi.fn(),
32+
})),
33+
};
34+
});
35+
36+
describe("CredentialAccessService", () => {
37+
beforeEach(() => {
38+
vi.clearAllMocks();
39+
});
40+
41+
test("should allow access when credential belongs to logged-in user", async () => {
42+
const credentialId = 1;
43+
const loggedInUserId = 100;
44+
const bookingOwnerId = 200;
45+
46+
vi.mocked(CredentialRepository.findFirstByIdWithKeyAndUser).mockResolvedValue({
47+
id: credentialId,
48+
userId: loggedInUserId,
49+
teamId: null,
50+
type: "zoom_video",
51+
appId: "zoom",
52+
key: {},
53+
invalid: false,
54+
user: { email: "user@example.com" },
55+
delegatedTo: null,
56+
delegatedToId: null,
57+
delegationCredentialId: null,
58+
} as any);
59+
60+
const service = new CredentialAccessService();
61+
await expect(
62+
service.ensureAccessible({
63+
credentialId,
64+
loggedInUserId,
65+
bookingOwnerId,
66+
})
67+
).resolves.not.toThrow();
68+
});
69+
70+
test("should allow access when credential belongs to booking owner", async () => {
71+
const credentialId = 1;
72+
const loggedInUserId = 100;
73+
const bookingOwnerId = 200;
74+
75+
vi.mocked(CredentialRepository.findFirstByIdWithKeyAndUser).mockResolvedValue({
76+
id: credentialId,
77+
userId: bookingOwnerId,
78+
teamId: null,
79+
type: "zoom_video",
80+
appId: "zoom",
81+
key: {},
82+
invalid: false,
83+
user: { email: "owner@example.com" },
84+
delegatedTo: null,
85+
delegatedToId: null,
86+
delegationCredentialId: null,
87+
} as any);
88+
89+
const service = new CredentialAccessService();
90+
await expect(
91+
service.ensureAccessible({
92+
credentialId,
93+
loggedInUserId,
94+
bookingOwnerId,
95+
})
96+
).resolves.not.toThrow();
97+
});
98+
99+
test("should allow access when credential belongs to team the logged-in user belongs to", async () => {
100+
const credentialId = 1;
101+
const loggedInUserId = 100;
102+
const bookingOwnerId = 200;
103+
const teamId = 50;
104+
105+
vi.mocked(CredentialRepository.findFirstByIdWithKeyAndUser).mockResolvedValue({
106+
id: credentialId,
107+
userId: null,
108+
teamId: teamId,
109+
type: "zoom_video",
110+
appId: "zoom",
111+
key: {},
112+
invalid: false,
113+
user: null,
114+
delegatedTo: null,
115+
delegatedToId: null,
116+
delegationCredentialId: null,
117+
} as any);
118+
119+
const mockUserRepo = {
120+
getUserOrganizationAndTeams: vi.fn().mockResolvedValue({
121+
organizationId: null,
122+
teams: [{ teamId: teamId }],
123+
}),
124+
};
125+
126+
vi.mocked(UserRepository).mockImplementation(() => mockUserRepo as any);
127+
128+
const service = new CredentialAccessService();
129+
await expect(
130+
service.ensureAccessible({
131+
credentialId,
132+
loggedInUserId,
133+
bookingOwnerId,
134+
})
135+
).resolves.not.toThrow();
136+
});
137+
138+
test("should allow access when credential belongs to team the booking owner belongs to", async () => {
139+
const credentialId = 1;
140+
const loggedInUserId = 100;
141+
const bookingOwnerId = 200;
142+
const teamId = 50;
143+
144+
vi.mocked(CredentialRepository.findFirstByIdWithKeyAndUser).mockResolvedValue({
145+
id: credentialId,
146+
userId: null,
147+
teamId: teamId,
148+
type: "zoom_video",
149+
appId: "zoom",
150+
key: {},
151+
invalid: false,
152+
user: null,
153+
delegatedTo: null,
154+
delegatedToId: null,
155+
delegationCredentialId: null,
156+
} as any);
157+
158+
const mockUserRepo = {
159+
getUserOrganizationAndTeams: vi
160+
.fn()
161+
.mockResolvedValueOnce({
162+
organizationId: null,
163+
teams: [],
164+
})
165+
.mockResolvedValueOnce({
166+
organizationId: null,
167+
teams: [{ teamId: teamId }],
168+
}),
169+
};
170+
171+
vi.mocked(UserRepository).mockImplementation(() => mockUserRepo as any);
172+
173+
const service = new CredentialAccessService();
174+
await expect(
175+
service.ensureAccessible({
176+
credentialId,
177+
loggedInUserId,
178+
bookingOwnerId,
179+
})
180+
).resolves.not.toThrow();
181+
});
182+
183+
test("should allow access when credential belongs to organization the logged-in user belongs to", async () => {
184+
const credentialId = 1;
185+
const loggedInUserId = 100;
186+
const bookingOwnerId = 200;
187+
const orgId = 50;
188+
189+
vi.mocked(CredentialRepository.findFirstByIdWithKeyAndUser).mockResolvedValue({
190+
id: credentialId,
191+
userId: null,
192+
teamId: orgId,
193+
type: "zoom_video",
194+
appId: "zoom",
195+
key: {},
196+
invalid: false,
197+
user: null,
198+
delegatedTo: null,
199+
delegatedToId: null,
200+
delegationCredentialId: null,
201+
} as any);
202+
203+
const mockUserRepo = {
204+
getUserOrganizationAndTeams: vi.fn().mockResolvedValue({
205+
organizationId: orgId,
206+
teams: [],
207+
}),
208+
};
209+
210+
vi.mocked(UserRepository).mockImplementation(() => mockUserRepo as any);
211+
212+
const service = new CredentialAccessService();
213+
await expect(
214+
service.ensureAccessible({
215+
credentialId,
216+
loggedInUserId,
217+
bookingOwnerId,
218+
})
219+
).resolves.not.toThrow();
220+
});
221+
222+
test("should throw NOT_FOUND when credential does not exist", async () => {
223+
const credentialId = 1;
224+
const loggedInUserId = 100;
225+
const bookingOwnerId = 200;
226+
227+
vi.mocked(CredentialRepository.findFirstByIdWithKeyAndUser).mockResolvedValue(null);
228+
229+
const service = new CredentialAccessService();
230+
const error = await service
231+
.ensureAccessible({
232+
credentialId,
233+
loggedInUserId,
234+
bookingOwnerId,
235+
})
236+
.catch((e) => e);
237+
238+
expect(error).toBeInstanceOf(HttpError);
239+
expect(error.statusCode).toBe(404);
240+
expect(error.message).toBe("Credential not found");
241+
});
242+
243+
test("should throw FORBIDDEN when credential is not accessible", async () => {
244+
const credentialId = 1;
245+
const loggedInUserId = 100;
246+
const bookingOwnerId = 200;
247+
const otherUserId = 300;
248+
249+
vi.mocked(CredentialRepository.findFirstByIdWithKeyAndUser).mockResolvedValue({
250+
id: credentialId,
251+
userId: otherUserId,
252+
teamId: null,
253+
type: "zoom_video",
254+
appId: "zoom",
255+
key: {},
256+
invalid: false,
257+
user: { email: "other@example.com" },
258+
delegatedTo: null,
259+
delegatedToId: null,
260+
delegationCredentialId: null,
261+
} as any);
262+
263+
const mockUserRepo = {
264+
getUserOrganizationAndTeams: vi
265+
.fn()
266+
.mockResolvedValueOnce({
267+
organizationId: null,
268+
teams: [],
269+
})
270+
.mockResolvedValueOnce({
271+
organizationId: null,
272+
teams: [],
273+
}),
274+
};
275+
276+
vi.mocked(UserRepository).mockImplementation(() => mockUserRepo as any);
277+
278+
const service = new CredentialAccessService();
279+
const error = await service
280+
.ensureAccessible({
281+
credentialId,
282+
loggedInUserId,
283+
bookingOwnerId,
284+
})
285+
.catch((e) => e);
286+
287+
expect(error).toBeInstanceOf(HttpError);
288+
expect(error.statusCode).toBe(403);
289+
expect(error.message).toBe("You do not have access to this credential");
290+
});
291+
292+
test("should handle null bookingOwnerId", async () => {
293+
const credentialId = 1;
294+
const loggedInUserId = 100;
295+
const bookingOwnerId = null;
296+
297+
vi.mocked(CredentialRepository.findFirstByIdWithKeyAndUser).mockResolvedValue({
298+
id: credentialId,
299+
userId: loggedInUserId,
300+
teamId: null,
301+
type: "zoom_video",
302+
appId: "zoom",
303+
key: {},
304+
invalid: false,
305+
user: { email: "user@example.com" },
306+
delegatedTo: null,
307+
delegatedToId: null,
308+
delegationCredentialId: null,
309+
} as any);
310+
311+
const service = new CredentialAccessService();
312+
await expect(
313+
service.ensureAccessible({
314+
credentialId,
315+
loggedInUserId,
316+
bookingOwnerId,
317+
})
318+
).resolves.not.toThrow();
319+
});
320+
});

0 commit comments

Comments
 (0)