import * as vscode from 'vscode'; import * as path from 'path'; import * as os from 'os'; import * as fs from 'fs'; import DevstarAPIHandler from './devstar-api'; import { showErrorNotification } from './utils'; const { generateKeyPairSync, createHash } = require('node:crypto'); const sshpk = require('sshpk'); export default class User { private context: vscode.ExtensionContext; private username: string | undefined; private userToken: string | undefined; private usernameKey: string = 'devstarUsername' private userTokenKey: string = 'devstarUserToken' private devstarHostname: string; constructor(context: vscode.ExtensionContext) { this.context = context; this.username = this.context.globalState.get(this.usernameKey); this.userToken = this.context.globalState.get(this.userTokenKey); // 提取devstar domain的主域名,用于本地ssh key的命名 let devstarDomainFromConfig: string | undefined; let devstarDomainURL: string; devstarDomainFromConfig = vscode.workspace.getConfiguration('devstar').get('devstarDomain') // 如果没有配置devstar domain,则默认domain为https://devstar.cn devstarDomainURL = (devstarDomainFromConfig === undefined || devstarDomainFromConfig === "") ? 'https://devstar.cn' : devstarDomainFromConfig; let parsedUrl = new URL(devstarDomainURL); this.devstarHostname = parsedUrl.hostname.replace(/\./g, '_'); //提取hostname,并用下划线替换. } public async login(token: string, username: string) { const devstarAPIHandler = new DevstarAPIHandler() try { const res = await devstarAPIHandler.verifyToken(token, username) if (!res) { throw new Error('Token verification failed') } // token与用户名验证通过 // 插件登录:存储token与用户名 this.setUserTokenToLocal(token) this.setUsernameToLocal(username) // 检查本地是否有用户所属公钥,没有则创建 if (!this.existUserPublicKey()) { await this.createUserSSHKey() // 上传公钥 const uploadResult = await devstarAPIHandler.uploadUserPublicKey(this) if (uploadResult !== 'ok') { throw new Error('Upload user public key failed') } } vscode.window.showInformationMessage('用户已登录!') return 'ok' } catch (error) { console.error(error) await showErrorNotification('用户登录失败!') return 'login failed' } } public logout() { this.setUserTokenToLocal("") this.setUsernameToLocal("") vscode.window.showInformationMessage('用户已登出!') } public isLogged() { var existUsername = false; var existUserToken = false; if (this.username != undefined && this.username != '') { existUsername = true; } if (this.userToken != undefined && this.userToken != '') { existUserToken = true; } if (existUsername && existUserToken) { return true; } else { return false; } } public getUsernameFromLocal(): string | undefined { return this.username; } public getUserTokenFromLocal(): string | undefined { return this.userToken; } public setUsernameToLocal(username: string) { this.context.globalState.update(this.usernameKey, username); this.username = username; if (username !== "") { console.log('Username has been stored.') } else { console.log('Username has been cleaned up.') } } public setUserTokenToLocal(userToken: string) { this.context.globalState.update(this.userTokenKey, userToken) this.userToken = userToken if (userToken !== "") { console.log('Token has been stored.'); } else { console.log('Token has been cleaned up.') } } public getUserPrivateKeyPath(): string { if (!this.isLogged) { return ''; } return path.join(os.homedir(), '.ssh', `id_rsa_${this.username}_${this.devstarHostname}`) } public getUserPublicKeyPath(): string { if (!this.isLogged) { return ''; } return path.join(os.homedir(), '.ssh', `id_rsa_${this.username}_${this.devstarHostname}.pub`) } public existUserPublicKey(): boolean { const userPublicKeyPath = this.getUserPublicKeyPath(); return fs.existsSync(userPublicKeyPath) } public existUserPrivateKey(): boolean { const userPrivateKeyPath = this.getUserPrivateKeyPath(); return fs.existsSync(userPrivateKeyPath) } public getUserPublicKey(): string { const userPublicKeyPath = this.getUserPublicKeyPath(); const userPublicKey = fs.readFileSync(userPublicKeyPath, 'utf-8'); // remove `\r` `\n` const trimmedDefaultPublicKey = userPublicKey.replace(/[\r\n]/g, ""); return trimmedDefaultPublicKey; } public getUserPrivateKey(): string { const userPrivateKey = this.getUserPrivateKeyPath(); return fs.readFileSync(userPrivateKey, 'utf-8'); } public async createUserSSHKey() { if (this.existUserPublicKey() && this.existUserPrivateKey()) { // if both public and private key exists, stop return; } const { publicKey, privateKey, } = await generateKeyPairSync('rsa', { modulusLength: 4096, publicKeyEncoding: { type: 'pkcs1', format: 'pem', }, privateKeyEncoding: { type: 'pkcs1', format: 'pem', }, }); const publicKeyFingerprint = sshpk.parseKey(publicKey, 'pem').toString('ssh'); const publicKeyStr = publicKeyFingerprint; // public key is public key fingerprint const privateKeyStr = privateKey; try { await fs.writeFileSync(this.getUserPublicKeyPath(), publicKeyStr); await fs.writeFileSync(this.getUserPrivateKeyPath(), privateKeyStr); // limit the permission of private key to prevent that the private key not works await fs.chmodSync(this.getUserPrivateKeyPath(), 0o600) console.log(`User's ssh key has been created.`) } catch (error) { console.error("Failed to write public/private key into the default ssh public/key file: ", error); } } }