feat: Add login/logout audit logging (refs #10)
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 4m42s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 38s
Deploy to Staging / Verify Staging (pull_request) Successful in 7s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 6s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped

Backend:
- Add login event logging to getUserStatus() controller method
- Create POST /auth/track-logout endpoint for logout tracking

Frontend:
- Create useLogout hook that wraps Auth0 logout with audit tracking
- Update all logout locations to use the new hook (SettingsPage,
  Layout, MobileSettingsScreen, useDeletion)

Login events are logged when the frontend calls /auth/user-status after
Auth0 callback. Logout events are logged via fire-and-forget call to
/auth/track-logout before Auth0 logout.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Eric Gullickson
2026-01-11 12:08:41 -06:00
parent cdfba3c1a8
commit fbde51b8fd
7 changed files with 121 additions and 13 deletions

View File

@@ -193,6 +193,9 @@ export class AuthController {
* GET /api/auth/user-status
* Get user status for routing decisions
* Protected endpoint - requires JWT
*
* Note: This endpoint is called once per Auth0 callback (from CallbackPage/CallbackMobileScreen).
* We log the login event here since it's the first authenticated request after Auth0 redirect.
*/
async getUserStatus(request: FastifyRequest, reply: FastifyReply) {
try {
@@ -200,6 +203,17 @@ export class AuthController {
const result = await this.authService.getUserStatus(userId);
// Log login event to audit trail (called once per Auth0 callback)
const ipAddress = this.getClientIp(request);
await auditLogService.info(
'auth',
userId,
'User login',
'user',
userId,
{ ipAddress }
).catch(err => logger.error('Failed to log login audit event', { error: err }));
logger.info('User status retrieved', {
userId: userId.substring(0, 8) + '...',
emailVerified: result.emailVerified,
@@ -287,4 +301,43 @@ export class AuthController {
});
}
}
/**
* POST /api/auth/track-logout
* Track user logout event for audit logging
* Protected endpoint - requires JWT
*
* Called by frontend before Auth0 logout to capture the logout event.
* Returns success even if audit logging fails (non-blocking).
*/
async trackLogout(request: FastifyRequest, reply: FastifyReply) {
try {
const userId = (request as any).user.sub;
const ipAddress = this.getClientIp(request);
// Log logout event to audit trail
await auditLogService.info(
'auth',
userId,
'User logout',
'user',
userId,
{ ipAddress }
).catch(err => logger.error('Failed to log logout audit event', { error: err }));
logger.info('User logout tracked', {
userId: userId.substring(0, 8) + '...',
});
return reply.code(200).send({ success: true });
} catch (error: any) {
// Don't block logout on audit failure - always return success
logger.error('Failed to track logout', {
error,
userId: (request as any).user?.sub,
});
return reply.code(200).send({ success: true });
}
}
}

View File

@@ -48,4 +48,10 @@ export const authRoutes: FastifyPluginAsync = async (
preHandler: [fastify.authenticate],
handler: authController.requestPasswordReset.bind(authController),
});
// POST /api/auth/track-logout - Track logout event for audit (requires JWT)
fastify.post('/auth/track-logout', {
preHandler: [fastify.authenticate],
handler: authController.trackLogout.bind(authController),
});
};