Skip to content

Commit cadb9c2

Browse files
committed
fix vulnerabilities
1 parent cb18f5b commit cadb9c2

File tree

4 files changed

+107
-71
lines changed

4 files changed

+107
-71
lines changed

checkout/README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ Before running this example, you need:
1919
- commercetools account and project
2020
- API client credentials with required scopes
2121
- Sample products in your project (or import using the import example)
22+
- Shipping methods configured with rates
23+
- Configure taxes with rates for address being used for testing
2224

2325
### 2. commercetools Checkout Application Setup
2426
This is the **critical step** - you must set up a Checkout application in Merchant Center:
@@ -44,7 +46,7 @@ This is the **critical step** - you must set up a Checkout application in Mercha
4446
- Add payment methods (card etc.) and enable them
4547

4648
5. **Copy Application ID**
47-
- After configuration, copy the **Application ID**
49+
- After configuration, copy the **Application Key**
4850
- You'll need this for your `.env` file
4951

5052
## 🛠 Installation & Setup
@@ -76,7 +78,7 @@ CTP_CLIENT_ID=your-client-id
7678
CTP_CLIENT_SECRET=your-client-secret
7779
CTP_AUTH_URL=https://auth.europe-west1.gcp.commercetools.com
7880
CTP_API_URL=https://api.europe-west1.gcp.commercetools.com
79-
CTP_SCOPES=manage_project:your-project-key manage_payments:your-project-key
81+
CTP_SCOPES=manage_orders:your-project-key manage_customers:your-project-key view_shipping_methods:your-project-key view_cart_discounts:your-project-key view_discount_codes:your-project-key view_tax_categories:your-project-key view_sessions:your-project-key view_categories:your-project-key view_products:your-project-key view_published_products:your-project-key view_project_settings:your-project-key create_anonymous_token:your-project-key view_standalone_prices:your-project-key manage_shopping_lists:your-project-key manage_order_edits:your-project-key view_product_selections:your-project-key view_types:your-project-key manage_sessions:your-project-key
8082
8183
# commercetools Checkout Configuration
8284
CHECKOUT_APPLICATION_ID=your-checkout-application-id-from-step-5-above
@@ -90,7 +92,7 @@ CLIENT_PORT=3000
9092
Edit client `.env` with your project:
9193

9294
```env
93-
REACT_APP_CTP_PROJECT_KEY=consulting
95+
REACT_APP_CTP_PROJECT_KEY=your-project-key
9496
```
9597

9698

checkout/client/package-lock.json

Lines changed: 7 additions & 39 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

checkout/client/package.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"react": "^18.2.0",
99
"react-dom": "^18.2.0",
1010
"react-router-dom": "^6.8.0",
11-
"react-scripts": "5.0.1"
11+
"react-scripts": "^5.0.1"
1212
},
1313
"scripts": {
1414
"start": "react-scripts start",
@@ -34,5 +34,10 @@
3434
"last 1 safari version"
3535
]
3636
},
37-
"proxy": "http://localhost:3001"
37+
"proxy": "http://localhost:3001",
38+
"overrides": {
39+
"nth-check": "^2.0.1",
40+
"postcss": "^8.4.31",
41+
"webpack-dev-server": "^4.15.1"
42+
}
3843
}

checkout/src/server.js

Lines changed: 88 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ const express = require('express');
22
const cors = require('cors');
33
const fetch = require('node-fetch');
44
const { v4: uuidv4 } = require('uuid');
5+
const { URL } = require('url');
56
require('dotenv').config();
67

78
const { ClientBuilder } = require('@commercetools/ts-client');
@@ -12,9 +13,58 @@ const PORT = process.env.PORT || 3001;
1213

1314
// Middleware
1415
app.use(cors());
15-
app.use(express.json());
16+
app.use(express.json({ limit: '10mb' })); // Limit payload size for DoS protection
1617
app.use(express.static('client/build'));
1718

