diff --git a/packages/node/src/attributes/MachineIdentitfierAttributeProvider.ts b/packages/node/src/attributes/MachineIdentitfierAttributeProvider.ts index fac11035..581b4032 100644 --- a/packages/node/src/attributes/MachineIdentitfierAttributeProvider.ts +++ b/packages/node/src/attributes/MachineIdentitfierAttributeProvider.ts @@ -1,5 +1,9 @@ import { BacktraceAttributeProvider, IdGenerator } from '@backtrace/sdk-core'; import { execSync } from 'child_process'; +import crypto from 'crypto'; + +const UUID_REGEX = /[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/i; +const DASHLESS_UUID_REGEX = /[a-f0-9]{32}/i; export class MachineIdentitfierAttributeProvider implements BacktraceAttributeProvider { public static readonly SUPPORTED_PLATFORMS = ['win32', 'darwin', 'linux', 'freebsd']; @@ -15,21 +19,24 @@ export class MachineIdentitfierAttributeProvider implements BacktraceAttributePr public get type(): 'scoped' | 'dynamic' { return 'scoped'; } + public get(): Record { - const guid = this.generateGuid() ?? IdGenerator.uuid(); + let machineId = this.getMachineId(); + if (machineId) { + machineId = this.getValidGuid(machineId); + } else { + machineId = IdGenerator.uuid(); + } return { - [this.MACHINE_ID_ATTRIBUTE]: guid, + [this.MACHINE_ID_ATTRIBUTE]: machineId, }; } - public generateGuid() { + public getMachineId() { switch (process.platform) { case 'win32': { - return execSync(this.COMMANDS['win32']) - .toString() - .match(/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/i)?.[0] - .toLowerCase(); + return execSync(this.COMMANDS['win32']).toString().match(UUID_REGEX)?.[0].toLowerCase(); } case 'darwin': { return execSync(this.COMMANDS[process.platform]) @@ -51,4 +58,21 @@ export class MachineIdentitfierAttributeProvider implements BacktraceAttributePr } } } + + private getValidGuid(input: string) { + if (input.length === 36 && UUID_REGEX.test(input)) { + return input; + } + + if (input.length === 32 && DASHLESS_UUID_REGEX.test(input)) { + return this.addDashesToUuid(input); + } + + const sha = crypto.createHash('sha1').update(input).digest('hex').substring(0, 32); + return this.addDashesToUuid(sha); + } + + private addDashesToUuid(uuid: string) { + return `${uuid.substring(0, 8)}-${uuid.substring(8, 12)}-${uuid.substring(12, 16)}-${uuid.substring(16, 20)}-${uuid.substring(20, 32)}`; + } } diff --git a/packages/node/tests/attributes/machineIdAttributeProviderTests.spec.ts b/packages/node/tests/attributes/machineIdAttributeProviderTests.spec.ts index 351525bb..6fb94de1 100644 --- a/packages/node/tests/attributes/machineIdAttributeProviderTests.spec.ts +++ b/packages/node/tests/attributes/machineIdAttributeProviderTests.spec.ts @@ -1,3 +1,4 @@ +import crypto from 'crypto'; import { MachineIdentitfierAttributeProvider } from '../../src/attributes/MachineIdentitfierAttributeProvider.js'; describe('Machine id attribute provider test', () => { @@ -6,7 +7,7 @@ describe('Machine id attribute provider test', () => { const machineIdentifier1 = new MachineIdentitfierAttributeProvider(); const machineIdentifier2 = new MachineIdentitfierAttributeProvider(); - expect(machineIdentifier1.generateGuid()).toBe(machineIdentifier2.generateGuid()); + expect(machineIdentifier1.getMachineId()).toBe(machineIdentifier2.getMachineId()); }); } @@ -15,4 +16,36 @@ describe('Machine id attribute provider test', () => { expect(machineIdentifier.get()['guid']).toBeDefined(); }); + + it(`Should return a guid unchanged if it's a valid guid`, () => { + const machineIdentifier = new MachineIdentitfierAttributeProvider(); + const uuid = crypto.randomUUID(); + + jest.spyOn(machineIdentifier, 'getMachineId').mockReturnValue(uuid); + + expect(machineIdentifier.get()['guid']).toEqual(uuid); + }); + + it(`Should convert guid to a guid with dashes`, () => { + const machineIdentifier = new MachineIdentitfierAttributeProvider(); + const uuid = crypto.randomUUID(); + + jest.spyOn(machineIdentifier, 'getMachineId').mockReturnValue(uuid.replace(/-/g, '')); + + expect(machineIdentifier.get()['guid']).toEqual(uuid); + }); + + it(`Should create a hash of guid if it is not a proper guid`, () => { + const machineIdentifier = new MachineIdentitfierAttributeProvider(); + const guidResult = 'foo'; + const sha = crypto.createHash('sha1').update(guidResult).digest('hex').substring(0, 32); + const expected = `${sha.substring(0, 8)}-${sha.substring(8, 12)}-${sha.substring(12, 16)}-${sha.substring(16, 20)}-${sha.substring(20, 32)}`; + + // Sanity check for creating a dashed guid + expect(expected.replace(/-/g, '')).toEqual(sha); + + jest.spyOn(machineIdentifier, 'getMachineId').mockReturnValue(guidResult); + + expect(machineIdentifier.get()['guid']).toEqual(expected); + }); });