Skip to content

Commit ade508d

Browse files
committed
refactor: node constants file
1 parent 5bc6108 commit ade508d

File tree

4 files changed

+208
-173
lines changed

4 files changed

+208
-173
lines changed

src-node/constants.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
exports.SYSTEM_SETTINGS_DIR_WIN = 'C:\\Program Files\\Phoenix Code Control\\';
2+
exports.SYSTEM_SETTINGS_DIR_MAC = '/Library/Application Support/phoenix-code-control/';
3+
exports.SYSTEM_SETTINGS_DIR_LINUX = '/etc/phoenix-code-control/';

src-node/licence-device.js

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
const os = require('os');
2+
const sudo = require('@expo/sudo-prompt');
3+
const fs = require('fs');
4+
const fsPromise = require('fs').promises;
5+
const path = require('path');
6+
const { exec } = require('child_process');
7+
const { SYSTEM_SETTINGS_DIR_WIN, SYSTEM_SETTINGS_DIR_MAC, SYSTEM_SETTINGS_DIR_LINUX } = require('./constants');
8+
9+
const options = { name: 'Phoenix Code' };
10+
const licenseFileContent = JSON.stringify({});
11+
12+
function getLicensePath() {
13+
switch (os.platform()) {
14+
case 'win32':
15+
return `${SYSTEM_SETTINGS_DIR_WIN}device-license`;
16+
case 'darwin':
17+
return `${SYSTEM_SETTINGS_DIR_MAC}device-license`;
18+
case 'linux':
19+
return `${SYSTEM_SETTINGS_DIR_LINUX}device-license`;
20+
default:
21+
throw new Error(`Unsupported platform: ${os.platform()}`);
22+
}
23+
}
24+
25+
function sudoExec(command) {
26+
return new Promise((resolve, reject) => {
27+
sudo.exec(command, options, (error, stdout, stderr) => {
28+
if (error) {
29+
return reject(error);
30+
}
31+
resolve({ stdout, stderr });
32+
});
33+
});
34+
}
35+
36+
function readFileUtf8(p) {
37+
return new Promise((resolve, reject) => {
38+
fs.readFile(p, 'utf8', (err, data) => (err ? reject(err) : resolve(data)));
39+
});
40+
}
41+
42+
/**
43+
* Writes the license file in a world-readable location.
44+
* Works on Windows, macOS, and Linux.
45+
*/
46+
async function addDeviceLicense() {
47+
const targetPath = getLicensePath();
48+
let command;
49+
// we should not store any sensitive information in this file as this is world readable. we use the
50+
// device id itself as license key for that machine. the device id is not associated with any cloud credits
51+
// and all entitlements are local to device only for this threat model to work. So stolen device IDs doesn't
52+
// have any meaning.
53+
54+
if (os.platform() === 'win32') {
55+
// Windows: write file and explicitly grant Everyone read rights
56+
const dir = 'C:\\Program Files\\Phoenix Code Control';
57+
command =
58+
`powershell -Command "` +
59+
`New-Item -ItemType Directory -Force '${dir}' | Out-Null; ` +
60+
`Set-Content -Path '${targetPath}' -Value '${licenseFileContent}' -Encoding UTF8; ` +
61+
`icacls '${targetPath}' /inheritance:e /grant *S-1-1-0:RX | Out-Null"`;
62+
} else {
63+
// macOS / Linux: mkdir + write + chmod 0644 (world-readable, owner-writable)
64+
const dir = path.dirname(targetPath);
65+
command =
66+
`/bin/mkdir -p "${dir}"` +
67+
` && printf '%s' '${licenseFileContent}' > "${targetPath}"` +
68+
` && /bin/chmod 0644 "${targetPath}"`;
69+
}
70+
71+
await sudoExec(command);
72+
return targetPath;
73+
}
74+
75+
async function removeDeviceLicense() {
76+
const targetPath = getLicensePath();
77+
let command;
78+
79+
if (os.platform() === 'win32') {
80+
command = `powershell -Command "if (Test-Path '${targetPath}') { Remove-Item -Path '${targetPath}' -Force }"`;
81+
} else {
82+
command = `/bin/rm -f "${targetPath}"`;
83+
}
84+
85+
await sudoExec(command);
86+
return targetPath;
87+
}
88+
89+
async function isLicensedDevice() {
90+
const targetPath = getLicensePath();
91+
try {
92+
const data = await readFileUtf8(targetPath);
93+
JSON.parse(data.trim());
94+
return true; // currently, the existence of the file itself is flag. in future, we may choose to add more.
95+
} catch {
96+
// file missing, unreadable, or invalid JSON
97+
return false;
98+
}
99+
}
100+
101+
async function _getLinuxDeviceID() {
102+
const data = await fsPromise.readFile("/etc/machine-id", "utf8");
103+
const id = data.trim();
104+
return id || null;
105+
// throw on error to main.
106+
// no fallback, /var/lib/dbus/machine-id may need sudo in some machines
107+
}
108+
109+
/**
110+
* Get the macOS device ID (IOPlatformUUID).
111+
* @returns {Promise<string|null>}
112+
*/
113+
function _getMacDeviceID() {
114+
// to read this in mac bash, do:
115+
// #!/bin/bash
116+
// device_id=$(ioreg -rd1 -c IOPlatformExpertDevice | awk -F\" '/IOPlatformUUID/ {print $4}' | tr -d '[:space:]')
117+
// echo "$device_id"
118+
return new Promise((resolve, reject) => {
119+
exec(
120+
'ioreg -rd1 -c IOPlatformExpertDevice | grep IOPlatformUUID',
121+
{ encoding: 'utf8' },
122+
(err, stdout) => {
123+
if (err) {
124+
console.error('Failed to get Mac device ID:', err.message);
125+
return reject(err);
126+
}
127+
128+
const match = stdout.match(/"IOPlatformUUID" = "([^"]+)"/);
129+
if (match && match[1]) {
130+
resolve(match[1]);
131+
} else {
132+
resolve(null);
133+
}
134+
}
135+
);
136+
});
137+
}
138+
139+
/**
140+
* Get the Windows device ID (MachineGuid).
141+
* @returns {Promise<string|null>}
142+
*
143+
* In a Windows batch file, you can get this with:
144+
* reg query HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography /v MachineGuid
145+
*/
146+
function _getWindowsDeviceID() {
147+
return new Promise((resolve, reject) => {
148+
exec(
149+
'reg query HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Cryptography /v MachineGuid',
150+
{ encoding: 'utf8' },
151+
(err, stdout) => {
152+
if (err) {
153+
console.error('Failed to get Windows device ID:', err.message);
154+
return reject(err);
155+
}
156+
157+
// Example output:
158+
// HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography
159+
// MachineGuid REG_SZ 4c4c4544-0034-5a10-8051-cac04f305a31
160+
const match = stdout.match(/MachineGuid\s+REG_[A-Z]+\s+([a-fA-F0-9-]+)/);
161+
if (match && match[1]) {
162+
resolve(match[1].trim());
163+
} else {
164+
resolve(null);
165+
}
166+
}
167+
);
168+
});
169+
}
170+
171+
async function getDeviceID() {
172+
if (process.platform === "linux") {
173+
return _getLinuxDeviceID();
174+
} else if (process.platform === "darwin") {
175+
return _getMacDeviceID();
176+
} else if (process.platform === "win32") {
177+
return _getWindowsDeviceID();
178+
}
179+
throw new Error(`Unsupported platform: ${process.platform}`);
180+
}
181+
182+
exports.addDeviceLicense = addDeviceLicense;
183+
exports.removeDeviceLicense = removeDeviceLicense;
184+
exports.isLicensedDevice = isLicensedDevice;
185+
exports.getDeviceID = getDeviceID;

