@oussema_mili/test-pkg-123 1.1.34 → 1.1.36

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.
@@ -24,6 +24,10 @@ async function handleRegistryAction(ws, action, payload) {
24
24
  return await handleSetActiveRegistry(ws, payload);
25
25
  case "fetchRegistryImages":
26
26
  return await handleFetchRegistryImages(ws, payload);
27
+ case "validateRegistry":
28
+ return await handleValidateRegistry(ws, payload);
29
+ case "updateRegistryCredentials":
30
+ return await handleUpdateRegistryCredentials(ws, payload);
27
31
  default:
28
32
  throw new Error(`Unknown registry action: ${action}`);
29
33
  }
@@ -148,6 +152,8 @@ async function handleConnectRegistry(ws, payload) {
148
152
  type,
149
153
  connected: true,
150
154
  connectedAt: new Date().toISOString(),
155
+ status: "valid", // Mark as valid since we just authenticated
156
+ lastValidated: new Date().toISOString(),
151
157
  // Store metadata at top level for API exposure
152
158
  ...(type === "ecr" && {
153
159
  username: extractedUsername,
@@ -604,6 +610,248 @@ async function handleSetActiveRegistry(ws, payload) {
604
610
  }
605
611
  }
606
612
 
613
+ async function handleValidateRegistry(ws, payload) {
614
+ try {
615
+ const { id, requestId } = payload;
616
+
617
+ if (!id) {
618
+ throw new Error("Registry ID is required");
619
+ }
620
+
621
+ // Get registry with credentials
622
+ const registry = await registryStore.getRegistryWithCredentials(id);
623
+ if (!registry) {
624
+ throw new Error("Registry not found");
625
+ }
626
+
627
+ let isValid = false;
628
+ let errorMessage = "";
629
+
630
+ try {
631
+ const { credentials, type } = registry;
632
+
633
+ switch (type) {
634
+ case "docker-hub":
635
+ isValid = await testDockerHubCredentials(
636
+ credentials.username,
637
+ credentials.password
638
+ );
639
+ break;
640
+
641
+ case "ecr":
642
+ const ecrResult = await testECRCredentials(
643
+ credentials.accessKeyId,
644
+ credentials.secretAccessKey,
645
+ registry.region,
646
+ credentials.sessionToken
647
+ );
648
+ isValid = ecrResult.success;
649
+ break;
650
+
651
+ case "gcr":
652
+ isValid = await testGCRCredentials(
653
+ credentials.serviceAccountJson,
654
+ registry.url
655
+ );
656
+ break;
657
+
658
+ case "acr":
659
+ isValid = await testACRCredentials(
660
+ credentials.username,
661
+ credentials.password,
662
+ registry.url
663
+ );
664
+ break;
665
+
666
+ case "custom":
667
+ isValid = await testCustomRegistryCredentials(
668
+ credentials.username,
669
+ credentials.password,
670
+ registry.url
671
+ );
672
+ break;
673
+
674
+ default:
675
+ throw new Error(`Unsupported registry type: ${type}`);
676
+ }
677
+ } catch (validationError) {
678
+ isValid = false;
679
+ errorMessage = validationError.message;
680
+ }
681
+
682
+ // Update registry status in store
683
+ const status = isValid ? "valid" : "invalid";
684
+ await registryStore.updateRegistryStatus(id, status, new Date().toISOString());
685
+
686
+ // Get updated registry for response
687
+ const updatedRegistry = await registryStore.getRegistryWithCredentials(id);
688
+ const { credentials, ...safeRegistry } = updatedRegistry;
689
+
690
+ ws.send(
691
+ JSON.stringify({
692
+ type: "registryValidated",
693
+ registry: {
694
+ ...safeRegistry,
695
+ status,
696
+ lastValidated: new Date().toISOString(),
697
+ },
698
+ isValid,
699
+ errorMessage: isValid ? null : errorMessage,
700
+ requestId,
701
+ })
702
+ );
703
+
704
+ if (isValid) {
705
+ console.log(chalk.green(`✅ Registry "${registry.name}" credentials are valid`));
706
+ } else {
707
+ console.log(chalk.yellow(`⚠️ Registry "${registry.name}" credentials are invalid: ${errorMessage}`));
708
+ }
709
+ } catch (error) {
710
+ console.error("Error validating registry:", error);
711
+ ws.send(
712
+ JSON.stringify({
713
+ type: "error",
714
+ error: "Failed to validate registry: " + error.message,
715
+ requestId: payload.requestId,
716
+ })
717
+ );
718
+ }
719
+ }
720
+
721
+ async function handleUpdateRegistryCredentials(ws, payload) {
722
+ try {
723
+ const {
724
+ id,
725
+ password,
726
+ accessKeyId,
727
+ secretAccessKey,
728
+ sessionToken,
729
+ serviceAccountJson,
730
+ requestId,
731
+ } = payload;
732
+
733
+ if (!id) {
734
+ throw new Error("Registry ID is required");
735
+ }
736
+
737
+ // Get existing registry
738
+ const registry = await registryStore.getRegistryWithCredentials(id);
739
+ if (!registry) {
740
+ throw new Error("Registry not found");
741
+ }
742
+
743
+ // Build new credentials based on registry type
744
+ let newCredentials = {};
745
+ let authenticationValid = false;
746
+ let errorMessage = "";
747
+
748
+ try {
749
+ switch (registry.type) {
750
+ case "docker-hub":
751
+ case "acr":
752
+ case "custom":
753
+ if (!password) {
754
+ throw new Error("Password/token is required");
755
+ }
756
+ newCredentials = {
757
+ ...registry.credentials,
758
+ password,
759
+ };
760
+ // Validate the new credentials
761
+ if (registry.type === "docker-hub") {
762
+ authenticationValid = await testDockerHubCredentials(
763
+ registry.credentials.username,
764
+ password
765
+ );
766
+ } else if (registry.type === "acr") {
767
+ authenticationValid = await testACRCredentials(
768
+ registry.credentials.username,
769
+ password,
770
+ registry.url
771
+ );
772
+ } else {
773
+ authenticationValid = await testCustomRegistryCredentials(
774
+ registry.credentials.username,
775
+ password,
776
+ registry.url
777
+ );
778
+ }
779
+ break;
780
+
781
+ case "ecr":
782
+ if (!accessKeyId || !secretAccessKey) {
783
+ throw new Error("AWS Access Key ID and Secret Access Key are required");
784
+ }
785
+ newCredentials = {
786
+ accessKeyId,
787
+ secretAccessKey,
788
+ sessionToken,
789
+ };
790
+ const ecrResult = await testECRCredentials(
791
+ accessKeyId,
792
+ secretAccessKey,
793
+ registry.region,
794
+ sessionToken
795
+ );
796
+ authenticationValid = ecrResult.success;
797
+ break;
798
+
799
+ case "gcr":
800
+ if (!serviceAccountJson) {
801
+ throw new Error("Service Account JSON is required");
802
+ }
803
+ newCredentials = {
804
+ serviceAccountJson,
805
+ };
806
+ authenticationValid = await testGCRCredentials(
807
+ serviceAccountJson,
808
+ registry.url
809
+ );
810
+ break;
811
+
812
+ default:
813
+ throw new Error(`Unsupported registry type: ${registry.type}`);
814
+ }
815
+ } catch (authError) {
816
+ authenticationValid = false;
817
+ errorMessage = authError.message;
818
+ }
819
+
820
+ if (!authenticationValid) {
821
+ throw new Error(errorMessage || "New credentials are invalid");
822
+ }
823
+
824
+ // Update credentials in store
825
+ const updatedRegistry = await registryStore.updateCredentials(id, newCredentials);
826
+
827
+ console.log(chalk.green(`✅ Successfully updated credentials for registry: ${registry.name}`));
828
+
829
+ // Return safe registry data
830
+ const { credentials, ...safeRegistry } = updatedRegistry;
831
+
832
+ ws.send(
833
+ JSON.stringify({
834
+ type: "registryCredentialsUpdated",
835
+ registry: {
836
+ ...safeRegistry,
837
+ status: "valid",
838
+ lastValidated: new Date().toISOString(),
839
+ },
840
+ requestId,
841
+ })
842
+ );
843
+ } catch (error) {
844
+ console.error("Error updating registry credentials:", error);
845
+ ws.send(
846
+ JSON.stringify({
847
+ type: "error",
848
+ error: "Failed to update registry credentials: " + error.message,
849
+ requestId: payload.requestId,
850
+ })
851
+ );
852
+ }
853
+ }
854
+
607
855
  async function handleFetchRegistryImages(ws, payload) {
608
856
  try {
609
857
  const { registryId, requestId } = payload;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oussema_mili/test-pkg-123",
3
- "version": "1.1.34",
3
+ "version": "1.1.36",
4
4
  "description": "Fenwave Docker Agent and CLI",
5
5
  "keywords": [
6
6
  "fenwave",
@@ -3,6 +3,7 @@ import fs from "fs";
3
3
  import path from "path";
4
4
  import chalk from "chalk";
5
5
  import dotenv from "dotenv";
6
+ import { encrypt, decrypt } from "../utils/encryption.js";
6
7
  dotenv.config();
7
8
 
8
9
  const fsPromises = fs.promises;
@@ -14,10 +15,93 @@ const REGISTRY_FILE = path.join(
14
15
  os.homedir(),
15
16
  AGENT_ROOT_DIR,
16
17
  REGISTRIES_DIR,
17
- REGISTRIES_FILE
18
+ REGISTRIES_FILE,
18
19
  );
19
20
 
20
- const SCHEMA_VERSION = 1;
21
+ const SCHEMA_VERSION = 2; // Bumped for encryption support
22
+
23
+ /**
24
+ * Encrypt sensitive credentials based on registry type
25
+ */
26
+ function encryptCredentials(credentials, type) {
27
+ if (!credentials) return credentials;
28
+
29
+ const encrypted = { ...credentials };
30
+
31
+ switch (type) {
32
+ case "docker-hub":
33
+ case "acr":
34
+ case "custom":
35
+ if (credentials.password) {
36
+ encrypted.password = encrypt(credentials.password);
37
+ encrypted._encrypted = true;
38
+ }
39
+ break;
40
+ case "ecr":
41
+ if (credentials.secretAccessKey) {
42
+ encrypted.secretAccessKey = encrypt(credentials.secretAccessKey);
43
+ encrypted._encrypted = true;
44
+ }
45
+ if (credentials.sessionToken) {
46
+ encrypted.sessionToken = encrypt(credentials.sessionToken);
47
+ }
48
+ break;
49
+ case "gcr":
50
+ if (credentials.serviceAccountJson) {
51
+ encrypted.serviceAccountJson = encrypt(credentials.serviceAccountJson);
52
+ encrypted._encrypted = true;
53
+ }
54
+ break;
55
+ }
56
+
57
+ return encrypted;
58
+ }
59
+
60
+ /**
61
+ * Decrypt sensitive credentials based on registry type
62
+ */
63
+ function decryptCredentials(credentials, type) {
64
+ if (!credentials || !credentials._encrypted) return credentials;
65
+
66
+ const decrypted = { ...credentials };
67
+ delete decrypted._encrypted;
68
+
69
+ try {
70
+ switch (type) {
71
+ case "docker-hub":
72
+ case "acr":
73
+ case "custom":
74
+ if (credentials.password) {
75
+ decrypted.password = decrypt(credentials.password);
76
+ }
77
+ break;
78
+ case "ecr":
79
+ if (credentials.secretAccessKey) {
80
+ decrypted.secretAccessKey = decrypt(credentials.secretAccessKey);
81
+ }
82
+ if (credentials.sessionToken) {
83
+ decrypted.sessionToken = decrypt(credentials.sessionToken);
84
+ }
85
+ break;
86
+ case "gcr":
87
+ if (credentials.serviceAccountJson) {
88
+ decrypted.serviceAccountJson = decrypt(
89
+ credentials.serviceAccountJson,
90
+ );
91
+ }
92
+ break;
93
+ }
94
+ } catch (error) {
95
+ console.error(
96
+ chalk.red("❌ Failed to decrypt credentials:", error.message),
97
+ );
98
+ throw new Error(
99
+ "Failed to decrypt credentials. The encryption key may have changed.",
100
+ );
101
+ }
102
+
103
+ return decrypted;
104
+ }
21
105
 
22
106
  /**
23
107
  * Registry Store for managing registry credentials
@@ -44,7 +128,7 @@ class RegistryStore {
44
128
  this.initialized = true;
45
129
  } catch (error) {
46
130
  console.error(
47
- chalk.red("❌ Failed to initialize registry store:", error.message)
131
+ chalk.red("❌ Failed to initialize registry store:", error.message),
48
132
  );
49
133
  throw error;
50
134
  }
@@ -72,13 +156,16 @@ class RegistryStore {
72
156
  const data = await fsPromises.readFile(REGISTRY_FILE, "utf8");
73
157
  const parsed = JSON.parse(data);
74
158
 
75
- if (parsed.version !== SCHEMA_VERSION) {
76
- console.warn(
77
- `⚠️ Registry file version mismatch. Expected ${SCHEMA_VERSION}, got ${parsed.version}`
159
+ // Handle schema migration
160
+ if (parsed.version < SCHEMA_VERSION) {
161
+ console.log(
162
+ chalk.yellow(
163
+ `⚠️ Migrating registry file from version ${parsed.version} to ${SCHEMA_VERSION}`,
164
+ ),
78
165
  );
79
166
  }
80
167
 
81
- // Load registries with plain text credentials
168
+ // Load registries and decrypt credentials
82
169
  let hasActiveRegistry = false;
83
170
 
84
171
  for (const registry of parsed.registries || []) {
@@ -92,12 +179,20 @@ class RegistryStore {
92
179
  hasActiveRegistry = true;
93
180
  }
94
181
 
95
- // Store registry with credentials directly
182
+ // Decrypt credentials if they were encrypted
183
+ if (registry.credentials?._encrypted) {
184
+ registry.credentials = decryptCredentials(
185
+ registry.credentials,
186
+ registry.type,
187
+ );
188
+ }
189
+
190
+ // Store registry with decrypted credentials in memory
96
191
  this.registries.set(registry.id, registry);
97
192
  } catch (error) {
98
193
  console.error(
99
194
  chalk.red(`❌ Failed to load registry ${registry.id}:`),
100
- error.message
195
+ error.message,
101
196
  );
102
197
  // Skip this registry but continue with others
103
198
  }
@@ -123,15 +218,19 @@ class RegistryStore {
123
218
  }
124
219
 
125
220
  /**
126
- * Save registries to file with plain text credentials
221
+ * Save registries to file with encrypted credentials
127
222
  */
128
223
  async saveRegistries() {
129
224
  try {
130
225
  const registriesToSave = [];
131
226
 
132
227
  for (const [id, registry] of this.registries.entries()) {
133
- // Save registry with credentials directly
134
- registriesToSave.push(registry);
228
+ // Create a copy and encrypt credentials before saving
229
+ const registryToSave = {
230
+ ...registry,
231
+ credentials: encryptCredentials(registry.credentials, registry.type),
232
+ };
233
+ registriesToSave.push(registryToSave);
135
234
  }
136
235
 
137
236
  const data = {
@@ -212,6 +311,10 @@ class RegistryStore {
212
311
  // Ensure active field is included (default to false if not set)
213
312
  metadata.active = registry.active || false;
214
313
 
314
+ // Include status and lastValidated fields
315
+ metadata.status = registry.status || "unknown";
316
+ metadata.lastValidated = registry.lastValidated || null;
317
+
215
318
  result.push(metadata);
216
319
  }
217
320
 
@@ -321,6 +424,55 @@ class RegistryStore {
321
424
  const activeRegistry = await this.getActiveRegistry();
322
425
  return activeRegistry ? activeRegistry.id : null;
323
426
  }
427
+
428
+ /**
429
+ * Update credentials for an existing registry
430
+ */
431
+ async updateCredentials(id, newCredentials) {
432
+ await this.initialize();
433
+
434
+ const registry = this.registries.get(id);
435
+ if (!registry) {
436
+ throw new Error("Registry not found");
437
+ }
438
+
439
+ // Update credentials
440
+ registry.credentials = {
441
+ ...registry.credentials,
442
+ ...newCredentials,
443
+ };
444
+ registry.updatedAt = new Date().toISOString();
445
+ registry.lastValidated = new Date().toISOString();
446
+ registry.status = "valid";
447
+
448
+ this.registries.set(id, registry);
449
+ await this.saveRegistries();
450
+
451
+ return registry;
452
+ }
453
+
454
+ /**
455
+ * Update registry status
456
+ */
457
+ async updateRegistryStatus(id, status, lastValidated = null) {
458
+ await this.initialize();
459
+
460
+ const registry = this.registries.get(id);
461
+ if (!registry) {
462
+ throw new Error("Registry not found");
463
+ }
464
+
465
+ registry.status = status;
466
+ if (lastValidated) {
467
+ registry.lastValidated = lastValidated;
468
+ }
469
+ registry.updatedAt = new Date().toISOString();
470
+
471
+ this.registries.set(id, registry);
472
+ await this.saveRegistries();
473
+
474
+ return registry;
475
+ }
324
476
  }
325
477
 
326
478
  // Singleton instance
@@ -334,7 +334,9 @@ async function routeMessage(ws, clientId, data) {
334
334
  action === "disconnectRegistry" ||
335
335
  action === "renameRegistry" ||
336
336
  action === "fetchRegistryImages" ||
337
- action === "setActiveRegistry"
337
+ action === "setActiveRegistry" ||
338
+ action === "validateRegistry" ||
339
+ action === "updateRegistryCredentials"
338
340
  ) {
339
341
  return await registryHandlers.handleRegistryAction(ws, action, payload);
340
342
  }