sonamu 0.9.3 → 0.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/dist/api/config.d.ts +0 -8
  2. package/dist/api/config.d.ts.map +1 -1
  3. package/dist/api/config.js +1 -1
  4. package/dist/api/sonamu.d.ts +0 -1
  5. package/dist/api/sonamu.d.ts.map +1 -1
  6. package/dist/api/sonamu.js +2 -41
  7. package/dist/auth/audit-log/builders.d.ts +216 -0
  8. package/dist/auth/audit-log/builders.d.ts.map +1 -0
  9. package/dist/auth/audit-log/builders.js +307 -0
  10. package/dist/auth/audit-log/events.d.ts +143 -0
  11. package/dist/auth/audit-log/events.d.ts.map +1 -0
  12. package/dist/auth/audit-log/events.js +74 -0
  13. package/dist/auth/audit-log/plugin.d.ts +11 -0
  14. package/dist/auth/audit-log/plugin.d.ts.map +1 -0
  15. package/dist/auth/audit-log/plugin.js +427 -0
  16. package/dist/auth/audit-log-ingestor.d.ts +3 -3
  17. package/dist/auth/audit-log-ingestor.d.ts.map +1 -1
  18. package/dist/auth/audit-log-ingestor.js +44 -50
  19. package/dist/auth/index.d.ts +2 -0
  20. package/dist/auth/index.d.ts.map +1 -1
  21. package/dist/auth/index.js +4 -4
  22. package/dist/auth/plugins/entity-definitions/admin.d.ts +1 -1
  23. package/dist/auth/plugins/entity-definitions/admin.js +4 -4
  24. package/dist/auth/plugins/entity-definitions/audit-log.d.ts +2 -2
  25. package/dist/auth/plugins/entity-definitions/audit-log.js +3 -3
  26. package/dist/auth/plugins/wrappers/admin.d.ts +2 -2
  27. package/dist/auth/plugins/wrappers/sso.d.ts +1 -1
  28. package/dist/bin/fixture.d.ts.map +1 -1
  29. package/dist/bin/fixture.js +111 -1
  30. package/dist/database/_batch_update.d.ts +1 -1
  31. package/dist/database/_batch_update.js +2 -2
  32. package/dist/entity/entity-manager.d.ts +2 -2
  33. package/dist/entity/entity-manager.d.ts.map +1 -1
  34. package/dist/entity/entity-manager.js +14 -4
  35. package/dist/index.js +4 -3
  36. package/dist/syncer/syncer.d.ts.map +1 -1
  37. package/dist/syncer/syncer.js +2 -9
  38. package/dist/template/implementations/entry-server.template.js +3 -2
  39. package/dist/template/implementations/generated.template.d.ts.map +1 -1
  40. package/dist/template/implementations/generated.template.js +2 -1
  41. package/dist/template/implementations/generated_sso.template.d.ts.map +1 -1
  42. package/dist/template/implementations/generated_sso.template.js +2 -1
  43. package/dist/template/implementations/queries.template.d.ts.map +1 -1
  44. package/dist/template/implementations/queries.template.js +3 -1
  45. package/dist/template/implementations/sd.template.js +3 -2
  46. package/dist/template/implementations/services.template.d.ts.map +1 -1
  47. package/dist/template/implementations/services.template.js +44 -7
  48. package/dist/template/zod-converter.d.ts.map +1 -1
  49. package/dist/template/zod-converter.js +2 -2
  50. package/dist/types/types.d.ts +14 -14
  51. package/dist/ui-web/assets/{index-DrTfl0Ts.js → index-C5KUjXm0.js} +46 -46
  52. package/dist/ui-web/index.html +1 -1
  53. package/dist/utils/fs-utils.d.ts.map +1 -1
  54. package/dist/utils/fs-utils.js +4 -4
  55. package/package.json +1 -2
  56. package/src/api/config.ts +0 -8
  57. package/src/api/sonamu.ts +1 -51
  58. package/src/auth/audit-log/builders.ts +791 -0
  59. package/src/auth/audit-log/events.ts +149 -0
  60. package/src/auth/audit-log/plugin.ts +913 -0
  61. package/src/auth/audit-log-ingestor.ts +3 -4
  62. package/src/auth/index.ts +2 -0
  63. package/src/auth/plugins/entity-definitions/admin.ts +3 -3
  64. package/src/auth/plugins/entity-definitions/audit-log.ts +2 -2
  65. package/src/bin/fixture.ts +143 -0
  66. package/src/database/_batch_update.ts +1 -1
  67. package/src/entity/entity-manager.ts +10 -3
  68. package/src/shared/app.shared.ts.txt +2 -3
  69. package/src/shared/web.shared.ts.txt +2 -2
  70. package/src/syncer/syncer.ts +1 -11
  71. package/src/template/implementations/entry-server.template.ts +1 -1
  72. package/src/template/implementations/generated.template.ts +1 -0
  73. package/src/template/implementations/generated_sso.template.ts +1 -0
  74. package/src/template/implementations/queries.template.ts +10 -1
  75. package/src/template/implementations/sd.template.ts +1 -1
  76. package/src/template/implementations/services.template.ts +62 -6
  77. package/src/template/zod-converter.ts +2 -1
  78. package/src/utils/fs-utils.ts +6 -4
  79. package/dist/auth/audit-log-proxy-types.d.ts +0 -23
  80. package/dist/auth/audit-log-proxy-types.d.ts.map +0 -1
  81. package/dist/auth/audit-log-proxy-types.js +0 -1
  82. package/src/auth/audit-log-proxy-types.ts +0 -23