19+
// Security helpers
20+
function validateCommercetoolsUrl(url) {
21+
try {
22+
const parsedUrl = new URL(url);
23+
// Only allow commercetools domains to prevent SSRF
24+
const allowedHosts = [
25+
'session.europe-west1.gcp.commercetools.com',
26+
'session.us-central1.gcp.commercetools.com',
27+
'session.australia-southeast1.gcp.commercetools.com',
28+
'auth.europe-west1.gcp.commercetools.com',
29+
'auth.us-central1.gcp.commercetools.com',
30+
'auth.australia-southeast1.gcp.commercetools.com',
31+
'api.europe-west1.gcp.commercetools.com',
32+
'api.us-central1.gcp.commercetools.com',
33+
'api.australia-southeast1.gcp.commercetools.com'
34+
];
35+
36+
return allowedHosts.includes(parsedUrl.hostname) &&
37+
(parsedUrl.protocol === 'https:');
38+
} catch (error) {
39+
return false;
40+
}
41+
}
42+
43+
function validateInput(input, type) {
44+
if (!input) return false;
45+
46+
switch (type) {
47+
case 'uuid':
48+
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(input);
49+
case 'cartId':
50+
// Allow UUID or commercetools ID format
51+
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(input) ||
52+
/^[a-zA-Z0-9-_]{1,256}$/.test(input);
53+
case 'currency':
54+
return /^[A-Z]{3}$/.test(input);
55+
case 'country':
56+
return /^[A-Z]{2}$/.test(input);
57+
case 'locale':
58+
return /^[a-z]{2}(-[A-Z]{2})?$/.test(input);
59+
case 'string':
60+
return typeof input === 'string' && input.length <= 1000;
61+
case 'number':
62+
return typeof input === 'number' && input >= 0 && input <= 10000;
63+
default:
64+
return false;
65+
}
66+
}
67+
1868
// commercetools client setup
1969
const projectKey = process.env.CTP_PROJECT_KEY;
2070
const scopes = process.env.CTP_SCOPES.split(' ');
@@ -49,8 +99,17 @@ app.post('/api/checkout/session', async (req, res) => {
4999
try {
50100
const { cartId, returnUrl, locale = 'en' } = req.body;
51101

52-
if (!cartId) {
53-
return res.status(400).json({ error: 'Cart ID is required' });
102+
// Input validation for DoS protection
103+
if (!cartId || !validateInput(cartId, 'cartId')) {
104+
return res.status(400).json({ error: 'Valid Cart ID is required' });
105+
}
106+
107+
if (locale && !validateInput(locale, 'locale')) {
108+
return res.status(400).json({ error: 'Invalid locale format' });
109+
}
110+
111+
if (returnUrl && !validateInput(returnUrl, 'string')) {
112+
return res.status(400).json({ error: 'Invalid return URL format' });
54113
}
55114

56115
console.log(`Creating checkout session for cart: ${cartId}`);
@@ -83,8 +142,13 @@ app.post('/api/checkout/session', async (req, res) => {
83142
console.log('Creating checkout session with correct structure:', checkoutSessionData);
84143

85144
// Call commercetools Checkout API to create session
86-
// Using the correct endpoint structure
145+
// Using the correct endpoint structure with SSRF protection
87146
const sessionApiUrl = process.env.SESSION_API_URL || 'https://session.europe-west1.gcp.commercetools.com';
147+
148+
if (!validateCommercetoolsUrl(sessionApiUrl)) {
149+
return res.status(400).json({ error: 'Invalid session API URL' });
150+
}
151+
88152
const checkoutResponse = await fetch(
89153
`${sessionApiUrl}/${projectKey}/sessions`,
90154
{
@@ -139,7 +203,24 @@ app.post('/api/checkout/session', async (req, res) => {
139203
// Sample cart creation endpoint for demo
140204
app.post('/api/cart/create', async (req, res) => {
141205
try {
142-
const { currency = 'USD', country = 'US' } = req.body;
206+
const { currency = 'USD', country = 'US', productId, quantity = 1 } = req.body;
207+
208+
// Input validation
209+
if (currency && !validateInput(currency, 'currency')) {
210+
return res.status(400).json({ error: 'Invalid currency format' });
211+
}
212+
213+
if (country && !validateInput(country, 'country')) {
214+
return res.status(400).json({ error: 'Invalid country format' });
215+
}
216+
217+
if (productId && !validateInput(productId, 'string')) {
218+
return res.status(400).json({ error: 'Invalid product ID format' });
219+
}
220+
221+
if (typeof quantity !== 'undefined' && !validateInput(quantity, 'number')) {
222+
return res.status(400).json({ error: 'Invalid quantity' });
223+
}
143224

144225
console.log('Creating sample cart...');
145226

@@ -152,9 +233,9 @@ app.post('/api/cart/create', async (req, res) => {
152233
country: country,
153234
lineItems: [
154235
{
155-
productId: req.body.productId || await getSampleProductId(),
236+
productId: productId,
156237
variantId: 1,
157-
quantity: req.body.quantity || 1,
238+
quantity: quantity,
158239
}
159240
]
160241
}
@@ -284,26 +365,6 @@ async function getAccessToken() {
284365
return tokenData.access_token;
285366
}
286367

287-
async function getSampleProductId() {
288-
try {
289-
const products = await apiRoot
290-
.productProjections()
291-
.search()
292-
.get({
293-
queryArgs: { limit: 1 }
294-
})
295-
.execute();
296-
297-
if (products.body.results.length > 0) {
298-
return products.body.results[0].id;
299-
}
300-
301-
throw new Error('No products found in project');
302-
} catch (error) {
303-
console.error('Error getting sample product:', error);
304-
throw error;
305-
}
306-
}
307368

308369
// Start server
309370
app.listen(PORT, () => {

0 commit comments

Comments
 (0)