Skip to content

Commit 404d68f

Browse files
committed
Apply patches: Update pnpm-lock.yaml and fix security vulnerabilities in Trustpilot integration
1 parent 47cee75 commit 404d68f

File tree

20 files changed

+345
-172
lines changed

20 files changed

+345
-172
lines changed

components/trustpilot/actions/fetch-product-review-by-id/fetch-product-review-by-id.mjs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ import trustpilot from "../../app/trustpilot.app.ts";
33
export default {
44
key: "trustpilot-fetch-product-review-by-id",
55
name: "Fetch Product Review by ID",
6-
description: "Fetch a specific product review by its ID. [See the documentation](https://developers.trustpilot.com/product-reviews-api#get-private-product-review)",
6+
description: "Retrieves detailed information about a specific product review on Trustpilot. Use this action to get comprehensive data about a single product review, including customer feedback, star rating, review text, and metadata. Perfect for analyzing individual customer experiences, responding to specific feedback, or integrating review data into your customer service workflows. [See the documentation](https://developers.trustpilot.com/product-reviews-api#get-private-product-review)",
77
version: "0.0.1",
8+
publishedAt: "2025-07-18T00:00:00.000Z",
89
type: "action",
910
props: {
1011
trustpilot,
@@ -24,7 +25,7 @@ export default {
2425
});
2526

2627
$.export("$summary", `Successfully fetched product review ${reviewId}`);
27-
28+
2829
return {
2930
review,
3031
metadata: {
@@ -36,4 +37,4 @@ export default {
3637
throw new Error(`Failed to fetch product review: ${error.message}`);
3738
}
3839
},
39-
};
40+
};

components/trustpilot/actions/fetch-product-reviews/fetch-product-reviews.mjs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ import trustpilot from "../../app/trustpilot.app.ts";
33
export default {
44
key: "trustpilot-fetch-product-reviews",
55
name: "Fetch Product Reviews",
6-
description: "Fetch product reviews for a business unit. [See the documentation](https://developers.trustpilot.com/product-reviews-api#get-private-product-reviews)",
6+
description: "Retrieves a list of product reviews for a specific business unit on Trustpilot. This action enables you to fetch multiple product reviews with powerful filtering options including star ratings, language, tags, and sorting preferences. Ideal for monitoring product feedback trends, generating reports, analyzing customer sentiment across your product catalog, or building review dashboards. Supports pagination for handling large review volumes. [See the documentation](https://developers.trustpilot.com/product-reviews-api#get-private-product-reviews)",
77
version: "0.0.1",
8+
publishedAt: "2025-07-18T00:00:00.000Z",
89
type: "action",
910
props: {
1011
trustpilot,
@@ -83,10 +84,12 @@ export default {
8384
offset,
8485
});
8586

86-
const { reviews, pagination } = result;
87+
const {
88+
reviews, pagination,
89+
} = result;
8790

8891
$.export("$summary", `Successfully fetched ${reviews.length} product review(s) for business unit ${businessUnitId}`);
89-
92+
9093
return {
9194
reviews,
9295
pagination,
@@ -106,4 +109,4 @@ export default {
106109
throw new Error(`Failed to fetch product reviews: ${error.message}`);
107110
}
108111
},
109-
};
112+
};

components/trustpilot/actions/fetch-service-review-by-id/fetch-service-review-by-id.mjs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ import trustpilot from "../../app/trustpilot.app.ts";
33
export default {
44
key: "trustpilot-fetch-service-review-by-id",
55
name: "Fetch Service Review by ID",
6-
description: "Fetch a specific service review by its ID. [See the documentation](https://developers.trustpilot.com/business-units-api#get-business-unit-review)",
6+
description: "Retrieves detailed information about a specific service review for your business on Trustpilot. Use this action to access comprehensive data about an individual service review, including the customer's rating, review content, date, and any responses. Essential for customer service teams to analyze specific feedback, track review history, or integrate individual review data into CRM systems and support tickets. [See the documentation](https://developers.trustpilot.com/business-units-api#get-business-unit-review)",
77
version: "0.0.1",
8+
publishedAt: "2025-07-18T00:00:00.000Z",
89
type: "action",
910
props: {
1011
trustpilot,
@@ -34,7 +35,7 @@ export default {
3435
});
3536

3637
$.export("$summary", `Successfully fetched service review ${reviewId} for business unit ${businessUnitId}`);
37-
38+
3839
return {
3940
review,
4041
metadata: {
@@ -47,4 +48,4 @@ export default {
4748
throw new Error(`Failed to fetch service review: ${error.message}`);
4849
}
4950
},
50-
};
51+
};

