Skip to content

Commit df3877e

Browse files
authored
Merge pull request #65 from rashmithachamikara/feature/portal-update-to-v0.3.0
Upgrade Consent Portal to Support Consent Management API v0.3.0
2 parents c075ca4 + 8c08f26 commit df3877e

26 files changed

Lines changed: 1112 additions & 1665 deletions

portal/backend/.env.example

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,14 @@ BFF_CORS__ALLOWED_HEADERS=Content-Type,X-Correlation-ID
2222
BFF_CORS__ALLOW_CREDENTIALS=false
2323

2424
# Proxy
25-
BFF_PROXY__OPENFGC_API_URL=http://localhost:9090
25+
BFF_PROXY__OPENFGC_API_URL=http://localhost:8060
2626
BFF_PROXY__OPENFGC_API_TIMEOUT=10s
2727
BFF_PROXY__MAX_REQUEST_BYTES=1048576
2828
BFF_PROXY__MAX_RESPONSE_BYTES=10485760
2929
BFF_PROXY__ALLOWED_PASSTHROUGH_METHODS=["GET","POST","PUT","DELETE"]
3030

31-
# Placeholder identity mode (Phase 2 only; must be false in production)
31+
# Placeholder identity mode for local development (must be false in production)
3232
BFF_PROXY__PLACEHOLDER_MODE_ENABLED=false
3333
BFF_PROXY__PLACEHOLDER_USER_ID=
3434
BFF_PROXY__PLACEHOLDER_ORG_ID=
35-
BFF_PROXY__PLACEHOLDER_CLIENT_ID=
35+
BFF_PROXY__PLACEHOLDER_GROUP_ID=

