Skip to content

Commit b872d7e

Browse files
authored
fix: Prevent periods in device id check (#405)
* Add validateDeviceId function * Add device id check during init * add tests * fix test * deviceId includes nit * extra test
1 parent d06ae51 commit b872d7e

File tree

3 files changed

+57
-3
lines changed

3 files changed

+57
-3
lines changed

src/amplitude-client.js

+10-3
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,12 @@ AmplitudeClient.prototype.init = function init(apiKey, opt_userId, opt_config, o
148148
this._pendingReadStorage = true;
149149

150150
const initFromStorage = (storedDeviceId) => {
151+
if (opt_config && opt_config.deviceId && !utils.validateDeviceId(opt_config.deviceId)) {
152+
utils.log.error(
153+
`Invalid device ID rejected. Randomly generated UUID will be used instead of "${opt_config.deviceId}"`,
154+
);
155+
delete opt_config.deviceId;
156+
}
151157
this.options.deviceId = this._getInitialDeviceId(opt_config && opt_config.deviceId, storedDeviceId);
152158
this.options.userId =
153159
(type(opt_userId) === 'string' && !utils.isEmptyString(opt_userId) && opt_userId) ||
@@ -927,8 +933,9 @@ AmplitudeClient.prototype.regenerateDeviceId = function regenerateDeviceId() {
927933
};
928934

929935
/**
930-
* Sets a custom deviceId for current user. Note: this is not recommended unless you know what you are doing
931-
* (like if you have your own system for managing deviceIds). Make sure the deviceId you set is sufficiently unique
936+
* Sets a custom deviceId for current user. **Values may not have `.` inside them**
937+
* Note: this is not recommended unless you know what you are doing (like if you have your own system for managing deviceIds).
938+
* Make sure the deviceId you set is sufficiently unique
932939
* (we recommend something like a UUID - see src/uuid.js for an example of how to generate) to prevent conflicts with other devices in our system.
933940
* @public
934941
* @param {string} deviceId - custom deviceId for current user.
@@ -939,7 +946,7 @@ AmplitudeClient.prototype.setDeviceId = function setDeviceId(deviceId) {
939946
return this._q.push(['setDeviceId'].concat(Array.prototype.slice.call(arguments, 0)));
940947
}
941948

942-
if (!utils.validateInput(deviceId, 'deviceId', 'string')) {
949+
if (!utils.validateDeviceId(deviceId)) {
943950
return;
944951
}
945952

src/utils.js

+12
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,17 @@ var validateInput = function validateInput(input, name, expectedType) {
9797
return true;
9898
};
9999

100+
const validateDeviceId = function validateDeviceId(deviceId) {
101+
if (!validateInput(deviceId, 'deviceId', 'string')) {
102+
return false;
103+
}
104+
if (deviceId.includes('.')) {
105+
log.error(`Device IDs may not contain '.' characters. Value will be ignored: "${deviceId}"`);
106+
return false;
107+
}
108+
return true;
109+
};
110+
100111
// do some basic sanitization and type checking, also catch property dicts with more than 1000 key/value pairs
101112
var validateProperties = function validateProperties(properties) {
102113
var propsType = type(properties);
@@ -257,4 +268,5 @@ export default {
257268
validateGroups,
258269
validateInput,
259270
validateProperties,
271+
validateDeviceId,
260272
};

test/amplitude-client.js

+35
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,26 @@ describe('AmplitudeClient', function () {
285285
amplitude._getUrlParams.restore();
286286
});
287287

288+
it('should reject invalid device ids that contain periods', function () {
289+
const spyErrorWarning = sinon.spy(utils.log, 'error');
290+
const badDeviceId = 'bad.device.id';
291+
amplitude.init(apiKey, null, { deviceId: badDeviceId });
292+
293+
assert.isTrue(
294+
spyErrorWarning.calledWith(
295+
`Device IDs may not contain '.' characters. Value will be ignored: "${badDeviceId}"`,
296+
),
297+
);
298+
assert.isTrue(
299+
spyErrorWarning.calledWith(
300+
`Invalid device ID rejected. Randomly generated UUID will be used instead of "${badDeviceId}"`,
301+
),
302+
);
303+
assert.notEqual(amplitude.options.deviceId, badDeviceId);
304+
305+
spyErrorWarning.restore();
306+
});
307+
288308
it('should load device id from the cookie', function () {
289309
// deviceId and sequenceNumber not set, init should load value from localStorage
290310
var cookieData = {
@@ -1048,6 +1068,21 @@ describe('AmplitudeClient', function () {
10481068
var stored = amplitude._metadataStorage.load();
10491069
assert.propertyVal(stored, 'deviceId', 'deviceId');
10501070
});
1071+
1072+
it('should not take periods in deviceId', function () {
1073+
const spyErrorWarning = sinon.spy(utils.log, 'error');
1074+
amplitude.init(apiKey, null, { deviceId: 'fakeDeviceId' });
1075+
const badDeviceId = 'bad.device.id';
1076+
amplitude.setDeviceId(badDeviceId);
1077+
var stored = amplitude._metadataStorage.load();
1078+
assert.propertyVal(stored, 'deviceId', 'fakeDeviceId');
1079+
assert.isTrue(
1080+
spyErrorWarning.calledWith(
1081+
`Device IDs may not contain '.' characters. Value will be ignored: "${badDeviceId}"`,
1082+
),
1083+
);
1084+
spyErrorWarning.restore();
1085+
});
10511086
});
10521087

10531088
describe('resetSessionId', function () {

0 commit comments

Comments
 (0)