Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Audit log GraphQL API #5530

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions codegen.mts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ const config: CodegenConfig = {
excludeTypes: [
'TokenInfoPayload',
'OrganizationByInviteCodePayload',
'AuditLog',
'JoinOrganizationPayload',
'Schema',
'GraphQLNamedType',
Expand Down
41 changes: 41 additions & 0 deletions integration-tests/tests/api/audit-log/creation.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { graphql } from 'testkit/gql';
import { execute } from '../../../testkit/graphql';
import { initSeed } from '../../../testkit/seed';

describe('Audit Logs Creation', () => {
describe('Organization', () => {
const query = graphql(`
query MyQuery($selector: OrganizationSelectorInput!) {
auditLogs(selector: $selector) {
nodes {
eventTime
id
__typename
}
}
}
`);
test.concurrent(
'Should be only one audit log for organization creation',
async ({ expect }) => {
const { ownerToken, createOrg } = await initSeed().createOwner();
const { organization } = await createOrg();

const result = await execute({
document: query,
variables: {
selector: {
organization: organization.id,
},
},
authToken: ownerToken,
});
expect(result.rawBody.data?.auditLogs.nodes).not.toBeNull();
expect(result.rawBody.data?.auditLogs.nodes.length).toBeGreaterThan(0);
expect(result.rawBody.data?.auditLogs.nodes[0].__typename).toBe(
'OrganizationCreatedAuditLog',
);
},
);
});
});
20 changes: 20 additions & 0 deletions packages/migrations/src/clickhouse-actions/011-audit-logs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { Action } from '../clickhouse';

export const action: Action = async exec => {
await exec(`
CREATE TABLE IF NOT EXISTS "audit_log"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to configure indexing?

@TuvalSimha please confirm what queries are you making here, and @kamilkisiela can help with the indexes.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reminder of this one

(
"id" UUID DEFAULT generateUUIDv4() CODEC(ZSTD(1)),
"event_time" DateTime CODEC(ZSTD(1)),
"user_id" String CODEC(ZSTD(1)),
"user_email" String CODEC(ZSTD(1)),
"organization_id" String CODEC(ZSTD(1)),
"event_action" String CODEC(ZSTD(1)),
"metadata" String CODEC(ZSTD(1))
)
ENGINE = ReplacingMergeTree
ORDER BY ("event_time", "user_id", "organization_id")
TTL event_time + INTERVAL 1 YEAR
TuvalSimha marked this conversation as resolved.
Show resolved Hide resolved
SETTINGS index_granularity = 8192
`);
};
1 change: 1 addition & 0 deletions packages/migrations/src/clickhouse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ export async function migrateClickHouse(
import('./clickhouse-actions/008-daily-operations-log'),
import('./clickhouse-actions/009-ttl-1-year'),
import('./clickhouse-actions/010-app-deployment-operations'),
import('./clickhouse-actions/011-audit-logs'),
]);

async function actionRunner(action: Action, index: number) {
Expand Down
2 changes: 2 additions & 0 deletions packages/services/api/src/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { alertsModule } from './modules/alerts';
import { WEBHOOKS_CONFIG, WebhooksConfig } from './modules/alerts/providers/tokens';
import { appDeploymentsModule } from './modules/app-deployments';
import { APP_DEPLOYMENTS_ENABLED } from './modules/app-deployments/providers/app-deployments-enabled-token';
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';
Expand Down Expand Up @@ -82,6 +83,7 @@ const modules = [
integrationsModule,
alertsModule,
feedbackModule,
auditLogsModule,
cdnModule,
adminModule,
usageEstimationModule,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { AuditLogManager } from '../../../audit-logs/providers/audit-logs-manager';
import { AuthManager } from '../../../auth/providers/auth-manager';
import { AppDeploymentsManager } from '../../providers/app-deployments-manager';
import type { MutationResolvers } from './../../../../__generated__/types.next';

Expand All @@ -20,6 +22,28 @@ export const activateAppDeployment: NonNullable<
};
}

const currentUser = await injector.get(AuthManager).getCurrentUser();
const organization = await injector.get(AuthManager).getOrganizationOwnerByToken();
await injector.get(AuditLogManager).createLogAuditEvent(
{
eventType: 'APP_DEPLOYMENT_UPDATED',
appDeploymentUpdatedAuditLogSchema: {
deploymentId: result.appDeployment.id,
updatedFields: JSON.stringify({
name: result.appDeployment.name,
version: result.appDeployment.version,
status: 'ACTIVATED',
}),
},
},
{
userId: currentUser.id,
userEmail: currentUser.email,
organizationId: organization.id,
user: currentUser,
},
);

return {
error: null,
ok: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { AuditLogManager } from '../../../audit-logs/providers/audit-logs-manager';
import { AuthManager } from '../../../auth/providers/auth-manager';
import { AppDeploymentsManager } from '../../providers/app-deployments-manager';
import type { MutationResolvers } from './../../../../__generated__/types.next';

Expand All @@ -22,6 +24,28 @@ export const addDocumentsToAppDeployment: NonNullable<
};
}

const currentUser = await injector.get(AuthManager).getCurrentUser();
const organization = await injector.get(AuthManager).getOrganizationOwnerByToken();
await injector.get(AuditLogManager).createLogAuditEvent(
{
eventType: 'APP_DEPLOYMENT_UPDATED',
appDeploymentUpdatedAuditLogSchema: {
deploymentId: result.appDeployment.id,
updatedFields: JSON.stringify({
name: result.appDeployment.name,
version: result.appDeployment.version,
documents: input.documents,
}),
},
},
{
userId: currentUser.id,
userEmail: currentUser.email,
organizationId: organization.id,
user: currentUser,
},
);

