Files
devstar_plugin/src/user.ts
2025-04-23 09:13:30 +08:00

194 lines
6.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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(vscode.l10n.t('User login successfully!'))
return 'ok'
} catch (error) {
console.error(error)
await showErrorNotification('用户登录失败!')
return 'login failed'
}
}
public logout() {
this.setUserTokenToLocal("")
this.setUsernameToLocal("")
vscode.window.showInformationMessage(vscode.l10n.t("User has logged out!"))
}
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);
}
}
}