@oxyhq/core 1.11.10 → 1.11.11

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 (44) hide show
  1. package/dist/cjs/.tsbuildinfo +1 -1
  2. package/dist/cjs/HttpService.js +13 -0
  3. package/dist/cjs/OxyServices.base.js +21 -0
  4. package/dist/cjs/mixins/OxyServices.managedAccounts.js +117 -0
  5. package/dist/cjs/mixins/OxyServices.utility.js +81 -2
  6. package/dist/cjs/mixins/index.js +2 -0
  7. package/dist/esm/.tsbuildinfo +1 -1
  8. package/dist/esm/HttpService.js +13 -0
  9. package/dist/esm/OxyServices.base.js +21 -0
  10. package/dist/esm/mixins/OxyServices.managedAccounts.js +114 -0
  11. package/dist/esm/mixins/OxyServices.utility.js +81 -2
  12. package/dist/esm/mixins/index.js +2 -0
  13. package/dist/types/.tsbuildinfo +1 -1
  14. package/dist/types/HttpService.d.ts +3 -0
  15. package/dist/types/OxyServices.base.d.ts +17 -0
  16. package/dist/types/index.d.ts +1 -0
  17. package/dist/types/mixins/OxyServices.analytics.d.ts +2 -0
  18. package/dist/types/mixins/OxyServices.assets.d.ts +2 -0
  19. package/dist/types/mixins/OxyServices.auth.d.ts +2 -0
  20. package/dist/types/mixins/OxyServices.developer.d.ts +2 -0
  21. package/dist/types/mixins/OxyServices.devices.d.ts +2 -0
  22. package/dist/types/mixins/OxyServices.features.d.ts +5 -1
  23. package/dist/types/mixins/OxyServices.fedcm.d.ts +2 -0
  24. package/dist/types/mixins/OxyServices.karma.d.ts +2 -0
  25. package/dist/types/mixins/OxyServices.language.d.ts +2 -0
  26. package/dist/types/mixins/OxyServices.location.d.ts +2 -0
  27. package/dist/types/mixins/OxyServices.managedAccounts.d.ts +125 -0
  28. package/dist/types/mixins/OxyServices.payment.d.ts +2 -0
  29. package/dist/types/mixins/OxyServices.popup.d.ts +2 -0
  30. package/dist/types/mixins/OxyServices.privacy.d.ts +2 -0
  31. package/dist/types/mixins/OxyServices.redirect.d.ts +2 -0
  32. package/dist/types/mixins/OxyServices.security.d.ts +2 -0
  33. package/dist/types/mixins/OxyServices.topics.d.ts +2 -0
  34. package/dist/types/mixins/OxyServices.user.d.ts +2 -0
  35. package/dist/types/mixins/OxyServices.utility.d.ts +22 -0
  36. package/dist/types/models/interfaces.d.ts +2 -0
  37. package/package.json +1 -1
  38. package/src/HttpService.ts +17 -0
  39. package/src/OxyServices.base.ts +23 -0
  40. package/src/index.ts +1 -0
  41. package/src/mixins/OxyServices.managedAccounts.ts +147 -0
  42. package/src/mixins/OxyServices.utility.ts +103 -2
  43. package/src/mixins/index.ts +2 -0
  44. package/src/models/interfaces.ts +3 -0
@@ -20,6 +20,15 @@ interface JwtPayload {
20
20
  [key: string]: any;
21
21
  }
22
22
 
23
+ /**
24
+ * Result from the managed-accounts verification endpoint.
25
+ * Indicates whether a user is authorized to act as a given managed account.
26
+ */
27
+ interface ActingAsVerification {
28
+ authorized: boolean;
29
+ role: 'owner' | 'admin' | 'editor';
30
+ }
31
+
23
32
  /**
24
33
  * Service app metadata attached to requests authenticated with service tokens
25
34
  */