return {
error: null,
ok: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { AuditLogManager } from '../../../audit-logs/providers/audit-logs-manager';
import { AuthManager } from '../../../auth/providers/auth-manager';
import { AppDeploymentsManager } from '../../providers/app-deployments-manager';
import type { MutationResolvers } from './../../../../__generated__/types.next';

Expand All @@ -23,6 +25,25 @@ export const createAppDeployment: NonNullable<MutationResolvers['createAppDeploy
};
}

const currentUser = await injector.get(AuthManager).getCurrentUser();
const organization = await injector.get(AuthManager).getOrganizationOwnerByToken();
await injector.get(AuditLogManager).createLogAuditEvent(
{
eventType: 'APP_DEPLOYMENT_CREATED',
appDeploymentCreatedAuditLogSchema: {
deploymentId: result.appDeployment.id,
deploymentName: result.appDeployment.name,
deploymentVersion: result.appDeployment.version,
},
},
{
userId: currentUser.id,
userEmail: currentUser.email,
organizationId: organization.id,
user: currentUser,
},
);

return {
error: null,
ok: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { AuditLogManager } from '../../../audit-logs/providers/audit-logs-manager';
import { AuthManager } from '../../../auth/providers/auth-manager';
import { AppDeploymentsManager } from '../../providers/app-deployments-manager';
import type { MutationResolvers } from './../../../../__generated__/types.next';

Expand All @@ -23,6 +25,28 @@ export const retireAppDeployment: NonNullable<MutationResolvers['retireAppDeploy
};
}

const currentUser = await injector.get(AuthManager).getCurrentUser();
const organization = await injector.get(AuthManager).getOrganizationOwnerByToken();
await injector.get(AuditLogManager).createLogAuditEvent(
{
eventType: 'APP_DEPLOYMENT_UPDATED',
appDeploymentUpdatedAuditLogSchema: {
deploymentId: result.appDeployment.id,
updatedFields: JSON.stringify({
name: result.appDeployment.name,
version: result.appDeployment.version,
status: 'RETIRED',
}),
},
},
{
userId: currentUser.id,
userEmail: currentUser.email,
organizationId: organization.id,
user: currentUser,
},
);

return {
error: null,
ok: {
Expand Down
13 changes: 13 additions & 0 deletions packages/services/api/src/modules/audit-logs/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { createModule } from 'graphql-modules';
import { ClickHouse } from '../operations/providers/clickhouse-client';
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, ClickHouse],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { AuditLogModel } from './providers/audit-logs-manager';

export type AuditLogMapper = AuditLogModel;
TuvalSimha marked this conversation as resolved.
Show resolved Hide resolved
// Schema
export type SchemaPolicySettingsUpdatedAuditLogMapper = AuditLogModel;
export type SchemaCheckedAuditLogMapper = AuditLogModel;
export type SchemaPublishAuditLogMapper = AuditLogModel;
export type ServiceDeletedAuditLogMapper = AuditLogModel;
// Organization
export type OrganizationSettingsUpdatedAuditLogMapper = AuditLogModel;
export type OrganizationTransferredAuditLogMapper = AuditLogModel;
export type OrganizationTransferredRequestAuditLogMapper = AuditLogModel;
export type OrganizationCreatedAuditLogMapper = AuditLogModel;
export type OrganizationDeletedAuditLogMapper = AuditLogModel;
export type OrganizationUpdatedIntegrationAuditLogMapper = AuditLogModel;
// Project
export type ProjectCreatedAuditLogMapper = AuditLogModel;
export type ProjectSettingsUpdatedAuditLogMapper = AuditLogModel;
export type ProjectDeletedAuditLogMapper = AuditLogModel;
// User Role
export type RoleCreatedAuditLogMapper = AuditLogModel;
export type RoleAssignedAuditLogMapper = AuditLogModel;
export type RoleDeletedAuditLogMapper = AuditLogModel;
export type RoleUpdatedAuditLogMapper = AuditLogModel;
// Support
export type SupportTicketCreatedAuditLogMapper = AuditLogModel;
export type SupportTicketUpdatedAuditLogMapper = AuditLogModel;
// Laboratory Collection
export type CollectionCreatedAuditLogMapper = AuditLogModel;
export type CollectionDeletedAuditLogMapper = AuditLogModel;
export type CollectionUpdatedAuditLogMapper = AuditLogModel;
// Laboratory Collection Operation
export type OperationInDocumentCollectionCreatedAuditLogMapper = AuditLogModel;
export type OperationInDocumentCollectionUpdatedAuditLogMapper = AuditLogModel;
export type OperationInDocumentCollectionDeletedAuditLogMapper = AuditLogModel;
// User
export type UserInvitedAuditLogMapper = AuditLogModel;
export type UserJoinedAuditLogMapper = AuditLogModel;
export type UserRemovedAuditLogMapper = AuditLogModel;
export type UserSettingsUpdatedAuditLogMapper = AuditLogModel;
// Target
export type TargetCreatedAuditLogMapper = AuditLogModel;
export type TargetSettingsUpdatedAuditLogMapper = AuditLogModel;
export type TargetDeletedAuditLogMapper = AuditLogModel;
// Subscription
export type SubscriptionCreatedAuditLogMapper = AuditLogModel;
export type SubscriptionUpdatedAuditLogMapper = AuditLogModel;
export type SubscriptionCanceledAuditLogMapper = AuditLogModel;
// App Deployment
export type AppDeploymentCreatedAuditLogMapper = AuditLogModel;
export type AppDeploymentUpdatedAuditLogMapper = AuditLogModel;
export type AppDeploymentPublishedAuditLogMapper = AuditLogModel;
Loading