@oxyhq/core 4.0.1 → 5.1.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/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/HttpService.js +6 -18
- package/dist/cjs/OxyServices.base.js +0 -21
- package/dist/cjs/crypto/keyManager.js +7 -7
- package/dist/cjs/crypto/polyfill.js +6 -5
- package/dist/cjs/crypto/signatureService.js +44 -220
- package/dist/cjs/index.js +4 -8
- package/dist/cjs/mixins/OxyServices.accounts.js +90 -0
- package/dist/cjs/mixins/OxyServices.assets.js +68 -2
- package/dist/cjs/mixins/OxyServices.auth.js +3 -3
- package/dist/cjs/mixins/OxyServices.civic.js +3 -3
- package/dist/cjs/mixins/OxyServices.language.js +2 -2
- package/dist/cjs/mixins/OxyServices.utility.js +7 -95
- package/dist/cjs/utils/cacheKey.js +17 -19
- package/dist/cjs/utils/deviceManager.js +2 -2
- package/dist/cjs/utils/platform.js +0 -14
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/HttpService.js +6 -18
- package/dist/esm/OxyServices.base.js +0 -21
- package/dist/esm/crypto/keyManager.js +4 -4
- package/dist/esm/crypto/polyfill.js +5 -4
- package/dist/esm/crypto/signatureService.js +39 -214
- package/dist/esm/index.js +1 -2
- package/dist/esm/mixins/OxyServices.accounts.js +90 -0
- package/dist/esm/mixins/OxyServices.assets.js +67 -1
- package/dist/esm/mixins/OxyServices.auth.js +1 -1
- package/dist/esm/mixins/OxyServices.civic.js +3 -3
- package/dist/esm/mixins/OxyServices.language.js +1 -1
- package/dist/esm/mixins/OxyServices.utility.js +6 -94
- package/dist/esm/utils/cacheKey.js +17 -19
- package/dist/esm/utils/deviceManager.js +1 -1
- package/dist/esm/utils/platform.js +0 -12
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/HttpService.d.ts +3 -6
- package/dist/types/OxyServices.base.d.ts +0 -17
- package/dist/types/crypto/polyfill.d.ts +2 -2
- package/dist/types/crypto/signatureService.d.ts +18 -84
- package/dist/types/index.d.ts +4 -5
- package/dist/types/mixins/OxyServices.accounts.d.ts +66 -5
- package/dist/types/mixins/OxyServices.analytics.d.ts +0 -2
- package/dist/types/mixins/OxyServices.appData.d.ts +0 -2
- package/dist/types/mixins/OxyServices.assets.d.ts +42 -3
- package/dist/types/mixins/OxyServices.auth.d.ts +0 -2
- package/dist/types/mixins/OxyServices.civic.d.ts +3 -5
- package/dist/types/mixins/OxyServices.connectedApps.d.ts +0 -2
- package/dist/types/mixins/OxyServices.contacts.d.ts +0 -2
- package/dist/types/mixins/OxyServices.devices.d.ts +0 -2
- package/dist/types/mixins/OxyServices.features.d.ts +0 -2
- package/dist/types/mixins/OxyServices.fedcm.d.ts +0 -2
- package/dist/types/mixins/OxyServices.identity.d.ts +8 -5
- package/dist/types/mixins/OxyServices.language.d.ts +0 -2
- package/dist/types/mixins/OxyServices.links.d.ts +0 -2
- package/dist/types/mixins/OxyServices.location.d.ts +0 -2
- package/dist/types/mixins/OxyServices.nodes.d.ts +0 -44
- package/dist/types/mixins/OxyServices.payment.d.ts +0 -2
- package/dist/types/mixins/OxyServices.privacy.d.ts +0 -2
- package/dist/types/mixins/OxyServices.redirect.d.ts +0 -2
- package/dist/types/mixins/OxyServices.reputation.d.ts +0 -2
- package/dist/types/mixins/OxyServices.security.d.ts +0 -2
- package/dist/types/mixins/OxyServices.silent.d.ts +0 -2
- package/dist/types/mixins/OxyServices.sso.d.ts +0 -2
- package/dist/types/mixins/OxyServices.topics.d.ts +0 -2
- package/dist/types/mixins/OxyServices.user.d.ts +0 -2
- package/dist/types/mixins/OxyServices.utility.d.ts +0 -32
- package/dist/types/models/interfaces.d.ts +18 -0
- package/dist/types/server/auth.d.ts +0 -6
- package/dist/types/server/index.d.ts +1 -1
- package/dist/types/utils/cacheKey.d.ts +6 -7
- package/dist/types/utils/platform.d.ts +0 -8
- package/package.json +4 -7
- package/src/HttpService.ts +6 -22
- package/src/OxyServices.base.ts +0 -23
- package/src/__tests__/httpServiceCache.test.ts +0 -19
- package/src/crypto/__tests__/keyManager.atomicity.test.ts +2 -1
- package/src/crypto/__tests__/keyManager.test.ts +9 -7
- package/src/crypto/__tests__/signChallengeShared.test.ts +2 -1
- package/src/crypto/__tests__/signedRecord.test.ts +37 -150
- package/src/crypto/keyManager.ts +28 -17
- package/src/crypto/polyfill.ts +5 -4
- package/src/crypto/signatureService.ts +67 -255
- package/src/index.ts +4 -3
- package/src/mixins/OxyServices.accounts.ts +136 -3
- package/src/mixins/OxyServices.assets.ts +96 -2
- package/src/mixins/OxyServices.auth.ts +1 -1
- package/src/mixins/OxyServices.civic.ts +6 -17
- package/src/mixins/OxyServices.identity.ts +8 -2
- package/src/mixins/OxyServices.language.ts +1 -1
- package/src/mixins/OxyServices.nodes.ts +1 -12
- package/src/mixins/OxyServices.utility.ts +6 -119
- package/src/mixins/__tests__/OxyServices.civic.test.ts +2 -2
- package/src/mixins/__tests__/OxyServices.serviceAssetMetadata.test.ts +116 -0
- package/src/mixins/__tests__/accounts.test.ts +120 -0
- package/src/models/interfaces.ts +19 -0
- package/src/server/auth.ts +0 -7
- package/src/server/index.ts +0 -1
- package/src/utils/__tests__/cacheKey.test.ts +0 -0
- package/src/utils/cacheKey.ts +16 -21
- package/src/utils/deviceManager.ts +1 -1
- package/src/utils/platform.ts +0 -14
- package/dist/cjs/crypto/canonicalJson.js +0 -107
- package/dist/cjs/utils/platformCrypto.js +0 -165
- package/dist/cjs/utils/platformCrypto.native.js +0 -123
- package/dist/esm/crypto/canonicalJson.js +0 -104
- package/dist/esm/utils/platformCrypto.js +0 -125
- package/dist/esm/utils/platformCrypto.native.js +0 -80
- package/dist/types/crypto/canonicalJson.d.ts +0 -44
- package/dist/types/utils/platformCrypto.d.ts +0 -87
- package/dist/types/utils/platformCrypto.native.d.ts +0 -54
- package/src/crypto/__tests__/canonicalJson.test.ts +0 -116
- package/src/crypto/canonicalJson.ts +0 -120
- package/src/utils/platformCrypto.native.ts +0 -101
- package/src/utils/platformCrypto.ts +0 -145
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
import { isReactNative } from '
|
|
1
|
+
import { isReactNative } from '@oxyhq/protocol';
|
|
2
|
+
import { logger } from '../utils/loggerUtils.js';
|
|
3
|
+
import { extractErrorStatus } from '../utils/errorUtils.js';
|
|
4
|
+
/**
|
|
5
|
+
* Maximum number of ids sent per `POST /assets/service/by-ids` request. Matches
|
|
6
|
+
* the server-side batch cap (the route rejects empty or > 100 id arrays with a
|
|
7
|
+
* 400); larger inputs are split into multiple chunked calls and merged. Mirrors
|
|
8
|
+
* `getUsersByIds`'s `USERS_BY_IDS_CHUNK_SIZE`.
|
|
9
|
+
*/
|
|
10
|
+
const SERVICE_ASSET_METADATA_CHUNK_SIZE = 100;
|
|
2
11
|
export function OxyServicesAssetsMixin(Base) {
|
|
3
12
|
return class extends Base {
|
|
4
13
|
constructor(...args) {
|
|
@@ -144,6 +153,63 @@ export function OxyServicesAssetsMixin(Base) {
|
|
|
144
153
|
}
|
|
145
154
|
return urls;
|
|
146
155
|
}
|
|
156
|
+
/**
|
|
157
|
+
* Resolve many Oxy asset ids to their content-addressed metadata in one
|
|
158
|
+
* round-trip per chunk via `POST /assets/service/by-ids` (body `{ ids }`).
|
|
159
|
+
*
|
|
160
|
+
* Returns each asset's `sha256`, `mime`, byte `size`, and `status` — built
|
|
161
|
+
* for server-to-server callers (e.g. Mention's MTN Protocol blob-ref
|
|
162
|
+
* resolution) that need the content hash for an asset id. Ids are
|
|
163
|
+
* deduplicated and validated (empty/blank ids dropped) before being split
|
|
164
|
+
* into chunks of {@link SERVICE_ASSET_METADATA_CHUNK_SIZE} (the server-side
|
|
165
|
+
* cap). The server omits unknown/deleted ids from each chunk's `data`, so
|
|
166
|
+
* the merged result may be shorter than the requested id list and the caller
|
|
167
|
+
* is expected to map by `id`.
|
|
168
|
+
*
|
|
169
|
+
* **Service-token auth (required).** `/assets/service/by-ids` is guarded by
|
|
170
|
+
* `serviceAuthMiddleware` + the `files:read` scope and is called via
|
|
171
|
+
* `makeServiceRequest`, which attaches `Authorization: Bearer <serviceToken>`
|
|
172
|
+
* (the same client that calls `POST /assets/service/cache`). The calling
|
|
173
|
+
* client MUST be service-configured (`configureServiceAuth(apiKey,
|
|
174
|
+
* apiSecret)`) before invoking this method; otherwise `getServiceToken()`
|
|
175
|
+
* throws because no credentials are available. A plain user-session request
|
|
176
|
+
* is rejected by the route's service-auth guard.
|
|
177
|
+
*
|
|
178
|
+
* Resilience: chunks are independent. A failed chunk is logged and skipped —
|
|
179
|
+
* the method returns every entry that resolved successfully rather than
|
|
180
|
+
* discarding the whole call on one chunk's failure. An empty/whitespace-only
|
|
181
|
+
* input resolves immediately with `[]` and performs no network call.
|
|
182
|
+
*
|
|
183
|
+
* Not cached at the SDK layer: it's a POST keyed on a multi-id body (low hit
|
|
184
|
+
* rate), mirroring the sibling service/POST methods which never cache.
|
|
185
|
+
*/
|
|
186
|
+
async getServiceAssetMetadataByIds(ids) {
|
|
187
|
+
const uniqueIds = Array.from(new Set(ids.filter((id) => typeof id === 'string' && id.trim().length > 0)));
|
|
188
|
+
if (uniqueIds.length === 0) {
|
|
189
|
+
return [];
|
|
190
|
+
}
|
|
191
|
+
const chunks = [];
|
|
192
|
+
for (let i = 0; i < uniqueIds.length; i += SERVICE_ASSET_METADATA_CHUNK_SIZE) {
|
|
193
|
+
chunks.push(uniqueIds.slice(i, i + SERVICE_ASSET_METADATA_CHUNK_SIZE));
|
|
194
|
+
}
|
|
195
|
+
// Run chunks concurrently; a single chunk failure must not sink the rest.
|
|
196
|
+
const settled = await Promise.all(chunks.map(async (chunk) => {
|
|
197
|
+
try {
|
|
198
|
+
const entries = await this.makeServiceRequest('POST', '/assets/service/by-ids', { ids: chunk });
|
|
199
|
+
return Array.isArray(entries) ? entries : [];
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
logger.warn('getServiceAssetMetadataByIds: chunk failed, continuing with remaining chunks', {
|
|
203
|
+
method: 'getServiceAssetMetadataByIds',
|
|
204
|
+
chunkSize: chunk.length,
|
|
205
|
+
status: extractErrorStatus(error),
|
|
206
|
+
error: error instanceof Error ? error.message : String(error),
|
|
207
|
+
});
|
|
208
|
+
return [];
|
|
209
|
+
}
|
|
210
|
+
}));
|
|
211
|
+
return settled.flat();
|
|
212
|
+
}
|
|
147
213
|
/**
|
|
148
214
|
* Upload raw file data
|
|
149
215
|
*/
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { OxyAuthenticationError } from '../OxyServices.errors.js';
|
|
2
2
|
import { KeyManager } from '../crypto/keyManager.js';
|
|
3
3
|
import { SignatureService } from '../crypto/signatureService.js';
|
|
4
|
-
import { loadNodeCrypto } from '
|
|
4
|
+
import { loadNodeCrypto } from '@oxyhq/protocol';
|
|
5
5
|
import { logger } from '../utils/loggerUtils.js';
|
|
6
6
|
import { normalizeUserIdentity, normalizeUserIdentityOrNull } from '../utils/userIdentity.js';
|
|
7
7
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { canonicalize } from '
|
|
1
|
+
import { canonicalize, verifySignature } from '@oxyhq/protocol';
|
|
2
2
|
import { SignatureService } from '../crypto/signatureService.js';
|
|
3
3
|
import { buildUserDid } from './OxyServices.identity.js';
|
|
4
4
|
import { CACHE_TIMES } from './mixinHelpers.js';
|
|
@@ -150,7 +150,7 @@ export function parseAttestPayload(raw) {
|
|
|
150
150
|
* key, matching the server which omits absent keys entirely) and checks the
|
|
151
151
|
* `ES256K-DER-SHA256` signature against `attestation.publicKey`.
|
|
152
152
|
*
|
|
153
|
-
* NEVER throws: `
|
|
153
|
+
* NEVER throws: `verifySignature` already swallows malformed-input
|
|
154
154
|
* errors and returns `false`, and an absent attestation short-circuits to
|
|
155
155
|
* `false`. A pure, reusable helper (Commons can call it on a cached card).
|
|
156
156
|
*
|
|
@@ -165,7 +165,7 @@ export async function verifyPublicCardAttestation(card, attestation) {
|
|
|
165
165
|
if (!signature || !publicKey) {
|
|
166
166
|
return false;
|
|
167
167
|
}
|
|
168
|
-
return
|
|
168
|
+
return verifySignature(canonicalize(card), signature, publicKey);
|
|
169
169
|
}
|
|
170
170
|
export function OxyServicesCivicMixin(Base) {
|
|
171
171
|
return class extends Base {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Language Methods Mixin
|
|
3
3
|
*/
|
|
4
4
|
import { normalizeLanguageCode, getLanguageMetadata, getLanguageName, getNativeLanguageName } from '../utils/languageUtils.js';
|
|
5
|
-
import { loadAsyncStorage } from '
|
|
5
|
+
import { loadAsyncStorage } from '@oxyhq/protocol';
|
|
6
6
|
import { isDev } from '../shared/utils/debugUtils.js';
|
|
7
7
|
export function OxyServicesLanguageMixin(Base) {
|
|
8
8
|
return class extends Base {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* and Express.js authentication middleware
|
|
6
6
|
*/
|
|
7
7
|
import { jwtDecode } from 'jwt-decode';
|
|
8
|
-
import { loadNodeCrypto } from '
|
|
8
|
+
import { loadNodeCrypto } from '@oxyhq/protocol';
|
|
9
9
|
import { logger } from '../utils/loggerUtils.js';
|
|
10
10
|
import { CACHE_TIMES } from './mixinHelpers.js';
|
|
11
11
|
/**
|
|
@@ -47,8 +47,6 @@ export function OxyServicesUtilityMixin(Base) {
|
|
|
47
47
|
// monorepo-wide refactor of the mixin pipeline.
|
|
48
48
|
constructor(...args) {
|
|
49
49
|
super(...args);
|
|
50
|
-
/** @internal In-memory cache for acting-as verification results (TTL: 5 min) */
|
|
51
|
-
this._actingAsCache = new Map();
|
|
52
50
|
/**
|
|
53
51
|
* In-memory cache for service-acting-as verification.
|
|
54
52
|
* Negative results are cached for 1min to avoid hammering the verify
|
|
@@ -58,46 +56,6 @@ export function OxyServicesUtilityMixin(Base) {
|
|
|
58
56
|
*/
|
|
59
57
|
this._serviceActingAsCache = new Map();
|
|
60
58
|
}
|
|
61
|
-
/**
|
|
62
|
-
* Verify that a user is authorized to act as an account (direct membership
|
|
63
|
-
* or inherited via an ancestor). Backed by `GET /accounts/verify-acting-as`.
|
|
64
|
-
* Results are cached in-memory for 5 minutes to avoid repeated API calls.
|
|
65
|
-
*
|
|
66
|
-
* @internal Used by the auth() middleware — not part of the public API
|
|
67
|
-
*/
|
|
68
|
-
async verifyActingAs(userId, accountId) {
|
|
69
|
-
const cacheKey = `${userId}:${accountId}`;
|
|
70
|
-
const now = Date.now();
|
|
71
|
-
// Check cache
|
|
72
|
-
const cached = this._actingAsCache.get(cacheKey);
|
|
73
|
-
if (cached && cached.expiresAt > now) {
|
|
74
|
-
return cached.result;
|
|
75
|
-
}
|
|
76
|
-
// Query the API
|
|
77
|
-
try {
|
|
78
|
-
const result = await this.makeRequest('GET', '/accounts/verify-acting-as', { accountId, userId }, { cache: false, retry: false, timeout: 5000 });
|
|
79
|
-
// Cache successful result for 5 minutes
|
|
80
|
-
this._actingAsCache.set(cacheKey, {
|
|
81
|
-
result: result && result.authorized ? result : null,
|
|
82
|
-
expiresAt: now + 5 * 60 * 1000,
|
|
83
|
-
});
|
|
84
|
-
return result && result.authorized ? result : null;
|
|
85
|
-
}
|
|
86
|
-
catch (error) {
|
|
87
|
-
logger.warn('[oxy.auth] verifyActingAs lookup failed — caching negative result', {
|
|
88
|
-
component: 'auth',
|
|
89
|
-
method: 'verifyActingAs',
|
|
90
|
-
userId,
|
|
91
|
-
accountId,
|
|
92
|
-
}, error);
|
|
93
|
-
// Cache negative result for 1 minute to avoid hammering on transient errors
|
|
94
|
-
this._actingAsCache.set(cacheKey, {
|
|
95
|
-
result: null,
|
|
96
|
-
expiresAt: now + 1 * 60 * 1000,
|
|
97
|
-
});
|
|
98
|
-
return null;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
59
|
/**
|
|
102
60
|
* Verify that a service app holds an active delegation grant authorising
|
|
103
61
|
* it to act on behalf of `userId`. Returns the grant (with allowed scopes)
|
|
@@ -218,47 +176,6 @@ export function OxyServicesUtilityMixin(Base) {
|
|
|
218
176
|
const oxyInstance = this;
|
|
219
177
|
// Return an async middleware function
|
|
220
178
|
return async (req, res, next) => {
|
|
221
|
-
// Process X-Acting-As header for managed account identity delegation.
|
|
222
|
-
// Called after successful authentication, before next(). If the header
|
|
223
|
-
// is present, verifies authorization and swaps the request identity to
|
|
224
|
-
// the managed account, preserving the original user for audit trails.
|
|
225
|
-
const processActingAs = async () => {
|
|
226
|
-
const actingAsUserId = req.headers['x-acting-as'];
|
|
227
|
-
if (!actingAsUserId || typeof actingAsUserId !== 'string')
|
|
228
|
-
return true; // No header, proceed normally
|
|
229
|
-
const currentUserId = req.userId;
|
|
230
|
-
if (!currentUserId)
|
|
231
|
-
return true; // No authenticated user yet — nothing to swap
|
|
232
|
-
const verification = await oxyInstance.verifyActingAs(currentUserId, actingAsUserId);
|
|
233
|
-
if (!verification) {
|
|
234
|
-
const error = {
|
|
235
|
-
error: 'ACTING_AS_UNAUTHORIZED',
|
|
236
|
-
message: 'Not authorized to act as this account',
|
|
237
|
-
code: 'ACTING_AS_UNAUTHORIZED',
|
|
238
|
-
status: 403,
|
|
239
|
-
};
|
|
240
|
-
if (onError) {
|
|
241
|
-
onError(error);
|
|
242
|
-
}
|
|
243
|
-
else {
|
|
244
|
-
res.status(403).json(error);
|
|
245
|
-
}
|
|
246
|
-
return false;
|
|
247
|
-
}
|
|
248
|
-
// Preserve original user for audit trails
|
|
249
|
-
req.originalUser = { id: currentUserId, ...(req.user ?? {}) };
|
|
250
|
-
req.actingAs = { userId: actingAsUserId, role: verification.role };
|
|
251
|
-
// Swap user identity to the managed account
|
|
252
|
-
req.userId = actingAsUserId;
|
|
253
|
-
req.user = { id: actingAsUserId, _id: actingAsUserId };
|
|
254
|
-
if (debug) {
|
|
255
|
-
logger.debug(`[oxy.auth] Acting as ${actingAsUserId} (role=${verification.role}) original=${currentUserId}`, {
|
|
256
|
-
component: 'auth',
|
|
257
|
-
method: 'auth.processActingAs',
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
return true;
|
|
261
|
-
};
|
|
262
179
|
try {
|
|
263
180
|
// Extract token from Authorization header.
|
|
264
181
|
// Node/Express normalizes `Authorization` to a string; we guard
|
|
@@ -340,9 +257,9 @@ export function OxyServicesUtilityMixin(Base) {
|
|
|
340
257
|
// Signature verification uses a manual HMAC-SHA256 compare because
|
|
341
258
|
// this file ships into RN/web bundles where `jsonwebtoken` is
|
|
342
259
|
// unavailable. The middleware only ever runs on Node hosts (see
|
|
343
|
-
//
|
|
344
|
-
// platform: the RN variant throws so Metro
|
|
345
|
-
// built-in reference.
|
|
260
|
+
// `@oxyhq/protocol`'s `platform/crypto` doc-comment), and
|
|
261
|
+
// `loadNodeCrypto` is per-platform: the RN variant throws so Metro
|
|
262
|
+
// never bundles a Node built-in reference.
|
|
346
263
|
try {
|
|
347
264
|
await verifyServiceTokenSignature(token, jwtSecret);
|
|
348
265
|
verifyServiceTokenClaims(decoded, {
|
|
@@ -551,10 +468,7 @@ export function OxyServicesUtilityMixin(Base) {
|
|
|
551
468
|
method: 'auth',
|
|
552
469
|
});
|
|
553
470
|
}
|
|
554
|
-
|
|
555
|
-
if (await processActingAs())
|
|
556
|
-
return next();
|
|
557
|
-
return;
|
|
471
|
+
return next();
|
|
558
472
|
}
|
|
559
473
|
catch (validationError) {
|
|
560
474
|
if (debug) {
|
|
@@ -618,9 +532,7 @@ export function OxyServicesUtilityMixin(Base) {
|
|
|
618
532
|
method: 'auth',
|
|
619
533
|
});
|
|
620
534
|
}
|
|
621
|
-
|
|
622
|
-
if (await processActingAs())
|
|
623
|
-
next();
|
|
535
|
+
next();
|
|
624
536
|
}
|
|
625
537
|
catch (error) {
|
|
626
538
|
const handled = oxyInstance.handleError(error);
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Extracted from {@link HttpService} so the identity-tag derivation is a pure,
|
|
5
5
|
* independently testable function with no dependency on instance/token state.
|
|
6
|
-
* The HTTP service injects the live access token
|
|
7
|
-
*
|
|
6
|
+
* The HTTP service injects the live access token; everything here is
|
|
7
|
+
* referentially transparent given that input.
|
|
8
8
|
*/
|
|
9
9
|
import { jwtDecode } from 'jwt-decode';
|
|
10
10
|
/**
|
|
@@ -58,25 +58,23 @@ export function fnv1a32(str) {
|
|
|
58
58
|
*
|
|
59
59
|
* We use the decoded user id rather than the raw JWT so the token never lands
|
|
60
60
|
* in a cache key (no token leakage through any cache-key logging, no key bloat).
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
61
|
+
* Switching into a managed account mints a REAL new session whose access token
|
|
62
|
+
* carries the target account's id, so the identity tag changes naturally on a
|
|
63
|
+
* switch — there is no separate acting-as discriminator to fold in.
|
|
64
64
|
*
|
|
65
65
|
* @param accessToken The current bearer access token, or `null` when anonymous.
|
|
66
|
-
* @param actingAsUserId The active managed-account id, or `null`.
|
|
67
66
|
*/
|
|
68
|
-
export function computeIdentityTag(accessToken
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
67
|
+
export function computeIdentityTag(accessToken) {
|
|
68
|
+
if (!accessToken) {
|
|
69
|
+
return ANON_IDENTITY;
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
const decoded = jwtDecode(accessToken);
|
|
73
|
+
return decoded.userId || decoded.id || `t${fnv1a32(accessToken)}`;
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// Undecodable token — still partition it away from anon and from other
|
|
77
|
+
// tokens via a hash. Never silently fall back to ANON_IDENTITY.
|
|
78
|
+
return `t${fnv1a32(accessToken)}`;
|
|
80
79
|
}
|
|
81
|
-
return actingAsUserId ? `${principal}~as${actingAsUserId}` : principal;
|
|
82
80
|
}
|
|
@@ -74,18 +74,6 @@ export function isIOS() {
|
|
|
74
74
|
export function isAndroid() {
|
|
75
75
|
return getPlatformOS() === 'android';
|
|
76
76
|
}
|
|
77
|
-
/**
|
|
78
|
-
* Check if running in React Native
|
|
79
|
-
*/
|
|
80
|
-
export function isReactNative() {
|
|
81
|
-
return typeof navigator !== 'undefined' && navigator.product === 'ReactNative';
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Check if running in Node.js
|
|
85
|
-
*/
|
|
86
|
-
export function isNodeJS() {
|
|
87
|
-
return typeof process !== 'undefined' && process.versions != null && process.versions.node != null;
|
|
88
|
-
}
|
|
89
77
|
/**
|
|
90
78
|
* Set the platform OS explicitly
|
|
91
79
|
* Called by React Native entry point to register the platform
|