@dishantlangayan/sc-cli-core 0.1.2 → 0.2.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/lib/auth/auth-encryption.d.ts +40 -0
- package/lib/auth/auth-encryption.js +106 -0
- package/lib/auth/auth-manager.d.ts +95 -0
- package/lib/auth/auth-manager.js +293 -0
- package/lib/auth/auth-types.d.ts +89 -0
- package/lib/auth/auth-types.js +41 -0
- package/lib/auth/index.d.ts +7 -0
- package/lib/auth/index.js +7 -0
- package/lib/exported.d.ts +1 -0
- package/lib/exported.js +1 -0
- package/lib/util/sc-connection.d.ts +1 -1
- package/lib/util/sc-connection.js +3 -3
- package/package.json +3 -1
|
@@ -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,95 @@
|
|
|
1
|
+
import { ScConnection } from '../util/sc-connection.js';
|
|
2
|
+
import { type BrokerAuth } from './auth-types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Manager for broker authentication storage
|
|
5
|
+
* Handles encrypted storage of broker credentials
|
|
6
|
+
*/
|
|
7
|
+
export declare class BrokerAuthManager {
|
|
8
|
+
private static instance;
|
|
9
|
+
private readonly configDir;
|
|
10
|
+
private readonly configFile;
|
|
11
|
+
private currentPassword;
|
|
12
|
+
private encryptionKey;
|
|
13
|
+
private storage;
|
|
14
|
+
private constructor();
|
|
15
|
+
/**
|
|
16
|
+
* Get singleton instance
|
|
17
|
+
*/
|
|
18
|
+
static getInstance(): BrokerAuthManager;
|
|
19
|
+
/**
|
|
20
|
+
* Add a new broker configuration
|
|
21
|
+
* @param broker - Broker authentication configuration
|
|
22
|
+
*/
|
|
23
|
+
addBroker(broker: BrokerAuth): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Check if broker exists
|
|
26
|
+
* @param name - Broker name
|
|
27
|
+
* @returns true if broker exists
|
|
28
|
+
*/
|
|
29
|
+
brokerExists(name: string): Promise<boolean>;
|
|
30
|
+
/**
|
|
31
|
+
* Clear all broker configurations
|
|
32
|
+
*/
|
|
33
|
+
clearAll(): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Create ScConnection instance from stored broker config
|
|
36
|
+
* @param brokerName - Name of the broker to connect to
|
|
37
|
+
* @param timeout - Optional timeout override
|
|
38
|
+
* @returns Configured ScConnection instance
|
|
39
|
+
*/
|
|
40
|
+
createConnection(brokerName: string, timeout?: number): Promise<ScConnection>;
|
|
41
|
+
/**
|
|
42
|
+
* Get all broker configurations
|
|
43
|
+
* @returns Array of all broker configurations
|
|
44
|
+
*/
|
|
45
|
+
getAllBrokers(): Promise<BrokerAuth[]>;
|
|
46
|
+
/**
|
|
47
|
+
* Get broker configuration by name
|
|
48
|
+
* @param name - Broker name/alias
|
|
49
|
+
* @returns Broker configuration or null if not found
|
|
50
|
+
*/
|
|
51
|
+
getBroker(name: string): Promise<BrokerAuth | null>;
|
|
52
|
+
/**
|
|
53
|
+
* Initialize the auth manager with encryption key
|
|
54
|
+
* @param password - Password to derive encryption key
|
|
55
|
+
*/
|
|
56
|
+
initialize(password: string): Promise<void>;
|
|
57
|
+
/**
|
|
58
|
+
* List all broker names
|
|
59
|
+
* @returns Array of broker names
|
|
60
|
+
*/
|
|
61
|
+
listBrokers(): Promise<string[]>;
|
|
62
|
+
/**
|
|
63
|
+
* Remove broker configuration
|
|
64
|
+
* @param name - Broker name to remove
|
|
65
|
+
*/
|
|
66
|
+
removeBroker(name: string): Promise<void>;
|
|
67
|
+
/**
|
|
68
|
+
* Update existing broker configuration
|
|
69
|
+
* @param name - Broker name to update
|
|
70
|
+
* @param updates - Partial updates to apply
|
|
71
|
+
*/
|
|
72
|
+
updateBroker(name: string, updates: Partial<Omit<BrokerAuth, 'name'>>): Promise<void>;
|
|
73
|
+
/**
|
|
74
|
+
* Ensure manager is initialized
|
|
75
|
+
*/
|
|
76
|
+
private ensureInitialized;
|
|
77
|
+
/**
|
|
78
|
+
* Check if config file exists
|
|
79
|
+
*/
|
|
80
|
+
private fileExists;
|
|
81
|
+
/**
|
|
82
|
+
* Load storage from encrypted file
|
|
83
|
+
* @param password - Password to decrypt
|
|
84
|
+
*/
|
|
85
|
+
private loadStorage;
|
|
86
|
+
/**
|
|
87
|
+
* Save storage to encrypted file
|
|
88
|
+
*/
|
|
89
|
+
private saveStorage;
|
|
90
|
+
/**
|
|
91
|
+
* Validate broker configuration
|
|
92
|
+
* @param broker - Broker to validate
|
|
93
|
+
*/
|
|
94
|
+
private validateBroker;
|
|
95
|
+
}
|
|
@@ -0,0 +1,293 @@
|
|
|
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
|
+
/**
|
|
8
|
+
* Manager for broker authentication storage
|
|
9
|
+
* Handles encrypted storage of broker credentials
|
|
10
|
+
*/
|
|
11
|
+
export class BrokerAuthManager {
|
|
12
|
+
static instance = null;
|
|
13
|
+
configDir;
|
|
14
|
+
configFile;
|
|
15
|
+
currentPassword = null;
|
|
16
|
+
encryptionKey = null;
|
|
17
|
+
storage = null;
|
|
18
|
+
constructor() {
|
|
19
|
+
const homeDirectory = homedir();
|
|
20
|
+
this.configDir = join(homeDirectory, '.sc');
|
|
21
|
+
this.configFile = join(this.configDir, 'brokers.json');
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Get singleton instance
|
|
25
|
+
*/
|
|
26
|
+
static getInstance() {
|
|
27
|
+
if (!BrokerAuthManager.instance) {
|
|
28
|
+
BrokerAuthManager.instance = new BrokerAuthManager();
|
|
29
|
+
}
|
|
30
|
+
return BrokerAuthManager.instance;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Add a new broker configuration
|
|
34
|
+
* @param broker - Broker authentication configuration
|
|
35
|
+
*/
|
|
36
|
+
async addBroker(broker) {
|
|
37
|
+
this.ensureInitialized();
|
|
38
|
+
// Validate broker
|
|
39
|
+
this.validateBroker(broker);
|
|
40
|
+
// Check if broker already exists
|
|
41
|
+
const existing = this.storage.brokers.find((b) => b.name === broker.name);
|
|
42
|
+
if (existing) {
|
|
43
|
+
throw new BrokerAuthError(`Broker '${broker.name}' already exists`, BrokerAuthErrorCode.BROKER_ALREADY_EXISTS);
|
|
44
|
+
}
|
|
45
|
+
// Add broker
|
|
46
|
+
this.storage.brokers.push(broker);
|
|
47
|
+
// Save to file
|
|
48
|
+
await this.saveStorage();
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Check if broker exists
|
|
52
|
+
* @param name - Broker name
|
|
53
|
+
* @returns true if broker exists
|
|
54
|
+
*/
|
|
55
|
+
async brokerExists(name) {
|
|
56
|
+
this.ensureInitialized();
|
|
57
|
+
return this.storage.brokers.some((b) => b.name === name);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Clear all broker configurations
|
|
61
|
+
*/
|
|
62
|
+
async clearAll() {
|
|
63
|
+
this.ensureInitialized();
|
|
64
|
+
this.storage.brokers = [];
|
|
65
|
+
await this.saveStorage();
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Create ScConnection instance from stored broker config
|
|
69
|
+
* @param brokerName - Name of the broker to connect to
|
|
70
|
+
* @param timeout - Optional timeout override
|
|
71
|
+
* @returns Configured ScConnection instance
|
|
72
|
+
*/
|
|
73
|
+
async createConnection(brokerName, timeout = 10_000) {
|
|
74
|
+
this.ensureInitialized();
|
|
75
|
+
const broker = await this.getBroker(brokerName);
|
|
76
|
+
if (!broker) {
|
|
77
|
+
throw new BrokerAuthError(`Broker '${brokerName}' not found`, BrokerAuthErrorCode.BROKER_NOT_FOUND);
|
|
78
|
+
}
|
|
79
|
+
const baseURL = `${broker.sempEndpoint}:${broker.sempPort}`;
|
|
80
|
+
const accessToken = broker.authType === AuthType.OAUTH ? broker.accessToken : broker.encodedCredentials;
|
|
81
|
+
const isBasic = broker.authType === AuthType.BASIC;
|
|
82
|
+
return new ScConnection(baseURL, accessToken, timeout, isBasic);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Get all broker configurations
|
|
86
|
+
* @returns Array of all broker configurations
|
|
87
|
+
*/
|
|
88
|
+
async getAllBrokers() {
|
|
89
|
+
this.ensureInitialized();
|
|
90
|
+
return [...this.storage.brokers];
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Get broker configuration by name
|
|
94
|
+
* @param name - Broker name/alias
|
|
95
|
+
* @returns Broker configuration or null if not found
|
|
96
|
+
*/
|
|
97
|
+
async getBroker(name) {
|
|
98
|
+
this.ensureInitialized();
|
|
99
|
+
const broker = this.storage.brokers.find((b) => b.name === name);
|
|
100
|
+
return broker ?? null;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Initialize the auth manager with encryption key
|
|
104
|
+
* @param password - Password to derive encryption key
|
|
105
|
+
*/
|
|
106
|
+
async initialize(password) {
|
|
107
|
+
try {
|
|
108
|
+
// Store password for re-encryption
|
|
109
|
+
this.currentPassword = password;
|
|
110
|
+
// Try to load existing storage
|
|
111
|
+
const fileExists = await this.fileExists();
|
|
112
|
+
if (fileExists) {
|
|
113
|
+
// Load existing file and derive key from stored salt
|
|
114
|
+
await this.loadStorage(password);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
// Create new storage with new salt
|
|
118
|
+
const salt = BrokerAuthEncryption.generateSalt();
|
|
119
|
+
this.encryptionKey = await BrokerAuthEncryption.deriveKey(password, salt);
|
|
120
|
+
this.storage = {
|
|
121
|
+
brokers: [],
|
|
122
|
+
version: '1.0.0',
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
if (error instanceof BrokerAuthError) {
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
130
|
+
throw new BrokerAuthError('Failed to initialize broker auth manager', BrokerAuthErrorCode.NOT_INITIALIZED, error);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* List all broker names
|
|
135
|
+
* @returns Array of broker names
|
|
136
|
+
*/
|
|
137
|
+
async listBrokers() {
|
|
138
|
+
this.ensureInitialized();
|
|
139
|
+
return this.storage.brokers.map((b) => b.name);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Remove broker configuration
|
|
143
|
+
* @param name - Broker name to remove
|
|
144
|
+
*/
|
|
145
|
+
async removeBroker(name) {
|
|
146
|
+
this.ensureInitialized();
|
|
147
|
+
const index = this.storage.brokers.findIndex((b) => b.name === name);
|
|
148
|
+
if (index === -1) {
|
|
149
|
+
throw new BrokerAuthError(`Broker '${name}' not found`, BrokerAuthErrorCode.BROKER_NOT_FOUND);
|
|
150
|
+
}
|
|
151
|
+
// Remove broker
|
|
152
|
+
this.storage.brokers.splice(index, 1);
|
|
153
|
+
// Save to file
|
|
154
|
+
await this.saveStorage();
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Update existing broker configuration
|
|
158
|
+
* @param name - Broker name to update
|
|
159
|
+
* @param updates - Partial updates to apply
|
|
160
|
+
*/
|
|
161
|
+
async updateBroker(name, updates) {
|
|
162
|
+
this.ensureInitialized();
|
|
163
|
+
const index = this.storage.brokers.findIndex((b) => b.name === name);
|
|
164
|
+
if (index === -1) {
|
|
165
|
+
throw new BrokerAuthError(`Broker '${name}' not found`, BrokerAuthErrorCode.BROKER_NOT_FOUND);
|
|
166
|
+
}
|
|
167
|
+
// Merge updates
|
|
168
|
+
const updated = {
|
|
169
|
+
...this.storage.brokers[index],
|
|
170
|
+
...updates,
|
|
171
|
+
name, // Ensure name doesn't change
|
|
172
|
+
};
|
|
173
|
+
// Validate updated broker
|
|
174
|
+
this.validateBroker(updated);
|
|
175
|
+
// Update broker
|
|
176
|
+
this.storage.brokers[index] = updated;
|
|
177
|
+
// Save to file
|
|
178
|
+
await this.saveStorage();
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Ensure manager is initialized
|
|
182
|
+
*/
|
|
183
|
+
ensureInitialized() {
|
|
184
|
+
if (!this.encryptionKey || !this.storage) {
|
|
185
|
+
throw new BrokerAuthError('BrokerAuthManager not initialized. Call initialize() first.', BrokerAuthErrorCode.NOT_INITIALIZED);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Check if config file exists
|
|
190
|
+
*/
|
|
191
|
+
async fileExists() {
|
|
192
|
+
try {
|
|
193
|
+
await readFile(this.configFile);
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Load storage from encrypted file
|
|
202
|
+
* @param password - Password to decrypt
|
|
203
|
+
*/
|
|
204
|
+
async loadStorage(password) {
|
|
205
|
+
try {
|
|
206
|
+
const fileContent = await readFile(this.configFile, 'utf8');
|
|
207
|
+
const encryptedData = JSON.parse(fileContent);
|
|
208
|
+
// Derive key from password and stored salt
|
|
209
|
+
const salt = Buffer.from(encryptedData.salt, 'base64');
|
|
210
|
+
this.encryptionKey = await BrokerAuthEncryption.deriveKey(password, salt);
|
|
211
|
+
// Decrypt storage
|
|
212
|
+
this.storage = await BrokerAuthEncryption.decrypt(encryptedData, this.encryptionKey);
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
if (error instanceof BrokerAuthError) {
|
|
216
|
+
throw error;
|
|
217
|
+
}
|
|
218
|
+
throw new BrokerAuthError('Failed to load broker storage', BrokerAuthErrorCode.FILE_READ_ERROR, error);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Save storage to encrypted file
|
|
223
|
+
*/
|
|
224
|
+
async saveStorage() {
|
|
225
|
+
try {
|
|
226
|
+
if (!this.currentPassword) {
|
|
227
|
+
throw new BrokerAuthError('Password not set', BrokerAuthErrorCode.NOT_INITIALIZED);
|
|
228
|
+
}
|
|
229
|
+
// Ensure directory exists
|
|
230
|
+
await mkdir(this.configDir, { mode: 0o700, recursive: true });
|
|
231
|
+
// Encrypt data
|
|
232
|
+
const encrypted = await BrokerAuthEncryption.encrypt(this.storage, this.encryptionKey);
|
|
233
|
+
// Re-derive key with new salt for next save
|
|
234
|
+
const newSalt = Buffer.from(encrypted.salt, 'base64');
|
|
235
|
+
this.encryptionKey = await BrokerAuthEncryption.deriveKey(this.currentPassword, newSalt);
|
|
236
|
+
// Write to temp file first (atomic write)
|
|
237
|
+
const jsonData = JSON.stringify(encrypted, null, 2);
|
|
238
|
+
const tempFile = `${this.configFile}.tmp`;
|
|
239
|
+
await writeFile(tempFile, jsonData, { mode: 0o600 });
|
|
240
|
+
// Atomic rename
|
|
241
|
+
await rename(tempFile, this.configFile);
|
|
242
|
+
// Set restrictive permissions (Unix only)
|
|
243
|
+
if (process.platform !== 'win32') {
|
|
244
|
+
// Already set via writeFile mode option
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
catch (error) {
|
|
248
|
+
// Clean up temp file if it exists
|
|
249
|
+
try {
|
|
250
|
+
await unlink(`${this.configFile}.tmp`);
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
// Ignore cleanup errors
|
|
254
|
+
}
|
|
255
|
+
if (error instanceof BrokerAuthError) {
|
|
256
|
+
throw error;
|
|
257
|
+
}
|
|
258
|
+
throw new BrokerAuthError('Failed to save broker storage', BrokerAuthErrorCode.FILE_WRITE_ERROR, error);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Validate broker configuration
|
|
263
|
+
* @param broker - Broker to validate
|
|
264
|
+
*/
|
|
265
|
+
validateBroker(broker) {
|
|
266
|
+
// Validate name
|
|
267
|
+
if (!broker.name || broker.name.trim() === '') {
|
|
268
|
+
throw new BrokerAuthError('Broker name is required', BrokerAuthErrorCode.INVALID_NAME);
|
|
269
|
+
}
|
|
270
|
+
// Validate endpoint
|
|
271
|
+
if (!broker.sempEndpoint || !(broker.sempEndpoint.startsWith('http://') || broker.sempEndpoint.startsWith('https://'))) {
|
|
272
|
+
throw new BrokerAuthError('SEMP endpoint must start with http:// or https://', BrokerAuthErrorCode.INVALID_ENDPOINT);
|
|
273
|
+
}
|
|
274
|
+
// Validate port
|
|
275
|
+
if (broker.sempPort < 1 || broker.sempPort > 65_535) {
|
|
276
|
+
throw new BrokerAuthError('SEMP port must be between 1 and 65535', BrokerAuthErrorCode.INVALID_PORT);
|
|
277
|
+
}
|
|
278
|
+
// Validate auth-specific fields
|
|
279
|
+
if (broker.authType === AuthType.OAUTH) {
|
|
280
|
+
if (!broker.accessToken || !broker.clientId) {
|
|
281
|
+
throw new BrokerAuthError('OAuth brokers require accessToken and clientId', BrokerAuthErrorCode.INVALID_OAUTH_CONFIG);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
else if (broker.authType === AuthType.BASIC) {
|
|
285
|
+
if (!broker.encodedCredentials) {
|
|
286
|
+
throw new BrokerAuthError('Basic auth brokers require encodedCredentials', BrokerAuthErrorCode.INVALID_BASIC_CONFIG);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
throw new BrokerAuthError('Invalid auth type', BrokerAuthErrorCode.INVALID_AUTH_TYPE);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
@@ -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';
|
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,
|
|
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,
|
|
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 =
|
|
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:
|
|
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.
|
|
3
|
+
"version": "0.2.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",
|