@oxyhq/core 1.11.9 → 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 (63) hide show
  1. package/dist/cjs/.tsbuildinfo +1 -1
  2. package/dist/cjs/AuthManager.js +158 -1
  3. package/dist/cjs/HttpService.js +13 -0
  4. package/dist/cjs/OxyServices.base.js +21 -0
  5. package/dist/cjs/crypto/keyManager.js +4 -6
  6. package/dist/cjs/crypto/polyfill.js +56 -12
  7. package/dist/cjs/crypto/signatureService.js +7 -4
  8. package/dist/cjs/mixins/OxyServices.fedcm.js +9 -4
  9. package/dist/cjs/mixins/OxyServices.managedAccounts.js +117 -0
  10. package/dist/cjs/mixins/OxyServices.popup.js +9 -5
  11. package/dist/cjs/mixins/OxyServices.utility.js +81 -2
  12. package/dist/cjs/mixins/index.js +2 -0
  13. package/dist/esm/.tsbuildinfo +1 -1
  14. package/dist/esm/AuthManager.js +158 -1
  15. package/dist/esm/HttpService.js +13 -0
  16. package/dist/esm/OxyServices.base.js +21 -0
  17. package/dist/esm/crypto/keyManager.js +4 -6
  18. package/dist/esm/crypto/polyfill.js +23 -12
  19. package/dist/esm/crypto/signatureService.js +7 -4
  20. package/dist/esm/mixins/OxyServices.fedcm.js +9 -4
  21. package/dist/esm/mixins/OxyServices.managedAccounts.js +114 -0
  22. package/dist/esm/mixins/OxyServices.popup.js +9 -5
  23. package/dist/esm/mixins/OxyServices.utility.js +81 -2
  24. package/dist/esm/mixins/index.js +2 -0
  25. package/dist/types/.tsbuildinfo +1 -1
  26. package/dist/types/AuthManager.d.ts +21 -0
  27. package/dist/types/HttpService.d.ts +3 -0
  28. package/dist/types/OxyServices.base.d.ts +17 -0
  29. package/dist/types/index.d.ts +1 -0
  30. package/dist/types/mixins/OxyServices.analytics.d.ts +2 -0
  31. package/dist/types/mixins/OxyServices.assets.d.ts +2 -0
  32. package/dist/types/mixins/OxyServices.auth.d.ts +2 -0
  33. package/dist/types/mixins/OxyServices.developer.d.ts +2 -0
  34. package/dist/types/mixins/OxyServices.devices.d.ts +2 -0
  35. package/dist/types/mixins/OxyServices.features.d.ts +5 -1
  36. package/dist/types/mixins/OxyServices.fedcm.d.ts +3 -0
  37. package/dist/types/mixins/OxyServices.karma.d.ts +2 -0
  38. package/dist/types/mixins/OxyServices.language.d.ts +2 -0
  39. package/dist/types/mixins/OxyServices.location.d.ts +2 -0
  40. package/dist/types/mixins/OxyServices.managedAccounts.d.ts +125 -0
  41. package/dist/types/mixins/OxyServices.payment.d.ts +2 -0
  42. package/dist/types/mixins/OxyServices.popup.d.ts +4 -0
  43. package/dist/types/mixins/OxyServices.privacy.d.ts +2 -0
  44. package/dist/types/mixins/OxyServices.redirect.d.ts +2 -0
  45. package/dist/types/mixins/OxyServices.security.d.ts +2 -0
  46. package/dist/types/mixins/OxyServices.topics.d.ts +2 -0
  47. package/dist/types/mixins/OxyServices.user.d.ts +2 -0
  48. package/dist/types/mixins/OxyServices.utility.d.ts +22 -0
  49. package/dist/types/models/interfaces.d.ts +2 -0
  50. package/package.json +1 -1
  51. package/src/AuthManager.ts +186 -4
  52. package/src/HttpService.ts +17 -0
  53. package/src/OxyServices.base.ts +23 -0
  54. package/src/crypto/keyManager.ts +4 -6
  55. package/src/crypto/polyfill.ts +23 -12
  56. package/src/crypto/signatureService.ts +7 -4
  57. package/src/index.ts +1 -0
  58. package/src/mixins/OxyServices.fedcm.ts +11 -4
  59. package/src/mixins/OxyServices.managedAccounts.ts +147 -0
  60. package/src/mixins/OxyServices.popup.ts +11 -5
  61. package/src/mixins/OxyServices.utility.ts +103 -2
  62. package/src/mixins/index.ts +2 -0
  63. package/src/models/interfaces.ts +3 -0
