mirror of
https://github.com/whyour/qinglong.git
synced 2026-06-30 20:35:09 +08:00
增加登录日志
This commit is contained in:
@@ -167,4 +167,19 @@ export default (app: Router) => {
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
route.get(
|
||||
'/user/login-log',
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
const authService = Container.get(AuthService);
|
||||
const data = await authService.getLoginLog();
|
||||
res.send({ code: 200, data });
|
||||
} catch (e) {
|
||||
logger.error('🔥 error: %o', e);
|
||||
return next(e);
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
@@ -24,6 +24,7 @@ const dbPath = path.join(rootPath, 'db/');
|
||||
const cronDbFile = path.join(rootPath, 'db/crontab.db');
|
||||
const envDbFile = path.join(rootPath, 'db/env.db');
|
||||
const appDbFile = path.join(rootPath, 'db/app.db');
|
||||
const authDbFile = path.join(rootPath, 'db/auth.db');
|
||||
|
||||
const configFound = dotenv.config({ path: confFile });
|
||||
|
||||
@@ -60,6 +61,7 @@ export default {
|
||||
cronDbFile,
|
||||
envDbFile,
|
||||
appDbFile,
|
||||
authDbFile,
|
||||
configPath,
|
||||
scriptPath,
|
||||
samplePath,
|
||||
|
||||
+3
-2
@@ -158,11 +158,12 @@ export async function getNetIp(req: any) {
|
||||
}
|
||||
try {
|
||||
const baiduApi = got
|
||||
.get(`https://www.cip.cc/${ip}`, { timeout: 100000 })
|
||||
.get(`https://www.cip.cc/${ip}`, { timeout: 10000, retry: 0 })
|
||||
.text();
|
||||
const ipApi = got
|
||||
.get(`https://whois.pconline.com.cn/ipJson.jsp?ip=${ip}&json=true`, {
|
||||
timeout: 100000,
|
||||
timeout: 10000,
|
||||
retry: 0,
|
||||
})
|
||||
.buffer();
|
||||
const [data, ipApiBody] = await await Promise.all<any>([baiduApi, ipApi]);
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
export class AuthInfo {
|
||||
ip?: string;
|
||||
type: AuthInfoType;
|
||||
info?: any;
|
||||
_id?: string;
|
||||
|
||||
constructor(options: AuthInfo) {
|
||||
this.ip = options.ip;
|
||||
this.info = options.info;
|
||||
this.type = options.type;
|
||||
this._id = options._id;
|
||||
}
|
||||
}
|
||||
|
||||
export enum LoginStatus {
|
||||
'success',
|
||||
'fail',
|
||||
}
|
||||
|
||||
export type AuthInfoType = 'loginLog' | 'authToken';
|
||||
+61
-1
@@ -6,10 +6,20 @@ import * as fs from 'fs';
|
||||
import _ from 'lodash';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { authenticator } from '@otplib/preset-default';
|
||||
import { exec } from 'child_process';
|
||||
import DataStore from 'nedb';
|
||||
import { AuthInfo, LoginStatus } from '../data/auth';
|
||||
|
||||
@Service()
|
||||
export default class AuthService {
|
||||
constructor(@Inject('logger') private logger: winston.Logger) {}
|
||||
private authDb = new DataStore({ filename: config.authDbFile });
|
||||
|
||||
constructor(@Inject('logger') private logger: winston.Logger) {
|
||||
this.authDb.loadDatabase((err) => {
|
||||
if (err) throw err;
|
||||
});
|
||||
this.authDb.persistence.setAutocompactionInterval(30000);
|
||||
}
|
||||
|
||||
public async login(
|
||||
payloads: {
|
||||
@@ -84,6 +94,16 @@ export default class AuthService {
|
||||
lastaddr: address,
|
||||
isTwoFactorChecking: false,
|
||||
});
|
||||
exec(
|
||||
`notify 登陆通知 你于${new Date(
|
||||
timestamp,
|
||||
).toLocaleString()}在${address}登陆成功,ip地址${ip}`,
|
||||
);
|
||||
await this.getLoginLog();
|
||||
await this.insertDb({
|
||||
type: 'loginLog',
|
||||
info: { timestamp, address, ip, status: LoginStatus.success },
|
||||
});
|
||||
return {
|
||||
code: 200,
|
||||
data: { token, lastip, lastaddr, lastlogon, retries },
|
||||
@@ -95,6 +115,16 @@ export default class AuthService {
|
||||
lastip: ip,
|
||||
lastaddr: address,
|
||||
});
|
||||
exec(
|
||||
`notify 登陆通知 你于${new Date(
|
||||
timestamp,
|
||||
).toLocaleString()}在${address}登陆失败,ip地址${ip}`,
|
||||
);
|
||||
await this.getLoginLog();
|
||||
await this.insertDb({
|
||||
type: 'loginLog',
|
||||
info: { timestamp, address, ip, status: LoginStatus.fail },
|
||||
});
|
||||
return { code: 400, message: config.authError };
|
||||
}
|
||||
} else {
|
||||
@@ -102,6 +132,36 @@ export default class AuthService {
|
||||
}
|
||||
}
|
||||
|
||||
public async getLoginLog(): Promise<AuthInfo[]> {
|
||||
return new Promise((resolve) => {
|
||||
this.authDb.find({ type: 'loginLog' }).exec((err, docs) => {
|
||||
if (err || docs.length === 0) {
|
||||
resolve(docs);
|
||||
} else {
|
||||
const result = docs.sort(
|
||||
(a, b) => b.info.timestamp - a.info.timestamp,
|
||||
);
|
||||
if (result.length > 100) {
|
||||
this.authDb.remove({ _id: result[result.length - 1]._id });
|
||||
}
|
||||
resolve(result.map((x) => x.info));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async insertDb(payload: AuthInfo): Promise<AuthInfo> {
|
||||
return new Promise((resolve) => {
|
||||
this.authDb.insert(payload, (err, doc) => {
|
||||
if (err) {
|
||||
this.logger.error(err);
|
||||
} else {
|
||||
resolve(doc);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private initAuthInfo() {
|
||||
const newPassword = createRandomString(16, 22);
|
||||
fs.writeFileSync(
|
||||
|
||||
Reference in New Issue
Block a user