Skip to content
This repository was archived by the owner on May 28, 2023. It is now read-only.

Commit aead6d8

Browse files
author
Tomasz Kostuch
authored
Merge pull request #488 from gibkigonzo/bugfix/sec-fixes
add validation for user profile update, add `getToken`
2 parents 6487a8b + b5f92c3 commit aead6d8

File tree

8 files changed

+217
-39
lines changed

8 files changed

+217
-39
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010

1111
- Fix default value for `maxAgeForResponse` - @lauraseidler (#485)
1212

13+
## [1.12.2] - UNRELEASED
14+
15+
### Added
16+
17+
- Added validation for user profile update.
18+
- add `getToken` to handle getting token from header
19+
20+
### Fixed
21+
1322
## [1.12.1] - 2020.06.22
1423

1524
### Added

config/default.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
"invalidateCacheForwardUrl": "http://localhost:3000/invalidate?key=aeSu7aip&tag=",
1313
"showErrorStack": false
1414
},
15+
"users": {
16+
"tokenInHeader": false
17+
},
1518
"orders": {
1619
"useServerQueue": false
1720
},

src/api/cart.ts

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { apiStatus, apiError } from '../lib/util';
1+
import { apiStatus, apiError, getToken } from '../lib/util';
22
import { Router } from 'express';
33
import PlatformFactory from '../platform/factory';
44

