The Events service collects and persists user and organization events for audit logging, compliance reporting, and security monitoring.
Overview
The Events service provides:
Event Collection : Client-side event submission endpoint
Event Storage : Persisting events to database and Azure Table Storage
Event Types : User actions, cipher operations, organization activities
Compliance Support : Audit trails for regulatory requirements
Security Monitoring : Track suspicious activities and access patterns
Architecture
Configuration
From src/Events/Startup.cs:26:
public void ConfigureServices ( IServiceCollection services )
{
// Settings
var globalSettings = services . AddGlobalSettingsServices ( Configuration , Environment );
// Data Protection
services . AddCustomDataProtectionServices ( Environment , globalSettings );
// Repositories
services . AddDatabaseRepositories ( globalSettings );
// Context
services . AddScoped < ICurrentContext , CurrentContext >();
// Authentication
services . AddIdentityAuthenticationServices ( globalSettings , Environment , config =>
{
config . AddPolicy ( "Application" , policy =>
{
policy . RequireAuthenticatedUser ();
policy . RequireClaim ( JwtClaimTypes . AuthenticationMethod , "Application" , "external" );
policy . RequireClaim ( JwtClaimTypes . Scope , ApiScopes . Api );
});
});
// Event Services
var usingServiceBusAppCache = CoreHelpers . SettingHasValue (
globalSettings . ServiceBus . ConnectionString ) &&
CoreHelpers . SettingHasValue ( globalSettings . ServiceBus . ApplicationCacheTopicName );
services . AddScoped < IApplicationCacheService , FeatureRoutedCacheService >();
if ( usingServiceBusAppCache )
{
services . AddSingleton < IVCurrentInMemoryApplicationCacheService ,
InMemoryServiceBusApplicationCacheService > ();
}
else
{
services . AddSingleton < IVCurrentInMemoryApplicationCacheService ,
InMemoryApplicationCacheService > ();
}
services . AddEventWriteServices ( globalSettings );
services . AddScoped < IEventService , EventService >();
// Event integrations
services . AddDistributedCache ( globalSettings );
services . AddRabbitMqListeners ( globalSettings );
}
Event Collection
Collect Endpoint
From src/Events/Controllers/CollectController.cs:16:
[ Route ( "collect" )]
[ Authorize ( "Application" )]
public class CollectController : Controller
{
[ HttpPost ]
public async Task < IActionResult > Post ([ FromBody ] IEnumerable < EventModel > model )
{
if ( model == null || ! model . Any ())
{
return new BadRequestResult ();
}
foreach ( var eventModel in model )
{
switch ( eventModel . Type )
{
case EventType . User_ClientExportedVault :
await _eventService . LogUserEventAsync (
_currentContext . UserId . Value ,
eventModel . Type ,
eventModel . Date );
break ;
case EventType . Cipher_ClientViewed :
case EventType . Cipher_ClientAutofilled :
case EventType . Cipher_ClientCopiedPassword :
// Validate cipher access and log event
var cipher = await _cipherRepository . GetByIdAsync (
eventModel . CipherId . Value ,
_currentContext . UserId . Value );
if ( cipher != null )
{
cipherEvents . Add ( new Tuple < Cipher , EventType , DateTime ?>(
cipher , eventModel . Type , eventModel . Date ));
}
break ;
case EventType . Organization_ClientExportedVault :
var organization = await _organizationRepository . GetByIdAsync (
eventModel . OrganizationId . Value );
if ( organization != null )
{
await _eventService . LogOrganizationEventAsync (
organization , eventModel . Type , eventModel . Date );
}
break ;
}
}
// Batch process cipher events
if ( cipherEvents . Any ())
{
foreach ( var eventsBatch in cipherEvents . Chunk ( 50 ))
{
await _eventService . LogCipherEventsAsync ( eventsBatch );
}
}
return new OkResult ();
}
}
Endpoint : POST /collect
Authentication : Required (Bearer token)
Request Body :
[
{
"type" : 1000 ,
"cipherId" : "guid" ,
"date" : "2024-03-10T12:00:00Z"
},
{
"type" : 1100 ,
"organizationId" : "guid" ,
"date" : "2024-03-10T12:01:00Z"
}
]
Event Types
User Events
Login/Logout User authentication events
Vault Export Vault data export operations
Settings Changes User profile and settings modifications
2FA Events Two-factor authentication changes
Cipher Events
From src/Events/Controllers/CollectController.cs:80:
EventType . Cipher_ClientViewed
EventType . Cipher_ClientAutofilled
EventType . Cipher_ClientCopiedPassword
EventType . Cipher_ClientCopiedHiddenField
EventType . Cipher_ClientCopiedCardCode
EventType . Cipher_ClientToggledPasswordVisible
EventType . Cipher_ClientToggledCardCodeVisible
EventType . Cipher_ClientToggledHiddenFieldVisible
These events track vault item interactions:
Viewing credentials
Auto-filling forms
Copying sensitive data
Revealing hidden fields
Organization Events
EventType . Organization_ClientExportedVault
EventType . Organization_ItemOrganization_Accepted
EventType . Organization_ItemOrganization_Declined
EventType . Organization_AutoConfirmEnabled_Admin
EventType . Organization_AutoConfirmDisabled_Admin
Administrative Events
User invitations
Policy changes
Collection modifications
Group management
Permission changes
Event Validation
The service validates event submissions:
Authentication
Verify user is authenticated via Bearer token
Authorization
Check user has access to referenced resources (ciphers, organizations)
Batching
Group cipher events for efficient processing (50 per batch)
Storage
Persist events to database and Azure Table Storage
Application Cache
From src/Events/Startup.cs:56:
The Events service uses application cache for organization abilities:
services . AddScoped < IApplicationCacheService , FeatureRoutedCacheService >();
if ( usingServiceBusAppCache )
{
services . AddSingleton < IVCurrentInMemoryApplicationCacheService ,
InMemoryServiceBusApplicationCacheService > ();
}
else
{
services . AddSingleton < IVCurrentInMemoryApplicationCacheService ,
InMemoryApplicationCacheService > ();
}
Cache synchronization via:
Azure Service Bus : Multi-instance deployments
In-Memory : Single instance deployments
From src/Events/Startup.cs:80:
if ( usingServiceBusAppCache )
{
services . AddHostedService < Core . HostedServices . ApplicationCacheHostedService >();
}
Event Integrations
The service supports event-driven integrations via RabbitMQ:
From src/Events/Startup.cs:86:
services . AddDistributedCache ( globalSettings );
services . AddRabbitMqListeners ( globalSettings );
Integrations include:
Slack : Organization event notifications
Microsoft Teams : Webhook notifications
Custom Webhooks : User-defined integrations
Middleware Pipeline
From src/Events/Startup.cs:90:
public void Configure ( IApplicationBuilder app )
{
// Security headers
app . UseMiddleware < SecurityHeadersMiddleware >();
// Forwarded headers (self-hosted)
if ( globalSettings . SelfHosted )
{
app . UseForwardedHeaders ( globalSettings );
}
// Default middleware
app . UseDefaultMiddleware ( env , globalSettings );
// Routing
app . UseRouting ();
// CORS
app . UseCors ( policy => policy
. SetIsOriginAllowed ( o => CoreHelpers . IsCorsOriginAllowed ( o , globalSettings ))
. AllowAnyMethod ()
. AllowAnyHeader ()
. AllowCredentials ());
// Authentication & Authorization
app . UseAuthentication ();
app . UseAuthorization ();
// Current context
app . UseMiddleware < CurrentContextMiddleware >();
// Controllers
app . UseEndpoints ( endpoints => endpoints . MapDefaultControllerRoute ());
}
Event Storage
Events are stored in two locations:
SQL Database
Primary event storage with relational queries:
CREATE TABLE [dbo].[Event] (
[Id] UNIQUEIDENTIFIER NOT NULL ,
[Type] INT NOT NULL ,
[UserId] UNIQUEIDENTIFIER ,
[OrganizationId] UNIQUEIDENTIFIER ,
[CipherId] UNIQUEIDENTIFIER ,
[CollectionId] UNIQUEIDENTIFIER ,
[PolicyId] UNIQUEIDENTIFIER ,
[GroupId] UNIQUEIDENTIFIER ,
[OrganizationUserId] UNIQUEIDENTIFIER ,
[ActingUserId] UNIQUEIDENTIFIER ,
[DeviceType] SMALLINT ,
[IpAddress] VARCHAR ( 50 ),
[Date] DATETIME2 ( 7 ) NOT NULL ,
CONSTRAINT [PK_Event] PRIMARY KEY CLUSTERED ([Id] ASC )
);
Azure Table Storage
Scalable event storage for high-volume events:
PartitionKey : OrganizationId or UserId
RowKey : { Date : yyyyMMddHHmmss } _ { EventId }
Benefits:
High throughput
Cost-effective storage
Efficient time-range queries
Client Integration
JavaScript Example
const events = [
{
type: 1000 , // Cipher_ClientViewed
cipherId: '3fa85f64-5717-4562-b3fc-2c963f66afa6' ,
date: new Date (). toISOString ()
},
{
type: 1003 , // Cipher_ClientAutofilled
cipherId: '3fa85f64-5717-4562-b3fc-2c963f66afa6' ,
date: new Date (). toISOString ()
}
];
await fetch ( 'https://events.bitwarden.com/collect' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ accessToken } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ( events )
});
Mobile Example (C#)
var events = new List < EventModel >
{
new EventModel
{
Type = EventType . Cipher_ClientViewed ,
CipherId = cipherId ,
Date = DateTime . UtcNow
}
};
await _apiService . PostEventsCollectAsync ( events );
Deployment
Environment Variables
GLOBALSETTINGS__SELFHOSTED = true
GLOBALSETTINGS__SQLSERVER__CONNECTIONSTRING =< connection >
GLOBALSETTINGS__EVENTS__CONNECTIONSTRING =< azure_table >
GLOBALSETTINGS__SERVICEBUS__CONNECTIONSTRING =< service_bus >
GLOBALSETTINGS__SERVICEBUS__APPLICATIONCACHETOPICNAME =< topic >
Docker
docker run -d \
--name bitwarden-events \
-p 5005:5000 \
-e GLOBALSETTINGS__SelfHosted= true \
-e GLOBALSETTINGS__SqlServer__ConnectionString="<connection>" \
bitwarden/events:latest
Batch Processing Cipher events processed in batches of 50 for efficiency
Async Operations Non-blocking event persistence
Table Storage Azure Table for high-volume event storage
Cache Sync Service Bus for distributed cache invalidation
Security Considerations
Event data contains sensitive information. Ensure proper access controls and encryption.
Authentication : All requests require valid Bearer tokens
Authorization : Users can only log events for resources they access
Validation : Cipher and organization access validated before logging
IP Tracking : Client IP addresses captured for security analysis
Audit Trail : Events are immutable once stored
Compliance Support
The Events service supports compliance requirements:
GDPR : User activity tracking and data access logs
SOC 2 : Audit trails for security controls
HIPAA : Access logging for protected health information
ISO 27001 : Information security event management
Querying Events
Organization administrators can query events via the API service:
curl -X GET "https://api.bitwarden.com/organizations/{orgId}/events" \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json"
Query parameters:
start: Start date (ISO 8601)
end: End date (ISO 8601)
actingUserId: Filter by user
itemId: Filter by resource (cipher, collection, etc.)