@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.
- package/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/AuthManager.js +158 -1
- package/dist/cjs/HttpService.js +13 -0
- package/dist/cjs/OxyServices.base.js +21 -0
- package/dist/cjs/crypto/keyManager.js +4 -6
- package/dist/cjs/crypto/polyfill.js +56 -12
- package/dist/cjs/crypto/signatureService.js +7 -4
- package/dist/cjs/mixins/OxyServices.fedcm.js +9 -4
- package/dist/cjs/mixins/OxyServices.managedAccounts.js +117 -0
- package/dist/cjs/mixins/OxyServices.popup.js +9 -5
- 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/AuthManager.js +158 -1
- package/dist/esm/HttpService.js +13 -0
- package/dist/esm/OxyServices.base.js +21 -0
- package/dist/esm/crypto/keyManager.js +4 -6
- package/dist/esm/crypto/polyfill.js +23 -12
- package/dist/esm/crypto/signatureService.js +7 -4
- package/dist/esm/mixins/OxyServices.fedcm.js +9 -4
- package/dist/esm/mixins/OxyServices.managedAccounts.js +114 -0
- package/dist/esm/mixins/OxyServices.popup.js +9 -5
- 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/AuthManager.d.ts +21 -0
- 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 +3 -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 +4 -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/AuthManager.ts +186 -4
- package/src/HttpService.ts +17 -0
- package/src/OxyServices.base.ts +23 -0
- package/src/crypto/keyManager.ts +4 -6
- package/src/crypto/polyfill.ts +23 -12
- package/src/crypto/signatureService.ts +7 -4
- package/src/index.ts +1 -0
- package/src/mixins/OxyServices.fedcm.ts +11 -4
- package/src/mixins/OxyServices.managedAccounts.ts +147 -0
- package/src/mixins/OxyServices.popup.ts +11 -5
- package/src/mixins/OxyServices.utility.ts +103 -2
- package/src/mixins/index.ts +2 -0
- 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: `${
|
|
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 = `${
|
|
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 =
|
|
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 !==
|
|
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(`${
|
|
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
|
-
|
|
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,
|