Files
devstar_plugin/src/user.ts

260 lines
9.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 * as utils from './utils';
const {
generateKeyPairSync,
createHash
} = require('node:crypto');
const sshpk = require('sshpk');
export default class User {
private context: vscode.ExtensionContext;
// 修改devstarDomain会影响hostname, username key, user token key
private devstarDomain: string;
private devstarHostname: string;
private usernameKey: string; // devstarUsername_<devstarDomain>
private userTokenKey: string; // devstarUserToken_<devstarDomain>
/**
* devstarDomain由配置项提供
* @param context
*/
constructor(context: vscode.ExtensionContext);
/**
* devstarDomain由open with vscode链接提供
* @param context
* @param devstarDomain
*/
constructor(context: vscode.ExtensionContext, devstarDomain: string);
constructor(context: vscode.ExtensionContext, devstarDomain?: string) {
this.context = context;
// 提取devstar domain的主域名用于本地ssh key的命名
if (devstarDomain != undefined && devstarDomain != "") {
// open with vscode链接提供域名
this.devstarDomain = devstarDomain.endsWith('/') ? devstarDomain.slice(0, -1) : devstarDomain
let parsedUrl = new URL(devstarDomain);
this.devstarHostname = parsedUrl.hostname.replace(/\./g, '_'); //提取hostname并用下划线替换.
} else {
const devstarDomainFromConfig: string | undefined = utils.devstarDomain();
if (devstarDomainFromConfig != undefined && devstarDomainFromConfig != "") {
// 用户配置项提供域名
this.devstarDomain = devstarDomainFromConfig.endsWith('/') ? devstarDomainFromConfig.slice(0, -1) : devstarDomainFromConfig
let parsedUrl = new URL(this.devstarDomain);
this.devstarHostname = parsedUrl.hostname.replace(/\./g, '_'); //提取hostname并用下划线替换.
} else {
// 如果没有配置devstar domain则默认domain为https://devstar.cn
this.devstarDomain = "https://devstar.cn"
this.devstarHostname = "devstar_cn"
}
}
this.usernameKey = "devstarUsername_" + this.devstarDomain
this.userTokenKey = "devstarUserToken_" + this.devstarDomain
}
setDevstarDomain(devstarDomain: string) {
// 修改devstar domain会影响hostname、usertoken key、username key
if (devstarDomain != "") {
this.devstarDomain = devstarDomain.endsWith('/') ? devstarDomain.slice(0, -1) : devstarDomain
const parsedUrl = new URL(devstarDomain)
this.devstarHostname = parsedUrl.hostname.replace(/\./g, '_');
this.usernameKey = "devstarUsername_" + this.devstarDomain
this.userTokenKey = "devstarUserToken_" + this.devstarDomain
} else {
console.error("devstar domain is null")
}
}
getDevstarDomain(): string {
return this.devstarDomain
}
public async login(token: string, username: string): Promise<string> {
const devstarAPIHandler = new DevstarAPIHandler(this.devstarDomain)
try {
const res = await devstarAPIHandler.verifyToken(token, username)
if (!res) {
throw new Error('Token verification failed')
} else {
// 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 utils.showErrorNotification('用户登录失败!')
return 'login failed'
}
}
public logout() {
this.setUserTokenToLocal("")
this.setUsernameToLocal("")
vscode.window.showInformationMessage(vscode.l10n.t("User has logged out!"))
}
public async isLogged(): Promise<boolean> {
const username: string|undefined = this.context.globalState.get(this.usernameKey)
const userToken: string|undefined = this.context.globalState.get(this.userTokenKey)
if ((username != undefined && username != '') && (userToken != undefined && userToken != '')) {
const devstarAPIHandler = new DevstarAPIHandler(this.devstarDomain)
const res = await devstarAPIHandler.verifyToken(userToken, username)
if (!res) {
console.error("username or token is error")
return false;
} else {
return true;
}
} else {
return false;
}
}
public getUsernameFromLocal(): string | undefined {
return this.context.globalState.get(this.usernameKey);
}
public getUserTokenFromLocal(): string | undefined {
return this.context.globalState.get(this.userTokenKey);
}
public setUsernameToLocal(username: string) {
this.context.globalState.update(this.usernameKey, 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)
if (userToken !== "") {
console.log('Token has been stored.');
} else {
console.log('Token has been cleaned up.')
}
}
public updateLocalUserPrivateKeyPath(localUserPrivateKeyPath: string) {
this.context.globalState.update('localUserPrivateKeyPath', localUserPrivateKeyPath)
}
public getLocalUserPrivateKeyPath(): undefined | string {
return this.context.globalState.get('localUserPrivateKeyPath')
}
public getUserPrivateKeyPath(): string {
if (!this.isLogged()) {
return '';
} else {
const username: string|undefined = this.context.globalState.get(this.usernameKey)
// islogged为trueusername不为空
return path.join(os.homedir(), '.ssh', `id_rsa_${username}_${this.devstarHostname}`)
}
}
public getUserPublicKeyPath(): string {
if (!this.isLogged()) {
return '';
} else {
const username: string|undefined = this.context.globalState.get(this.usernameKey)
// islogged为trueusername不为空
return path.join(os.homedir(), '.ssh', `id_rsa_${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 {
// 确保公/私钥目录存在
const publicKeyDir = path.dirname(this.getUserPublicKeyPath())
if (!fs.existsSync(publicKeyDir)) {
console.log(`Directory ${publicKeyDir} does not exist, creating it...`);
// 公钥与私钥的目录一样,所以只用创建一次
fs.mkdirSync(publicKeyDir, { recursive: true })
}
fs.writeFileSync(this.getUserPublicKeyPath(), publicKeyStr);
fs.writeFileSync(this.getUserPrivateKeyPath(), privateKeyStr);
// limit the permission of private key to prevent that the private key not works
fs.chmodSync(this.getUserPrivateKeyPath(), 0o600)
console.log(`User's ssh key has been created.`)
// 更新local user private key path
this.updateLocalUserPrivateKeyPath(this.getUserPrivateKeyPath())
console.log(`Update local user private key path.`)
} catch (error) {
const username: string|undefined = this.context.globalState.get(this.usernameKey)
console.error(`Failed to write public/private key into the user(${username}) ssh public/key file: `, error);
}
}
}