@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.
- package/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/HttpService.js +13 -0
- package/dist/cjs/OxyServices.base.js +21 -0
- package/dist/cjs/mixins/OxyServices.managedAccounts.js +117 -0
- package/dist/cjs/mixins/OxyServices.utility.js +81 -2
- package/dist/cjs/mixins/index.js +2 -0
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/HttpService.js +13 -0
- package/dist/esm/OxyServices.base.js +21 -0
- package/dist/esm/mixins/OxyServices.managedAccounts.js +114 -0
- package/dist/esm/mixins/OxyServices.utility.js +81 -2
- package/dist/esm/mixins/index.js +2 -0
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/HttpService.d.ts +3 -0
- package/dist/types/OxyServices.base.d.ts +17 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/mixins/OxyServices.analytics.d.ts +2 -0
- package/dist/types/mixins/OxyServices.assets.d.ts +2 -0
- package/dist/types/mixins/OxyServices.auth.d.ts +2 -0
- package/dist/types/mixins/OxyServices.developer.d.ts +2 -0
- package/dist/types/mixins/OxyServices.devices.d.ts +2 -0
- package/dist/types/mixins/OxyServices.features.d.ts +5 -1
- package/dist/types/mixins/OxyServices.fedcm.d.ts +2 -0
- package/dist/types/mixins/OxyServices.karma.d.ts +2 -0
- package/dist/types/mixins/OxyServices.language.d.ts +2 -0
- package/dist/types/mixins/OxyServices.location.d.ts +2 -0
- package/dist/types/mixins/OxyServices.managedAccounts.d.ts +125 -0
- package/dist/types/mixins/OxyServices.payment.d.ts +2 -0
- package/dist/types/mixins/OxyServices.popup.d.ts +2 -0
- package/dist/types/mixins/OxyServices.privacy.d.ts +2 -0
- package/dist/types/mixins/OxyServices.redirect.d.ts +2 -0
- package/dist/types/mixins/OxyServices.security.d.ts +2 -0
- package/dist/types/mixins/OxyServices.topics.d.ts +2 -0
- package/dist/types/mixins/OxyServices.user.d.ts +2 -0
- package/dist/types/mixins/OxyServices.utility.d.ts +22 -0
- package/dist/types/models/interfaces.d.ts +2 -0
- package/package.json +1 -1
- package/src/HttpService.ts +17 -0
- package/src/OxyServices.base.ts +23 -0
- package/src/index.ts +1 -0
- package/src/mixins/OxyServices.managedAccounts.ts +147 -0
- package/src/mixins/OxyServices.utility.ts +103 -2
- package/src/mixins/index.ts +2 -0
- 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
|
-
|
|
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
|
-
|
|
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
|
|
package/src/mixins/index.ts
CHANGED
|
@@ -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,
|