1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
|
# JMAP Standards Compliance Improvements
**Date:** August 15, 2025
**Based on:** JMAP Crash Course and RFC 8620
## Summary of Improvements Made
After reviewing the JMAP crash course, I've implemented several key improvements to make our JCHAT server more standards-compliant with RFC 8620.
## Core Improvements
### 1. Enhanced Request/Response Structure Validation
**Previous:**
- Basic validation of `using` and `methodCalls`
- Limited error handling
**Improved:**
```erlang
% Now validates complete JMAP request structure
validate_jmap_request(#{<<"using">> := Using, <<"methodCalls">> := MethodCalls}) ->
case validate_using_array(Using) of
ok -> validate_method_calls_array(MethodCalls);
Error -> Error
end.
% Ensures method call IDs are unique within request
validate_unique_call_ids(MethodCalls) ->
CallIds = [CallId || [_, _, CallId] <- MethodCalls],
case length(CallIds) =:= length(lists:usort(CallIds)) of
true -> ok;
false -> {error, "Method call IDs must be unique"}
end.
```
### 2. Proper Content-Type Handling
**Previous:**
- Loose content type checking
**Improved:**
```erlang
validate_content_type(Req) ->
case cowboy_req:header(<<"content-type">>, Req) of
<<"application/json", _/binary>> -> ok;
_ -> {error, invalid_content_type}
end.
```
**Now returns proper content-type:**
```
Content-Type: application/json; charset=utf-8
```
### 3. Comprehensive Error Response Format
**Previous:**
- Basic error types
**Improved:**
- All standard JMAP error types from RFC 8620:
```erlang
format_error(invalid_arguments) -> #{type => <<"invalidArguments">>};
format_error(account_not_found) -> #{type => <<"accountNotFound">>};
format_error(forbidden) -> #{type => <<"forbidden">>};
format_error(invalid_result_reference) -> #{type => <<"invalidResultReference">>};
format_error(anchor_not_found) -> #{type => <<"anchorNotFound">>};
format_error(unsupported_sort) -> #{type => <<"unsupportedSort">>};
format_error(unsupported_filter) -> #{type => <<"unsupportedFilter">>};
format_error(cannot_calculate_changes) -> #{type => <<"cannotCalculateChanges">>};
format_error(too_large) -> #{type => <<"tooLarge">>};
format_error(rate_limited) -> #{type => <<"rateLimited">>};
format_error(state_mismatch) -> #{type => <<"stateMismatch">>};
% ... and more
```
### 4. Authentication Framework
**Previous:**
- Hardcoded account ID
**Improved:**
```erlang
% Extract Bearer token from Authorization header
extract_auth_token(<<"Bearer ", Token/binary>>) -> {ok, Token};
% Session endpoint now handles authentication
authenticate_session_request(<<"Bearer ", _Token/binary>>) ->
{ok, <<"demo@example.com">>}; % In production, validate token
% Account ID determination from auth context
determine_account_id(_AuthHeader) ->
<<"default">>. % In production, extract from validated JWT token
```
### 5. Method Call Validation
**Added comprehensive method call structure validation:**
```erlang
validate_method_call([Method, Args, CallId])
when is_binary(Method), is_map(Args), is_binary(CallId) ->
case validate_method_name(Method) of
true -> ok;
false -> {error, invalid_method_name}
end.
validate_method_name(Method) ->
case binary:split(Method, <<"/">>) of
[Type, Operation] when byte_size(Type) > 0, byte_size(Operation) > 0 ->
validate_method_chars(Method);
_ -> false
end.
```
### 6. Session Object Improvements
**Previous:**
- Static session state
- Fixed username
**Improved:**
```erlang
build_session_object(Username) ->
#{
<<"capabilities">> => #{...},
<<"accounts">> => #{
<<"default">> => #{
<<"name">> => Username, % Dynamic username
<<"isPersonal">> => true,
<<"isReadOnly">> => false,
<<"accountCapabilities">> => #{...}
}
},
<<"username">> => Username,
<<"state">> => jchat_utils:generate_id() % Unique session state
}.
```
## Standards Compliance Checklist
### ✅ Now Compliant
1. **Request Structure Validation**
- ✅ Validates `using` array is non-empty and contains only strings
- ✅ Validates `methodCalls` array structure
- ✅ Ensures method call IDs are unique within request
- ✅ Proper method name format validation (`Type/operation`)
2. **Content-Type Handling**
- ✅ Validates `application/json` content type
- ✅ Returns `application/json; charset=utf-8` in responses
3. **Error Handling**
- ✅ All standard JMAP error types implemented
- ✅ Proper error response structure
- ✅ URN-based error type identifiers
4. **Session Endpoint**
- ✅ Authentication framework in place
- ✅ Dynamic session state generation
- ✅ Proper CORS handling
- ✅ Method validation (GET/OPTIONS only)
5. **HTTP Method Handling**
- ✅ Proper OPTIONS preflight support
- ✅ Method not allowed responses
- ✅ Authorization header extraction
### ⚠️ Still Needs Work
1. **Real Authentication**
- Currently returns demo session regardless of auth
- Need JWT token validation implementation
- Need user lookup from token
2. **Account Management**
- Still using hardcoded "default" account
- Need proper account ID extraction from auth context
- Need multi-account support
3. **State Management**
- Session state is generated but not tracked
- Need persistent state tracking per account/object type
- Need state validation in method calls
4. **Binary Data**
- Upload/download endpoints defined but not implemented
- Need blob storage system
- Need proper multipart handling
## Example: Standards-Compliant Request Flow
### 1. Client Session Request
```http
GET /jmap/session HTTP/1.1
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...
```
### 2. Server Session Response
```json
{
"capabilities": {
"urn:ietf:params:jmap:core": {
"maxSizeUpload": 50000000,
"maxConcurrentRequests": 4,
"maxCallsInRequest": 16
},
"urn:ietf:params:jmap:chat": {
"maxMessageLength": 10000,
"maxParticipantsPerConversation": 100
}
},
"accounts": {
"default": {
"name": "user@example.com",
"isPersonal": true,
"isReadOnly": false,
"accountCapabilities": {
"urn:ietf:params:jmap:core": {},
"urn:ietf:params:jmap:chat": {}
}
}
},
"primaryAccounts": {
"urn:ietf:params:jmap:chat": "default"
},
"username": "user@example.com",
"apiUrl": "http://localhost:8080/jmap/api",
"state": "b3e91c7a-8f2d-4c1e-9a5b-7d3e2f1c8b9a"
}
```
### 3. Client API Request
```http
POST /jmap/api HTTP/1.1
Content-Type: application/json; charset=utf-8
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...
{
"using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:chat"],
"methodCalls": [
["Conversation/query", {"accountId": "default"}, "c1"],
["Message/query", {"accountId": "default", "filter": {"conversationId": "#c1"}}, "c2"]
]
}
```
### 4. Server API Response
```json
{
"methodResponses": [
["Conversation/query", {"accountId": "default", "ids": ["conv1", "conv2"]}, "c1"],
["Message/query", {"accountId": "default", "ids": ["msg1", "msg2", "msg3"]}, "c2"]
],
"sessionState": "b3e91c7a-8f2d-4c1e-9a5b-7d3e2f1c8b9a"
}
```
## Testing the Improvements
### Manual Testing
```bash
# Test session endpoint with proper headers
curl -X GET http://localhost:8080/jmap/session \
-H "Authorization: Bearer test-token"
# Test API endpoint with proper content type
curl -X POST http://localhost:8080/jmap/api \
-H "Content-Type: application/json; charset=utf-8" \
-H "Authorization: Bearer test-token" \
-d '{
"using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:chat"],
"methodCalls": [["Core/echo", {"test": "data"}, "c1"]]
}'
# Test error handling with invalid content type
curl -X POST http://localhost:8080/jmap/api \
-H "Content-Type: text/plain" \
-d '{"invalid": "request"}'
```
### Expected Error Response
```json
{
"type": "urn:ietf:params:jmap:error:notJSON",
"status": 400,
"detail": "Content-Type must be application/json"
}
```
## Next Steps for Full Compliance
1. **Implement Real Authentication**
- JWT token validation
- User database lookup
- Session management
2. **Add Binary Data Support**
- Upload endpoint implementation
- Download endpoint implementation
- Blob storage in Mnesia
3. **Enhanced State Management**
- Per-account state tracking
- State validation in method calls
- Proper change detection
4. **Method Enhancements**
- Better property filtering
- Pagination support
- Advanced query features
These improvements move us significantly closer to full JMAP RFC 8620 compliance while maintaining our clean architecture and extensibility.
|