@oxy.so/core 7.10.3 → 7.10.4
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/dist/cjs/HttpService.js +54 -1
- package/dist/esm/HttpService.js +54 -1
- package/dist/types/HttpService.d.ts +4 -0
- package/package.json +1 -1
- package/src/HttpService.ts +61 -1
package/dist/cjs/HttpService.js
CHANGED
|
@@ -58,6 +58,14 @@ function sameTokenSession(left, right) {
|
|
|
58
58
|
}
|
|
59
59
|
if (!(a.userId || a.id || a.sub) || !(b.userId || b.id || b.sub))
|
|
60
60
|
return false;
|
|
61
|
+
const leftBinding = knownTokenContext(left);
|
|
62
|
+
const rightBinding = knownTokenContext(right);
|
|
63
|
+
if ((leftBinding.actor && rightBinding.actor && leftBinding.actor !== rightBinding.actor) ||
|
|
64
|
+
(leftBinding.context && rightBinding.context && leftBinding.context !== rightBinding.context) ||
|
|
65
|
+
(leftBinding.deviceSession &&
|
|
66
|
+
rightBinding.deviceSession &&
|
|
67
|
+
leftBinding.deviceSession !== rightBinding.deviceSession))
|
|
68
|
+
return false;
|
|
61
69
|
// A legacy bearer may omit deviceId; empty or non-string values also carry no
|
|
62
70
|
// usable device. Two known, different devices are distinct local contexts.
|
|
63
71
|
if (typeof a.deviceId === 'string' &&
|
|
@@ -79,6 +87,20 @@ function sameTokenSession(left, right) {
|
|
|
79
87
|
'authMethodId',
|
|
80
88
|
].every((key) => (a[key] === undefined || typeof a[key] === 'string') && a[key] === b[key]);
|
|
81
89
|
}
|
|
90
|
+
/** Decoded claims only fence local cache/transport; they do not authorize a token. */
|
|
91
|
+
function knownTokenContext(token) {
|
|
92
|
+
const claims = (0, tokenClaims_1.decodeTokenClaims)(token);
|
|
93
|
+
if (!claims || claims.type === 'service')
|
|
94
|
+
return { actor: null, context: null, deviceSession: null };
|
|
95
|
+
const actor = claims.act;
|
|
96
|
+
const actorId = actor && typeof actor === 'object' ? actor.sub : undefined;
|
|
97
|
+
const known = (value) => (typeof value === 'string' && value.length > 0 ? value : null);
|
|
98
|
+
return {
|
|
99
|
+
actor: known(actorId),
|
|
100
|
+
context: known(claims.device_context_id),
|
|
101
|
+
deviceSession: known(claims.device_session_id),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
82
104
|
/** Extract a known device for local ownership checks, never authorization. */
|
|
83
105
|
function knownTokenDeviceId(token) {
|
|
84
106
|
const claims = (0, tokenClaims_1.decodeTokenClaims)(token);
|
|
@@ -345,6 +367,10 @@ class HttpService {
|
|
|
345
367
|
this.sessionEpoch = 0;
|
|
346
368
|
/** Preserve a known device across legacy rotations which omit the claim. */
|
|
347
369
|
this.sessionDeviceId = null;
|
|
370
|
+
/** Preserve known actor and context hints across rotations without those claims. */
|
|
371
|
+
this.sessionActorId = null;
|
|
372
|
+
this.sessionContextId = null;
|
|
373
|
+
this.sessionDeviceSessionId = null;
|
|
348
374
|
/**
|
|
349
375
|
* Set by {@link endSession}, cleared by the next {@link setTokens}. Read by the
|
|
350
376
|
* native Commons-identity re-mint arm: after a sign-out a 401 is the expected
|
|
@@ -792,6 +818,9 @@ class HttpService {
|
|
|
792
818
|
this.sessionEpoch += 1;
|
|
793
819
|
this.tokenStore.clearTokens();
|
|
794
820
|
this.sessionDeviceId = null;
|
|
821
|
+
this.sessionActorId = null;
|
|
822
|
+
this.sessionContextId = null;
|
|
823
|
+
this.sessionDeviceSessionId = null;
|
|
795
824
|
selfClearedEpoch = this.sessionEpoch;
|
|
796
825
|
this.notifyTokenChange();
|
|
797
826
|
}
|
|
@@ -1676,12 +1705,30 @@ class HttpService {
|
|
|
1676
1705
|
setTokens(accessToken) {
|
|
1677
1706
|
const current = this.tokenStore.getAccessToken();
|
|
1678
1707
|
const deviceId = knownTokenDeviceId(accessToken);
|
|
1708
|
+
const binding = knownTokenContext(accessToken);
|
|
1679
1709
|
const changed = current !== null &&
|
|
1680
1710
|
(!sameTokenSession(current, accessToken) ||
|
|
1681
|
-
(this.sessionDeviceId !== null && deviceId !== null && this.sessionDeviceId !== deviceId)
|
|
1711
|
+
(this.sessionDeviceId !== null && deviceId !== null && this.sessionDeviceId !== deviceId) ||
|
|
1712
|
+
(this.sessionActorId !== null &&
|
|
1713
|
+
binding.actor !== null &&
|
|
1714
|
+
this.sessionActorId !== binding.actor) ||
|
|
1715
|
+
(this.sessionContextId !== null &&
|
|
1716
|
+
binding.context !== null &&
|
|
1717
|
+
this.sessionContextId !== binding.context) ||
|
|
1718
|
+
(this.sessionDeviceSessionId !== null &&
|
|
1719
|
+
binding.deviceSession !== null &&
|
|
1720
|
+
this.sessionDeviceSessionId !== binding.deviceSession));
|
|
1682
1721
|
if (changed)
|
|
1683
1722
|
this.sessionEpoch += 1;
|
|
1684
1723
|
this.sessionDeviceId = !current || changed ? deviceId : (deviceId ?? this.sessionDeviceId);
|
|
1724
|
+
this.sessionActorId =
|
|
1725
|
+
!current || changed ? binding.actor : (binding.actor ?? this.sessionActorId);
|
|
1726
|
+
this.sessionContextId =
|
|
1727
|
+
!current || changed ? binding.context : (binding.context ?? this.sessionContextId);
|
|
1728
|
+
this.sessionDeviceSessionId =
|
|
1729
|
+
!current || changed
|
|
1730
|
+
? binding.deviceSession
|
|
1731
|
+
: (binding.deviceSession ?? this.sessionDeviceSessionId);
|
|
1685
1732
|
this.sessionEnded = false;
|
|
1686
1733
|
this.tokenStore.setTokens(accessToken);
|
|
1687
1734
|
this.notifyTokenChange();
|
|
@@ -1733,6 +1780,9 @@ class HttpService {
|
|
|
1733
1780
|
this.sessionEpoch += 1;
|
|
1734
1781
|
this.tokenStore.clearTokens();
|
|
1735
1782
|
this.sessionDeviceId = null;
|
|
1783
|
+
this.sessionActorId = null;
|
|
1784
|
+
this.sessionContextId = null;
|
|
1785
|
+
this.sessionDeviceSessionId = null;
|
|
1736
1786
|
// Drop the response cache on logout. The cache is identity-scoped, so a
|
|
1737
1787
|
// different user could never read these entries, but a logged-out client
|
|
1738
1788
|
// must not keep the previous session's personalized data resident in
|
|
@@ -1893,6 +1943,9 @@ class HttpService {
|
|
|
1893
1943
|
this.sessionEnded = false;
|
|
1894
1944
|
this.tokenStore.clearTokens();
|
|
1895
1945
|
this.sessionDeviceId = null;
|
|
1946
|
+
this.sessionActorId = null;
|
|
1947
|
+
this.sessionContextId = null;
|
|
1948
|
+
this.sessionDeviceSessionId = null;
|
|
1896
1949
|
}
|
|
1897
1950
|
}
|
|
1898
1951
|
exports.HttpService = HttpService;
|
package/dist/esm/HttpService.js
CHANGED
|
@@ -55,6 +55,14 @@ function sameTokenSession(left, right) {
|
|
|
55
55
|
}
|
|
56
56
|
if (!(a.userId || a.id || a.sub) || !(b.userId || b.id || b.sub))
|
|
57
57
|
return false;
|
|
58
|
+
const leftBinding = knownTokenContext(left);
|
|
59
|
+
const rightBinding = knownTokenContext(right);
|
|
60
|
+
if ((leftBinding.actor && rightBinding.actor && leftBinding.actor !== rightBinding.actor) ||
|
|
61
|
+
(leftBinding.context && rightBinding.context && leftBinding.context !== rightBinding.context) ||
|
|
62
|
+
(leftBinding.deviceSession &&
|
|
63
|
+
rightBinding.deviceSession &&
|
|
64
|
+
leftBinding.deviceSession !== rightBinding.deviceSession))
|
|
65
|
+
return false;
|
|
58
66
|
// A legacy bearer may omit deviceId; empty or non-string values also carry no
|
|
59
67
|
// usable device. Two known, different devices are distinct local contexts.
|
|
60
68
|
if (typeof a.deviceId === 'string' &&
|
|
@@ -76,6 +84,20 @@ function sameTokenSession(left, right) {
|
|
|
76
84
|
'authMethodId',
|
|
77
85
|
].every((key) => (a[key] === undefined || typeof a[key] === 'string') && a[key] === b[key]);
|
|
78
86
|
}
|
|
87
|
+
/** Decoded claims only fence local cache/transport; they do not authorize a token. */
|
|
88
|
+
function knownTokenContext(token) {
|
|
89
|
+
const claims = decodeTokenClaims(token);
|
|
90
|
+
if (!claims || claims.type === 'service')
|
|
91
|
+
return { actor: null, context: null, deviceSession: null };
|
|
92
|
+
const actor = claims.act;
|
|
93
|
+
const actorId = actor && typeof actor === 'object' ? actor.sub : undefined;
|
|
94
|
+
const known = (value) => (typeof value === 'string' && value.length > 0 ? value : null);
|
|
95
|
+
return {
|
|
96
|
+
actor: known(actorId),
|
|
97
|
+
context: known(claims.device_context_id),
|
|
98
|
+
deviceSession: known(claims.device_session_id),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
79
101
|
/** Extract a known device for local ownership checks, never authorization. */
|
|
80
102
|
function knownTokenDeviceId(token) {
|
|
81
103
|
const claims = decodeTokenClaims(token);
|
|
@@ -342,6 +364,10 @@ export class HttpService {
|
|
|
342
364
|
this.sessionEpoch = 0;
|
|
343
365
|
/** Preserve a known device across legacy rotations which omit the claim. */
|
|
344
366
|
this.sessionDeviceId = null;
|
|
367
|
+
/** Preserve known actor and context hints across rotations without those claims. */
|
|
368
|
+
this.sessionActorId = null;
|
|
369
|
+
this.sessionContextId = null;
|
|
370
|
+
this.sessionDeviceSessionId = null;
|
|
345
371
|
/**
|
|
346
372
|
* Set by {@link endSession}, cleared by the next {@link setTokens}. Read by the
|
|
347
373
|
* native Commons-identity re-mint arm: after a sign-out a 401 is the expected
|
|
@@ -789,6 +815,9 @@ export class HttpService {
|
|
|
789
815
|
this.sessionEpoch += 1;
|
|
790
816
|
this.tokenStore.clearTokens();
|
|
791
817
|
this.sessionDeviceId = null;
|
|
818
|
+
this.sessionActorId = null;
|
|
819
|
+
this.sessionContextId = null;
|
|
820
|
+
this.sessionDeviceSessionId = null;
|
|
792
821
|
selfClearedEpoch = this.sessionEpoch;
|
|
793
822
|
this.notifyTokenChange();
|
|
794
823
|
}
|
|
@@ -1673,12 +1702,30 @@ export class HttpService {
|
|
|
1673
1702
|
setTokens(accessToken) {
|
|
1674
1703
|
const current = this.tokenStore.getAccessToken();
|
|
1675
1704
|
const deviceId = knownTokenDeviceId(accessToken);
|
|
1705
|
+
const binding = knownTokenContext(accessToken);
|
|
1676
1706
|
const changed = current !== null &&
|
|
1677
1707
|
(!sameTokenSession(current, accessToken) ||
|
|
1678
|
-
(this.sessionDeviceId !== null && deviceId !== null && this.sessionDeviceId !== deviceId)
|
|
1708
|
+
(this.sessionDeviceId !== null && deviceId !== null && this.sessionDeviceId !== deviceId) ||
|
|
1709
|
+
(this.sessionActorId !== null &&
|
|
1710
|
+
binding.actor !== null &&
|
|
1711
|
+
this.sessionActorId !== binding.actor) ||
|
|
1712
|
+
(this.sessionContextId !== null &&
|
|
1713
|
+
binding.context !== null &&
|
|
1714
|
+
this.sessionContextId !== binding.context) ||
|
|
1715
|
+
(this.sessionDeviceSessionId !== null &&
|
|
1716
|
+
binding.deviceSession !== null &&
|
|
1717
|
+
this.sessionDeviceSessionId !== binding.deviceSession));
|
|
1679
1718
|
if (changed)
|
|
1680
1719
|
this.sessionEpoch += 1;
|
|
1681
1720
|
this.sessionDeviceId = !current || changed ? deviceId : (deviceId ?? this.sessionDeviceId);
|
|
1721
|
+
this.sessionActorId =
|
|
1722
|
+
!current || changed ? binding.actor : (binding.actor ?? this.sessionActorId);
|
|
1723
|
+
this.sessionContextId =
|
|
1724
|
+
!current || changed ? binding.context : (binding.context ?? this.sessionContextId);
|
|
1725
|
+
this.sessionDeviceSessionId =
|
|
1726
|
+
!current || changed
|
|
1727
|
+
? binding.deviceSession
|
|
1728
|
+
: (binding.deviceSession ?? this.sessionDeviceSessionId);
|
|
1682
1729
|
this.sessionEnded = false;
|
|
1683
1730
|
this.tokenStore.setTokens(accessToken);
|
|
1684
1731
|
this.notifyTokenChange();
|
|
@@ -1730,6 +1777,9 @@ export class HttpService {
|
|
|
1730
1777
|
this.sessionEpoch += 1;
|
|
1731
1778
|
this.tokenStore.clearTokens();
|
|
1732
1779
|
this.sessionDeviceId = null;
|
|
1780
|
+
this.sessionActorId = null;
|
|
1781
|
+
this.sessionContextId = null;
|
|
1782
|
+
this.sessionDeviceSessionId = null;
|
|
1733
1783
|
// Drop the response cache on logout. The cache is identity-scoped, so a
|
|
1734
1784
|
// different user could never read these entries, but a logged-out client
|
|
1735
1785
|
// must not keep the previous session's personalized data resident in
|
|
@@ -1890,6 +1940,9 @@ export class HttpService {
|
|
|
1890
1940
|
this.sessionEnded = false;
|
|
1891
1941
|
this.tokenStore.clearTokens();
|
|
1892
1942
|
this.sessionDeviceId = null;
|
|
1943
|
+
this.sessionActorId = null;
|
|
1944
|
+
this.sessionContextId = null;
|
|
1945
|
+
this.sessionDeviceSessionId = null;
|
|
1893
1946
|
}
|
|
1894
1947
|
}
|
|
1895
1948
|
/**
|
|
@@ -230,6 +230,10 @@ export declare class HttpService {
|
|
|
230
230
|
private sessionEpoch;
|
|
231
231
|
/** Preserve a known device across legacy rotations which omit the claim. */
|
|
232
232
|
private sessionDeviceId;
|
|
233
|
+
/** Preserve known actor and context hints across rotations without those claims. */
|
|
234
|
+
private sessionActorId;
|
|
235
|
+
private sessionContextId;
|
|
236
|
+
private sessionDeviceSessionId;
|
|
233
237
|
/**
|
|
234
238
|
* Set by {@link endSession}, cleared by the next {@link setTokens}. Read by the
|
|
235
239
|
* native Commons-identity re-mint arm: after a sign-out a 401 is the expected
|
package/package.json
CHANGED
package/src/HttpService.ts
CHANGED
|
@@ -74,6 +74,16 @@ function sameTokenSession(left: string | null, right: string | null): boolean {
|
|
|
74
74
|
);
|
|
75
75
|
}
|
|
76
76
|
if (!(a.userId || a.id || a.sub) || !(b.userId || b.id || b.sub)) return false;
|
|
77
|
+
const leftBinding = knownTokenContext(left);
|
|
78
|
+
const rightBinding = knownTokenContext(right);
|
|
79
|
+
if (
|
|
80
|
+
(leftBinding.actor && rightBinding.actor && leftBinding.actor !== rightBinding.actor) ||
|
|
81
|
+
(leftBinding.context && rightBinding.context && leftBinding.context !== rightBinding.context) ||
|
|
82
|
+
(leftBinding.deviceSession &&
|
|
83
|
+
rightBinding.deviceSession &&
|
|
84
|
+
leftBinding.deviceSession !== rightBinding.deviceSession)
|
|
85
|
+
)
|
|
86
|
+
return false;
|
|
77
87
|
// A legacy bearer may omit deviceId; empty or non-string values also carry no
|
|
78
88
|
// usable device. Two known, different devices are distinct local contexts.
|
|
79
89
|
if (
|
|
@@ -98,6 +108,25 @@ function sameTokenSession(left: string | null, right: string | null): boolean {
|
|
|
98
108
|
].every((key) => (a[key] === undefined || typeof a[key] === 'string') && a[key] === b[key]);
|
|
99
109
|
}
|
|
100
110
|
|
|
111
|
+
/** Decoded claims only fence local cache/transport; they do not authorize a token. */
|
|
112
|
+
function knownTokenContext(token: string): {
|
|
113
|
+
actor: string | null;
|
|
114
|
+
context: string | null;
|
|
115
|
+
deviceSession: string | null;
|
|
116
|
+
} {
|
|
117
|
+
const claims = decodeTokenClaims(token);
|
|
118
|
+
if (!claims || claims.type === 'service')
|
|
119
|
+
return { actor: null, context: null, deviceSession: null };
|
|
120
|
+
const actor = claims.act;
|
|
121
|
+
const actorId = actor && typeof actor === 'object' ? (actor as { sub?: unknown }).sub : undefined;
|
|
122
|
+
const known = (value: unknown) => (typeof value === 'string' && value.length > 0 ? value : null);
|
|
123
|
+
return {
|
|
124
|
+
actor: known(actorId),
|
|
125
|
+
context: known(claims.device_context_id),
|
|
126
|
+
deviceSession: known(claims.device_session_id),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
101
130
|
/** Extract a known device for local ownership checks, never authorization. */
|
|
102
131
|
function knownTokenDeviceId(token: string): string | null {
|
|
103
132
|
const claims = decodeTokenClaims(token);
|
|
@@ -549,6 +578,10 @@ export class HttpService {
|
|
|
549
578
|
private sessionEpoch = 0;
|
|
550
579
|
/** Preserve a known device across legacy rotations which omit the claim. */
|
|
551
580
|
private sessionDeviceId: string | null = null;
|
|
581
|
+
/** Preserve known actor and context hints across rotations without those claims. */
|
|
582
|
+
private sessionActorId: string | null = null;
|
|
583
|
+
private sessionContextId: string | null = null;
|
|
584
|
+
private sessionDeviceSessionId: string | null = null;
|
|
552
585
|
/**
|
|
553
586
|
* Set by {@link endSession}, cleared by the next {@link setTokens}. Read by the
|
|
554
587
|
* native Commons-identity re-mint arm: after a sign-out a 401 is the expected
|
|
@@ -1083,6 +1116,9 @@ export class HttpService {
|
|
|
1083
1116
|
if (this.tokenStore.getAccessToken()) this.sessionEpoch += 1;
|
|
1084
1117
|
this.tokenStore.clearTokens();
|
|
1085
1118
|
this.sessionDeviceId = null;
|
|
1119
|
+
this.sessionActorId = null;
|
|
1120
|
+
this.sessionContextId = null;
|
|
1121
|
+
this.sessionDeviceSessionId = null;
|
|
1086
1122
|
selfClearedEpoch = this.sessionEpoch;
|
|
1087
1123
|
this.notifyTokenChange();
|
|
1088
1124
|
}
|
|
@@ -2104,12 +2140,30 @@ export class HttpService {
|
|
|
2104
2140
|
setTokens(accessToken: string): void {
|
|
2105
2141
|
const current = this.tokenStore.getAccessToken();
|
|
2106
2142
|
const deviceId = knownTokenDeviceId(accessToken);
|
|
2143
|
+
const binding = knownTokenContext(accessToken);
|
|
2107
2144
|
const changed =
|
|
2108
2145
|
current !== null &&
|
|
2109
2146
|
(!sameTokenSession(current, accessToken) ||
|
|
2110
|
-
(this.sessionDeviceId !== null && deviceId !== null && this.sessionDeviceId !== deviceId)
|
|
2147
|
+
(this.sessionDeviceId !== null && deviceId !== null && this.sessionDeviceId !== deviceId) ||
|
|
2148
|
+
(this.sessionActorId !== null &&
|
|
2149
|
+
binding.actor !== null &&
|
|
2150
|
+
this.sessionActorId !== binding.actor) ||
|
|
2151
|
+
(this.sessionContextId !== null &&
|
|
2152
|
+
binding.context !== null &&
|
|
2153
|
+
this.sessionContextId !== binding.context) ||
|
|
2154
|
+
(this.sessionDeviceSessionId !== null &&
|
|
2155
|
+
binding.deviceSession !== null &&
|
|
2156
|
+
this.sessionDeviceSessionId !== binding.deviceSession));
|
|
2111
2157
|
if (changed) this.sessionEpoch += 1;
|
|
2112
2158
|
this.sessionDeviceId = !current || changed ? deviceId : (deviceId ?? this.sessionDeviceId);
|
|
2159
|
+
this.sessionActorId =
|
|
2160
|
+
!current || changed ? binding.actor : (binding.actor ?? this.sessionActorId);
|
|
2161
|
+
this.sessionContextId =
|
|
2162
|
+
!current || changed ? binding.context : (binding.context ?? this.sessionContextId);
|
|
2163
|
+
this.sessionDeviceSessionId =
|
|
2164
|
+
!current || changed
|
|
2165
|
+
? binding.deviceSession
|
|
2166
|
+
: (binding.deviceSession ?? this.sessionDeviceSessionId);
|
|
2113
2167
|
this.sessionEnded = false;
|
|
2114
2168
|
this.tokenStore.setTokens(accessToken);
|
|
2115
2169
|
this.notifyTokenChange();
|
|
@@ -2166,6 +2220,9 @@ export class HttpService {
|
|
|
2166
2220
|
if (this.tokenStore.getAccessToken()) this.sessionEpoch += 1;
|
|
2167
2221
|
this.tokenStore.clearTokens();
|
|
2168
2222
|
this.sessionDeviceId = null;
|
|
2223
|
+
this.sessionActorId = null;
|
|
2224
|
+
this.sessionContextId = null;
|
|
2225
|
+
this.sessionDeviceSessionId = null;
|
|
2169
2226
|
// Drop the response cache on logout. The cache is identity-scoped, so a
|
|
2170
2227
|
// different user could never read these entries, but a logged-out client
|
|
2171
2228
|
// must not keep the previous session's personalized data resident in
|
|
@@ -2340,5 +2397,8 @@ export class HttpService {
|
|
|
2340
2397
|
this.sessionEnded = false;
|
|
2341
2398
|
this.tokenStore.clearTokens();
|
|
2342
2399
|
this.sessionDeviceId = null;
|
|
2400
|
+
this.sessionActorId = null;
|
|
2401
|
+
this.sessionContextId = null;
|
|
2402
|
+
this.sessionDeviceSessionId = null;
|
|
2343
2403
|
}
|
|
2344
2404
|
}
|