@@ -0,0 +1,791 @@
1
+ import {
2
+ type AccountSnapshot,
3
+ type AuditLogEvent,
4
+ type Builder,
5
+ type BuilderLocation,
6
+ type BuilderTrigger,
7
+ EVENT_TYPES,
8
+ type InvitationSnapshot,
9
+ type MemberSnapshot,
10
+ ORGANIZATION_EVENT_TYPES,
11
+ type OrganizationSnapshot,
12
+ type SessionSnapshot,
13
+ type TeamSnapshot,
14
+ type UserProfileLite,
15
+ type UserSnapshot,
16
+ type VerificationSnapshot,
17
+ } from "./events";
18
+
19
+ // 모든 빌더 공통 필드(triggeredBy/triggerContext, ipAddress/city/country/countryCode)를 합성한다.
20
+ const createEvent = (
21
+ base: { eventKey: string; eventType: string; eventDisplayName?: string },
22
+ data: Record<string, unknown>,
23
+ trigger: BuilderTrigger,
24
+ location?: BuilderLocation,
25
+ ): AuditLogEvent => ({
26
+ ...base,
27
+ eventData: {
28
+ ...data,
29
+ triggeredBy: trigger.triggeredBy,
30
+ triggerContext: trigger.triggerContext,
31
+ },
32
+ ipAddress: location?.ipAddress,
33
+ city: location?.city,
34
+ country: location?.country,
35
+ countryCode: location?.countryCode,
36
+ });
37
+
38
+ // ============================================================================
39
+ // Account 빌더
40
+ // ============================================================================
41
+ export type AccountEventBuilders = {
42
+ trackAccountLinking: Builder<
43
+ [AccountSnapshot, UserProfileLite, BuilderTrigger, BuilderLocation | undefined]
44
+ >;
45
+ trackAccountUnlink: Builder<
46
+ [AccountSnapshot, UserProfileLite, BuilderTrigger, BuilderLocation | undefined]
47
+ >;
48
+ trackAccountPasswordChange: Builder<
49
+ [AccountSnapshot, UserProfileLite, BuilderTrigger, BuilderLocation | undefined]
50
+ >;
51
+ };
52
+
53
+ const buildAccountEvent = (
54
+ eventType: string,
55
+ eventDisplayName: string,
56
+ account: AccountSnapshot,
57
+ user: UserProfileLite,
58
+ trigger: BuilderTrigger,
59
+ location: BuilderLocation | undefined,
60
+ ): AuditLogEvent =>
61
+ createEvent(
62
+ { eventKey: account.userId, eventType, eventDisplayName },
63
+ {
64
+ userId: account.userId,
65
+ userEmail: user?.email ?? "unknown",
66
+ userName: user?.name ?? "unknown",
67
+ accountId: account.id,
68
+ providerId: account.providerId,
69
+ },
70
+ trigger,
71
+ location,
72
+ );
73
+
74
+ const buildAccountEvents = (): AccountEventBuilders => ({
75
+ trackAccountLinking: (account, user, trigger, location) =>
76
+ buildAccountEvent(
77
+ EVENT_TYPES.ACCOUNT_LINKED,
78
+ `Linked ${account.providerId} account`,
79
+ account,
80
+ user,
81
+ trigger,
82
+ location,
83
+ ),
84
+ trackAccountUnlink: (account, user, trigger, location) =>
85
+ buildAccountEvent(
86
+ EVENT_TYPES.ACCOUNT_UNLINKED,
87
+ `Unlinked ${account.providerId} account`,
88
+ account,
89
+ user,
90
+ trigger,
91
+ location,
92
+ ),
93
+ trackAccountPasswordChange: (account, user, trigger, location) =>
94
+ buildAccountEvent(
95
+ EVENT_TYPES.PASSWORD_CHANGED,
96
+ "Password changed",
97
+ account,
98
+ user,
99
+ trigger,
100
+ location,
101
+ ),
102
+ });
103
+
104
+ // ============================================================================
105
+ // Session 빌더
106
+ // ============================================================================
107
+ export type SessionEventBuilders = {
108
+ trackUserSignedIn: Builder<
109
+ [SessionSnapshot, UserProfileLite, BuilderTrigger, BuilderLocation | undefined]
110
+ >;
111
+ trackUserSignedOut: Builder<
112
+ [SessionSnapshot, UserProfileLite, BuilderTrigger, BuilderLocation | undefined]
113
+ >;
114
+ trackSessionCreated: Builder<
115
+ [SessionSnapshot, UserProfileLite, BuilderTrigger, BuilderLocation | undefined]
116
+ >;
117
+ trackSessionRevoked: Builder<
118
+ [SessionSnapshot, UserProfileLite, BuilderTrigger, BuilderLocation | undefined]
119
+ >;
120
+ trackSessionRevokedAll: Builder<[SessionSnapshot, UserProfileLite, BuilderTrigger]>;
121
+ trackUserImpersonated: Builder<
122
+ [SessionSnapshot, UserProfileLite, UserProfileLite, BuilderTrigger, BuilderLocation | undefined]
123
+ >;
124
+ trackUserImpersonationStop: Builder<
125
+ [SessionSnapshot, UserProfileLite, UserProfileLite, BuilderTrigger, BuilderLocation | undefined]
126
+ >;
127
+ trackEmailVerificationSent: Builder<
128
+ [SessionSnapshot, { name?: string; email?: string }, BuilderTrigger]
129
+ >;
130
+ trackEmailSignInAttempt: Builder<
131
+ [
132
+ { email: string; loginMethod: string | null },
133
+ UserProfileLite,
134
+ BuilderTrigger,
135
+ BuilderLocation | undefined,
136
+ ]
137
+ >;
138
+ trackSocialSignInAttempt: Builder<
139
+ [{ loginMethod: string | null }, UserProfileLite, BuilderTrigger, BuilderLocation | undefined]
140
+ >;
141
+ trackSocialSignInRedirectionAttempt: Builder<
142
+ [{ loginMethod: string | null }, UserProfileLite, BuilderTrigger, BuilderLocation | undefined]
143
+ >;
144
+ };
145
+
146
+ const sessionLifecycleData = (session: SessionSnapshot, user: UserProfileLite) => ({
147
+ userId: session.userId,
148
+ userName: user?.name ?? "unknown",
149
+ userEmail: user?.email ?? "unknown",
150
+ sessionId: session.id,
151
+ loginMethod: session.loginMethod ?? "unknown",
152
+ userAgent: session.userAgent,
153
+ });
154
+
155
+ const buildSessionLifecycleEvent = (
156
+ eventType: string,
157
+ eventDisplayName: string,
158
+ session: SessionSnapshot,
159
+ user: UserProfileLite,
160
+ trigger: BuilderTrigger,
161
+ location: BuilderLocation | undefined,
162
+ ): AuditLogEvent =>
163
+ createEvent(
164
+ { eventKey: session.userId, eventType, eventDisplayName },
165
+ sessionLifecycleData(session, user),
166
+ trigger,
167
+ location,
168
+ );
169
+
170
+ const buildImpersonationEvent = (
171
+ eventType: string,
172
+ eventDisplayName: string,
173
+ session: SessionSnapshot,
174
+ user: UserProfileLite,
175
+ impersonator: UserProfileLite,
176
+ trigger: BuilderTrigger,
177
+ location: BuilderLocation | undefined,
178
+ ): AuditLogEvent =>
179
+ createEvent(
180
+ { eventKey: session.userId, eventType, eventDisplayName },
181
+ {
182
+ ...sessionLifecycleData(session, user),
183
+ impersonatedBy: impersonator?.name ?? impersonator?.email ?? session.impersonatedBy,
184
+ impersonatedById: session.impersonatedBy,
185
+ },
186
+ trigger,
187
+ location,
188
+ );
189
+
190
+ const SIGN_IN_FAILED_DISPLAY = "User sign-in attempt failed";
191
+
192
+ const buildSessionEvents = (): SessionEventBuilders => ({
193
+ trackUserSignedIn: (session, user, trigger, location) =>
194
+ buildSessionLifecycleEvent(
195
+ EVENT_TYPES.USER_SIGNED_IN,
196
+ `Signed in via ${session.loginMethod ?? "unknown"}`,
197
+ session,
198
+ user,
199
+ trigger,
200
+ location,
201
+ ),
202
+ trackUserSignedOut: (session, user, trigger, location) =>
203
+ buildSessionLifecycleEvent(
204
+ EVENT_TYPES.USER_SIGNED_OUT,
205
+ "User signed out",
206
+ session,
207
+ user,
208
+ trigger,
209
+ location,
210
+ ),
211
+ trackSessionCreated: (session, user, trigger, location) =>
212
+ buildSessionLifecycleEvent(
213
+ EVENT_TYPES.SESSION_CREATED,
214
+ "Session created",
215
+ session,
216
+ user,
217
+ trigger,
218
+ location,
219
+ ),
220
+ trackSessionRevoked: (session, user, trigger, location) =>
221
+ buildSessionLifecycleEvent(
222
+ EVENT_TYPES.SESSION_REVOKED,
223
+ "Session revoked",
224
+ session,
225
+ user,
226
+ trigger,
227
+ location,
228
+ ),
229
+ trackSessionRevokedAll: (session, user, trigger) =>
230
+ createEvent(
231
+ {
232
+ eventKey: session.userId,
233
+ eventType: EVENT_TYPES.ALL_SESSIONS_REVOKED,
234
+ eventDisplayName: "All sessions revoked",
235
+ },
236
+ {
237
+ userId: session.userId,
238
+ userName: user?.name ?? "unknown",
239
+ userEmail: user?.email ?? "unknown",
240
+ },
241
+ trigger,
242
+ ),
243
+ trackUserImpersonated: (session, user, impersonator, trigger, location) =>
244
+ buildImpersonationEvent(
245
+ EVENT_TYPES.USER_IMPERSONATED,
246
+ "User impersonated",
247
+ session,
248
+ user,
249
+ impersonator,
250
+ trigger,
251
+ location,
252
+ ),
253
+ trackUserImpersonationStop: (session, user, impersonator, trigger, location) =>
254
+ buildImpersonationEvent(
255
+ EVENT_TYPES.USER_IMPERSONATED_STOPPED,
256
+ "User impersonation stopped",
257
+ session,
258
+ user,
259
+ impersonator,
260
+ trigger,
261
+ location,
262
+ ),
263
+ trackEmailVerificationSent: (session, user, trigger) =>
264
+ createEvent(
265
+ {
266
+ eventKey: session.userId,
267
+ eventType: EVENT_TYPES.EMAIL_VERIFICATION_SENT,
268
+ eventDisplayName: "Verification email sent",
269
+ },
270
+ {
271
+ userId: session.userId,
272
+ userName: user.name,
273
+ userEmail: user.email,
274
+ sessionId: session.id,
275
+ },
276
+ trigger,
277
+ ),
278
+ // 주의: 기존 동작 유지 — `nameName` 오타 필드명도 그대로 보존한다.
279
+ trackEmailSignInAttempt: (attempt, user, trigger, location) =>
280
+ createEvent(
281
+ {
282
+ eventKey: user?.id ?? "unknown",
283
+ eventType: EVENT_TYPES.USER_SIGN_IN_FAILED,
284
+ eventDisplayName: SIGN_IN_FAILED_DISPLAY,
285
+ },
286
+ {
287
+ userId: user?.id ?? "unknown",
288
+ nameName: user?.name ?? "unknown",
289
+ userEmail: attempt.email,
290
+ loginMethod: attempt.loginMethod,
291
+ },
292
+ { triggeredBy: user?.id ?? trigger.triggeredBy, triggerContext: trigger.triggerContext },
293
+ location,
294
+ ),
295
+ trackSocialSignInAttempt: (attempt, user, trigger, location) =>
296
+ createEvent(
297
+ {
298
+ eventKey: user?.id ?? "unknown",
299
+ eventType: EVENT_TYPES.USER_SIGN_IN_FAILED,
300
+ eventDisplayName: SIGN_IN_FAILED_DISPLAY,
301
+ },
302
+ {
303
+ userId: user?.id ?? "unknown",
304
+ userName: user?.name ?? "unknown",
305
+ userEmail: user?.email ?? "unknown",
306
+ loginMethod: attempt.loginMethod,
307
+ },
308
+ { triggeredBy: user?.id ?? trigger.triggeredBy, triggerContext: trigger.triggerContext },
309
+ location,
310
+ ),
311
+ // 주의: 기존 동작 유지 — userEmail 값이 user?.id로 설정되는 기존 동작도 그대로 보존한다.
312
+ trackSocialSignInRedirectionAttempt: (attempt, user, trigger, location) =>
313
+ createEvent(
314
+ {
315
+ eventKey: user?.id ?? "unknown",
316
+ eventType: EVENT_TYPES.USER_SIGN_IN_FAILED,
317
+ eventDisplayName: SIGN_IN_FAILED_DISPLAY,
318
+ },
319
+ {
320
+ userId: user?.id ?? "unknown",
321
+ userName: user?.name ?? "unknown",
322
+ userEmail: user?.id ?? "unknown",
323
+ loginMethod: attempt.loginMethod,
324
+ },
325
+ trigger,
326
+ location,
327
+ ),
328
+ });
329
+
330
+ // ============================================================================
331
+ // User 빌더
332
+ // ============================================================================
333
+ export type UserEventBuilders = {
334
+ trackUserSignedUp: Builder<[UserSnapshot, BuilderTrigger, BuilderLocation | undefined]>;
335
+ trackUserDeleted: Builder<[UserSnapshot, BuilderTrigger, BuilderLocation | undefined]>;
336
+ trackUserProfileUpdated: Builder<
337
+ [UserSnapshot, string[], BuilderTrigger, BuilderLocation | undefined]
338
+ >;
339
+ trackUserProfileImageUpdated: Builder<
340
+ [UserSnapshot, BuilderTrigger, BuilderLocation | undefined]
341
+ >;
342
+ trackUserBanned: Builder<[UserSnapshot, BuilderTrigger, BuilderLocation | undefined]>;
343
+ trackUserUnBanned: Builder<[UserSnapshot, BuilderTrigger, BuilderLocation | undefined]>;
344
+ trackUserEmailVerified: Builder<[UserSnapshot, BuilderTrigger, BuilderLocation | undefined]>;
345
+ };
346
+
347
+ const userIdentityData = (user: UserSnapshot) => ({
348
+ userId: user.id,
349
+ userEmail: user.email,
350
+ userName: user.name,
351
+ });
352
+
353
+ const buildUserEvent = (
354
+ eventType: string,
355
+ eventDisplayName: string,
356
+ user: UserSnapshot,
357
+ trigger: BuilderTrigger,
358
+ location: BuilderLocation | undefined,
359
+ extra?: Record<string, unknown>,
360
+ ): AuditLogEvent =>
361
+ createEvent(
362
+ { eventKey: user.id, eventType, eventDisplayName },
363
+ { ...userIdentityData(user), ...extra },
364
+ trigger,
365
+ location,
366
+ );
367
+
368
+ const buildUserEvents = (): UserEventBuilders => ({
369
+ trackUserSignedUp: (user, trigger, location) =>
370
+ buildUserEvent(
371
+ EVENT_TYPES.USER_CREATED,
372
+ `${user.name || user.email} signed up`,
373
+ user,
374
+ trigger,
375
+ location,
376
+ ),
377
+ trackUserDeleted: (user, trigger, location) =>
378
+ buildUserEvent(EVENT_TYPES.USER_DELETED, "User deleted", user, trigger, location),
379
+ trackUserProfileUpdated: (user, updatedFields, trigger, location) =>
380
+ buildUserEvent(EVENT_TYPES.PROFILE_UPDATED, "Profile updated", user, trigger, location, {
381
+ updatedFields,
382
+ }),
383
+ trackUserProfileImageUpdated: (user, trigger, location) =>
384
+ buildUserEvent(
385
+ EVENT_TYPES.PROFILE_IMAGE_UPDATED,
386
+ "Profile image updated",
387
+ user,
388
+ trigger,
389
+ location,
390
+ ),
391
+ trackUserBanned: (user, trigger, location) => {
392
+ const reasonSuffix = user.banReason ? `: ${user.banReason}` : "";
393
+ const expiresSuffix = user.banExpires ? ` (until ${user.banExpires.toISOString()})` : "";
394
+ return buildUserEvent(
395
+ EVENT_TYPES.USER_BANNED,
396
+ `User banned${reasonSuffix}${expiresSuffix}`,
397
+ user,
398
+ trigger,
399
+ location,
400
+ {
401
+ banned: user.banned,
402
+ banReason: user.banReason,
403
+ banExpires: user.banExpires,
404
+ },
405
+ );
406
+ },
407
+ trackUserUnBanned: (user, trigger, location) =>
408
+ buildUserEvent(EVENT_TYPES.USER_UNBANNED, "User unbanned", user, trigger, location, {
409
+ banned: user.banned,
410
+ }),
411
+ trackUserEmailVerified: (user, trigger, location) =>
412
+ buildUserEvent(EVENT_TYPES.EMAIL_VERIFIED, "Email verified", user, trigger, location),
413
+ });
414
+
415
+ // ============================================================================
416
+ // Verification 빌더
417
+ // ============================================================================
418
+ export type VerificationEventBuilders = {
419
+ trackPasswordResetRequest: Builder<
420
+ [VerificationSnapshot, UserProfileLite, BuilderTrigger, BuilderLocation | undefined]
421
+ >;
422
+ trackPasswordResetRequestCompletion: Builder<
423
+ [VerificationSnapshot, UserProfileLite, BuilderTrigger, BuilderLocation | undefined]
424
+ >;
425
+ };
426
+
427
+ const buildVerificationEvent = (
428
+ eventType: string,
429
+ eventDisplayName: string,
430
+ verification: VerificationSnapshot,
431
+ user: UserProfileLite,
432
+ trigger: BuilderTrigger,
433
+ location: BuilderLocation | undefined,
434
+ ): AuditLogEvent =>
435
+ createEvent(
436
+ { eventKey: verification.value, eventType, eventDisplayName },
437
+ {
438
+ userId: verification.value,
439
+ userName: user?.name ?? "unknown",
440
+ userEmail: user?.email ?? "unknown",
441
+ },
442
+ trigger,
443
+ location,
444
+ );
445
+
446
+ const buildVerificationEvents = (): VerificationEventBuilders => ({
447
+ trackPasswordResetRequest: (verification, user, trigger, location) =>
448
+ buildVerificationEvent(
449
+ EVENT_TYPES.PASSWORD_RESET_REQUESTED,
450
+ "Password reset requested",
451
+ verification,
452
+ user,
453
+ trigger,
454
+ location,
455
+ ),
456
+ trackPasswordResetRequestCompletion: (verification, user, trigger, location) =>
457
+ buildVerificationEvent(
458
+ EVENT_TYPES.PASSWORD_RESET_COMPLETED,
459
+ "Password reset completed",
460
+ verification,
461
+ user,
462
+ trigger,
463
+ location,
464
+ ),
465
+ });
466
+
467
+ // ============================================================================
468
+ // Organization / Team / Member / Invitation 빌더
469
+ // ============================================================================
470
+ const organizationIdentityData = (organization: OrganizationSnapshot) => ({
471
+ organizationId: organization.id,
472
+ organizationSlug: organization.slug,
473
+ organizationName: organization.name,
474
+ });
475
+
476
+ export type OrganizationEventBuilders = {
477
+ trackOrganizationCreated: Builder<[OrganizationSnapshot, BuilderTrigger]>;
478
+ trackOrganizationUpdated: Builder<[OrganizationSnapshot, BuilderTrigger]>;
479
+ };
480
+
481
+ const buildOrganizationEvent = (
482
+ eventType: string,
483
+ eventDisplayName: string,
484
+ organization: OrganizationSnapshot,
485
+ trigger: BuilderTrigger,
486
+ ): AuditLogEvent =>
487
+ createEvent(
488
+ { eventKey: organization.id, eventType, eventDisplayName },
489
+ organizationIdentityData(organization),
490
+ trigger,
491
+ );
492
+
493
+ const buildOrganizationEvents = (): OrganizationEventBuilders => ({
494
+ trackOrganizationCreated: (organization, trigger) =>
495
+ buildOrganizationEvent(
496
+ ORGANIZATION_EVENT_TYPES.ORGANIZATION_CREATED,
497
+ "Organization Created",
498
+ organization,
499
+ trigger,
500
+ ),
501
+ trackOrganizationUpdated: (organization, trigger) =>
502
+ buildOrganizationEvent(
503
+ ORGANIZATION_EVENT_TYPES.ORGANIZATION_UPDATED,
504
+ "Organization Updated",
505
+ organization,
506
+ trigger,
507
+ ),
508
+ });
509
+
510
+ export type TeamEventBuilders = {
511
+ trackOrganizationTeamCreated: Builder<[OrganizationSnapshot, TeamSnapshot, BuilderTrigger]>;
512
+ trackOrganizationTeamUpdated: Builder<[OrganizationSnapshot, TeamSnapshot, BuilderTrigger]>;
513
+ trackOrganizationTeamDeleted: Builder<[OrganizationSnapshot, TeamSnapshot, BuilderTrigger]>;
514
+ trackOrganizationTeamMemberAdded: Builder<
515
+ [
516
+ OrganizationSnapshot,
517
+ TeamSnapshot,
518
+ UserSnapshot,
519
+ { teamId: string; userId: string },
520
+ BuilderTrigger,
521
+ ]
522
+ >;
523
+ trackOrganizationTeamMemberRemoved: Builder<
524
+ [
525
+ OrganizationSnapshot,
526
+ TeamSnapshot,
527
+ UserSnapshot,
528
+ { teamId: string; userId: string },
529
+ BuilderTrigger,
530
+ ]
531
+ >;
532
+ };
533
+
534
+ const buildTeamLifecycleEvent = (
535
+ eventType: string,
536
+ eventDisplayName: string,
537
+ organization: OrganizationSnapshot,
538
+ team: TeamSnapshot,
539
+ trigger: BuilderTrigger,
540
+ ): AuditLogEvent =>
541
+ createEvent(
542
+ { eventKey: organization.id, eventType, eventDisplayName },
543
+ { ...organizationIdentityData(organization), teamId: team.id, teamName: team.name },
544
+ trigger,
545
+ );
546
+
547
+ const buildTeamMemberEvent = (
548
+ eventType: string,
549
+ eventDisplayName: string,
550
+ organization: OrganizationSnapshot,
551
+ team: TeamSnapshot,
552
+ user: UserSnapshot,
553
+ teamMember: { teamId: string; userId: string },
554
+ trigger: BuilderTrigger,
555
+ ): AuditLogEvent =>
556
+ createEvent(
557
+ { eventKey: organization.id, eventType, eventDisplayName },
558
+ {
559
+ ...organizationIdentityData(organization),
560
+ teamId: teamMember.teamId,
561
+ teamName: team.name,
562
+ userid: teamMember.userId,
563
+ memberName: user.name,
564
+ },
565
+ trigger,
566
+ );
567
+
568
+ const buildTeamEvents = (): TeamEventBuilders => ({
569
+ trackOrganizationTeamCreated: (organization, team, trigger) =>
570
+ buildTeamLifecycleEvent(
571
+ ORGANIZATION_EVENT_TYPES.ORGANIZATION_TEAM_CREATED,
572
+ "Organization team created",
573
+ organization,
574
+ team,
575
+ trigger,
576
+ ),
577
+ trackOrganizationTeamUpdated: (organization, team, trigger) =>
578
+ buildTeamLifecycleEvent(
579
+ ORGANIZATION_EVENT_TYPES.ORGANIZATION_TEAM_UPDATED,
580
+ "Organization team updated",
581
+ organization,
582
+ team,
583
+ trigger,
584
+ ),
585
+ trackOrganizationTeamDeleted: (organization, team, trigger) =>
586
+ buildTeamLifecycleEvent(
587
+ ORGANIZATION_EVENT_TYPES.ORGANIZATION_TEAM_DELETED,
588
+ "Organization team deleted",
589
+ organization,
590
+ team,
591
+ trigger,
592
+ ),
593
+ trackOrganizationTeamMemberAdded: (organization, team, user, teamMember, trigger) =>
594
+ buildTeamMemberEvent(
595
+ ORGANIZATION_EVENT_TYPES.ORGANIZATION_TEAM_MEMBER_ADDED,
596
+ "User added to organization team",
597
+ organization,
598
+ team,
599
+ user,
600
+ teamMember,
601
+ trigger,
602
+ ),
603
+ trackOrganizationTeamMemberRemoved: (organization, team, user, teamMember, trigger) =>
604
+ buildTeamMemberEvent(
605
+ ORGANIZATION_EVENT_TYPES.ORGANIZATION_TEAM_MEMBER_REMOVED,
606
+ "User removed from organization team",
607
+ organization,
608
+ team,
609
+ user,
610
+ teamMember,
611
+ trigger,
612
+ ),
613
+ });
614
+
615
+ export type MemberEventBuilders = {
616
+ trackOrganizationMemberAdded: Builder<
617
+ [OrganizationSnapshot, MemberSnapshot, UserSnapshot, BuilderTrigger]
618
+ >;
619
+ trackOrganizationMemberRemoved: Builder<
620
+ [OrganizationSnapshot, MemberSnapshot, UserSnapshot, BuilderTrigger]
621
+ >;
622
+ trackOrganizationMemberRoleUpdated: Builder<
623
+ [OrganizationSnapshot, MemberSnapshot, UserSnapshot, string, BuilderTrigger]
624
+ >;
625
+ };
626
+
627
+ const memberCoreData = (member: MemberSnapshot, user: UserSnapshot) => ({
628
+ userId: member.userId,
629
+ memberName: user.name,
630
+ role: member.role,
631
+ memberId: member.id,
632
+ memberEmail: user.email,
633
+ });
634
+
635
+ const buildMemberEvents = (): MemberEventBuilders => ({
636
+ trackOrganizationMemberAdded: (organization, member, user, trigger) =>
637
+ createEvent(
638
+ {
639
+ eventKey: organization.id,
640
+ eventType: ORGANIZATION_EVENT_TYPES.ORGANIZATION_MEMBER_ADDED,
641
+ eventDisplayName: "Member added to organization",
642
+ },
643
+ { ...organizationIdentityData(organization), ...memberCoreData(member, user) },
644
+ trigger,
645
+ ),
646
+ trackOrganizationMemberRemoved: (organization, member, user, trigger) =>
647
+ createEvent(
648
+ {
649
+ eventKey: organization.id,
650
+ eventType: ORGANIZATION_EVENT_TYPES.ORGANIZATION_MEMBER_REMOVED,
651
+ eventDisplayName: "Member removed from organization",
652
+ },
653
+ { ...organizationIdentityData(organization), ...memberCoreData(member, user) },
654
+ trigger,
655
+ ),
656
+ trackOrganizationMemberRoleUpdated: (organization, member, user, previousRole, trigger) =>
657
+ createEvent(
658
+ {
659
+ eventKey: organization.id,
660
+ eventType: ORGANIZATION_EVENT_TYPES.ORGANIZATION_MEMBER_ROLE_UPDATED,
661
+ eventDisplayName: "Organization member role updated",
662
+ },
663
+ {
664
+ ...organizationIdentityData(organization),
665
+ userId: member.userId,
666
+ memberName: user.name,
667
+ newRole: member.role,
668
+ oldRole: previousRole,
669
+ memberId: member.id,
670
+ memberEmail: user.email,
671
+ },
672
+ trigger,
673
+ ),
674
+ });
675
+
676
+ export type InvitationEventBuilders = {
677
+ trackOrganizationMemberInvited: Builder<
678
+ [OrganizationSnapshot, InvitationSnapshot, UserSnapshot, BuilderTrigger]
679
+ >;
680
+ trackOrganizationMemberInviteAccepted: Builder<
681
+ [OrganizationSnapshot, InvitationSnapshot, MemberSnapshot, UserSnapshot, BuilderTrigger]
682
+ >;
683
+ trackOrganizationMemberInviteRejected: Builder<
684
+ [OrganizationSnapshot, InvitationSnapshot, UserSnapshot, BuilderTrigger]
685
+ >;
686
+ trackOrganizationMemberInviteCanceled: Builder<
687
+ [OrganizationSnapshot, InvitationSnapshot, UserSnapshot, BuilderTrigger]
688
+ >;
689
+ };
690
+
691
+ const inviteeData = (invitation: InvitationSnapshot) => ({
692
+ inviteeId: invitation.id,
693
+ inviteeEmail: invitation.email,
694
+ inviteeRole: invitation.role,
695
+ inviteeTeamId: invitation.teamId,
696
+ });
697
+
698
+ const buildInvitationEvents = (): InvitationEventBuilders => ({
699
+ trackOrganizationMemberInvited: (organization, invitation, inviter, trigger) =>
700
+ createEvent(
701
+ {
702
+ eventKey: organization.id,
703
+ eventType: ORGANIZATION_EVENT_TYPES.ORGANIZATION_MEMBER_INVITED,
704
+ eventDisplayName: "User invited to organization",
705
+ },
706
+ {
707
+ ...organizationIdentityData(organization),
708
+ ...inviteeData(invitation),
709
+ inviterId: inviter.id,
710
+ inviterName: inviter.name,
711
+ inviterEmail: inviter.email,
712
+ },
713
+ trigger,
714
+ ),
715
+ trackOrganizationMemberInviteAccepted: (organization, invitation, member, acceptedBy, trigger) =>
716
+ createEvent(
717
+ {
718
+ eventKey: organization.id,
719
+ eventType: ORGANIZATION_EVENT_TYPES.ORGANIZATION_MEMBER_INVITE_ACCEPTED,
720
+ eventDisplayName: "User accepted invite organization invite",
721
+ },
722
+ {
723
+ ...organizationIdentityData(organization),
724
+ ...inviteeData(invitation),
725
+ acceptedById: acceptedBy.id,
726
+ acceptedByEmail: acceptedBy.email,
727
+ acceptedByName: acceptedBy.name,
728
+ memberId: member.id,
729
+ memberRole: member.role,
730
+ },
731
+ trigger,
732
+ ),
733
+ trackOrganizationMemberInviteRejected: (organization, invitation, rejectedBy, trigger) =>
734
+ createEvent(
735
+ {
736
+ eventKey: organization.id,
737
+ eventType: ORGANIZATION_EVENT_TYPES.ORGANIZATION_MEMBER_INVITE_REJECTED,
738
+ eventDisplayName: "User rejected organization invite",
739
+ },
740
+ {
741
+ ...organizationIdentityData(organization),
742
+ ...inviteeData(invitation),
743
+ rejectedById: rejectedBy.id,
744
+ rejectedByEmail: rejectedBy.email,
745
+ rejectedByName: rejectedBy.name,
746
+ },
747
+ trigger,
748
+ ),
749
+ trackOrganizationMemberInviteCanceled: (organization, invitation, cancelledBy, trigger) =>
750
+ createEvent(
751
+ {
752
+ eventKey: organization.id,
753
+ eventType: ORGANIZATION_EVENT_TYPES.ORGANIZATION_MEMBER_INVITE_CANCELED,
754
+ eventDisplayName: "Organization invite cancelled",
755
+ },
756
+ {
757
+ ...organizationIdentityData(organization),
758
+ ...inviteeData(invitation),
759
+ cancelledById: cancelledBy.id,
760
+ cancelledByName: cancelledBy.name,
761
+ cancelledByEmail: cancelledBy.email,
762
+ },
763
+ trigger,
764
+ ),
765
+ });
766
+
767
+ // TODO(security): sonamu.config의 security 옵션이 도입되면
768
+ // SecurityEventBuilders (trackSecurityBlocked/Allowed/Challenged/StaleAccount) 섹션을 추가한다.
769
+ // dash의 createSecurityClient/onSecurityEvent 구현은 현재 scope out (R1).
770
+
771
+ export type AuditEventBuilderCatalog = {
772
+ account: AccountEventBuilders;
773
+ session: SessionEventBuilders;
774
+ user: UserEventBuilders;
775
+ verification: VerificationEventBuilders;
776
+ organization: OrganizationEventBuilders;
777
+ team: TeamEventBuilders;
778
+ member: MemberEventBuilders;
779
+ invitation: InvitationEventBuilders;
780
+ };
781
+
782
+ export const buildAuditEventCatalog = (): AuditEventBuilderCatalog => ({
783
+ account: buildAccountEvents(),
784
+ session: buildSessionEvents(),
785
+ user: buildUserEvents(),
786
+ verification: buildVerificationEvents(),
787
+ organization: buildOrganizationEvents(),
788
+ team: buildTeamEvents(),
789
+ member: buildMemberEvents(),
790
+ invitation: buildInvitationEvents(),
791
+ });