diff --git a/packages/libs/lib-server/src/basic/audit.ts b/packages/libs/lib-server/src/basic/audit.ts index f86a62fc4..adb1640d3 100644 --- a/packages/libs/lib-server/src/basic/audit.ts +++ b/packages/libs/lib-server/src/basic/audit.ts @@ -18,4 +18,5 @@ export type AuditLogContext = { scope?: string; userId?: number; username?: string; + success?: boolean; }; diff --git a/packages/libs/lib-server/src/basic/core/decoractor.ts b/packages/libs/lib-server/src/basic/core/decoractor.ts new file mode 100644 index 000000000..eccf60899 --- /dev/null +++ b/packages/libs/lib-server/src/basic/core/decoractor.ts @@ -0,0 +1,13 @@ +import { createRequestParamDecorator } from "@midwayjs/core"; + +export const AuditLog = (opts: { enabled?: boolean }) => { + return createRequestParamDecorator(ctx => { + if (!ctx.auditLog) { + ctx.auditLog = {}; + } + if (opts.enabled !== undefined) { + ctx.auditLog.enabled = opts.enabled || true; + } + return ctx.auditLog; + }); +}; diff --git a/packages/libs/lib-server/src/basic/core/index.ts b/packages/libs/lib-server/src/basic/core/index.ts new file mode 100644 index 000000000..78f9ec43e --- /dev/null +++ b/packages/libs/lib-server/src/basic/core/index.ts @@ -0,0 +1 @@ +export * from "./decoractor.js"; diff --git a/packages/libs/lib-server/src/basic/index.ts b/packages/libs/lib-server/src/basic/index.ts index 742df5d30..1ca5e9eaa 100644 --- a/packages/libs/lib-server/src/basic/index.ts +++ b/packages/libs/lib-server/src/basic/index.ts @@ -7,3 +7,4 @@ export * from "./result.js"; export * from "./base-service.js"; export * from "./audit.js"; export * from "./mode.js"; +export * from "./core/index.js"; diff --git a/packages/ui/certd-server/db/migration-mysql/v10051__audit_log_scope.sql b/packages/ui/certd-server/db/migration-mysql/v10051__audit_log_scope.sql index 0dd358558..8f1e93495 100644 --- a/packages/ui/certd-server/db/migration-mysql/v10051__audit_log_scope.sql +++ b/packages/ui/certd-server/db/migration-mysql/v10051__audit_log_scope.sql @@ -1,2 +1,4 @@ ALTER TABLE `cd_audit_log` ADD COLUMN `scope` varchar(32) NOT NULL DEFAULT 'user'; CREATE INDEX `index_audit_log_scope` ON `cd_audit_log` (`scope`); + +ALTER TABLE `cd_audit_log` ADD COLUMN `success` tinyint(1) NOT NULL DEFAULT 1; diff --git a/packages/ui/certd-server/db/migration-pg/v10051__audit_log_scope.sql b/packages/ui/certd-server/db/migration-pg/v10051__audit_log_scope.sql index 9dd4eb54a..e935d6a64 100644 --- a/packages/ui/certd-server/db/migration-pg/v10051__audit_log_scope.sql +++ b/packages/ui/certd-server/db/migration-pg/v10051__audit_log_scope.sql @@ -1,2 +1,4 @@ ALTER TABLE "cd_audit_log" ADD COLUMN "scope" varchar(32) NOT NULL DEFAULT ('user'); CREATE INDEX "index_audit_log_scope" ON "cd_audit_log" ("scope"); + +ALTER TABLE "cd_audit_log" ADD COLUMN "success" boolean NOT NULL DEFAULT true; diff --git a/packages/ui/certd-server/db/migration/v10051__audit_log_scope.sql b/packages/ui/certd-server/db/migration/v10051__audit_log_scope.sql index 9dd4eb54a..91a9adcba 100644 --- a/packages/ui/certd-server/db/migration/v10051__audit_log_scope.sql +++ b/packages/ui/certd-server/db/migration/v10051__audit_log_scope.sql @@ -1,2 +1,4 @@ ALTER TABLE "cd_audit_log" ADD COLUMN "scope" varchar(32) NOT NULL DEFAULT ('user'); CREATE INDEX "index_audit_log_scope" ON "cd_audit_log" ("scope"); + +ALTER TABLE "cd_audit_log" ADD COLUMN "success" boolean NOT NULL DEFAULT (1); \ No newline at end of file diff --git a/packages/ui/certd-server/src/configuration.ts b/packages/ui/certd-server/src/configuration.ts index f7b4d66be..28bb09337 100644 --- a/packages/ui/certd-server/src/configuration.ts +++ b/packages/ui/certd-server/src/configuration.ts @@ -134,6 +134,5 @@ export class MainConfiguration { logger.info(text); }); logger.info("当前环境:", this.app.getEnv()); // prod - } } diff --git a/packages/ui/certd-server/src/controller/basic/login/forgot-password-controller.ts b/packages/ui/certd-server/src/controller/basic/login/forgot-password-controller.ts index 787cba21a..90d12a599 100644 --- a/packages/ui/certd-server/src/controller/basic/login/forgot-password-controller.ts +++ b/packages/ui/certd-server/src/controller/basic/login/forgot-password-controller.ts @@ -54,7 +54,7 @@ export class ForgotPasswordController extends BaseController { } else { throw new CommonException("暂不支持的找回类型,请联系管理员找回"); } - const {id,username} = await this.userService.forgotPassword(body); + const { id, username } = await this.userService.forgotPassword(body); username && this.loginService.clearCacheOnSuccess(username); this.auditLog({ userId: id, content: "用户请求找回密码" }); return this.ok(); diff --git a/packages/ui/certd-server/src/controller/basic/login/login-controller.ts b/packages/ui/certd-server/src/controller/basic/login/login-controller.ts index 94ca404ff..dec526b40 100644 --- a/packages/ui/certd-server/src/controller/basic/login/login-controller.ts +++ b/packages/ui/certd-server/src/controller/basic/login/login-controller.ts @@ -7,8 +7,6 @@ import { CaptchaService } from "../../../modules/basic/service/captcha-service.j import { PasskeyService } from "../../../modules/login/service/passkey-service.js"; import { AuditType } from "../../../modules/sys/enterprise/service/audit-constants.js"; -/** - */ @Provide() @Controller("/api/") export class LoginController extends BaseController { @@ -46,10 +44,10 @@ export class LoginController extends BaseController { try { const token = await this.loginService.loginByPassword(body); this.writeTokenCookie(token); - this.auditLog({ userId: token.userId, username: body.username, content: `用户「${body.username}」登录成功` }); + this.auditLog({ userId: token.userId, username: token.username, content: `用户「${body.username}」登录成功` }); return this.ok(token); - } catch (err:any) { - this.auditLog({userId:err.userId, username: body.username, content: `用户「${body.username}」登录失败` }); + } catch (err: any) { + this.auditLog({userId:err.userId, username: body.username, content: `用户「${body.username}」登录失败:${err.message}` }); throw err; } } @@ -79,10 +77,10 @@ export class LoginController extends BaseController { }); this.writeTokenCookie(token); - this.auditLog({ userId: token.userId, username: body.mobile, content: `用户「${body.mobile}」短信登录成功` }); + this.auditLog({ userId: token.userId, username: token.username, content: `用户「${body.mobile}」短信登录成功` }); return this.ok(token); - } catch (err) { - this.auditLog({ userId: err.userId, username: body.mobile, content: `用户「${body.mobile}」短信登录失败` }); + } catch (err: any) { + this.auditLog({userId: err.userId, username: body.mobile, content: `用户「${body.mobile}」短信登录失败:${err.message}` }); throw err; } } @@ -99,10 +97,10 @@ export class LoginController extends BaseController { }); this.writeTokenCookie(token); - this.auditLog({ userId: token.userId, username: body.loginId, content: `用户「${body.loginId}」两步验证登录成功` }); + this.auditLog({ userId: token.userId, username: token.username, content: `用户「${body.loginId}」两步验证登录成功` }); return this.ok(token); - } catch (err) { - this.auditLog({ userId: err.userId, username: body.loginId, content: `用户「${body.loginId}」两步验证登录失败` }); + } catch (err: any) { + this.auditLog({userId: err.userId, username: body.loginId, content: `用户「${body.loginId}」两步验证登录失败:${err.message}` }); throw err; } } @@ -132,10 +130,10 @@ export class LoginController extends BaseController { ); this.writeTokenCookie(token); - this.auditLog({ userId: token.userId, content: "用户Passkey登录成功" }); + this.auditLog({ userId: token.userId, username: token.username, content: "用户Passkey登录成功" }); return this.ok(token); - } catch (err) { - this.auditLog({ userId: err.userId, content: "用户Passkey登录失败" }); + } catch (err: any) { + this.auditLog({userId: err.userId, username: body.credential, content: `用户Passkey登录失败:${err.message}` }); throw err; } } diff --git a/packages/ui/certd-server/src/controller/user/audit/audit-controller.ts b/packages/ui/certd-server/src/controller/user/audit/audit-controller.ts index aa5c6909e..f5e1f686c 100644 --- a/packages/ui/certd-server/src/controller/user/audit/audit-controller.ts +++ b/packages/ui/certd-server/src/controller/user/audit/audit-controller.ts @@ -14,18 +14,18 @@ export class AuditLogController extends BaseController { @Post("/page", { description: Constants.per.authOnly, summary: "分页查询当前用户操作日志" }) async page(@Body(ALL) body: any) { const { projectId, userId } = await this.getProjectUserIdRead(); - const query: any = { } + const query: any = {}; if (projectId) { //如果是项目级别,排除userId参数,因为日志这里userId不是-1 ,而是实际的用户 query.projectId = projectId; - }else{ + } else { query.userId = userId; } body.query = { - ...body.query || {}, + ...(body.query || {}), ...query, - scope: "user" - } + scope: "user", + }; const pageRet = await this.auditService.page(body); return this.ok(pageRet); diff --git a/packages/ui/certd-server/src/controller/user/cert/dns-persist-record-controller.ts b/packages/ui/certd-server/src/controller/user/cert/dns-persist-record-controller.ts index 4ad56f081..d452aa8ab 100644 --- a/packages/ui/certd-server/src/controller/user/cert/dns-persist-record-controller.ts +++ b/packages/ui/certd-server/src/controller/user/cert/dns-persist-record-controller.ts @@ -33,7 +33,7 @@ export class DnsPersistRecordController extends CrudController { assert.equal(records.length, 0); }); - it("skips failed response", async () => { + it("writes audit log with success=false on failed response", async () => { const { middleware, records } = createMiddleware(); const ctx = createCtx(); ctx.body = Constants.res.error; await middleware.resolve()(ctx, async () => {}); - assert.equal(records.length, 0); + assert.equal(records.length, 1); + assert.equal(records[0].success, false); }); it("skips anonymous request", async () => { diff --git a/packages/ui/certd-server/src/middleware/audit-log.ts b/packages/ui/certd-server/src/middleware/audit-log.ts index 5b436234f..fa60526ee 100644 --- a/packages/ui/certd-server/src/middleware/audit-log.ts +++ b/packages/ui/certd-server/src/middleware/audit-log.ts @@ -2,53 +2,47 @@ import { Inject, Provide } from "@midwayjs/core"; import { IMidwayKoaContext, IWebMiddleware, NextFunction } from "@midwayjs/koa"; import { Constants } from "@certd/lib-server"; import { AuditService } from "../modules/sys/enterprise/service/audit-service.js"; +import { isPlus } from "@certd/plus-core"; @Provide() export class AuditLogMiddleware implements IWebMiddleware { @Inject() auditService: AuditService; - private isPlusFn: (() => Promise) | null = null; - - private async getIsPlus(): Promise { - if (!this.isPlusFn) { - try { - const mod = await import("@certd/plus-core"); - this.isPlusFn = async () => mod.isPlus(); - } catch { - this.isPlusFn = async () => false; - } - } - return this.isPlusFn(); - } resolve() { return async (ctx: IMidwayKoaContext, next: NextFunction) => { - await next(); - await this.writeAuditLog(ctx); + try { + await next(); + await this.writeAuditLog(ctx); + } catch (err) { + await this.writeAuditLog(ctx, err); + throw err; + } }; } - private async writeAuditLog(ctx: IMidwayKoaContext) { + private async writeAuditLog(ctx: IMidwayKoaContext, err?: any) { const routeInfo = ctx.auditRouteInfo; if (!routeInfo) { return; } - if (!ctx.auditLog?.enabled) { - return; - } - const isPlus = await this.getIsPlus(); - if (!isPlus) { - return; - } - if (!this.isSuccessResponse(ctx)) { - return; - } - const auditLog = ctx.auditLog; - if (!auditLog.enabled) { + if (!auditLog?.enabled) { return; } + if (!isPlus()) { + return; + } + let isSuccess = this.isSuccessResponse(ctx); + + if (err) { + isSuccess = false; + if (err?.userId != null && auditLog?.userId == null) { + auditLog.userId = err.userId; + } + } + const type = auditLog.type || (await this.resolveControllerType(routeInfo.controllerClz, ctx as any)); const action = auditLog.action || routeInfo.summary || ""; const append = auditLog.append; @@ -61,7 +55,7 @@ export class AuditLogMiddleware implements IWebMiddleware { const projectId = auditLog.projectId ?? ctx.projectId ?? 0; const scope = auditLog.scope || (ctx.path.startsWith("/api/sys/") ? "system" : "user"); - const ipAddress = ctx.ip || ""; + const ipAddress = ctx.ip || ""; await this.auditService.log({ userId: auditLog.userId ?? ctx.user?.id ?? 0, @@ -70,8 +64,10 @@ export class AuditLogMiddleware implements IWebMiddleware { content, username: auditLog.username || ctx.user?.username, projectId, + projectName: auditLog.projectName, ipAddress, scope, + success: isSuccess, }); } diff --git a/packages/ui/certd-server/src/modules/login/service/login-service.ts b/packages/ui/certd-server/src/modules/login/service/login-service.ts index 8c5ac377a..b1b689c0f 100644 --- a/packages/ui/certd-server/src/modules/login/service/login-service.ts +++ b/packages/ui/certd-server/src/modules/login/service/login-service.ts @@ -252,6 +252,7 @@ export class LoginService { token, expire, userId: user.id, + username: user.username, }; } diff --git a/packages/ui/certd-server/src/modules/pipeline/service/pipeline-service.ts b/packages/ui/certd-server/src/modules/pipeline/service/pipeline-service.ts index fe1700c4c..a4640829a 100644 --- a/packages/ui/certd-server/src/modules/pipeline/service/pipeline-service.ts +++ b/packages/ui/certd-server/src/modules/pipeline/service/pipeline-service.ts @@ -974,7 +974,7 @@ export class PipelineService extends BaseService { await this.checkUserId(id, projectId, "projectId"); } await this.delete(id); - ids.push(id); + ids.push(id); } return ids.length; } diff --git a/packages/ui/certd-server/src/modules/sys/enterprise/entity/audit-log.ts b/packages/ui/certd-server/src/modules/sys/enterprise/entity/audit-log.ts index 77d702fac..63633d75b 100644 --- a/packages/ui/certd-server/src/modules/sys/enterprise/entity/audit-log.ts +++ b/packages/ui/certd-server/src/modules/sys/enterprise/entity/audit-log.ts @@ -34,6 +34,9 @@ export class AuditLogEntity { @Column({ name: "ip_address", comment: "IP地址" }) ipAddress: string; + @Column({ name: "success", comment: "是否成功", default: true }) + success: boolean; + @Column({ name: "create_time", comment: "创建时间", diff --git a/packages/ui/certd-server/src/modules/sys/enterprise/service/audit-service.ts b/packages/ui/certd-server/src/modules/sys/enterprise/service/audit-service.ts index 2f9cf9241..02988c316 100644 --- a/packages/ui/certd-server/src/modules/sys/enterprise/service/audit-service.ts +++ b/packages/ui/certd-server/src/modules/sys/enterprise/service/audit-service.ts @@ -25,13 +25,13 @@ export class AuditService extends BaseService { const end = pageReq.query.createTime[1]; delete pageReq.query.createTime; pageReq.buildQuery = (qb: any) => { - qb.andWhere("main.createTime BETWEEN :start AND :end", { start, end }); + qb.andWhere("main.createTime BETWEEN :start AND :end", { start, end }); }; } return await super.page(pageReq); } - async log(params: { userId: number; type: string; action: string; content: string; username?: string; projectId?: number; ipAddress?: string; scope?: string }): Promise { + async log(params: { userId: number; type: string; action: string; content: string; username?: string; projectId?: number; projectName?: string; ipAddress?: string; scope?: string; success?: boolean }): Promise { try { let { username } = params; if (!username && params.userId != null) { @@ -47,9 +47,10 @@ export class AuditService extends BaseService { entity.action = params.action; entity.content = params.content; entity.projectId = params.projectId || 0; - entity.projectName = ""; + entity.projectName = params.projectName || ""; entity.ipAddress = params.ipAddress || ""; entity.scope = params.scope || "user"; + entity.success = params.success ?? true; await this.repository.save(entity); } catch (e) { logger.error("写入审计日志失败:", e);