aboutsummaryrefslogtreecommitdiff
path: root/JMAP_IMPROVEMENTS.md
diff options
context:
space:
mode:
Diffstat (limited to 'JMAP_IMPROVEMENTS.md')
-rw-r--r--JMAP_IMPROVEMENTS.md313
1 files changed, 313 insertions, 0 deletions
diff --git a/JMAP_IMPROVEMENTS.md b/JMAP_IMPROVEMENTS.md
new file mode 100644
index 0000000..7360ea3
--- /dev/null
+++ b/JMAP_IMPROVEMENTS.md
@@ -0,0 +1,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.