@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.
Files changed (109) hide show
  1. package/dist/cjs/.tsbuildinfo +1 -1
  2. package/dist/cjs/HttpService.js +6 -18
  3. package/dist/cjs/OxyServices.base.js +0 -21
  4. package/dist/cjs/crypto/keyManager.js +7 -7
  5. package/dist/cjs/crypto/polyfill.js +6 -5
  6. package/dist/cjs/crypto/signatureService.js +44 -220
  7. package/dist/cjs/index.js +4 -8
  8. package/dist/cjs/mixins/OxyServices.accounts.js +54 -0
  9. package/dist/cjs/mixins/OxyServices.assets.js +2 -2
  10. package/dist/cjs/mixins/OxyServices.auth.js +3 -3
  11. package/dist/cjs/mixins/OxyServices.civic.js +3 -3
  12. package/dist/cjs/mixins/OxyServices.language.js +2 -2
  13. package/dist/cjs/mixins/OxyServices.utility.js +7 -95
  14. package/dist/cjs/utils/cacheKey.js +17 -19
  15. package/dist/cjs/utils/deviceManager.js +2 -2
  16. package/dist/cjs/utils/platform.js +0 -14
  17. package/dist/esm/.tsbuildinfo +1 -1
  18. package/dist/esm/HttpService.js +6 -18
  19. package/dist/esm/OxyServices.base.js +0 -21
  20. package/dist/esm/crypto/keyManager.js +4 -4
  21. package/dist/esm/crypto/polyfill.js +5 -4
  22. package/dist/esm/crypto/signatureService.js +39 -214
  23. package/dist/esm/index.js +1 -2
  24. package/dist/esm/mixins/OxyServices.accounts.js +54 -0
  25. package/dist/esm/mixins/OxyServices.assets.js +1 -1
  26. package/dist/esm/mixins/OxyServices.auth.js +1 -1
  27. package/dist/esm/mixins/OxyServices.civic.js +3 -3
  28. package/dist/esm/mixins/OxyServices.language.js +1 -1
  29. package/dist/esm/mixins/OxyServices.utility.js +6 -94
  30. package/dist/esm/utils/cacheKey.js +17 -19
  31. package/dist/esm/utils/deviceManager.js +1 -1
  32. package/dist/esm/utils/platform.js +0 -12
  33. package/dist/types/.tsbuildinfo +1 -1
  34. package/dist/types/HttpService.d.ts +3 -6
  35. package/dist/types/OxyServices.base.d.ts +0 -17
  36. package/dist/types/crypto/polyfill.d.ts +2 -2
  37. package/dist/types/crypto/signatureService.d.ts +18 -84
  38. package/dist/types/index.d.ts +3 -4
  39. package/dist/types/mixins/OxyServices.accounts.d.ts +57 -5
  40. package/dist/types/mixins/OxyServices.analytics.d.ts +0 -2
  41. package/dist/types/mixins/OxyServices.appData.d.ts +0 -2
  42. package/dist/types/mixins/OxyServices.assets.d.ts +0 -2
  43. package/dist/types/mixins/OxyServices.auth.d.ts +0 -2
  44. package/dist/types/mixins/OxyServices.civic.d.ts +3 -5
  45. package/dist/types/mixins/OxyServices.connectedApps.d.ts +0 -2
  46. package/dist/types/mixins/OxyServices.contacts.d.ts +0 -2
  47. package/dist/types/mixins/OxyServices.devices.d.ts +0 -2
  48. package/dist/types/mixins/OxyServices.features.d.ts +0 -2
  49. package/dist/types/mixins/OxyServices.fedcm.d.ts +0 -2
  50. package/dist/types/mixins/OxyServices.identity.d.ts +8 -5
  51. package/dist/types/mixins/OxyServices.language.d.ts +0 -2
  52. package/dist/types/mixins/OxyServices.links.d.ts +0 -2
  53. package/dist/types/mixins/OxyServices.location.d.ts +0 -2
  54. package/dist/types/mixins/OxyServices.nodes.d.ts +0 -44
  55. package/dist/types/mixins/OxyServices.payment.d.ts +0 -2
  56. package/dist/types/mixins/OxyServices.privacy.d.ts +0 -2
  57. package/dist/types/mixins/OxyServices.redirect.d.ts +0 -2
  58. package/dist/types/mixins/OxyServices.reputation.d.ts +0 -2
  59. package/dist/types/mixins/OxyServices.security.d.ts +0 -2
  60. package/dist/types/mixins/OxyServices.silent.d.ts +0 -2
  61. package/dist/types/mixins/OxyServices.sso.d.ts +0 -2
  62. package/dist/types/mixins/OxyServices.topics.d.ts +0 -2
  63. package/dist/types/mixins/OxyServices.user.d.ts +0 -2
  64. package/dist/types/mixins/OxyServices.utility.d.ts +0 -32
  65. package/dist/types/server/auth.d.ts +0 -6
  66. package/dist/types/server/index.d.ts +1 -1
  67. package/dist/types/utils/cacheKey.d.ts +6 -7
  68. package/dist/types/utils/platform.d.ts +0 -8
  69. package/package.json +4 -7
  70. package/src/HttpService.ts +6 -22
  71. package/src/OxyServices.base.ts +0 -23
  72. package/src/__tests__/httpServiceCache.test.ts +0 -19
  73. package/src/crypto/__tests__/keyManager.atomicity.test.ts +2 -1
  74. package/src/crypto/__tests__/keyManager.test.ts +9 -7
  75. package/src/crypto/__tests__/signChallengeShared.test.ts +2 -1
  76. package/src/crypto/__tests__/signedRecord.test.ts +37 -150
  77. package/src/crypto/keyManager.ts +28 -17
  78. package/src/crypto/polyfill.ts +5 -4
  79. package/src/crypto/signatureService.ts +67 -255
  80. package/src/index.ts +3 -3
  81. package/src/mixins/OxyServices.accounts.ts +91 -3
  82. package/src/mixins/OxyServices.assets.ts +1 -1
  83. package/src/mixins/OxyServices.auth.ts +1 -1
  84. package/src/mixins/OxyServices.civic.ts +6 -17
  85. package/src/mixins/OxyServices.identity.ts +8 -2
  86. package/src/mixins/OxyServices.language.ts +1 -1
  87. package/src/mixins/OxyServices.nodes.ts +1 -12
  88. package/src/mixins/OxyServices.utility.ts +6 -119
  89. package/src/mixins/__tests__/OxyServices.civic.test.ts +2 -2
  90. package/src/mixins/__tests__/accounts.test.ts +70 -0
  91. package/src/server/auth.ts +0 -7
  92. package/src/server/index.ts +0 -1
  93. package/src/utils/__tests__/cacheKey.test.ts +0 -0
  94. package/src/utils/cacheKey.ts +16 -21
  95. package/src/utils/deviceManager.ts +1 -1
  96. package/src/utils/platform.ts +0 -14
  97. package/dist/cjs/crypto/canonicalJson.js +0 -107
  98. package/dist/cjs/utils/platformCrypto.js +0 -165
  99. package/dist/cjs/utils/platformCrypto.native.js +0 -123
  100. package/dist/esm/crypto/canonicalJson.js +0 -104
  101. package/dist/esm/utils/platformCrypto.js +0 -125
  102. package/dist/esm/utils/platformCrypto.native.js +0 -80
  103. package/dist/types/crypto/canonicalJson.d.ts +0 -44
  104. package/dist/types/utils/platformCrypto.d.ts +0 -87
  105. package/dist/types/utils/platformCrypto.native.d.ts +0 -54
  106. package/src/crypto/__tests__/canonicalJson.test.ts +0 -116
  107. package/src/crypto/canonicalJson.ts +0 -120
  108. package/src/utils/platformCrypto.native.ts +0 -101
  109. 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 '../utils/platformCrypto.js';
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
- // platformCrypto's doc-comment), and `loadNodeCrypto` is per-
344
- // platform: the RN variant throws so Metro never bundles a Node
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
- // Process X-Acting-As header before proceeding
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
- // Process X-Acting-As header before proceeding
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 and acting-as id; everything
7
- * here is referentially transparent given those inputs.
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
- * The acting-as id is folded in because managed-account responses differ per
62
- * acting identity and `X-Acting-As` already changes the server response for
63
- * the same bearer token.
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, actingAsUserId) {
69
- let principal = ANON_IDENTITY;
70
- if (accessToken) {
71
- try {
72
- const decoded = jwtDecode(accessToken);
73
- principal = 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
- principal = `t${fnv1a32(accessToken)}`;
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
  }
@@ -1,4 +1,4 @@
1
- import { loadAsyncStorage } from './platformCrypto.js';
1
+ import { loadAsyncStorage } from '@oxyhq/protocol';
2
2
  /**
3
3
  * Client-side device management utility
4
4
  * Handles persistent device identification across app sessions
@@ -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