登录日志本地化储存

This commit is contained in:
chaos-zhu 2024-10-20 22:31:27 +08:00
parent e9a567c3fe
commit fe5e75878a
11 changed files with 101 additions and 27 deletions

View File

@ -5,6 +5,7 @@
* 兼容移动端UI
* 新增移动端虚拟功能按键映射
* 调整终端功能菜单
* 登录日志本地化储存
* 修复终端选中文本无法复制的bug
* 修复无法展示服务端ping客户端延迟ms的bug
* 修复暗黑模式下的一些样式问题

View File

@ -16,6 +16,7 @@ module.exports = {
notifyDBPath: path.join(process.cwd(),'app/db/notify.db'),
notifyConfigDBPath: path.join(process.cwd(),'app/db/notify-config.db'),
onekeyDBPath: path.join(process.cwd(),'app/db/onekey.db'),
logDBPath: path.join(process.cwd(),'app/db/log.db'),
apiPrefix: '/api/v1',
logConfig: {
outDir: path.join(process.cwd(),'./app/logs'),

View File

@ -0,0 +1,16 @@
const { readLog } = require('../utils/storage')
let whiteList = process.env.ALLOWED_IPS ? process.env.ALLOWED_IPS.split(',') : []
async function getLog({ res }) {
let list = await readLog()
list = list.map(item => {
return { ...item, id: item._id }
})
list?.sort((a, b) => Number(b.date) - Number(a.date))
res.success({ data: { list, whiteList } })
}
module.exports = {
getLog
}

View File

@ -1,7 +1,7 @@
const jwt = require('jsonwebtoken')
const axios = require('axios')
const { asyncSendNotice } = require('../utils/notify')
const { readKey, writeKey } = require('../utils/storage')
const { readKey, writeKey, writeLog } = require('../utils/storage')
const { RSADecryptSync, AESEncryptSync, SHA1Encrypt } = require('../utils/encrypt')
const { getNetIPInfo } = require('../utils/tools')
@ -86,8 +86,7 @@ const beforeLoginHandler = async (clientIp, jwtExpires) => {
// 邮件登录通知
asyncSendNotice('login', '登录提醒', `地点:${ country + city }\nIP: ${ ip }`)
global.loginRecord.unshift(clientIPInfo)
if (global.loginRecord.length > 10) global.loginRecord = global.loginRecord.slice(0, 10)
await writeLog({ ip, country, city, date: Date.now(), type: 'login' })
return token
}
@ -109,10 +108,6 @@ const updatePwd = async ({ res, request }) => {
res.success({ data: true, msg: 'success' })
}
const getLoginRecord = async ({ res }) => {
res.success({ data: global.loginRecord, msg: 'success' })
}
const getEasynodeVersion = async ({ res }) => {
try {
// const { data } = await axios.get('https://api.github.com/repos/chaos-zhu/easynode/releases/latest')
@ -129,6 +124,5 @@ module.exports = {
login,
getpublicKey,
updatePwd,
getLoginRecord,
getEasynodeVersion
}

View File

@ -6,7 +6,7 @@ const { AESEncryptSync } = require('./utils/encrypt')
// 初始化公私钥, 供登录、保存ssh密钥/密码等加解密
async function initRsa() {
let keyObj = await readKey()
if(keyObj.privateKey && keyObj.publicKey) return consola.info('公私钥已存在[重新生成会导致已保存的ssh密钥信息失效]')
if (keyObj.privateKey && keyObj.publicKey) return consola.info('公私钥已存在[重新生成会导致已保存的ssh密钥信息失效]')
let key = new NodeRSA({ b: 1024 })
key.setOptions({ encryptionScheme: 'pkcs1', environment: 'browser' })
let privateKey = key.exportKey('pkcs1-private-pem')
@ -20,7 +20,7 @@ async function initRsa() {
// 随机的commonKey secret
async function randomJWTSecret() {
let keyObj = await readKey()
if(keyObj?.commonKey) return consola.info('commonKey密钥已存在')
if (keyObj?.commonKey) return consola.info('commonKey密钥已存在')
keyObj.commonKey = randomStr(16)
await writeKey(keyObj)
@ -30,6 +30,4 @@ async function randomJWTSecret() {
module.exports = async () => {
await randomJWTSecret() // 全局密钥
await initRsa() // 全局公钥密钥
// 用于记录客户端登录IP的列表
global.loginRecord = []
}

View File

@ -1,10 +1,11 @@
const { getSSHList, addSSH, updateSSH, removeSSH, getCommand } = require('../controller/ssh')
const { getHostList, addHost, updateHost, removeHost, importHost } = require('../controller/host')
const { login, getpublicKey, updatePwd, getLoginRecord, getEasynodeVersion } = require('../controller/user')
const { login, getpublicKey, updatePwd, getEasynodeVersion } = require('../controller/user')
const { getNotifyConfig, updateNotifyConfig, getNotifyList, updateNotifyList } = require('../controller/notify')
const { getGroupList, addGroupList, updateGroupList, removeGroup } = require('../controller/group')
const { getScriptList, getLocalScriptList, addScript, updateScriptList, removeScript } = require('../controller/scripts')
const { getOnekeyRecord, removeOnekeyRecord } = require('../controller/onekey')
const { getLog } = require('../controller/log')
const ssh = [
{
@ -76,11 +77,6 @@ const user = [
path: '/pwd',
controller: updatePwd
},
{
method: 'get',
path: '/get-login-record',
controller: getLoginRecord
},
{
method: 'get',
path: '/version',
@ -173,4 +169,12 @@ const onekey = [
controller: removeOnekeyRecord
}
]
module.exports = [].concat(ssh, host, user, notify, group, scripts, onekey)
const log = [
{
method: 'get',
path: '/log',
controller: getLog
}
]
module.exports = [].concat(ssh, host, user, notify, group, scripts, onekey, log)

View File

@ -7,7 +7,8 @@ const {
notifyConfigDBPath,
groupConfDBPath,
scriptsDBPath,
onekeyDBPath
onekeyDBPath,
logDBPath
} = require('../config')
module.exports.KeyDB = class KeyDB {
@ -97,3 +98,14 @@ module.exports.OnekeyDB = class OnekeyDB {
return OnekeyDB.instance
}
}
module.exports.LogDB = class LogDB {
constructor() {
if (!LogDB.instance) {
LogDB.instance = new Datastore({ filename: logDBPath, autoload: true })
}
}
getInstance() {
return LogDB.instance
}
}

View File

@ -1,4 +1,4 @@
const { KeyDB, HostListDB, SshRecordDB, NotifyDB, NotifyConfigDB, ScriptsDB, GroupDB, OnekeyDB } = require('./db-class')
const { KeyDB, HostListDB, SshRecordDB, NotifyDB, NotifyConfigDB, ScriptsDB, GroupDB, OnekeyDB, LogDB } = require('./db-class')
const readKey = async () => {
return new Promise((resolve, reject) => {
@ -281,6 +281,7 @@ const writeOneKeyRecord = async (records =[]) => {
})
})
}
const deleteOneKeyRecord = async (ids =[]) => {
return new Promise((resolve, reject) => {
const onekeyDB = new OnekeyDB().getInstance()
@ -296,6 +297,36 @@ const deleteOneKeyRecord = async (ids =[]) => {
})
}
const readLog = async () => {
return new Promise((resolve, reject) => {
const logDB = new LogDB().getInstance()
logDB.find({}, (err, docs) => {
if (err) {
consola.error('读取log DB错误: ', err)
reject(err)
} else {
logDB.compactDatafile()
resolve(docs)
}
})
})
}
const writeLog = async (records = {}) => {
return new Promise((resolve, reject) => {
const logDB = new LogDB().getInstance()
logDB.insert(records, (err, newDocs) => {
if (err) {
consola.error('写入新的onekey记录出错:', err)
reject(err)
} else {
logDB.compactDatafile()
resolve(newDocs)
}
})
})
}
module.exports = {
readSSHRecord, writeSSHRecord,
readHostList, writeHostList,
@ -304,5 +335,6 @@ module.exports = {
readNotifyConfig, writeNotifyConfig, getNotifySwByType,
readGroupList, writeGroupList,
readScriptList, writeScriptList,
readOneKeyRecord, writeOneKeyRecord, deleteOneKeyRecord
readOneKeyRecord, writeOneKeyRecord, deleteOneKeyRecord,
readLog, writeLog
}

View File

@ -47,7 +47,7 @@ export default {
return axios({ url: '/login', method: 'post', data })
},
getLoginRecord() {
return axios({ url: '/get-login-record', method: 'get' })
return axios({ url: '/log', method: 'get' })
},
updatePwd(data) {
return axios({ url: '/pwd', method: 'put', data })

View File

@ -88,7 +88,7 @@
<el-table-column label="操作" fixed="right" :width="isMobileScreen ? 'auto' : '260px'">
<template #default="{ row }">
<el-dropdown v-if="isMobileScreen" trigger="click">
<span class="el-dropdown-link">
<span class="link">
操作
<el-icon class="el-icon--right">
<arrow-down />

View File

@ -1,7 +1,15 @@
<template>
<el-alert type="success" :closable="false">
<el-alert v-if="allowedIPs" type="success" :closable="false">
<template #title>
<span style="letter-spacing: 2px;"> 系统只保存最近10条登录记录, 目前版本只保存在内存中, 重启面板服务后会丢失 </span>
<span style="letter-spacing: 2px;"> 登录白名单IP: </span>
<el-tag
v-for="(item, index) in allowedIPs"
:key="index"
class="allowed_ip_tag"
type="warning"
>
{{ item }}
</el-tag>
</template>
</el-alert>
<el-table v-loading="loading" :data="loginRecordList">
@ -22,12 +30,17 @@ const { proxy: { $api, $tools } } = getCurrentInstance()
const loginRecordList = ref([])
const loading = ref(false)
const total = ref('')
const allowedIPs = ref('')
const handleLookupLoginRecord = () => {
loading.value = true
$api.getLoginRecord()
.then(({ data }) => {
loginRecordList.value = data.map((item) => {
const { list, whiteList } = data
total.value = list.length
allowedIPs.value = whiteList || []
loginRecordList.value = list.map((item) => {
item.date = $tools.formatTimestamp(item.date)
return item
})
@ -43,4 +56,7 @@ onMounted(() => {
</script>
<style lang="scss" scoped>
.allowed_ip_tag {
margin: 0 5px;
}
</style>