@redacto.io/consent-sdk-react 9.4.0 → 9.5.0-beta.0
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/CHANGELOG.md +6 -0
- package/dist/privacy-center.js +14 -6
- package/dist/privacy-center.mjs +14 -6
- package/package.json +1 -1
- package/src/RedactoPrivacyCenter/PrivacyCenterLayout.tsx +6 -4
- package/src/RedactoPrivacyCenter/context/AuthContext.tsx +7 -5
- package/src/RedactoPrivacyCenter/lib/utils.ts +28 -0
- package/tests/resolveContactFromJwt.test.ts +44 -0
package/CHANGELOG.md
CHANGED
package/dist/privacy-center.js
CHANGED
|
@@ -9271,6 +9271,14 @@ var decodeJwtToken = (token) => {
|
|
|
9271
9271
|
return null;
|
|
9272
9272
|
}
|
|
9273
9273
|
};
|
|
9274
|
+
var resolveContactFromJwt = (decoded) => {
|
|
9275
|
+
if (!decoded) {
|
|
9276
|
+
return null;
|
|
9277
|
+
}
|
|
9278
|
+
const userData = decoded.user_data || {};
|
|
9279
|
+
const candidate = decoded.contact || decoded.primary_email || userData.primary_email || userData.primary_mobile || userData.org_user_id;
|
|
9280
|
+
return typeof candidate === "string" && candidate.trim() !== "" ? candidate : null;
|
|
9281
|
+
};
|
|
9274
9282
|
var isJwtExpired = (token, bufferSeconds = 5 * 60) => {
|
|
9275
9283
|
if (!token || typeof token !== "string") {
|
|
9276
9284
|
return true;
|
|
@@ -9998,9 +10006,9 @@ var syncJwtDataToSessionStorage = (accessToken) => {
|
|
|
9998
10006
|
if (!decoded) {
|
|
9999
10007
|
return;
|
|
10000
10008
|
}
|
|
10001
|
-
const
|
|
10002
|
-
if (
|
|
10003
|
-
sessionStorage.setItem("email",
|
|
10009
|
+
const contact = resolveContactFromJwt(decoded) || sessionStorage.getItem("email") || (typeof localStorage !== "undefined" ? localStorage.getItem("userEmail") : null);
|
|
10010
|
+
if (contact && typeof contact === "string") {
|
|
10011
|
+
sessionStorage.setItem("email", contact);
|
|
10004
10012
|
}
|
|
10005
10013
|
const orgUuid = decoded.organisation_uuid;
|
|
10006
10014
|
if (orgUuid && typeof orgUuid === "string") {
|
|
@@ -15145,9 +15153,9 @@ function PrivacyCenterLayout({ children, slug, onBack }) {
|
|
|
15145
15153
|
if (orgId) sessionStorage.setItem("pc_org_id", orgId);
|
|
15146
15154
|
if (workspaceId) sessionStorage.setItem("pc_workspace_id", workspaceId);
|
|
15147
15155
|
if (orgSlug) sessionStorage.setItem("pc_org_slug", orgSlug);
|
|
15148
|
-
const
|
|
15149
|
-
if (
|
|
15150
|
-
sessionStorage.setItem("email",
|
|
15156
|
+
const contact = resolveContactFromJwt(decoded);
|
|
15157
|
+
if (contact) {
|
|
15158
|
+
sessionStorage.setItem("email", contact);
|
|
15151
15159
|
}
|
|
15152
15160
|
}
|
|
15153
15161
|
}
|
package/dist/privacy-center.mjs
CHANGED
|
@@ -9205,6 +9205,14 @@ var decodeJwtToken = (token) => {
|
|
|
9205
9205
|
return null;
|
|
9206
9206
|
}
|
|
9207
9207
|
};
|
|
9208
|
+
var resolveContactFromJwt = (decoded) => {
|
|
9209
|
+
if (!decoded) {
|
|
9210
|
+
return null;
|
|
9211
|
+
}
|
|
9212
|
+
const userData = decoded.user_data || {};
|
|
9213
|
+
const candidate = decoded.contact || decoded.primary_email || userData.primary_email || userData.primary_mobile || userData.org_user_id;
|
|
9214
|
+
return typeof candidate === "string" && candidate.trim() !== "" ? candidate : null;
|
|
9215
|
+
};
|
|
9208
9216
|
var isJwtExpired = (token, bufferSeconds = 5 * 60) => {
|
|
9209
9217
|
if (!token || typeof token !== "string") {
|
|
9210
9218
|
return true;
|
|
@@ -9932,9 +9940,9 @@ var syncJwtDataToSessionStorage = (accessToken) => {
|
|
|
9932
9940
|
if (!decoded) {
|
|
9933
9941
|
return;
|
|
9934
9942
|
}
|
|
9935
|
-
const
|
|
9936
|
-
if (
|
|
9937
|
-
sessionStorage.setItem("email",
|
|
9943
|
+
const contact = resolveContactFromJwt(decoded) || sessionStorage.getItem("email") || (typeof localStorage !== "undefined" ? localStorage.getItem("userEmail") : null);
|
|
9944
|
+
if (contact && typeof contact === "string") {
|
|
9945
|
+
sessionStorage.setItem("email", contact);
|
|
9938
9946
|
}
|
|
9939
9947
|
const orgUuid = decoded.organisation_uuid;
|
|
9940
9948
|
if (orgUuid && typeof orgUuid === "string") {
|
|
@@ -15085,9 +15093,9 @@ function PrivacyCenterLayout({ children, slug, onBack }) {
|
|
|
15085
15093
|
if (orgId) sessionStorage.setItem("pc_org_id", orgId);
|
|
15086
15094
|
if (workspaceId) sessionStorage.setItem("pc_workspace_id", workspaceId);
|
|
15087
15095
|
if (orgSlug) sessionStorage.setItem("pc_org_slug", orgSlug);
|
|
15088
|
-
const
|
|
15089
|
-
if (
|
|
15090
|
-
sessionStorage.setItem("email",
|
|
15096
|
+
const contact = resolveContactFromJwt(decoded);
|
|
15097
|
+
if (contact) {
|
|
15098
|
+
sessionStorage.setItem("email", contact);
|
|
15091
15099
|
}
|
|
15092
15100
|
}
|
|
15093
15101
|
}
|
package/package.json
CHANGED
|
@@ -5,6 +5,7 @@ import type { OrganizationDetail } from './lib/types';
|
|
|
5
5
|
import {
|
|
6
6
|
checkAndRefreshAuth,
|
|
7
7
|
decodeJwtToken,
|
|
8
|
+
resolveContactFromJwt,
|
|
8
9
|
} from './lib/utils';
|
|
9
10
|
import { useBaseUrl } from './context/BaseUrlContext';
|
|
10
11
|
import { useAuth } from './context/AuthContext';
|
|
@@ -56,10 +57,11 @@ export function PrivacyCenterLayout({ children, slug, onBack }: PrivacyCenterLay
|
|
|
56
57
|
if (workspaceId) sessionStorage.setItem('pc_workspace_id', workspaceId);
|
|
57
58
|
if (orgSlug) sessionStorage.setItem('pc_org_slug', orgSlug);
|
|
58
59
|
|
|
59
|
-
//
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
// Resolve the contact identifier (email, or mobile/org_user_id for
|
|
61
|
+
// mobile-only logins) for the sidebar and consent manager.
|
|
62
|
+
const contact = resolveContactFromJwt(decoded);
|
|
63
|
+
if (contact) {
|
|
64
|
+
sessionStorage.setItem('email', contact);
|
|
63
65
|
}
|
|
64
66
|
}
|
|
65
67
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createContext, useContext, useState, useEffect, useCallback, useMemo, type ReactNode } from 'react';
|
|
2
|
-
import { setRefreshTokenCookie, decodeJwtToken, setSessionExpiredListener } from '../lib/utils';
|
|
2
|
+
import { setRefreshTokenCookie, decodeJwtToken, resolveContactFromJwt, setSessionExpiredListener } from '../lib/utils';
|
|
3
3
|
import type { AuthContextValue } from './types';
|
|
4
4
|
|
|
5
5
|
export type { AuthContextValue } from './types';
|
|
@@ -25,12 +25,14 @@ const syncJwtDataToSessionStorage = (accessToken: string): void => {
|
|
|
25
25
|
return;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
//
|
|
29
|
-
|
|
28
|
+
// Resolve the user's contact identifier (email, or mobile/org_user_id for
|
|
29
|
+
// mobile-only logins) so case + consent-management actions have a subject.
|
|
30
|
+
// Falls back to previously stored values when the token omits identifiers.
|
|
31
|
+
const contact = resolveContactFromJwt(decoded)
|
|
30
32
|
|| sessionStorage.getItem('email')
|
|
31
33
|
|| (typeof localStorage !== 'undefined' ? localStorage.getItem('userEmail') : null);
|
|
32
|
-
if (
|
|
33
|
-
sessionStorage.setItem('email',
|
|
34
|
+
if (contact && typeof contact === 'string') {
|
|
35
|
+
sessionStorage.setItem('email', contact);
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
// Extract organization UUID
|
|
@@ -244,6 +244,34 @@ export const decodeJwtToken = (token: string): { organisation_uuid?: string; org
|
|
|
244
244
|
}
|
|
245
245
|
};
|
|
246
246
|
|
|
247
|
+
/**
|
|
248
|
+
* Resolve the user's contact identifier from a decoded JWT.
|
|
249
|
+
*
|
|
250
|
+
* Identifiers live under `user_data` (primary_email / primary_mobile /
|
|
251
|
+
* org_user_id); some token shapes also expose `contact`/`primary_email` at the
|
|
252
|
+
* top level. Email is preferred, but a mobile-only login must still resolve to
|
|
253
|
+
* the phone (or org_user_id) so case/grievance and consent-management actions —
|
|
254
|
+
* which key off this contact — work without an email. The backend matches
|
|
255
|
+
* identifiers type-aware (phone-to-phone), so storing the mobile here is safe.
|
|
256
|
+
*/
|
|
257
|
+
export const resolveContactFromJwt = (
|
|
258
|
+
decoded: { [key: string]: any } | null,
|
|
259
|
+
): string | null => {
|
|
260
|
+
if (!decoded) {
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
const userData = (decoded.user_data as Record<string, unknown>) || {};
|
|
264
|
+
const candidate =
|
|
265
|
+
decoded.contact ||
|
|
266
|
+
decoded.primary_email ||
|
|
267
|
+
userData.primary_email ||
|
|
268
|
+
userData.primary_mobile ||
|
|
269
|
+
userData.org_user_id;
|
|
270
|
+
return typeof candidate === 'string' && candidate.trim() !== ''
|
|
271
|
+
? candidate
|
|
272
|
+
: null;
|
|
273
|
+
};
|
|
274
|
+
|
|
247
275
|
/**
|
|
248
276
|
* Decode JWT payload and check if token is expired or will expire soon
|
|
249
277
|
* @param token - JWT token string
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { resolveContactFromJwt } from '../src/RedactoPrivacyCenter/lib/utils';
|
|
4
|
+
|
|
5
|
+
describe('resolveContactFromJwt', () => {
|
|
6
|
+
it('prefers the email when present in user_data', () => {
|
|
7
|
+
const decoded = {
|
|
8
|
+
user_data: {
|
|
9
|
+
primary_email: 'user@example.com',
|
|
10
|
+
primary_mobile: '+918955610253',
|
|
11
|
+
org_user_id: '+918955610253',
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
expect(resolveContactFromJwt(decoded)).toBe('user@example.com');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('falls back to the mobile for a mobile-only login (no email)', () => {
|
|
18
|
+
const decoded = {
|
|
19
|
+
user_data: {
|
|
20
|
+
primary_email: null,
|
|
21
|
+
primary_mobile: '+918955610253',
|
|
22
|
+
org_user_id: '+918955610253',
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
expect(resolveContactFromJwt(decoded)).toBe('+918955610253');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('falls back to org_user_id when neither email nor mobile is present', () => {
|
|
29
|
+
const decoded = { user_data: { org_user_id: 'ucid-123' } };
|
|
30
|
+
expect(resolveContactFromJwt(decoded)).toBe('ucid-123');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('reads top-level contact / primary_email when not nested', () => {
|
|
34
|
+
expect(resolveContactFromJwt({ contact: 'a@b.com' })).toBe('a@b.com');
|
|
35
|
+
expect(resolveContactFromJwt({ primary_email: 'c@d.com' })).toBe('c@d.com');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('returns null for missing, empty, or whitespace identifiers', () => {
|
|
39
|
+
expect(resolveContactFromJwt(null)).toBeNull();
|
|
40
|
+
expect(resolveContactFromJwt({})).toBeNull();
|
|
41
|
+
expect(resolveContactFromJwt({ user_data: {} })).toBeNull();
|
|
42
|
+
expect(resolveContactFromJwt({ user_data: { primary_mobile: ' ' } })).toBeNull();
|
|
43
|
+
});
|
|
44
|
+
});
|