Skip to content

Commit cbb234c

Browse files
authored
fix: validate credential ownership in attribute sync update (calcom#28873)
1 parent 51e852f commit cbb234c

3 files changed

Lines changed: 35 additions & 2 deletions

File tree

packages/features/ee/integration-attribute-sync/repositories/IIntegrationAttributeSyncRepository.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ export interface ISyncFormData {
6363
id: string;
6464
name: string;
6565
credentialId?: number;
66+
integration?: AttributeSyncIntegrations;
6667
enabled: boolean;
6768
organizationId: number;
6869
ruleId: string;
@@ -138,7 +139,7 @@ export interface IIntegrationAttributeSyncUpdateParams {
138139
integrationAttributeSync: Omit<
139140
IntegrationAttributeSync,
140141
"attributeSyncRule" | "syncFieldMappings" | "integration"
141-
>;
142+
> & { integration?: AttributeSyncIntegrations };
142143
attributeSyncRule: AttributeSyncRule;
143144
fieldMappingsToCreate: Omit<AttributeSyncFieldMapping, "id">[];
144145
fieldMappingsToUpdate: AttributeSyncFieldMapping[];

packages/features/ee/integration-attribute-sync/services/IntegrationAttributeSyncService.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,4 +217,11 @@ export class IntegrationAttributeSyncService {
217217
async getAllByCredentialId(credentialId: number) {
218218
return this.deps.integrationAttributeSyncRepository.getAllByCredentialId(credentialId);
219219
}
220+
221+
async validateCredentialBelongsToOrg(credentialId: number, organizationId: number) {
222+
return this.deps.credentialRepository.findByIdAndTeamId({
223+
id: credentialId,
224+
teamId: organizationId,
225+
});
226+
}
220227
}

packages/trpc/server/routers/viewer/attribute-sync/updateAttributeSync.handler.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
import { getIntegrationAttributeSyncService } from "@calcom/ee/integration-attribute-sync/di/IntegrationAttributeSyncService.container";
2+
import {
3+
AttributeSyncIntegrations,
4+
type ISyncFormData,
5+
} from "@calcom/ee/integration-attribute-sync/repositories/IIntegrationAttributeSyncRepository";
26
import {
37
DuplicateAttributeWithinSyncError,
48
DuplicateAttributeAcrossSyncsError,
@@ -37,8 +41,29 @@ const updateAttributeSyncHandler = async ({ ctx, input }: UpdateAttributeSyncOpt
3741
throw new TRPCError({ code: "UNAUTHORIZED" });
3842
}
3943

44+
// Never trust user-supplied organizationId — override with authenticated user's org
45+
const safeInput: ISyncFormData = { ...input, organizationId: org.id };
46+
47+
// Validate credential belongs to the user's organization and derive integration type
48+
if (input.credentialId !== undefined) {
49+
const credential = await integrationAttributeSyncService.validateCredentialBelongsToOrg(
50+
input.credentialId,
51+
org.id
52+
);
53+
if (!credential) {
54+
throw new TRPCError({ code: "NOT_FOUND", message: "Credential not found" });
55+
}
56+
57+
const integrationValue = credential.app?.slug || credential.type;
58+
if (!Object.values(AttributeSyncIntegrations).includes(integrationValue as AttributeSyncIntegrations)) {
59+
throw new TRPCError({ code: "BAD_REQUEST", message: `Unsupported integration type: ${integrationValue}` });
60+
}
61+
62+
safeInput.integration = integrationValue as AttributeSyncIntegrations;
63+
}
64+
4065
try {
41-
await integrationAttributeSyncService.updateIncludeRulesAndMappings(input);
66+
await integrationAttributeSyncService.updateIncludeRulesAndMappings(safeInput);
4267
} catch (error) {
4368
if (error instanceof DuplicateAttributeWithinSyncError) {
4469
throw new TRPCError({

0 commit comments

Comments
 (0)