Goal: Route all geo writes through Auth Service, enforce content ownership via JWT email matching, and return SSE streams for real-time client feedback.
Context
Geo write operations (save/update/delete) currently go directly from Gateway to Geospatial Service. This is wrong because:
- Geospatial Service is an internal data service and should not be externally exposed
- No content ownership tracking — anyone with a valid JWT can modify/delete anyone’s content
- No real-time feedback to clients during multi-step write operations
Target Architecture
Public reads: Client → Gateway → Web Service → Geospatial Service
Authenticated writes: Client → Gateway (/auth/geo/**) → Auth Service (SSE) → Geospatial Service
Internal only: /geo/** no longer exposed through gateway
SSE Event Model
All write endpoints return Flux<ServerSentEvent<GeoSseEvent>> (text/event-stream):
{"step": "auth", "message": "Token validated, user: user@email.com", "terminal": false}
{"step": "ownership", "message": "Ownership verified", "terminal": false}
{"step": "forward", "message": "Forwarding to geospatial-service...", "terminal": false}
{"step": "accepted", "message": "Data accepted: youtubeContentID=xyz", "terminal": true}
Steps: auth → ownership (update/delete only) → forward → accepted|updated|deleted|error
Note: On error at any stage, a terminal error event is emitted and the stream completes.
Ownership Enforcement Flow
- Auth Service extracts email from JWT (
jwtUtil.extractUsername(token)) - Save: sets
ownerEmailon payload, forwards to Geospatial Service - Update/Delete: Auth Service GETs the document from Geospatial Service, compares
ownerEmailwith JWT email - Mismatch → SSE error event “You do not own this content” (403)
- Match → proceeds with write operation
Geospatial Service has no security logic — it just stores ownerEmail as a data field.
Implementation Phases
M1. ServiceClient — Add POST/PUT/DELETE raw methods
File: common-api/.../client/ServiceClient.java
| Method | Signature |
|---|---|
postRawReactive |
(baseUrl, path, body, responseType) → Mono<T> |
putRawReactive |
(baseUrl, path, body, responseType) → Mono<T> |
deleteRawReactive |
(baseUrl, path) → Mono<Void> (uses .toBodilessEntity()) |
*NonReactive wrappers |
postRawNonReactive(), putRawNonReactive(), deleteRawNonReactive() |
M2. Geospatial Service — ownerEmail field + bug fix
| File | Change |
|---|---|
.../entity/AdventureTubeData.java |
Add private String ownerEmail; with @Indexed, getter/setter, update constructor |
.../service/AdventureTubeDataService.java |
Fix deleteByYoutubeContentID bug (data.getYoutubeContentID() → data.getId()). Preserve ownerEmail in update() |
.../repository/AdventureTubeDataRepository.java |
Add findByOwnerEmail(String) |
M3. Auth Service — SSE geo write proxy with ownership
| File | Action |
|---|---|
.../model/sse/GeoSseEvent.java |
NEW — step, message, terminal fields |
.../service/GeoWriteService.java |
NEW — save(), update(), deleteById(), deleteByYoutubeContentID() returning Flux<ServerSentEvent<GeoSseEvent>> with ownership verification |
.../controller/GeoWriteController.java |
NEW — @RequestMapping("/auth/geo"), all endpoints produce text/event-stream |
.../exceptions/OwnershipViolationException.java |
NEW — extends BaseServiceException |
.../exceptions/code/AuthErrorCode.java |
Add: GEO_OWNERSHIP_VIOLATION (403), GEO_DATA_NOT_FOUND (404), GEO_SERVICE_ERROR (500) |
config-service/.../auth-service.yml |
Add geospatial-service.url, circuit breaker config for GEOSPATIAL-SERVICE |
GeoWriteService key pattern (Flux.concat for sequential SSE):
public Flux<ServerSentEvent<GeoSseEvent>> deleteById(String authorization, String id) {
return Mono.fromCallable(() -> extractEmail(authorization))
.flatMapMany(email -> {
var authEvent = Flux.just(sseEvent("auth", "Token validated, user: " + email, false));
var ownershipAndDelete = verifyOwnership(email, "/geo/data/" + id)
.flatMapMany(doc -> Flux.concat(
Flux.just(sseEvent("ownership", "Ownership verified", false)),
serviceClient.deleteRawReactive(geoServiceUrl, "/geo/data/" + id)
.then(Mono.just(sseEvent("deleted", "Content deleted successfully", true)))
.onErrorResume(e -> Mono.just(sseEvent("error", "Delete failed: " + e.getMessage(), true)))
.flux()
))
.onErrorResume(OwnershipViolationException.class, e ->
Flux.just(sseEvent("error", e.getMessage(), true)));
return Flux.concat(authEvent, ownershipAndDelete);
})
.onErrorResume(e -> Flux.just(sseEvent("error", "Authentication failed: " + e.getMessage(), true)));
}
M4. Gateway — Remove /geo/** route
File: gateway-service/.../config/GatewayConfig.java
- Remove:
.route("geo-service", r -> r.path("/geo/**")...) - Keep: geo-docs swagger route
/auth/geo/**already covered by existing/auth/**route- No
RouterValidatorchanges needed — not inopenEndPoints, so JWT is required
M5. Postman + Notion
- Update Postman URLs:
/geo/save→/auth/geo/save,/geo/data/{id}→/auth/geo/data/{id}, etc. - Update this Notion document with final architecture
Implementation Order
M1 and M2 can be done in parallel. M3 depends on both. M4 after M3. M5 last.
M1 (ServiceClient) ──┐
├──→ M3 (Auth Service SSE) → M4 (Gateway) → M5 (Postman/Notion)
M2 (Geospatial) ──┘
Key Design Decisions
- Auth Service uses
JsonNode(notAdventureTubeData) to avoid coupling to geospatial entity classes - Ownership enforced in Auth Service, not Geospatial Service — keeps geo as a simple internal data store
- SSE via
Flux.concat— each step emits before the next begins, giving real sequential progress terminal: trueon the last event signals clients to close the SSE connection- Legacy documents (no
ownerEmail) treated as unowned — ownership check fails POST /geo/savevia Kafka — SSE can only confirm “accepted by Kafka”, not “saved to MongoDB”
Verification
POST /auth/geo/savewith JWT → SSE:auth → forward → accepted. Check MongoDB forownerEmailDELETE /auth/geo/data/youtube/{id}with same user JWT → SSE:auth → ownership → deleted- Same DELETE with different user JWT → SSE:
auth → error(ownership violation) GET /web/geo/data→ still works (public read, no auth)GET /geo/datadirectly → 404 (route removed from gateway)- Check Zipkin traces:
gateway → auth-service → geospatial-service
