File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -23,6 +23,7 @@ export async function buildServer(): Promise<FastifyInstance> {
2323 await server . register ( import ( './plugins/rate-limiter' ) )
2424 await server . register ( import ( './plugins/database' ) )
2525 await server . register ( import ( './plugins/cache' ) )
26+ await server . register ( import ( './plugins/audit' ) )
2627 await server . register ( import ( './plugins/email' ) )
2728 await server . register ( import ( './plugins/email-queue' ) )
2829 await server . register ( import ( './plugins/webhook-queue' ) )
Original file line number Diff line number Diff line change 1+ import type { FastifyInstance , FastifyRequest } from 'fastify'
2+
3+ export const auditEventCatalog = [
4+ 'user.created' ,
5+ 'user.updated' ,
6+ 'user.deleted' ,
7+ 'user.signed_in' ,
8+ 'user.signed_out' ,
9+ 'session.created' ,
10+ 'session.revoked' ,
11+ 'session.compromised' ,
12+ 'oauth.connected' ,
13+ 'oauth.disconnected' ,
14+ 'passkey.registered' ,
15+ 'passkey.removed' ,
16+ 'mfa.enabled' ,
17+ 'mfa.disabled' ,
18+ 'password.changed' ,
19+ 'password.reset' ,
20+ 'email.verified' ,
21+ ] as const
22+
23+ export type AuditEventName = ( typeof auditEventCatalog ) [ number ]
24+
25+ export type AuditEventInput = {
26+ projectId : string
27+ event : AuditEventName | ( string & { } )
28+ userId ?: string
29+ metadata ?: Record < string , unknown >
30+ request ?: FastifyRequest
31+ }
32+
33+ export async function emitAuditEvent (
34+ server : FastifyInstance ,
35+ input : AuditEventInput ,
36+ ) : Promise < void > {
37+ await server . dbAdapter . createAuditLog ( {
38+ projectId : input . projectId ,
39+ userId : input . userId ,
40+ event : input . event ,
41+ ipAddress : input . request ?. ip ,
42+ userAgent : input . request ?. headers [ 'user-agent' ] as string | undefined ,
43+ metadata : input . metadata ?? { } ,
44+ } )
45+ }
Original file line number Diff line number Diff line change @@ -78,16 +78,17 @@ async function handleReuseDetection(
7878) : Promise < never > {
7979 await request . server . dbAdapter . revokeSessionFamily ( marker . tokenFamily )
8080
81- await request . server . dbAdapter . createAuditLog ( {
82- projectId : marker . projectId ,
83- userId : marker . userId ,
84- event : 'session.compromised' ,
85- ipAddress : request . ip ,
86- userAgent : request . headers [ 'user-agent' ] as string | undefined ,
87- metadata : {
88- reason : 'refresh_token_reuse_detected' ,
89- } ,
90- } )
81+ if ( typeof request . server . emitAuditEvent === 'function' ) {
82+ await request . server . emitAuditEvent ( {
83+ projectId : marker . projectId ,
84+ userId : marker . userId ,
85+ event : 'session.compromised' ,
86+ request,
87+ metadata : {
88+ reason : 'refresh_token_reuse_detected' ,
89+ } ,
90+ } )
91+ }
9192
9293 if ( typeof request . server . emitWebhookEvent === 'function' ) {
9394 try {
Original file line number Diff line number Diff line change @@ -149,6 +149,28 @@ export async function signinHandler(
149149 }
150150 }
151151
152+ if ( typeof request . server . emitAuditEvent === 'function' ) {
153+ await request . server . emitAuditEvent ( {
154+ projectId,
155+ userId : user . id ,
156+ event : 'user.signed_in' ,
157+ request,
158+ metadata : {
159+ method : 'password' ,
160+ } ,
161+ } )
162+
163+ await request . server . emitAuditEvent ( {
164+ projectId,
165+ userId : user . id ,
166+ event : 'session.created' ,
167+ request,
168+ metadata : {
169+ sessionId : session . id ,
170+ } ,
171+ } )
172+ }
173+
152174 reply . send ( {
153175 data : {
154176 user,
Original file line number Diff line number Diff line change @@ -68,6 +68,28 @@ export async function signoutHandler(
6868 request . log . warn ( { error } , 'Failed to enqueue signout webhook events' )
6969 }
7070 }
71+
72+ if ( typeof request . server . emitAuditEvent === 'function' ) {
73+ await request . server . emitAuditEvent ( {
74+ projectId : payload . pid ,
75+ userId : payload . sub ,
76+ event : 'user.signed_out' ,
77+ request,
78+ metadata : {
79+ sessionId : session . id ,
80+ } ,
81+ } )
82+
83+ await request . server . emitAuditEvent ( {
84+ projectId : payload . pid ,
85+ userId : payload . sub ,
86+ event : 'session.revoked' ,
87+ request,
88+ metadata : {
89+ sessionId : session . id ,
90+ } ,
91+ } )
92+ }
7193 }
7294
7395 clearRefreshTokenCookie ( reply , config . nodeEnv === 'production' )
Original file line number Diff line number Diff line change @@ -123,6 +123,28 @@ export async function signupHandler(
123123 }
124124 }
125125
126+ if ( typeof request . server . emitAuditEvent === 'function' ) {
127+ await request . server . emitAuditEvent ( {
128+ projectId,
129+ userId : user . id ,
130+ event : 'user.created' ,
131+ request,
132+ metadata : {
133+ method : 'password' ,
134+ } ,
135+ } )
136+
137+ await request . server . emitAuditEvent ( {
138+ projectId,
139+ userId : user . id ,
140+ event : 'session.created' ,
141+ request,
142+ metadata : {
143+ sessionId : session . id ,
144+ } ,
145+ } )
146+ }
147+
126148 reply . code ( 201 ) . send ( {
127149 data : {
128150 user,
Original file line number Diff line number Diff line change @@ -139,7 +139,7 @@ export async function mfaTotpEnableHandler(
139139 request : FastifyRequest ,
140140 reply : FastifyReply ,
141141) : Promise < void > {
142- const { userId } = await getCurrentUser ( request )
142+ const { userId, projectId } = await getCurrentUser ( request )
143143 const parsed = mfaCodeBodySchema . parse ( request . body )
144144
145145 const pendingRaw = await request . server . cache . get ( setupCacheKey ( userId ) )
@@ -177,6 +177,18 @@ export async function mfaTotpEnableHandler(
177177 300 ,
178178 )
179179
180+ if ( typeof request . server . emitAuditEvent === 'function' ) {
181+ await request . server . emitAuditEvent ( {
182+ projectId,
183+ userId,
184+ event : 'mfa.enabled' ,
185+ request,
186+ metadata : {
187+ method : 'totp' ,
188+ } ,
189+ } )
190+ }
191+
180192 reply . send ( {
181193 data : {
182194 success : true ,
@@ -189,7 +201,7 @@ export async function mfaTotpDisableHandler(
189201 request : FastifyRequest ,
190202 reply : FastifyReply ,
191203) : Promise < void > {
192- const { userId } = await getCurrentUser ( request )
204+ const { userId, projectId } = await getCurrentUser ( request )
193205 const parsed = mfaCodeBodySchema . parse ( request . body )
194206
195207 const valid = await verifyActiveMfaCode ( request , userId , parsed . code )
@@ -202,6 +214,18 @@ export async function mfaTotpDisableHandler(
202214 await request . server . cache . delete ( setupCacheKey ( userId ) )
203215 await request . server . cache . delete ( backupViewCacheKey ( userId ) )
204216
217+ if ( typeof request . server . emitAuditEvent === 'function' ) {
218+ await request . server . emitAuditEvent ( {
219+ projectId,
220+ userId,
221+ event : 'mfa.disabled' ,
222+ request,
223+ metadata : {
224+ method : 'totp' ,
225+ } ,
226+ } )
227+ }
228+
205229 reply . send ( {
206230 data : {
207231 success : true ,
Original file line number Diff line number Diff line change @@ -62,16 +62,17 @@ export async function oauthDisconnectHandler(
6262 }
6363 }
6464
65- await request . server . dbAdapter . createAuditLog ( {
66- projectId : auth . pid ,
67- userId : auth . sub ,
68- event : 'oauth.disconnected' ,
69- ipAddress : request . ip ,
70- userAgent : request . headers [ 'user-agent' ] as string | undefined ,
71- metadata : {
72- provider : providerId ,
73- } ,
74- } )
65+ if ( typeof request . server . emitAuditEvent === 'function' ) {
66+ await request . server . emitAuditEvent ( {
67+ projectId : auth . pid ,
68+ userId : auth . sub ,
69+ event : 'oauth.disconnected' ,
70+ request,
71+ metadata : {
72+ provider : providerId ,
73+ } ,
74+ } )
75+ }
7576
7677 reply . send ( {
7778 data : {
Original file line number Diff line number Diff line change @@ -304,6 +304,40 @@ export async function oauthCallbackHandler(
304304 }
305305 }
306306
307+ if ( typeof request . server . emitAuditEvent === 'function' ) {
308+ if ( oauthResult . linkedAccountCreated ) {
309+ await request . server . emitAuditEvent ( {
310+ projectId : state . projectId ,
311+ userId : user . id ,
312+ event : 'oauth.connected' ,
313+ request,
314+ metadata : {
315+ provider : providerConfig . id ,
316+ } ,
317+ } )
318+ }
319+
320+ await request . server . emitAuditEvent ( {
321+ projectId : state . projectId ,
322+ userId : user . id ,
323+ event : 'user.signed_in' ,
324+ request,
325+ metadata : {
326+ method : `oauth:${ providerConfig . id } ` ,
327+ } ,
328+ } )
329+
330+ await request . server . emitAuditEvent ( {
331+ projectId : state . projectId ,
332+ userId : user . id ,
333+ event : 'session.created' ,
334+ request,
335+ metadata : {
336+ sessionId : session . id ,
337+ } ,
338+ } )
339+ }
340+
307341 const location = appendHash (
308342 appendQuery ( state . redirectUrl , {
309343 provider : providerConfig . id ,
You can’t perform that action at this time.
0 commit comments