194 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			194 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
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);
 | 
						||
    }
 | 
						||
  }
 | 
						||
} |