@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
|
@@ -141,6 +141,27 @@ class OxyServicesBase {
|
|
|
141
141
|
getAccessToken() {
|
|
142
142
|
return this.httpService.getAccessToken();
|
|
143
143
|
}
|
|
144
|
+
/**
|
|
145
|
+
* Set the acting-as identity for managed accounts.
|
|
146
|
+
*
|
|
147
|
+
* When set, all subsequent API requests will include the `X-Acting-As` header,
|
|
148
|
+
* causing the server to attribute actions to the managed account. The
|
|
149
|
+
* authenticated user must be an authorized manager of the target account.
|
|
150
|
+
*
|
|
151
|
+
* Pass `null` to clear and revert to the authenticated user's own identity.
|
|
152
|
+
*
|
|
153
|
+
* @param userId - The managed account user ID, or null to clear
|
|
154
|
+
*/
|
|
155
|
+
setActingAs(userId) {
|
|
156
|
+
this.httpService.setActingAs(userId);
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Get the current acting-as identity (managed account user ID), or null
|
|
160
|
+
* if operating as the authenticated user's own identity.
|
|
161
|
+
*/
|
|
162
|
+
getActingAs() {
|
|
163
|
+
return this.httpService.getActingAs();
|
|
164
|
+
}
|
|
144
165
|
/**
|
|
145
166
|
* Wait for authentication to be ready
|
|
146
167
|
*
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OxyServicesManagedAccountsMixin = OxyServicesManagedAccountsMixin;
|
|
4
|
+
function OxyServicesManagedAccountsMixin(Base) {
|
|
5
|
+
return class extends Base {
|
|
6
|
+
constructor(...args) {
|
|
7
|
+
super(...args);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Create a new managed account (sub-account).
|
|
11
|
+
*
|
|
12
|
+
* The server creates a User document with `isManagedAccount: true` and links
|
|
13
|
+
* it to the authenticated user as owner.
|
|
14
|
+
*/
|
|
15
|
+
async createManagedAccount(data) {
|
|
16
|
+
try {
|
|
17
|
+
return await this.makeRequest('POST', '/managed-accounts', data, {
|
|
18
|
+
cache: false,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
throw this.handleError(error);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* List all accounts the authenticated user manages.
|
|
27
|
+
*/
|
|
28
|
+
async getManagedAccounts() {
|
|
29
|
+
try {
|
|
30
|
+
return await this.makeRequest('GET', '/managed-accounts', undefined, {
|
|
31
|
+
cache: true,
|
|
32
|
+
cacheTTL: 2 * 60 * 1000, // 2 minutes cache
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
throw this.handleError(error);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Get details for a specific managed account.
|
|
41
|
+
*/
|
|
42
|
+
async getManagedAccountDetails(accountId) {
|
|
43
|
+
try {
|
|
44
|
+
return await this.makeRequest('GET', `/managed-accounts/${accountId}`, undefined, {
|
|
45
|
+
cache: true,
|
|
46
|
+
cacheTTL: 2 * 60 * 1000,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
throw this.handleError(error);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Update a managed account's profile data.
|
|
55
|
+
* Requires owner or admin role.
|
|
56
|
+
*/
|
|
57
|
+
async updateManagedAccount(accountId, data) {
|
|
58
|
+
try {
|
|
59
|
+
return await this.makeRequest('PUT', `/managed-accounts/${accountId}`, data, {
|
|
60
|
+
cache: false,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
throw this.handleError(error);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Delete a managed account permanently.
|
|
69
|
+
* Requires owner role.
|
|
70
|
+
*/
|
|
71
|
+
async deleteManagedAccount(accountId) {
|
|
72
|
+
try {
|
|
73
|
+
await this.makeRequest('DELETE', `/managed-accounts/${accountId}`, undefined, {
|
|
74
|
+
cache: false,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
throw this.handleError(error);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Add a manager to a managed account.
|
|
83
|
+
* Requires owner or admin role on the account.
|
|
84
|
+
*
|
|
85
|
+
* @param accountId - The managed account to add the manager to
|
|
86
|
+
* @param userId - The user to grant management access
|
|
87
|
+
* @param role - The role to assign: 'admin' or 'editor'
|
|
88
|
+
*/
|
|
89
|
+
async addManager(accountId, userId, role) {
|
|
90
|
+
try {
|
|
91
|
+
await this.makeRequest('POST', `/managed-accounts/${accountId}/managers`, { userId, role }, {
|
|
92
|
+
cache: false,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
throw this.handleError(error);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Remove a manager from a managed account.
|
|
101
|
+
* Requires owner role.
|
|
102
|
+
*
|
|
103
|
+
* @param accountId - The managed account
|
|
104
|
+
* @param userId - The manager to remove
|
|
105
|
+
*/
|
|
106
|
+
async removeManager(accountId, userId) {
|
|
107
|
+
try {
|
|
108
|
+
await this.makeRequest('DELETE', `/managed-accounts/${accountId}/managers/${userId}`, undefined, {
|
|
109
|
+
cache: false,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
throw this.handleError(error);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
}
|
|
@@ -46,6 +46,41 @@ function OxyServicesUtilityMixin(Base) {
|
|
|
46
46
|
return class extends Base {
|
|
47
47
|
constructor(...args) {
|
|
48
48
|
super(...args);
|
|
49
|
+
/** @internal In-memory cache for acting-as verification results (TTL: 5 min) */
|
|
50
|
+
this._actingAsCache = new Map();
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Verify that a user is authorized to act as a managed account.
|
|
54
|
+
* Results are cached in-memory for 5 minutes to avoid repeated API calls.
|
|
55
|
+
*
|
|
56
|
+
* @internal Used by the auth() middleware — not part of the public API
|
|
57
|
+
*/
|
|
58
|
+
async verifyActingAs(userId, accountId) {
|
|
59
|
+
const cacheKey = `${userId}:${accountId}`;
|
|
60
|
+
const now = Date.now();
|
|
61
|
+
// Check cache
|
|
62
|
+
const cached = this._actingAsCache.get(cacheKey);
|
|
63
|
+
if (cached && cached.expiresAt > now) {
|
|
64
|
+
return cached.result;
|
|
65
|
+
}
|
|
66
|
+
// Query the API
|
|
67
|
+
try {
|
|
68
|
+
const result = await this.makeRequest('GET', '/managed-accounts/verify', { accountId, userId }, { cache: false, retry: false, timeout: 5000 });
|
|
69
|
+
// Cache successful result for 5 minutes
|
|
70
|
+
this._actingAsCache.set(cacheKey, {
|
|
71
|
+
result: result && result.authorized ? result : null,
|
|
72
|
+
expiresAt: now + 5 * 60 * 1000,
|
|
73
|
+
});
|
|
74
|
+
return result && result.authorized ? result : null;
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
// Cache negative result for 1 minute to avoid hammering on transient errors
|
|
78
|
+
this._actingAsCache.set(cacheKey, {
|
|
79
|
+
result: null,
|
|
80
|
+
expiresAt: now + 1 * 60 * 1000,
|
|
81
|
+
});
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
49
84
|
}
|
|
50
85
|
/**
|
|
51
86
|
* Fetch link metadata
|
|
@@ -108,6 +143,45 @@ function OxyServicesUtilityMixin(Base) {
|
|
|
108
143
|
const oxyInstance = this;
|
|
109
144
|
// Return an async middleware function
|
|
110
145
|
return async (req, res, next) => {
|
|
146
|
+
// Process X-Acting-As header for managed account identity delegation.
|
|
147
|
+
// Called after successful authentication, before next(). If the header
|
|
148
|
+
// is present, verifies authorization and swaps the request identity to
|
|
149
|
+
// the managed account, preserving the original user for audit trails.
|
|
150
|
+
const processActingAs = async () => {
|
|
151
|
+
const actingAsUserId = req.headers['x-acting-as'];
|
|
152
|
+
if (!actingAsUserId)
|
|
153
|
+
return true; // No header, proceed normally
|
|
154
|
+
const verification = await oxyInstance.verifyActingAs(req.userId, actingAsUserId);
|
|
155
|
+
if (!verification) {
|
|
156
|
+
const error = {
|
|
157
|
+
error: 'ACTING_AS_UNAUTHORIZED',
|
|
158
|
+
message: 'Not authorized to act as this account',
|
|
159
|
+
code: 'ACTING_AS_UNAUTHORIZED',
|
|
160
|
+
status: 403,
|
|
161
|
+
};
|
|
162
|
+
if (onError) {
|
|
163
|
+
onError(error);
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
res.status(403).json(error);
|
|
167
|
+
}
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
// Preserve original user for audit trails
|
|
171
|
+
req.originalUser = { id: req.userId, ...req.user };
|
|
172
|
+
req.actingAs = { userId: actingAsUserId, role: verification.role };
|
|
173
|
+
// Swap user identity to the managed account
|
|
174
|
+
req.userId = actingAsUserId;
|
|
175
|
+
req.user = { id: actingAsUserId };
|
|
176
|
+
// Also set _id for routes that use Pattern B (req.user._id)
|
|
177
|
+
if (req.user) {
|
|
178
|
+
req.user._id = actingAsUserId;
|
|
179
|
+
}
|
|
180
|
+
if (debug) {
|
|
181
|
+
console.log(`[oxy.auth] Acting as ${actingAsUserId} (role=${verification.role}) original=${req.originalUser.id}`);
|
|
182
|
+
}
|
|
183
|
+
return true;
|
|
184
|
+
};
|
|
111
185
|
try {
|
|
112
186
|
// Extract token from Authorization header or query params
|
|
113
187
|
const authHeader = req.headers['authorization'];
|
|
@@ -330,7 +404,10 @@ function OxyServicesUtilityMixin(Base) {
|
|
|
330
404
|
if (debug) {
|
|
331
405
|
console.log(`[oxy.auth] OK user=${userId} session=${decoded.sessionId}`);
|
|
332
406
|
}
|
|
333
|
-
|
|
407
|
+
// Process X-Acting-As header before proceeding
|
|
408
|
+
if (await processActingAs())
|
|
409
|
+
return next();
|
|
410
|
+
return;
|
|
334
411
|
}
|
|
335
412
|
catch (validationError) {
|
|
336
413
|
if (debug) {
|
|
@@ -381,7 +458,9 @@ function OxyServicesUtilityMixin(Base) {
|
|
|
381
458
|
if (debug) {
|
|
382
459
|
console.log(`[oxy.auth] OK user=${userId} (no session)`);
|
|
383
460
|
}
|
|
384
|
-
|
|
461
|
+
// Process X-Acting-As header before proceeding
|
|
462
|
+
if (await processActingAs())
|
|
463
|
+
next();
|
|
385
464
|
}
|
|
386
465
|
catch (error) {
|
|
387
466
|
const apiError = oxyInstance.handleError(error);
|
package/dist/cjs/mixins/index.js
CHANGED
|
@@ -27,6 +27,7 @@ const OxyServices_security_1 = require("./OxyServices.security");
|
|
|
27
27
|
const OxyServices_utility_1 = require("./OxyServices.utility");
|
|
28
28
|
const OxyServices_features_1 = require("./OxyServices.features");
|
|
29
29
|
const OxyServices_topics_1 = require("./OxyServices.topics");
|
|
30
|
+
const OxyServices_managedAccounts_1 = require("./OxyServices.managedAccounts");
|
|
30
31
|
/**
|
|
31
32
|
* Mixin pipeline - applied in order from first to last.
|
|
32
33
|
*
|
|
@@ -64,6 +65,7 @@ const MIXIN_PIPELINE = [
|
|
|
64
65
|
OxyServices_security_1.OxyServicesSecurityMixin,
|
|
65
66
|
OxyServices_features_1.OxyServicesFeaturesMixin,
|
|
66
67
|
OxyServices_topics_1.OxyServicesTopicsMixin,
|
|
68
|
+
OxyServices_managedAccounts_1.OxyServicesManagedAccountsMixin,
|
|
67
69
|
// Utility (last, can use all above)
|
|
68
70
|
OxyServices_utility_1.OxyServicesUtilityMixin,
|
|
69
71
|
];
|