@commit451/salamander 1.0.7 → 1.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/create-runner.js +1 -1
- package/dist/commands/create-runner.js.map +1 -1
- package/dist/commands/runner-selection.d.ts.map +1 -1
- package/dist/commands/runner-selection.js +48 -4
- package/dist/commands/runner-selection.js.map +1 -1
- package/dist/services/api.d.ts +7 -0
- package/dist/services/api.d.ts.map +1 -1
- package/dist/services/api.js +6 -0
- package/dist/services/api.js.map +1 -1
- package/dist/services/auth.d.ts.map +1 -1
- package/dist/services/auth.js.map +1 -1
- package/dist/services/command-listener.d.ts.map +1 -1
- package/dist/services/command-listener.js +46 -12
- package/dist/services/command-listener.js.map +1 -1
- package/dist/services/crypto.d.ts +52 -0
- package/dist/services/crypto.d.ts.map +1 -0
- package/dist/services/crypto.js +104 -0
- package/dist/services/crypto.js.map +1 -0
- package/dist/services/key-manager.d.ts +45 -0
- package/dist/services/key-manager.d.ts.map +1 -0
- package/dist/services/key-manager.js +123 -0
- package/dist/services/key-manager.js.map +1 -0
- package/dist/services/multi-device-key-manager.d.ts +56 -0
- package/dist/services/multi-device-key-manager.d.ts.map +1 -0
- package/dist/services/multi-device-key-manager.js +159 -0
- package/dist/services/multi-device-key-manager.js.map +1 -0
- package/dist/services/runner.d.ts +3 -3
- package/dist/services/runner.d.ts.map +1 -1
- package/dist/services/runner.js +24 -28
- package/dist/services/runner.js.map +1 -1
- package/dist/types/runner.d.ts +0 -7
- package/dist/types/runner.d.ts.map +1 -1
- package/dist/utils/encryption.d.ts +33 -0
- package/dist/utils/encryption.d.ts.map +1 -0
- package/dist/utils/encryption.js +71 -0
- package/dist/utils/encryption.js.map +1 -0
- package/dist/utils/storage.d.ts +1 -0
- package/dist/utils/storage.d.ts.map +1 -1
- package/dist/utils/storage.js.map +1 -1
- package/package.json +5 -7
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { CryptoService } from './crypto.js';
|
|
2
|
+
import { StorageService } from '../utils/storage.js';
|
|
3
|
+
export class KeyManagerService {
|
|
4
|
+
static KEYS_STORAGE_KEY = 'salamander_runner_keys';
|
|
5
|
+
static KEY_EXPIRY_DAYS = 30;
|
|
6
|
+
/**
|
|
7
|
+
* Initialize keys for a new runner
|
|
8
|
+
*/
|
|
9
|
+
static async initializeRunnerKeys(runnerId) {
|
|
10
|
+
const ecdhKeyPair = CryptoService.generateECDHKeyPair();
|
|
11
|
+
const runnerKeys = {
|
|
12
|
+
ecdhKeyPair,
|
|
13
|
+
createdAt: Date.now()
|
|
14
|
+
};
|
|
15
|
+
await this.storeRunnerKeys(runnerId, runnerKeys);
|
|
16
|
+
return ecdhKeyPair;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Complete the key exchange with the Flutter app's public key
|
|
20
|
+
*/
|
|
21
|
+
static async completeKeyExchange(runnerId, flutterPublicKey) {
|
|
22
|
+
const runnerKeys = await this.getRunnerKeys(runnerId);
|
|
23
|
+
if (!runnerKeys) {
|
|
24
|
+
throw new Error(`No keys found for runner ${runnerId}`);
|
|
25
|
+
}
|
|
26
|
+
// Derive shared secret using our private key and Flutter's public key
|
|
27
|
+
const sharedSecret = CryptoService.deriveSharedSecret(runnerKeys.ecdhKeyPair.privateKey, flutterPublicKey);
|
|
28
|
+
// Update stored keys with shared secret
|
|
29
|
+
runnerKeys.sharedSecret = sharedSecret;
|
|
30
|
+
runnerKeys.keyHash = CryptoService.createKeyHash(sharedSecret);
|
|
31
|
+
await this.storeRunnerKeys(runnerId, runnerKeys);
|
|
32
|
+
return sharedSecret;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Get the shared secret for encryption/decryption
|
|
36
|
+
*/
|
|
37
|
+
static async getSharedSecret(runnerId) {
|
|
38
|
+
const runnerKeys = await this.getRunnerKeys(runnerId);
|
|
39
|
+
if (!runnerKeys?.sharedSecret) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
// Check if keys are expired
|
|
43
|
+
const daysSinceCreation = (Date.now() - runnerKeys.createdAt) / (1000 * 60 * 60 * 24);
|
|
44
|
+
if (daysSinceCreation > this.KEY_EXPIRY_DAYS) {
|
|
45
|
+
console.warn(`Keys for runner ${runnerId} have expired. Consider key rotation.`);
|
|
46
|
+
}
|
|
47
|
+
return runnerKeys.sharedSecret;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Get the CLI's public key for a runner
|
|
51
|
+
*/
|
|
52
|
+
static async getPublicKey(runnerId) {
|
|
53
|
+
const runnerKeys = await this.getRunnerKeys(runnerId);
|
|
54
|
+
return runnerKeys?.ecdhKeyPair.publicKey ?? null;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Check if keys exist and are properly initialized for a runner
|
|
58
|
+
*/
|
|
59
|
+
static async hasValidKeys(runnerId) {
|
|
60
|
+
const runnerKeys = await this.getRunnerKeys(runnerId);
|
|
61
|
+
return !!(runnerKeys?.ecdhKeyPair && runnerKeys?.sharedSecret);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Rotate keys for a runner (generate new key pair)
|
|
65
|
+
*/
|
|
66
|
+
static async rotateKeys(runnerId) {
|
|
67
|
+
console.log(`Rotating keys for runner ${runnerId}`);
|
|
68
|
+
return await this.initializeRunnerKeys(runnerId);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Remove keys for a runner (when runner is deleted)
|
|
72
|
+
*/
|
|
73
|
+
static async removeRunnerKeys(runnerId) {
|
|
74
|
+
const allKeys = await this.getAllStoredKeys();
|
|
75
|
+
delete allKeys[runnerId];
|
|
76
|
+
await StorageService.set(this.KEYS_STORAGE_KEY, allKeys);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Get all runners that have encryption keys
|
|
80
|
+
*/
|
|
81
|
+
static async getEncryptedRunnerIds() {
|
|
82
|
+
const allKeys = await this.getAllStoredKeys();
|
|
83
|
+
return Object.keys(allKeys).filter(runnerId => {
|
|
84
|
+
const keys = allKeys[runnerId];
|
|
85
|
+
return keys?.sharedSecret;
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Cleanup expired keys
|
|
90
|
+
*/
|
|
91
|
+
static async cleanupExpiredKeys() {
|
|
92
|
+
const allKeys = await this.getAllStoredKeys();
|
|
93
|
+
const now = Date.now();
|
|
94
|
+
let cleanedCount = 0;
|
|
95
|
+
const updatedKeys = {};
|
|
96
|
+
for (const [runnerId, keys] of Object.entries(allKeys)) {
|
|
97
|
+
const daysSinceCreation = (now - keys.createdAt) / (1000 * 60 * 60 * 24);
|
|
98
|
+
if (daysSinceCreation <= this.KEY_EXPIRY_DAYS) {
|
|
99
|
+
updatedKeys[runnerId] = keys;
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
cleanedCount++;
|
|
103
|
+
console.log(`Cleaned up expired keys for runner ${runnerId}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
await StorageService.set(this.KEYS_STORAGE_KEY, updatedKeys);
|
|
107
|
+
return cleanedCount;
|
|
108
|
+
}
|
|
109
|
+
// Private helper methods
|
|
110
|
+
static async getRunnerKeys(runnerId) {
|
|
111
|
+
const allKeys = await this.getAllStoredKeys();
|
|
112
|
+
return allKeys[runnerId] ?? null;
|
|
113
|
+
}
|
|
114
|
+
static async storeRunnerKeys(runnerId, keys) {
|
|
115
|
+
const allKeys = await this.getAllStoredKeys();
|
|
116
|
+
allKeys[runnerId] = keys;
|
|
117
|
+
await StorageService.set(this.KEYS_STORAGE_KEY, allKeys);
|
|
118
|
+
}
|
|
119
|
+
static async getAllStoredKeys() {
|
|
120
|
+
return (await StorageService.get(this.KEYS_STORAGE_KEY)) ?? {};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=key-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key-manager.js","sourceRoot":"","sources":["../../src/services/key-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { type DeviceKey } from '../types/runner.js';
|
|
2
|
+
export declare class MultiDeviceKeyManagerService {
|
|
3
|
+
private static readonly KEYS_STORAGE_KEY;
|
|
4
|
+
private static readonly KEY_EXPIRY_DAYS;
|
|
5
|
+
/**
|
|
6
|
+
* Initialize keys for a new runner (CLI side)
|
|
7
|
+
*/
|
|
8
|
+
static initializeRunnerKeys(runnerId: string): Promise<{
|
|
9
|
+
publicKey: string;
|
|
10
|
+
privateKey: string;
|
|
11
|
+
}>;
|
|
12
|
+
/**
|
|
13
|
+
* Register a new device and derive shared secret
|
|
14
|
+
*/
|
|
15
|
+
static registerDevice(runnerId: string, deviceKey: DeviceKey): Promise<string>;
|
|
16
|
+
/**
|
|
17
|
+
* Remove a device from the runner
|
|
18
|
+
*/
|
|
19
|
+
static removeDevice(runnerId: string, deviceId: string): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Get shared secret for specific device
|
|
22
|
+
*/
|
|
23
|
+
static getSharedSecret(runnerId: string, deviceId: string): Promise<string | null>;
|
|
24
|
+
/**
|
|
25
|
+
* Get all registered device IDs for a runner
|
|
26
|
+
*/
|
|
27
|
+
static getRegisteredDevices(runnerId: string): Promise<string[]>;
|
|
28
|
+
/**
|
|
29
|
+
* Encrypt command for specific device
|
|
30
|
+
*/
|
|
31
|
+
static encryptForDevice(runnerId: string, deviceId: string, command: string): Promise<string>;
|
|
32
|
+
/**
|
|
33
|
+
* Encrypt command for all registered devices
|
|
34
|
+
*/
|
|
35
|
+
static encryptForAllDevices(runnerId: string, command: string): Promise<Record<string, string>>;
|
|
36
|
+
/**
|
|
37
|
+
* Decrypt command from specific device
|
|
38
|
+
*/
|
|
39
|
+
static decryptFromDevice(runnerId: string, deviceId: string, encryptedCommand: string): Promise<string>;
|
|
40
|
+
/**
|
|
41
|
+
* Check if runner has any registered devices
|
|
42
|
+
*/
|
|
43
|
+
static hasRegisteredDevices(runnerId: string): Promise<boolean>;
|
|
44
|
+
/**
|
|
45
|
+
* Clean up all keys for a runner
|
|
46
|
+
*/
|
|
47
|
+
static removeRunnerKeys(runnerId: string): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* Get CLI's public key for a runner
|
|
50
|
+
*/
|
|
51
|
+
static getCliPublicKey(runnerId: string): Promise<string | null>;
|
|
52
|
+
private static getRunnerKeys;
|
|
53
|
+
private static storeRunnerKeys;
|
|
54
|
+
private static getAllStoredKeys;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=multi-device-key-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multi-device-key-manager.d.ts","sourceRoot":"","sources":["../../src/services/multi-device-key-manager.ts"],"names":[],"mappings":"AAEA,OAAO,EAAC,KAAK,SAAS,EAAC,MAAM,oBAAoB,CAAC;AASlD,qBAAa,4BAA4B;IACrC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAkC;IAC1E,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAM;IAE7C;;OAEG;WACU,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAC,CAAC;IAcrG;;OAEG;WACU,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC;IA6BpF;;OAEG;WACU,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAa5E;;OAEG;WACU,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAexF;;OAEG;WACU,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAStE;;OAEG;WACU,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IASnG;;OAEG;WACU,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAuBrG;;OAEG;WACU,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAS7G;;OAEG;WACU,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAKrE;;OAEG;WACU,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAO9D;;OAEG;WACU,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;mBAOjD,aAAa;mBAKb,eAAe;mBAMf,gBAAgB;CAGxC"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { CryptoService } from './crypto.js';
|
|
2
|
+
import { StorageService } from '../utils/storage.js';
|
|
3
|
+
export class MultiDeviceKeyManagerService {
|
|
4
|
+
static KEYS_STORAGE_KEY = 'salamander_multi_device_keys';
|
|
5
|
+
static KEY_EXPIRY_DAYS = 30;
|
|
6
|
+
/**
|
|
7
|
+
* Initialize keys for a new runner (CLI side)
|
|
8
|
+
*/
|
|
9
|
+
static async initializeRunnerKeys(runnerId) {
|
|
10
|
+
const ecdhKeyPair = CryptoService.generateECDHKeyPair();
|
|
11
|
+
const keyData = {
|
|
12
|
+
ecdhKeyPair,
|
|
13
|
+
sharedSecrets: {},
|
|
14
|
+
keyHashes: {},
|
|
15
|
+
createdAt: Date.now()
|
|
16
|
+
};
|
|
17
|
+
await this.storeRunnerKeys(runnerId, keyData);
|
|
18
|
+
return ecdhKeyPair;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Register a new device and derive shared secret
|
|
22
|
+
*/
|
|
23
|
+
static async registerDevice(runnerId, deviceKey) {
|
|
24
|
+
const keyData = await this.getRunnerKeys(runnerId);
|
|
25
|
+
if (!keyData) {
|
|
26
|
+
throw new Error(`No keys found for runner ${runnerId}`);
|
|
27
|
+
}
|
|
28
|
+
// Derive shared secret with this device
|
|
29
|
+
const sharedSecret = CryptoService.deriveSharedSecret(keyData.ecdhKeyPair.privateKey, deviceKey.publicKey);
|
|
30
|
+
const keyHash = CryptoService.createKeyHash(sharedSecret);
|
|
31
|
+
// Verify the key hash matches what the device expects
|
|
32
|
+
if (keyHash !== deviceKey.keyHash) {
|
|
33
|
+
throw new Error('Key hash mismatch during device registration');
|
|
34
|
+
}
|
|
35
|
+
// Store the shared secret for this device
|
|
36
|
+
keyData.sharedSecrets[deviceKey.deviceId] = sharedSecret;
|
|
37
|
+
keyData.keyHashes[deviceKey.deviceId] = keyHash;
|
|
38
|
+
await this.storeRunnerKeys(runnerId, keyData);
|
|
39
|
+
console.log(`✓ Device ${deviceKey.deviceId} registered for runner ${runnerId}`);
|
|
40
|
+
return sharedSecret;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Remove a device from the runner
|
|
44
|
+
*/
|
|
45
|
+
static async removeDevice(runnerId, deviceId) {
|
|
46
|
+
const keyData = await this.getRunnerKeys(runnerId);
|
|
47
|
+
if (!keyData) {
|
|
48
|
+
return; // No keys to clean up
|
|
49
|
+
}
|
|
50
|
+
delete keyData.sharedSecrets[deviceId];
|
|
51
|
+
delete keyData.keyHashes[deviceId];
|
|
52
|
+
await this.storeRunnerKeys(runnerId, keyData);
|
|
53
|
+
console.log(`Device ${deviceId} removed from runner ${runnerId}`);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Get shared secret for specific device
|
|
57
|
+
*/
|
|
58
|
+
static async getSharedSecret(runnerId, deviceId) {
|
|
59
|
+
const keyData = await this.getRunnerKeys(runnerId);
|
|
60
|
+
if (!keyData) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
// Check if keys are expired
|
|
64
|
+
const daysSinceCreation = (Date.now() - keyData.createdAt) / (1000 * 60 * 60 * 24);
|
|
65
|
+
if (daysSinceCreation > this.KEY_EXPIRY_DAYS) {
|
|
66
|
+
console.warn(`Keys for runner ${runnerId} have expired. Consider key rotation.`);
|
|
67
|
+
}
|
|
68
|
+
return keyData.sharedSecrets[deviceId] || null;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Get all registered device IDs for a runner
|
|
72
|
+
*/
|
|
73
|
+
static async getRegisteredDevices(runnerId) {
|
|
74
|
+
const keyData = await this.getRunnerKeys(runnerId);
|
|
75
|
+
if (!keyData) {
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
return Object.keys(keyData.sharedSecrets);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Encrypt command for specific device
|
|
82
|
+
*/
|
|
83
|
+
static async encryptForDevice(runnerId, deviceId, command) {
|
|
84
|
+
const sharedSecret = await this.getSharedSecret(runnerId, deviceId);
|
|
85
|
+
if (!sharedSecret) {
|
|
86
|
+
throw new Error(`No shared secret found for device ${deviceId} on runner ${runnerId}`);
|
|
87
|
+
}
|
|
88
|
+
return CryptoService.safeEncrypt(command, sharedSecret);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Encrypt command for all registered devices
|
|
92
|
+
*/
|
|
93
|
+
static async encryptForAllDevices(runnerId, command) {
|
|
94
|
+
const keyData = await this.getRunnerKeys(runnerId);
|
|
95
|
+
if (!keyData) {
|
|
96
|
+
throw new Error(`No keys found for runner ${runnerId}`);
|
|
97
|
+
}
|
|
98
|
+
const encryptedCommands = {};
|
|
99
|
+
for (const [deviceId, sharedSecret] of Object.entries(keyData.sharedSecrets)) {
|
|
100
|
+
try {
|
|
101
|
+
encryptedCommands[deviceId] = CryptoService.safeEncrypt(command, sharedSecret);
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
console.warn(`Failed to encrypt command for device ${deviceId}: ${error}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (Object.keys(encryptedCommands).length === 0) {
|
|
108
|
+
throw new Error(`No devices registered for runner ${runnerId}`);
|
|
109
|
+
}
|
|
110
|
+
return encryptedCommands;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Decrypt command from specific device
|
|
114
|
+
*/
|
|
115
|
+
static async decryptFromDevice(runnerId, deviceId, encryptedCommand) {
|
|
116
|
+
const sharedSecret = await this.getSharedSecret(runnerId, deviceId);
|
|
117
|
+
if (!sharedSecret) {
|
|
118
|
+
throw new Error(`No shared secret found for device ${deviceId} on runner ${runnerId}`);
|
|
119
|
+
}
|
|
120
|
+
return CryptoService.safeDecrypt(encryptedCommand, sharedSecret);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Check if runner has any registered devices
|
|
124
|
+
*/
|
|
125
|
+
static async hasRegisteredDevices(runnerId) {
|
|
126
|
+
const devices = await this.getRegisteredDevices(runnerId);
|
|
127
|
+
return devices.length > 0;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Clean up all keys for a runner
|
|
131
|
+
*/
|
|
132
|
+
static async removeRunnerKeys(runnerId) {
|
|
133
|
+
const allKeys = await this.getAllStoredKeys();
|
|
134
|
+
delete allKeys[runnerId];
|
|
135
|
+
await StorageService.set(this.KEYS_STORAGE_KEY, allKeys);
|
|
136
|
+
console.log(`Cleaned up all keys for runner ${runnerId}`);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Get CLI's public key for a runner
|
|
140
|
+
*/
|
|
141
|
+
static async getCliPublicKey(runnerId) {
|
|
142
|
+
const keyData = await this.getRunnerKeys(runnerId);
|
|
143
|
+
return keyData?.ecdhKeyPair.publicKey || null;
|
|
144
|
+
}
|
|
145
|
+
// Private helper methods
|
|
146
|
+
static async getRunnerKeys(runnerId) {
|
|
147
|
+
const allKeys = await this.getAllStoredKeys();
|
|
148
|
+
return allKeys[runnerId] || null;
|
|
149
|
+
}
|
|
150
|
+
static async storeRunnerKeys(runnerId, keys) {
|
|
151
|
+
const allKeys = await this.getAllStoredKeys();
|
|
152
|
+
allKeys[runnerId] = keys;
|
|
153
|
+
await StorageService.set(this.KEYS_STORAGE_KEY, allKeys);
|
|
154
|
+
}
|
|
155
|
+
static async getAllStoredKeys() {
|
|
156
|
+
return (await StorageService.get(this.KEYS_STORAGE_KEY)) ?? {};
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=multi-device-key-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multi-device-key-manager.js","sourceRoot":"","sources":["../../src/services/multi-device-key-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CreateMessageRequest } from './api.js';
|
|
2
|
+
import { type CreateRunnerData, type Runner } from '../types/runner.js';
|
|
2
3
|
export declare class RunnerService {
|
|
3
4
|
private static readonly RUNNERS_COLLECTION;
|
|
4
|
-
private static readonly MESSAGES_COLLECTION;
|
|
5
5
|
static getAllRunners(): Promise<Runner[]>;
|
|
6
6
|
static createRunner(data: CreateRunnerData): Promise<Runner>;
|
|
7
7
|
static deleteRunner(runnerId: string): Promise<void>;
|
|
8
8
|
static clearPendingCommand(runnerId: string): Promise<void>;
|
|
9
9
|
static updateRunnerAfterCommand(runnerId: string, result: string): Promise<void>;
|
|
10
|
-
static createMessage(data:
|
|
10
|
+
static createMessage(runnerId: string, data: CreateMessageRequest): Promise<void>;
|
|
11
11
|
static listenToRunner(runnerId: string, callback: (runner: Runner | null) => void): () => void;
|
|
12
12
|
private static docToRunner;
|
|
13
13
|
private static parseRunnerType;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/services/runner.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/services/runner.ts"],"names":[],"mappings":"AAGA,OAAO,EAAa,oBAAoB,EAAC,MAAM,UAAU,CAAC;AAC1D,OAAO,EAAC,KAAK,gBAAgB,EAAE,KAAK,MAAM,EAAa,MAAM,oBAAoB,CAAC;AAKlF,qBAAa,aAAa;IACtB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAa;WAE1C,aAAa,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;WAkBlC,YAAY,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC;WAmDrD,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;WAK7C,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;WAMpD,wBAAwB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;WAQzE,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC;IAavF,MAAM,CAAC,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,GAAG,MAAM,IAAI;IAa9F,OAAO,CAAC,MAAM,CAAC,WAAW;IAgB1B,OAAO,CAAC,MAAM,CAAC,eAAe;CAWjC"}
|
package/dist/services/runner.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { collection, doc, getDocs, onSnapshot, orderBy, query, where, } from 'firebase/firestore';
|
|
2
2
|
import { db } from '../config/firebase.js';
|
|
3
3
|
import { AuthService } from './auth.js';
|
|
4
4
|
import { ApiService } from './api.js';
|
|
5
5
|
import { RunnerType } from '../types/runner.js';
|
|
6
6
|
import { generateDeviceId, getDeviceDisplayName } from '../utils/device.js';
|
|
7
|
+
import { EncryptionService } from '../utils/encryption.js';
|
|
8
|
+
import { loadAuth, saveAuth } from '../utils/storage.js';
|
|
7
9
|
export class RunnerService {
|
|
8
10
|
static RUNNERS_COLLECTION = 'runners';
|
|
9
|
-
static MESSAGES_COLLECTION = 'messages';
|
|
10
11
|
static async getAllRunners() {
|
|
11
12
|
const userId = AuthService.userId;
|
|
12
13
|
const deviceId = generateDeviceId();
|
|
@@ -24,12 +25,28 @@ export class RunnerService {
|
|
|
24
25
|
}
|
|
25
26
|
const deviceId = generateDeviceId();
|
|
26
27
|
const deviceName = getDeviceDisplayName();
|
|
27
|
-
//
|
|
28
|
+
// Generate or load encryption code
|
|
29
|
+
let encryptionCode;
|
|
30
|
+
const storedAuth = await loadAuth();
|
|
31
|
+
if (storedAuth?.encryptionCode) {
|
|
32
|
+
encryptionCode = storedAuth.encryptionCode;
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
// Generate new random encryption code
|
|
36
|
+
encryptionCode = EncryptionService.generateRandomSecret();
|
|
37
|
+
// Save to storage
|
|
38
|
+
const updatedAuth = { ...storedAuth, encryptionCode };
|
|
39
|
+
await saveAuth(updatedAuth);
|
|
40
|
+
}
|
|
41
|
+
// Create verification string
|
|
42
|
+
const encryptionVerification = EncryptionService.createVerificationString(encryptionCode);
|
|
43
|
+
// Create runner via API with encryption verification
|
|
28
44
|
const response = await ApiService.createRunner({
|
|
29
45
|
name: data.name,
|
|
30
46
|
directory: data.directory,
|
|
31
47
|
machineId: deviceId,
|
|
32
|
-
machineName: deviceName
|
|
48
|
+
machineName: deviceName,
|
|
49
|
+
encryptionVerification
|
|
33
50
|
});
|
|
34
51
|
const runnerId = response.id;
|
|
35
52
|
return {
|
|
@@ -42,7 +59,6 @@ export class RunnerService {
|
|
|
42
59
|
lastUsed: new Date(),
|
|
43
60
|
machineId: deviceId,
|
|
44
61
|
machineName: deviceName,
|
|
45
|
-
online: false
|
|
46
62
|
};
|
|
47
63
|
}
|
|
48
64
|
static async deleteRunner(runnerId) {
|
|
@@ -60,34 +76,15 @@ export class RunnerService {
|
|
|
60
76
|
lastMessage: result ?? ''
|
|
61
77
|
});
|
|
62
78
|
}
|
|
63
|
-
static async createMessage(data) {
|
|
79
|
+
static async createMessage(runnerId, data) {
|
|
64
80
|
const userId = AuthService.userId;
|
|
65
81
|
if (!userId) {
|
|
66
82
|
throw new Error('User not authenticated');
|
|
67
83
|
}
|
|
68
|
-
if (!
|
|
84
|
+
if (!runnerId) {
|
|
69
85
|
throw new Error('Runner ID is required to create a message');
|
|
70
86
|
}
|
|
71
|
-
|
|
72
|
-
content: data.content ?? '',
|
|
73
|
-
senderName: data.senderName ?? '',
|
|
74
|
-
type: data.type,
|
|
75
|
-
userId,
|
|
76
|
-
timestamp: serverTimestamp()
|
|
77
|
-
};
|
|
78
|
-
// Store message in the runner's messages subcollection
|
|
79
|
-
const runnerDocRef = doc(db, this.RUNNERS_COLLECTION, data.runnerId);
|
|
80
|
-
const messagesCollectionRef = collection(runnerDocRef, this.MESSAGES_COLLECTION);
|
|
81
|
-
const docRef = await addDoc(messagesCollectionRef, messageData);
|
|
82
|
-
return {
|
|
83
|
-
id: docRef.id,
|
|
84
|
-
content: data.content,
|
|
85
|
-
senderName: data.senderName,
|
|
86
|
-
type: data.type,
|
|
87
|
-
userId,
|
|
88
|
-
timestamp: new Date(),
|
|
89
|
-
runnerId: data.runnerId
|
|
90
|
-
};
|
|
87
|
+
await ApiService.createRunnerMessage(runnerId, data);
|
|
91
88
|
}
|
|
92
89
|
static listenToRunner(runnerId, callback) {
|
|
93
90
|
const runnerDoc = doc(db, this.RUNNERS_COLLECTION, runnerId);
|
|
@@ -114,7 +111,6 @@ export class RunnerService {
|
|
|
114
111
|
machineName: data.machineName || undefined,
|
|
115
112
|
machineId: data.machineId || undefined,
|
|
116
113
|
pendingCommand: data.pendingCommand || undefined,
|
|
117
|
-
online: data.online || false
|
|
118
114
|
};
|
|
119
115
|
}
|
|
120
116
|
static parseRunnerType(type) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.js","sourceRoot":"","sources":["../../src/services/runner.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"runner.js","sourceRoot":"","sources":["../../src/services/runner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAAE,GAAG,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,GAAE,MAAM,oBAAoB,CAAC;AAChG,OAAO,EAAC,EAAE,EAAC,MAAM,uBAAuB,CAAC;AACzC,OAAO,EAAC,WAAW,EAAC,MAAM,WAAW,CAAC;AACtC,OAAO,EAAC,UAAU,EAAuB,MAAM,UAAU,CAAC;AAC1D,OAAO,EAAqC,UAAU,EAAC,MAAM,oBAAoB,CAAC;AAClF,OAAO,EAAC,gBAAgB,EAAE,oBAAoB,EAAC,MAAM,oBAAoB,CAAC;AAC1E,OAAO,EAAC,iBAAiB,EAAC,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAC,QAAQ,EAAE,QAAQ,EAAC,MAAM,qBAAqB,CAAC;AAEvD,MAAM,OAAO,aAAa;IACd,MAAM,CAAU,kBAAkB,GAAG,SAAS,CAAC;IAEvD,MAAM,CAAC,KAAK,CAAC,aAAa;QACtB,MAAM,MAAM,GAAG,WAAW,CAAC,MAAM,CAAC;QAClC,MAAM,QAAQ,GAAG,gBAAgB,EAAE,CAAC;QACpC,IAAI,CAAC,MAAM,EAAE,CAAC;YACV,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;QAC9C,CAAC;QAED,MAAM,CAAC,GAAG,KAAK,CACX,UAAU,CAAC,EAAE,EAAE,IAAI,CAAC,kBAAkB,CAAC,EACvC,KAAK,CAAC,QAAQ,EAAE,IAAI,EAAE,MAAM,CAAC,EAC7B,KAAK,CAAC,WAAW,EAAE,IAAI,EAAE,QAAQ,CAAC,EAClC,OAAO,CAAC,UAAU,EAAE,MAAM,CAAC,CAC9B,CAAC;QAEF,MAAM,aAAa,GAAG,MAAM,OAAO,CAAC,CAAC,CAAC,CAAC;QACvC,OAAO,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IAC/E,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,IAAsB;QAC5C,MAAM,MAAM,GAAG,WAAW,CAAC,MAAM,CAAC;QAClC,IAAI,CAAC,MAAM,EAAE,CAAC;YACV,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;QAC9C,CAAC;QAED,MAAM,QAAQ,GAAG,gBAAgB,EAAE,CAAC;QACpC,MAAM,UAAU,GAAG,oBAAoB,EAAE,CAAC;QAE1C,mCAAmC;QACnC,IAAI,cAAsB,CAAC;QAC3B,MAAM,UAAU,GAAG,MAAM,QAAQ,EAAE,CAAC;QAEpC,IAAI,UAAU,EAAE,cAAc,EAAE,CAAC;YAC7B,cAAc,GAAG,UAAU,CAAC,cAAc,CAAC;QAC/C,CAAC;aAAM,CAAC;YACJ,sCAAsC;YACtC,cAAc,GAAG,iBAAiB,CAAC,oBAAoB,EAAE,CAAC;YAE1D,kBAAkB;YAClB,MAAM,WAAW,GAAG,EAAE,GAAG,UAAU,EAAE,cAAc,EAAE,CAAC;YACtD,MAAM,QAAQ,CAAC,WAAW,CAAC,CAAC;QAChC,CAAC;QAED,6BAA6B;QAC7B,MAAM,sBAAsB,GAAG,iBAAiB,CAAC,wBAAwB,CAAC,cAAc,CAAC,CAAC;QAE1F,qDAAqD;QACrD,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,YAAY,CAAC;YAC3C,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,SAAS,EAAE,QAAQ;YACnB,WAAW,EAAE,UAAU;YACvB,sBAAsB;SACzB,CAAC,CAAC;QAEH,MAAM,QAAQ,GAAG,QAAQ,CAAC,EAAE,CAAC;QAE7B,OAAO;YACH,EAAE,EAAE,QAAQ;YACZ,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,MAAM;YACN,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,SAAS,EAAE,IAAI,IAAI,EAAE;YACrB,QAAQ,EAAE,IAAI,IAAI,EAAE;YACpB,SAAS,EAAE,QAAQ;YACnB,WAAW,EAAE,UAAU;SAC1B,CAAC;IACN,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,QAAgB;QACtC,oCAAoC;QACpC,MAAM,UAAU,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;IAC5C,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,mBAAmB,CAAC,QAAgB;QAC7C,MAAM,UAAU,CAAC,YAAY,CAAC,QAAQ,EAAE;YACpC,mBAAmB,EAAE,IAAI;SAC5B,CAAC,CAAC;IACP,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,wBAAwB,CAAC,QAAgB,EAAE,MAAc;QAClE,MAAM,UAAU,CAAC,YAAY,CAAC,QAAQ,EAAE;YACpC,cAAc,EAAE,IAAI;YACpB,WAAW,EAAE,MAAM,IAAI,EAAE;SAC5B,CAAC,CAAC;IACP,CAAC;IAGD,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,QAAgB,EAAE,IAA0B;QACnE,MAAM,MAAM,GAAG,WAAW,CAAC,MAAM,CAAC;QAClC,IAAI,CAAC,MAAM,EAAE,CAAC;YACV,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;QAC9C,CAAC;QAED,IAAI,CAAC,QAAQ,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;QACjE,CAAC;QAED,MAAM,UAAU,CAAC,mBAAmB,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IACzD,CAAC;IAED,MAAM,CAAC,cAAc,CAAC,QAAgB,EAAE,QAAyC;QAC7E,MAAM,SAAS,GAAG,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,kBAAkB,EAAE,QAAQ,CAAC,CAAC;QAE7D,OAAO,UAAU,CAAC,SAAS,EAAE,CAAC,GAAG,EAAE,EAAE;YACjC,IAAI,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC;gBACf,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;gBACpD,QAAQ,CAAC,MAAM,CAAC,CAAC;YACrB,CAAC;iBAAM,CAAC;gBACJ,QAAQ,CAAC,IAAI,CAAC,CAAC;YACnB,CAAC;QACL,CAAC,CAAC,CAAC;IACP,CAAC;IAEO,MAAM,CAAC,WAAW,CAAC,IAAS,EAAE,EAAU;QAC5C,OAAO;YACH,EAAE;YACF,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,EAAE;YACrB,SAAS,EAAE,IAAI,CAAC,SAAS,IAAI,EAAE;YAC/B,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,EAAE;YACzB,UAAU,EAAE,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,UAAU,CAAC;YACjD,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,IAAI;YAC3C,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,IAAI;YACzC,WAAW,EAAE,IAAI,CAAC,WAAW,IAAI,SAAS;YAC1C,WAAW,EAAE,IAAI,CAAC,WAAW,IAAI,SAAS;YAC1C,SAAS,EAAE,IAAI,CAAC,SAAS,IAAI,SAAS;YACtC,cAAc,EAAE,IAAI,CAAC,cAAc,IAAI,SAAS;SACnD,CAAC;IACN,CAAC;IAEO,MAAM,CAAC,eAAe,CAAC,IAAY;QACvC,QAAQ,IAAI,EAAE,CAAC;YACX,KAAK,OAAO;gBACR,OAAO,UAAU,CAAC,KAAK,CAAC;YAC5B,KAAK,QAAQ;gBACT,OAAO,UAAU,CAAC,MAAM,CAAC;YAC7B,KAAK,QAAQ,CAAC;YACd;gBACI,OAAO,UAAU,CAAC,MAAM,CAAC;QACjC,CAAC;IACL,CAAC"}
|
package/dist/types/runner.d.ts
CHANGED
|
@@ -15,7 +15,6 @@ export interface Runner {
|
|
|
15
15
|
machineName?: string;
|
|
16
16
|
machineId?: string;
|
|
17
17
|
pendingCommand?: string;
|
|
18
|
-
online?: boolean;
|
|
19
18
|
}
|
|
20
19
|
export interface CreateRunnerData {
|
|
21
20
|
name: string;
|
|
@@ -31,12 +30,6 @@ export interface Message {
|
|
|
31
30
|
userId: string;
|
|
32
31
|
runnerId?: string;
|
|
33
32
|
}
|
|
34
|
-
export interface CreateMessageData {
|
|
35
|
-
content: string;
|
|
36
|
-
senderName: string;
|
|
37
|
-
type: 'user' | 'runner' | 'error';
|
|
38
|
-
runnerId: string;
|
|
39
|
-
}
|
|
40
33
|
export interface User {
|
|
41
34
|
id: string;
|
|
42
35
|
email: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/types/runner.ts"],"names":[],"mappings":"AAAA,oBAAY,UAAU;IAClB,MAAM,WAAW;IACjB,KAAK,UAAU;IACf,MAAM,WAAW;CACpB;AAED,MAAM,WAAW,MAAM;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,UAAU,CAAC;IACvB,SAAS,EAAE,IAAI,GAAG,IAAI,CAAC;IACvB,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/types/runner.ts"],"names":[],"mappings":"AAAA,oBAAY,UAAU;IAClB,MAAM,WAAW;IACjB,KAAK,UAAU;IACf,MAAM,WAAW;CACpB;AAED,MAAM,WAAW,MAAM;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,UAAU,CAAC;IACvB,SAAS,EAAE,IAAI,GAAG,IAAI,CAAC;IACvB,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,gBAAgB;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,UAAU,CAAC;CAC1B;AAED,MAAM,WAAW,OAAO;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,IAAI,CAAC;IAChB,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,IAAI;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,MAAM,CAAC;IACzB,SAAS,EAAE,IAAI,GAAG,IAAI,CAAC;IACvB,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAC;CACzB"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export interface EncryptionResult {
|
|
2
|
+
encrypted: string;
|
|
3
|
+
success: boolean;
|
|
4
|
+
}
|
|
5
|
+
export interface DecryptionResult {
|
|
6
|
+
decrypted: string;
|
|
7
|
+
success: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare class EncryptionService {
|
|
10
|
+
/**
|
|
11
|
+
* Generate a random shared secret
|
|
12
|
+
* This ensures the backend never knows the encryption key
|
|
13
|
+
*/
|
|
14
|
+
static generateRandomSecret(): string;
|
|
15
|
+
/**
|
|
16
|
+
* Simple XOR-based encryption compatible with Dart implementation
|
|
17
|
+
*/
|
|
18
|
+
static encrypt(data: string, secret: string): EncryptionResult;
|
|
19
|
+
/**
|
|
20
|
+
* Simple XOR-based decryption
|
|
21
|
+
*/
|
|
22
|
+
static decrypt(encryptedData: string, secret: string): DecryptionResult;
|
|
23
|
+
/**
|
|
24
|
+
* Create verification string encrypted with the secret
|
|
25
|
+
* This can be stored on the runner to verify the correct secret
|
|
26
|
+
*/
|
|
27
|
+
static createVerificationString(secret: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* Verify if a secret is correct by trying to decrypt the verification string
|
|
30
|
+
*/
|
|
31
|
+
static verifySecret(encryptedVerification: string, secret: string): boolean;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=encryption.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"encryption.d.ts","sourceRoot":"","sources":["../../src/utils/encryption.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,gBAAgB;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;CACpB;AAED,qBAAa,iBAAiB;IAC1B;;;OAGG;IACH,MAAM,CAAC,oBAAoB,IAAI,MAAM;IAMrC;;OAEG;IACH,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,gBAAgB;IAqB9D;;OAEG;IACH,MAAM,CAAC,OAAO,CAAC,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,gBAAgB;IAsBvE;;;OAGG;IACH,MAAM,CAAC,wBAAwB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;IAMvD;;OAEG;IACH,MAAM,CAAC,YAAY,CAAC,qBAAqB,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO;CAI9E"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { createHash, randomBytes } from 'crypto';
|
|
2
|
+
export class EncryptionService {
|
|
3
|
+
/**
|
|
4
|
+
* Generate a random shared secret
|
|
5
|
+
* This ensures the backend never knows the encryption key
|
|
6
|
+
*/
|
|
7
|
+
static generateRandomSecret() {
|
|
8
|
+
// Generate 8 random bytes and convert to hex for a 16-character string
|
|
9
|
+
const randomBuffer = randomBytes(8);
|
|
10
|
+
return randomBuffer.toString('hex').toUpperCase();
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Simple XOR-based encryption compatible with Dart implementation
|
|
14
|
+
*/
|
|
15
|
+
static encrypt(data, secret) {
|
|
16
|
+
try {
|
|
17
|
+
// Use a hash of the secret to create consistent encryption
|
|
18
|
+
const secretHash = createHash('sha256').update(secret).digest('hex');
|
|
19
|
+
const keyBytes = Buffer.from(secretHash.substring(0, 32), 'utf8');
|
|
20
|
+
const dataBytes = Buffer.from(data, 'utf8');
|
|
21
|
+
const encrypted = Buffer.alloc(dataBytes.length);
|
|
22
|
+
for (let i = 0; i < dataBytes.length; i++) {
|
|
23
|
+
encrypted[i] = dataBytes[i] ^ keyBytes[i % keyBytes.length];
|
|
24
|
+
}
|
|
25
|
+
// Convert to hex string
|
|
26
|
+
const result = encrypted.toString('hex');
|
|
27
|
+
return { encrypted: result, success: true };
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
return { encrypted: '', success: false };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Simple XOR-based decryption
|
|
35
|
+
*/
|
|
36
|
+
static decrypt(encryptedData, secret) {
|
|
37
|
+
try {
|
|
38
|
+
// Use a hash of the secret to create consistent decryption key
|
|
39
|
+
const secretHash = createHash('sha256').update(secret).digest('hex');
|
|
40
|
+
const keyBytes = Buffer.from(secretHash.substring(0, 32), 'utf8');
|
|
41
|
+
// Convert hex string back to bytes
|
|
42
|
+
const encrypted = Buffer.from(encryptedData, 'hex');
|
|
43
|
+
const decrypted = Buffer.alloc(encrypted.length);
|
|
44
|
+
for (let i = 0; i < encrypted.length; i++) {
|
|
45
|
+
decrypted[i] = encrypted[i] ^ keyBytes[i % keyBytes.length];
|
|
46
|
+
}
|
|
47
|
+
const result = decrypted.toString('utf8');
|
|
48
|
+
return { decrypted: result, success: true };
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
return { decrypted: '', success: false };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Create verification string encrypted with the secret
|
|
56
|
+
* This can be stored on the runner to verify the correct secret
|
|
57
|
+
*/
|
|
58
|
+
static createVerificationString(secret) {
|
|
59
|
+
const verificationText = 'salamander_verification_success';
|
|
60
|
+
const result = this.encrypt(verificationText, secret);
|
|
61
|
+
return result.encrypted;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Verify if a secret is correct by trying to decrypt the verification string
|
|
65
|
+
*/
|
|
66
|
+
static verifySecret(encryptedVerification, secret) {
|
|
67
|
+
const result = this.decrypt(encryptedVerification, secret);
|
|
68
|
+
return result.success && result.decrypted === 'salamander_verification_success';
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=encryption.js.map
|