@oxyhq/core 3.4.1 → 3.4.3
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 +91 -319
- package/dist/cjs/CrossDomainAuth.js +19 -106
- package/dist/cjs/HttpService.js +49 -73
- package/dist/cjs/OxyServices.base.js +2 -2
- package/dist/cjs/i18n/index.js +7 -1
- package/dist/cjs/i18n/locales/ar-SA.json +18 -2
- package/dist/cjs/i18n/locales/ca-ES.json +18 -2
- package/dist/cjs/i18n/locales/de-DE.json +18 -2
- package/dist/cjs/i18n/locales/en-US.json +16 -2
- package/dist/cjs/i18n/locales/es-ES.json +16 -2
- package/dist/cjs/i18n/locales/fr-FR.json +18 -2
- package/dist/cjs/i18n/locales/it-IT.json +18 -2
- package/dist/cjs/i18n/locales/ja-JP.json +18 -2
- package/dist/cjs/i18n/locales/ko-KR.json +18 -2
- package/dist/cjs/i18n/locales/locales/ar-SA.json +18 -2
- package/dist/cjs/i18n/locales/locales/ca-ES.json +18 -2
- package/dist/cjs/i18n/locales/locales/de-DE.json +18 -2
- package/dist/cjs/i18n/locales/locales/en-US.json +17 -3
- package/dist/cjs/i18n/locales/locales/es-ES.json +16 -2
- package/dist/cjs/i18n/locales/locales/fr-FR.json +18 -2
- package/dist/cjs/i18n/locales/locales/it-IT.json +18 -2
- package/dist/cjs/i18n/locales/locales/ja-JP.json +18 -2
- package/dist/cjs/i18n/locales/locales/ko-KR.json +18 -2
- package/dist/cjs/i18n/locales/locales/pt-PT.json +18 -2
- package/dist/cjs/i18n/locales/locales/zh-CN.json +18 -2
- package/dist/cjs/i18n/locales/pt-PT.json +18 -2
- package/dist/cjs/i18n/locales/zh-CN.json +18 -2
- package/dist/cjs/mixins/OxyServices.auth.js +20 -63
- package/dist/cjs/mixins/OxyServices.fedcm.js +10 -12
- package/dist/cjs/mixins/OxyServices.popup.js +50 -299
- package/dist/cjs/mixins/OxyServices.redirect.js +84 -348
- package/dist/cjs/mixins/OxyServices.silent.js +204 -0
- package/dist/cjs/mixins/OxyServices.sso.js +4 -5
- package/dist/cjs/mixins/OxyServices.utility.js +6 -15
- package/dist/cjs/mixins/index.js +5 -6
- package/dist/cjs/server/index.js +21 -0
- package/dist/cjs/server/rateLimit.js +77 -0
- package/dist/cjs/shared/utils/debugUtils.js +1 -1
- package/dist/cjs/utils/accountUtils.js +4 -4
- package/dist/cjs/utils/authHelpers.js +21 -15
- package/dist/cjs/utils/coldBoot.js +3 -3
- package/dist/cjs/utils/fapiAutoDetect.js +1 -1
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/AuthManager.js +91 -319
- package/dist/esm/CrossDomainAuth.js +19 -106
- package/dist/esm/HttpService.js +49 -73
- package/dist/esm/OxyServices.base.js +2 -2
- package/dist/esm/i18n/index.js +7 -1
- package/dist/esm/i18n/locales/ar-SA.json +18 -2
- package/dist/esm/i18n/locales/ca-ES.json +18 -2
- package/dist/esm/i18n/locales/de-DE.json +18 -2
- package/dist/esm/i18n/locales/en-US.json +16 -2
- package/dist/esm/i18n/locales/es-ES.json +16 -2
- package/dist/esm/i18n/locales/fr-FR.json +18 -2
- package/dist/esm/i18n/locales/it-IT.json +18 -2
- package/dist/esm/i18n/locales/ja-JP.json +18 -2
- package/dist/esm/i18n/locales/ko-KR.json +18 -2
- package/dist/esm/i18n/locales/locales/ar-SA.json +18 -2
- package/dist/esm/i18n/locales/locales/ca-ES.json +18 -2
- package/dist/esm/i18n/locales/locales/de-DE.json +18 -2
- package/dist/esm/i18n/locales/locales/en-US.json +17 -3
- package/dist/esm/i18n/locales/locales/es-ES.json +16 -2
- package/dist/esm/i18n/locales/locales/fr-FR.json +18 -2
- package/dist/esm/i18n/locales/locales/it-IT.json +18 -2
- package/dist/esm/i18n/locales/locales/ja-JP.json +18 -2
- package/dist/esm/i18n/locales/locales/ko-KR.json +18 -2
- package/dist/esm/i18n/locales/locales/pt-PT.json +18 -2
- package/dist/esm/i18n/locales/locales/zh-CN.json +18 -2
- package/dist/esm/i18n/locales/pt-PT.json +18 -2
- package/dist/esm/i18n/locales/zh-CN.json +18 -2
- package/dist/esm/mixins/OxyServices.auth.js +20 -63
- package/dist/esm/mixins/OxyServices.fedcm.js +10 -12
- package/dist/esm/mixins/OxyServices.popup.js +52 -301
- package/dist/esm/mixins/OxyServices.redirect.js +84 -349
- package/dist/esm/mixins/OxyServices.silent.js +202 -0
- package/dist/esm/mixins/OxyServices.sso.js +4 -5
- package/dist/esm/mixins/OxyServices.utility.js +6 -15
- package/dist/esm/mixins/index.js +5 -6
- package/dist/esm/server/index.js +17 -0
- package/dist/esm/server/rateLimit.js +71 -0
- package/dist/esm/shared/utils/debugUtils.js +1 -1
- package/dist/esm/utils/accountUtils.js +4 -4
- package/dist/esm/utils/authHelpers.js +21 -15
- package/dist/esm/utils/coldBoot.js +3 -3
- package/dist/esm/utils/fapiAutoDetect.js +1 -1
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/AuthManager.d.ts +26 -53
- package/dist/types/AuthManagerTypes.d.ts +5 -9
- package/dist/types/CrossDomainAuth.d.ts +13 -52
- package/dist/types/HttpService.d.ts +9 -8
- package/dist/types/OxyServices.base.d.ts +1 -1
- package/dist/types/OxyServices.d.ts +4 -10
- package/dist/types/index.d.ts +1 -1
- package/dist/types/mixins/OxyServices.analytics.d.ts +1 -1
- package/dist/types/mixins/OxyServices.appData.d.ts +1 -1
- package/dist/types/mixins/OxyServices.applications.d.ts +1 -1
- package/dist/types/mixins/OxyServices.assets.d.ts +1 -1
- package/dist/types/mixins/OxyServices.auth.d.ts +10 -31
- package/dist/types/mixins/OxyServices.contacts.d.ts +1 -1
- package/dist/types/mixins/OxyServices.devices.d.ts +1 -1
- package/dist/types/mixins/OxyServices.features.d.ts +1 -1
- package/dist/types/mixins/OxyServices.fedcm.d.ts +5 -5
- package/dist/types/mixins/OxyServices.language.d.ts +1 -1
- package/dist/types/mixins/OxyServices.location.d.ts +1 -1
- package/dist/types/mixins/OxyServices.managedAccounts.d.ts +1 -1
- package/dist/types/mixins/OxyServices.payment.d.ts +1 -1
- package/dist/types/mixins/OxyServices.popup.d.ts +18 -120
- package/dist/types/mixins/OxyServices.privacy.d.ts +1 -1
- package/dist/types/mixins/OxyServices.redirect.d.ts +13 -174
- package/dist/types/mixins/OxyServices.reputation.d.ts +1 -1
- package/dist/types/mixins/OxyServices.security.d.ts +1 -1
- package/dist/types/mixins/OxyServices.silent.d.ts +131 -0
- package/dist/types/mixins/OxyServices.sso.d.ts +4 -5
- package/dist/types/mixins/OxyServices.topics.d.ts +1 -1
- package/dist/types/mixins/OxyServices.user.d.ts +1 -1
- package/dist/types/mixins/OxyServices.utility.d.ts +3 -8
- package/dist/types/mixins/OxyServices.workspaces.d.ts +1 -1
- package/dist/types/mixins/index.d.ts +3 -3
- package/dist/types/models/interfaces.d.ts +5 -16
- package/dist/types/models/session.d.ts +0 -2
- package/dist/types/server/index.d.ts +18 -0
- package/dist/types/server/rateLimit.d.ts +40 -0
- package/dist/types/shared/utils/debugUtils.d.ts +1 -1
- package/dist/types/utils/authHelpers.d.ts +4 -3
- package/dist/types/utils/coldBoot.d.ts +2 -2
- package/dist/types/utils/fapiAutoDetect.d.ts +1 -1
- package/package.json +24 -2
- package/src/AuthManager.ts +100 -370
- package/src/AuthManagerTypes.ts +5 -9
- package/src/CrossDomainAuth.ts +22 -129
- package/src/HttpService.ts +55 -73
- package/src/OxyServices.base.ts +2 -3
- package/src/OxyServices.ts +9 -11
- package/src/__tests__/authManager.cookiePath.test.ts +19 -17
- package/src/__tests__/authManager.security.test.ts +7 -3
- package/src/__tests__/crossDomainAuth.test.ts +26 -118
- package/src/i18n/index.ts +7 -1
- package/src/i18n/locales/ar-SA.json +18 -2
- package/src/i18n/locales/ca-ES.json +18 -2
- package/src/i18n/locales/de-DE.json +18 -2
- package/src/i18n/locales/en-US.json +17 -3
- package/src/i18n/locales/es-ES.json +16 -2
- package/src/i18n/locales/fr-FR.json +18 -2
- package/src/i18n/locales/it-IT.json +18 -2
- package/src/i18n/locales/ja-JP.json +18 -2
- package/src/i18n/locales/ko-KR.json +18 -2
- package/src/i18n/locales/pt-PT.json +18 -2
- package/src/i18n/locales/zh-CN.json +18 -2
- package/src/index.ts +1 -1
- package/src/mixins/OxyServices.auth.ts +23 -75
- package/src/mixins/OxyServices.fedcm.ts +10 -12
- package/src/mixins/OxyServices.redirect.ts +82 -371
- package/src/mixins/OxyServices.silent.ts +272 -0
- package/src/mixins/OxyServices.sso.ts +5 -6
- package/src/mixins/OxyServices.utility.ts +9 -22
- package/src/mixins/__tests__/appData.test.ts +1 -1
- package/src/mixins/__tests__/onTokensChanged.test.ts +1 -1
- package/src/mixins/__tests__/reputation.test.ts +1 -1
- package/src/mixins/__tests__/serviceAuth.test.ts +7 -5
- package/src/mixins/__tests__/silent.test.ts +102 -0
- package/src/mixins/__tests__/verifyChallenge.test.ts +9 -14
- package/src/mixins/index.ts +6 -8
- package/src/models/interfaces.ts +5 -16
- package/src/models/session.ts +1 -3
- package/src/server/index.ts +19 -0
- package/src/server/rateLimit.ts +170 -0
- package/src/shared/utils/debugUtils.ts +1 -1
- package/src/utils/accountUtils.ts +4 -4
- package/src/utils/authHelpers.ts +23 -15
- package/src/utils/coldBoot.ts +4 -4
- package/src/utils/fapiAutoDetect.ts +1 -1
- package/src/mixins/OxyServices.popup.ts +0 -631
- package/src/mixins/__tests__/popup.test.ts +0 -374
|
@@ -33,8 +33,7 @@ const debug = (0, debugUtils_1.createDebugLogger)('SSO');
|
|
|
33
33
|
*
|
|
34
34
|
* Exposed as a module-level helper (in addition to the instance method below)
|
|
35
35
|
* so consumers that do not yet hold an `OxyServices` instance can still mint a
|
|
36
|
-
* bounce state.
|
|
37
|
-
* mixin uses for its CSRF state, with a `getRandomValues` fallback.
|
|
36
|
+
* bounce state. Uses `crypto.randomUUID` with a `getRandomValues` fallback.
|
|
38
37
|
*/
|
|
39
38
|
function generateSsoState() {
|
|
40
39
|
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
|
|
@@ -55,8 +54,8 @@ function OxyServicesSsoMixin(Base) {
|
|
|
55
54
|
/**
|
|
56
55
|
* Generate cryptographically secure state for the SSO bounce (CSRF
|
|
57
56
|
* protection). Delegates to the module-level {@link generateSsoState}
|
|
58
|
-
* helper, which uses
|
|
59
|
-
*
|
|
57
|
+
* helper, which uses `crypto.randomUUID` when available and falls back to
|
|
58
|
+
* `crypto.getRandomValues`.
|
|
60
59
|
*/
|
|
61
60
|
generateSsoState() {
|
|
62
61
|
return generateSsoState();
|
|
@@ -127,7 +126,7 @@ function OxyServicesSsoMixin(Base) {
|
|
|
127
126
|
// Plant the access token exactly like exchangeIdTokenForSession does.
|
|
128
127
|
// The SSO exchange does not return a refresh token (the central store
|
|
129
128
|
// holds the refresh credential), so default it to an empty string.
|
|
130
|
-
this.httpService.setTokens(payload.accessToken
|
|
129
|
+
this.httpService.setTokens(payload.accessToken);
|
|
131
130
|
debug.log('SSO exchange complete:', { hasSession: !!payload.sessionId });
|
|
132
131
|
const session = {
|
|
133
132
|
sessionId: payload.sessionId,
|
|
@@ -262,20 +262,12 @@ function OxyServicesUtilityMixin(Base) {
|
|
|
262
262
|
return true;
|
|
263
263
|
};
|
|
264
264
|
try {
|
|
265
|
-
// Extract token from Authorization header
|
|
265
|
+
// Extract token from Authorization header.
|
|
266
266
|
// Node/Express normalizes `Authorization` to a string; we guard
|
|
267
267
|
// against the (legal but unusual) string[] case anyway.
|
|
268
268
|
const rawAuthHeader = req.headers.authorization;
|
|
269
269
|
const authHeader = Array.isArray(rawAuthHeader) ? rawAuthHeader[0] : rawAuthHeader;
|
|
270
|
-
|
|
271
|
-
// Fallback to query params (useful for WebSocket upgrades)
|
|
272
|
-
if (!token) {
|
|
273
|
-
const q = req.query || {};
|
|
274
|
-
if (typeof q.token === 'string' && q.token)
|
|
275
|
-
token = q.token;
|
|
276
|
-
else if (typeof q.access_token === 'string' && q.access_token)
|
|
277
|
-
token = q.access_token;
|
|
278
|
-
}
|
|
270
|
+
const token = authHeader?.startsWith('Bearer ') ? authHeader.substring(7) : null;
|
|
279
271
|
if (debug) {
|
|
280
272
|
loggerUtils_1.logger.debug(`[oxy.auth] ${req.method} ${req.path} | token: ${!!token}`, {
|
|
281
273
|
component: 'auth',
|
|
@@ -423,13 +415,14 @@ function OxyServicesUtilityMixin(Base) {
|
|
|
423
415
|
}
|
|
424
416
|
// Validate required service token fields
|
|
425
417
|
const appId = decoded.appId;
|
|
426
|
-
|
|
418
|
+
const credentialId = decoded.credentialId;
|
|
419
|
+
if (!appId || typeof credentialId !== 'string' || credentialId.length === 0) {
|
|
427
420
|
if (optional) {
|
|
428
421
|
req.userId = null;
|
|
429
422
|
req.user = null;
|
|
430
423
|
return next();
|
|
431
424
|
}
|
|
432
|
-
const error = { error: 'INVALID_SERVICE_TOKEN', message: 'Invalid service token: missing
|
|
425
|
+
const error = { error: 'INVALID_SERVICE_TOKEN', message: 'Invalid service token: missing required claims', code: 'INVALID_SERVICE_TOKEN', status: 401 };
|
|
433
426
|
if (onError)
|
|
434
427
|
return onError(error);
|
|
435
428
|
return res.status(401).json(error);
|
|
@@ -474,10 +467,8 @@ function OxyServicesUtilityMixin(Base) {
|
|
|
474
467
|
req.serviceApp = {
|
|
475
468
|
appId,
|
|
476
469
|
appName: decoded.appName || 'unknown',
|
|
470
|
+
credentialId,
|
|
477
471
|
scopes: Array.isArray(decoded.scopes) ? decoded.scopes : [],
|
|
478
|
-
...(typeof decoded.credentialId === 'string' && decoded.credentialId.length > 0
|
|
479
|
-
? { credentialId: decoded.credentialId }
|
|
480
|
-
: {}),
|
|
481
472
|
};
|
|
482
473
|
if (debug) {
|
|
483
474
|
loggerUtils_1.logger.debug(`[oxy.auth] Service token OK app=${decoded.appName} delegateUser=${oxyUserId || '(none)'}`, {
|
package/dist/cjs/mixins/index.js
CHANGED
|
@@ -11,7 +11,7 @@ exports.composeOxyServices = composeOxyServices;
|
|
|
11
11
|
const OxyServices_base_1 = require("../OxyServices.base");
|
|
12
12
|
const OxyServices_auth_1 = require("./OxyServices.auth");
|
|
13
13
|
const OxyServices_fedcm_1 = require("./OxyServices.fedcm");
|
|
14
|
-
const
|
|
14
|
+
const OxyServices_silent_1 = require("./OxyServices.silent");
|
|
15
15
|
const OxyServices_redirect_1 = require("./OxyServices.redirect");
|
|
16
16
|
const OxyServices_sso_1 = require("./OxyServices.sso");
|
|
17
17
|
const OxyServices_user_1 = require("./OxyServices.user");
|
|
@@ -37,7 +37,7 @@ const OxyServices_appData_1 = require("./OxyServices.appData");
|
|
|
37
37
|
*
|
|
38
38
|
* Order matters for dependencies:
|
|
39
39
|
* 1. Base auth mixin first (required by all others)
|
|
40
|
-
* 2. Cross-domain auth mixins (FedCM,
|
|
40
|
+
* 2. Cross-domain auth mixins (FedCM, silent iframe, Redirect)
|
|
41
41
|
* 3. User mixin (requires auth)
|
|
42
42
|
* 4. Feature mixins (can depend on user)
|
|
43
43
|
* 5. Utility mixin last (augments all)
|
|
@@ -49,13 +49,12 @@ const MIXIN_PIPELINE = [
|
|
|
49
49
|
OxyServices_auth_1.OxyServicesAuthMixin,
|
|
50
50
|
// Cross-domain authentication (web-only)
|
|
51
51
|
// - FedCM: Modern browser-native identity federation (Google-style)
|
|
52
|
-
// -
|
|
52
|
+
// - Silent: iframe-based restore for first-party IdP hosts
|
|
53
53
|
// - Redirect: Traditional redirect-based authentication
|
|
54
54
|
OxyServices_fedcm_1.OxyServicesFedCMMixin,
|
|
55
|
-
|
|
55
|
+
OxyServices_silent_1.OxyServicesSilentAuthMixin,
|
|
56
56
|
OxyServices_redirect_1.OxyServicesRedirectAuthMixin,
|
|
57
|
-
// Central cross-domain SSO (opaque-code exchange).
|
|
58
|
-
// reuse the popup mixin's secure-random `generateState()`.
|
|
57
|
+
// Central cross-domain SSO (opaque-code exchange).
|
|
59
58
|
OxyServices_sso_1.OxyServicesSsoMixin,
|
|
60
59
|
// User management (requires auth)
|
|
61
60
|
OxyServices_user_1.OxyServicesUserMixin,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @oxyhq/core/server — Server-only utilities for Oxy backends
|
|
4
|
+
*
|
|
5
|
+
* This subpath export provides Express middleware and Node.js-specific
|
|
6
|
+
* utilities that are not available in React Native or browser environments.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { createOxyRateLimit } from '@oxyhq/core/server';
|
|
11
|
+
* import { oxyClient } from '@oxyhq/core';
|
|
12
|
+
*
|
|
13
|
+
* const oxy = oxyClient({ apiUrl: 'https://api.oxy.so' });
|
|
14
|
+
*
|
|
15
|
+
* app.use(createOxyRateLimit(oxy, { store: redisStore }));
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.createOxyRateLimit = void 0;
|
|
20
|
+
var rateLimit_1 = require("./rateLimit");
|
|
21
|
+
Object.defineProperty(exports, "createOxyRateLimit", { enumerable: true, get: function () { return rateLimit_1.createOxyRateLimit; } });
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createOxyRateLimit = createOxyRateLimit;
|
|
7
|
+
const express_rate_limit_1 = __importDefault(require("express-rate-limit"));
|
|
8
|
+
/**
|
|
9
|
+
* Built-in exemptions. A media app's cover-art/avatar fan-out and HLS
|
|
10
|
+
* sub-requests must not consume the coarse global budget; health probes from
|
|
11
|
+
* the load balancer must never be limited; CORS preflight is not a real call.
|
|
12
|
+
*/
|
|
13
|
+
function isBuiltInExempt(req) {
|
|
14
|
+
const path = req.path;
|
|
15
|
+
return (req.method === 'OPTIONS' ||
|
|
16
|
+
path.startsWith('/files/upload') ||
|
|
17
|
+
path.includes('/images/') ||
|
|
18
|
+
path.includes('/media/') ||
|
|
19
|
+
path.startsWith('/api/stream/') ||
|
|
20
|
+
path.includes('/stream/') ||
|
|
21
|
+
path === '/health' ||
|
|
22
|
+
path.endsWith('/health'));
|
|
23
|
+
}
|
|
24
|
+
/** IPv6-safe IP key generator (replaces colons to avoid Redis namespace issues). */
|
|
25
|
+
function ipKeyGenerator(ip) {
|
|
26
|
+
return ip.replace(/:/g, '_');
|
|
27
|
+
}
|
|
28
|
+
/** Resolve the rate-limit key: per authenticated user, else per (IPv6-safe) IP. */
|
|
29
|
+
function resolveKey(req) {
|
|
30
|
+
const userId = req.userId ?? req.user?.id ?? req.user?._id;
|
|
31
|
+
if (userId) {
|
|
32
|
+
return `user:${userId}`;
|
|
33
|
+
}
|
|
34
|
+
const ip = req.ip || req.socket.remoteAddress || 'unknown';
|
|
35
|
+
return ipKeyGenerator(ip);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Build the composed Oxy rate-limit middleware. See module docs for rationale.
|
|
39
|
+
*/
|
|
40
|
+
function createOxyRateLimit(oxy, options = {}) {
|
|
41
|
+
const { authenticatedMax = 5000, anonymousMax = 600, windowMs = 15 * 60 * 1000, store, exempt, message = 'Too many requests, please try again later.', auth, } = options;
|
|
42
|
+
// Idempotent optional-auth resolver. Reuses the SAME session resolution as
|
|
43
|
+
// every protected route, so the limiter keys by the real user identity.
|
|
44
|
+
const resolveSession = oxy.auth({ ...auth, optional: true });
|
|
45
|
+
const skip = (req) => isBuiltInExempt(req) || (exempt ? exempt(req) : false);
|
|
46
|
+
const limiter = (0, express_rate_limit_1.default)({
|
|
47
|
+
windowMs,
|
|
48
|
+
...(store ? { store } : {}),
|
|
49
|
+
max: (req) => {
|
|
50
|
+
const authed = req;
|
|
51
|
+
const userId = authed.userId ?? authed.user?.id ?? authed.user?._id;
|
|
52
|
+
return userId ? authenticatedMax : anonymousMax;
|
|
53
|
+
},
|
|
54
|
+
keyGenerator: (req) => resolveKey(req),
|
|
55
|
+
message,
|
|
56
|
+
standardHeaders: true,
|
|
57
|
+
legacyHeaders: false,
|
|
58
|
+
skip,
|
|
59
|
+
});
|
|
60
|
+
return (req, res, next) => {
|
|
61
|
+
// Skipped paths bypass BOTH session resolution and limiting — cheap and
|
|
62
|
+
// safe for static/streaming/health traffic.
|
|
63
|
+
if (skip(req)) {
|
|
64
|
+
next();
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
resolveSession(req, res, (err) => {
|
|
68
|
+
if (err) {
|
|
69
|
+
// Optional auth never rejects; a token error just means "anonymous".
|
|
70
|
+
// Swallow the error and continue to limit as anonymous.
|
|
71
|
+
next();
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
limiter(req, res, next);
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
}
|
|
@@ -58,7 +58,7 @@ const debugError = (prefix, ...args) => {
|
|
|
58
58
|
exports.debugError = debugError;
|
|
59
59
|
/**
|
|
60
60
|
* Create a namespaced debug logger
|
|
61
|
-
* @param namespace - Logger namespace (e.g., 'FedCM', '
|
|
61
|
+
* @param namespace - Logger namespace (e.g., 'FedCM', 'SilentAuth')
|
|
62
62
|
* @returns Object with log, warn, error methods
|
|
63
63
|
*
|
|
64
64
|
* @example
|
|
@@ -146,10 +146,10 @@ const mergeAccountsFromRefreshAll = (stored, fresh) => {
|
|
|
146
146
|
}
|
|
147
147
|
const merged = fresh.map((entry) => {
|
|
148
148
|
const previous = storedByAuthuser.get(entry.authuser);
|
|
149
|
-
//
|
|
150
|
-
//
|
|
151
|
-
//
|
|
152
|
-
//
|
|
149
|
+
// Preserve any previously cached identity for a slot that arrives
|
|
150
|
+
// without a user shape rather than overwriting it with blanks, and let
|
|
151
|
+
// AuthManager's getCurrentUser() hydration refresh it on the next
|
|
152
|
+
// snapshot.
|
|
153
153
|
const wireUser = entry.user;
|
|
154
154
|
const username = wireUser?.username ?? previous?.username ?? '';
|
|
155
155
|
const displayName = (0, exports.getAccountDisplayName)({
|
|
@@ -31,25 +31,32 @@ class AuthenticationFailedError extends Error {
|
|
|
31
31
|
exports.AuthenticationFailedError = AuthenticationFailedError;
|
|
32
32
|
/**
|
|
33
33
|
* Ensures a valid token exists before making authenticated API calls.
|
|
34
|
-
* If no valid token exists
|
|
35
|
-
*
|
|
34
|
+
* If no valid token exists, callers may provide a session synchronizer that
|
|
35
|
+
* uses the platform-appropriate new flow (cookie restore, device claim, or
|
|
36
|
+
* native secure restore). This helper never exchanges a session id for a token.
|
|
36
37
|
*
|
|
37
38
|
* @throws {SessionSyncRequiredError} If the session needs to be synced (offline session)
|
|
38
39
|
*/
|
|
39
|
-
async function ensureValidToken(oxyServices,
|
|
40
|
-
if (oxyServices.hasValidToken()
|
|
40
|
+
async function ensureValidToken(oxyServices, _activeSessionId, syncSession) {
|
|
41
|
+
if (oxyServices.hasValidToken()) {
|
|
41
42
|
return;
|
|
42
43
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
if (syncSession) {
|
|
45
|
+
try {
|
|
46
|
+
await syncSession();
|
|
47
|
+
if (oxyServices.hasValidToken()) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch (syncError) {
|
|
52
|
+
const errorMessage = syncError instanceof Error ? syncError.message : String(syncError);
|
|
53
|
+
if (errorMessage.includes('AUTH_REQUIRED_OFFLINE_SESSION') || errorMessage.includes('offline')) {
|
|
54
|
+
throw new SessionSyncRequiredError();
|
|
55
|
+
}
|
|
56
|
+
throw syncError;
|
|
50
57
|
}
|
|
51
|
-
throw tokenError;
|
|
52
58
|
}
|
|
59
|
+
throw new SessionSyncRequiredError('No active access token is available. Sync the session before calling authenticated APIs.');
|
|
53
60
|
}
|
|
54
61
|
/**
|
|
55
62
|
* Checks if an error is an authentication error (401 or auth-related message)
|
|
@@ -79,10 +86,9 @@ async function withAuthErrorHandling(apiCall, options) {
|
|
|
79
86
|
if (!isAuthenticationError(error)) {
|
|
80
87
|
throw error;
|
|
81
88
|
}
|
|
82
|
-
if (options?.syncSession && options?.
|
|
89
|
+
if (options?.syncSession && options?.oxyServices) {
|
|
83
90
|
try {
|
|
84
91
|
await options.syncSession();
|
|
85
|
-
await options.oxyServices.getTokenBySession(options.activeSessionId);
|
|
86
92
|
return await apiCall();
|
|
87
93
|
}
|
|
88
94
|
catch {
|
|
@@ -105,7 +111,7 @@ async function withAuthErrorHandling(apiCall, options) {
|
|
|
105
111
|
* ```
|
|
106
112
|
*/
|
|
107
113
|
async function authenticatedApiCall(oxyServices, activeSessionId, apiCall, syncSession) {
|
|
108
|
-
await ensureValidToken(oxyServices, activeSessionId);
|
|
114
|
+
await ensureValidToken(oxyServices, activeSessionId, syncSession);
|
|
109
115
|
return withAuthErrorHandling(apiCall, {
|
|
110
116
|
syncSession,
|
|
111
117
|
activeSessionId,
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* On a fresh page load / app launch the SDK may have several ways to recover an
|
|
7
7
|
* existing session (silent FedCM, a persisted refresh token, a cross-domain
|
|
8
|
-
* claim,
|
|
8
|
+
* claim, a redirect SSO return, ...). They must be attempted in a deterministic
|
|
9
9
|
* order*, and the FIRST one that yields a session wins — every later step is
|
|
10
10
|
* skipped. This module encodes exactly that contract and nothing else.
|
|
11
11
|
*
|
|
@@ -84,8 +84,8 @@ async function runColdBoot(options) {
|
|
|
84
84
|
}
|
|
85
85
|
let result;
|
|
86
86
|
try {
|
|
87
|
-
// Without a deadline
|
|
88
|
-
//
|
|
87
|
+
// Without a deadline, await the step directly. With a deadline, race
|
|
88
|
+
// the step against the shared deadline. The
|
|
89
89
|
// step's `run()` still STARTS synchronously up to its first `await`
|
|
90
90
|
// (so a terminal step's synchronous navigation side effect always
|
|
91
91
|
// executes), but a non-settling step can no longer block the loop —
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* Clerk-style multi-domain SSO depends on the IdP being reachable on a
|
|
10
10
|
* subdomain of the RP's own apex (e.g. `auth.mention.earth` CNAMEd to the
|
|
11
11
|
* central Oxy IdP). That way every FedCM endpoint, the session cookie,
|
|
12
|
-
* and any
|
|
12
|
+
* and any redirect target are same-site with the RP — the only way
|
|
13
13
|
* to get first-party cookies in Safari ITP and Firefox Total Cookie
|
|
14
14
|
* Protection.
|
|
15
15
|
*
|