@@ -50,9 +59,55 @@ interface AuthMiddlewareOptions {
50
59
 
51
60
  export function OxyServicesUtilityMixin<T extends typeof OxyServicesBase>(Base: T) {
52
61
  return class extends Base {
62
+ /** @internal In-memory cache for acting-as verification results (TTL: 5 min) */
63
+ _actingAsCache = new Map<string, { result: ActingAsVerification | null; expiresAt: number }>();
64
+
53
65
  constructor(...args: any[]) {
54
66
  super(...(args as [any]));
55
67
  }
68
+
69
+ /**
70
+ * Verify that a user is authorized to act as a managed account.
71
+ * Results are cached in-memory for 5 minutes to avoid repeated API calls.
72
+ *
73
+ * @internal Used by the auth() middleware — not part of the public API
74
+ */
75
+ async verifyActingAs(userId: string, accountId: string): Promise<ActingAsVerification | null> {
76
+ const cacheKey = `${userId}:${accountId}`;
77
+ const now = Date.now();
78
+
79
+ // Check cache
80
+ const cached = this._actingAsCache.get(cacheKey);
81
+ if (cached && cached.expiresAt > now) {
82
+ return cached.result;
83
+ }
84
+
85
+ // Query the API
86
+ try {
87
+ const result = await this.makeRequest<ActingAsVerification>(
88
+ 'GET',
89
+ '/managed-accounts/verify',
90
+ { accountId, userId },
91
+ { cache: false, retry: false, timeout: 5000 }
92
+ );
93
+
94
+ // Cache successful result for 5 minutes
95
+ this._actingAsCache.set(cacheKey, {
96
+ result: result && result.authorized ? result : null,
97
+ expiresAt: now + 5 * 60 * 1000,
98
+ });
99
+
100
+ return result && result.authorized ? result : null;
101
+ } catch {
102
+ // Cache negative result for 1 minute to avoid hammering on transient errors
103
+ this._actingAsCache.set(cacheKey, {
104
+ result: null,
105
+ expiresAt: now + 1 * 60 * 1000,
106
+ });
107
+ return null;
108
+ }
109
+ }
110
+
56
111
  /**
57
112
  * Fetch link metadata
58
113
  */
@@ -125,6 +180,49 @@ export function OxyServicesUtilityMixin<T extends typeof OxyServicesBase>(Base:
125
180
 
126
181
  // Return an async middleware function
127
182
  return async (req: any, res: any, next: any) => {
183
+ // Process X-Acting-As header for managed account identity delegation.
184
+ // Called after successful authentication, before next(). If the header
185
+ // is present, verifies authorization and swaps the request identity to
186
+ // the managed account, preserving the original user for audit trails.
187
+ const processActingAs = async (): Promise<boolean> => {
188
+ const actingAsUserId = req.headers['x-acting-as'] as string | undefined;
189
+ if (!actingAsUserId) return true; // No header, proceed normally
190
+
191
+ const verification = await oxyInstance.verifyActingAs(req.userId, actingAsUserId);
192
+ if (!verification) {
193
+ const error = {
194
+ error: 'ACTING_AS_UNAUTHORIZED',
195
+ message: 'Not authorized to act as this account',
196
+ code: 'ACTING_AS_UNAUTHORIZED',
197
+ status: 403,
198
+ };
199
+ if (onError) {
200
+ onError(error);
201
+ } else {
202
+ res.status(403).json(error);
203
+ }
204
+ return false;
205
+ }
206
+
207
+ // Preserve original user for audit trails
208
+ req.originalUser = { id: req.userId, ...req.user };
209
+ req.actingAs = { userId: actingAsUserId, role: verification.role };
210
+
211
+ // Swap user identity to the managed account
212
+ req.userId = actingAsUserId;
213
+ req.user = { id: actingAsUserId } as any;
214
+ // Also set _id for routes that use Pattern B (req.user._id)
215
+ if (req.user) {
216
+ (req.user as any)._id = actingAsUserId;
217
+ }
218
+
219
+ if (debug) {
220
+ console.log(`[oxy.auth] Acting as ${actingAsUserId} (role=${verification.role}) original=${req.originalUser.id}`);
221
+ }
222
+
223
+ return true;
224
+ };
225
+
128
226
  try {
129
227
  // Extract token from Authorization header or query params
130
228
  const authHeader = req.headers['authorization'];
@@ -360,7 +458,9 @@ export function OxyServicesUtilityMixin<T extends typeof OxyServicesBase>(Base:
360
458
  console.log(`[oxy.auth] OK user=${userId} session=${decoded.sessionId}`);
361
459
  }
362
460
 
363
- return next();
461
+ // Process X-Acting-As header before proceeding
462
+ if (await processActingAs()) return next();
463
+ return;
364
464
  } catch (validationError) {
365
465
  if (debug) {
366
466
  console.log(`[oxy.auth] Session validation failed:`, validationError);
@@ -414,7 +514,8 @@ export function OxyServicesUtilityMixin<T extends typeof OxyServicesBase>(Base:
414
514
  console.log(`[oxy.auth] OK user=${userId} (no session)`);
415
515
  }
416
516
 
417
- next();
517
+ // Process X-Acting-As header before proceeding
518
+ if (await processActingAs()) next();
418
519
  } catch (error) {
419
520
  const apiError = oxyInstance.handleError(error) as any;
420
521
 
@@ -24,6 +24,7 @@ import { OxyServicesSecurityMixin } from './OxyServices.security';
24
24
  import { OxyServicesUtilityMixin } from './OxyServices.utility';
25
25
  import { OxyServicesFeaturesMixin } from './OxyServices.features';
26
26
  import { OxyServicesTopicsMixin } from './OxyServices.topics';
27
+ import { OxyServicesManagedAccountsMixin } from './OxyServices.managedAccounts';
27
28
 
28
29
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
29
30
  type MixinFunction = (Base: any) => any;
@@ -68,6 +69,7 @@ const MIXIN_PIPELINE: MixinFunction[] = [
68
69
  OxyServicesSecurityMixin,
69
70
  OxyServicesFeaturesMixin,
70
71
  OxyServicesTopicsMixin,
72
+ OxyServicesManagedAccountsMixin,
71
73
 
72
74
  // Utility (last, can use all above)
73
75
  OxyServicesUtilityMixin,
@@ -74,6 +74,9 @@ export interface User {
74
74
  automation?: {
75
75
  ownerId?: string;
76
76
  };
77
+ // Managed account fields
78
+ isManagedAccount?: boolean;
79
+ managedBy?: string;
77
80
  [key: string]: unknown;
78
81
  }
79
82