docs: add events system pages (#4294)

* feat: events docs

* fix: make the conditions optional in casl check

* Update backend/src/lib/api-docs/constants.ts

* Update backend/src/lib/api-docs/constants.ts

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* Update docs/docs.json

* docs: content

* fix: pr changes

* feat: improve docs

* chore: remove recursive

* fix: pr changes

* fix: change

* fix: pr changes

* fix: pr changes

* fix: change
This commit is contained in:
Sid
2025-08-06 00:43:41 +05:30
committed by GitHub
parent 4e3613ac6e
commit c1fb5d8998
30 changed files with 174 additions and 24 deletions

View File

@@ -13,11 +13,9 @@ const AUTH_REFRESH_INTERVAL = 60 * 1000;
const HEART_BEAT_INTERVAL = 15 * 1000;
export const sseServiceFactory = (bus: TEventBusService, redis: Redis) => {
let heartbeatInterval: NodeJS.Timeout | null = null;
const clients = new Set<EventStreamClient>();
heartbeatInterval = setInterval(() => {
const heartbeatInterval = setInterval(() => {
for (const client of clients) {
if (client.stream.closed) continue;
void client.ping();

View File

@@ -66,15 +66,24 @@ export type EventStreamClient = {
};
export function createEventStreamClient(redis: Redis, options: IEventStreamClientOpts): EventStreamClient {
const rules = options.registered.map((r) => ({
subject: options.type,
action: "subscribe",
conditions: {
eventType: r.event,
secretPath: r.conditions?.secretPath ?? "/",
environment: r.conditions?.environmentSlug
}
}));
const rules = options.registered.map((r) => {
const secretPath = r.conditions?.secretPath;
const hasConditions = r.conditions?.environmentSlug || r.conditions?.secretPath;
return {
subject: options.type,
action: "subscribe",
conditions: {
eventType: r.event,
...(hasConditions
? {
environment: r.conditions?.environmentSlug ?? "",
secretPath: { $glob: secretPath }
}
: {})
}
};
});
const id = `sse-${nanoid()}`;
const control = new AbortController();

View File

@@ -70,7 +70,8 @@ export enum ApiDocsTags {
SecretScanning = "Secret Scanning",
OidcSso = "OIDC SSO",
SamlSso = "SAML SSO",
LdapSso = "LDAP SSO"
LdapSso = "LDAP SSO",
Events = "Event Subscriptions"
}
export const GROUPS = {
@@ -2872,3 +2873,10 @@ export const LdapSso = {
caCert: "The CA certificate to use when verifying the LDAP server certificate."
}
};
export const EventSubscriptions = {
SUBSCRIBE_PROJECT_EVENTS: {
projectId: "The ID of the project to subscribe to events for.",
register: "List of events you want to subscribe to"
}
};

View File

@@ -7,6 +7,7 @@ import { ActionProjectType, ProjectType } from "@app/db/schemas";
import { getServerSentEventsHeaders } from "@app/ee/services/event/event-sse-stream";
import { EventRegisterSchema } from "@app/ee/services/event/types";
import { ProjectPermissionSecretActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { ApiDocsTags, EventSubscriptions } from "@app/lib/api-docs";
import { BadRequestError, ForbiddenRequestError, RateLimitError } from "@app/lib/errors";
import { readLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
@@ -20,10 +21,14 @@ export const registerEventRouter = async (server: FastifyZodProvider) => {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.Events],
description: "Subscribe to project events",
body: z.object({
projectId: z.string().trim(),
register: z.array(EventRegisterSchema).max(10)
})
projectId: z.string().trim().describe(EventSubscriptions.SUBSCRIBE_PROJECT_EVENTS.projectId),
register: z.array(EventRegisterSchema).min(1).max(10)
}),
produces: ["text/event-stream"]
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req, reply) => {
@@ -75,13 +80,15 @@ export const registerEventRouter = async (server: FastifyZodProvider) => {
}
req.body.register.forEach((r) => {
const fields = {
environment: r.conditions?.environmentSlug ?? "",
secretPath: r.conditions?.secretPath ?? "/",
eventType: r.event
};
const allowed = info.permission.can(
ProjectPermissionSecretActions.Subscribe,
subject(ProjectPermissionSub.Secrets, {
environment: r.conditions?.environmentSlug ?? "",
secretPath: r.conditions?.secretPath ?? "/",
eventType: r.event
})
subject(ProjectPermissionSub.Secrets, fields)
);
if (!allowed) {
@@ -89,9 +96,9 @@ export const registerEventRouter = async (server: FastifyZodProvider) => {
name: "PermissionDenied",
message: `You are not allowed to subscribe on secrets`,
details: {
event: r.event,
environmentSlug: r.conditions?.environmentSlug,
secretPath: r.conditions?.secretPath ?? "/"
event: fields.eventType,
environmentSlug: fields.environment,
secretPath: fields.secretPath
}
});
}

View File

@@ -0,0 +1,4 @@
---
title: "Project Events"
openapi: "POST /api/v1/events/subscribe/project-events"
---

View File

@@ -41,6 +41,8 @@
"group": "Platform Reference",
"pages": [
"documentation/platform/organization",
"documentation/platform/event-subscriptions",
"documentation/platform/folder",
{
"group": "Projects",
"pages": [
@@ -764,6 +766,10 @@
"group": "Admin",
"pages": ["api-reference/endpoints/admin/bootstrap-instance"]
},
{
"group": "Events",
"pages": ["api-reference/endpoints/events/project-events"]
},
{
"group": "Identities",
"pages": [

View File

@@ -0,0 +1,118 @@
---
title: "Event Subscriptions"
sidebarTitle: "Events"
description: "Subscribe to events in Infisical for real-time updates"
---
<Info>
**Note:** Event Subscriptions is a paid feature. - **Infisical Cloud users:** Event Subscriptions is available under
the **Enterprise Tier**. - **Self-Hosted Infisical:** Please contact [sales@infisical.com](mailto:sales@infisical.com)
to purchase an enterprise license.
</Info>
Event Subscriptions in Infisical allow you to receive real-time notifications when specific actions occur within your account or organization. These notifications include changes to secrets, users, teams, and many more **coming soon**.
## How It Works
- Server receives message over pubsub connection indicating changes have occurred
- Server processes the change notification
- Updated data is synchronized across all connected Infisical instances
- Client applications receive real-time updates through [Server-Sent Events (SSE)](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events)
- All servers maintain consistent state without manual intervention
This ensures your infrastructure stays up-to-date automatically, without requiring restarts or manual synchronization.
<Note>
Event Subscriptions are designed for real-time communication and do not include persistence or replay
capabilities—events are delivered once and are not stored for future retrieval.
</Note>
## Supported Resources
You can currently subscribe to notifications for the following resources and event types:
- **Secrets**
- `secret:created`: Triggered when a secret is created
- `secret:updated`: Triggered when a secret is updated
- `secret:deleted`: Triggered when a secret is deleted
## Permissions Setup
To receive events on a supported resource, the identity must have `Subscribe` action permission on that resource.
Follow these steps to set up the necessary permissions:
<Steps>
<Step title="Select a project and copy the Project ID">
![Select Project](/images/platform/events/select-project.png)
On your project page, open **Project Settings** from the sidebar.
In the Project name section, click **Copy Project ID** to copy your Project ID, or extract it from the URL:
`https://app.infisical.com/project/<your_project_id>/settings`
</Step>
<Step title="Navigate to Access Management and open Project Roles">
![Project Detail](/images/platform/events/project-detail.png) ![Project
Access](/images/platform/events/project-access.png) Navigate to **Access Management**, then select **Project Roles**.
</Step>
<Step title="Select an existing role or create a new one">
![Project Role](/images/platform/events/project-role.png) You can either edit an existing role or create a new role
for event subscriptions.
</Step>
<Step title="Assign policies to the role">
![Role Detail](/images/platform/events/role-detail.png) Select the specific resources that the role should have access
to. ![Add policy](/images/platform/events/add-policy.png)
</Step>
<Step title="Enable the Subscribe action in permissions">
![Policy setting](/images/platform/events/policy-setting.png)
Ensure the **Subscribe** action is selected for the relevant resources and events.
## Conditions
By default, the role will have access to all events for the selected resources in this project.
<AccordionGroup>
<Accordion title="Full Access">
![Policy setting](/images/platform/events/access-full.png)
</Accordion>
<Accordion title="Path Prefix">
![Policy setting](/images/platform/events/access-path.png)
</Accordion>
<Accordion title="Environment">
![Policy setting](/images/platform/events/access-dev.png)
</Accordion>
</AccordionGroup>
</Step>
</Steps>
## Getting Started
Currently, events are only available via [API](/api-reference/endpoints/events) but will soon be available in our SDKs, Kubernetes Operator, and more.
### API Usage
You need an auth token to use this API. To get an authentication token, follow the authentication guide for one of our supported auth methods from the [machine identities documentation](/documentation/platform/identities/machine-identities#authentication-methods).
#### Creating a Subscription
![Postman Subscription](/images/platform/events/postman-subscribe.png)
**Request Parameters:**
- `projectId`: Project whose events you want to subscribe to
- `register`: List of event filters
- `conditions`: Conditions to filter events on
- `environmentSlug`: Project environment
- `secretPath`: Path of the secrets
![Postman Subscription Response](/images/platform/events/postman-sse-response.png)
The subscribe endpoint responds with a `text/event-stream` content type to initiate SSE streaming.
For more specific details, please refer to our [API Reference](/api-reference/endpoints/events).

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 476 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 747 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 910 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 531 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 401 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 484 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 397 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 734 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 441 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 735 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 697 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 704 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 879 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 858 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 866 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 895 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 834 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 678 KiB