@dishantlangayan/sc-cli-core 0.1.2 → 0.3.0

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/README.md CHANGED
@@ -20,6 +20,9 @@ The ScCommand abstract class extends [@oclif/core's Command class](https://githu
20
20
  ## ScConnection Class
21
21
  The ScConnection class provide abstraction functions for Solace Cloud API REST calls. It handles the access token and base URL for each REST call, avoiding the need to set these on each Command.
22
22
 
23
+ ## BrokerAuthManager
24
+ The BrokerAuthManager class provides utility functions to store and retrieve broker SEMP management authentication information from user's home directory: `~/.sf/` or `%USERPROFILE%\sf\`. The implementation uses AES-256-GCM for authenticated encryption and provides machine-bound encryption that combines OS-level security (keychain) with machine-specific identifiers, making credentials non-transferable between machines.
25
+
23
26
  # Contributing
24
27
  Contributions are encouraged! Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us.
25
28
 
@@ -0,0 +1,40 @@
1
+ import { type BrokerAuthStorage, type EncryptedData } from './auth-types.js';
2
+ /**
3
+ * Encryption utility for broker authentication storage
4
+ * Uses AES-256-GCM for authenticated encryption
5
+ */
6
+ export declare class BrokerAuthEncryption {
7
+ private static readonly ALGORITHM;
8
+ private static readonly DIGEST;
9
+ private static readonly ITERATIONS;
10
+ private static readonly IV_LENGTH;
11
+ private static readonly KEY_LENGTH;
12
+ private static readonly SALT_LENGTH;
13
+ private static readonly TAG_LENGTH;
14
+ /**
15
+ * Decrypt broker storage data
16
+ * @param encryptedData - Encrypted data to decrypt
17
+ * @param key - Decryption key
18
+ * @returns Decrypted broker storage
19
+ */
20
+ static decrypt(encryptedData: EncryptedData, key: Buffer): Promise<BrokerAuthStorage>;
21
+ /**
22
+ * Derive encryption key from password using PBKDF2
23
+ * @param password - User password
24
+ * @param salt - Salt for key derivation
25
+ * @returns Derived encryption key
26
+ */
27
+ static deriveKey(password: string, salt: Buffer): Promise<Buffer>;
28
+ /**
29
+ * Encrypt broker storage data
30
+ * @param data - Broker storage to encrypt
31
+ * @param key - Encryption key
32
+ * @returns Encrypted data with metadata
33
+ */
34
+ static encrypt(data: BrokerAuthStorage, key: Buffer): Promise<EncryptedData>;
35
+ /**
36
+ * Generate cryptographically secure random salt
37
+ * @returns Random salt buffer
38
+ */
39
+ static generateSalt(): Buffer;
40
+ }
@@ -0,0 +1,106 @@
1
+ import { createCipheriv, createDecipheriv, pbkdf2, randomBytes } from 'node:crypto';
2
+ import { promisify } from 'node:util';
3
+ import { BrokerAuthError, BrokerAuthErrorCode } from './auth-types.js';
4
+ const pbkdf2Async = promisify(pbkdf2);
5
+ /**
6
+ * Encryption utility for broker authentication storage
7
+ * Uses AES-256-GCM for authenticated encryption
8
+ */
9
+ export class BrokerAuthEncryption {
10
+ static ALGORITHM = 'aes-256-gcm';
11
+ static DIGEST = 'sha256';
12
+ static ITERATIONS = 100_000; // OWASP recommended minimum for PBKDF2
13
+ static IV_LENGTH = 16;
14
+ static KEY_LENGTH = 32; // 256 bits
15
+ static SALT_LENGTH = 32;
16
+ static TAG_LENGTH = 16;
17
+ /**
18
+ * Decrypt broker storage data
19
+ * @param encryptedData - Encrypted data to decrypt
20
+ * @param key - Decryption key
21
+ * @returns Decrypted broker storage
22
+ */
23
+ static async decrypt(encryptedData, key) {
24
+ try {
25
+ // Parse metadata and buffers
26
+ const iv = Buffer.from(encryptedData.iv, 'base64');
27
+ const authTag = Buffer.from(encryptedData.authTag, 'base64');
28
+ // Create decipher
29
+ const decipher = createDecipheriv(this.ALGORITHM, key, iv);
30
+ decipher.setAuthTag(authTag);
31
+ // Decrypt data
32
+ let decrypted = decipher.update(encryptedData.encryptedContent, 'base64', 'utf8');
33
+ decrypted += decipher.final('utf8');
34
+ // Parse JSON
35
+ const storage = JSON.parse(decrypted);
36
+ return storage;
37
+ }
38
+ catch (error) {
39
+ throw new BrokerAuthError('Failed to decrypt broker storage. The password may be incorrect or the file may be corrupted.', BrokerAuthErrorCode.DECRYPTION_FAILED, error);
40
+ }
41
+ }
42
+ /**
43
+ * Derive encryption key from password using PBKDF2
44
+ * @param password - User password
45
+ * @param salt - Salt for key derivation
46
+ * @returns Derived encryption key
47
+ */
48
+ static async deriveKey(password, salt) {
49
+ try {
50
+ if (!password || password.length === 0) {
51
+ throw new BrokerAuthError('Password cannot be empty', BrokerAuthErrorCode.INVALID_PASSWORD);
52
+ }
53
+ return await pbkdf2Async(password, salt, this.ITERATIONS, this.KEY_LENGTH, this.DIGEST);
54
+ }
55
+ catch (error) {
56
+ if (error instanceof BrokerAuthError) {
57
+ throw error;
58
+ }
59
+ throw new BrokerAuthError('Failed to derive encryption key', BrokerAuthErrorCode.ENCRYPTION_FAILED, error);
60
+ }
61
+ }
62
+ /**
63
+ * Encrypt broker storage data
64
+ * @param data - Broker storage to encrypt
65
+ * @param key - Encryption key
66
+ * @returns Encrypted data with metadata
67
+ */
68
+ static async encrypt(data, key) {
69
+ try {
70
+ // Generate random IV
71
+ const iv = randomBytes(this.IV_LENGTH);
72
+ // Create cipher
73
+ const cipher = createCipheriv(this.ALGORITHM, key, iv);
74
+ // Encrypt data
75
+ const plaintext = JSON.stringify(data);
76
+ let encrypted = cipher.update(plaintext, 'utf8', 'base64');
77
+ encrypted += cipher.final('base64');
78
+ // Get authentication tag
79
+ const authTag = cipher.getAuthTag();
80
+ // Generate new salt for next key derivation
81
+ const salt = this.generateSalt();
82
+ return {
83
+ authTag: authTag.toString('base64'),
84
+ encryptedContent: encrypted,
85
+ iv: iv.toString('base64'),
86
+ metadata: {
87
+ algorithm: this.ALGORITHM,
88
+ iterations: this.ITERATIONS,
89
+ keyDerivation: 'pbkdf2',
90
+ saltLength: this.SALT_LENGTH,
91
+ },
92
+ salt: salt.toString('base64'),
93
+ };
94
+ }
95
+ catch (error) {
96
+ throw new BrokerAuthError('Failed to encrypt broker storage', BrokerAuthErrorCode.ENCRYPTION_FAILED, error);
97
+ }
98
+ }
99
+ /**
100
+ * Generate cryptographically secure random salt
101
+ * @returns Random salt buffer
102
+ */
103
+ static generateSalt() {
104
+ return randomBytes(this.SALT_LENGTH);
105
+ }
106
+ }
@@ -0,0 +1,98 @@
1
+ import { ScConnection } from '../util/sc-connection.js';
2
+ import { type BrokerAuth } from './auth-types.js';
3
+ import { KeychainService } from './keychain.js';
4
+ /**
5
+ * Manager for broker authentication storage
6
+ * Handles encrypted storage of broker credentials
7
+ */
8
+ export declare class BrokerAuthManager {
9
+ private static instance;
10
+ private readonly configDir;
11
+ private readonly configFile;
12
+ private encryptionKey;
13
+ private readonly keychainService;
14
+ private machineId;
15
+ private masterKey;
16
+ private storage;
17
+ private constructor();
18
+ /**
19
+ * Get singleton instance
20
+ * @param keychainService - Optional keychain service for testing
21
+ */
22
+ static getInstance(keychainService?: KeychainService): BrokerAuthManager;
23
+ /**
24
+ * Add a new broker configuration
25
+ * @param broker - Broker authentication configuration
26
+ */
27
+ addBroker(broker: BrokerAuth): Promise<void>;
28
+ /**
29
+ * Check if broker exists
30
+ * @param name - Broker name
31
+ * @returns true if broker exists
32
+ */
33
+ brokerExists(name: string): Promise<boolean>;
34
+ /**
35
+ * Clear all broker configurations
36
+ */
37
+ clearAll(): Promise<void>;
38
+ /**
39
+ * Create ScConnection instance from stored broker config
40
+ * @param brokerName - Name of the broker to connect to
41
+ * @param timeout - Optional timeout override
42
+ * @returns Configured ScConnection instance
43
+ */
44
+ createConnection(brokerName: string, timeout?: number): Promise<ScConnection>;
45
+ /**
46
+ * Get all broker configurations
47
+ * @returns Array of all broker configurations
48
+ */
49
+ getAllBrokers(): Promise<BrokerAuth[]>;
50
+ /**
51
+ * Get broker configuration by name
52
+ * @param name - Broker name/alias
53
+ * @returns Broker configuration or null if not found
54
+ */
55
+ getBroker(name: string): Promise<BrokerAuth | null>;
56
+ /**
57
+ * Initialize the auth manager with encryption key derived from OS keychain and machine ID
58
+ */
59
+ initialize(): Promise<void>;
60
+ /**
61
+ * List all broker names
62
+ * @returns Array of broker names
63
+ */
64
+ listBrokers(): Promise<string[]>;
65
+ /**
66
+ * Remove broker configuration
67
+ * @param name - Broker name to remove
68
+ */
69
+ removeBroker(name: string): Promise<void>;
70
+ /**
71
+ * Update existing broker configuration
72
+ * @param name - Broker name to update
73
+ * @param updates - Partial updates to apply
74
+ */
75
+ updateBroker(name: string, updates: Partial<Omit<BrokerAuth, 'name'>>): Promise<void>;
76
+ /**
77
+ * Ensure manager is initialized
78
+ */
79
+ private ensureInitialized;
80
+ /**
81
+ * Check if config file exists
82
+ */
83
+ private fileExists;
84
+ /**
85
+ * Load storage from encrypted file
86
+ * @param combinedKey - Combined master key and machine ID for decryption
87
+ */
88
+ private loadStorage;
89
+ /**
90
+ * Save storage to encrypted file
91
+ */
92
+ private saveStorage;
93
+ /**
94
+ * Validate broker configuration
95
+ * @param broker - Broker to validate
96
+ */
97
+ private validateBroker;
98
+ }
@@ -0,0 +1,309 @@
1
+ import { mkdir, readFile, rename, unlink, writeFile } from 'node:fs/promises';
2
+ import { homedir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { ScConnection } from '../util/sc-connection.js';
5
+ import { BrokerAuthEncryption } from './auth-encryption.js';
6
+ import { AuthType, BrokerAuthError, BrokerAuthErrorCode, } from './auth-types.js';
7
+ import { KeychainService } from './keychain.js';
8
+ const SERVICE_NAME = 'local';
9
+ const KEY_NAME = 'sc-cli';
10
+ /**
11
+ * Manager for broker authentication storage
12
+ * Handles encrypted storage of broker credentials
13
+ */
14
+ export class BrokerAuthManager {
15
+ static instance = null;
16
+ configDir;
17
+ configFile;
18
+ encryptionKey = null;
19
+ keychainService;
20
+ machineId = null;
21
+ masterKey = null;
22
+ storage = null;
23
+ constructor(keychainService) {
24
+ const homeDirectory = homedir();
25
+ this.configDir = join(homeDirectory, '.sc');
26
+ this.configFile = join(this.configDir, 'brokers.json');
27
+ this.keychainService = keychainService ?? new KeychainService();
28
+ }
29
+ /**
30
+ * Get singleton instance
31
+ * @param keychainService - Optional keychain service for testing
32
+ */
33
+ static getInstance(keychainService) {
34
+ if (!BrokerAuthManager.instance) {
35
+ BrokerAuthManager.instance = new BrokerAuthManager(keychainService);
36
+ }
37
+ return BrokerAuthManager.instance;
38
+ }
39
+ /**
40
+ * Add a new broker configuration
41
+ * @param broker - Broker authentication configuration
42
+ */
43
+ async addBroker(broker) {
44
+ this.ensureInitialized();
45
+ // Validate broker
46
+ this.validateBroker(broker);
47
+ // Check if broker already exists
48
+ const existing = this.storage.brokers.find((b) => b.name === broker.name);
49
+ if (existing) {
50
+ throw new BrokerAuthError(`Broker '${broker.name}' already exists`, BrokerAuthErrorCode.BROKER_ALREADY_EXISTS);
51
+ }
52
+ // Add broker
53
+ this.storage.brokers.push(broker);
54
+ // Save to file
55
+ await this.saveStorage();
56
+ }
57
+ /**
58
+ * Check if broker exists
59
+ * @param name - Broker name
60
+ * @returns true if broker exists
61
+ */
62
+ async brokerExists(name) {
63
+ this.ensureInitialized();
64
+ return this.storage.brokers.some((b) => b.name === name);
65
+ }
66
+ /**
67
+ * Clear all broker configurations
68
+ */
69
+ async clearAll() {
70
+ this.ensureInitialized();
71
+ this.storage.brokers = [];
72
+ await this.saveStorage();
73
+ }
74
+ /**
75
+ * Create ScConnection instance from stored broker config
76
+ * @param brokerName - Name of the broker to connect to
77
+ * @param timeout - Optional timeout override
78
+ * @returns Configured ScConnection instance
79
+ */
80
+ async createConnection(brokerName, timeout = 10_000) {
81
+ this.ensureInitialized();
82
+ const broker = await this.getBroker(brokerName);
83
+ if (!broker) {
84
+ throw new BrokerAuthError(`Broker '${brokerName}' not found`, BrokerAuthErrorCode.BROKER_NOT_FOUND);
85
+ }
86
+ const baseURL = `${broker.sempEndpoint}:${broker.sempPort}`;
87
+ const accessToken = broker.authType === AuthType.OAUTH ? broker.accessToken : broker.encodedCredentials;
88
+ const isBasic = broker.authType === AuthType.BASIC;
89
+ return new ScConnection(baseURL, accessToken, timeout, isBasic);
90
+ }
91
+ /**
92
+ * Get all broker configurations
93
+ * @returns Array of all broker configurations
94
+ */
95
+ async getAllBrokers() {
96
+ this.ensureInitialized();
97
+ return [...this.storage.brokers];
98
+ }
99
+ /**
100
+ * Get broker configuration by name
101
+ * @param name - Broker name/alias
102
+ * @returns Broker configuration or null if not found
103
+ */
104
+ async getBroker(name) {
105
+ this.ensureInitialized();
106
+ const broker = this.storage.brokers.find((b) => b.name === name);
107
+ return broker ?? null;
108
+ }
109
+ /**
110
+ * Initialize the auth manager with encryption key derived from OS keychain and machine ID
111
+ */
112
+ async initialize() {
113
+ try {
114
+ // Get machine ID
115
+ this.machineId = this.keychainService.getMachineId();
116
+ // Get or create master key from OS keychain
117
+ this.masterKey = await this.keychainService.getPassword(KEY_NAME, SERVICE_NAME);
118
+ if (!this.masterKey) {
119
+ // Generate new master key and store in OS keychain
120
+ this.masterKey = this.keychainService.generateMasterKey();
121
+ await this.keychainService.setPassword(KEY_NAME, SERVICE_NAME, this.masterKey);
122
+ }
123
+ // Combine master key with machine ID for encryption
124
+ const combinedKey = `${this.masterKey}:${this.machineId}`;
125
+ // Try to load existing storage
126
+ const fileExists = await this.fileExists();
127
+ if (fileExists) {
128
+ // Load existing file and derive key from stored salt
129
+ await this.loadStorage(combinedKey);
130
+ }
131
+ else {
132
+ // Create new storage with new salt
133
+ const salt = BrokerAuthEncryption.generateSalt();
134
+ this.encryptionKey = await BrokerAuthEncryption.deriveKey(combinedKey, salt);
135
+ this.storage = {
136
+ brokers: [],
137
+ version: '1.0.0',
138
+ };
139
+ }
140
+ }
141
+ catch (error) {
142
+ if (error instanceof BrokerAuthError) {
143
+ throw error;
144
+ }
145
+ throw new BrokerAuthError('Failed to initialize broker auth manager', BrokerAuthErrorCode.NOT_INITIALIZED, error);
146
+ }
147
+ }
148
+ /**
149
+ * List all broker names
150
+ * @returns Array of broker names
151
+ */
152
+ async listBrokers() {
153
+ this.ensureInitialized();
154
+ return this.storage.brokers.map((b) => b.name);
155
+ }
156
+ /**
157
+ * Remove broker configuration
158
+ * @param name - Broker name to remove
159
+ */
160
+ async removeBroker(name) {
161
+ this.ensureInitialized();
162
+ const index = this.storage.brokers.findIndex((b) => b.name === name);
163
+ if (index === -1) {
164
+ throw new BrokerAuthError(`Broker '${name}' not found`, BrokerAuthErrorCode.BROKER_NOT_FOUND);
165
+ }
166
+ // Remove broker
167
+ this.storage.brokers.splice(index, 1);
168
+ // Save to file
169
+ await this.saveStorage();
170
+ }
171
+ /**
172
+ * Update existing broker configuration
173
+ * @param name - Broker name to update
174
+ * @param updates - Partial updates to apply
175
+ */
176
+ async updateBroker(name, updates) {
177
+ this.ensureInitialized();
178
+ const index = this.storage.brokers.findIndex((b) => b.name === name);
179
+ if (index === -1) {
180
+ throw new BrokerAuthError(`Broker '${name}' not found`, BrokerAuthErrorCode.BROKER_NOT_FOUND);
181
+ }
182
+ // Merge updates
183
+ const updated = {
184
+ ...this.storage.brokers[index],
185
+ ...updates,
186
+ name, // Ensure name doesn't change
187
+ };
188
+ // Validate updated broker
189
+ this.validateBroker(updated);
190
+ // Update broker
191
+ this.storage.brokers[index] = updated;
192
+ // Save to file
193
+ await this.saveStorage();
194
+ }
195
+ /**
196
+ * Ensure manager is initialized
197
+ */
198
+ ensureInitialized() {
199
+ if (!this.encryptionKey || !this.storage) {
200
+ throw new BrokerAuthError('BrokerAuthManager not initialized. Call initialize() first.', BrokerAuthErrorCode.NOT_INITIALIZED);
201
+ }
202
+ }
203
+ /**
204
+ * Check if config file exists
205
+ */
206
+ async fileExists() {
207
+ try {
208
+ await readFile(this.configFile);
209
+ return true;
210
+ }
211
+ catch {
212
+ return false;
213
+ }
214
+ }
215
+ /**
216
+ * Load storage from encrypted file
217
+ * @param combinedKey - Combined master key and machine ID for decryption
218
+ */
219
+ async loadStorage(combinedKey) {
220
+ try {
221
+ const fileContent = await readFile(this.configFile, 'utf8');
222
+ const encryptedData = JSON.parse(fileContent);
223
+ // Derive key from combined key and stored salt
224
+ const salt = Buffer.from(encryptedData.salt, 'base64');
225
+ this.encryptionKey = await BrokerAuthEncryption.deriveKey(combinedKey, salt);
226
+ // Decrypt storage
227
+ this.storage = await BrokerAuthEncryption.decrypt(encryptedData, this.encryptionKey);
228
+ }
229
+ catch (error) {
230
+ if (error instanceof BrokerAuthError) {
231
+ throw error;
232
+ }
233
+ throw new BrokerAuthError('Failed to load broker storage', BrokerAuthErrorCode.FILE_READ_ERROR, error);
234
+ }
235
+ }
236
+ /**
237
+ * Save storage to encrypted file
238
+ */
239
+ async saveStorage() {
240
+ try {
241
+ if (!this.masterKey || !this.machineId) {
242
+ throw new BrokerAuthError('Auth manager not initialized', BrokerAuthErrorCode.NOT_INITIALIZED);
243
+ }
244
+ // Ensure directory exists
245
+ await mkdir(this.configDir, { mode: 0o700, recursive: true });
246
+ // Encrypt data
247
+ const encrypted = await BrokerAuthEncryption.encrypt(this.storage, this.encryptionKey);
248
+ // Re-derive key with new salt for next save
249
+ const combinedKey = `${this.masterKey}:${this.machineId}`;
250
+ const newSalt = Buffer.from(encrypted.salt, 'base64');
251
+ this.encryptionKey = await BrokerAuthEncryption.deriveKey(combinedKey, newSalt);
252
+ // Write to temp file first (atomic write)
253
+ const jsonData = JSON.stringify(encrypted, null, 2);
254
+ const tempFile = `${this.configFile}.tmp`;
255
+ await writeFile(tempFile, jsonData, { mode: 0o600 });
256
+ // Atomic rename
257
+ await rename(tempFile, this.configFile);
258
+ // Set restrictive permissions (Unix only)
259
+ if (process.platform !== 'win32') {
260
+ // Already set via writeFile mode option
261
+ }
262
+ }
263
+ catch (error) {
264
+ // Clean up temp file if it exists
265
+ try {
266
+ await unlink(`${this.configFile}.tmp`);
267
+ }
268
+ catch {
269
+ // Ignore cleanup errors
270
+ }
271
+ if (error instanceof BrokerAuthError) {
272
+ throw error;
273
+ }
274
+ throw new BrokerAuthError('Failed to save broker storage', BrokerAuthErrorCode.FILE_WRITE_ERROR, error);
275
+ }
276
+ }
277
+ /**
278
+ * Validate broker configuration
279
+ * @param broker - Broker to validate
280
+ */
281
+ validateBroker(broker) {
282
+ // Validate name
283
+ if (!broker.name || broker.name.trim() === '') {
284
+ throw new BrokerAuthError('Broker name is required', BrokerAuthErrorCode.INVALID_NAME);
285
+ }
286
+ // Validate endpoint
287
+ if (!broker.sempEndpoint || !(broker.sempEndpoint.startsWith('http://') || broker.sempEndpoint.startsWith('https://'))) {
288
+ throw new BrokerAuthError('SEMP endpoint must start with http:// or https://', BrokerAuthErrorCode.INVALID_ENDPOINT);
289
+ }
290
+ // Validate port
291
+ if (broker.sempPort < 1 || broker.sempPort > 65_535) {
292
+ throw new BrokerAuthError('SEMP port must be between 1 and 65535', BrokerAuthErrorCode.INVALID_PORT);
293
+ }
294
+ // Validate auth-specific fields
295
+ if (broker.authType === AuthType.OAUTH) {
296
+ if (!broker.accessToken || !broker.clientId) {
297
+ throw new BrokerAuthError('OAuth brokers require accessToken and clientId', BrokerAuthErrorCode.INVALID_OAUTH_CONFIG);
298
+ }
299
+ }
300
+ else if (broker.authType === AuthType.BASIC) {
301
+ if (!broker.encodedCredentials) {
302
+ throw new BrokerAuthError('Basic auth brokers require encodedCredentials', BrokerAuthErrorCode.INVALID_BASIC_CONFIG);
303
+ }
304
+ }
305
+ else {
306
+ throw new BrokerAuthError('Invalid auth type', BrokerAuthErrorCode.INVALID_AUTH_TYPE);
307
+ }
308
+ }
309
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Authentication types supported by the broker auth system
3
+ */
4
+ export declare enum AuthType {
5
+ BASIC = "basic",
6
+ OAUTH = "oauth"
7
+ }
8
+ /**
9
+ * Base broker authentication configuration
10
+ */
11
+ export interface BrokerAuthBase {
12
+ authType: AuthType;
13
+ name: string;
14
+ sempEndpoint: string;
15
+ sempPort: number;
16
+ }
17
+ /**
18
+ * OAuth broker authentication configuration
19
+ */
20
+ export interface OAuthBrokerAuth extends BrokerAuthBase {
21
+ accessToken: string;
22
+ authType: AuthType.OAUTH;
23
+ clientId: string;
24
+ refreshToken: string;
25
+ }
26
+ /**
27
+ * Basic authentication broker configuration
28
+ */
29
+ export interface BasicBrokerAuth extends BrokerAuthBase {
30
+ authType: AuthType.BASIC;
31
+ encodedCredentials: string;
32
+ }
33
+ /**
34
+ * Union type for broker authentication
35
+ */
36
+ export type BrokerAuth = BasicBrokerAuth | OAuthBrokerAuth;
37
+ /**
38
+ * Storage format for broker configurations
39
+ */
40
+ export interface BrokerAuthStorage {
41
+ brokers: BrokerAuth[];
42
+ version: string;
43
+ }
44
+ /**
45
+ * Encryption metadata stored alongside encrypted data
46
+ */
47
+ export interface EncryptionMetadata {
48
+ algorithm: string;
49
+ iterations: number;
50
+ keyDerivation: string;
51
+ saltLength: number;
52
+ }
53
+ /**
54
+ * Encrypted data structure
55
+ */
56
+ export interface EncryptedData {
57
+ authTag: string;
58
+ encryptedContent: string;
59
+ iv: string;
60
+ metadata: EncryptionMetadata;
61
+ salt: string;
62
+ }
63
+ /**
64
+ * Error codes for broker auth operations
65
+ */
66
+ export declare enum BrokerAuthErrorCode {
67
+ BROKER_ALREADY_EXISTS = "BROKER_ALREADY_EXISTS",
68
+ BROKER_NOT_FOUND = "BROKER_NOT_FOUND",
69
+ DECRYPTION_FAILED = "DECRYPTION_FAILED",
70
+ ENCRYPTION_FAILED = "ENCRYPTION_FAILED",
71
+ FILE_READ_ERROR = "FILE_READ_ERROR",
72
+ FILE_WRITE_ERROR = "FILE_WRITE_ERROR",
73
+ INVALID_AUTH_TYPE = "INVALID_AUTH_TYPE",
74
+ INVALID_BASIC_CONFIG = "INVALID_BASIC_CONFIG",
75
+ INVALID_ENDPOINT = "INVALID_ENDPOINT",
76
+ INVALID_NAME = "INVALID_NAME",
77
+ INVALID_OAUTH_CONFIG = "INVALID_OAUTH_CONFIG",
78
+ INVALID_PASSWORD = "INVALID_PASSWORD",
79
+ INVALID_PORT = "INVALID_PORT",
80
+ NOT_INITIALIZED = "NOT_INITIALIZED"
81
+ }
82
+ /**
83
+ * Custom error class for broker authentication operations
84
+ */
85
+ export declare class BrokerAuthError extends Error {
86
+ readonly code: BrokerAuthErrorCode;
87
+ readonly cause?: Error | undefined;
88
+ constructor(message: string, code: BrokerAuthErrorCode, cause?: Error | undefined);
89
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Authentication types supported by the broker auth system
3
+ */
4
+ export var AuthType;
5
+ (function (AuthType) {
6
+ AuthType["BASIC"] = "basic";
7
+ AuthType["OAUTH"] = "oauth";
8
+ })(AuthType || (AuthType = {}));
9
+ /**
10
+ * Error codes for broker auth operations
11
+ */
12
+ export var BrokerAuthErrorCode;
13
+ (function (BrokerAuthErrorCode) {
14
+ BrokerAuthErrorCode["BROKER_ALREADY_EXISTS"] = "BROKER_ALREADY_EXISTS";
15
+ BrokerAuthErrorCode["BROKER_NOT_FOUND"] = "BROKER_NOT_FOUND";
16
+ BrokerAuthErrorCode["DECRYPTION_FAILED"] = "DECRYPTION_FAILED";
17
+ BrokerAuthErrorCode["ENCRYPTION_FAILED"] = "ENCRYPTION_FAILED";
18
+ BrokerAuthErrorCode["FILE_READ_ERROR"] = "FILE_READ_ERROR";
19
+ BrokerAuthErrorCode["FILE_WRITE_ERROR"] = "FILE_WRITE_ERROR";
20
+ BrokerAuthErrorCode["INVALID_AUTH_TYPE"] = "INVALID_AUTH_TYPE";
21
+ BrokerAuthErrorCode["INVALID_BASIC_CONFIG"] = "INVALID_BASIC_CONFIG";
22
+ BrokerAuthErrorCode["INVALID_ENDPOINT"] = "INVALID_ENDPOINT";
23
+ BrokerAuthErrorCode["INVALID_NAME"] = "INVALID_NAME";
24
+ BrokerAuthErrorCode["INVALID_OAUTH_CONFIG"] = "INVALID_OAUTH_CONFIG";
25
+ BrokerAuthErrorCode["INVALID_PASSWORD"] = "INVALID_PASSWORD";
26
+ BrokerAuthErrorCode["INVALID_PORT"] = "INVALID_PORT";
27
+ BrokerAuthErrorCode["NOT_INITIALIZED"] = "NOT_INITIALIZED";
28
+ })(BrokerAuthErrorCode || (BrokerAuthErrorCode = {}));
29
+ /**
30
+ * Custom error class for broker authentication operations
31
+ */
32
+ export class BrokerAuthError extends Error {
33
+ code;
34
+ cause;
35
+ constructor(message, code, cause) {
36
+ super(message);
37
+ this.code = code;
38
+ this.cause = cause;
39
+ this.name = 'BrokerAuthError';
40
+ }
41
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Broker authentication management module
3
+ * Provides encrypted storage for SEMP broker credentials
4
+ */
5
+ export { BrokerAuthEncryption } from './auth-encryption.js';
6
+ export { BrokerAuthManager } from './auth-manager.js';
7
+ export { AuthType, type BasicBrokerAuth, type BrokerAuth, type BrokerAuthBase, BrokerAuthError, BrokerAuthErrorCode, type BrokerAuthStorage, type EncryptedData, type EncryptionMetadata, type OAuthBrokerAuth, } from './auth-types.js';
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Broker authentication management module
3
+ * Provides encrypted storage for SEMP broker credentials
4
+ */
5
+ export { BrokerAuthEncryption } from './auth-encryption.js';
6
+ export { BrokerAuthManager } from './auth-manager.js';
7
+ export { AuthType, BrokerAuthError, BrokerAuthErrorCode, } from './auth-types.js';
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Keychain service for storing and retrieving encryption keys
3
+ * This abstraction allows for easier testing and mocking
4
+ */
5
+ export declare class KeychainService {
6
+ /**
7
+ * Delete a password from the keychain
8
+ * @param service - Service name
9
+ * @param account - Account name
10
+ * @returns True if deleted, false if not found
11
+ */
12
+ deletePassword(service: string, account: string): Promise<boolean>;
13
+ /**
14
+ * Generate a random master key
15
+ * @returns Base64-encoded random key
16
+ */
17
+ generateMasterKey(): string;
18
+ /**
19
+ * Get unique machine identifier
20
+ * @returns Machine ID string
21
+ */
22
+ getMachineId(): string;
23
+ /**
24
+ * Get a password from the keychain
25
+ * @param service - Service name
26
+ * @param account - Account name
27
+ * @returns Password string or null if not found
28
+ */
29
+ getPassword(service: string, account: string): Promise<null | string>;
30
+ /**
31
+ * Set a password in the keychain
32
+ * @param service - Service name
33
+ * @param account - Account name
34
+ * @param password - Password to store
35
+ */
36
+ setPassword(service: string, account: string, password: string): Promise<void>;
37
+ /**
38
+ * Lazy load keytar module
39
+ * @returns keytar module
40
+ */
41
+ private loadKeytar;
42
+ /**
43
+ * Lazy load node-machine-id module
44
+ * @returns node-machine-id module
45
+ */
46
+ private loadMachineId;
47
+ }
@@ -0,0 +1,71 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ import { createRequire } from 'node:module';
3
+ const require = createRequire(import.meta.url);
4
+ /**
5
+ * Keychain service for storing and retrieving encryption keys
6
+ * This abstraction allows for easier testing and mocking
7
+ */
8
+ export class KeychainService {
9
+ /**
10
+ * Delete a password from the keychain
11
+ * @param service - Service name
12
+ * @param account - Account name
13
+ * @returns True if deleted, false if not found
14
+ */
15
+ async deletePassword(service, account) {
16
+ const keytar = await this.loadKeytar();
17
+ return keytar.deletePassword(service, account);
18
+ }
19
+ /**
20
+ * Generate a random master key
21
+ * @returns Base64-encoded random key
22
+ */
23
+ generateMasterKey() {
24
+ return randomBytes(32).toString('base64');
25
+ }
26
+ /**
27
+ * Get unique machine identifier
28
+ * @returns Machine ID string
29
+ */
30
+ getMachineId() {
31
+ const machineIdPkg = this.loadMachineId();
32
+ return machineIdPkg.machineIdSync();
33
+ }
34
+ /**
35
+ * Get a password from the keychain
36
+ * @param service - Service name
37
+ * @param account - Account name
38
+ * @returns Password string or null if not found
39
+ */
40
+ async getPassword(service, account) {
41
+ const keytar = await this.loadKeytar();
42
+ return keytar.getPassword(service, account);
43
+ }
44
+ /**
45
+ * Set a password in the keychain
46
+ * @param service - Service name
47
+ * @param account - Account name
48
+ * @param password - Password to store
49
+ */
50
+ async setPassword(service, account, password) {
51
+ const keytar = await this.loadKeytar();
52
+ return keytar.setPassword(service, account, password);
53
+ }
54
+ /**
55
+ * Lazy load keytar module
56
+ * @returns keytar module
57
+ */
58
+ async loadKeytar() {
59
+ const keytar = await import('keytar');
60
+ return keytar.default;
61
+ }
62
+ /**
63
+ * Lazy load node-machine-id module
64
+ * @returns node-machine-id module
65
+ */
66
+ loadMachineId() {
67
+ // Use require for CommonJS module (node-machine-id)
68
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
69
+ return require('node-machine-id');
70
+ }
71
+ }
package/lib/exported.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export { AuthType, type BasicBrokerAuth, type BrokerAuth, BrokerAuthError, BrokerAuthErrorCode, BrokerAuthManager, type OAuthBrokerAuth, } from './auth/index.js';
1
2
  export { EnvironmentVariable, envVars } from './config/env-vars.js';
2
3
  export { ScCommand } from './sc-command.js';
3
4
  export { ScConnection } from './util/sc-connection.js';
package/lib/exported.js CHANGED
@@ -1,3 +1,4 @@
1
+ export { AuthType, BrokerAuthError, BrokerAuthErrorCode, BrokerAuthManager, } from './auth/index.js';
1
2
  export { EnvironmentVariable, envVars } from './config/env-vars.js';
2
3
  export { ScCommand } from './sc-command.js';
3
4
  export { ScConnection } from './util/sc-connection.js';
@@ -2,7 +2,7 @@ import { AxiosRequestConfig } from 'axios';
2
2
  export declare class ScConnection {
3
3
  private axiosInstance;
4
4
  private endpointUrl;
5
- constructor(baseURL?: string, accessToken?: string, timeout?: number, semp?: boolean);
5
+ constructor(baseURL?: string, accessToken?: string, timeout?: number, basic?: boolean);
6
6
  delete<T>(url: string, config?: AxiosRequestConfig): Promise<T>;
7
7
  get<T>(url: string, config?: AxiosRequestConfig): Promise<T>;
8
8
  patch<T>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T>;
@@ -3,16 +3,16 @@ import { DefaultBaseUrl, EnvironmentVariable, envVars } from '../config/env-vars
3
3
  export class ScConnection {
4
4
  axiosInstance;
5
5
  endpointUrl = '';
6
- constructor(baseURL = envVars.getString(EnvironmentVariable.SC_BASE_URL, DefaultBaseUrl), accessToken = envVars.getString(EnvironmentVariable.SC_ACCESS_TOKEN, ''), timeout = 10_000, semp = false) {
6
+ constructor(baseURL = envVars.getString(EnvironmentVariable.SC_BASE_URL, DefaultBaseUrl), accessToken = envVars.getString(EnvironmentVariable.SC_ACCESS_TOKEN, ''), timeout = 10_000, basic = false) {
7
7
  const apiVersion = envVars.getString(EnvironmentVariable.SC_API_VERSION, 'v2');
8
8
  const sempApiVersion = envVars.getString(EnvironmentVariable.SEMP_API_VERSION, 'v2');
9
- this.endpointUrl = semp
9
+ this.endpointUrl = basic
10
10
  ? this.joinPaths(baseURL, `/SEMP/${sempApiVersion}`)
11
11
  : this.joinPaths(baseURL, `/api/${apiVersion}`);
12
12
  this.axiosInstance = axios.create({
13
13
  baseURL: this.endpointUrl,
14
14
  headers: {
15
- Authorization: semp ? `Basic ${accessToken}` : `Bearer ${accessToken}`,
15
+ Authorization: basic ? `Basic ${accessToken}` : `Bearer ${accessToken}`,
16
16
  'Content-Type': 'application/json',
17
17
  },
18
18
  timeout,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dishantlangayan/sc-cli-core",
3
- "version": "0.1.2",
3
+ "version": "0.3.0",
4
4
  "description": "Base library for the Solace Cloud CLI and plugins",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Dishant Langayan",
@@ -34,10 +34,12 @@
34
34
  "@oclif/prettier-config": "^0.2.1",
35
35
  "@oclif/test": "^4.1.16",
36
36
  "@types/chai": "^5.2.3",
37
+ "@types/chai-as-promised": "^8.0.2",
37
38
  "@types/mocha": "^10.0.10",
38
39
  "@types/node": "^25.0.3",
39
40
  "@types/sinon": "^21.0.0",
40
41
  "chai": "^6.2.2",
42
+ "chai-as-promised": "^8.0.2",
41
43
  "eslint": "^9",
42
44
  "eslint-config-oclif": "^6.0.137",
43
45
  "eslint-config-prettier": "^10",
@@ -52,6 +54,8 @@
52
54
  "dependencies": {
53
55
  "@oclif/core": "^4.8.0",
54
56
  "axios": "^1.13.2",
57
+ "keytar": "^7.9.0",
58
+ "node-machine-id": "^1.1.12",
55
59
  "table": "^6.9.0"
56
60
  }
57
61
  }