components/trustpilot/actions/fetch-service-reviews/fetch-service-reviews.mjs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ import trustpilot from "../../app/trustpilot.app.ts";
33
export default {
44
key: "trustpilot-fetch-service-reviews",
55
name: "Fetch Service Reviews",
6-
description: "Fetch service reviews for a business unit. [See the documentation](https://developers.trustpilot.com/business-units-api#get-business-unit-reviews)",
6+
description: "Fetches service reviews for a specific business unit from Trustpilot with support for filtering by star rating, tags, language, and more. [See the documentation](https://developers.trustpilot.com/business-units-api#get-business-unit-reviews)",
77
version: "0.0.1",
88
type: "action",
9+
publishedAt: "2025-07-18T00:00:00.000Z",
910
props: {
1011
trustpilot,
1112
businessUnitId: {
@@ -83,10 +84,12 @@ export default {
8384
offset,
8485
});
8586

86-
const { reviews, pagination } = result;
87+
const {
88+
reviews, pagination,
89+
} = result;
8790

8891
$.export("$summary", `Successfully fetched ${reviews.length} service review(s) for business unit ${businessUnitId}`);
89-
92+
9093
return {
9194
reviews,
9295
pagination,
@@ -106,4 +109,4 @@ export default {
106109
throw new Error(`Failed to fetch service reviews: ${error.message}`);
107110
}
108111
},
109-
};
112+
};

components/trustpilot/actions/reply-to-product-review/reply-to-product-review.mjs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ import trustpilot from "../../app/trustpilot.app.ts";
33
export default {
44
key: "trustpilot-reply-to-product-review",
55
name: "Reply to Product Review",
6-
description: "Reply to a product review on behalf of your business. [See the documentation](https://developers.trustpilot.com/product-reviews-api#reply-to-product-review)",
6+
description: "Posts a public reply to a product review on Trustpilot on behalf of your business. This action allows you to respond to customer feedback, address concerns, thank customers for positive reviews, or provide additional information about products. Replies help demonstrate your commitment to customer satisfaction and can improve your overall reputation. Note that replies are publicly visible and cannot be edited once posted. [See the documentation](https://developers.trustpilot.com/product-reviews-api#reply-to-product-review)",
77
version: "0.0.1",
8+
publishedAt: "2025-07-18T00:00:00.000Z",
89
type: "action",
910
props: {
1011
trustpilot,
@@ -37,7 +38,7 @@ export default {
3738
});
3839

3940
$.export("$summary", `Successfully replied to product review ${reviewId}`);
40-
41+
4142
return {
4243
success: true,
4344
reply: result,
@@ -51,4 +52,4 @@ export default {
5152
throw new Error(`Failed to reply to product review: ${error.message}`);
5253
}
5354
},
54-
};
55+
};

components/trustpilot/actions/reply-to-service-review/reply-to-service-review.mjs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ import trustpilot from "../../app/trustpilot.app.ts";
33
export default {
44
key: "trustpilot-reply-to-service-review",
55
name: "Reply to Service Review",
6-
description: "Reply to a service review on behalf of your business. [See the documentation](https://developers.trustpilot.com/business-units-api#reply-to-review)",
6+
description: "Posts a public reply to a service review on Trustpilot on behalf of your business. This action enables you to engage with customers who have reviewed your services, allowing you to address complaints, clarify misunderstandings, express gratitude for positive feedback, or provide updates on how you're improving based on their input. Professional responses to reviews can significantly impact your business reputation and show potential customers that you value feedback. Remember that all replies are permanent and publicly visible. [See the documentation](https://developers.trustpilot.com/business-units-api#reply-to-review)",
77
version: "0.0.1",
8+
publishedAt: "2025-07-18T00:00:00.000Z",
89
type: "action",
910
props: {
1011
trustpilot,
@@ -45,7 +46,7 @@ export default {
4546
});
4647

4748
$.export("$summary", `Successfully replied to service review ${reviewId}`);
48-
49+
4950
return {
5051
success: true,
5152
reply: result,
@@ -60,4 +61,4 @@ export default {
6061
throw new Error(`Failed to reply to service review: ${error.message}`);
6162
}
6263
},
63-
};
64+
};

