refactor(audit): type-safe enums, expanded coverage, and crash-safe logging
Replace magic strings with AuditAction/AuditStatus enums across all consumers, add logAuditWithCurrentUser() convenience wrapper, extend audit coverage to data import, result export, app update, and ERP credentials operations, and harden crash handlers with try/catch to prevent audit failures from cascading. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,32 +1,42 @@
|
||||
import { app } from 'electron'
|
||||
import logger from '../services/logger/index'
|
||||
import { logAudit, closeAuditLogger } from '../services/logger/audit-logger'
|
||||
import { AuditAction, AuditStatus } from '../types/audit.types'
|
||||
import { serializeError } from '../services/logger/error-utils'
|
||||
|
||||
export function setupProcessGuards(): void {
|
||||
process.on('uncaughtException', (err) => {
|
||||
logger.error('Uncaught exception', { error: err })
|
||||
logAudit('SYSTEM_CRASH', 'system', {
|
||||
username: 'system',
|
||||
computerName: process.env.COMPUTERNAME || 'unknown',
|
||||
resource: 'main-process',
|
||||
status: 'failure',
|
||||
metadata: { error: err.message, stack: err.stack }
|
||||
})
|
||||
setTimeout(() => process.exit(1), 1000)
|
||||
try {
|
||||
logAudit(AuditAction.SYSTEM_CRASH, 'system', {
|
||||
username: 'system',
|
||||
computerName: process.env.COMPUTERNAME || 'unknown',
|
||||
resource: 'main-process',
|
||||
status: AuditStatus.FAILURE,
|
||||
metadata: { error: err.message, stack: err.stack }
|
||||
})
|
||||
} catch (auditError) {
|
||||
logger.error('Failed to write crash audit log', { error: auditError })
|
||||
} finally {
|
||||
setTimeout(() => process.exit(1), 1000)
|
||||
}
|
||||
})
|
||||
|
||||
process.on('unhandledRejection', (reason) => {
|
||||
const errorMeta =
|
||||
reason instanceof Error ? { error: serializeError(reason) } : { reason: String(reason) }
|
||||
logger.error('Unhandled Rejection', errorMeta)
|
||||
logAudit('SYSTEM_ERROR', 'system', {
|
||||
username: 'system',
|
||||
computerName: process.env.COMPUTERNAME || 'unknown',
|
||||
resource: 'main-process',
|
||||
status: 'failure',
|
||||
metadata: errorMeta
|
||||
})
|
||||
try {
|
||||
logAudit(AuditAction.SYSTEM_ERROR, 'system', {
|
||||
username: 'system',
|
||||
computerName: process.env.COMPUTERNAME || 'unknown',
|
||||
resource: 'main-process',
|
||||
status: AuditStatus.FAILURE,
|
||||
metadata: errorMeta
|
||||
})
|
||||
} catch (auditError) {
|
||||
logger.error('Failed to write unhandled rejection audit log', { error: auditError })
|
||||
}
|
||||
})
|
||||
|
||||
app.on('render-process-gone', (_, webContents, details) => {
|
||||
|
||||
@@ -5,7 +5,8 @@ import { OrderNumberResolver } from '../services/erp/order-resolver'
|
||||
import { create, type IDatabaseService } from '../services/database'
|
||||
import { ExtractorOperationHistoryDAO } from '../services/database/extractor-operation-history-dao'
|
||||
import { createLogger } from '../services/logger'
|
||||
import { logAudit } from '../services/logger/audit-logger'
|
||||
import { logAuditWithCurrentUser } from '../services/logger/audit-logger'
|
||||
import { AuditAction, AuditStatus } from '../types/audit.types'
|
||||
import { SessionManager } from '../services/user/session-manager'
|
||||
import { withErrorHandling, type IpcResult } from './index'
|
||||
import { ErpConnectionError, ValidationError, DatabaseQueryError } from '../types/errors'
|
||||
@@ -278,24 +279,17 @@ export function registerExtractorHandlers(): void {
|
||||
}
|
||||
|
||||
// Audit log: EXTRACT (non-blocking)
|
||||
const os = await import('os')
|
||||
if (currentUser) {
|
||||
const auditStatus: 'success' | 'failure' | 'partial' =
|
||||
const auditStatus: AuditStatus =
|
||||
result.errors.length > 0 && result.recordCount > 0
|
||||
? 'partial'
|
||||
? AuditStatus.PARTIAL
|
||||
: result.errors.length > 0
|
||||
? 'failure'
|
||||
: 'success'
|
||||
logAudit('EXTRACT', String(currentUser.id), {
|
||||
username: currentUser.username,
|
||||
computerName: os.hostname(),
|
||||
resource: 'MATERIAL_PLAN',
|
||||
status: auditStatus,
|
||||
metadata: {
|
||||
orderCount: validOrderNumbers.length,
|
||||
recordCount: result.recordCount,
|
||||
errorCount: result.errors.length
|
||||
}
|
||||
? AuditStatus.FAILURE
|
||||
: AuditStatus.SUCCESS
|
||||
logAuditWithCurrentUser(AuditAction.EXTRACT, 'MATERIAL_PLAN', auditStatus, {
|
||||
orderCount: validOrderNumbers.length,
|
||||
recordCount: result.recordCount,
|
||||
errorCount: result.errors.length
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,8 @@ import { UserErpConfigService } from '../services/user/user-erp-config-service'
|
||||
import { MySqlService } from '../services/database/mysql'
|
||||
import { SqlServerService } from '../services/database/sql-server'
|
||||
import { createLogger } from '../services/logger'
|
||||
import { logAudit } from '../services/logger/audit-logger'
|
||||
import { logAuditWithCurrentUser } from '../services/logger/audit-logger'
|
||||
import { AuditAction, AuditStatus } from '../types/audit.types'
|
||||
import type { UserType, ConnectionTestResult, SaveSettingsResult } from '../types/settings.types'
|
||||
import { IPC_CHANNELS } from '../../shared/ipc-channels'
|
||||
import { ValidationError } from '../types/errors'
|
||||
@@ -67,13 +68,9 @@ export function registerSettingsHandlers(): void {
|
||||
})
|
||||
|
||||
// Audit log: SETTINGS_CHANGE (non-blocking)
|
||||
const os = await import('os')
|
||||
logAudit('SETTINGS_CHANGE', String(currentUser.id), {
|
||||
username: currentUser.username,
|
||||
computerName: os.hostname(),
|
||||
resource: 'ERP_CONFIG',
|
||||
status: 'success',
|
||||
metadata: { changeType: 'erp_credentials', usernameChanged: !!settings.erp.username }
|
||||
logAuditWithCurrentUser(AuditAction.SETTINGS_CHANGE, 'ERP_CONFIG', AuditStatus.SUCCESS, {
|
||||
changeType: 'erp_credentials',
|
||||
usernameChanged: !!settings.erp.username
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { SessionManager } from '../user/session-manager'
|
||||
import { UpdateService } from '../update/update-service'
|
||||
import { createLogger, run, getRequestId, getContext } from '../logger'
|
||||
import { logAudit } from '../logger/audit-logger'
|
||||
import { AuditAction, AuditStatus } from '../../types/audit.types'
|
||||
import { ValidationError } from '../../types/errors'
|
||||
import type { UserInfo } from '../../types/user.types'
|
||||
import type {
|
||||
@@ -73,11 +74,11 @@ export class AuthApplicationService {
|
||||
userId: userInfo.id
|
||||
})
|
||||
|
||||
this.writeAuditLog('LOGIN', String(userInfo.id), {
|
||||
this.writeAuditLog(AuditAction.LOGIN, String(userInfo.id), {
|
||||
username: userInfo.username,
|
||||
computerName: hostname(),
|
||||
resource: 'ERP_SYSTEM',
|
||||
status: 'success',
|
||||
status: AuditStatus.SUCCESS,
|
||||
metadata: { loginType: 'silent', userType: userInfo.userType }
|
||||
})
|
||||
|
||||
@@ -127,11 +128,11 @@ export class AuthApplicationService {
|
||||
const userInfo = this.sessionManager.getUserInfo()
|
||||
|
||||
if (!success || !userInfo) {
|
||||
this.writeAuditLog('LOGIN', '0', {
|
||||
this.writeAuditLog(AuditAction.LOGIN, '0', {
|
||||
username,
|
||||
computerName: hostname(),
|
||||
resource: 'ERP_SYSTEM',
|
||||
status: 'failure',
|
||||
status: AuditStatus.FAILURE,
|
||||
metadata: { loginType: 'credentials', reason: 'invalid_credentials' }
|
||||
})
|
||||
|
||||
@@ -155,11 +156,11 @@ export class AuthApplicationService {
|
||||
})
|
||||
await this.updateService.setUserContext(userInfo.userType)
|
||||
|
||||
this.writeAuditLog('LOGIN', String(userInfo.id), {
|
||||
this.writeAuditLog(AuditAction.LOGIN, String(userInfo.id), {
|
||||
username: userInfo.username,
|
||||
computerName: hostname(),
|
||||
resource: 'ERP_SYSTEM',
|
||||
status: 'success',
|
||||
status: AuditStatus.SUCCESS,
|
||||
metadata: { loginType: 'credentials', userType: userInfo.userType }
|
||||
})
|
||||
|
||||
@@ -204,11 +205,11 @@ export class AuthApplicationService {
|
||||
})
|
||||
|
||||
if (userInfo) {
|
||||
this.writeAuditLog('LOGOUT', String(userInfo.id), {
|
||||
this.writeAuditLog(AuditAction.LOGOUT, String(userInfo.id), {
|
||||
username: userInfo.username,
|
||||
computerName: hostname(),
|
||||
resource: 'ERP_SYSTEM',
|
||||
status: 'success',
|
||||
status: AuditStatus.SUCCESS,
|
||||
metadata: { userType: userInfo.userType }
|
||||
})
|
||||
}
|
||||
@@ -310,7 +311,7 @@ export class AuthApplicationService {
|
||||
}
|
||||
|
||||
private writeAuditLog(
|
||||
action: 'LOGIN' | 'LOGOUT',
|
||||
action: AuditAction.LOGIN | AuditAction.LOGOUT,
|
||||
actorId: string,
|
||||
payload: Parameters<typeof logAudit>[2]
|
||||
): void {
|
||||
|
||||
@@ -13,7 +13,8 @@ import { RustfsService } from '../rustfs'
|
||||
import { SessionManager } from '../user/session-manager'
|
||||
import { UserErpConfigService } from '../user/user-erp-config-service'
|
||||
import { createLogger } from '../logger'
|
||||
import { logAudit } from '../logger/audit-logger'
|
||||
import { logAuditWithCurrentUser } from '../logger/audit-logger'
|
||||
import { AuditAction, AuditStatus } from '../../types/audit.types'
|
||||
import { IPC_CHANNELS } from '../../../shared/ipc-channels'
|
||||
import { DatabaseQueryError, ErpConnectionError, ValidationError } from '../../types/errors'
|
||||
import type {
|
||||
@@ -278,27 +279,21 @@ export class CleanerApplicationService {
|
||||
return
|
||||
}
|
||||
|
||||
const status: 'success' | 'failure' | 'partial' =
|
||||
const status: AuditStatus =
|
||||
result.errors.length > 0 && result.materialsDeleted > 0
|
||||
? 'partial'
|
||||
? AuditStatus.PARTIAL
|
||||
: result.errors.length > 0
|
||||
? 'failure'
|
||||
: 'success'
|
||||
? AuditStatus.FAILURE
|
||||
: AuditStatus.SUCCESS
|
||||
|
||||
logAudit('CLEAN', String(currentUser.id), {
|
||||
username: currentUser.username,
|
||||
computerName: (await import('os')).hostname(),
|
||||
resource: 'MATERIAL_PLAN',
|
||||
status,
|
||||
metadata: {
|
||||
orderCount,
|
||||
dryRun: input.dryRun ?? false,
|
||||
queryBatchSize: input.queryBatchSize ?? 100,
|
||||
processConcurrency: input.processConcurrency ?? 1,
|
||||
materialsDeleted: result.materialsDeleted,
|
||||
materialsSkipped: result.materialsSkipped,
|
||||
errorCount: result.errors.length
|
||||
}
|
||||
logAuditWithCurrentUser(AuditAction.CLEAN, 'MATERIAL_PLAN', status, {
|
||||
orderCount,
|
||||
dryRun: input.dryRun ?? false,
|
||||
queryBatchSize: input.queryBatchSize ?? 100,
|
||||
processConcurrency: input.processConcurrency ?? 1,
|
||||
materialsDeleted: result.materialsDeleted,
|
||||
materialsSkipped: result.materialsSkipped,
|
||||
errorCount: result.errors.length
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
*/
|
||||
|
||||
import { createLogger } from '../logger'
|
||||
import { logAuditWithCurrentUser } from '../logger/audit-logger'
|
||||
import { AuditAction, AuditStatus } from '../../types/audit.types'
|
||||
import { DiscreteMaterialPlanDAO, type MaterialPlanRecord } from './discrete-material-plan-dao'
|
||||
|
||||
const log = createLogger('DataImportService')
|
||||
@@ -148,6 +150,20 @@ export class DataImportService {
|
||||
}
|
||||
}
|
||||
|
||||
// Audit log: DATA_IMPORT
|
||||
logAuditWithCurrentUser(
|
||||
AuditAction.DATA_IMPORT,
|
||||
'MATERIAL_PLAN',
|
||||
result.success ? AuditStatus.SUCCESS : AuditStatus.FAILURE,
|
||||
{
|
||||
recordsRead: result.recordsRead,
|
||||
recordsDeleted: result.recordsDeleted,
|
||||
recordsImported: result.recordsImported,
|
||||
uniqueSourceNumbers: result.uniqueSourceNumbers,
|
||||
errorCount: result.errors.length
|
||||
}
|
||||
)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ import path from 'path'
|
||||
import { app } from 'electron'
|
||||
import fs from 'fs'
|
||||
import { createLogger } from '../logger'
|
||||
import { logAuditWithCurrentUser } from '../logger/audit-logger'
|
||||
import { AuditAction, AuditStatus } from '../../types/audit.types'
|
||||
import type { ExportResultItem, ExportResultResponse } from '../../types/cleaner.types'
|
||||
|
||||
const log = createLogger('ResultExporter')
|
||||
@@ -37,8 +39,8 @@ export class ResultExporter {
|
||||
* @returns Export result with file path or error
|
||||
*/
|
||||
async exportValidationResults(items: ExportResultItem[]): Promise<ExportResultResponse> {
|
||||
const filePath = path.join(this.exportDir, this.fileName)
|
||||
try {
|
||||
const filePath = path.join(this.exportDir, this.fileName)
|
||||
log.info('Exporting validation results', { count: items.length, path: filePath })
|
||||
|
||||
const workbook = new ExcelJS.Workbook()
|
||||
@@ -97,6 +99,12 @@ export class ResultExporter {
|
||||
await workbook.xlsx.writeFile(filePath)
|
||||
log.info('Export completed', { path: filePath, rows: items.length })
|
||||
|
||||
// Audit log: RESULT_EXPORT success
|
||||
logAuditWithCurrentUser(AuditAction.RESULT_EXPORT, 'VALIDATION_RESULT', AuditStatus.SUCCESS, {
|
||||
itemCount: items.length,
|
||||
filePath
|
||||
})
|
||||
|
||||
return {
|
||||
success: true,
|
||||
filePath
|
||||
@@ -104,6 +112,14 @@ export class ResultExporter {
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
log.error('Export failed', { error: errorMessage })
|
||||
|
||||
// Audit log: RESULT_EXPORT failure
|
||||
logAuditWithCurrentUser(AuditAction.RESULT_EXPORT, 'VALIDATION_RESULT', AuditStatus.FAILURE, {
|
||||
itemCount: items.length,
|
||||
filePath,
|
||||
error: errorMessage
|
||||
})
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: errorMessage
|
||||
|
||||
@@ -6,33 +6,12 @@
|
||||
import winston from 'winston'
|
||||
import DailyRotateFile from 'winston-daily-rotate-file'
|
||||
import path from 'path'
|
||||
import { hostname } from 'os'
|
||||
import { app } from 'electron'
|
||||
import { getLogDir } from './shared'
|
||||
|
||||
/**
|
||||
* Audit log entry structure
|
||||
* All 8 required fields for comprehensive audit tracking
|
||||
*/
|
||||
export interface AuditEntry {
|
||||
/** ISO 8601 timestamp of the audit event */
|
||||
timestamp: string
|
||||
/** The action that was performed (e.g., 'LOGIN', 'EXTRACT', 'DELETE') */
|
||||
action: string
|
||||
/** User ID who performed the action */
|
||||
userId: string
|
||||
/** Username of the user who performed the action */
|
||||
username: string
|
||||
/** Computer name from which the action was performed */
|
||||
computerName: string
|
||||
/** Application version when the action was performed */
|
||||
appVersion: string
|
||||
/** The resource that was affected (e.g., table name, file path) */
|
||||
resource: string
|
||||
/** Status of the action: 'success' | 'failure' | 'partial' */
|
||||
status: 'success' | 'failure' | 'partial'
|
||||
/** Additional metadata about the audit event */
|
||||
metadata: Record<string, unknown>
|
||||
}
|
||||
import { SessionManager } from '../user/session-manager'
|
||||
import type { AuditEntry } from '../../types/audit.types'
|
||||
import { AuditAction, AuditStatus } from '../../types/audit.types'
|
||||
|
||||
/**
|
||||
* JSONL formatter - outputs one JSON object per line
|
||||
@@ -91,31 +70,58 @@ export function applyAuditConfig(retentionDays: number): void {
|
||||
* @param details - Additional details including username, computerName, resource, status, and optional metadata
|
||||
*/
|
||||
export function logAudit(
|
||||
action: string,
|
||||
action: AuditAction,
|
||||
userId: string,
|
||||
details: {
|
||||
username: string
|
||||
computerName: string
|
||||
resource: string
|
||||
status: 'success' | 'failure' | 'partial'
|
||||
status: AuditStatus
|
||||
metadata?: Record<string, unknown>
|
||||
}
|
||||
): void {
|
||||
const entry: AuditEntry = {
|
||||
timestamp: new Date().toISOString(),
|
||||
action,
|
||||
userId,
|
||||
username: details.username,
|
||||
computerName: details.computerName,
|
||||
appVersion: app.getVersion(),
|
||||
resource: details.resource,
|
||||
status: details.status,
|
||||
metadata: details.metadata || {}
|
||||
}
|
||||
try {
|
||||
const entry: AuditEntry = {
|
||||
timestamp: new Date().toISOString(),
|
||||
action,
|
||||
userId,
|
||||
username: details.username,
|
||||
computerName: details.computerName,
|
||||
appVersion: app.getVersion(),
|
||||
resource: details.resource,
|
||||
status: details.status,
|
||||
metadata: details.metadata ?? {}
|
||||
}
|
||||
|
||||
// Write as JSONL - one JSON object per line
|
||||
// Using info level with the entry stringified as the message
|
||||
auditLogger.info(JSON.stringify(entry))
|
||||
// Write as JSONL - one JSON object per line
|
||||
// Using info level with the entry stringified as the message
|
||||
auditLogger.info(JSON.stringify(entry))
|
||||
} catch (error) {
|
||||
console.error('Audit logging failed:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/** Cached hostname — invariant for the app lifecycle */
|
||||
const cachedHostname = hostname()
|
||||
|
||||
/**
|
||||
* Audit log shortcut that auto-resolves the current user context.
|
||||
* Falls back to 'anonymous' if no user is logged in, so the record is always written.
|
||||
*/
|
||||
export function logAuditWithCurrentUser(
|
||||
action: AuditAction,
|
||||
resource: string,
|
||||
status: AuditStatus,
|
||||
metadata?: Record<string, unknown>
|
||||
): void {
|
||||
const user = SessionManager.getInstance().getUserInfo()
|
||||
logAudit(action, user ? String(user.id) : 'anonymous', {
|
||||
username: user?.username ?? 'anonymous',
|
||||
computerName: cachedHostname,
|
||||
resource,
|
||||
status,
|
||||
metadata: metadata ?? {}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import * as fs from 'fs'
|
||||
import { ConfigManager } from '../config/config-manager'
|
||||
import { createLogger } from '../logger'
|
||||
import { logAuditWithCurrentUser } from '../logger/audit-logger'
|
||||
import { AuditAction, AuditStatus } from '../../types/audit.types'
|
||||
import type { UpdateConfig } from '../../types/config.schema'
|
||||
import type { UserType } from '../../types/user.types'
|
||||
import type {
|
||||
@@ -281,10 +283,21 @@ export class UpdateService {
|
||||
log.error('Update package hash mismatch', {
|
||||
version: request.version,
|
||||
channel: request.channel,
|
||||
expectedHash: request.sha256,
|
||||
actualHash: hash
|
||||
expectedHash: request.sha256.substring(0, 16),
|
||||
actualHash: hash.substring(0, 16)
|
||||
})
|
||||
await fs.promises.rm(downloadPath, { force: true })
|
||||
|
||||
// Audit log: APP_UPDATE download hash mismatch
|
||||
logAuditWithCurrentUser(AuditAction.APP_UPDATE, 'UPDATE_PACKAGE', AuditStatus.FAILURE, {
|
||||
version: request.version,
|
||||
channel: request.channel,
|
||||
phase: 'download',
|
||||
error: 'Hash mismatch',
|
||||
expectedHash: request.sha256.substring(0, 16),
|
||||
actualHash: hash.substring(0, 16)
|
||||
})
|
||||
|
||||
throw new Error('更新包校验失败,文件哈希不匹配')
|
||||
}
|
||||
|
||||
@@ -294,6 +307,13 @@ export class UpdateService {
|
||||
downloadPath
|
||||
})
|
||||
|
||||
// Audit log: APP_UPDATE download success
|
||||
logAuditWithCurrentUser(AuditAction.APP_UPDATE, 'UPDATE_PACKAGE', AuditStatus.SUCCESS, {
|
||||
version: request.version,
|
||||
channel: request.channel,
|
||||
phase: 'download'
|
||||
})
|
||||
|
||||
this.publishStatus({
|
||||
phase: 'downloaded',
|
||||
progress: 100,
|
||||
@@ -336,11 +356,29 @@ export class UpdateService {
|
||||
error: undefined
|
||||
})
|
||||
|
||||
await this.installer.installDownloadedRelease(downloaded)
|
||||
log.info('Update installation completed', {
|
||||
version: downloaded.version,
|
||||
channel: downloaded.channel
|
||||
})
|
||||
try {
|
||||
await this.installer.installDownloadedRelease(downloaded)
|
||||
log.info('Update installation completed', {
|
||||
version: downloaded.version,
|
||||
channel: downloaded.channel
|
||||
})
|
||||
|
||||
// Audit log: APP_UPDATE install success
|
||||
logAuditWithCurrentUser(AuditAction.APP_UPDATE, 'UPDATE_PACKAGE', AuditStatus.SUCCESS, {
|
||||
version: downloaded.version,
|
||||
channel: downloaded.channel,
|
||||
phase: 'install'
|
||||
})
|
||||
} catch (installError) {
|
||||
const msg = installError instanceof Error ? installError.message : String(installError)
|
||||
logAuditWithCurrentUser(AuditAction.APP_UPDATE, 'UPDATE_PACKAGE', AuditStatus.FAILURE, {
|
||||
version: downloaded.version,
|
||||
channel: downloaded.channel,
|
||||
phase: 'install',
|
||||
error: msg
|
||||
})
|
||||
throw installError
|
||||
}
|
||||
}
|
||||
|
||||
private ensureInitialized(): void {
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
import { BIPUsersDAO } from './bip-users-dao'
|
||||
import { SessionManager } from './session-manager'
|
||||
import { createLogger } from '../logger'
|
||||
import { logAuditWithCurrentUser } from '../logger/audit-logger'
|
||||
import { AuditAction, AuditStatus } from '../../types/audit.types'
|
||||
|
||||
const log = createLogger('UserErpConfigService')
|
||||
|
||||
@@ -131,9 +133,22 @@ export class UserErpConfigService {
|
||||
log.error('Failed to update ERP credentials', { username: currentUser.username })
|
||||
}
|
||||
|
||||
logAuditWithCurrentUser(
|
||||
AuditAction.ERP_CREDENTIALS_UPDATE,
|
||||
'ERP_CREDENTIALS',
|
||||
success ? AuditStatus.SUCCESS : AuditStatus.FAILURE,
|
||||
{ targetUsername: currentUser.username, updateType: 'self' }
|
||||
)
|
||||
|
||||
return success
|
||||
} catch (error) {
|
||||
log.error('Error updating current user ERP credentials', { error })
|
||||
logAuditWithCurrentUser(
|
||||
AuditAction.ERP_CREDENTIALS_UPDATE,
|
||||
'ERP_CREDENTIALS',
|
||||
AuditStatus.FAILURE,
|
||||
{ targetUsername: 'unknown', updateType: 'self', error: String(error) }
|
||||
)
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -159,9 +174,22 @@ export class UserErpConfigService {
|
||||
log.error('Failed to update ERP credentials', { username })
|
||||
}
|
||||
|
||||
logAuditWithCurrentUser(
|
||||
AuditAction.ERP_CREDENTIALS_UPDATE,
|
||||
'ERP_CREDENTIALS',
|
||||
success ? AuditStatus.SUCCESS : AuditStatus.FAILURE,
|
||||
{ targetUsername: username, updateType: 'admin' }
|
||||
)
|
||||
|
||||
return success
|
||||
} catch (error) {
|
||||
log.error('Error updating user ERP credentials', { error })
|
||||
logAuditWithCurrentUser(
|
||||
AuditAction.ERP_CREDENTIALS_UPDATE,
|
||||
'ERP_CREDENTIALS',
|
||||
AuditStatus.FAILURE,
|
||||
{ targetUsername: username, updateType: 'admin', error: String(error) }
|
||||
)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,23 +10,31 @@ export enum AuditAction {
|
||||
LOGOUT = 'LOGOUT',
|
||||
EXTRACT = 'EXTRACT',
|
||||
CLEAN = 'CLEAN',
|
||||
SETTINGS_CHANGE = 'SETTINGS_CHANGE'
|
||||
SETTINGS_CHANGE = 'SETTINGS_CHANGE',
|
||||
SYSTEM_CRASH = 'SYSTEM_CRASH',
|
||||
SYSTEM_ERROR = 'SYSTEM_ERROR',
|
||||
DATA_IMPORT = 'DATA_IMPORT',
|
||||
RESULT_EXPORT = 'RESULT_EXPORT',
|
||||
APP_UPDATE = 'APP_UPDATE',
|
||||
ERP_CREDENTIALS_UPDATE = 'ERP_CREDENTIALS_UPDATE'
|
||||
}
|
||||
|
||||
/**
|
||||
* Audit status enumeration
|
||||
* Note: Values are lowercase to match JSON logging conventions
|
||||
*/
|
||||
export enum AuditStatus {
|
||||
SUCCESS = 'SUCCESS',
|
||||
FAILURE = 'FAILURE'
|
||||
SUCCESS = 'success',
|
||||
FAILURE = 'failure',
|
||||
PARTIAL = 'partial'
|
||||
}
|
||||
|
||||
/**
|
||||
* Audit entry interface
|
||||
*/
|
||||
export interface AuditEntry {
|
||||
/** Timestamp of the action */
|
||||
timestamp: Date
|
||||
/** ISO timestamp of the action */
|
||||
timestamp: string
|
||||
/** Action performed */
|
||||
action: AuditAction
|
||||
/** User ID who performed the action */
|
||||
@@ -38,9 +46,9 @@ export interface AuditEntry {
|
||||
/** Application version when action was performed */
|
||||
appVersion: string
|
||||
/** Resource affected by the action */
|
||||
resource?: string
|
||||
resource: string
|
||||
/** Status of the action */
|
||||
status: AuditStatus
|
||||
/** Additional metadata in JSON format */
|
||||
metadata?: string
|
||||
/** Additional metadata */
|
||||
metadata: Record<string, unknown>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user