diff --git a/server/app/controller/host.js b/server/app/controller/host.js index d2c2873..ba5a866 100644 --- a/server/app/controller/host.js +++ b/server/app/controller/host.js @@ -1,30 +1,45 @@ -const { readHostList, writeHostList } = require('../utils') +const { readHostList, writeHostList, RSADecryptSync, AESEncryptSync, AESDecryptSync } = require('../utils') async function getHostList({ res }) { // console.log('get-host-list') - const data = await readHostList() + let data = await readHostList() data?.sort((a, b) => Number(b.index || 0) - Number(a.index || 0)) + data = data.map((item) => { + const isConfig = Boolean(item.username && item.port && (item[item.authType])) + return { + ...item, + isConfig, + password: '', + privateKey: '' + } + }) res.success({ data }) } -async function saveHost({ +async function addHost({ res, request }) { let { body: { name, host: newHost, index, expired, expiredNotify, group, consoleUrl, remark, - port, username, authType, password, privateKey, command + port, username, authType, password, privateKey, credential, command, tempKey } } = request // console.log(request) if (!newHost || !name) return res.fail({ msg: 'missing params: name or host' }) let hostList = await readHostList() if (hostList?.some(({ host }) => host === newHost)) return res.fail({ msg: `主机${ newHost }已存在` }) - if (!Array.isArray(hostList)) hostList = [] - hostList.push({ - host: newHost, name, index, expired, expiredNotify, group, consoleUrl, remark, - port, username, authType, password, privateKey, command - }) + let record = { + name, host: newHost, index, expired, expiredNotify, group, consoleUrl, remark, + port, username, authType, password, privateKey, credential, command + } + const clearTempKey = await RSADecryptSync(tempKey) + console.log('clearTempKey:', clearTempKey) + const clearSSHKey = await AESDecryptSync(record[authType], clearTempKey) + // console.log(`${ authType }原密文: `, clearSSHKey) + record[authType] = await AESEncryptSync(clearSSHKey) + console.log(`${ authType }__commonKey加密存储: `, record[authType]) + hostList.push(record) await writeHostList(hostList) res.success() } @@ -35,17 +50,31 @@ async function updateHost({ let { body: { host: newHost, name: newName, index, oldHost, expired, expiredNotify, group, consoleUrl, remark, - port, username, authType, password, privateKey, command + port, username, authType, password, privateKey, credential, command, tempKey } } = request if (!newHost || !newName || !oldHost) return res.fail({ msg: '参数错误' }) let hostList = await readHostList() - if (!hostList.some(({ host }) => host === oldHost)) return res.fail({ msg: `原实例[${ oldHost }]不存在,请尝试新增实例` }) - let targetIdx = hostList.findIndex(({ host }) => host === oldHost) - hostList.splice(targetIdx, 1, { + let record = { name: newName, host: newHost, index, expired, expiredNotify, group, consoleUrl, remark, - port, username, authType, password, privateKey, command - }) + port, username, authType, password, privateKey, credential, command + } + if (!hostList.some(({ host }) => host === oldHost)) return res.fail({ msg: `原实例[${ oldHost }]不存在,请尝试新增实例` }) + + let idx = hostList.findIndex(({ host }) => host === oldHost) + const oldRecord = hostList[idx] + // 如果存在原认证方式则保存下来 + if (!record[authType] && oldRecord[authType]) { + record[authType] = oldRecord[authType] + } else { + const clearTempKey = await RSADecryptSync(tempKey) + console.log('clearTempKey:', clearTempKey) + const clearSSHKey = await AESDecryptSync(record[authType], clearTempKey) + // console.log(`${ authType }原密文: `, clearSSHKey) + record[authType] = await AESEncryptSync(clearSSHKey) + console.log(`${ authType }__commonKey加密存储: `, record[authType]) + } + hostList.splice(idx, 1, record) writeHostList(hostList) res.success() } @@ -89,7 +118,7 @@ async function removeHost({ module.exports = { getHostList, - saveHost, + addHost, updateHost, removeHost // updateHostSort diff --git a/server/app/controller/ssh.js b/server/app/controller/ssh.js index 6af5f76..3f53d26 100644 --- a/server/app/controller/ssh.js +++ b/server/app/controller/ssh.js @@ -1,52 +1,81 @@ -const { readSSHRecord, writeSSHRecord, AESEncryptSync } = require('../utils') +const { readSSHRecord, writeSSHRecord, readHostList, writeHostList, RSADecryptSync, AESEncryptSync, AESDecryptSync } = require('../utils') async function getSSHList({ res }) { // console.log('get-host-list') let data = await readSSHRecord() data = data?.map(item => { - const { host, port, username, _id } = item - return { host, port, username, _id } + const { name, authType, _id: id, date } = item + return { id, name, authType, privateKey: '', password: '', date } }) || [] + data.sort((a, b) => b.date - a.date) res.success({ data }) } -const updateSSH = async ({ res, request }) => { - let { body: { host, port, username, type, password, privateKey, randomKey, command } } = request - let record = { host, port, username, type, password, privateKey, randomKey, command } - if(!host || !port || !username || !type || !randomKey) return res.fail({ data: false, msg: '参数错误' }) - // 再做一次对称加密(方便ssh连接时解密) - record.randomKey = await AESEncryptSync(randomKey) +const addSSH = async ({ res, request }) => { + let { body: { name, authType, password, privateKey, tempKey } } = request + let record = { name, authType, password, privateKey } + if(!name || !record[authType]) return res.fail({ data: false, msg: '参数错误' }) let sshRecord = await readSSHRecord() - let idx = sshRecord.findIndex(item => item.host === host) - if(idx === -1) - sshRecord.push(record) - else - sshRecord.splice(idx, 1, record) + if (sshRecord.some(item => item.name === name)) return res.fail({ data: false, msg: '已存在同名凭证' }) + + const clearTempKey = await RSADecryptSync(tempKey) + console.log('clearTempKey:', clearTempKey) + const clearSSHKey = await AESDecryptSync(record[authType], clearTempKey) + // console.log(`${ authType }原密文: `, clearSSHKey) + record[authType] = await AESEncryptSync(clearSSHKey) + console.log(`${ authType }__commonKey加密存储: `, record[authType]) + + sshRecord.push({ ...record, date: Date.now() }) await writeSSHRecord(sshRecord) - consola.info('新增凭证:', host) + consola.info('新增凭证:', name) + res.success({ data: '保存成功' }) +} + +const updateSSH = async ({ res, request }) => { + let { body: { id, name, authType, password, privateKey, date, tempKey } } = request + let record = { name, authType, password, privateKey, date } + if(!id || !name) return res.fail({ data: false, msg: '请输入凭据名称' }) + let sshRecord = await readSSHRecord() + let idx = sshRecord.findIndex(item => item._id === id) + if (sshRecord.some(item => item.name === name && item.date !== date)) return res.fail({ data: false, msg: '已存在同名凭证' }) + if(idx === -1) res.fail({ data: false, msg: '请输入凭据名称' }) + const oldRecord = sshRecord[idx] + // 判断原记录是否存在当前更新记录的认证方式 + if (!oldRecord[authType] && !record[authType]) return res.fail({ data: false, msg: `请输入${ authType === 'password' ? '密码' : '密钥' }` }) + if (!record[authType] && oldRecord[authType]) { + record[authType] = oldRecord[authType] + } else { + const clearTempKey = await RSADecryptSync(tempKey) + console.log('clearTempKey:', clearTempKey) + const clearSSHKey = await AESDecryptSync(record[authType], clearTempKey) + // console.log(`${ authType }原密文: `, clearSSHKey) + record[authType] = await AESEncryptSync(clearSSHKey) + console.log(`${ authType }__commonKey加密存储: `, record[authType]) + } + sshRecord.splice(idx, 1, record) + await writeSSHRecord(sshRecord) + consola.info('修改凭证:', name) res.success({ data: '保存成功' }) } const removeSSH = async ({ res, request }) => { - let { body: { host } } = request + let { params: { id } } = request let sshRecord = await readSSHRecord() - let idx = sshRecord.findIndex(item => item.host === host) + let idx = sshRecord.findIndex(item => item._id === id) if(idx === -1) return res.fail({ msg: '凭证不存在' }) sshRecord.splice(idx, 1) - consola.info('移除凭证:', host) + // 将删除的凭证id从host中删除 + let hostList = await readHostList() + hostList = hostList.map(item => { + if (item.credential === id) item.credential = '' + return item + }) + await writeHostList(hostList) + consola.info('移除凭证:', id) await writeSSHRecord(sshRecord) res.success({ data: '移除成功' }) } -const existSSH = async ({ res, request }) => { - let { body: { host } } = request - let sshRecord = await readSSHRecord() - let isExist = sshRecord?.some(item => item.host === host) - consola.info('查询凭证:', host) - if(!isExist) return res.success({ data: false }) // host不存在 - res.success({ data: true }) // 存在 -} - const getCommand = async ({ res, request }) => { let { host } = request.query if(!host) return res.fail({ data: false, msg: '参数错误' }) @@ -61,8 +90,8 @@ const getCommand = async ({ res, request }) => { module.exports = { getSSHList, + addSSH, updateSSH, removeSSH, - existSSH, getCommand } diff --git a/server/app/router/routes.js b/server/app/router/routes.js index 9c1027a..b4794d8 100644 --- a/server/app/router/routes.js +++ b/server/app/router/routes.js @@ -1,5 +1,5 @@ -const { getSSHList, updateSSH, removeSSH, existSSH, getCommand } = require('../controller/ssh') -const { getHostList, saveHost, updateHost, removeHost } = require('../controller/host') +const { getSSHList, addSSH, updateSSH, removeSSH, getCommand } = require('../controller/ssh') +const { getHostList, addHost, updateHost, removeHost } = require('../controller/host') const { login, getpublicKey, updatePwd, getLoginRecord } = require('../controller/user') const { getSupportEmailList, getUserEmailList, updateUserEmailList, removeUserEmail, pushEmail, getNotifyList, updateNotifyList } = require('../controller/notify') const { getGroupList, addGroupList, updateGroupList, removeGroup } = require('../controller/group') @@ -10,21 +10,21 @@ const ssh = [ path: '/get-ssh-list', controller: getSSHList }, + { + method: 'post', + path: '/add-ssh', + controller: addSSH + }, { method: 'post', path: '/update-ssh', controller: updateSSH }, { - method: 'post', - path: '/remove-ssh', + method: 'delete', + path: '/remove-ssh/:id', controller: removeSSH }, - { - method: 'post', - path: '/exist-ssh', - controller: existSSH - }, { method: 'get', path: '/command', @@ -40,7 +40,7 @@ const host = [ { method: 'post', path: '/host-save', - controller: saveHost + controller: addHost }, { method: 'put', diff --git a/server/app/socket/terminal.js b/server/app/socket/terminal.js index b92e2dc..3e54dd4 100644 --- a/server/app/socket/terminal.js +++ b/server/app/socket/terminal.js @@ -52,6 +52,7 @@ module.exports = (httpServer) => { const sshRecord = await readSSHRecord() let loginInfo = sshRecord.find(item => item.host === ip) if (!sshRecord.some(item => item.host === ip)) return socket.emit('create_fail', `未找到【${ ip }】凭证`) + // :TODO: 不用tempKey加密了,统一使用commonKey加密 let { type, host, port, username, randomKey } = loginInfo try { // 解密放到try里面,防止报错【公私钥必须配对, 否则需要重新添加服务器密钥】 diff --git a/web/src/api/index.js b/web/src/api/index.js index 70471e0..69393fd 100644 --- a/web/src/api/index.js +++ b/web/src/api/index.js @@ -10,22 +10,25 @@ export default { getSSHList(params = {}) { return axios({ url: '/get-ssh-list', method: 'get', params }) }, + addSSH(data) { + return axios({ url: '/add-ssh', method: 'post', data }) + }, updateSSH(data) { return axios({ url: '/update-ssh', method: 'post', data }) }, - removeSSH(host) { - return axios({ url: '/remove-ssh', method: 'post', data: { host } }) - }, - existSSH(host) { - return axios({ url: '/exist-ssh', method: 'post', data: { host } }) + removeSSH(id) { + return axios({ url: `/remove-ssh/${ id }`, method: 'delete' }) }, + // existSSH(host) { + // return axios({ url: '/exist-ssh', method: 'post', data: { host } }) + // }, getCommand(host) { return axios({ url: '/command', method: 'get', params: { host } }) }, getHostList() { return axios({ url: '/host-list', method: 'get' }) }, - saveHost(data) { + addHost(data) { return axios({ url: '/host-save', method: 'post', data }) }, updateHost(data) { diff --git a/web/src/components/aside-box.vue b/web/src/components/aside-box.vue index 49e7234..86bd508 100644 --- a/web/src/components/aside-box.vue +++ b/web/src/components/aside-box.vue @@ -52,11 +52,11 @@ let menuList = reactive([ icon: markRaw(ScaleToOriginal), index: '/terminal' }, - // { - // name: '凭据管理', - // icon: markRaw(Key), - // index: '/credentials' - // }, + { + name: '凭据管理', + icon: markRaw(Key), + index: '/credentials' + }, { name: '分组管理', icon: markRaw(FolderOpened), diff --git a/web/src/store/index.js b/web/src/store/index.js index 9606194..5af481b 100644 --- a/web/src/store/index.js +++ b/web/src/store/index.js @@ -49,6 +49,11 @@ const useStore = defineStore({ // console.log('groupList:', groupList) this.$patch({ groupList }) }, + async getSSHList() { + const { data: sshList } = await $api.getSSHList() + // console.log('sshList:', sshList) + this.$patch({ sshList }) + }, getHostPing() { setTimeout(() => { this.hostList.forEach((item) => { diff --git a/web/src/utils/index.js b/web/src/utils/index.js index 5da324e..a830fc9 100644 --- a/web/src/utils/index.js +++ b/web/src/utils/index.js @@ -14,21 +14,11 @@ export const randomStr = (e) =>{ // rsa公钥加密 export const RSAEncrypt = (text) => { const publicKey = localStorage.getItem('publicKey') - if(!publicKey) return -1 // 公钥不存在 - const RSAPubEncrypt = new JSRsaEncrypt() // 生成实例 - RSAPubEncrypt.setPublicKey(publicKey) // 配置公钥(不是将公钥实例化时传入!!!) - const ciphertext = RSAPubEncrypt.encrypt(text) // 加密 - // console.log('rsa加密:', ciphertext) - return ciphertext -} - -// rsa公钥解密 -export const RSADecrypt = (text) => { - const publicKey = localStorage.getItem('publicKey') - if(!publicKey) return -1 // 公钥不存在 + if (!publicKey) return -1 // 公钥不存在 const RSAPubEncrypt = new JSRsaEncrypt() // 生成实例 RSAPubEncrypt.setPublicKey(publicKey) // 配置公钥(不是将公钥实例化时传入!!!) const ciphertext = RSAPubEncrypt.encrypt(text) // 加密 + // console.log('rsa公钥加密:', ciphertext) return ciphertext } diff --git a/web/src/views/credentials/index.vue b/web/src/views/credentials/index.vue index a7c34c8..1f774ab 100644 --- a/web/src/views/credentials/index.vue +++ b/web/src/views/credentials/index.vue @@ -1,19 +1,204 @@ - +.host_count { + display: block; + width: 100px; + text-align: center; + font-size: 15px; + color: #87cf63; + cursor: pointer; +} + \ No newline at end of file diff --git a/web/src/views/index.vue b/web/src/views/index.vue index 285c235..7aba68c 100644 --- a/web/src/views/index.vue +++ b/web/src/views/index.vue @@ -46,6 +46,7 @@ onBeforeMount(async () => { .router_box { min-height: calc(100vh - 60px - 20px); background-color: #fff; + border-radius: 6px; margin: 10px; } } diff --git a/web/src/views/login/index.vue b/web/src/views/login/index.vue index 772da82..6bcb6dd 100644 --- a/web/src/views/login/index.vue +++ b/web/src/views/login/index.vue @@ -86,7 +86,6 @@ const { proxy: { $store, $api, $message, $router } } = getCurrentInstance() const loginFormRefs = ref(null) const isSession = ref(true) -const visible = ref(true) const notKey = ref(false) const loading = ref(false) const loginForm = reactive({ diff --git a/web/src/views/server/components/host-card.vue b/web/src/views/server/components/host-card.vue index 7d5dbd8..c3b6765 100644 --- a/web/src/views/server/components/host-card.vue +++ b/web/src/views/server/components/host-card.vue @@ -164,12 +164,10 @@ - - diff --git a/web/src/views/server/index.vue b/web/src/views/server/index.vue index dec5bec..15230fa 100644 --- a/web/src/views/server/index.vue +++ b/web/src/views/server/index.vue @@ -106,7 +106,7 @@ const unwatchHost = watch(hostList, () => { const connectIo = () => { if (socket.value) socket.value.close() - unwatchHost() + if (typeof(unwatchHost) === 'function') unwatchHost() let socketInstance = io($serviceURI, { path: '/clients', forceNew: true, diff --git a/web/src/views/setting/components/group.vue b/web/src/views/setting/components/group.vue deleted file mode 100644 index 5c3cd9a..0000000 --- a/web/src/views/setting/components/group.vue +++ /dev/null @@ -1,239 +0,0 @@ - - - - - diff --git a/web/src/views/setting/components/sort.vue b/web/src/views/setting/components/sort.vue deleted file mode 100644 index 35cfa78..0000000 --- a/web/src/views/setting/components/sort.vue +++ /dev/null @@ -1,98 +0,0 @@ - - - - - diff --git a/web/src/views/setting/components/password.vue b/web/src/views/setting/components/user.vue similarity index 100% rename from web/src/views/setting/components/password.vue rename to web/src/views/setting/components/user.vue diff --git a/web/src/views/setting/index.vue b/web/src/views/setting/index.vue index 642ffd3..79eeadd 100644 --- a/web/src/views/setting/index.vue +++ b/web/src/views/setting/index.vue @@ -2,7 +2,7 @@
- +