components/trustpilot/app/trustpilot.app.ts

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { defineApp } from "@pipedream/types";
22
import { axios } from "@pipedream/platform";
3+
import crypto from "crypto";
34
import {
45
BASE_URL,
56
ENDPOINTS,
@@ -20,6 +21,7 @@ import {
2021
formatQueryParams,
2122
parseApiError,
2223
sleep,
24+
sanitizeInput,
2325
} from "../common/utils.mjs";
2426

2527
export default defineApp({
@@ -305,11 +307,20 @@ export default defineApp({
305307
throw new Error("Reply message is required");
306308
}
307309

310+
// Sanitize and validate message length (Trustpilot limit is 5000 characters)
311+
const sanitizedMessage = sanitizeInput(message, 5000);
312+
if (sanitizedMessage.length === 0) {
313+
throw new Error("Reply message cannot be empty after sanitization");
314+
}
315+
if (message.length > 5000) {
316+
console.warn("Reply message was truncated to 5000 characters");
317+
}
318+
308319
const endpoint = buildUrl(ENDPOINTS.REPLY_TO_SERVICE_REVIEW, { businessUnitId, reviewId });
309320
const response = await this._makeRequest({
310321
endpoint,
311322
method: "POST",
312-
data: { message },
323+
data: { message: sanitizedMessage },
313324
});
314325
return response;
315326
},
@@ -377,11 +388,20 @@ export default defineApp({
377388
throw new Error("Reply message is required");
378389
}
379390

391+
// Sanitize and validate message length (Trustpilot limit is 5000 characters)
392+
const sanitizedMessage = sanitizeInput(message, 5000);
393+
if (sanitizedMessage.length === 0) {
394+
throw new Error("Reply message cannot be empty after sanitization");
395+
}
396+
if (message.length > 5000) {
397+
console.warn("Reply message was truncated to 5000 characters");
398+
}
399+
380400
const endpoint = buildUrl(ENDPOINTS.REPLY_TO_PRODUCT_REVIEW, { reviewId });
381401
const response = await this._makeRequest({
382402
endpoint,
383403
method: "POST",
384-
data: { message },
404+
data: { message: sanitizedMessage },
385405
});
386406
return response;
387407
},
@@ -437,11 +457,20 @@ export default defineApp({
437457
throw new Error("Reply message is required");
438458
}
439459

460+
// Sanitize and validate message length (Trustpilot limit is 5000 characters)
461+
const sanitizedMessage = sanitizeInput(message, 5000);
462+
if (sanitizedMessage.length === 0) {
463+
throw new Error("Reply message cannot be empty after sanitization");
464+
}
465+
if (message.length > 5000) {
466+
console.warn("Reply message was truncated to 5000 characters");
467+
}
468+
440469
const endpoint = buildUrl(ENDPOINTS.REPLY_TO_CONVERSATION, { conversationId });
441470
const response = await this._makeRequest({
442471
endpoint,
443472
method: "POST",
444-
data: { message },
473+
data: { message: sanitizedMessage },
445474
});
446475
return response;
447476
},
@@ -497,14 +526,25 @@ export default defineApp({
497526
},
498527

499528
validateWebhookSignature(payload, signature, secret) {
500-
// TODO: Implement webhook signature validation when Trustpilot provides it
501-
return true;
502-
},
529+
// Trustpilot uses HMAC-SHA256 for webhook signature validation
530+
// The signature is sent in the x-trustpilot-signature header
531+
if (!signature || !secret) {
532+
return false;
533+
}
503534

504-
// Legacy method for debugging
505-
authKeys() {
506-
console.log("Auth keys:", Object.keys(this.$auth || {}));
507-
return Object.keys(this.$auth || {});
535+
const payloadString = typeof payload === 'string' ? payload : JSON.stringify(payload);
536+
537+
const expectedSignature = crypto
538+
.createHmac('sha256', secret)
539+
.update(payloadString)
540+
.digest('hex');
541+
542+
// Constant time comparison to prevent timing attacks
543+
return crypto.timingSafeEqual(
544+
Buffer.from(signature),
545+
Buffer.from(expectedSignature)
546+
);
508547
},
548+
509549
},
510550
});

components/trustpilot/common/constants.mjs

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,30 +13,34 @@ export const ENDPOINTS = {
1313
// Business Units
1414
BUSINESS_UNITS: "/business-units",
1515
BUSINESS_UNIT_BY_ID: "/business-units/{businessUnitId}",
16-
16+
1717
// Public Reviews
1818
PUBLIC_REVIEWS: "/business-units/{businessUnitId}/reviews",
1919
PUBLIC_REVIEW_BY_ID: "/business-units/{businessUnitId}/reviews/{reviewId}",
20-
20+
2121
// Private Reviews (Service)
2222
PRIVATE_SERVICE_REVIEWS: "/private/business-units/{businessUnitId}/reviews",
2323
PRIVATE_SERVICE_REVIEW_BY_ID: "/private/business-units/{businessUnitId}/reviews/{reviewId}",
2424
REPLY_TO_SERVICE_REVIEW: "/private/business-units/{businessUnitId}/reviews/{reviewId}/reply",
25-
25+
2626
// Private Reviews (Product)
2727
PRIVATE_PRODUCT_REVIEWS: "/private/product-reviews/business-units/{businessUnitId}/reviews",
2828
PRIVATE_PRODUCT_REVIEW_BY_ID: "/private/product-reviews/{reviewId}",
2929
REPLY_TO_PRODUCT_REVIEW: "/private/product-reviews/{reviewId}/reply",
30-
30+
3131
// Conversations
3232
CONVERSATIONS: "/private/conversations",
3333
CONVERSATION_BY_ID: "/private/conversations/{conversationId}",
3434
REPLY_TO_CONVERSATION: "/private/conversations/{conversationId}/reply",
35-
35+
3636
// Invitations
3737
EMAIL_INVITATIONS: "/private/business-units/{businessUnitId}/email-invitations",
38-
39-
// Webhooks (deprecated for polling)
38+
39+
// Webhooks
40+
// Note: This integration uses polling sources instead of webhooks for better reliability
41+
// and simpler implementation. Webhook signature validation is implemented in the app
42+
// using HMAC-SHA256 with the x-trustpilot-signature header for future webhook sources.
43+
// These endpoints and validation methods are ready for webhook implementation if needed.
4044
WEBHOOKS: "/private/webhooks",
4145
WEBHOOK_BY_ID: "/private/webhooks/{webhookId}",
4246
};
@@ -60,7 +64,13 @@ export const SORT_OPTIONS = {
6064
UPDATED_AT_DESC: "updatedat.desc",
6165
};
6266

63-
export const RATING_SCALE = [1, 2, 3, 4, 5];
67+
export const RATING_SCALE = [
68+
1,
69+
2,
70+
3,
71+
4,
72+
5,
73+
];
6474

6575
export const DEFAULT_LIMIT = 20;
6676
export const MAX_LIMIT = 100;
@@ -92,7 +102,7 @@ export const POLLING_CONFIG = {
92102
export const SOURCE_TYPES = {
93103
NEW_REVIEWS: "new_reviews",
94104
UPDATED_REVIEWS: "updated_reviews",
95-
NEW_REPLIES: "new_replies",
105+
NEW_REPLIES: "new_replies",
96106
NEW_CONVERSATIONS: "new_conversations",
97107
UPDATED_CONVERSATIONS: "updated_conversations",
98-
};
108+
};

0 commit comments

Comments
 (0)