From 005b64adf15740421dd87071fc2f80644e2544a1 Mon Sep 17 00:00:00 2001 From: TuvalSimha Date: Mon, 26 Aug 2024 17:20:59 +0300 Subject: [PATCH] Draft: Audit log GraphQL API codegen setup fix some some some some more ? ?? SOME? ?? ? fix some? SOME ??? more some some test fix some code review! ? fix some clean! fix codegen path ??? ? ?? ? ? added scope ti auditlogmanager scheme publish & check & delete members role CLEAN MORE target project prettier fix resolver schema published lint --- codegen.mts | 25 ++ packages/libraries/core/src/client/agent.ts | 1 - packages/services/api/src/create.ts | 2 + .../api/src/modules/audit-logs/index.ts | 12 + .../audit-logs/module.graphql.mappers.ts | 3 + .../src/modules/audit-logs/module.graphql.ts | 263 ++++++++++++++++++ .../providers/audit-logs-manager.ts | 86 ++++++ .../audit-logs/providers/audit-logs-types.ts | 195 +++++++++++++ .../Mutation/exportAuditLogsToFile.ts | 15 + .../OrganizationSettingsUpdatedAuditLog.ts | 23 ++ .../OrganizationTransferredAuditLog.ts | 29 ++ .../resolvers/ProjectCreatedAuditLog.ts | 29 ++ .../resolvers/ProjectDeletedAuditLog.ts | 29 ++ .../ProjectSettingsUpdatedAuditLog.ts | 29 ++ .../resolvers/Query/auditLogExports.ts | 31 +++ .../audit-logs/resolvers/Query/auditLogs.ts | 22 ++ .../resolvers/RoleAssignedAuditLog.ts | 41 +++ .../resolvers/RoleCreatedAuditLog.ts | 29 ++ .../resolvers/RoleDeletedAuditLog.ts | 29 ++ .../resolvers/RoleUpdatedAuditLog.ts | 35 +++ .../resolvers/SchemaCheckedAuditLog.ts | 35 +++ .../resolvers/SchemaDeletedAuditLog.ts | 35 +++ .../SchemaPolicySettingsUpdatedAuditLog.ts | 29 ++ .../resolvers/SchemaPublishAuditLog.ts | 35 +++ .../resolvers/TargetCreatedAuditLog.ts | 35 +++ .../resolvers/TargetDeletedAuditLog.ts | 35 +++ .../TargetSettingsUpdatedAuditLog.ts | 35 +++ .../resolvers/UserInvitedAuditLog.ts | 29 ++ .../resolvers/UserJoinedAuditLog.ts | 29 ++ .../resolvers/UserRemovedAuditLog.ts | 28 ++ .../api/src/modules/organization/resolvers.ts | 123 +++++++- .../resolvers/Mutation/createProject.ts | 18 ++ .../resolvers/Mutation/deleteProject.ts | 19 ++ .../resolvers/Mutation/updateProjectName.ts | 21 ++ .../api/src/modules/schema/resolvers.ts | 127 ++++++++- .../target/resolvers/Mutation/createTarget.ts | 20 ++ .../target/resolvers/Mutation/deleteTarget.ts | 20 ++ ...rimental__updateTargetSchemaComposition.ts | 27 +- .../resolvers/Mutation/setTargetValidation.ts | 28 +- .../updateTargetGraphQLEndpointUrl.ts | 23 ++ .../resolvers/Mutation/updateTargetName.ts | 23 ++ .../updateTargetValidationSettings.ts | 26 ++ 42 files changed, 1715 insertions(+), 13 deletions(-) create mode 100644 packages/services/api/src/modules/audit-logs/index.ts create mode 100644 packages/services/api/src/modules/audit-logs/module.graphql.mappers.ts create mode 100644 packages/services/api/src/modules/audit-logs/module.graphql.ts create mode 100644 packages/services/api/src/modules/audit-logs/providers/audit-logs-manager.ts create mode 100644 packages/services/api/src/modules/audit-logs/providers/audit-logs-types.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/Mutation/exportAuditLogsToFile.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/OrganizationSettingsUpdatedAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/OrganizationTransferredAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/ProjectCreatedAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/ProjectDeletedAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/ProjectSettingsUpdatedAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/Query/auditLogExports.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/Query/auditLogs.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/RoleAssignedAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/RoleCreatedAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/RoleDeletedAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/RoleUpdatedAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/SchemaCheckedAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/SchemaDeletedAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/SchemaPolicySettingsUpdatedAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/SchemaPublishAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/TargetCreatedAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/TargetDeletedAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/TargetSettingsUpdatedAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/UserInvitedAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/UserJoinedAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/UserRemovedAuditLog.ts diff --git a/codegen.mts b/codegen.mts index ea367df5503..87c67ef77f0 100644 --- a/codegen.mts +++ b/codegen.mts @@ -80,6 +80,31 @@ const config: CodegenConfig = { ID: 'string', }, mappers: { + AuditLog: '../modules/audit-logs/module.graphql.mappers#AuditLogMapper', + UserInvitedAuditLog: '../modules/audit-logs/module.graphql.mappers#AuditLogMapper', + UserJoinedAuditLog: '../modules/audit-logs/module.graphql.mappers#AuditLogMapper', + UserRemovedAuditLog: '../modules/audit-logs/module.graphql.mappers#AuditLogMapper', + OrganizationSettingsUpdatedAuditLog: + '../modules/audit-logs/module.graphql.mappers#AuditLogMapper', + OrganizationTransferredAuditLog: + '../modules/audit-logs/module.graphql.mappers#AuditLogMapper', + ProjectCreatedAuditLog: '../modules/audit-logs/module.graphql.mappers#AuditLogMapper', + ProjectSettingsUpdatedAuditLog: + '../modules/audit-logs/module.graphql.mappers#AuditLogMapper', + ProjectDeletedAuditLog: '../modules/audit-logs/module.graphql.mappers#AuditLogMapper', + TargetCreatedAuditLog: '../modules/audit-logs/module.graphql.mappers#AuditLogMapper', + TargetSettingsUpdatedAuditLog: + '../modules/audit-logs/module.graphql.mappers#AuditLogMapper', + TargetDeletedAuditLog: '../modules/audit-logs/module.graphql.mappers#AuditLogMapper', + SchemaPolicySettingsUpdatedAuditLog: + '../modules/audit-logs/module.graphql.mappers#AuditLogMapper', + SchemaCheckedAuditLog: '../modules/audit-logs/module.graphql.mappers#AuditLogMapper', + SchemaPublishAuditLog: '../modules/audit-logs/module.graphql.mappers#AuditLogMapper', + SchemaDeletedAuditLog: '../modules/audit-logs/module.graphql.mappers#AuditLogMapper', + RoleCreatedAuditLog: '../modules/audit-logs/module.graphql.mappers#AuditLogMapper', + RoleAssignedAuditLog: '../modules/audit-logs/module.graphql.mappers#AuditLogMapper', + RoleDeletedAuditLog: '../modules/audit-logs/module.graphql.mappers#AuditLogMapper', + RoleUpdatedAuditLog: '../modules/audit-logs/module.graphql.mappers#AuditLogMapper', SchemaChange: '../modules/schema/module.graphql.mappers#SchemaChangeMapper', SchemaChangeApproval: '../modules/schema/module.graphql.mappers#SchemaChangeApprovalMapper', diff --git a/packages/libraries/core/src/client/agent.ts b/packages/libraries/core/src/client/agent.ts index 385da554e84..d4a3873aabe 100644 --- a/packages/libraries/core/src/client/agent.ts +++ b/packages/libraries/core/src/client/agent.ts @@ -110,7 +110,6 @@ export function createAgent( const promise = captureAsync(event); inProgressCaptures.push(promise); void promise.finally(() => { - // eslint-disable-next-line @typescript-eslint/no-floating-promises inProgressCaptures = inProgressCaptures.filter(p => p !== promise); }); } else { diff --git a/packages/services/api/src/create.ts b/packages/services/api/src/create.ts index 5a04048af43..5ac62894d39 100644 --- a/packages/services/api/src/create.ts +++ b/packages/services/api/src/create.ts @@ -4,6 +4,7 @@ import { adminModule } from './modules/admin'; import { alertsModule } from './modules/alerts'; import { WEBHOOKS_CONFIG, WebhooksConfig } from './modules/alerts/providers/tokens'; import { appDeploymentsModule } from './modules/app-deployments'; +import { auditLogsModule } from './modules/audit-logs'; import { authModule } from './modules/auth'; import { billingModule } from './modules/billing'; import { BILLING_CONFIG, BillingConfig } from './modules/billing/providers/tokens'; @@ -81,6 +82,7 @@ const modules = [ integrationsModule, alertsModule, feedbackModule, + auditLogsModule, cdnModule, adminModule, usageEstimationModule, diff --git a/packages/services/api/src/modules/audit-logs/index.ts b/packages/services/api/src/modules/audit-logs/index.ts new file mode 100644 index 00000000000..7819b533833 --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/index.ts @@ -0,0 +1,12 @@ +import { createModule } from 'graphql-modules'; +import { AuditLogManager } from './providers/audit-logs-manager'; +import { resolvers } from './resolvers.generated'; +import { typeDefs } from './module.graphql'; + +export const auditLogsModule = createModule({ + id: 'audit-logs', + dirname: __dirname, + typeDefs, + resolvers, + providers: [AuditLogManager], +}); diff --git a/packages/services/api/src/modules/audit-logs/module.graphql.mappers.ts b/packages/services/api/src/modules/audit-logs/module.graphql.mappers.ts new file mode 100644 index 00000000000..972b66157ac --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/module.graphql.mappers.ts @@ -0,0 +1,3 @@ +import { AuditLogEvent } from './providers/audit-logs-types'; + +export type AuditLogMapper = AuditLogEvent; diff --git a/packages/services/api/src/modules/audit-logs/module.graphql.ts b/packages/services/api/src/modules/audit-logs/module.graphql.ts new file mode 100644 index 00000000000..48e7ca64d1c --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/module.graphql.ts @@ -0,0 +1,263 @@ +import { gql } from 'graphql-modules'; + +export const typeDefs = gql` + interface AuditLog { + id: ID! + eventTime: Date! + user: AuditLogUserRecord! + organizationId: String! + eventType: String! + } + + type AuditLogUserRecord { + userId: ID! + userEmail: String! + user: User # this one is nullable because it can be deleted! + } + + type AuditLogConnection { + nodes: [AuditLog]! + total: Int! + } + + type AuditLogFileExport { + id: ID! + url: String! + validUntil: Date! + createdAt: Date! + } + + type ExportResultEdge { + node: AuditLogFileExport! + cursor: String! + } + + type ExportResultConnection { + edges: [ExportResultEdge!]! + pageInfo: PageInfo! + } + + input AuditLogFilter { + startDate: Date + endDate: Date + userId: String + userEmail: String + projectId: String + targetId: String + eventType: String # Filter by __typename if needed + } + + extend type Query { + auditLogs( + filter: AuditLogFilter + first: String = "100" + after: String = null + ): AuditLogConnection! + auditLogExports(first: String = 100, after: String = null): ExportResultConnection! + } + + extend type Mutation { + exportAuditLogsToFile(filter: AuditLogFilter!): AuditLogFileExport! + } + + type ModifyAuditLogError implements Error { + message: String! + } + + type UserInvitedAuditLog implements AuditLog { + id: ID! + eventTime: Date! + user: AuditLogUserRecord! + organizationId: String! + eventType: String! + inviteeId: String! + inviteeEmail: String! + } + + type UserJoinedAuditLog implements AuditLog { + id: ID! + eventTime: Date! + user: AuditLogUserRecord! + organizationId: String! + eventType: String! + inviteeId: String! + inviteeEmail: String! + } + type UserRemovedAuditLog implements AuditLog { + id: ID! + eventTime: Date! + user: AuditLogUserRecord! + organizationId: String! + eventType: String! + removedUserId: String! + removedUserEmail: String! + } + + type OrganizationSettingsUpdatedAuditLog implements AuditLog { + id: ID! + eventTime: Date! + user: AuditLogUserRecord! + organizationId: String! + eventType: String! + updatedFields: JSON! + } + + type OrganizationTransferredAuditLog implements AuditLog { + id: ID! + eventTime: Date! + user: AuditLogUserRecord! + organizationId: String! + eventType: String! + newOwnerId: String! + newOwnerEmail: String! + } + + type ProjectCreatedAuditLog implements AuditLog { + id: ID! + eventTime: Date! + user: AuditLogUserRecord! + organizationId: String! + eventType: String! + projectId: String! + projectName: String! + } + + type ProjectSettingsUpdatedAuditLog implements AuditLog { + id: ID! + eventTime: Date! + user: AuditLogUserRecord! + organizationId: String! + eventType: String! + projectId: String! + updatedFields: JSON! + } + + type ProjectDeletedAuditLog implements AuditLog { + id: ID! + eventTime: Date! + user: AuditLogUserRecord! + organizationId: String! + eventType: String! + projectId: String! + projectName: String! + } + + type TargetCreatedAuditLog implements AuditLog { + id: ID! + eventTime: Date! + user: AuditLogUserRecord! + organizationId: String! + eventType: String! + projectId: String! + targetId: String! + targetName: String! + } + + type TargetSettingsUpdatedAuditLog implements AuditLog { + id: ID! + eventTime: Date! + user: AuditLogUserRecord! + organizationId: String! + eventType: String! + projectId: String! + targetId: String! + updatedFields: JSON! + } + + type TargetDeletedAuditLog implements AuditLog { + id: ID! + eventTime: Date! + user: AuditLogUserRecord! + organizationId: String! + eventType: String! + projectId: String! + targetId: String! + targetName: String! + } + + type SchemaPolicySettingsUpdatedAuditLog implements AuditLog { + id: ID! + eventTime: Date! + user: AuditLogUserRecord! + organizationId: String! + eventType: String! + projectId: String! + updatedFields: JSON! + } + + type SchemaCheckedAuditLog implements AuditLog { + id: ID! + eventTime: Date! + user: AuditLogUserRecord! + organizationId: String! + eventType: String! + projectId: String! + targetId: String! + schemaSdl: String! + } + + type SchemaPublishAuditLog implements AuditLog { + id: ID! + eventTime: Date! + user: AuditLogUserRecord! + organizationId: String! + eventType: String! + projectId: String! + targetId: String! + schemaName: String! + } + + type SchemaDeletedAuditLog implements AuditLog { + id: ID! + eventTime: Date! + user: AuditLogUserRecord! + organizationId: String! + eventType: String! + projectId: String! + targetId: String! + schemaName: String! + } + + type RoleCreatedAuditLog implements AuditLog { + id: ID! + eventTime: Date! + user: AuditLogUserRecord! + organizationId: String! + eventType: String! + roleId: String! + roleName: String! + } + + type RoleAssignedAuditLog implements AuditLog { + id: ID! + eventTime: Date! + user: AuditLogUserRecord! + organizationId: String! + eventType: String! + roleId: String! + roleName: String! + userIdAssigned: String! + userEmailAssigned: String! + } + + type RoleDeletedAuditLog implements AuditLog { + id: ID! + eventTime: Date! + user: AuditLogUserRecord! + organizationId: String! + eventType: String! + roleId: String! + roleName: String! + } + + type RoleUpdatedAuditLog implements AuditLog { + id: ID! + eventTime: Date! + user: AuditLogUserRecord! + organizationId: String! + eventType: String! + roleId: String! + roleName: String! + updatedFields: JSON! + } +`; diff --git a/packages/services/api/src/modules/audit-logs/providers/audit-logs-manager.ts b/packages/services/api/src/modules/audit-logs/providers/audit-logs-manager.ts new file mode 100644 index 00000000000..385e3bbefea --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/providers/audit-logs-manager.ts @@ -0,0 +1,86 @@ +import { Injectable, Scope } from 'graphql-modules'; +import { ClickHouse, sql } from '../../operations/providers/clickhouse-client'; +import { Logger } from '../../shared/providers/logger'; +import { AuditLogEvent, auditLogSchema } from './audit-logs-types'; + +// TODO: Error: No provider for ClickHouse! (AuditLogManager -> ClickHouse) - in Module "audit-logs" (Operation Scope) +@Injectable({ + scope: Scope.Operation, + global: true, +}) +export class AuditLogManager { + private logger: Logger; + + constructor( + logger: Logger, + private clickHouse: ClickHouse, + ) { + this.logger = logger.child({ source: 'AuditLogsManager' }); + } + + async createLogAuditEvent(event: AuditLogEvent): Promise { + const { eventTime, eventType, id, organizationId, user } = event; + this.logger.info('Creating a log audit event (event=%o)', event); + const parsedEvent = auditLogSchema.parse(event); + + const query = sql` + INSERT INTO audit_log (id, event_time, user_id, user_email, organization_id, event_action, metadata) + FORMAT CSV + `; + + const values = [ + id, + eventTime, + user.userId, + user.userEmail, + organizationId, + eventType, + JSON.stringify(parsedEvent), + ]; + + const result = await this.clickHouse.insert({ + data: [values], + query, + queryId: 'audit-log-create', + timeout: 5000, + }); + return result; + } + + async getPaginatedAuditLogs(limit: string, offset: string): Promise { + this.logger.info('Getting paginated audit logs (limit=%s, offset=%s)', limit, offset); + + const query = sql` + SELECT * + FROM audit_log + ORDER BY event_time DESC + LIMIT ${limit} + OFFSET ${offset} + `; + + const result = await this.clickHouse.query({ + query, + queryId: 'get-audit-logs', + timeout: 5000, + }); + if (!result || !result.data || result.data.length === 0) { + throw new Error('Audit logs not found'); + } + return result.data as AuditLogEvent[]; + } + + async getAuditLogsCount(): Promise { + this.logger.info('Getting audit logs count'); + const query = sql` + SELECT COUNT(*) + FROM audit_log + `; + + const result = await this.clickHouse.query({ + query, + queryId: 'get-audit-logs-count', + timeout: 5000, + }); + return result.data.length; + } +} diff --git a/packages/services/api/src/modules/audit-logs/providers/audit-logs-types.ts b/packages/services/api/src/modules/audit-logs/providers/audit-logs-types.ts new file mode 100644 index 00000000000..2eb42a5962d --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/providers/audit-logs-types.ts @@ -0,0 +1,195 @@ +import { z } from 'zod'; +import { User } from '../../../__generated__/types'; + +const UserInvitedAuditLogSchema = z.object({ + inviteeId: z.string(), + inviteeEmail: z.string(), +}); +const UserJoinedAuditLogSchema = z.object({ + inviteeId: z.string(), + inviteeEmail: z.string(), +}); + +const UserRemovedAuditLogSchema = z.object({ + removedUserId: z.string(), + removedUserEmail: z.string(), +}); + +const OrganizationSettingsUpdatedAuditLogSchema = z.object({ + updatedFields: z.string(), +}); + +const OrganizationTransferredAuditLogSchema = z.object({ + newOwnerId: z.string(), + newOwnerEmail: z.string(), +}); + +const ProjectCreatedAuditLogSchema = z.object({ + projectId: z.string(), + projectName: z.string(), +}); + +const ProjectSettingsUpdatedAuditLogSchema = z.object({ + projectId: z.string(), + updatedFields: z.string(), +}); + +const ProjectDeletedAuditLogSchema = z.object({ + projectId: z.string(), + projectName: z.string(), +}); + +const TargetCreatedAuditLogSchema = z.object({ + projectId: z.string(), + targetId: z.string(), + targetName: z.string(), +}); + +const TargetSettingsUpdatedAuditLogSchema = z.object({ + projectId: z.string(), + targetId: z.string(), + updatedFields: z.string(), +}); + +const TargetDeletedAuditLogSchema = z.object({ + projectId: z.string(), + targetId: z.string(), + targetName: z.string(), +}); + +const SchemaPolicySettingsUpdatedAuditLogSchema = z.object({ + projectId: z.string(), + updatedFields: z.string(), +}); + +const SchemaCheckedAuditLogSchema = z.object({ + projectId: z.string(), + targetId: z.string(), + schemaSdl: z.string(), +}); + +const SchemaPublishAuditLogSchema = z.object({ + projectId: z.string(), + targetId: z.string(), + schemaName: z.string().nullable().optional(), + schemaSdl: z.string(), +}); + +const SchemaDeletedAuditLogSchema = z.object({ + projectId: z.string(), + targetId: z.string(), + schemaName: z.string(), +}); + +const RoleCreatedAuditLogSchema = z.object({ + roleId: z.string(), + roleName: z.string(), +}); + +const RoleAssignedAuditLogSchema = z.object({ + roleId: z.string(), + roleName: z.string(), + userIdAssigned: z.string(), + userEmailAssigned: z.string(), +}); + +const RoleDeletedAuditLogSchema = z.object({ + roleId: z.string(), + roleName: z.string(), +}); + +const RoleUpdatedAuditLogSchema = z.object({ + roleId: z.string(), + roleName: z.string(), + updatedFields: z.string(), +}); + +export const auditLogSchema = z.discriminatedUnion('eventType', [ + z.object({ + eventType: z.literal('USER_INVITED'), + UserInvitedAuditLogSchema, + }), + z.object({ + eventType: z.literal('USER_JOINED'), + UserJoinedAuditLogSchema, + }), + z.object({ + eventType: z.literal('USER_REMOVED'), + UserRemovedAuditLogSchema, + }), + z.object({ + eventType: z.literal('ORGANIZATION_SETTINGS_UPDATED'), + OrganizationSettingsUpdatedAuditLogSchema, + }), + z.object({ + eventType: z.literal('ORGANIZATION_TRANSFERRED'), + OrganizationTransferredAuditLogSchema, + }), + z.object({ + eventType: z.literal('PROJECT_CREATED'), + ProjectCreatedAuditLogSchema, + }), + z.object({ + eventType: z.literal('PROJECT_SETTINGS_UPDATED'), + ProjectSettingsUpdatedAuditLogSchema, + }), + z.object({ + eventType: z.literal('PROJECT_DELETED'), + ProjectDeletedAuditLogSchema, + }), + z.object({ + eventType: z.literal('TARGET_CREATED'), + TargetCreatedAuditLogSchema, + }), + z.object({ + eventType: z.literal('TARGET_SETTINGS_UPDATED'), + TargetSettingsUpdatedAuditLogSchema, + }), + z.object({ + eventType: z.literal('TARGET_DELETED'), + TargetDeletedAuditLogSchema, + }), + z.object({ + eventType: z.literal('SCHEMA_POLICY_SETTINGS_UPDATED'), + SchemaPolicySettingsUpdatedAuditLogSchema, + }), + z.object({ + eventType: z.literal('SCHEMA_CHECKED'), + SchemaCheckedAuditLogSchema, + }), + z.object({ + eventType: z.literal('SCHEMA_PUBLISH'), + SchemaPublishAuditLogSchema, + }), + z.object({ + eventType: z.literal('SCHEMA_DELETED'), + SchemaDeletedAuditLogSchema, + }), + z.object({ + eventType: z.literal('ROLE_CREATED'), + RoleCreatedAuditLogSchema, + }), + z.object({ + eventType: z.literal('ROLE_ASSIGNED'), + RoleAssignedAuditLogSchema, + }), + z.object({ + eventType: z.literal('ROLE_DELETED'), + RoleDeletedAuditLogSchema, + }), + z.object({ + eventType: z.literal('ROLE_UPDATED'), + RoleUpdatedAuditLogSchema, + }), +]); + +export type AuditLogEvent = z.infer & { + id?: string; + eventTime: string; + user: { + userId: string; + userEmail: string; + user?: User | null; + }; + organizationId: string; +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/Mutation/exportAuditLogsToFile.ts b/packages/services/api/src/modules/audit-logs/resolvers/Mutation/exportAuditLogsToFile.ts new file mode 100644 index 00000000000..36caf1cce22 --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/Mutation/exportAuditLogsToFile.ts @@ -0,0 +1,15 @@ +import type { MutationResolvers } from './../../../../__generated__/types.next'; + +// TODO: Export audit logs to a file +export const exportAuditLogsToFile: NonNullable< + MutationResolvers['exportAuditLogsToFile'] +> = async (_parent, _arg, _ctx) => { + /* Implement Mutation.exportAuditLogsToFile resolver logic here */ + return { + createdAt: '2021-09-01T00:00:00Z', + id: 'id', + url: 'url', + validUntil: '2021-09-01T00:00:00Z', + __typename: 'AuditLogFileExport', + }; +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/OrganizationSettingsUpdatedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/OrganizationSettingsUpdatedAuditLog.ts new file mode 100644 index 00000000000..4be4c14826d --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/OrganizationSettingsUpdatedAuditLog.ts @@ -0,0 +1,23 @@ +import type { OrganizationSettingsUpdatedAuditLogResolvers } from './../../../__generated__/types.next'; + +export const OrganizationSettingsUpdatedAuditLog: OrganizationSettingsUpdatedAuditLogResolvers = { + __isTypeOf: e => e.eventType === 'ORGANIZATION_SETTINGS_UPDATED', + eventTime: e => e.eventTime, + eventType: e => e.eventType, + id: e => e.id, + updatedFields: e => { + if (e.eventType === 'ORGANIZATION_SETTINGS_UPDATED') { + return e.updatedFields; + } + throw new Error('Invalid eventType'); + }, + organizationId: e => e.organizationId, + user: async (parent, _args, _ctx) => { + return { + userEmail: parent.user.userEmail, + userId: parent.user.userId, + user: parent.user.user, + __typename: 'AuditLogUserRecord', + }; + }, +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/OrganizationTransferredAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/OrganizationTransferredAuditLog.ts new file mode 100644 index 00000000000..d0f373f431c --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/OrganizationTransferredAuditLog.ts @@ -0,0 +1,29 @@ +import type { OrganizationTransferredAuditLogResolvers } from './../../../__generated__/types.next'; + +export const OrganizationTransferredAuditLog: OrganizationTransferredAuditLogResolvers = { + __isTypeOf: e => e.eventType === 'ORGANIZATION_TRANSFERRED', + eventTime: e => e.eventTime, + eventType: e => e.eventType, + id: e => e.id, + newOwnerEmail: e => { + if (e.eventType === 'ORGANIZATION_TRANSFERRED') { + return e.newOwnerEmail; + } + throw new Error('Invalid eventType'); + }, + newOwnerId: e => { + if (e.eventType === 'ORGANIZATION_TRANSFERRED') { + return e.newOwnerId; + } + throw new Error('Invalid eventType'); + }, + organizationId: e => e.organizationId, + user: async (parent, _args, _ctx) => { + return { + userEmail: parent.user.userEmail, + userId: parent.user.userId, + user: parent.user.user, + __typename: 'AuditLogUserRecord', + }; + }, +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/ProjectCreatedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/ProjectCreatedAuditLog.ts new file mode 100644 index 00000000000..c5a64bd944a --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/ProjectCreatedAuditLog.ts @@ -0,0 +1,29 @@ +import type { ProjectCreatedAuditLogResolvers } from './../../../__generated__/types.next'; + +export const ProjectCreatedAuditLog: ProjectCreatedAuditLogResolvers = { + __isTypeOf: e => e.eventType === 'PROJECT_CREATED', + eventTime: e => e.eventTime, + eventType: e => e.eventType, + id: e => e.id, + projectId: e => { + if (e.eventType === 'PROJECT_CREATED') { + return e.projectId; + } + throw new Error('Invalid eventType'); + }, + projectName: e => { + if (e.eventType === 'PROJECT_CREATED') { + return e.projectName; + } + throw new Error('Invalid eventType'); + }, + organizationId: e => e.organizationId, + user: async (parent, _args, _ctx) => { + return { + userEmail: parent.user.userEmail, + userId: parent.user.userId, + user: parent.user.user, + __typename: 'AuditLogUserRecord', + }; + }, +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/ProjectDeletedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/ProjectDeletedAuditLog.ts new file mode 100644 index 00000000000..6003651df08 --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/ProjectDeletedAuditLog.ts @@ -0,0 +1,29 @@ +import type { ProjectDeletedAuditLogResolvers } from './../../../__generated__/types.next'; + +export const ProjectDeletedAuditLog: ProjectDeletedAuditLogResolvers = { + __isTypeOf: e => e.eventType === 'PROJECT_DELETED', + eventTime: e => e.eventTime, + eventType: e => e.eventType, + id: e => e.id, + projectId: e => { + if (e.eventType === 'PROJECT_DELETED') { + return e.projectId; + } + throw new Error('Invalid eventType'); + }, + projectName: e => { + if (e.eventType === 'PROJECT_DELETED') { + return e.projectName; + } + throw new Error('Invalid eventType'); + }, + organizationId: e => e.organizationId, + user: async (parent, _args, _ctx) => { + return { + userEmail: parent.user.userEmail, + userId: parent.user.userId, + user: parent.user.user, + __typename: 'AuditLogUserRecord', + }; + }, +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/ProjectSettingsUpdatedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/ProjectSettingsUpdatedAuditLog.ts new file mode 100644 index 00000000000..6283ccbc695 --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/ProjectSettingsUpdatedAuditLog.ts @@ -0,0 +1,29 @@ +import type { ProjectSettingsUpdatedAuditLogResolvers } from './../../../__generated__/types.next'; + +export const ProjectSettingsUpdatedAuditLog: ProjectSettingsUpdatedAuditLogResolvers = { + __isTypeOf: e => e.eventType === 'PROJECT_SETTINGS_UPDATED', + eventTime: e => e.eventTime, + eventType: e => e.eventType, + id: e => e.id, + projectId: e => { + if (e.eventType === 'PROJECT_SETTINGS_UPDATED') { + return e.projectId; + } + throw new Error('Invalid eventType'); + }, + updatedFields: e => { + if (e.eventType === 'PROJECT_SETTINGS_UPDATED') { + return e.updatedFields; + } + throw new Error('Invalid eventType'); + }, + organizationId: e => e.organizationId, + user: async (parent, _args, _ctx) => { + return { + userEmail: parent.user.userEmail, + userId: parent.user.userId, + user: parent.user.user, + __typename: 'AuditLogUserRecord', + }; + }, +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/Query/auditLogExports.ts b/packages/services/api/src/modules/audit-logs/resolvers/Query/auditLogExports.ts new file mode 100644 index 00000000000..b0828cb25d1 --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/Query/auditLogExports.ts @@ -0,0 +1,31 @@ +import type { QueryResolvers } from './../../../../__generated__/types.next'; + +// TODO: Implement Query.auditLogExports resolver +export const auditLogExports: NonNullable = async ( + _parent, + _arg, + _ctx, +) => { + return { + edges: [ + { + cursor: 'cursor', + node: { + createdAt: '2021-09-01T00:00:00Z', + id: 'id', + url: 'url', + validUntil: '2021-09-01T00:00:00Z', + __typename: 'AuditLogFileExport', + }, + __typename: 'ExportResultEdge', + }, + ], + pageInfo: { + hasNextPage: false, + hasPreviousPage: false, + startCursor: 'startCursor', + endCursor: 'endCursor', + }, + __typename: 'ExportResultConnection', + }; +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/Query/auditLogs.ts b/packages/services/api/src/modules/audit-logs/resolvers/Query/auditLogs.ts new file mode 100644 index 00000000000..f1285d833a3 --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/Query/auditLogs.ts @@ -0,0 +1,22 @@ +import { AuditLogManager } from '../../providers/audit-logs-manager'; +import type { QueryResolvers } from './../../../../__generated__/types.next'; + +export const auditLogs: NonNullable = async (_parent, arg, ctx) => { + const countAuditLogs = await ctx.injector.get(AuditLogManager).getAuditLogsCount(); + if (countAuditLogs === 0) { + return { + nodes: [], + total: 0, + __typename: 'AuditLogConnection', + }; + } + + const { after, first } = arg; + const auditLogs = await ctx.injector.get(AuditLogManager).getPaginatedAuditLogs(after, first); + + return { + nodes: auditLogs, + total: countAuditLogs, + __typename: 'AuditLogConnection', + }; +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/RoleAssignedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/RoleAssignedAuditLog.ts new file mode 100644 index 00000000000..9546761b716 --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/RoleAssignedAuditLog.ts @@ -0,0 +1,41 @@ +import type { RoleAssignedAuditLogResolvers } from './../../../__generated__/types.next'; + +export const RoleAssignedAuditLog: RoleAssignedAuditLogResolvers = { + __isTypeOf: e => e.eventType === 'ROLE_ASSIGNED', + eventTime: e => e.eventTime, + eventType: e => e.eventType, + id: e => e.id, + roleId: e => { + if (e.eventType === 'ROLE_ASSIGNED') { + return e.roleId; + } + throw new Error('Invalid eventType'); + }, + roleName: e => { + if (e.eventType === 'ROLE_ASSIGNED') { + return e.roleName; + } + throw new Error('Invalid eventType'); + }, + userEmailAssigned: e => { + if (e.eventType === 'ROLE_ASSIGNED') { + return e.userEmailAssigned; + } + throw new Error('Invalid eventType'); + }, + userIdAssigned: e => { + if (e.eventType === 'ROLE_ASSIGNED') { + return e.userIdAssigned; + } + throw new Error('Invalid eventType'); + }, + organizationId: e => e.organizationId, + user: async (parent, _args, _ctx) => { + return { + userEmail: parent.user.userEmail, + userId: parent.user.userId, + user: parent.user.user, + __typename: 'AuditLogUserRecord', + }; + }, +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/RoleCreatedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/RoleCreatedAuditLog.ts new file mode 100644 index 00000000000..097395458e7 --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/RoleCreatedAuditLog.ts @@ -0,0 +1,29 @@ +import type { RoleCreatedAuditLogResolvers } from './../../../__generated__/types.next'; + +export const RoleCreatedAuditLog: RoleCreatedAuditLogResolvers = { + __isTypeOf: e => e.eventType === 'ROLE_CREATED', + eventTime: e => e.eventTime, + eventType: e => e.eventType, + id: e => e.id, + roleId: e => { + if (e.eventType === 'ROLE_CREATED') { + return e.roleId; + } + throw new Error('Invalid eventType'); + }, + roleName: e => { + if (e.eventType === 'ROLE_CREATED') { + return e.roleName; + } + throw new Error('Invalid eventType'); + }, + organizationId: e => e.organizationId, + user: async (parent, _args, _ctx) => { + return { + userEmail: parent.user.userEmail, + userId: parent.user.userId, + user: parent.user.user, + __typename: 'AuditLogUserRecord', + }; + }, +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/RoleDeletedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/RoleDeletedAuditLog.ts new file mode 100644 index 00000000000..ced803a3d68 --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/RoleDeletedAuditLog.ts @@ -0,0 +1,29 @@ +import type { RoleDeletedAuditLogResolvers } from './../../../__generated__/types.next'; + +export const RoleDeletedAuditLog: RoleDeletedAuditLogResolvers = { + __isTypeOf: e => e.eventType === 'ROLE_DELETED', + eventTime: e => e.eventTime, + eventType: e => e.eventType, + id: e => e.id, + roleId: e => { + if (e.eventType === 'ROLE_DELETED') { + return e.roleId; + } + throw new Error('Invalid eventType'); + }, + roleName: e => { + if (e.eventType === 'ROLE_DELETED') { + return e.roleName; + } + throw new Error('Invalid eventType'); + }, + organizationId: e => e.organizationId, + user: async (parent, _args, _ctx) => { + return { + userEmail: parent.user.userEmail, + userId: parent.user.userId, + user: parent.user.user, + __typename: 'AuditLogUserRecord', + }; + }, +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/RoleUpdatedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/RoleUpdatedAuditLog.ts new file mode 100644 index 00000000000..61dee9ccd37 --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/RoleUpdatedAuditLog.ts @@ -0,0 +1,35 @@ +import type { RoleUpdatedAuditLogResolvers } from './../../../__generated__/types.next'; + +export const RoleUpdatedAuditLog: RoleUpdatedAuditLogResolvers = { + __isTypeOf: e => e.eventType === 'ROLE_UPDATED', + eventTime: e => e.eventTime, + eventType: e => e.eventType, + id: e => e.id, + roleId: e => { + if (e.eventType === 'ROLE_UPDATED') { + return e.roleId; + } + throw new Error('Invalid eventType'); + }, + roleName: e => { + if (e.eventType === 'ROLE_UPDATED') { + return e.roleName; + } + throw new Error('Invalid eventType'); + }, + organizationId: e => e.organizationId, + user: async (parent, _args, _ctx) => { + return { + userEmail: parent.user.userEmail, + userId: parent.user.userId, + user: parent.user.user, + __typename: 'AuditLogUserRecord', + }; + }, + updatedFields: e => { + if (e.eventType === 'ROLE_UPDATED') { + return e.updatedFields; + } + throw new Error('Invalid eventType'); + }, +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/SchemaCheckedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/SchemaCheckedAuditLog.ts new file mode 100644 index 00000000000..a87b3b189b9 --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/SchemaCheckedAuditLog.ts @@ -0,0 +1,35 @@ +import type { SchemaCheckedAuditLogResolvers } from './../../../__generated__/types.next'; + +export const SchemaCheckedAuditLog: SchemaCheckedAuditLogResolvers = { + __isTypeOf: e => e.eventType === 'SCHEMA_CHECKED', + eventTime: e => e.eventTime, + eventType: e => e.eventType, + id: e => e.id, + projectId: e => { + if (e.eventType === 'SCHEMA_CHECKED') { + return e.projectId; + } + throw new Error('Invalid eventType'); + }, + schemaSdl: e => { + if (e.eventType === 'SCHEMA_CHECKED') { + return e.schemaSdl; + } + throw new Error('Invalid eventType'); + }, + targetId: e => { + if (e.eventType === 'SCHEMA_CHECKED') { + return e.targetId; + } + throw new Error('Invalid eventType'); + }, + organizationId: e => e.organizationId, + user: async (parent, _args, _ctx) => { + return { + userEmail: parent.user.userEmail, + userId: parent.user.userId, + user: parent.user.user, + __typename: 'AuditLogUserRecord', + }; + }, +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/SchemaDeletedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/SchemaDeletedAuditLog.ts new file mode 100644 index 00000000000..2fdd55a73a5 --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/SchemaDeletedAuditLog.ts @@ -0,0 +1,35 @@ +import type { SchemaDeletedAuditLogResolvers } from './../../../__generated__/types.next'; + +export const SchemaDeletedAuditLog: SchemaDeletedAuditLogResolvers = { + __isTypeOf: e => e.eventType === 'SCHEMA_DELETED', + eventTime: e => e.eventTime, + eventType: e => e.eventType, + id: e => e.id, + projectId: e => { + if (e.eventType === 'SCHEMA_DELETED') { + return e.projectId; + } + throw new Error('Invalid eventType'); + }, + targetId: e => { + if (e.eventType === 'SCHEMA_DELETED') { + return e.targetId; + } + throw new Error('Invalid eventType'); + }, + schemaName: e => { + if (e.eventType === 'SCHEMA_DELETED') { + return e.schemaName; + } + throw new Error('Invalid eventType'); + }, + organizationId: e => e.organizationId, + user: async (parent, _args, _ctx) => { + return { + userEmail: parent.user.userEmail, + userId: parent.user.userId, + user: parent.user.user, + __typename: 'AuditLogUserRecord', + }; + }, +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/SchemaPolicySettingsUpdatedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/SchemaPolicySettingsUpdatedAuditLog.ts new file mode 100644 index 00000000000..4c17df00002 --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/SchemaPolicySettingsUpdatedAuditLog.ts @@ -0,0 +1,29 @@ +import type { SchemaPolicySettingsUpdatedAuditLogResolvers } from './../../../__generated__/types.next'; + +export const SchemaPolicySettingsUpdatedAuditLog: SchemaPolicySettingsUpdatedAuditLogResolvers = { + __isTypeOf: e => e.eventType === 'SCHEMA_POLICY_SETTINGS_UPDATED', + eventTime: e => e.eventTime, + eventType: e => e.eventType, + id: e => e.id, + projectId: e => { + if (e.eventType === 'SCHEMA_POLICY_SETTINGS_UPDATED') { + return e.projectId; + } + throw new Error('Invalid eventType'); + }, + updatedFields: e => { + if (e.eventType === 'SCHEMA_POLICY_SETTINGS_UPDATED') { + return e.updatedFields; + } + throw new Error('Invalid eventType'); + }, + organizationId: e => e.organizationId, + user: async (parent, _args, _ctx) => { + return { + userEmail: parent.user.userEmail, + userId: parent.user.userId, + user: parent.user.user, + __typename: 'AuditLogUserRecord', + }; + }, +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/SchemaPublishAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/SchemaPublishAuditLog.ts new file mode 100644 index 00000000000..d8a2ea1ef21 --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/SchemaPublishAuditLog.ts @@ -0,0 +1,35 @@ +import type { SchemaPublishAuditLogResolvers } from './../../../__generated__/types.next'; + +export const SchemaPublishAuditLog: SchemaPublishAuditLogResolvers = { + __isTypeOf: e => e.eventType === 'SCHEMA_PUBLISH', + eventTime: e => e.eventTime, + eventType: e => e.eventType, + id: e => e.id, + projectId: e => { + if (e.eventType === 'SCHEMA_PUBLISH') { + return e.projectId; + } + throw new Error('Invalid eventType'); + }, + targetId: e => { + if (e.eventType === 'SCHEMA_PUBLISH') { + return e.targetId; + } + throw new Error('Invalid eventType'); + }, + schemaName: e => { + if (e.eventType === 'SCHEMA_PUBLISH') { + return e.schemaName; + } + throw new Error('Invalid eventType'); + }, + organizationId: e => e.organizationId, + user: async (parent, _args, _ctx) => { + return { + userEmail: parent.user.userEmail, + userId: parent.user.userId, + user: parent.user.user, + __typename: 'AuditLogUserRecord', + }; + }, +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/TargetCreatedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/TargetCreatedAuditLog.ts new file mode 100644 index 00000000000..b0b0b63d40b --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/TargetCreatedAuditLog.ts @@ -0,0 +1,35 @@ +import type { TargetCreatedAuditLogResolvers } from './../../../__generated__/types.next'; + +export const TargetCreatedAuditLog: TargetCreatedAuditLogResolvers = { + __isTypeOf: e => e.eventType === 'TARGET_CREATED', + eventTime: e => e.eventTime, + eventType: e => e.eventType, + id: e => e.id, + projectId: e => { + if (e.eventType === 'TARGET_CREATED') { + return e.projectId; + } + throw new Error('Invalid eventType'); + }, + targetId: e => { + if (e.eventType === 'TARGET_CREATED') { + return e.targetId; + } + throw new Error('Invalid eventType'); + }, + targetName: e => { + if (e.eventType === 'TARGET_CREATED') { + return e.targetName; + } + throw new Error('Invalid eventType'); + }, + organizationId: e => e.organizationId, + user: async (parent, _args, _ctx) => { + return { + userEmail: parent.user.userEmail, + userId: parent.user.userId, + user: parent.user.user, + __typename: 'AuditLogUserRecord', + }; + }, +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/TargetDeletedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/TargetDeletedAuditLog.ts new file mode 100644 index 00000000000..68f982899e3 --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/TargetDeletedAuditLog.ts @@ -0,0 +1,35 @@ +import type { TargetDeletedAuditLogResolvers } from './../../../__generated__/types.next'; + +export const TargetDeletedAuditLog: TargetDeletedAuditLogResolvers = { + __isTypeOf: e => e.eventType === 'TARGET_DELETED', + eventTime: e => e.eventTime, + eventType: e => e.eventType, + id: e => e.id, + projectId: e => { + if (e.eventType === 'TARGET_DELETED') { + return e.projectId; + } + throw new Error('Invalid eventType'); + }, + targetId: e => { + if (e.eventType === 'TARGET_DELETED') { + return e.targetId; + } + throw new Error('Invalid eventType'); + }, + targetName: e => { + if (e.eventType === 'TARGET_DELETED') { + return e.targetName; + } + throw new Error('Invalid eventType'); + }, + organizationId: e => e.organizationId, + user: async (parent, _args, _ctx) => { + return { + userEmail: parent.user.userEmail, + userId: parent.user.userId, + user: parent.user.user, + __typename: 'AuditLogUserRecord', + }; + }, +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/TargetSettingsUpdatedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/TargetSettingsUpdatedAuditLog.ts new file mode 100644 index 00000000000..458c9890b3b --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/TargetSettingsUpdatedAuditLog.ts @@ -0,0 +1,35 @@ +import type { TargetSettingsUpdatedAuditLogResolvers } from './../../../__generated__/types.next'; + +export const TargetSettingsUpdatedAuditLog: TargetSettingsUpdatedAuditLogResolvers = { + __isTypeOf: e => e.eventType === 'TARGET_SETTINGS_UPDATED', + eventTime: e => e.eventTime, + eventType: e => e.eventType, + id: e => e.id, + projectId: e => { + if (e.eventType === 'TARGET_SETTINGS_UPDATED') { + return e.projectId; + } + throw new Error('Invalid eventType'); + }, + targetId: e => { + if (e.eventType === 'TARGET_SETTINGS_UPDATED') { + return e.targetId; + } + throw new Error('Invalid eventType'); + }, + updatedFields: e => { + if (e.eventType === 'TARGET_SETTINGS_UPDATED') { + return e.updatedFields; + } + throw new Error('Invalid eventType'); + }, + organizationId: e => e.organizationId, + user: async (parent, _args, _ctx) => { + return { + userEmail: parent.user.userEmail, + userId: parent.user.userId, + user: parent.user.user, + __typename: 'AuditLogUserRecord', + }; + }, +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/UserInvitedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/UserInvitedAuditLog.ts new file mode 100644 index 00000000000..67491d1718a --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/UserInvitedAuditLog.ts @@ -0,0 +1,29 @@ +import type { UserInvitedAuditLogResolvers } from './../../../__generated__/types.next'; + +export const UserInvitedAuditLog: UserInvitedAuditLogResolvers = { + __isTypeOf: e => e.eventType === 'USER_INVITED', + eventTime: e => e.eventTime, + eventType: e => e.eventType, + id: e => e.id, + inviteeEmail: e => { + if (e.eventType === 'USER_INVITED') { + return e.inviteeEmail; + } + throw new Error('Invalid eventType'); + }, + inviteeId: e => { + if (e.eventType === 'USER_INVITED') { + return e.inviteeId; + } + throw new Error('Invalid eventType'); + }, + organizationId: e => e.organizationId, + user: async (parent, _args, _ctx) => { + return { + userEmail: parent.user.userEmail, + userId: parent.user.userId, + user: parent.user.user, + __typename: 'AuditLogUserRecord', + }; + }, +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/UserJoinedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/UserJoinedAuditLog.ts new file mode 100644 index 00000000000..bcced7c4b94 --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/UserJoinedAuditLog.ts @@ -0,0 +1,29 @@ +import type { UserJoinedAuditLogResolvers } from './../../../__generated__/types.next'; + +export const UserJoinedAuditLog: UserJoinedAuditLogResolvers = { + __isTypeOf: e => e.eventType === 'USER_JOINED', + eventTime: e => e.eventTime, + eventType: e => e.eventType, + id: e => e.id, + inviteeEmail: e => { + if (e.eventType === 'USER_JOINED') { + return e.inviteeEmail; + } + throw new Error('Invalid eventType'); + }, + inviteeId: e => { + if (e.eventType === 'USER_JOINED') { + return e.inviteeId; + } + throw new Error('Invalid eventType'); + }, + organizationId: e => e.organizationId, + user: async (parent, _args, _ctx) => { + return { + userEmail: parent.user.userEmail, + userId: parent.user.userId, + user: parent.user.user, + __typename: 'AuditLogUserRecord', + }; + }, +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/UserRemovedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/UserRemovedAuditLog.ts new file mode 100644 index 00000000000..6b08e05bafd --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/UserRemovedAuditLog.ts @@ -0,0 +1,28 @@ +import type { UserRemovedAuditLogResolvers } from './../../../__generated__/types.next'; + +export const UserRemovedAuditLog: UserRemovedAuditLogResolvers = { + __isTypeOf: e => e.eventType === 'USER_REMOVED', + eventTime: e => e.eventTime, + eventType: e => e.eventType, + id: e => e.id, + removedUserEmail: e => { + if (e.eventType === 'USER_REMOVED') { + return e.removedUserEmail; + } + throw new Error('Invalid eventType'); + }, + removedUserId: e => { + if (e.eventType === 'USER_REMOVED') { + return e.removedUserEmail; + } + throw new Error('Invalid eventType'); + }, + organizationId: e => e.organizationId, + user: e => { + return { + userEmail: e.user.userEmail, + userId: e.user.userId, + user: e.user.user, + }; + }, +}; diff --git a/packages/services/api/src/modules/organization/resolvers.ts b/packages/services/api/src/modules/organization/resolvers.ts index 48a144ced3b..fe6019f5068 100644 --- a/packages/services/api/src/modules/organization/resolvers.ts +++ b/packages/services/api/src/modules/organization/resolvers.ts @@ -2,6 +2,7 @@ import { createHash } from 'node:crypto'; import { z } from 'zod'; import { NameModel } from '../../shared/entities'; import { createConnection } from '../../shared/schema'; +import { AuditLogManager } from '../audit-logs/providers/audit-logs-manager'; import { AuthManager } from '../auth/providers/auth-manager'; import { isOrganizationScope, @@ -251,6 +252,23 @@ export const resolvers: OrganizationModule.Resolvers = { organization: organizationId, }); + const currentUser = await injector.get(AuthManager).getCurrentUser(); + const jsonUpdatedFields = JSON.stringify({ + name: input.name, + }); + await injector.get(AuditLogManager).createLogAuditEvent({ + eventType: 'ORGANIZATION_SETTINGS_UPDATED', + eventTime: new Date().toISOString(), + organizationId: organizationId, + user: { + userEmail: currentUser.email, + userId: currentUser.id, + }, + OrganizationSettingsUpdatedAuditLogSchema: { + updatedFields: jsonUpdatedFields, + }, + }); + return { ok: { updatedOrganizationPayload: { @@ -442,8 +460,7 @@ export const resolvers: OrganizationModule.Resolvers = { } const organizationId = await injector.get(IdTranslator).translateOrganizationId(input); - - return injector.get(OrganizationManager).createMemberRole({ + const result = await injector.get(OrganizationManager).createMemberRole({ organizationId, name: inputValidation.data.name, description: inputValidation.data.description, @@ -451,6 +468,27 @@ export const resolvers: OrganizationModule.Resolvers = { projectAccessScopes: input.projectAccessScopes, targetAccessScopes: input.targetAccessScopes, }); + + // Audit log event + if (result.ok) { + const currentUser = await injector.get(AuthManager).getCurrentUser(); + + await injector.get(AuditLogManager).createLogAuditEvent({ + eventType: 'ROLE_CREATED', + eventTime: new Date().toISOString(), + organizationId, + user: { + userEmail: currentUser.email, + userId: currentUser.id, + }, + RoleCreatedAuditLogSchema: { + roleName: inputValidation.data.name, + roleId: result.ok.createdRole.id, + }, + }); + } + + return result; }, async updateMemberRole(_, { input }, { injector }) { const inputValidation = createOrUpdateMemberRoleInputSchema.safeParse({ @@ -470,8 +508,7 @@ export const resolvers: OrganizationModule.Resolvers = { }; } const organizationId = await injector.get(IdTranslator).translateOrganizationId(input); - - return injector.get(OrganizationManager).updateMemberRole({ + const result = await injector.get(OrganizationManager).updateMemberRole({ organizationId, roleId: input.role, name: inputValidation.data.name, @@ -480,23 +517,93 @@ export const resolvers: OrganizationModule.Resolvers = { projectAccessScopes: input.projectAccessScopes, targetAccessScopes: input.targetAccessScopes, }); + + // Audit log event + if (result.ok) { + const currentUser = await injector.get(AuthManager).getCurrentUser(); + + await injector.get(AuditLogManager).createLogAuditEvent({ + eventType: 'ROLE_UPDATED', + eventTime: new Date().toISOString(), + organizationId, + user: { + userEmail: currentUser.email, + userId: currentUser.id, + }, + RoleUpdatedAuditLogSchema: { + roleName: inputValidation.data.name, + roleId: input.role, + updatedFields: JSON.stringify({ + name: inputValidation.data.name, + description: inputValidation.data.description, + organizationAccessScopes: input.organizationAccessScopes, + projectAccessScopes: input.projectAccessScopes, + targetAccessScopes: input.targetAccessScopes, + }), + }, + }); + } + return result; }, async deleteMemberRole(_, { input }, { injector }) { const organizationId = await injector.get(IdTranslator).translateOrganizationId(input); - - return injector.get(OrganizationManager).deleteMemberRole({ + const result = await injector.get(OrganizationManager).deleteMemberRole({ organizationId, roleId: input.role, }); + + // Audit log event + if (result.ok) { + const currentUser = await injector.get(AuthManager).getCurrentUser(); + + await injector.get(AuditLogManager).createLogAuditEvent({ + eventType: 'ROLE_DELETED', + eventTime: new Date().toISOString(), + organizationId, + user: { + userEmail: currentUser.email, + userId: currentUser.id, + }, + RoleDeletedAuditLogSchema: { + roleName: result.ok.updatedOrganization.name, + roleId: input.role, + }, + }); + } + return result; }, async assignMemberRole(_, { input }, { injector }) { const organizationId = await injector.get(IdTranslator).translateOrganizationId(input); - - return injector.get(OrganizationManager).assignMemberRole({ + const result = await injector.get(OrganizationManager).assignMemberRole({ organizationId, userId: input.user, roleId: input.role, }); + + // Audit log event + if (result.ok) { + const currentUser = await injector.get(AuthManager).getCurrentUser(); + + await injector.get(AuditLogManager).createLogAuditEvent({ + eventType: 'ROLE_ASSIGNED', + eventTime: new Date().toISOString(), + organizationId, + user: { + userEmail: currentUser.email, + userId: currentUser.id, + }, + RoleAssignedAuditLogSchema: { + roleId: input.role, + roleName: result.ok.previousMemberRole + ? result.ok.previousMemberRole.name + : 'No Previous Role', + userEmailAssigned: result.ok.updatedMember.user.email, + userIdAssigned: input.user, + }, + }); + } + + return result; }, async migrateUnassignedMembers(_, { input }, { injector }) { const organizationIdFromInput = diff --git a/packages/services/api/src/modules/project/resolvers/Mutation/createProject.ts b/packages/services/api/src/modules/project/resolvers/Mutation/createProject.ts index ccb89814fef..325ccd9b5f9 100644 --- a/packages/services/api/src/modules/project/resolvers/Mutation/createProject.ts +++ b/packages/services/api/src/modules/project/resolvers/Mutation/createProject.ts @@ -1,4 +1,6 @@ import { z } from 'zod'; +import { AuditLogManager } from '../../../audit-logs/providers/audit-logs-manager'; +import { AuthManager } from '../../../auth/providers/auth-manager'; import { OrganizationManager } from '../../../organization/providers/organization-manager'; import { IdTranslator } from '../../../shared/providers/id-translator'; import { TargetManager } from '../../../target/providers/target-manager'; @@ -63,6 +65,22 @@ export const createProject: NonNullable = as }), ]); + // Audit Log Event + const currentUser = await injector.get(AuthManager).getCurrentUser(); + await injector.get(AuditLogManager).createLogAuditEvent({ + eventTime: new Date().toISOString(), + eventType: 'PROJECT_CREATED', + organizationId: organizationId, + user: { + userId: currentUser.id, + userEmail: currentUser.email, + }, + ProjectCreatedAuditLogSchema: { + projectId: project.id, + projectName: project.name, + }, + }); + return { ok: { createdProject: project, diff --git a/packages/services/api/src/modules/project/resolvers/Mutation/deleteProject.ts b/packages/services/api/src/modules/project/resolvers/Mutation/deleteProject.ts index 27970f54f7c..3ffe4acff0d 100644 --- a/packages/services/api/src/modules/project/resolvers/Mutation/deleteProject.ts +++ b/packages/services/api/src/modules/project/resolvers/Mutation/deleteProject.ts @@ -1,3 +1,5 @@ +import { AuditLogManager } from '../../../audit-logs/providers/audit-logs-manager'; +import { AuthManager } from '../../../auth/providers/auth-manager'; import { IdTranslator } from '../../../shared/providers/id-translator'; import { ProjectManager } from '../../providers/project-manager'; import type { MutationResolvers } from './../../../../__generated__/types.next'; @@ -21,6 +23,23 @@ export const deleteProject: NonNullable = as organization: organizationId, project: projectId, }); + + // Audit Log Event + const currentUser = await injector.get(AuthManager).getCurrentUser(); + await injector.get(AuditLogManager).createLogAuditEvent({ + eventTime: new Date().toISOString(), + eventType: 'PROJECT_DELETED', + organizationId: organizationId, + user: { + userId: currentUser.id, + userEmail: currentUser.email, + }, + ProjectDeletedAuditLogSchema: { + projectId: projectId, + projectName: deletedProject.name, + }, + }); + return { selector: { organization: organizationId, diff --git a/packages/services/api/src/modules/project/resolvers/Mutation/updateProjectName.ts b/packages/services/api/src/modules/project/resolvers/Mutation/updateProjectName.ts index 595d2809521..1c5d6b8f025 100644 --- a/packages/services/api/src/modules/project/resolvers/Mutation/updateProjectName.ts +++ b/packages/services/api/src/modules/project/resolvers/Mutation/updateProjectName.ts @@ -1,4 +1,6 @@ import { z } from 'zod'; +import { AuditLogManager } from '../../../audit-logs/providers/audit-logs-manager'; +import { AuthManager } from '../../../auth/providers/auth-manager'; import { IdTranslator } from '../../../shared/providers/id-translator'; import { ProjectManager } from '../../providers/project-manager'; import { ProjectNameModel } from '../../validation'; @@ -35,6 +37,25 @@ export const updateProjectName: NonNullable = asyn project, name: input.name, }); + + // Audit Log Event + const currentUser = await injector.get(AuthManager).getCurrentUser(); + await injector.get(AuditLogManager).createLogAuditEvent({ + eventTime: new Date().toISOString(), + eventType: 'TARGET_CREATED', + organizationId: target.orgId, + user: { + userId: currentUser.id, + userEmail: currentUser.email, + }, + TargetCreatedAuditLogSchema: { + projectId: target.projectId, + targetId: target.id, + targetName: target.name, + }, + }); + return { ok: { selector: { diff --git a/packages/services/api/src/modules/target/resolvers/Mutation/deleteTarget.ts b/packages/services/api/src/modules/target/resolvers/Mutation/deleteTarget.ts index fa04e6d0950..fc4c78a6059 100644 --- a/packages/services/api/src/modules/target/resolvers/Mutation/deleteTarget.ts +++ b/packages/services/api/src/modules/target/resolvers/Mutation/deleteTarget.ts @@ -1,3 +1,5 @@ +import { AuditLogManager } from '../../../audit-logs/providers/audit-logs-manager'; +import { AuthManager } from '../../../auth/providers/auth-manager'; import { IdTranslator } from '../../../shared/providers/id-translator'; import { TargetManager } from '../../providers/target-manager'; import type { MutationResolvers } from './../../../../__generated__/types.next'; @@ -27,6 +29,24 @@ export const deleteTarget: NonNullable = asyn project: projectId, target: targetId, }); + + // Audit Log Event + const currentUser = await injector.get(AuthManager).getCurrentUser(); + await injector.get(AuditLogManager).createLogAuditEvent({ + eventTime: new Date().toISOString(), + eventType: 'TARGET_DELETED', + organizationId: target.orgId, + user: { + userId: currentUser.id, + userEmail: currentUser.email, + }, + TargetDeletedAuditLogSchema: { + projectId: target.projectId, + targetId: target.id, + targetName: target.name, + }, + }); + return { selector: { organization: organizationId, diff --git a/packages/services/api/src/modules/target/resolvers/Mutation/experimental__updateTargetSchemaComposition.ts b/packages/services/api/src/modules/target/resolvers/Mutation/experimental__updateTargetSchemaComposition.ts index 6e3beb25ba5..69cf05e293c 100644 --- a/packages/services/api/src/modules/target/resolvers/Mutation/experimental__updateTargetSchemaComposition.ts +++ b/packages/services/api/src/modules/target/resolvers/Mutation/experimental__updateTargetSchemaComposition.ts @@ -1,3 +1,5 @@ +import { AuditLogManager } from '../../../audit-logs/providers/audit-logs-manager'; +import { AuthManager } from '../../../auth/providers/auth-manager'; import { IdTranslator } from '../../../shared/providers/id-translator'; import { TargetManager } from '../../providers/target-manager'; import type { MutationResolvers } from './../../../../__generated__/types.next'; @@ -12,10 +14,33 @@ export const experimental__updateTargetSchemaComposition: NonNullable< translator.translateTargetId(input), ]); - return injector.get(TargetManager).updateTargetSchemaComposition({ + const result = await injector.get(TargetManager).updateTargetSchemaComposition({ organizationId, projectId, targetId, nativeComposition: input.nativeComposition, }); + + // Audit Log Event + const currentUser = await injector.get(AuthManager).getCurrentUser(); + const allUpdatedFields = JSON.stringify({ + nativeComposition: input.nativeComposition, + }); + + await injector.get(AuditLogManager).createLogAuditEvent({ + eventTime: new Date().toISOString(), + eventType: 'TARGET_SETTINGS_UPDATED', + organizationId: organizationId, + user: { + userId: currentUser.id, + userEmail: currentUser.email, + }, + TargetSettingsUpdatedAuditLogSchema: { + projectId: projectId, + targetId: targetId, + updatedFields: allUpdatedFields, + }, + }); + + return result; }; diff --git a/packages/services/api/src/modules/target/resolvers/Mutation/setTargetValidation.ts b/packages/services/api/src/modules/target/resolvers/Mutation/setTargetValidation.ts index 8dddfc24c88..60170993924 100644 --- a/packages/services/api/src/modules/target/resolvers/Mutation/setTargetValidation.ts +++ b/packages/services/api/src/modules/target/resolvers/Mutation/setTargetValidation.ts @@ -1,3 +1,5 @@ +import { AuditLogManager } from '../../../audit-logs/providers/audit-logs-manager'; +import { AuthManager } from '../../../auth/providers/auth-manager'; import { IdTranslator } from '../../../shared/providers/id-translator'; import { TargetManager } from '../../providers/target-manager'; import type { MutationResolvers } from './../../../../__generated__/types.next'; @@ -22,9 +24,33 @@ export const setTargetValidation: NonNullable