@@ -44,6 +44,12 @@ export function OxyServicesPopupAuthMixin<T extends typeof OxyServicesBase>(Base
44
44
  super(...(args as [any]));
45
45
  }
46
46
  public static readonly DEFAULT_AUTH_URL = 'https://auth.oxy.so';
47
+
48
+ /** Resolve auth URL from config or static default (method, not getter — getters break in TS mixins) */
49
+ public resolveAuthUrl(): string {
50
+ return this.config.authWebUrl || (this.constructor as any).DEFAULT_AUTH_URL;
51
+ }
52
+
47
53
  public static readonly POPUP_WIDTH = 500;
48
54
  public static readonly POPUP_HEIGHT = 700;
49
55
  public static readonly POPUP_TIMEOUT = 60000; // 1 minute
@@ -95,7 +101,7 @@ export function OxyServicesPopupAuthMixin<T extends typeof OxyServicesBase>(Base
95
101
  state,
96
102
  nonce,
97
103
  clientId: window.location.origin,
98
- redirectUri: `${(this.config.authWebUrl || (this.constructor as any).DEFAULT_AUTH_URL)}/auth/callback`,
104
+ redirectUri: `${this.resolveAuthUrl()}/auth/callback`,
99
105
  });
100
106
 
101
107
  const popup = this.openCenteredPopup(authUrl, 'Oxy Sign In', width, height);
@@ -198,7 +204,7 @@ export function OxyServicesPopupAuthMixin<T extends typeof OxyServicesBase>(Base
198
204
  iframe.style.height = '0';
199
205
  iframe.style.border = 'none';
200
206
 
201
- const silentUrl = `${(this.config.authWebUrl || (this.constructor as any).DEFAULT_AUTH_URL)}/auth/silent?` + `client_id=${encodeURIComponent(clientId)}&` + `nonce=${nonce}`;
207
+ const silentUrl = `${this.resolveAuthUrl()}/auth/silent?` + `client_id=${encodeURIComponent(clientId)}&` + `nonce=${nonce}`;
202
208
 
203
209
  iframe.src = silentUrl;
204
210
  document.body.appendChild(iframe);
@@ -260,7 +266,7 @@ export function OxyServicesPopupAuthMixin<T extends typeof OxyServicesBase>(Base
260
266
  }, timeout);
261
267
 
262
268
  const messageHandler = (event: MessageEvent) => {
263
- const authUrl = (this.config.authWebUrl || (this.constructor as any).DEFAULT_AUTH_URL);
269
+ const authUrl = this.resolveAuthUrl();
264
270
 
265
271
  // Log all messages for debugging
266
272
  if (event.data && typeof event.data === 'object' && event.data.type) {
@@ -347,7 +353,7 @@ export function OxyServicesPopupAuthMixin<T extends typeof OxyServicesBase>(Base
347
353
 
348
354
  const messageHandler = (event: MessageEvent) => {
349
355
  // Verify origin
350
- if (event.origin !== (this.config.authWebUrl || (this.constructor as any).DEFAULT_AUTH_URL)) {
356
+ if (event.origin !== this.resolveAuthUrl()) {
351
357
  return;
352
358
  }
353
359
 
@@ -382,7 +388,7 @@ export function OxyServicesPopupAuthMixin<T extends typeof OxyServicesBase>(Base
382
388
  clientId: string;
383
389
  redirectUri: string;
384
390
  }): string {
385
- const url = new URL(`${(this.config.authWebUrl || (this.constructor as any).DEFAULT_AUTH_URL)}/${params.mode}`);
391
+ const url = new URL(`${this.resolveAuthUrl()}/${params.mode}`);
386
392
  url.searchParams.set('response_type', 'token');
387
393
  url.searchParams.set('client_id', params.clientId);
388
394
  url.searchParams.set('redirect_uri', params.redirectUri);
@@ -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