Add audit logging for sensitive operations
Structured logging with request ID correlation throughout: - Auth: register, login success, login failure (warn level) - Accounts: soft-delete - Members: move between accounts - Tax exemptions: approve (info), revoke (warn with reason) - Files: upload, delete (already had logging) All logs include userId, entityId, and contextual data for debugging. 4xx errors logged as warn, 5xx as error.
This commit is contained in:
@@ -63,6 +63,7 @@ export const accountRoutes: FastifyPluginAsync = async (app) => {
|
|||||||
const { id } = request.params as { id: string }
|
const { id } = request.params as { id: string }
|
||||||
const account = await AccountService.softDelete(app.db, request.companyId, id)
|
const account = await AccountService.softDelete(app.db, request.companyId, id)
|
||||||
if (!account) return reply.status(404).send({ error: { message: 'Account not found', statusCode: 404 } })
|
if (!account) return reply.status(404).send({ error: { message: 'Account not found', statusCode: 404 } })
|
||||||
|
request.log.info({ accountId: id, userId: request.user.id }, 'Account soft-deleted')
|
||||||
return reply.send(account)
|
return reply.send(account)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -129,6 +130,7 @@ export const accountRoutes: FastifyPluginAsync = async (app) => {
|
|||||||
|
|
||||||
const member = await MemberService.move(app.db, request.companyId, id, targetAccountId)
|
const member = await MemberService.move(app.db, request.companyId, id, targetAccountId)
|
||||||
if (!member) return reply.status(404).send({ error: { message: 'Member not found', statusCode: 404 } })
|
if (!member) return reply.status(404).send({ error: { message: 'Member not found', statusCode: 404 } })
|
||||||
|
request.log.info({ memberId: id, targetAccountId, userId: request.user.id }, 'Member moved to account')
|
||||||
return reply.send(member)
|
return reply.send(member)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -294,6 +296,7 @@ export const accountRoutes: FastifyPluginAsync = async (app) => {
|
|||||||
const { id } = request.params as { id: string }
|
const { id } = request.params as { id: string }
|
||||||
const exemption = await TaxExemptionService.approve(app.db, request.companyId, id, request.user.id)
|
const exemption = await TaxExemptionService.approve(app.db, request.companyId, id, request.user.id)
|
||||||
if (!exemption) return reply.status(404).send({ error: { message: 'Tax exemption not found', statusCode: 404 } })
|
if (!exemption) return reply.status(404).send({ error: { message: 'Tax exemption not found', statusCode: 404 } })
|
||||||
|
request.log.info({ exemptionId: id, accountId: exemption.accountId, userId: request.user.id }, 'Tax exemption approved')
|
||||||
return reply.send(exemption)
|
return reply.send(exemption)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -305,6 +308,7 @@ export const accountRoutes: FastifyPluginAsync = async (app) => {
|
|||||||
}
|
}
|
||||||
const exemption = await TaxExemptionService.revoke(app.db, request.companyId, id, request.user.id, reason)
|
const exemption = await TaxExemptionService.revoke(app.db, request.companyId, id, request.user.id, reason)
|
||||||
if (!exemption) return reply.status(404).send({ error: { message: 'Tax exemption not found', statusCode: 404 } })
|
if (!exemption) return reply.status(404).send({ error: { message: 'Tax exemption not found', statusCode: 404 } })
|
||||||
|
request.log.warn({ exemptionId: id, accountId: exemption.accountId, userId: request.user.id, reason }, 'Tax exemption revoked')
|
||||||
return reply.send(exemption)
|
return reply.send(exemption)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ export const authRoutes: FastifyPluginAsync = async (app) => {
|
|||||||
role: user.role,
|
role: user.role,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
request.log.info({ userId: user.id, email: user.email, companyId }, 'User registered')
|
||||||
return reply.status(201).send({ user, token })
|
return reply.status(201).send({ user, token })
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -109,6 +110,7 @@ export const authRoutes: FastifyPluginAsync = async (app) => {
|
|||||||
.limit(1)
|
.limit(1)
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
|
request.log.warn({ email }, 'Login failed — unknown email')
|
||||||
return reply.status(401).send({
|
return reply.status(401).send({
|
||||||
error: { message: 'Invalid email or password', statusCode: 401 },
|
error: { message: 'Invalid email or password', statusCode: 401 },
|
||||||
})
|
})
|
||||||
@@ -116,6 +118,7 @@ export const authRoutes: FastifyPluginAsync = async (app) => {
|
|||||||
|
|
||||||
const valid = await bcrypt.compare(password, user.passwordHash)
|
const valid = await bcrypt.compare(password, user.passwordHash)
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
|
request.log.warn({ email, userId: user.id }, 'Login failed — wrong password')
|
||||||
return reply.status(401).send({
|
return reply.status(401).send({
|
||||||
error: { message: 'Invalid email or password', statusCode: 401 },
|
error: { message: 'Invalid email or password', statusCode: 401 },
|
||||||
})
|
})
|
||||||
@@ -127,6 +130,7 @@ export const authRoutes: FastifyPluginAsync = async (app) => {
|
|||||||
role: user.role,
|
role: user.role,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
request.log.info({ userId: user.id, email }, 'User logged in')
|
||||||
return reply.send({
|
return reply.send({
|
||||||
user: {
|
user: {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
|
|||||||
Reference in New Issue
Block a user