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

Commit 1354475

Browse files
rashkovMike Rashkovskykonraddysput
authored
Machine id fix (#37)
* Vendor machine ID lib to fix issue #34 * Fix up win32 machineId() case * Fix expose() for win32 case * Fallback to generating ID when unable to get one * Use hostname as a fall-back when machine id is undefined (#36) * Use hostname as fall-back when machine id is undefined * Use random bytes when hostname is undefined * Version bump Co-authored-by: Mike Rashkovsky <[email protected]> Co-authored-by: Konrad Dysput <[email protected]>
1 parent 25cef80 commit 1354475

File tree

6 files changed

+251
-9
lines changed

6 files changed

+251
-9
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Backtrace Node Release Notes
22

3+
## Version 1.1.1
4+
5+
- fixed an issue with node machine id (https://github.com/backtrace-labs/backtrace-node/issues/34)
6+
37
## Version 1.1.0
48

59
- improved deduplication: the relative library path is used instead of absolute path for purposes of deduplication.

package-lock.json

+13-5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "backtrace-node",
3-
"version": "1.1.0",
3+
"version": "1.1.1",
44
"description": "Backtrace error reporting tool",
55
"main": "./lib/index.js",
66
"types": "./lib/index.d.ts",
@@ -33,7 +33,7 @@
3333
"axios": "^0.21.1",
3434
"form-data": "^2.3.3",
3535
"json-stringify-safe": "^5.0.1",
36-
"node-machine-id": "^1.1.10",
36+
"native-reg": "^0.3.5",
3737
"source-scan": "~1.0.1",
3838
"tslib": "^1.10.0"
3939
},

source/helpers/machineId.ts

+138
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/*
2+
* The MIT License (MIT)
3+
*
4+
* Copyright (c) 2016 Aleksandr Komlev
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all
14+
* copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*/
24+
25+
/* Based on source: https://github.com/Nokel81/node-machine-id/commit/ee2d03efca9e9ccb363e850093a2e6654137275c */
26+
27+
import { execSync } from 'child_process';
28+
import { createHash, pseudoRandomBytes } from 'crypto';
29+
import * as reg from 'native-reg';
30+
import { hostname } from 'os';
31+
32+
type SupportedPlatforms = 'darwin' | 'linux' | 'freebsd' | 'win32';
33+
const supportedPlatforms = ['darwin', 'linux', 'freebsd', 'win32'];
34+
35+
export function getPlatform() {
36+
const platform: SupportedPlatforms = process.platform as SupportedPlatforms;
37+
if (supportedPlatforms.indexOf(platform) === -1) {
38+
return null;
39+
}
40+
return platform;
41+
}
42+
43+
const platform: SupportedPlatforms | null = getPlatform();
44+
45+
const guid: { [index: string]: string } = {
46+
darwin: 'ioreg -rd1 -c IOPlatformExpertDevice',
47+
linux: '( cat /var/lib/dbus/machine-id /etc/machine-id 2> /dev/null || hostname ) | head -n 1 || :',
48+
freebsd: 'kenv -q smbios.system.uuid || sysctl -n kern.hostuuid',
49+
};
50+
51+
function hash(str: string): string {
52+
return createHash('sha256').update(str).digest('hex');
53+
}
54+
55+
function expose(result: string): string | null {
56+
switch (platform) {
57+
case 'darwin':
58+
return result
59+
.split('IOPlatformUUID')[1]
60+
.split('\n')[0]
61+
.replace(/\=|\s+|\"/gi, '')
62+
.toLowerCase();
63+
case 'win32':
64+
return result
65+
.toString()
66+
.replace(/\r+|\n+|\s+/gi, '')
67+
.toLowerCase();
68+
case 'linux':
69+
return result
70+
.toString()
71+
.replace(/\r+|\n+|\s+/gi, '')
72+
.toLowerCase();
73+
case 'freebsd':
74+
return result
75+
.toString()
76+
.replace(/\r+|\n+|\s+/gi, '')
77+
.toLowerCase();
78+
default:
79+
return null;
80+
}
81+
}
82+
83+
function windowsMachineId(): string | null {
84+
const regVal = reg.getValue(reg.HKEY.LOCAL_MACHINE, 'SOFTWARE\\Microsoft\\Cryptography', 'MachineGuid');
85+
if (regVal) {
86+
return expose(regVal.toString());
87+
} else {
88+
return null;
89+
}
90+
}
91+
92+
function nonWindowsMachineId(): string | null {
93+
try {
94+
if (platform !== null && guid[platform]) {
95+
return expose(execSync(guid[platform]).toString());
96+
} else {
97+
return null;
98+
}
99+
} catch (_e) {
100+
return null;
101+
}
102+
}
103+
104+
export function generateUuid(name: string = hostname()): string {
105+
const uuidSize = 16;
106+
const bytes = name
107+
? Buffer.concat([Buffer.from(name, 'utf8'), Buffer.alloc(uuidSize)], uuidSize)
108+
: pseudoRandomBytes(uuidSize);
109+
return (
110+
bytes.slice(0, 4).toString('hex') +
111+
'-' +
112+
bytes.slice(4, 6).toString('hex') +
113+
'-' +
114+
bytes.slice(6, 8).toString('hex') +
115+
'-' +
116+
bytes.slice(8, 10).toString('hex') +
117+
'-' +
118+
bytes.slice(10, 16).toString('hex')
119+
);
120+
}
121+
122+
/**
123+
* This function gets the OS native UUID/GUID synchronously, hashed by default.
124+
* @param {boolean} [original=false] If true return original value of machine id, otherwise return hashed value (sha - 256)
125+
*/
126+
export function machineIdSync(original: boolean = false): string {
127+
let id: string | null = null;
128+
if (platform === 'win32') {
129+
id = windowsMachineId();
130+
} else if (platform !== null) {
131+
id = nonWindowsMachineId();
132+
}
133+
if (id === null) {
134+
id = generateUuid();
135+
}
136+
137+
return original ? id : hash(id);
138+
}

source/model/backtraceReport.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { pseudoRandomBytes } from 'crypto';
2-
import { machineIdSync } from 'node-machine-id';
32
import * as os from 'os';
3+
import { machineIdSync } from '../helpers/machineId';
44
import { readModule } from '../helpers/moduleResolver';
55
import { readMemoryInformation, readProcessStatus } from '../helpers/processHelper';
66
import { IBacktraceData } from './backtraceData';
@@ -42,7 +42,7 @@ export class BacktraceReport {
4242
// Backtrace-ndoe name
4343
public readonly agent = 'backtrace-node';
4444
// Backtrace-node version
45-
public readonly agentVersion = '1.1.0';
45+
public readonly agentVersion = '1.1.1';
4646
// main thread name
4747
public readonly mainThread = 'main';
4848

test/machineIdTest.ts

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
* The MIT License (MIT)
3+
*
4+
* Copyright (c) 2016 Aleksandr Komlev
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all
14+
* copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*/
24+
25+
/* Based on source: https://github.com/Nokel81/node-machine-id/commit/ee2d03efca9e9ccb363e850093a2e6654137275c */
26+
27+
import { assert } from 'chai';
28+
import { generateUuid, getPlatform, machineIdSync } from '../source/helpers/machineId';
29+
30+
let platform = getPlatform(),
31+
originalPattern = {
32+
darwin: /^[0-9,A-z]{8}-[0-9,A-z]{4}-[0-9,A-z]{4}-[0-9,A-z]{4}-[0-9,A-z]{12}$/,
33+
win32: /^[0-9,A-z]{8}-[0-9,A-z]{4}-[0-9,A-z]{4}-[0-9,A-z]{4}-[0-9,A-z]{12}$/,
34+
linux: /^[0-9,A-z]{32}$/,
35+
freebsd: /^[0-9,A-z]{8}-[0-9,A-z]{4}-[0-9,A-z]{4}-[0-9,A-z]{4}-[0-9,A-z]{12}$/,
36+
},
37+
hashPattern = /^[0-9,A-z]{64}$/;
38+
39+
// these tests need to be run on each of the platforms above to test this in a comprehensive way
40+
describe('Machine ID tests', () => {
41+
describe('Sync call (original=true): machineIdSync(true)', function () {
42+
it('should return original unique id', () => {
43+
if (platform === null) {
44+
throw 'null platform exception';
45+
}
46+
assert.match(machineIdSync(true), originalPattern[platform]);
47+
});
48+
});
49+
50+
describe('Sync call: machineIdSync()', function () {
51+
it('should return unique sha256-hash', () => {
52+
assert.match(machineIdSync(), hashPattern);
53+
});
54+
});
55+
56+
describe('Uuid generation tests - based on the host name', () => {
57+
it('should generate correctly uuid based on the host name that has less than 16 bytes', () => {
58+
const testOsName = 'foo';
59+
const expectedUuid = `${Buffer.from(testOsName, 'utf8').toString('hex')}00-0000-0000-0000-000000000000`;
60+
61+
const generatedUuid = generateUuid(testOsName);
62+
63+
assert.equal(generatedUuid, expectedUuid);
64+
});
65+
66+
it('should generate correctly uuid based on the host name that has more than 16 bytes', () => {
67+
const testOsName = 'abcdabcdabcdabcdabcd123124nuabgyiagbygvba';
68+
const expectedUuid = '61626364-6162-6364-6162-636461626364';
69+
70+
const generatedUuid = generateUuid(testOsName);
71+
72+
assert.equal(generatedUuid, expectedUuid);
73+
});
74+
75+
it('should generate correctly uuid based on the host name that is undefined', () => {
76+
const testOsName = undefined;
77+
78+
const generatedUuid = generateUuid(testOsName);
79+
80+
assert.isDefined(generatedUuid);
81+
});
82+
83+
it('should generate the same uuid over different user sessions', () => {
84+
const testOsName = 'abcd';
85+
86+
const firstSessionUuid = generateUuid(testOsName);
87+
const secondSessionUuid = generateUuid(testOsName);
88+
89+
assert.equal(firstSessionUuid, secondSessionUuid);
90+
});
91+
});
92+
});

0 commit comments

Comments
 (0)