Skip to content

Commit 05bb323

Browse files
committed
rfc: streams role based access control
1 parent 3a0cede commit 05bb323

File tree

2 files changed

+166
-1
lines changed

2 files changed

+166
-1
lines changed

.github/workflows/deploy.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ on:
1111
- 'v*'
1212
paths-ignore:
1313
- 'docs/**'
14+
- 'rfc/**'
1415
- 'packages/docs/**'
1516

1617
jobs:
@@ -59,4 +60,4 @@ jobs:
5960
pnpm publish -r --filter @motiadev/stream-client-browser --no-git-checks --tag pre-release
6061
pnpm publish -r --filter @motiadev/stream-client-react --no-git-checks --tag pre-release
6162
pnpm publish -r --filter motia --no-git-checks --tag pre-release
62-
pnpm publish -r --filter @motiadev/test --no-git-checks --tag pre-release
63+
pnpm publish -r --filter @motiadev/test --no-git-checks --tag pre-release

rfc/2025-06-11-rbac-streams.md

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
# RFC: Role-Based Access Control for Motia Streams
2+
3+
**Author:** Sergio Marcelino
4+
**Status:** Ready
5+
**Created:** 2025-06-11
6+
**Target Release:** v0.23.0
7+
**Topic:** Streams, WebSockets, Authentication, RBAC
8+
9+
---
10+
11+
## Summary
12+
13+
This RFC proposes an RBAC mechanism for the Motia Streams feature. Currently, any client can subscribe to any stream, with no enforcement of access rules. This proposal introduces a pluggable authentication entry point and per-stream authorization checks to enable secure, context-aware stream subscriptions.
14+
15+
---
16+
17+
## Motivation
18+
19+
- Improve security by controlling stream access via authentication and authorization logic.
20+
- Provide flexibility for developers to define custom access policies.
21+
- Support anonymous access for public streams when desired.
22+
23+
---
24+
25+
## Guide-Level Explanation
26+
27+
### Client Message Format
28+
29+
Motia Client Library will send a message of type `authentication` with the token as soon as it connects to the server.
30+
31+
```json
32+
{
33+
"type": "authentication",
34+
"data": {
35+
"token": "string"
36+
}
37+
}
38+
```
39+
40+
The Websocket server will ultimately invoke the defined authentication function defined in the project.
41+
42+
### Defining the authentication function
43+
44+
Developers need to create a file called `/motia/stream-auth.ts` to define the authentication function.
45+
The file content should export an `authenticate` function that should receive only one parameter, the token, in string.
46+
47+
```typescript
48+
export async function authenticate(token: string): Promise<StreamAuthContext | null> {
49+
// returning null means the user is not authenticated and will be considered anonymous
50+
// anonymous users can still have access to streams depending on the logic
51+
return null
52+
}
53+
```
54+
55+
The function can return a `StreamAuthContext` object or `null` if the authentication fails.
56+
The file can also export a type called contextSchema using zod
57+
58+
```typescript
59+
export const contextSchema = z.object({
60+
userId: z.string(),
61+
userName: z.string(),
62+
userStatus: z.enum(['active', 'inactive']),
63+
projectIds: z.array(z.string()),
64+
})
65+
```
66+
67+
Motia framework will automatically create the `StreamAuthContext` object based on the `contextSchema` type inside `types.d.ts` file for the project.
68+
69+
```typescript
70+
interface StreamAuthContext {
71+
userId: string
72+
userName: string
73+
userStatus: 'active' | 'inactive'
74+
projectIds: string[]
75+
}
76+
```
77+
78+
### Validating user access to a stream
79+
80+
Here's an existing stream definition:
81+
82+
```typescript
83+
import { StreamConfig } from 'motia'
84+
import { z } from 'zod'
85+
86+
export const config: StreamConfig = {
87+
name: 'message',
88+
schema: z.object({
89+
message: z.string(),
90+
from: z.enum(['user', 'assistant']),
91+
status: z.enum(['created', 'pending', 'completed']),
92+
}),
93+
baseConfig: { storageType: 'default' },
94+
}
95+
```
96+
97+
Users will be able to control whoever has access to a stream subscription using the `checkAccess` function.
98+
99+
```typescript
100+
export const config: StreamConfig = {
101+
name: 'message',
102+
schema: z.object({
103+
message: z.string(),
104+
from: z.enum(['user', 'assistant']),
105+
status: z.enum(['created', 'pending', 'completed']),
106+
}),
107+
baseConfig: { storageType: 'default' },
108+
109+
/**
110+
* type Subscription = { groupId: string, itemId?: string }
111+
* type StreamAuthContext depends on the contextSchema defined in the stream-auth.ts file
112+
*
113+
* If this function is not defined, anonymous user has access to the stream
114+
*
115+
* Since we receive groupId and itemId, developers are able to give granular access to the stream
116+
*
117+
* @param subscription - The subscription context
118+
* @param authContext - The authentication context
119+
* @returns true if the user has access to the stream, false otherwise
120+
*/
121+
checkAccess: (subscription: Subscription, authContext?: StreamAuthContext): boolean => {
122+
return true
123+
},
124+
}
125+
```
126+
127+
### Flow of authentication in Motia Streams client
128+
129+
```mermaid
130+
sequenceDiagram
131+
participant Client
132+
participant Server
133+
participant AuthFunction
134+
135+
Client->>Server: Connect
136+
Server-->>Client: Connected
137+
Client->>Server: Send message (authenticate)
138+
Server->>AuthFunction: Authenticate
139+
AuthFunction-->>Server: Return result (context or null)
140+
Note over Server: Server stores auth result securely<br/>attached to the connection
141+
Server-->>Client: Returns ack message (success or error)
142+
```
143+
144+
### Flow of subscription in Motia Streams client
145+
146+
```mermaid
147+
sequenceDiagram
148+
participant Client
149+
participant Server
150+
participant Stream
151+
152+
Client->>Server: Send message (subscribe)
153+
Server->>StreamFunction: checkAccess(sub, authContext)
154+
StreamFunction-->>Server: Returns result (true/false)
155+
alt Access Granted
156+
Server->>Stream: getGroup(groupId)
157+
Stream-->>Server: Returns data
158+
Server-->>Client: Send sync event with data
159+
Note over Server: Server creates subscription
160+
else Access Denied
161+
Server-->>Client: Returns error message
162+
Note over Server: No subscription created
163+
end
164+
```

0 commit comments

Comments
 (0)