src-node/utils.js

Lines changed: 5 additions & 173 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,10 @@ const fs = require('fs');
44
const fsPromise = require('fs').promises;
55
const path = require('path');
66
const os = require('os');
7-
const sudo = require('@expo/sudo-prompt');
8-
const {lintFile} = require("./ESLint/service");
7+
const { lintFile } = require("./ESLint/service");
8+
const { addDeviceLicense, getDeviceID, isLicensedDevice, removeDeviceLicense } = require("./licence-device");
99
let openModule, open; // dynamic import when needed
1010

11-
const options = { name: 'Phoenix Code' };
12-
const licenseFileContent = JSON.stringify({});
13-
1411
async function _importOpen() {
1512
if(open){
1613
return open;
@@ -273,174 +270,8 @@ async function getEnvironmentVariable(varName) {
273270
return process.env[varName];
274271
}
275272

276-
function getLicensePath() {
277-
switch (os.platform()) {
278-
case 'win32':
279-
return 'C:\\Program Files\\Phoenix Code Control\\device-license';
280-
case 'darwin':
281-
return '/Library/Application Support/phoenix-code-control/device-license';
282-
case 'linux':
283-
return '/etc/phoenix-code-control/device-license';
284-
default:
285-
throw new Error(`Unsupported platform: ${os.platform()}`);
286-
}
287-
}
288-
289-
function sudoExec(command) {
290-
return new Promise((resolve, reject) => {
291-
sudo.exec(command, options, (error, stdout, stderr) => {
292-
if (error) {
293-
return reject(error);
294-
}
295-
resolve({ stdout, stderr });
296-
});
297-
});
298-
}
299-
300-
function readFileUtf8(p) {
301-
return new Promise((resolve, reject) => {
302-
fs.readFile(p, 'utf8', (err, data) => (err ? reject(err) : resolve(data)));
303-
});
304-
}
305-
306-
/**
307-
* Writes the license file in a world-readable location.
308-
* Works on Windows, macOS, and Linux.
309-
*/
310-
async function addDeviceLicense() {
311-
const targetPath = getLicensePath();
312-
let command;
313-
// we should not store any sensitive information in this file as this is world readable. we use the
314-
// device id itself as license key for that machine. the device id is not associated with any cloud credits
315-
// and all entitlements are local to device only for this threat model to work. So stolen device IDs doesn't
316-
// have any meaning.
317-
318-
if (os.platform() === 'win32') {
319-
// Windows: write file and explicitly grant Everyone read rights
320-
const dir = 'C:\\Program Files\\Phoenix Code Control';
321-
command =
322-
`powershell -Command "` +
323-
`New-Item -ItemType Directory -Force '${dir}' | Out-Null; ` +
324-
`Set-Content -Path '${targetPath}' -Value '${licenseFileContent}' -Encoding UTF8; ` +
325-
`icacls '${targetPath}' /inheritance:e /grant *S-1-1-0:RX | Out-Null"`;
326-
} else {
327-
// macOS / Linux: mkdir + write + chmod 0644 (world-readable, owner-writable)
328-
const dir = path.dirname(targetPath);
329-
command =
330-
`/bin/mkdir -p "${dir}"` +
331-
` && printf '%s' '${licenseFileContent}' > "${targetPath}"` +
332-
` && /bin/chmod 0644 "${targetPath}"`;
333-
}
334-
335-
await sudoExec(command);
336-
return targetPath;
337-
}
338-
339-
async function removeDeviceLicense() {
340-
const targetPath = getLicensePath();
341-
let command;
342-
343-
if (os.platform() === 'win32') {
344-
command = `powershell -Command "if (Test-Path '${targetPath}') { Remove-Item -Path '${targetPath}' -Force }"`;
345-
} else {
346-
command = `/bin/rm -f "${targetPath}"`;
347-
}
348-
349-
await sudoExec(command);
350-
return targetPath;
351-
}
352-
353-
async function isLicensedDevice() {
354-
const targetPath = getLicensePath();
355-
try {
356-
const data = await readFileUtf8(targetPath);
357-
JSON.parse(data.trim());
358-
return true; // currently, the existence of the file itself is flag. in future, we may choose to add more.
359-
} catch {
360-
// file missing, unreadable, or invalid JSON
361-
return false;
362-
}
363-
}
364-
365-
async function _getLinuxDeviceID() {
366-
const data = await fsPromise.readFile("/etc/machine-id", "utf8");
367-
const id = data.trim();
368-
return id || null;
369-
// throw on error to main.
370-
// no fallback, /var/lib/dbus/machine-id may need sudo in some machines
371-
}
372-
373-
/**
374-
* Get the macOS device ID (IOPlatformUUID).
375-
* @returns {Promise<string|null>}
376-
*/
377-
function _getMacDeviceID() {
378-
// to read this in mac bash, do:
379-
// #!/bin/bash
380-
// device_id=$(ioreg -rd1 -c IOPlatformExpertDevice | awk -F\" '/IOPlatformUUID/ {print $4}' | tr -d '[:space:]')
381-
// echo "$device_id"
382-
return new Promise((resolve, reject) => {
383-
exec(
384-
'ioreg -rd1 -c IOPlatformExpertDevice | grep IOPlatformUUID',
385-
{ encoding: 'utf8' },
386-
(err, stdout) => {
387-
if (err) {
388-
console.error('Failed to get Mac device ID:', err.message);
389-
return reject(err);
390-
}
391-
392-
const match = stdout.match(/"IOPlatformUUID" = "([^"]+)"/);
393-
if (match && match[1]) {
394-
resolve(match[1]);
395-
} else {
396-
resolve(null);
397-
}
398-
}
399-
);
400-
});
401-
}
402-
403-
/**
404-
* Get the Windows device ID (MachineGuid).
405-
* @returns {Promise<string|null>}
406-
*
407-
* In a Windows batch file, you can get this with:
408-
* reg query HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography /v MachineGuid
409-
*/
410-
function _getWindowsDeviceID() {
411-
return new Promise((resolve, reject) => {
412-
exec(
413-
'reg query HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Cryptography /v MachineGuid',
414-
{ encoding: 'utf8' },
415-
(err, stdout) => {
416-
if (err) {
417-
console.error('Failed to get Windows device ID:', err.message);
418-
return reject(err);
419-
}
420-
421-
// Example output:
422-
// HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography
423-
// MachineGuid REG_SZ 4c4c4544-0034-5a10-8051-cac04f305a31
424-
const match = stdout.match(/MachineGuid\s+REG_[A-Z]+\s+([a-fA-F0-9-]+)/);
425-
if (match && match[1]) {
426-
resolve(match[1].trim());
427-
} else {
428-
resolve(null);
429-
}
430-
}
431-
);
432-
});
433-
}
434-
435-
async function getDeviceID() {
436-
if (process.platform === "linux") {
437-
return _getLinuxDeviceID();
438-
} else if (process.platform === "darwin") {
439-
return _getMacDeviceID();
440-
} else if (process.platform === "win32") {
441-
return _getWindowsDeviceID();
442-
}
443-
throw new Error(`Unsupported platform: ${process.platform}`);
273+
async function getOSUserName() {
274+
return os.userInfo().username;
444275
}
445276

446277
exports.getURLContent = getURLContent;
@@ -456,5 +287,6 @@ exports.addDeviceLicense = addDeviceLicense;
456287
exports.removeDeviceLicense = removeDeviceLicense;
457288
exports.isLicensedDevice = isLicensedDevice;
458289
exports.getDeviceID = getDeviceID;
290+
exports.getOSUserName = getOSUserName;
459291
exports._loadNodeExtensionModule = _loadNodeExtensionModule;
460292
exports._npmInstallInFolder = _npmInstallInFolder;

0 commit comments

Comments
 (0)