feat(ee): add enterprise audit logs settings page (#4111)

* feat(ee): add enterprise audit logs settings page with server-side search

Add a new audit logs page under enterprise settings that displays all
actions captured via recordAudit. Includes server-side search, resource
type filtering, date range selection, and cursor-based pagination.

- Add internal API route (app/api/audit-logs) with session auth
- Extract shared query logic (buildFilterConditions, buildOrgScopeCondition,
  queryAuditLogs) into app/api/v1/audit-logs/query.ts
- Refactor v1 and admin audit log routes to use shared query module
- Add React Query hook with useInfiniteQuery and cursor pagination
- Add audit logs UI with debounced search, combobox filters, expandable rows
- Gate behind requiresHosted + requiresEnterprise navigation flags
- Place all enterprise audit log code in ee/audit-logs/

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* lint

* fix(ee): fix build error and address PR review comments

- Fix import path: @/lib/utils → @/lib/core/utils/cn
- Guard against empty orgMemberIds array in buildOrgScopeCondition
- Skip debounce effect on mount when search is already synced

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* lint

* fix(ee): fix type error with unknown metadata in JSX expression

Use ternary instead of && chain to prevent unknown type from being
returned as ReactNode.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(ee): align skeleton filter width with actual component layout

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* lint

* feat(audit): add audit logging for passwords, credentials, and schedules

- Add PASSWORD_RESET_REQUESTED audit on forget-password with user lookup
- Add CREDENTIAL_CREATED/UPDATED/DELETED audit on credential CRUD routes
  with metadata (credentialType, providerId, updatedFields, envKey)
- Add SCHEDULE_CREATED audit on schedule creation with cron/timezone metadata
- Fix SCHEDULE_DELETED (was incorrectly using SCHEDULE_UPDATED for deletes)
- Enhance existing schedule update/disable/reactivate audit with structured
  metadata (operation, updatedFields, sourceType, previousStatus)
- Add CREDENTIAL resource type and Credential filter option to audit logs UI
- Enhance password reset completed description with user email

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(audit): align metadata with established recordAudit patterns

- Add actorName/actorEmail to all new credential and schedule audit calls
  to match the established pattern (e.g., api-keys, byok-keys, knowledge)
- Add resourceId and resourceName to forget-password audit call
- Enhance forget-password description with user email

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(testing): sync audit mock with new AuditAction and AuditResourceType entries

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(audit-logs): derive resource type filter from AuditResourceType

Instead of maintaining a separate hardcoded list, the filter dropdown
now derives its options directly from the AuditResourceType const object.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(audit): enrich all recordAudit calls with structured metadata

- Move resource type filter options to ee/audit-logs/constants.ts
  (derived from AuditResourceType, no separate list to maintain)
- Remove export from internal cursor helpers in query.ts
- Add 5 new AuditAction entries: BYOK_KEY_UPDATED, ENVIRONMENT_DELETED,
  INVITATION_RESENT, WORKSPACE_UPDATED, ORG_INVITATION_RESENT
- Enrich ~80 recordAudit calls across the codebase with structured
  metadata (knowledge bases, connectors, documents, workspaces, members,
  invitations, workflows, deployments, templates, MCP servers, credential
  sets, organizations, permission groups, files, tables, notifications,
  copilot operations)
- Sync audit mock with all new entries

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(audit): remove redundant metadata fields duplicating top-level audit fields

Remove metadata entries that duplicate resourceName, workspaceId, or
other top-level recordAudit fields. Also remove noisy fileNames arrays
from bulk document upload audits (kept fileCount).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(audit): split audit types from server-only log module

Extract AuditAction, AuditResourceType, and their types into
lib/audit/types.ts (client-safe, no @sim/db dependency). The
server-only recordAudit stays in log.ts and re-exports the types
for backwards compatibility. constants.ts now imports from types.ts
directly, breaking the postgres -> tls client bundle chain.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(audit): escape LIKE wildcards in audit log search query

Escape %, _, and \ characters in the search parameter before embedding
in the LIKE pattern to prevent unintended broad matches.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(audit): use actual deletedCount in bulk API key revoke description

The description was using keys.length (requested count) instead of
deletedCount (actual count), which could differ if some keys didn't
exist.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(audit-logs): fix OAuth label displaying as "Oauth" in filter dropdown

ACRONYMS set stored 'OAuth' but lookup used toUpperCase() producing
'OAUTH' which never matched. Now store all acronyms uppercase and use
a display override map for special casing like OAuth.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Waleed
2026-04-11 16:15:48 -07:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 6a4f5f2074
commit 30c5e82ab0
94 changed files with 1592 additions and 386 deletions
+11
View File
@@ -18,10 +18,13 @@ export const auditMock = {
PERSONAL_API_KEY_CREATED: 'personal_api_key.created',
PERSONAL_API_KEY_REVOKED: 'personal_api_key.revoked',
BYOK_KEY_CREATED: 'byok_key.created',
BYOK_KEY_UPDATED: 'byok_key.updated',
BYOK_KEY_DELETED: 'byok_key.deleted',
CHAT_DEPLOYED: 'chat.deployed',
CHAT_UPDATED: 'chat.updated',
CHAT_DELETED: 'chat.deleted',
CREDENTIAL_CREATED: 'credential.created',
CREDENTIAL_UPDATED: 'credential.updated',
CREDENTIAL_DELETED: 'credential.deleted',
CREDENTIAL_RENAMED: 'credential.renamed',
CREDIT_PURCHASED: 'credit.purchased',
@@ -43,6 +46,7 @@ export const auditMock = {
DOCUMENT_UPDATED: 'document.updated',
DOCUMENT_DELETED: 'document.deleted',
ENVIRONMENT_UPDATED: 'environment.updated',
ENVIRONMENT_DELETED: 'environment.deleted',
FILE_UPLOADED: 'file.uploaded',
FILE_UPDATED: 'file.updated',
FILE_DELETED: 'file.deleted',
@@ -55,6 +59,7 @@ export const auditMock = {
FORM_UPDATED: 'form.updated',
FORM_DELETED: 'form.deleted',
INVITATION_ACCEPTED: 'invitation.accepted',
INVITATION_RESENT: 'invitation.resent',
INVITATION_REVOKED: 'invitation.revoked',
CONNECTOR_CREATED: 'connector.created',
CONNECTOR_UPDATED: 'connector.updated',
@@ -75,6 +80,7 @@ export const auditMock = {
NOTIFICATION_DELETED: 'notification.deleted',
OAUTH_DISCONNECTED: 'oauth.disconnected',
PASSWORD_RESET: 'password.reset',
PASSWORD_RESET_REQUESTED: 'password.reset_requested',
ORGANIZATION_CREATED: 'organization.created',
ORGANIZATION_UPDATED: 'organization.updated',
ORG_MEMBER_ADDED: 'org_member.added',
@@ -85,12 +91,15 @@ export const auditMock = {
ORG_INVITATION_REJECTED: 'org_invitation.rejected',
ORG_INVITATION_CANCELLED: 'org_invitation.cancelled',
ORG_INVITATION_REVOKED: 'org_invitation.revoked',
ORG_INVITATION_RESENT: 'org_invitation.resent',
PERMISSION_GROUP_CREATED: 'permission_group.created',
PERMISSION_GROUP_UPDATED: 'permission_group.updated',
PERMISSION_GROUP_DELETED: 'permission_group.deleted',
PERMISSION_GROUP_MEMBER_ADDED: 'permission_group_member.added',
PERMISSION_GROUP_MEMBER_REMOVED: 'permission_group_member.removed',
SCHEDULE_CREATED: 'schedule.created',
SCHEDULE_UPDATED: 'schedule.updated',
SCHEDULE_DELETED: 'schedule.deleted',
SKILL_CREATED: 'skill.created',
SKILL_UPDATED: 'skill.updated',
SKILL_DELETED: 'skill.deleted',
@@ -115,6 +124,7 @@ export const auditMock = {
WORKFLOW_DEPLOYMENT_REVERTED: 'workflow.deployment_reverted',
WORKFLOW_VARIABLES_UPDATED: 'workflow.variables_updated',
WORKSPACE_CREATED: 'workspace.created',
WORKSPACE_UPDATED: 'workspace.updated',
WORKSPACE_DELETED: 'workspace.deleted',
WORKSPACE_DUPLICATED: 'workspace.duplicated',
},
@@ -124,6 +134,7 @@ export const auditMock = {
BYOK_KEY: 'byok_key',
CHAT: 'chat',
CONNECTOR: 'connector',
CREDENTIAL: 'credential',
CREDENTIAL_SET: 'credential_set',
CUSTOM_TOOL: 'custom_tool',
DOCUMENT: 'document',