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:
Ryan Moon
2026-03-28 16:23:20 -05:00
parent e44d461de1
commit e0493814f7
2 changed files with 8 additions and 0 deletions

View File

@@ -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)
}) })
} }

View File

@@ -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,