portal/backend/README.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,16 +61,16 @@ When credentials are enabled, origins must be explicitly allowlisted (wildcard o
6161

6262
Portal-facing user endpoints:
6363

64-
- `GET /me/consents` -> upstream `GET /api/v1/consents` with forced `userIds=<placeholder>`
65-
- `GET /me/consents/{consentId}` -> upstream `GET /api/v1/consents/{consentId}`
66-
- `POST /me/consents/{consentId}/approve` -> BFF fetches current consent, merges selected optional approvals, uses the consent's `clientId` as the trusted upstream `TPP-client-id`, updates an existing authorization to approved for the trusted user (or creates one if none exist), and upstreams `PUT /api/v1/consents/{consentId}`
67-
- `PUT /me/consents/{consentId}/revoke` -> upstream `PUT /api/v1/consents/{consentId}/revoke`
64+
- `GET /me/consents` -> upstream `GET /api/v1/consents` with forced `userIds=<placeholder>` and `details=true`
65+
- `GET /me/consents/{consentId}` -> one upstream `GET /api/v1/consents/{consentId}?details=true`; the detailed consent snapshot is returned unchanged
66+
- `POST /me/consents/{consentId}/approve` -> BFF fetches the current consent with `details=true`, validates selected optional elements against its embedded stable IDs and versions, automatically approves mandatory elements, preserves existing approvals, and sends a full upstream consent update with the consent's immutable `groupId` as the trusted `group-id` header
67+
- `POST /me/consents/{consentId}/revoke` -> upstream `POST /api/v1/consents/{consentId}/revoke`
6868

6969
Proxy hardening:
7070

7171
- Path rewrite `/api/*` -> `/api/v1/*` with query preservation
7272
- Deny-by-default allowlist for consent-server routes (unknown path -> `404`, known path wrong method -> `405`)
73-
- Hop-by-hop header stripping and trusted-header override prevention (`org-id`, `TPP-client-id`)
73+
- Hop-by-hop header stripping and trusted-header override prevention (`org-id`, `group-id`, and the rejected legacy client header)
7474
- Correlation ID propagation/generation via `X-Correlation-ID`
7575
- Request body limit enforcement (`BFF_PROXY__MAX_REQUEST_BYTES`) with `413`
7676
- Deterministic upstream error mapping: timeout -> `504`, other connectivity failures -> `502`
@@ -97,6 +97,8 @@ Common error codes:
9797

9898
- `BFF_PROXY__PLACEHOLDER_MODE_ENABLED=true` is blocked when `BFF_ENV=production`
9999
- `BFF_PROXY__PLACEHOLDER_USER_ID` must be empty if placeholder mode is disabled
100+
- `BFF_PROXY__PLACEHOLDER_ORG_ID` must be empty if placeholder mode is disabled
101+
- `BFF_PROXY__PLACEHOLDER_GROUP_ID` must be empty if placeholder mode is disabled
100102

101103
## AI Instructions
102104

portal/backend/internal/me/handler.go

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ func (h *Handler) Consents(w http.ResponseWriter, r *http.Request) {
6868
}
6969
if err := h.svc.Forward(w, r, http.MethodGet, "/api/v1/consents", func(q url.Values) {
7070
q.Set("userIds", userID)
71+
q.Set("details", "true")
7172
}, nil); err != nil {
7273
writeProxyError(w, err)
7374
}
@@ -93,25 +94,12 @@ func (h *Handler) ConsentByID(w http.ResponseWriter, r *http.Request) {
9394
return
9495
}
9596

96-
baseResp, err := h.svc.ForwardRaw(r, http.MethodGet, "/api/v1/consents/"+url.PathEscape(consentID), nil, nil)
97-
if err != nil {
98-
writeProxyError(w, err)
99-
return
100-
}
101-
if baseResp.StatusCode != http.StatusOK {
102-
h.svc.WriteUpstreamResponse(w, baseResp)
103-
return
104-
}
105-
106-
aggregatedBody, err := h.svc.BuildAggregatedConsentResponse(r, baseResp.Body)
107-
if err != nil {
97+
if err := h.svc.Forward(w, r, http.MethodGet, "/api/v1/consents/"+url.PathEscape(consentID), func(q url.Values) {
98+
q.Set("details", "true")
99+
q.Del("includeStatusHistory")
100+
}, nil); err != nil {
108101
writeProxyError(w, err)
109-
return
110102
}
111-
112-
w.Header().Set("Content-Type", "application/json")
113-
w.WriteHeader(http.StatusOK)
114-
_, _ = w.Write(aggregatedBody)
115103
}
116104

117105
// ConsentApprove handles POST /me/consents/{consentId}/approve.
@@ -144,7 +132,10 @@ func (h *Handler) ConsentApprove(w http.ResponseWriter, r *http.Request) {
144132
writeJSONError(w, http.StatusBadRequest, "INVALID_PAYLOAD", "invalid request payload")
145133
return
146134
}
147-
baseResp, err := h.svc.ForwardRaw(r, http.MethodGet, "/api/v1/consents/"+url.PathEscape(consentID), nil, nil)
135+
baseResp, err := h.svc.ForwardRaw(r, http.MethodGet, "/api/v1/consents/"+url.PathEscape(consentID), func(q url.Values) {
136+
q.Set("details", "true")
137+
q.Del("includeStatusHistory")
138+
}, nil)
148139
if err != nil {
149140
writeProxyError(w, err)
150141
return
@@ -154,7 +145,7 @@ func (h *Handler) ConsentApprove(w http.ResponseWriter, r *http.Request) {
154145
return
155146
}
156147

157-
payload, trustedClientID, err := h.svc.BuildApprovalUpdatePayload(r, baseResp.Body, selections, userID)
148+
payload, trustedGroupID, err := h.svc.BuildApprovalUpdatePayload(baseResp.Body, selections, userID)
158149
if err != nil {
159150
if errors.Is(err, proxy.ErrUpstreamTimeout) || errors.Is(err, proxy.ErrUpstreamUnavailable) || errors.Is(err, proxy.ErrUpstreamResponseTooLarge) {
160151
writeProxyError(w, err)
@@ -163,14 +154,14 @@ func (h *Handler) ConsentApprove(w http.ResponseWriter, r *http.Request) {
163154
writeJSONError(w, http.StatusBadRequest, "INVALID_PAYLOAD", "invalid request payload")
164155
return
165156
}
166-
if err := h.svc.ForwardWithClientID(w, r, http.MethodPut, "/api/v1/consents/"+url.PathEscape(consentID), nil, payload, trustedClientID); err != nil {
157+
if err := h.svc.ForwardWithGroupID(w, r, http.MethodPut, "/api/v1/consents/"+url.PathEscape(consentID), nil, payload, trustedGroupID); err != nil {
167158
writeProxyError(w, err)
168159
}
169160
}
170161

171-
// ConsentRevoke handles PUT /me/consents/{consentId}/revoke.
162+
// ConsentRevoke handles POST /me/consents/{consentId}/revoke.
172163
func (h *Handler) ConsentRevoke(w http.ResponseWriter, r *http.Request) {
173-
if r.Method != http.MethodPut {
164+
if r.Method != http.MethodPost {
174165
writeJSONError(w, http.StatusMethodNotAllowed, "METHOD_NOT_ALLOWED", "method not allowed")
175166
return
176167
}
@@ -198,7 +189,7 @@ func (h *Handler) ConsentRevoke(w http.ResponseWriter, r *http.Request) {
198189
writeJSONError(w, http.StatusBadRequest, "INVALID_PAYLOAD", "invalid request payload")
199190
return
200191
}
201-
if err := h.svc.Forward(w, r, http.MethodPut, "/api/v1/consents/"+url.PathEscape(consentID)+"/revoke", nil, payload); err != nil {
192+
if err := h.svc.Forward(w, r, http.MethodPost, "/api/v1/consents/"+url.PathEscape(consentID)+"/revoke", nil, payload); err != nil {
202193
writeProxyError(w, err)
203194
}
204195
}

portal/backend/internal/me/init.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ func Initialize(mux *http.ServeMux, cfg config.Config) error {
4141
mux.Handle("GET /me/consents", middleware.UserID(http.HandlerFunc(handler.Consents), userIDOptions))
4242
mux.Handle("GET /me/consents/{consentId}", middleware.UserID(http.HandlerFunc(handler.ConsentByID), userIDOptions))
4343
mux.Handle("POST /me/consents/{consentId}/approve", middleware.UserID(http.HandlerFunc(handler.ConsentApprove), userIDOptions))
44-
mux.Handle("PUT /me/consents/{consentId}/revoke", middleware.UserID(http.HandlerFunc(handler.ConsentRevoke), userIDOptions))
44+
mux.Handle("POST /me/consents/{consentId}/revoke", middleware.UserID(http.HandlerFunc(handler.ConsentRevoke), userIDOptions))
4545

4646
return nil
4747
}

0 commit comments

Comments
 (0)