@@ -13,11 +13,12 @@ export default ({ config, db }) => {
1313

1414
/**
1515
* POST create a cart
16-
* req.query.token - user token
16+
* req.query.token | req.headers.authorization - user token
1717
*/
1818
cartApi.post('/create', (req, res) => {
1919
const cartProxy = _getProxy(req)
20-
cartProxy.create(req.query.token).then((result) => {
20+
const token = getToken(req)
21+
cartProxy.create(token).then((result) => {
2122
apiStatus(res, result, 200);
2223
}).catch(err => {
2324
apiError(res, err);
@@ -26,18 +27,19 @@ export default ({ config, db }) => {
2627

2728
/**
2829
* POST update or add the cart item
29-
* req.query.token - user token
30+
* req.query.token | req.headers.authorization - user token
3031
* body.cartItem: {
3132
* sku: orderItem.sku,
3233
* qty: orderItem.qty,
3334
* quoteId: cartKey}
3435
*/
3536
cartApi.post('/update', (req, res) => {
3637
const cartProxy = _getProxy(req)
38+
const token = getToken(req)
3739
if (!req.body.cartItem) {
3840
return apiStatus(res, 'No cartItem element provided within the request body', 500)
3941
}
40-
cartProxy.update(req.query.token, req.query.cartId ? req.query.cartId : null, req.body.cartItem).then((result) => {
42+
cartProxy.update(token, req.query.cartId ? req.query.cartId : null, req.body.cartItem).then((result) => {
4143
apiStatus(res, result, 200);
4244
}).catch(err => {
4345
apiError(res, err);
@@ -46,16 +48,17 @@ export default ({ config, db }) => {
4648

4749
/**
4850
* POST apply the coupon code
49-
* req.query.token - user token
51+
* req.query.token | req.headers.authorization - user token
5052
* req.query.cartId - cart Ids
5153
* req.query.coupon - coupon
5254
*/
5355
cartApi.post('/apply-coupon', (req, res) => {
5456
const cartProxy = _getProxy(req)
57+
const token = getToken(req)
5558
if (!req.query.coupon) {
5659
return apiStatus(res, 'No coupon code provided', 500)
5760
}
58-
cartProxy.applyCoupon(req.query.token, req.query.cartId ? req.query.cartId : null, req.query.coupon).then((result) => {
61+
cartProxy.applyCoupon(token, req.query.cartId ? req.query.cartId : null, req.query.coupon).then((result) => {
5962
apiStatus(res, result, 200);
6063
}).catch(err => {
6164
apiError(res, err);
@@ -64,12 +67,13 @@ export default ({ config, db }) => {
6467

6568
/**
6669
* POST remove the coupon code
67-
* req.query.token - user token
70+
* req.query.token | req.headers.authorization - user token
6871
* req.query.cartId - cart Ids
6972
*/
7073
cartApi.post('/delete-coupon', (req, res) => {
7174
const cartProxy = _getProxy(req)
72-
cartProxy.deleteCoupon(req.query.token, req.query.cartId ? req.query.cartId : null).then((result) => {
75+
const token = getToken(req)
76+
cartProxy.deleteCoupon(token, req.query.cartId ? req.query.cartId : null).then((result) => {
7377
apiStatus(res, result, 200);
7478
}).catch(err => {
7579
apiError(res, err);
@@ -78,12 +82,13 @@ export default ({ config, db }) => {
7882

7983
/**
8084
* GET get the applied coupon code
81-
* req.query.token - user token
85+
* req.query.token | req.headers.authorization - user token
8286
* req.query.cartId - cart Ids
8387
*/
8488
cartApi.get('/coupon', (req, res) => {
8589
const cartProxy = _getProxy(req)
86-
cartProxy.getCoupon(req.query.token, req.query.cartId ? req.query.cartId : null).then((result) => {
90+
const token = getToken(req)
91+
cartProxy.getCoupon(token, req.query.cartId ? req.query.cartId : null).then((result) => {
8792
apiStatus(res, result, 200);
8893
}).catch(err => {
8994
apiError(res, err);
@@ -92,18 +97,19 @@ export default ({ config, db }) => {
9297

9398
/**
9499
* POST delete the cart item
95-
* req.query.token - user token
100+
* req.query.token | req.headers.authorization - user token
96101
* body.cartItem: {
97102
* sku: orderItem.sku,
98103
* qty: orderItem.qty,
99104
* quoteId: cartKey}
100105
*/
101106
cartApi.post('/delete', (req, res) => {
102107
const cartProxy = _getProxy(req)
108+
const token = getToken(req)
103109
if (!req.body.cartItem) {
104110
return apiStatus(res, 'No cartItem element provided within the request body', 500)
105111
}
106-
cartProxy.delete(req.query.token, req.query.cartId ? req.query.cartId : null, req.body.cartItem).then((result) => {
112+
cartProxy.delete(token, req.query.cartId ? req.query.cartId : null, req.body.cartItem).then((result) => {
107113
apiStatus(res, result, 200);
108114
}).catch(err => {
109115
apiError(res, err);
@@ -112,13 +118,14 @@ export default ({ config, db }) => {
112118

113119
/**
114120
* GET pull the whole cart as it's currently se server side
115-
* req.query.token - user token
121+
* req.query.token | req.headers.authorization - user token
116122
* req.query.cartId - cartId
117123
*/
118124
cartApi.get('/pull', (req, res) => {
119125
const cartProxy = _getProxy(req)
126+
const token = getToken(req)
120127
res.setHeader('Cache-Control', 'no-cache, no-store');
121-
cartProxy.pull(req.query.token, req.query.cartId ? req.query.cartId : null, req.body).then((result) => {
128+
cartProxy.pull(token, req.query.cartId ? req.query.cartId : null, req.body).then((result) => {
122129
apiStatus(res, result, 200);
123130
}).catch(err => {
124131
apiError(res, err);
@@ -127,13 +134,14 @@ export default ({ config, db }) => {
127134

128135
/**
129136
* GET totals the cart totals
130-
* req.query.token - user token
137+
* req.query.token | req.headers.authorization - user token
131138
* req.query.cartId - cartId
132139
*/
133140
cartApi.get('/totals', (req, res) => {
134141
const cartProxy = _getProxy(req)
142+
const token = getToken(req)
135143
res.setHeader('Cache-Control', 'no-cache, no-store');
136-
cartProxy.totals(req.query.token, req.query.cartId ? req.query.cartId : null, req.body).then((result) => {
144+
cartProxy.totals(token, req.query.cartId ? req.query.cartId : null, req.body).then((result) => {
137145
apiStatus(res, result, 200);
138146
}).catch(err => {
139147
apiError(res, err);
@@ -142,17 +150,18 @@ export default ({ config, db }) => {
142150

143151
/**
144152
* POST /shipping-methods - available shipping methods for a given address
145-
* req.query.token - user token
153+
* req.query.token | req.headers.authorization - user token
146154
* req.query.cartId - cart ID if user is logged in, cart token if not
147155
* req.body.address - shipping address object
148156
*/
149157
cartApi.post('/shipping-methods', (req, res) => {
150158
const cartProxy = _getProxy(req)
159+
const token = getToken(req)
151160
res.setHeader('Cache-Control', 'no-cache, no-store');
152161
if (!req.body.address) {
153162
return apiStatus(res, 'No address element provided within the request body', 500)
154163
}
155-
cartProxy.getShippingMethods(req.query.token, req.query.cartId ? req.query.cartId : null, req.body.address).then((result) => {
164+
cartProxy.getShippingMethods(token, req.query.cartId ? req.query.cartId : null, req.body.address).then((result) => {
156165
apiStatus(res, result, 200);
157166
}).catch(err => {
158167
apiError(res, err);
@@ -161,13 +170,14 @@ export default ({ config, db }) => {
161170

162171
/**
163172
* GET /payment-methods - available payment methods
164-
* req.query.token - user token
173+
* req.query.token | req.headers.authorization - user token
165174
* req.query.cartId - cart ID if user is logged in, cart token if not
166175
*/
167176
cartApi.get('/payment-methods', (req, res) => {
168177
const cartProxy = _getProxy(req)
178+
const token = getToken(req)
169179
res.setHeader('Cache-Control', 'no-cache, no-store');
170-
cartProxy.getPaymentMethods(req.query.token, req.query.cartId ? req.query.cartId : null).then((result) => {
180+
cartProxy.getPaymentMethods(token, req.query.cartId ? req.query.cartId : null).then((result) => {
171181
apiStatus(res, result, 200);
172182
}).catch(err => {
173183
apiError(res, err);
@@ -176,17 +186,18 @@ export default ({ config, db }) => {
176186

177187
/**
178188
* POST /shipping-information - set shipping information for collecting cart totals after address changed
179-
* req.query.token - user token
189+
* req.query.token | req.headers.authorization - user token
180190
* req.query.cartId - cart ID if user is logged in, cart token if not
181191
* req.body.addressInformation - shipping address object
182192
*/
183193
cartApi.post('/shipping-information', (req, res) => {
184194
const cartProxy = _getProxy(req)
195+
const token = getToken(req)
185196
res.setHeader('Cache-Control', 'no-cache, no-store');
186197
if (!req.body.addressInformation) {
187198
return apiStatus(res, 'No address element provided within the request body', 500)
188199
}
189-
cartProxy.setShippingInformation(req.query.token, req.query.cartId ? req.query.cartId : null, req.body).then((result) => {
200+
cartProxy.setShippingInformation(token, req.query.cartId ? req.query.cartId : null, req.body).then((result) => {
190201
apiStatus(res, result, 200);
191202
}).catch(err => {
192203
apiError(res, err);
@@ -195,17 +206,18 @@ export default ({ config, db }) => {
195206

196207
/**
197208
* POST /collect-totals - collect cart totals after shipping address changed
198-
* req.query.token - user token
209+
* req.query.token | req.headers.authorization - user token
199210
* req.query.cartId - cart ID if user is logged in, cart token if not
200211
* req.body.shippingMethod - shipping and payment methods object
201212
*/
202213
cartApi.post('/collect-totals', (req, res) => {
203214
const cartProxy = _getProxy(req)
215+
const token = getToken(req)
204216
res.setHeader('Cache-Control', 'no-cache, no-store');
205217
if (!req.body.methods) {
206218
return apiStatus(res, 'No shipping and payment methods element provided within the request body', 500)
207219
}
208-
cartProxy.collectTotals(req.query.token, req.query.cartId ? req.query.cartId : null, req.body.methods).then((result) => {
220+
cartProxy.collectTotals(token, req.query.cartId ? req.query.cartId : null, req.body.methods).then((result) => {
209221
apiStatus(res, result, 200);
210222
}).catch(err => {
211223
apiError(res, err);

src/api/user.ts

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { apiStatus, encryptToken, decryptToken, apiError } from '../lib/util';
1+
import { apiStatus, encryptToken, decryptToken, apiError, getToken } from '../lib/util';
22
import { Router } from 'express';
33
import PlatformFactory from '../platform/factory';
44
import jwt from 'jwt-simple';
@@ -21,6 +21,19 @@ function addUserGroupToken (config, result) {
2121
result.groupToken = jwt.encode(data, config.authHashSecret ? config.authHashSecret : config.objHashSecret)
2222
}
2323

24+
function validateAddresses (currentAddresses = [], newAddresses = []) {
25+
for (let address of newAddresses) {
26+
if (!address.customer_id && !address.id) {
27+
continue
28+
} else {
29+
const existingAddress = currentAddresses.find((existingAddress) => existingAddress.id === address.id && existingAddress.customer_id === address.customer_id)
30+
if (!existingAddress) {
31+
return 'Provided invalid address.id or address.customer_id'
32+
}
33+
}
34+
}
35+
}
36+
2437
export default ({config, db}) => {
2538
let userApi = Router();
2639

@@ -139,7 +152,8 @@ export default ({config, db}) => {
139152
*/
140153
userApi.get('/me', (req, res) => {
141154
const userProxy = _getProxy(req)
142-
userProxy.me(req.query.token).then((result) => {
155+
const token = getToken(req)
156+
userProxy.me(token).then((result) => {
143157
addUserGroupToken(config, result)
144158
apiStatus(res, result, 200);
145159
}).catch(err => {
@@ -152,8 +166,9 @@ export default ({config, db}) => {
152166
*/
153167
userApi.get('/order-history', (req, res) => {
154168
const userProxy = _getProxy(req)
169+
const token = getToken(req)
155170
userProxy.orderHistory(
156-
req.query.token,
171+
token,
157172
req.query.pageSize || 20,
158173
req.query.currentPage || 1
159174
).then((result) => {
@@ -166,12 +181,12 @@ export default ({config, db}) => {
166181
/**
167182
* POST for updating user
168183
*/
169-
userApi.post('/me', (req, res) => {
184+
userApi.post('/me', async (req, res) => {
170185
const ajv = new Ajv();
171-
const userProfileSchema = require('../models/userProfile.schema.json')
186+
const userProfileSchema = require('../models/userProfileUpdate.schema.json')
172187
let userProfileSchemaExtension = {};
173-
if (fs.existsSync(path.resolve(__dirname, '../models/userProfile.schema.extension.json'))) {
174-
userProfileSchemaExtension = require('../models/userProfile.schema.extension.json');
188+
if (fs.existsSync(path.resolve(__dirname, '../models/userProfileUpdate.schema.extension.json'))) {
189+
userProfileSchemaExtension = require('../models/userProfileUpdate.schema.extension.json');
175190
}
176191
const validate = ajv.compile(merge(userProfileSchema, userProfileSchemaExtension))
177192

@@ -186,20 +201,40 @@ export default ({config, db}) => {
186201
}
187202

188203
const userProxy = _getProxy(req)
189-
userProxy.update({token: req.query.token, body: req.body}).then((result) => {
204+
const token = getToken(req)
205+
206+
try {
207+
let { website_id, addresses } = await userProxy.me(token)
208+
const { customer } = req.body
209+
210+
const validationMessage = validateAddresses(addresses, customer.addresses)
211+
if (validationMessage) {
212+
return apiStatus(res, validationMessage, 403)
213+
}
214+
215+
const result = await userProxy.update({
216+
token,
217+
body: {
218+
customer: {
219+
...customer,
220+
website_id
221+
}
222+
}
223+
})
190224
addUserGroupToken(config, result)
191225
apiStatus(res, result, 200)
192-
}).catch(err => {
226+
} catch (err) {
193227
apiStatus(res, err, 500)
194-
})
228+
}
195229
})
196230

197231
/**
198232
* POST for changing user's password (old, keep for backward compatibility)
199233
*/
200234
userApi.post('/changePassword', (req, res) => {
201235
const userProxy = _getProxy(req)
202-
userProxy.changePassword({ token: req.query.token, body: req.body }).then((result) => {
236+
const token = getToken(req)
237+
userProxy.changePassword({ token, body: req.body }).then((result) => {
203238
apiStatus(res, result, 200)
204239
}).catch(err => {
205240
apiStatus(res, err, 500)
@@ -211,7 +246,8 @@ export default ({config, db}) => {
211246
*/
212247
userApi.post('/change-password', (req, res) => {
213248
const userProxy = _getProxy(req)
214-
userProxy.changePassword({token: req.query.token, body: req.body}).then((result) => {
249+
const token = getToken(req)
250+
userProxy.changePassword({token, body: req.body}).then((result) => {
215251
apiStatus(res, result, 200)
216252
}).catch(err => {
217253
apiStatus(res, err, 500)

src/lib/util.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,9 @@ export function decryptToken (textToken, secret) {
109109
dec += decipher.final('utf8');
110110
return dec;
111111
}
112+
113+
export function getToken (req) {
114+
return config.users.tokenInHeader
115+
? (req.headers.authorization || '').replace('Bearer ', '')
116+
: req.query.token
117+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}

0 commit comments

Comments
 (0)