@redacto.io/consent-sdk-react 10.0.0-beta.0 → 10.0.0-beta.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +22 -25
- package/CHANGELOG.md +123 -0
- package/README.md +78 -0
- package/dist/{chunk-6A665OPR.mjs → chunk-OGL6OC3A.mjs} +4 -0
- package/dist/index.d.mts +43 -8
- package/dist/index.d.ts +43 -8
- package/dist/index.js +345 -237
- package/dist/index.mjs +344 -238
- package/dist/privacy-center.d.mts +113 -37
- package/dist/privacy-center.d.ts +113 -37
- package/dist/privacy-center.js +2317 -1910
- package/dist/privacy-center.mjs +2245 -1835
- package/package.json +1 -1
- package/src/RedactoNoticeAssisted/RedactoNoticeAssisted.test.tsx +87 -0
- package/src/RedactoNoticeAssisted/RedactoNoticeAssisted.tsx +27 -0
- package/src/RedactoNoticeAssisted/api/index.ts +7 -1
- package/src/RedactoNoticeAssisted/api/types.ts +2 -0
- package/src/RedactoNoticeAssisted/i18n.ts +0 -55
- package/src/RedactoNoticeAssisted/types.ts +2 -0
- package/src/RedactoNoticeConsent/RedactoNoticeConsent.test.tsx +42 -8
- package/src/RedactoNoticeConsent/RedactoNoticeConsent.tsx +75 -33
- package/src/RedactoNoticeConsent/api/index.ts +90 -177
- package/src/RedactoNoticeConsent/api/sandbox.ts +150 -0
- package/src/RedactoNoticeConsent/api/types.ts +60 -44
- package/src/RedactoNoticeConsent/language-codes.ts +0 -2
- package/src/RedactoNoticeConsent/types.ts +29 -3
- package/src/RedactoNoticeConsentInline/RedactoNoticeConsentInline.tsx +86 -5
- package/src/RedactoNoticeConsentInline/api/index.ts +14 -5
- package/src/RedactoNoticeConsentInline/api/notice-read-routing.test.ts +72 -0
- package/src/RedactoNoticeConsentInline/api/types.ts +4 -1
- package/src/RedactoNoticeConsentInline/types.ts +1 -1
- package/src/RedactoPrivacyCenter/PrivacyCenterLayout.tsx +8 -5
- package/src/RedactoPrivacyCenter/RedactoPrivacyCenter.tsx +38 -3
- package/src/RedactoPrivacyCenter/api/actions.ts +13 -5
- package/src/RedactoPrivacyCenter/api/client.ts +216 -39
- package/src/RedactoPrivacyCenter/api/fetcher.ts +24 -9
- package/src/RedactoPrivacyCenter/components/AppShell/AppShell.tsx +28 -0
- package/src/RedactoPrivacyCenter/components/AppShell/UserMenu.tsx +107 -76
- package/src/RedactoPrivacyCenter/components/ConsentManager.tsx +2 -1
- package/src/RedactoPrivacyCenter/components/Form.tsx +53 -24
- package/src/RedactoPrivacyCenter/components/ModifyConsentModal.tsx +1 -1
- package/src/RedactoPrivacyCenter/components/SelectUser.tsx +95 -0
- package/src/RedactoPrivacyCenter/components/SelectUserGate.tsx +195 -0
- package/src/RedactoPrivacyCenter/components/constant.ts +11 -0
- package/src/RedactoPrivacyCenter/components/groupHelpers.ts +42 -6
- package/src/RedactoPrivacyCenter/context/AuthContext.tsx +131 -39
- package/src/RedactoPrivacyCenter/context/BaseUrlContext.tsx +1 -1
- package/src/RedactoPrivacyCenter/context/ProfileSwitchContext.tsx +15 -0
- package/src/RedactoPrivacyCenter/context/types.ts +39 -1
- package/src/RedactoPrivacyCenter/lib/api.ts +3 -0
- package/src/RedactoPrivacyCenter/lib/constants.ts +12 -6
- package/src/RedactoPrivacyCenter/lib/locales.ts +0 -3
- package/src/RedactoPrivacyCenter/lib/sandbox.ts +104 -0
- package/src/RedactoPrivacyCenter/lib/types.ts +78 -2
- package/src/RedactoPrivacyCenter/lib/utils.ts +15 -0
- package/src/RedactoPrivacyCenter/locales/as/translation.json +7 -1
- package/src/RedactoPrivacyCenter/locales/bn/translation.json +7 -1
- package/src/RedactoPrivacyCenter/locales/brx/translation.json +7 -1
- package/src/RedactoPrivacyCenter/locales/doi/translation.json +7 -1
- package/src/RedactoPrivacyCenter/locales/en/translation.json +8 -1
- package/src/RedactoPrivacyCenter/locales/gom/translation.json +7 -1
- package/src/RedactoPrivacyCenter/locales/gu/translation.json +7 -1
- package/src/RedactoPrivacyCenter/locales/hi/translation.json +7 -1
- package/src/RedactoPrivacyCenter/locales/kn/translation.json +7 -1
- package/src/RedactoPrivacyCenter/locales/ks/translation.json +7 -1
- package/src/RedactoPrivacyCenter/locales/mai/translation.json +7 -1
- package/src/RedactoPrivacyCenter/locales/ml/translation.json +7 -1
- package/src/RedactoPrivacyCenter/locales/mni-Mtei/translation.json +7 -1
- package/src/RedactoPrivacyCenter/locales/mr/translation.json +7 -1
- package/src/RedactoPrivacyCenter/locales/ne/translation.json +7 -1
- package/src/RedactoPrivacyCenter/locales/or/translation.json +7 -1
- package/src/RedactoPrivacyCenter/locales/pa/translation.json +7 -1
- package/src/RedactoPrivacyCenter/locales/sa/translation.json +7 -1
- package/src/RedactoPrivacyCenter/locales/sat/translation.json +7 -1
- package/src/RedactoPrivacyCenter/locales/sd/translation.json +7 -1
- package/src/RedactoPrivacyCenter/locales/ta/translation.json +7 -1
- package/src/RedactoPrivacyCenter/locales/te/translation.json +7 -1
- package/src/RedactoPrivacyCenter/locales/ur/translation.json +7 -1
- package/src/RedactoPrivacyCenter/styles/injectStyles.ts +112 -2
- package/src/RedactoPrivacyCenter/styles/pcStyles.ts +17 -0
- package/src/RedactoPrivacyCenter/ui/PCAvatar.test.tsx +83 -0
- package/src/RedactoPrivacyCenter/ui/PCAvatar.tsx +33 -9
- package/src/shared/notice-consent-status.ts +190 -0
- package/src/shared/sandbox.ts +6 -0
- package/tests/Form.test.tsx +190 -7
- package/tests/RevokeWarningMessage.test.tsx +151 -0
- package/tests/consentClient.test.ts +217 -0
- package/tests/manageConsentServer.test.ts +38 -0
- package/src/RedactoPrivacyCenter/locales/bho/translation.json +0 -334
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
import { apiFetch, ApiError } from "./fetcher";
|
|
2
2
|
import type { ApiFetchOptions, ApiResponse } from "./fetcher";
|
|
3
|
+
import {
|
|
4
|
+
isSandboxMode,
|
|
5
|
+
sandboxAuthHeaders,
|
|
6
|
+
sandboxContact,
|
|
7
|
+
sandboxQueryIdentity,
|
|
8
|
+
} from "../lib/sandbox";
|
|
3
9
|
import type {
|
|
4
10
|
CaseMessage,
|
|
11
|
+
IdentityListResponse,
|
|
5
12
|
MessageDocument,
|
|
6
13
|
NotificationActionResponse,
|
|
7
14
|
NotificationListResponse,
|
|
8
15
|
NotificationQueryParams,
|
|
9
16
|
NotificationSummary,
|
|
17
|
+
SelectPrincipalResponse,
|
|
10
18
|
UnreadCountResponse,
|
|
11
19
|
UploadDocumentResponse,
|
|
12
20
|
} from "../lib/types";
|
|
@@ -25,13 +33,18 @@ export type BaseRequest = {
|
|
|
25
33
|
* Creates a client instance with a specific base URL.
|
|
26
34
|
* @param baseUrl - Consent server base URL (Python service, e.g. http://host/consent)
|
|
27
35
|
* @param ledgerBaseUrl - Go ledger base URL (e.g. http://host:8090/consent-ledger).
|
|
28
|
-
* Used for user-consents reads
|
|
36
|
+
* Used for user-consents reads/writes served from the ledger's `/public/...` paths.
|
|
37
|
+
* When omitted, those calls fall back to the legacy Python
|
|
38
|
+
* `/dsar/privacy-center` endpoints and payloads (pre-ledger behavior), so
|
|
39
|
+
* integrations without a deployed ledger keep working.
|
|
29
40
|
*/
|
|
30
|
-
export function createConsentClient(baseUrl: string, ledgerBaseUrl
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
41
|
+
export function createConsentClient(baseUrl: string, ledgerBaseUrl?: string) {
|
|
42
|
+
const ledgerConfigured = Boolean(ledgerBaseUrl);
|
|
43
|
+
// Sandbox mode never routes to the Go ledger (it has no sandbox / test::
|
|
44
|
+
// support). A sandbox token forces the legacy consent-server path regardless
|
|
45
|
+
// of ledgerBaseUrl, so isolation can't be bypassed by also configuring a ledger.
|
|
46
|
+
const useLedger = () => ledgerConfigured && !isSandboxMode();
|
|
47
|
+
const ledgerBase = (ledgerBaseUrl ?? "").replace(/\/$/, "");
|
|
35
48
|
/**
|
|
36
49
|
* Privacy Center API fetch wrapper that uses Authorization: Bearer header.
|
|
37
50
|
* Privacy center endpoints require JWT tokens in Authorization header, not X-Auth-Token.
|
|
@@ -69,15 +82,26 @@ export function createConsentClient(baseUrl: string, ledgerBaseUrl: string) {
|
|
|
69
82
|
}
|
|
70
83
|
}
|
|
71
84
|
|
|
72
|
-
//
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
85
|
+
// Sandbox PoC: send only the X-Consent-Token credential (no JWT bearer). Set
|
|
86
|
+
// last so it wins over any caller-supplied Authorization header. The acting
|
|
87
|
+
// identity travels in the request payload -- query params on reads, the body
|
|
88
|
+
// on writes -- never a header.
|
|
89
|
+
const sandboxHeaders = sandboxAuthHeaders();
|
|
90
|
+
if (sandboxHeaders) {
|
|
91
|
+
Object.entries(sandboxHeaders).forEach(([key, value]) => {
|
|
92
|
+
requestHeaders.set(key, value);
|
|
93
|
+
});
|
|
94
|
+
} else {
|
|
95
|
+
// Add Authorization Bearer token for privacy center APIs
|
|
96
|
+
const token = authToken || (getAuthToken ? await getAuthToken() : null);
|
|
97
|
+
if (token) {
|
|
98
|
+
const trimmedToken = token.trim();
|
|
99
|
+
// Format token with Bearer prefix if not already present
|
|
100
|
+
const bearerToken = trimmedToken.startsWith('Bearer ')
|
|
101
|
+
? trimmedToken
|
|
102
|
+
: `Bearer ${trimmedToken}`;
|
|
103
|
+
requestHeaders.set("Authorization", bearerToken);
|
|
104
|
+
}
|
|
81
105
|
}
|
|
82
106
|
|
|
83
107
|
// Build fetch options
|
|
@@ -177,6 +201,9 @@ export function createConsentClient(baseUrl: string, ledgerBaseUrl: string) {
|
|
|
177
201
|
}
|
|
178
202
|
|
|
179
203
|
function ledgerUrl(baseRequest: BaseRequest, path: string): string {
|
|
204
|
+
if (!useLedger()) {
|
|
205
|
+
throw new Error("ledgerUrl called without a configured ledgerBaseUrl");
|
|
206
|
+
}
|
|
180
207
|
return `${ledgerBase}/public/organisations/${baseRequest.organisationUuid}/workspaces/${baseRequest.workspaceUuid}${path}`;
|
|
181
208
|
}
|
|
182
209
|
|
|
@@ -185,13 +212,47 @@ export function createConsentClient(baseUrl: string, ledgerBaseUrl: string) {
|
|
|
185
212
|
}
|
|
186
213
|
|
|
187
214
|
function notificationUrl(baseRequest: BaseRequest, path = ''): string {
|
|
188
|
-
|
|
215
|
+
const identitySuffix = sandboxIdentitySuffix();
|
|
216
|
+
if (!identitySuffix) {
|
|
217
|
+
return privacyCenterUrl(baseRequest, `/notifications${path}`);
|
|
218
|
+
}
|
|
219
|
+
const separator = path.includes('?') ? `&${identitySuffix.slice(1)}` : identitySuffix;
|
|
220
|
+
return privacyCenterUrl(baseRequest, `/notifications${path}${separator}`);
|
|
189
221
|
}
|
|
190
222
|
|
|
191
223
|
function resolveAuthToken(authToken: string | undefined, baseRequest: BaseRequest): string {
|
|
192
224
|
return authToken || baseRequest.authToken;
|
|
193
225
|
}
|
|
194
226
|
|
|
227
|
+
/**
|
|
228
|
+
* Append the sandbox acting identity (`primary_email` / `primary_mobile`) to a
|
|
229
|
+
* query-param bag when sandbox mode is active; a no-op otherwise. The server
|
|
230
|
+
* resolves the acting test identity from these query params on reads (there is
|
|
231
|
+
* no subject header). The credential is the X-Consent-Token header set in
|
|
232
|
+
* privacyCenterApiFetch.
|
|
233
|
+
*/
|
|
234
|
+
function appendSandboxIdentity(queryParams: URLSearchParams): void {
|
|
235
|
+
const identity = sandboxQueryIdentity();
|
|
236
|
+
if (identity) {
|
|
237
|
+
Object.entries(identity).forEach(([key, value]) => {
|
|
238
|
+
queryParams.append(key, value);
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* The sandbox acting-identity query suffix (`?primary_email=...` /
|
|
245
|
+
* `?primary_mobile=...`) for endpoints that build a bare URL with no other
|
|
246
|
+
* query params (the case-communication reads/POSTs). Empty string outside
|
|
247
|
+
* sandbox mode, so the JWT path is unchanged.
|
|
248
|
+
*/
|
|
249
|
+
function sandboxIdentitySuffix(): string {
|
|
250
|
+
const queryParams = new URLSearchParams();
|
|
251
|
+
appendSandboxIdentity(queryParams);
|
|
252
|
+
const queryString = queryParams.toString();
|
|
253
|
+
return queryString ? `?${queryString}` : "";
|
|
254
|
+
}
|
|
255
|
+
|
|
195
256
|
return {
|
|
196
257
|
/**
|
|
197
258
|
* Get form data (privacy center)
|
|
@@ -241,13 +302,23 @@ export function createConsentClient(baseUrl: string, ledgerBaseUrl: string) {
|
|
|
241
302
|
const url = privacyCenterUrl(baseRequest, '/case');
|
|
242
303
|
const { useSessionAuth = false, getAuthToken, ...fetchOptions } = options || {};
|
|
243
304
|
|
|
305
|
+
// Sandbox mode: the acting test identity is the single `contact` field
|
|
306
|
+
// (the server has no subject header; it namespaces this identity `test::`).
|
|
307
|
+
// Merge it into the body only when sandbox mode is active and the payload
|
|
308
|
+
// is a plain object; the JWT path is left byte-for-byte unchanged.
|
|
309
|
+
const contact = sandboxContact();
|
|
310
|
+
const requestPayload =
|
|
311
|
+
contact && payload && typeof payload === "object"
|
|
312
|
+
? { ...(payload as Record<string, unknown>), contact }
|
|
313
|
+
: payload;
|
|
314
|
+
|
|
244
315
|
// Case management uses session-based auth (X-Auth-Token)
|
|
245
316
|
// Privacy center uses JWT Bearer tokens (Authorization: Bearer)
|
|
246
317
|
if (useSessionAuth) {
|
|
247
318
|
return apiFetch(url, {
|
|
248
319
|
...fetchOptions,
|
|
249
320
|
method: "POST",
|
|
250
|
-
body: JSON.stringify(
|
|
321
|
+
body: JSON.stringify(requestPayload),
|
|
251
322
|
getAuthToken,
|
|
252
323
|
});
|
|
253
324
|
}
|
|
@@ -256,7 +327,7 @@ export function createConsentClient(baseUrl: string, ledgerBaseUrl: string) {
|
|
|
256
327
|
return privacyCenterApiFetch(url, {
|
|
257
328
|
...fetchOptions,
|
|
258
329
|
method: "POST",
|
|
259
|
-
body: JSON.stringify(
|
|
330
|
+
body: JSON.stringify(requestPayload),
|
|
260
331
|
authToken: resolveAuthToken(authToken, baseRequest),
|
|
261
332
|
});
|
|
262
333
|
},
|
|
@@ -382,9 +453,14 @@ export function createConsentClient(baseUrl: string, ledgerBaseUrl: string) {
|
|
|
382
453
|
if (params.status) queryParams.append("status", params.status);
|
|
383
454
|
if (params.product_uuid) queryParams.append("product_uuid", params.product_uuid);
|
|
384
455
|
if (params.language) queryParams.append("language", params.language);
|
|
456
|
+
// Sandbox mode: the acting identity travels as query params
|
|
457
|
+
// (primary_email / primary_mobile); the credential is the X-Consent-Token header.
|
|
458
|
+
appendSandboxIdentity(queryParams);
|
|
385
459
|
const queryString = queryParams.toString();
|
|
386
460
|
const suffix = `/user-consents${queryString ? `?${queryString}` : ""}`;
|
|
387
|
-
const url =
|
|
461
|
+
const url = useLedger()
|
|
462
|
+
? ledgerUrl(baseRequest, suffix)
|
|
463
|
+
: privacyCenterUrl(baseRequest, suffix);
|
|
388
464
|
return privacyCenterApiFetch(url, {
|
|
389
465
|
...options,
|
|
390
466
|
method: "GET",
|
|
@@ -418,9 +494,14 @@ export function createConsentClient(baseUrl: string, ledgerBaseUrl: string) {
|
|
|
418
494
|
if (params.search) queryParams.append("search", params.search);
|
|
419
495
|
if (params.language) queryParams.append("language", params.language);
|
|
420
496
|
if (params.nominator_uuid) queryParams.append("nominator_uuid", params.nominator_uuid);
|
|
497
|
+
// Sandbox mode: the acting identity travels as query params
|
|
498
|
+
// (primary_email / primary_mobile); the credential is the X-Consent-Token header.
|
|
499
|
+
appendSandboxIdentity(queryParams);
|
|
421
500
|
const queryString = queryParams.toString();
|
|
422
501
|
const suffix = `/user-consents/${productUuid}/purposes${queryString ? `?${queryString}` : ""}`;
|
|
423
|
-
const url =
|
|
502
|
+
const url = useLedger()
|
|
503
|
+
? ledgerUrl(baseRequest, suffix)
|
|
504
|
+
: privacyCenterUrl(baseRequest, suffix);
|
|
424
505
|
return privacyCenterApiFetch(url, {
|
|
425
506
|
...options,
|
|
426
507
|
method: "GET",
|
|
@@ -432,18 +513,23 @@ export function createConsentClient(baseUrl: string, ledgerBaseUrl: string) {
|
|
|
432
513
|
/**
|
|
433
514
|
* Manage consent (privacy center) - revoke, regrant, or renew.
|
|
434
515
|
*
|
|
435
|
-
* The Go ledger has no manage-consent route, so
|
|
436
|
-
* `partial` submit-consent on the ledger — mirroring
|
|
437
|
-
* relay (`build_consent_management_payload`): the
|
|
438
|
-
* re-submitted with `selected: false` to revoke or
|
|
439
|
-
* regrant/renew, and `partial: true` so the rest of the
|
|
440
|
-
* are left untouched. The principal is resolved from the
|
|
441
|
-
* `contact`/identifiers are sent in the body, and
|
|
442
|
-
* omitted so the ledger mints a fresh event.
|
|
516
|
+
* The Go ledger has no manage-consent route, so with a ledger configured
|
|
517
|
+
* this is expressed as a `partial` submit-consent on the ledger — mirroring
|
|
518
|
+
* the Python dual-write relay (`build_consent_management_payload`): the
|
|
519
|
+
* affected purpose is re-submitted with `selected: false` to revoke or
|
|
520
|
+
* `selected: true` to regrant/renew, and `partial: true` so the rest of the
|
|
521
|
+
* notice's purposes are left untouched. The principal is resolved from the
|
|
522
|
+
* JWT, so no `contact`/identifiers are sent in the body, and
|
|
523
|
+
* `consent_event_uuid` is omitted so the ledger mints a fresh event.
|
|
524
|
+
*
|
|
525
|
+
* Without a ledger, the legacy Python `/manage-consent?status=` endpoint is
|
|
526
|
+
* used with its `{ purpose_uuid, contact, ... }` payload.
|
|
443
527
|
*/
|
|
444
528
|
async manageConsent(
|
|
445
529
|
baseRequest: BaseRequest,
|
|
446
530
|
purposeId: string,
|
|
531
|
+
// Sent to the legacy Python endpoint only; the ledger resolves the
|
|
532
|
+
// principal from the JWT, so no identifiers are sent in ledger mode.
|
|
447
533
|
contact: string,
|
|
448
534
|
status: 'revoke' | 'regrant' | 'renew',
|
|
449
535
|
authToken?: string,
|
|
@@ -452,8 +538,31 @@ export function createConsentClient(baseUrl: string, ledgerBaseUrl: string) {
|
|
|
452
538
|
dataElementUuids?: string[],
|
|
453
539
|
productUuid?: string,
|
|
454
540
|
noticeUuid?: string,
|
|
455
|
-
dataElements?: Array<{ uuid: string; selected: boolean }
|
|
541
|
+
dataElements?: Array<{ uuid: string; selected: boolean }>,
|
|
542
|
+
language?: string
|
|
456
543
|
): Promise<unknown> {
|
|
544
|
+
if (!useLedger()) {
|
|
545
|
+
const url = privacyCenterUrl(baseRequest, `/manage-consent?status=${status}`);
|
|
546
|
+
const body: Record<string, unknown> = { purpose_uuid: purposeId, contact };
|
|
547
|
+
if (nominatorContact) {
|
|
548
|
+
body.nominator_contact = nominatorContact;
|
|
549
|
+
}
|
|
550
|
+
if (Array.isArray(dataElementUuids) && dataElementUuids.length > 0) {
|
|
551
|
+
body.data_element_uuids = dataElementUuids;
|
|
552
|
+
}
|
|
553
|
+
if (productUuid) {
|
|
554
|
+
body.product_uuid = productUuid;
|
|
555
|
+
}
|
|
556
|
+
if (language) {
|
|
557
|
+
body.language = language;
|
|
558
|
+
}
|
|
559
|
+
return privacyCenterApiFetch(url, {
|
|
560
|
+
...options,
|
|
561
|
+
method: "POST",
|
|
562
|
+
body: JSON.stringify(body),
|
|
563
|
+
authToken: resolveAuthToken(authToken, baseRequest),
|
|
564
|
+
});
|
|
565
|
+
}
|
|
457
566
|
if (!noticeUuid) {
|
|
458
567
|
throw new Error("notice_uuid is required to manage consent via the ledger");
|
|
459
568
|
}
|
|
@@ -473,7 +582,7 @@ export function createConsentClient(baseUrl: string, ledgerBaseUrl: string) {
|
|
|
473
582
|
}
|
|
474
583
|
return { uuid: de.uuid, selected };
|
|
475
584
|
});
|
|
476
|
-
const body = {
|
|
585
|
+
const body: Record<string, unknown> = {
|
|
477
586
|
notice_uuid: noticeUuid,
|
|
478
587
|
partial: true,
|
|
479
588
|
declined: false,
|
|
@@ -486,6 +595,7 @@ export function createConsentClient(baseUrl: string, ledgerBaseUrl: string) {
|
|
|
486
595
|
data_elements: elements,
|
|
487
596
|
},
|
|
488
597
|
],
|
|
598
|
+
...(language ? { language } : {}),
|
|
489
599
|
};
|
|
490
600
|
const url = ledgerUrl(baseRequest, "/submit-consent");
|
|
491
601
|
return privacyCenterApiFetch(url, {
|
|
@@ -511,6 +621,9 @@ export function createConsentClient(baseUrl: string, ledgerBaseUrl: string) {
|
|
|
511
621
|
if (offset !== undefined) queryParams.append("offset", offset.toString());
|
|
512
622
|
if (limit !== undefined) queryParams.append("limit", limit.toString());
|
|
513
623
|
if (language) queryParams.append("language", language);
|
|
624
|
+
// Sandbox mode: the acting identity travels as query params
|
|
625
|
+
// (primary_email / primary_mobile); the credential is the X-Consent-Token header.
|
|
626
|
+
appendSandboxIdentity(queryParams);
|
|
514
627
|
const queryString = queryParams.toString();
|
|
515
628
|
const url = privacyCenterUrl(baseRequest, `/activities${queryString ? `?${queryString}` : ""}`);
|
|
516
629
|
return privacyCenterApiFetch<unknown>(url, {
|
|
@@ -519,6 +632,44 @@ export function createConsentClient(baseUrl: string, ledgerBaseUrl: string) {
|
|
|
519
632
|
});
|
|
520
633
|
},
|
|
521
634
|
|
|
635
|
+
/**
|
|
636
|
+
* List all data principals that share this session's contact
|
|
637
|
+
* (shared-contact profile picker). Identity is derived server-side from the
|
|
638
|
+
* JWT — the caller sends nothing but its bearer token.
|
|
639
|
+
*/
|
|
640
|
+
async listIdentities(
|
|
641
|
+
baseRequest: BaseRequest,
|
|
642
|
+
authToken?: string,
|
|
643
|
+
options?: Omit<ApiFetchOptions, "getAuthToken">
|
|
644
|
+
): Promise<IdentityListResponse> {
|
|
645
|
+
const url = privacyCenterUrl(baseRequest, '/identities');
|
|
646
|
+
return privacyCenterApiFetch<IdentityListResponse>(url, {
|
|
647
|
+
...options,
|
|
648
|
+
cache: 'no-store',
|
|
649
|
+
authToken: resolveAuthToken(authToken, baseRequest),
|
|
650
|
+
});
|
|
651
|
+
},
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* Re-scope the session to a chosen principal uuid (must be one of the
|
|
655
|
+
* principals sharing this session's contact). Returns fresh access/refresh
|
|
656
|
+
* tokens scoped to that principal.
|
|
657
|
+
*/
|
|
658
|
+
async selectPrincipal(
|
|
659
|
+
baseRequest: BaseRequest,
|
|
660
|
+
principalUuid: string,
|
|
661
|
+
authToken?: string,
|
|
662
|
+
options?: Omit<ApiFetchOptions, "getAuthToken">
|
|
663
|
+
): Promise<SelectPrincipalResponse> {
|
|
664
|
+
const url = privacyCenterUrl(baseRequest, '/identities/select');
|
|
665
|
+
return privacyCenterApiFetch<SelectPrincipalResponse>(url, {
|
|
666
|
+
...options,
|
|
667
|
+
method: 'POST',
|
|
668
|
+
body: JSON.stringify({ uuid: principalUuid }),
|
|
669
|
+
authToken: resolveAuthToken(authToken, baseRequest),
|
|
670
|
+
});
|
|
671
|
+
},
|
|
672
|
+
|
|
522
673
|
/**
|
|
523
674
|
* Get receipts (privacy center) — consent receipts for the authenticated user
|
|
524
675
|
*/
|
|
@@ -547,8 +698,13 @@ export function createConsentClient(baseUrl: string, ledgerBaseUrl: string) {
|
|
|
547
698
|
if (params.created_before) queryParams.append("created_before", params.created_before);
|
|
548
699
|
if (params.language) queryParams.append("language", params.language);
|
|
549
700
|
const queryString = queryParams.toString();
|
|
550
|
-
//
|
|
551
|
-
|
|
701
|
+
// With a ledger configured, receipts are served by the Go ledger
|
|
702
|
+
// (paginated list under /public/...); otherwise the legacy Python
|
|
703
|
+
// privacy-center list is used. normalizeReceipts tolerates both envelopes.
|
|
704
|
+
const receiptsSuffix = `/receipts${queryString ? `?${queryString}` : ""}`;
|
|
705
|
+
const url = useLedger()
|
|
706
|
+
? ledgerUrl(baseRequest, receiptsSuffix)
|
|
707
|
+
: privacyCenterUrl(baseRequest, receiptsSuffix);
|
|
552
708
|
return privacyCenterApiFetch<unknown>(url, {
|
|
553
709
|
...options,
|
|
554
710
|
method: "GET",
|
|
@@ -568,13 +724,25 @@ export function createConsentClient(baseUrl: string, ledgerBaseUrl: string) {
|
|
|
568
724
|
receiptUuid: string,
|
|
569
725
|
authToken?: string
|
|
570
726
|
): Promise<Blob> {
|
|
571
|
-
// The ledger serves the receipt PDF at /receipts/{uuid}/download
|
|
572
|
-
//
|
|
573
|
-
|
|
727
|
+
// The ledger serves the receipt PDF at /receipts/{uuid}/download, the
|
|
728
|
+
// legacy Python service at /receipts/{uuid}/pdf. The receiptUuid always
|
|
729
|
+
// comes from the same-mode list above, so list and download stay consistent.
|
|
730
|
+
const url = useLedger()
|
|
731
|
+
? ledgerUrl(baseRequest, `/receipts/${receiptUuid}/download`)
|
|
732
|
+
: privacyCenterUrl(baseRequest, `/receipts/${receiptUuid}/pdf`);
|
|
574
733
|
const token = resolveAuthToken(authToken, baseRequest);
|
|
575
734
|
const requestHeaders = new Headers();
|
|
576
735
|
requestHeaders.set("Accept", "application/pdf");
|
|
577
|
-
|
|
736
|
+
// Sandbox PoC: send the single X-Consent-Token header instead of the JWT
|
|
737
|
+
// bearer. The receipt PDF download identifies the principal by receiptUuid
|
|
738
|
+
// (already scoped to the sandbox subject via the same-mode list), so no
|
|
739
|
+
// extra identity payload is needed here.
|
|
740
|
+
const sandboxHeaders = sandboxAuthHeaders();
|
|
741
|
+
if (sandboxHeaders) {
|
|
742
|
+
Object.entries(sandboxHeaders).forEach(([key, value]) => {
|
|
743
|
+
requestHeaders.set(key, value);
|
|
744
|
+
});
|
|
745
|
+
} else if (token) {
|
|
578
746
|
const trimmedToken = token.trim();
|
|
579
747
|
requestHeaders.set(
|
|
580
748
|
"Authorization",
|
|
@@ -617,6 +785,9 @@ export function createConsentClient(baseUrl: string, ledgerBaseUrl: string) {
|
|
|
617
785
|
if (offset !== undefined) queryParams.append("offset", offset.toString());
|
|
618
786
|
if (limit !== undefined) queryParams.append("limit", limit.toString());
|
|
619
787
|
if (language) queryParams.append("language", language);
|
|
788
|
+
// Sandbox mode: the acting identity travels as query params
|
|
789
|
+
// (primary_email / primary_mobile); the credential is the X-Consent-Token header.
|
|
790
|
+
appendSandboxIdentity(queryParams);
|
|
620
791
|
const queryString = queryParams.toString();
|
|
621
792
|
const url = privacyCenterUrl(baseRequest, `/cases?${queryString}`);
|
|
622
793
|
return privacyCenterApiFetch<unknown>(url, {
|
|
@@ -635,7 +806,9 @@ export function createConsentClient(baseUrl: string, ledgerBaseUrl: string) {
|
|
|
635
806
|
caseUuid: string,
|
|
636
807
|
authToken?: string
|
|
637
808
|
): Promise<CaseMessage[]> {
|
|
638
|
-
|
|
809
|
+
// Sandbox mode: the acting identity travels as query params
|
|
810
|
+
// (primary_email / primary_mobile); the credential is the X-Consent-Token header.
|
|
811
|
+
const url = caseUrl(baseRequest, caseUuid, `/messages${sandboxIdentitySuffix()}`);
|
|
639
812
|
return privacyCenterApiFetch<CaseMessage[]>(url, {
|
|
640
813
|
authToken: resolveAuthToken(authToken, baseRequest),
|
|
641
814
|
});
|
|
@@ -651,7 +824,9 @@ export function createConsentClient(baseUrl: string, ledgerBaseUrl: string) {
|
|
|
651
824
|
documentUuids: string[],
|
|
652
825
|
authToken?: string
|
|
653
826
|
): Promise<CaseMessage> {
|
|
654
|
-
|
|
827
|
+
// Sandbox mode: the acting identity travels as query params
|
|
828
|
+
// (primary_email / primary_mobile); the credential is the X-Consent-Token header.
|
|
829
|
+
const url = caseUrl(baseRequest, caseUuid, `/messages${sandboxIdentitySuffix()}`);
|
|
655
830
|
return privacyCenterApiFetch<CaseMessage>(url, {
|
|
656
831
|
method: 'POST',
|
|
657
832
|
body: JSON.stringify({ body, document_uuids: documentUuids }),
|
|
@@ -668,7 +843,9 @@ export function createConsentClient(baseUrl: string, ledgerBaseUrl: string) {
|
|
|
668
843
|
file: File,
|
|
669
844
|
authToken?: string
|
|
670
845
|
): Promise<UploadDocumentResponse> {
|
|
671
|
-
|
|
846
|
+
// Sandbox mode: the acting identity travels as query params
|
|
847
|
+
// (primary_email / primary_mobile); the credential is the X-Consent-Token header.
|
|
848
|
+
const url = caseUrl(baseRequest, caseUuid, `/upload-document${sandboxIdentitySuffix()}`);
|
|
672
849
|
const formData = new FormData();
|
|
673
850
|
formData.append('file', file, file.name); // field name must be exactly "file"
|
|
674
851
|
// "payload" is required by the privacy-center endpoint (omitting it causes 422)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { sandboxAuthHeaders } from "../lib/sandbox";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Options for apiFetch function
|
|
3
5
|
*/
|
|
@@ -125,16 +127,29 @@ export async function apiFetch<T>(
|
|
|
125
127
|
}
|
|
126
128
|
}
|
|
127
129
|
|
|
128
|
-
// Add auth
|
|
130
|
+
// Add auth AFTER custom headers so it's never overridden. Only for calls that
|
|
131
|
+
// opt into auth (a `getAuthToken` is supplied) — public calls like org
|
|
132
|
+
// branding (which hit the user server, not consent-server) stay unauthenticated
|
|
133
|
+
// and must NOT receive the sandbox headers.
|
|
129
134
|
if (getAuthToken) {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
135
|
+
// Sandbox PoC: send the single X-Consent-Token header instead of the session
|
|
136
|
+
// token. The acting identity travels in the request payload (injected by the
|
|
137
|
+
// client call sites), not a header.
|
|
138
|
+
const sandboxHeaders = sandboxAuthHeaders();
|
|
139
|
+
if (sandboxHeaders) {
|
|
140
|
+
Object.entries(sandboxHeaders).forEach(([key, value]) => {
|
|
141
|
+
requestHeaders.set(key, value);
|
|
142
|
+
});
|
|
143
|
+
} else {
|
|
144
|
+
const token = await getAuthToken();
|
|
145
|
+
if (token) {
|
|
146
|
+
// Use the exact header format that works in other parts of the codebase
|
|
147
|
+
// Other working API calls use 'X-Auth-Token' (capital A and T, rest lowercase)
|
|
148
|
+
const trimmedToken = token.trim();
|
|
149
|
+
requestHeaders.set("X-Auth-Token", trimmedToken);
|
|
150
|
+
// Also set lowercase for compatibility (some servers are case-sensitive)
|
|
151
|
+
requestHeaders.set("x-auth-token", trimmedToken);
|
|
152
|
+
}
|
|
138
153
|
}
|
|
139
154
|
}
|
|
140
155
|
|
|
@@ -214,6 +214,34 @@ export default function AppShell({
|
|
|
214
214
|
var(--pc-bg-shell) 100%
|
|
215
215
|
);
|
|
216
216
|
}
|
|
217
|
+
.pc-shell-user-menu-identity {
|
|
218
|
+
display: flex;
|
|
219
|
+
flex-direction: column;
|
|
220
|
+
gap: 2px;
|
|
221
|
+
min-width: 0;
|
|
222
|
+
flex: 1;
|
|
223
|
+
}
|
|
224
|
+
.pc-shell-user-menu-identity-email {
|
|
225
|
+
font-size: 13px;
|
|
226
|
+
font-weight: 600;
|
|
227
|
+
color: var(--pc-text-primary);
|
|
228
|
+
line-height: 1.3;
|
|
229
|
+
overflow: hidden;
|
|
230
|
+
text-overflow: ellipsis;
|
|
231
|
+
white-space: nowrap;
|
|
232
|
+
min-width: 0;
|
|
233
|
+
}
|
|
234
|
+
.pc-shell-user-menu-identity-id {
|
|
235
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
236
|
+
font-size: 11px;
|
|
237
|
+
font-weight: 500;
|
|
238
|
+
color: var(--pc-text-secondary);
|
|
239
|
+
line-height: 1.3;
|
|
240
|
+
overflow: hidden;
|
|
241
|
+
text-overflow: ellipsis;
|
|
242
|
+
white-space: nowrap;
|
|
243
|
+
min-width: 0;
|
|
244
|
+
}
|
|
217
245
|
.pc-popover.pc-shell-user-menu-popover {
|
|
218
246
|
border-radius: 12px !important;
|
|
219
247
|
overflow: hidden !important;
|