@oxyhq/core 4.0.1 → 5.0.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 +54 -0
- package/dist/cjs/mixins/OxyServices.assets.js +2 -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 +54 -0
- package/dist/esm/mixins/OxyServices.assets.js +1 -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 +3 -4
- package/dist/types/mixins/OxyServices.accounts.d.ts +57 -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 +0 -2
- 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/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 +3 -3
- package/src/mixins/OxyServices.accounts.ts +91 -3
- package/src/mixins/OxyServices.assets.ts +1 -1
- 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__/accounts.test.ts +70 -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
|
@@ -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
|