156 lines
6.5 KiB
Markdown
156 lines
6.5 KiB
Markdown
# Events
|
|
|
|
## Overview
|
|
|
|
The Identity Platform uses an **event-driven architecture** built on Redis Streams. Events decouple the platform from products and enable reliable, scalable, idempotent communication.
|
|
|
|
Each event is published to a Redis stream and includes:
|
|
- **event_type**: A namespaced identifier (e.g., `user.created`, `authentication.login`)
|
|
- **event_id**: A UUID that uniquely identifies THIS event occurrence
|
|
- **correlation_id**: A UUID that ties this event to a request chain (e.g., a login request may trigger `user.updated`, `session.created`, `authentication.login_success`)
|
|
- **timestamp**: ISO 8601 UTC
|
|
- **version**: Schema version of this event (semver: `1.0.0`)
|
|
- **data**: Event-specific payload (product-agnostic)
|
|
- **metadata**: Correlation info, source, IP, etc.
|
|
|
|
## Event Structure
|
|
|
|
```json
|
|
{
|
|
"event_id": "uuid",
|
|
"correlation_id": "uuid",
|
|
"event_type": "user.created",
|
|
"version": "1.0.0",
|
|
"timestamp": "2024-01-15T10:30:00.000Z",
|
|
"source": "identity-platform",
|
|
"data": {
|
|
"user_id": "uuid",
|
|
"email": "user@acme.com"
|
|
},
|
|
"metadata": {
|
|
"ip": "192.168.1.1",
|
|
"user_agent": "Mozilla/5.0...",
|
|
"request_id": "uuid"
|
|
}
|
|
}
|
|
```
|
|
|
|
## Event Types
|
|
|
|
### User Events
|
|
|
|
| Event Type | Version | Trigger | Data |
|
|
|-----------|---------|---------|------|
|
|
| `user.created` | `1.0.0` | New user registered | `user_id`, `email`, `phone`, `created_at` |
|
|
| `user.updated` | `1.0.0` | User profile updated | `user_id`, `changed_fields[]`, `updated_at` |
|
|
| `user.deleted` | `1.0.0` | User deleted (GDPR) | `user_id`, `deleted_at` |
|
|
| `user.disabled` | `1.0.0` | User status → suspended/banned | `user_id`, `status`, `reason` |
|
|
| `user.verified` | `1.0.0` | Email/phone verified | `user_id`, `channel` (email/phone), `verified_at` |
|
|
|
|
### Organization Events
|
|
|
|
| Event Type | Version | Trigger | Data |
|
|
|-----------|---------|---------|------|
|
|
| `organization.created` | `1.0.0` | New org created | `org_id`, `name`, `created_by` |
|
|
| `organization.updated` | `1.0.0` | Org details updated | `org_id`, `changed_fields[]`, `updated_at` |
|
|
|
|
### Membership Events
|
|
|
|
| Event Type | Version | Trigger | Data |
|
|
|-----------|---------|---------|------|
|
|
| `membership.created` | `1.0.0` | User added to org | `user_id`, `org_id`, `role`, `status` |
|
|
| `membership.updated` | `1.0.0` | Role/status changed | `user_id`, `org_id`, `changed_fields[]` |
|
|
| `membership.removed` | `1.0.0` | User removed from org | `user_id`, `org_id`, `removed_by` |
|
|
|
|
### Authentication Events
|
|
|
|
| Event Type | Version | Trigger | Data |
|
|
|-----------|---------|---------|------|
|
|
| `authentication.login` | `1.0.0` | Successful login | `user_id`, `session_id`, `product_key`, `ip`, `device` |
|
|
| `authentication.logout` | `1.0.0` | Successful logout | `user_id`, `session_id`, `product_key` |
|
|
| `authentication.failed` | `1.0.0` | Failed login attempt | `user_id` (if known), `email`, `ip`, `reason` |
|
|
| `authentication.mfa_required` | `1.0.0` | MFA step reached | `user_id`, `method` |
|
|
| `authentication.mfa_verified` | `1.0.0` | MFA passed | `user_id`, `method` |
|
|
|
|
### Session Events
|
|
|
|
| Event Type | Version | Trigger | Data |
|
|
|-----------|---------|---------|------|
|
|
| `session.created` | `1.0.0` | New session | `session_id`, `user_id`, `product_key`, `session_type` |
|
|
| `session.expired` | `1.0.0` | Session timed out | `session_id`, `user_id`, `reason` |
|
|
| `session.revoked` | `1.0.0` | Session revoked | `session_id`, `user_id`, `product_key`, `reason` |
|
|
|
|
### Security Events
|
|
|
|
| Event Type | Version | Trigger | Data |
|
|
|-----------|---------|---------|------|
|
|
| `security.event` | `1.0.0` | Any security event logged | `event_type`, `severity`, `user_id`, `ip`, `user_agent` |
|
|
| `security.token_revoked` | `1.0.0` | Token blacklisted | `user_id`, `token_type`, `reason` |
|
|
|
|
## Event Properties
|
|
|
|
### idempotency
|
|
Every event has a unique `event_id` (UUID). Consumers must deduplicate by `event_id`. This allows safe retry of failed deliveries without duplicate processing.
|
|
|
|
### correlation
|
|
Events triggered by the same request share a `correlation_id`. Example:
|
|
```
|
|
correlation_id: req_abc123
|
|
↓ triggers
|
|
event: user.created (user_id=123)
|
|
event: session.created (user_id=123, session_id=456)
|
|
event: authentication.login (user_id=123, session_id=456)
|
|
```
|
|
|
|
### versioning
|
|
Event payloads are versioned with semver. When the schema changes in a backward-compatible way (adding optional fields), the version increments the PATCH. Breaking changes (removing fields, changing types) increment MAJOR. Consumers MUST check version before processing.
|
|
|
|
## Delivery Guarantees
|
|
|
|
| Guarantee | Mechanism |
|
|
|-----------|-----------|
|
|
| **At-least-once** | Redis Streams with consumer groups |
|
|
| **Deduplication** | event_id uniqueness |
|
|
| **Ordering** | Per-stream ordering preserved |
|
|
| **Durability** | Redis AOF persistence |
|
|
| **Retry** | Consumer must ACK (XACK); unacked claimed by other consumers |
|
|
| **DLQ** | Failed messages (max retries exceeded) routed to `events.dlq.stream` |
|
|
|
|
## Consumer Contract
|
|
|
|
Products consuming events MUST:
|
|
1. Use consumer groups per product + service combination
|
|
2. Acknowledge (XACK) every processed message
|
|
3. Idempotently handle duplicates via `event_id`
|
|
4. Validate `version` before processing (reject unknown MAJOR versions)
|
|
5. Route failed messages to DLQ after max retries
|
|
6. Preserve correlation_id across their own downstream events
|
|
|
|
## Internal vs External Events
|
|
|
|
| Channel | Audience | Persistence | Delivery |
|
|
|---------|----------|-------------|----------|
|
|
| **SecurityEvent** (DB table) | Internal admin/audit | Permanent (2 years) | Queryable via API |
|
|
| **Redis Streams** | Products + internal services | 7 days | Consumer group |
|
|
| **Audit log** (SecurityEvent) | Compliance | Permanent | Archive |
|
|
|
|
The `SecurityEvent` model in PostgreSQL is the internal audit log. These are NOT the same as Redis Stream events but are correlated:
|
|
|
|
```
|
|
User logs in
|
|
→ SecurityEvent (DB: login_success, severity=info) [audit trail]
|
|
→ Redis Stream event (user.created, authentication.login) [product notifications]
|
|
```
|
|
|
|
## Stream Names
|
|
|
|
| Stream | Events |
|
|
|--------|--------|
|
|
| `events.user` | user.created, user.updated, user.deleted, user.disabled, user.verified |
|
|
| `events.organization` | organization.created, organization.updated |
|
|
| `events.membership` | membership.created, membership.updated, membership.removed |
|
|
| `events.authentication` | authentication.login, authentication.logout, authentication.failed |
|
|
| `events.session` | session.created, session.expired, session.revoked |
|
|
| `events.security` | security.event, security.token_revoked |
|
|
| `events.dlq` | Dead-letter queue for failed deliveries |
|