wative 1.2.7 → 1.2.10

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/src/ssh/client.ts DELETED
@@ -1,389 +0,0 @@
1
- import { Client } from 'ssh2';
2
- import { readFileSync } from 'fs';
3
-
4
-
5
- export interface SSHAuthConfig {
6
- username: string;
7
- privateKeyPath: string;
8
- publicKeyPath: string;
9
- passphrase?: string;
10
- }
11
-
12
- export interface SSHConfig {
13
- // Wative service configuration
14
- host: string;
15
- port: number;
16
-
17
- // Optional: specific client authentication for Wative service
18
- // If not provided, will use the main auth configuration
19
- clientAuth: SSHAuthConfig;
20
-
21
- // Remote server deployment support
22
- useRemoteServer?: boolean;
23
- remoteServer?: {
24
- host: string;
25
- port?: number;
26
- auth: SSHAuthConfig;
27
- };
28
- }
29
-
30
- export interface WativeRequest {
31
- method: string;
32
- keystoreId?: string;
33
- chainId?: string;
34
- params?: any;
35
- }
36
-
37
- export interface WativeResponse {
38
- status: boolean;
39
- data?: any;
40
- msg?: string;
41
- }
42
-
43
- export class WativeSSHClient {
44
- private config: SSHConfig;
45
-
46
- constructor(config: SSHConfig) {
47
- this.config = config;
48
- }
49
-
50
- /**
51
- * Execute a command on the SSH server
52
- * @param command The command to execute
53
- * @returns Promise<string> The command output
54
- */
55
- private async executeCommand(command: string): Promise<string> {
56
- if (this.config.useRemoteServer) {
57
- return this.executeCommandThroughRemoteServer(command);
58
- }
59
-
60
-
61
-
62
- return new Promise((resolve, reject) => {
63
- const conn = new Client();
64
-
65
- conn.on('ready', () => {
66
- conn.exec(command, (err, stream) => {
67
- if (err) {
68
- conn.end();
69
- return reject(err);
70
- }
71
-
72
- let output = '';
73
- let errorOutput = '';
74
-
75
- stream.on('close', (code: number, signal: string) => {
76
- conn.end();
77
- if (code === 0) {
78
- resolve(output);
79
- } else {
80
- reject(new Error(`Command failed with code ${code}: ${errorOutput || output}`));
81
- }
82
- }).on('data', (data: Buffer) => {
83
- output += data.toString();
84
- }).stderr.on('data', (data: Buffer) => {
85
- errorOutput += data.toString();
86
- });
87
- });
88
- }).on('error', (err) => {
89
- reject(err);
90
- });
91
-
92
- // Connect with public key authentication
93
- const authConfig = this.config.clientAuth;
94
- const connectOptions: any = {
95
- host: this.config.host,
96
- port: this.config.port,
97
- username: authConfig.username,
98
- privateKey: readFileSync(authConfig.privateKeyPath),
99
- passphrase: authConfig.passphrase
100
- };
101
-
102
- conn.connect(connectOptions);
103
- });
104
- }
105
-
106
- /**
107
- * Execute a command through remote server
108
- * @param command The command to execute
109
- * @returns Promise<string> The command output
110
- */
111
- private async executeCommandThroughRemoteServer(command: string): Promise<string> {
112
- return new Promise((resolve, reject) => {
113
- const remoteConn = new Client();
114
-
115
- remoteConn.on('ready', () => {
116
- // Create a tunnel through remote server to Wative service
117
- remoteConn.forwardOut(
118
- '127.0.0.1', 0, // source
119
- this.config.host, this.config.port, // destination (Wative service)
120
- (err, stream) => {
121
- if (err) {
122
- remoteConn.end();
123
- return reject(err);
124
- }
125
-
126
- // Create connection to Wative service through tunnel
127
- const wativeConn = new Client();
128
-
129
- wativeConn.on('ready', () => {
130
- wativeConn.exec(command, (err, execStream) => {
131
- if (err) {
132
- wativeConn.end();
133
- remoteConn.end();
134
- return reject(err);
135
- }
136
-
137
- let output = '';
138
- let errorOutput = '';
139
-
140
- execStream.on('close', (code: number, signal: string) => {
141
- wativeConn.end();
142
- remoteConn.end();
143
- if (code === 0) {
144
- resolve(output);
145
- } else {
146
- reject(new Error(`Command failed with code ${code}: ${errorOutput || output}`));
147
- }
148
- }).on('data', (data: Buffer) => {
149
- output += data.toString();
150
- }).stderr.on('data', (data: Buffer) => {
151
- errorOutput += data.toString();
152
- });
153
- });
154
- }).on('error', (err) => {
155
- remoteConn.end();
156
- reject(err);
157
- });
158
-
159
- // Connect to Wative service through tunnel using client keys
160
- const clientAuth = this.config.clientAuth;
161
- const wativeConnectOptions: any = {
162
- sock: stream,
163
- username: clientAuth.username,
164
- privateKey: readFileSync(clientAuth.privateKeyPath),
165
- passphrase: clientAuth.passphrase
166
- };
167
-
168
- wativeConn.connect(wativeConnectOptions);
169
- }
170
- );
171
- }).on('error', (err) => {
172
- reject(err);
173
- });
174
-
175
- // Connect to remote server using remote server keys
176
- const remoteConnectOptions: any = {
177
- host: this.config.remoteServer!.host,
178
- port: this.config.remoteServer!.port || 22,
179
- username: this.config.remoteServer!.auth.username,
180
- privateKey: readFileSync(this.config.remoteServer!.auth.privateKeyPath),
181
- passphrase: this.config.remoteServer!.auth.passphrase
182
- };
183
-
184
- remoteConn.connect(remoteConnectOptions);
185
- });
186
- }
187
-
188
-
189
-
190
- /**
191
- * Send a request to the Wative server
192
- * @param request The request object
193
- * @returns Promise<WativeResponse> The server response
194
- */
195
- async sendRequest(request: WativeRequest): Promise<WativeResponse> {
196
- try {
197
- const command = JSON.stringify(request);
198
-
199
- const output = await this.executeCommand(command);
200
-
201
- // Parse the JSON response
202
- const response: WativeResponse = JSON.parse(output);
203
-
204
- if (!response.status) {
205
- throw new Error(response.msg || 'Request failed');
206
- }
207
-
208
- return response;
209
- } catch (error) {
210
- if (error instanceof Error) {
211
- throw new Error(`SSH request failed: ${error.message}`);
212
- }
213
- throw error;
214
- }
215
- }
216
-
217
- /**
218
- * Get root account for a keystore and chain
219
- * @param keystoreId The keystore ID
220
- * @param chainId The chain ID
221
- * @returns Promise<any> The root account data
222
- */
223
- async getRootAccount(keystoreId: string, chainId: string): Promise<any> {
224
- const request: WativeRequest = {
225
- method: 'get_root_account',
226
- keystoreId,
227
- chainId
228
- };
229
-
230
- const response = await this.sendRequest(request);
231
- return response.data;
232
- }
233
-
234
- /**
235
- * Get sub accounts for a keystore and chain
236
- * @param keystoreId The keystore ID
237
- * @param chainId The chain ID
238
- * @param startIndex Optional start index
239
- * @param endIndex Optional end index
240
- * @returns Promise<any> The sub account data
241
- */
242
- async getSubAccount(keystoreId: string, chainId: string, startIndex?: number, endIndex?: number): Promise<any> {
243
- const request: WativeRequest = {
244
- method: 'get_sub_account',
245
- params: {
246
- keystoreId,
247
- chainId,
248
- startIndex,
249
- endIndex
250
- }
251
- };
252
-
253
- const response = await this.sendRequest(request);
254
- return response.data;
255
- }
256
-
257
- /**
258
- * Sign a transaction
259
- * @param txParams Transaction parameters
260
- * @param rpcUrl RPC URL
261
- * @returns Promise<any> The signed transaction
262
- */
263
- async signTransaction(txParams: any, rpcUrl: string): Promise<any> {
264
- const request: WativeRequest = {
265
- method: 'sign',
266
- params: {
267
- txParams,
268
- rpcUrl
269
- }
270
- };
271
-
272
- const response = await this.sendRequest(request);
273
- return response.data;
274
- }
275
-
276
- /**
277
- * Sign and send a transaction
278
- * @param txParams Transaction parameters
279
- * @param rpcUrl RPC URL
280
- * @returns Promise<any> The transaction hash
281
- */
282
- async signAndSendTransaction(txParams: any, rpcUrl: string): Promise<any> {
283
- const request: WativeRequest = {
284
- method: 'sign_and_send',
285
- params: {
286
- txParams,
287
- rpcUrl
288
- }
289
- };
290
-
291
- const response = await this.sendRequest(request);
292
- return response.data;
293
- }
294
-
295
- /**
296
- * Sign a Solana transaction
297
- * @param txParams Transaction parameters
298
- * @param rpcUrl RPC URL
299
- * @param priorityFee Priority fee
300
- * @returns Promise<any> The signed transaction
301
- */
302
- async signSolanaTransaction(txParams: any, rpcUrl: string, priorityFee?: any): Promise<any> {
303
- const request: WativeRequest = {
304
- method: 'solana_sign',
305
- params: {
306
- txParams,
307
- rpcUrl,
308
- priority_fee: priorityFee
309
- }
310
- };
311
-
312
- const response = await this.sendRequest(request);
313
- return response.data;
314
- }
315
-
316
- /**
317
- * Send a signed Solana transaction
318
- * @param txParams Transaction parameters with signature
319
- * @param rpcUrl RPC URL
320
- * @returns Promise<any> The transaction result
321
- */
322
- async sendSignedSolanaTransaction(txParams: any, rpcUrl: string): Promise<any> {
323
- const request: WativeRequest = {
324
- method: 'solana_send',
325
- params: {
326
- txParams,
327
- rpcUrl
328
- }
329
- };
330
-
331
- const response = await this.sendRequest(request);
332
- return response.data;
333
- }
334
-
335
- /**
336
- * Sign a message
337
- * @param account The account to sign with
338
- * @param message The message to sign
339
- * @returns Promise<any> The signature
340
- */
341
- async signMessage(account: string, message: string): Promise<any> {
342
- const request: WativeRequest = {
343
- method: 'sign_message',
344
- params: {
345
- account,
346
- message
347
- }
348
- };
349
-
350
- const response = await this.sendRequest(request);
351
- return response.data;
352
- }
353
-
354
- /**
355
- * Sign typed data (EIP-712)
356
- * @param account The account to sign with
357
- * @param domain The domain
358
- * @param typesName The types name
359
- * @param types The types
360
- * @param message The message
361
- * @returns Promise<any> The signature
362
- */
363
- async signTypedData(account: string, domain: string, typesName: string, types: string, message: string): Promise<any> {
364
- const request: WativeRequest = {
365
- method: 'sign_typed_data',
366
- params: {
367
- account,
368
- domain,
369
- types_name: typesName,
370
- types,
371
- message
372
- }
373
- };
374
-
375
- const response = await this.sendRequest(request);
376
- return response.data;
377
- }
378
- }
379
-
380
- /**
381
- * Create a new Wative SSH client instance
382
- * @param config SSH configuration
383
- * @returns WativeSSHClient instance
384
- */
385
- export function createWativeSSHClient(config: SSHConfig): WativeSSHClient {
386
- return new WativeSSHClient(config);
387
- }
388
-
389
- export default WativeSSHClient;
@@ -1,304 +0,0 @@
1
- import * as fs from 'fs';
2
- import * as path from 'path';
3
- import * as ini from 'ini';
4
- import * as crypto from 'crypto';
5
- import { SSHServiceConfig } from './types';
6
- import { execSync } from 'child_process';
7
-
8
- export class SSHConfigManager {
9
- private configDir: string;
10
- private keystorePath: string;
11
-
12
- constructor(configDir?: string, keystorePath?: string) {
13
- this.configDir = configDir || path.join(require('os').homedir(), '.wative/ssh');
14
- this.keystorePath = keystorePath || path.join(require('os').homedir(), '.wative');
15
-
16
- if (!fs.existsSync(this.configDir)) {
17
- fs.mkdirSync(this.configDir, { recursive: true });
18
- }
19
-
20
- // Create conf.d directory for service configurations
21
- const confDir = path.join(this.configDir, 'conf.d');
22
- if (!fs.existsSync(confDir)) {
23
- fs.mkdirSync(confDir, { recursive: true });
24
- }
25
- }
26
-
27
- /**
28
- * 获取 keystore 路径
29
- */
30
- public getKeystorePath(): string {
31
- return this.keystorePath;
32
- }
33
-
34
- /**
35
- * 获取配置目录路径
36
- */
37
- public getConfigDir(): string {
38
- return this.configDir;
39
- }
40
-
41
- /**
42
- * 创建新的 SSH 服务配置
43
- */
44
- public async createServiceConfig(
45
- serviceName: string,
46
- port: number,
47
- allowedKeystores: string[],
48
- clientName: string = 'default_client',
49
- description?: string
50
- ): Promise<SSHServiceConfig> {
51
- // 验证服务名称
52
- if (!this.isValidServiceName(serviceName)) {
53
- throw new Error('Invalid service name. Use only alphanumeric characters, hyphens, and underscores.');
54
- }
55
-
56
- // 检查端口是否已被使用
57
- if (this.isPortInUse(port)) {
58
- throw new Error(`Port ${port} is already in use by another service.`);
59
- }
60
-
61
- // 生成服务专用的密钥对
62
- const serverKeyPaths = await this.generateServiceKeyPair(serviceName);
63
- const clientKeyPaths = await this.generateClientKeyPair(serviceName, clientName);
64
-
65
- const config: SSHServiceConfig = {
66
- name: serviceName,
67
- description: description || `Wative SSH Service - ${serviceName}`,
68
- ssh: {
69
- listen: '127.0.0.1',
70
- port: port,
71
- log: {
72
- size: 10485760,
73
- backups: 3
74
- }
75
- },
76
- keystore: {
77
- path: this.keystorePath,
78
- allowed_keystores: allowedKeystores
79
- },
80
- clients: [{
81
- name: clientName,
82
- public_key_path: clientKeyPaths.publicKey,
83
- allowed_keystores: allowedKeystores
84
- }],
85
- server_keys: {
86
- private_key_path: serverKeyPaths.privateKey,
87
- passphrase: serverKeyPaths.passphrase
88
- }
89
- };
90
-
91
- // 保存配置文件
92
- this.saveServiceConfig(serviceName, config);
93
-
94
- return config;
95
- }
96
-
97
- /**
98
- * 加载服务配置
99
- */
100
- public loadServiceConfig(serviceName: string): SSHServiceConfig {
101
- const configPath = this.getServiceConfigPath(serviceName);
102
-
103
- if (!fs.existsSync(configPath)) {
104
- throw new Error(`Service configuration '${serviceName}' not found.`);
105
- }
106
-
107
- const configData = ini.parse(fs.readFileSync(configPath, 'utf-8'));
108
- return this.parseConfigData(configData);
109
- }
110
-
111
- public serviceIsExist(serviceName: string): boolean {
112
- const configPath = this.getServiceConfigPath(serviceName);
113
- return fs.existsSync(path.dirname(configPath));
114
- }
115
-
116
- /**
117
- * 获取所有服务配置列表
118
- */
119
- public listServiceConfigs(): string[] {
120
- const confDir = path.join(this.configDir, 'conf.d');
121
-
122
- if (!fs.existsSync(confDir)) {
123
- return [];
124
- }
125
-
126
- const serviceDirs = fs.readdirSync(confDir)
127
- .filter(item => {
128
- const itemPath = path.join(confDir, item);
129
- return fs.statSync(itemPath).isDirectory() &&
130
- fs.existsSync(path.join(itemPath, 'config.ini'));
131
- });
132
-
133
- return serviceDirs;
134
- }
135
-
136
- /**
137
- * 删除服务配置
138
- */
139
- public removeServiceConfig(serviceName: string): void {
140
- const configPath = this.getServiceConfigPath(serviceName);
141
- const configDir = path.dirname(configPath);
142
- if (fs.existsSync(configDir)) {
143
- fs.rmSync(configDir, { recursive: true });
144
- }
145
- }
146
-
147
- /**
148
- * 更新服务配置
149
- */
150
- public updateServiceConfig(serviceName: string, updates: Partial<SSHServiceConfig>): void {
151
- const config = this.loadServiceConfig(serviceName);
152
- const updatedConfig = { ...config, ...updates };
153
- this.saveServiceConfig(serviceName, updatedConfig);
154
- }
155
-
156
- /**
157
- * 验证 keystore 密码
158
- */
159
- public async validateKeystorePasswords(
160
- keystoreIds: string[],
161
- passwords: { [keystoreId: string]: string }
162
- ): Promise<{ [keystoreId: string]: boolean }> {
163
- const results: { [keystoreId: string]: boolean } = {};
164
-
165
- // 这里需要集成 wative-core 的密码验证逻辑
166
- const { WativeCore } = require('wative-core');
167
-
168
- for (const keystoreId of keystoreIds) {
169
- try {
170
- const password = passwords[keystoreId];
171
- if (!password) {
172
- results[keystoreId] = false;
173
- continue;
174
- }
175
-
176
- // 尝试创建 WativeCore 实例来验证密码
177
- const wativeCore = new WativeCore(this.keystorePath, [keystoreId], [password]);
178
- results[keystoreId] = wativeCore.account.isLogin(keystoreId);
179
- } catch (error) {
180
- results[keystoreId] = false;
181
- }
182
- }
183
-
184
- return results;
185
- }
186
-
187
- /**
188
- * 生成默认配置(向后兼容)
189
- */
190
-
191
-
192
- private isValidServiceName(name: string): boolean {
193
- return /^[a-zA-Z0-9_-]+$/.test(name) && name.length > 0 && name.length <= 50;
194
- }
195
-
196
- private isPortInUse(port: number): boolean {
197
- const configs = this.listServiceConfigs();
198
-
199
- for (const configName of configs) {
200
- try {
201
- const config = this.loadServiceConfig(configName);
202
- if (config.ssh.port === port) {
203
- return true;
204
- }
205
- } catch (error) {
206
- // 忽略无法加载的配置文件
207
- }
208
- }
209
-
210
- return false;
211
- }
212
-
213
- private async generateServiceKeyPair(serviceName: string): Promise<{ privateKey: string; publicKey: string; passphrase: string }> {
214
- const passphrase = this.generateSecurePassphrase();
215
- const serviceDir = path.join(this.configDir, 'conf.d', serviceName);
216
-
217
- // Create service directory if it doesn't exist
218
- if (!fs.existsSync(serviceDir)) {
219
- fs.mkdirSync(serviceDir, { recursive: true });
220
- }
221
-
222
- const privateKeyPath = path.join(serviceDir, 'server_private_key');
223
- const publicKeyPath = path.join(serviceDir, 'server_public_key.pub');
224
-
225
- if (!fs.existsSync(privateKeyPath)) {
226
- console.log(`Generating SSH server key pair for service '${serviceName}'...`);
227
- execSync(`ssh-keygen -t rsa -b 4096 -f ${privateKeyPath} -N '${passphrase}' -C 'wative_${serviceName}_server'`, { stdio: 'inherit' });
228
- }
229
-
230
- return {
231
- privateKey: privateKeyPath,
232
- publicKey: publicKeyPath,
233
- passphrase: passphrase
234
- };
235
- }
236
-
237
- private async generateClientKeyPair(serviceName: string, clientName: string): Promise<{ privateKey: string; publicKey: string; passphrase: string }> {
238
- const serviceDir = path.join(this.configDir, 'conf.d', serviceName);
239
-
240
- // Create service directory if it doesn't exist
241
- if (!fs.existsSync(serviceDir)) {
242
- fs.mkdirSync(serviceDir, { recursive: true });
243
- }
244
-
245
- const publicKeyPath = path.join(serviceDir, `${clientName}.pub`);
246
-
247
- // Note: We don't generate client private key here, only specify the public key path
248
- // The client public key should be provided by the user
249
- return {
250
- privateKey: '', // Client private key is not stored on server
251
- publicKey: publicKeyPath,
252
- passphrase: '' // No passphrase needed for public key
253
- };
254
- }
255
-
256
- private generateSecurePassphrase(): string {
257
- return crypto.randomBytes(32).toString('hex');
258
- }
259
-
260
- private getServiceConfigPath(serviceName: string): string {
261
- return path.join(this.configDir, 'conf.d', serviceName, 'config.ini');
262
- }
263
-
264
- private saveServiceConfig(serviceName: string, config: SSHServiceConfig): void {
265
- const configPath = this.getServiceConfigPath(serviceName);
266
- const configDir = path.dirname(configPath);
267
-
268
- // Ensure the service directory exists
269
- if (!fs.existsSync(configDir)) {
270
- fs.mkdirSync(configDir, { recursive: true });
271
- }
272
-
273
- const configText = ini.stringify(config);
274
- fs.writeFileSync(configPath, configText);
275
- }
276
-
277
- private parseConfigData(configData: any): SSHServiceConfig {
278
- return {
279
- name: configData.name,
280
- description: configData.description,
281
- ssh: {
282
- listen: configData.ssh.listen,
283
- port: parseInt(configData.ssh.port),
284
- log: {
285
- size: parseInt(configData.ssh.log.size),
286
- backups: parseInt(configData.ssh.log.backups)
287
- }
288
- },
289
- keystore: {
290
- path: configData.keystore.path,
291
- allowed_keystores: Array.isArray(configData.keystore.allowed_keystores)
292
- ? configData.keystore.allowed_keystores
293
- : configData.keystore.allowed_keystores.split(',')
294
- },
295
- clients: Array.isArray(configData.clients)
296
- ? configData.clients
297
- : [configData.clients],
298
- server_keys: {
299
- private_key_path: configData.server_keys.private_key_path,
300
- passphrase: configData.server_keys.passphrase
301
- }
302
- };
303
- }
304
- }