@oxyhq/core 12.11.0 → 13.0.0
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 +66 -6
- package/dist/cjs/index.js +2 -12
- package/dist/cjs/mixins/OxyServices.deviceBoot.js +0 -31
- package/dist/cjs/mixins/OxyServices.privacy.js +6 -0
- package/dist/cjs/mixins/OxyServices.user.js +1 -0
- package/dist/cjs/server/index.js +1 -6
- package/dist/cjs/server/rateLimit.js +3 -0
- package/dist/cjs/session/accountDialogController.js +6 -8
- package/dist/cjs/utils/oauthPkce.js +1 -5
- package/dist/cjs/utils/officialOrigins.js +3 -73
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/HttpService.js +66 -6
- package/dist/esm/index.js +2 -3
- package/dist/esm/mixins/OxyServices.deviceBoot.js +1 -32
- package/dist/esm/mixins/OxyServices.privacy.js +6 -0
- package/dist/esm/mixins/OxyServices.user.js +1 -0
- package/dist/esm/server/index.js +1 -1
- package/dist/esm/server/rateLimit.js +3 -0
- package/dist/esm/session/accountDialogController.js +6 -8
- package/dist/esm/utils/oauthPkce.js +0 -4
- package/dist/esm/utils/officialOrigins.js +3 -68
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/HttpService.d.ts +18 -1
- package/dist/types/index.d.ts +3 -5
- package/dist/types/mixins/OxyServices.auth.d.ts +1 -12
- package/dist/types/mixins/OxyServices.deviceBoot.d.ts +1 -5
- package/dist/types/mixins/OxyServices.user.d.ts +2 -0
- package/dist/types/server/index.d.ts +1 -1
- package/dist/types/session/accountDialogController.d.ts +9 -15
- package/dist/types/utils/oauthPkce.d.ts +11 -7
- package/dist/types/utils/officialOrigins.d.ts +3 -13
- package/package.json +4 -4
- package/src/HttpService.ts +67 -6
- package/src/__tests__/inSessionRefresh.test.ts +67 -0
- package/src/index.ts +6 -14
- package/src/mixins/OxyServices.auth.ts +6 -13
- package/src/mixins/OxyServices.deviceBoot.ts +0 -47
- package/src/mixins/OxyServices.privacy.ts +6 -0
- package/src/mixins/OxyServices.user.ts +3 -0
- package/src/mixins/__tests__/commonsSignIn.test.ts +9 -2
- package/src/mixins/__tests__/privacyCacheInvalidation.test.ts +2 -0
- package/src/server/index.ts +1 -8
- package/src/server/rateLimit.ts +3 -0
- package/src/session/__tests__/accountDialogController.test.ts +3 -5
- package/src/session/accountDialogController.ts +12 -18
- package/src/utils/__tests__/officialOrigins.test.ts +0 -57
- package/src/utils/oauthPkce.ts +11 -9
- package/src/utils/officialOrigins.ts +3 -70
- package/dist/cjs/session/hubSync.js +0 -55
- package/dist/esm/session/hubSync.js +0 -51
- package/dist/types/session/hubSync.d.ts +0 -20
- package/src/session/__tests__/hubSync.test.ts +0 -51
- package/src/session/hubSync.ts +0 -79
package/dist/cjs/HttpService.js
CHANGED
|
@@ -56,11 +56,30 @@ const CSRF_FETCH_MAX_ATTEMPTS = 2;
|
|
|
56
56
|
const CSRF_FETCH_RETRY_DELAY_MS = 500;
|
|
57
57
|
/**
|
|
58
58
|
* Cooldown (ms) applied after a failed access-token refresh before another
|
|
59
|
-
* refresh is attempted
|
|
59
|
+
* refresh is attempted while the CURRENT token is still valid (a proactive,
|
|
60
|
+
* near-expiry refresh). Prevents a refresh storm (and server hammering) when
|
|
60
61
|
* the auth refresh handler is failing — every in-flight request that
|
|
61
|
-
* hits a 401 would otherwise trigger its own refresh.
|
|
62
|
+
* hits a 401 would otherwise trigger its own refresh. A still-valid token can
|
|
63
|
+
* afford to wait this out; the request keeps carrying it in the meantime.
|
|
62
64
|
*/
|
|
63
65
|
const TOKEN_REFRESH_COOLDOWN_MS = 15000;
|
|
66
|
+
/**
|
|
67
|
+
* Cooldown (ms) applied after a failed refresh when the CURRENT access token is
|
|
68
|
+
* already past its `exp`. Much shorter than {@link TOKEN_REFRESH_COOLDOWN_MS}:
|
|
69
|
+
* an expired token is UNUSABLE, so the client must re-mint as soon as the mint
|
|
70
|
+
* endpoint is reachable again (e.g. a few seconds after an ECS rolling-deploy
|
|
71
|
+
* blip drains/restarts a task) instead of waiting out the full proactive
|
|
72
|
+
* cooldown while every request forwards or omits a stale bearer → server 401.
|
|
73
|
+
*
|
|
74
|
+
* Still NON-ZERO on purpose: it bounds the request-driven retry rate to at most
|
|
75
|
+
* one attempt per this interval so a PROLONGED outage cannot become a tight
|
|
76
|
+
* network storm. Combined with the process-wide single-flight below (concurrent
|
|
77
|
+
* requests coalesce to one in-flight mint) and the refresh handler's own
|
|
78
|
+
* terminal-state handling (a genuinely revoked session clears its device
|
|
79
|
+
* credential and stops issuing network mints), this recovers a transient blip
|
|
80
|
+
* ~15× faster without weakening the storm guard.
|
|
81
|
+
*/
|
|
82
|
+
const EXPIRED_TOKEN_REFRESH_COOLDOWN_MS = 1000;
|
|
64
83
|
/**
|
|
65
84
|
* Lead time (seconds) before access-token expiry at which a preflight refresh
|
|
66
85
|
* is triggered. A token within this window of `exp` is treated as effectively
|
|
@@ -133,7 +152,15 @@ class TokenStore {
|
|
|
133
152
|
class HttpService {
|
|
134
153
|
constructor(config) {
|
|
135
154
|
this.tokenRefreshPromise = null;
|
|
136
|
-
|
|
155
|
+
/**
|
|
156
|
+
* Epoch ms of the last FAILED refresh (0 = none since the last success). The
|
|
157
|
+
* post-failure cooldown is measured from here; its length depends on whether
|
|
158
|
+
* the current token is still valid ({@link TOKEN_REFRESH_COOLDOWN_MS}) or
|
|
159
|
+
* already expired ({@link EXPIRED_TOKEN_REFRESH_COOLDOWN_MS}), so an expired
|
|
160
|
+
* token recovers promptly the instant it crosses `exp` — without storing a
|
|
161
|
+
* fixed deadline that could not shrink once the token expired mid-cooldown.
|
|
162
|
+
*/
|
|
163
|
+
this.lastRefreshFailureAt = 0;
|
|
137
164
|
this.authRefreshHandler = null;
|
|
138
165
|
this.accessTokenProvider = null;
|
|
139
166
|
this.deviceSecretMintInFlight = null;
|
|
@@ -823,26 +850,38 @@ class HttpService {
|
|
|
823
850
|
if (!this.authRefreshHandler) {
|
|
824
851
|
return null;
|
|
825
852
|
}
|
|
826
|
-
|
|
853
|
+
// Post-failure cooldown. A genuinely EXPIRED current token uses a much
|
|
854
|
+
// shorter cooldown than a still-valid (proactive, near-expiry) one: an
|
|
855
|
+
// expired token is unusable, so re-mint as soon as the endpoint is reachable
|
|
856
|
+
// again rather than waiting out the full window while requests carry a stale
|
|
857
|
+
// bearer. Both cooldowns are measured from the last failure, so the moment a
|
|
858
|
+
// still-valid token crosses `exp` mid-cooldown the shorter window applies.
|
|
859
|
+
const cooldownMs = this.isAccessTokenExpired()
|
|
860
|
+
? EXPIRED_TOKEN_REFRESH_COOLDOWN_MS
|
|
861
|
+
: TOKEN_REFRESH_COOLDOWN_MS;
|
|
862
|
+
if (Date.now() - this.lastRefreshFailureAt < cooldownMs) {
|
|
827
863
|
return null;
|
|
828
864
|
}
|
|
829
865
|
if (!this.tokenRefreshPromise) {
|
|
830
866
|
this.tokenRefreshPromise = this.authRefreshHandler(reason)
|
|
831
867
|
.then((newToken) => {
|
|
832
868
|
if (!newToken) {
|
|
833
|
-
this.
|
|
869
|
+
this.lastRefreshFailureAt = Date.now();
|
|
834
870
|
return null;
|
|
835
871
|
}
|
|
836
872
|
if (this.tokenStore.getAccessToken() !== newToken) {
|
|
837
873
|
this.tokenStore.setTokens(newToken);
|
|
838
874
|
this.notifyTokenChange();
|
|
839
875
|
}
|
|
876
|
+
// A success clears the failure timestamp so the next refresh is never
|
|
877
|
+
// throttled by a stale cooldown.
|
|
878
|
+
this.lastRefreshFailureAt = 0;
|
|
840
879
|
this.logger.debug('Token refreshed via the auth refresh handler');
|
|
841
880
|
return newToken;
|
|
842
881
|
})
|
|
843
882
|
.catch((error) => {
|
|
844
883
|
this.logger.warn('Token refresh failed:', error);
|
|
845
|
-
this.
|
|
884
|
+
this.lastRefreshFailureAt = Date.now();
|
|
846
885
|
return null;
|
|
847
886
|
})
|
|
848
887
|
.finally(() => {
|
|
@@ -851,6 +890,27 @@ class HttpService {
|
|
|
851
890
|
}
|
|
852
891
|
return this.tokenRefreshPromise;
|
|
853
892
|
}
|
|
893
|
+
/**
|
|
894
|
+
* Whether the CURRENT stored access token is already past its `exp`. Drives
|
|
895
|
+
* the shorter post-failure refresh cooldown ({@link EXPIRED_TOKEN_REFRESH_COOLDOWN_MS}):
|
|
896
|
+
* a still-valid (near-expiry) token can wait out the full cooldown, but an
|
|
897
|
+
* expired one must re-mint promptly. Returns `false` for an absent or
|
|
898
|
+
* opaque/no-`exp` token — no proof it is expired, so keep the conservative
|
|
899
|
+
* (longer) cooldown and avoid an unnecessary retry loop.
|
|
900
|
+
*/
|
|
901
|
+
isAccessTokenExpired() {
|
|
902
|
+
const token = this.tokenStore.getAccessToken();
|
|
903
|
+
if (!token) {
|
|
904
|
+
return false;
|
|
905
|
+
}
|
|
906
|
+
try {
|
|
907
|
+
const decoded = (0, jwt_decode_1.jwtDecode)(token);
|
|
908
|
+
return typeof decoded.exp === 'number' && decoded.exp <= Math.floor(Date.now() / 1000);
|
|
909
|
+
}
|
|
910
|
+
catch {
|
|
911
|
+
return false;
|
|
912
|
+
}
|
|
913
|
+
}
|
|
854
914
|
/**
|
|
855
915
|
* PROCESS-WIDE single-flight for the rotating device-secret mint
|
|
856
916
|
* (`POST /session/device/token`).
|
package/dist/cjs/index.js
CHANGED
|
@@ -21,8 +21,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
21
21
|
exports.TopicType = exports.SECURITY_EVENT_SEVERITY_MAP = exports.DeviceManager = exports.deriveSharedSecret = exports.AEAD_NONCE_LENGTH = exports.AEAD_KEY_LENGTH = exports.decryptAead = exports.encryptAead = exports.hkdfSha256 = exports.RecoveryPhraseService = exports.SignatureService = exports.updateIdentityMarker = exports.readIdentityMarker = exports.IdentityUnavailableError = exports.IdentityPersistError = exports.IdentityAlreadyExistsError = exports.KeyManager = exports.sessionsArraysEqual = exports.normalizeAndSortSessions = exports.mergeSessions = exports.authenticatedApiCall = exports.withAuthErrorHandling = exports.isAuthenticationError = exports.ensureValidToken = exports.AuthenticationFailedError = exports.SessionSyncRequiredError = exports.verifyPublicCardAttestation = exports.parseAttestPayload = exports.parseIdPayload = exports.buildUserDid = exports.ORGANIZATION_CATEGORIES = exports.normalizeProfileLinks = exports.getNormalizedUserHandle = exports.getCanonicalUserHandle = exports.normalizeUserIdentityOrNull = exports.normalizeUserIdentity = exports.getNormalizedUserId = exports.OxyAppDataIdentifierError = exports.commonsDeliveryPlatform = exports.pushTargetsFromDelivery = exports.selectCommonsDelivery = exports.parseCommonsApprovalExpiresAt = exports.getCommonsApprovalBlockingReason = exports.ServiceCredentialMismatchError = exports.oxyClient = exports.OXY_CLOUD_URL = exports.OxyAuthenticationTimeoutError = exports.OxyAuthenticationError = exports.AssetUrlResolutionError = exports.OxyServices = void 0;
|
|
22
22
|
exports.withRetry = exports.delay = exports.shouldAllowRequest = exports.recordSuccess = exports.recordFailure = exports.calculateBackoffInterval = exports.createCircuitBreakerState = exports.DEFAULT_CIRCUIT_BREAKER_CONFIG = exports.isRetryableError = exports.isNetworkError = exports.isServerError = exports.isRateLimitError = exports.isNotFoundError = exports.isForbiddenError = exports.isUnauthorizedError = exports.isAlreadyRegisteredError = exports.getErrorMessage = exports.getErrorStatus = exports.HttpStatus = exports.getSystemColorScheme = exports.systemPrefersDarkMode = exports.getOppositeTheme = exports.normalizeColorScheme = exports.normalizeTheme = exports.getContrastTextColor = exports.isLightColor = exports.withOpacity = exports.rgbToHex = exports.hexToRgb = exports.lightenColor = exports.darkenColor = exports.isWebBrowser = exports.isAndroid = exports.isIOS = exports.isNative = exports.isWeb = exports.setPlatformOS = exports.getPlatformOS = exports.getPrimaryLanguage = exports.getUserLanguages = exports.isRTLLocale = exports.getNativeLanguageName = exports.getLanguageName = exports.getLanguageMetadata = exports.isSupportedLocale = exports.normalizeLocale = exports.getBaseLanguage = exports.FALLBACK_LOCALE = exports.SUPPORTED_LANGUAGES = exports.TopicSource = void 0;
|
|
23
23
|
exports.getAccountColor = exports.formatPublicKeyHandle = exports.getAccountFallbackHandle = exports.getAccountDisplayName = exports.createQuickAccount = exports.buildAccountsArray = exports.updateAvatarVisibility = exports.isDev = exports.consoleSink = exports.resetLoggerConfig = exports.getLoggerConfig = exports.configureLogger = exports.createLogger = exports.logger = exports.normalizeMultilineText = exports.normalizeInlineText = exports.validateAndSanitizeUserInput = exports.isValidObjectId = exports.sanitizeHTML = exports.sanitizeString = exports.isValidFileType = exports.isValidFileSize = exports.isValidDate = exports.isValidURL = exports.isValidUUID = exports.isValidObject = exports.isValidArray = exports.isRequiredBoolean = exports.isRequiredNumber = exports.isRequiredString = exports.DISPLAY_NAME_ORPHANED_MARK_SOURCE = exports.DISPLAY_NAME_DISALLOWED_SOURCE = exports.DISPLAY_NAME_ALLOWED_SCRIPTS = exports.isValidDisplayName = exports.isValidPassword = exports.isValidUsername = exports.isValidEmail = exports.PASSWORD_REGEX = exports.USERNAME_REGEX = exports.EMAIL_REGEX = exports.retryAsync = exports.validateRequiredFields = exports.handleHttpError = exports.createApiError = exports.ErrorCodes = exports.safeJsonParse = exports.buildPaginationParams = exports.buildUrl = exports.buildSearchParams = exports.translate = void 0;
|
|
24
|
-
exports.
|
|
25
|
-
exports.packageInfo = exports.runSessionColdBoot = exports.TOKEN_REFRESH_LEAD_MS = exports.startTokenRefreshScheduler = exports.installAuthRefreshHandler =
|
|
24
|
+
exports.createAuthRefreshHandler = exports.refreshDeviceSecretArm = exports.refreshPersistedSession = exports.AccountNotOnDeviceError = exports.establishIdentitySession = exports.resolveIdentityPin = exports.IDENTITY_PIN_STORAGE_KEY = exports.identityPinMatches = exports.createMemoryIdentityPinStore = exports.createNativeIdentityPinStore = exports.createWebIdentityPinStore = exports.AUTH_STATE_STORAGE_KEY = exports.createMemoryAuthStateStore = exports.createNativeAuthStateStore = exports.createWebAuthStateStore = exports.createAccountDialogController = exports.AccountDialogController = exports.switchableAccountIds = exports.projectSwitchableAccounts = exports.accountIdsOf = exports.activeUserOf = exports.activeSessionIdOf = exports.deviceStateToClientSessions = exports.createSessionClient = exports.createSessionClientHost = exports.SessionClient = exports.isAllowedDeviceJoinOrigin = exports.isOfficialWebOrigin = exports.isLoopbackOrigin = exports.consumeOAuthReturnPath = exports.persistOAuthReturnPath = exports.clearOAuthHandshake = exports.readOAuthHandshake = exports.persistOAuthHandshake = exports.canonicalizeOAuthRedirectUri = exports.normalizeOAuthRedirectUri = exports.OXY_OAUTH_RETURN_PATH_STORAGE_KEY = exports.OXY_OAUTH_REDIRECT_URI_STORAGE_KEY = exports.OXY_OAUTH_CODE_VERIFIER_STORAGE_KEY = exports.OXY_OAUTH_STATE_STORAGE_KEY = exports.OXY_AUTHORIZE_URL = exports.DEFAULT_OAUTH_SCOPE = exports.generatePkcePair = exports.generateOAuthState = exports.computeCodeChallenge = exports.buildOAuthAuthorizeUrl = exports.runColdBoot = exports.isOxyRpOrigin = exports.CENTRAL_IDP_APEX = exports.registrableApex = void 0;
|
|
25
|
+
exports.packageInfo = exports.runSessionColdBoot = exports.TOKEN_REFRESH_LEAD_MS = exports.startTokenRefreshScheduler = exports.installAuthRefreshHandler = void 0;
|
|
26
26
|
// Ensure crypto polyfills are loaded before anything else
|
|
27
27
|
require("./crypto/polyfill");
|
|
28
28
|
// ---------------------------------------------------------------------------
|
|
@@ -319,8 +319,6 @@ Object.defineProperty(exports, "OXY_AUTHORIZE_URL", { enumerable: true, get: fun
|
|
|
319
319
|
Object.defineProperty(exports, "OXY_OAUTH_STATE_STORAGE_KEY", { enumerable: true, get: function () { return oauthPkce_1.OXY_OAUTH_STATE_STORAGE_KEY; } });
|
|
320
320
|
Object.defineProperty(exports, "OXY_OAUTH_CODE_VERIFIER_STORAGE_KEY", { enumerable: true, get: function () { return oauthPkce_1.OXY_OAUTH_CODE_VERIFIER_STORAGE_KEY; } });
|
|
321
321
|
Object.defineProperty(exports, "OXY_OAUTH_REDIRECT_URI_STORAGE_KEY", { enumerable: true, get: function () { return oauthPkce_1.OXY_OAUTH_REDIRECT_URI_STORAGE_KEY; } });
|
|
322
|
-
Object.defineProperty(exports, "OXY_SILENT_OAUTH_ATTEMPTED_KEY", { enumerable: true, get: function () { return oauthPkce_1.OXY_SILENT_OAUTH_ATTEMPTED_KEY; } });
|
|
323
|
-
Object.defineProperty(exports, "OXY_CROSS_ORIGIN_RESTORE_ATTEMPTED_KEY", { enumerable: true, get: function () { return oauthPkce_1.OXY_CROSS_ORIGIN_RESTORE_ATTEMPTED_KEY; } });
|
|
324
322
|
Object.defineProperty(exports, "OXY_OAUTH_RETURN_PATH_STORAGE_KEY", { enumerable: true, get: function () { return oauthPkce_1.OXY_OAUTH_RETURN_PATH_STORAGE_KEY; } });
|
|
325
323
|
Object.defineProperty(exports, "normalizeOAuthRedirectUri", { enumerable: true, get: function () { return oauthPkce_1.normalizeOAuthRedirectUri; } });
|
|
326
324
|
Object.defineProperty(exports, "canonicalizeOAuthRedirectUri", { enumerable: true, get: function () { return oauthPkce_1.canonicalizeOAuthRedirectUri; } });
|
|
@@ -330,17 +328,9 @@ Object.defineProperty(exports, "clearOAuthHandshake", { enumerable: true, get: f
|
|
|
330
328
|
Object.defineProperty(exports, "persistOAuthReturnPath", { enumerable: true, get: function () { return oauthPkce_1.persistOAuthReturnPath; } });
|
|
331
329
|
Object.defineProperty(exports, "consumeOAuthReturnPath", { enumerable: true, get: function () { return oauthPkce_1.consumeOAuthReturnPath; } });
|
|
332
330
|
var officialOrigins_1 = require("./utils/officialOrigins");
|
|
333
|
-
Object.defineProperty(exports, "buildIdpHubOrigin", { enumerable: true, get: function () { return officialOrigins_1.buildIdpHubOrigin; } });
|
|
334
|
-
Object.defineProperty(exports, "buildHubSyncUrl", { enumerable: true, get: function () { return officialOrigins_1.buildHubSyncUrl; } });
|
|
335
|
-
Object.defineProperty(exports, "isIdpHubOrigin", { enumerable: true, get: function () { return officialOrigins_1.isIdpHubOrigin; } });
|
|
336
331
|
Object.defineProperty(exports, "isLoopbackOrigin", { enumerable: true, get: function () { return officialOrigins_1.isLoopbackOrigin; } });
|
|
337
332
|
Object.defineProperty(exports, "isOfficialWebOrigin", { enumerable: true, get: function () { return officialOrigins_1.isOfficialWebOrigin; } });
|
|
338
333
|
Object.defineProperty(exports, "isAllowedDeviceJoinOrigin", { enumerable: true, get: function () { return officialOrigins_1.isAllowedDeviceJoinOrigin; } });
|
|
339
|
-
Object.defineProperty(exports, "normalizeOfficialReturnOrigin", { enumerable: true, get: function () { return officialOrigins_1.normalizeOfficialReturnOrigin; } });
|
|
340
|
-
Object.defineProperty(exports, "parseHubSyncReturnUrl", { enumerable: true, get: function () { return officialOrigins_1.parseHubSyncReturnUrl; } });
|
|
341
|
-
var hubSync_1 = require("./session/hubSync");
|
|
342
|
-
Object.defineProperty(exports, "syncHubAfterSignIn", { enumerable: true, get: function () { return hubSync_1.syncHubAfterSignIn; } });
|
|
343
|
-
Object.defineProperty(exports, "redeemHubTicketOnHub", { enumerable: true, get: function () { return hubSync_1.redeemHubTicketOnHub; } });
|
|
344
334
|
// ---------------------------------------------------------------------------
|
|
345
335
|
// Session sync (device-scoped multi-account session client)
|
|
346
336
|
// ---------------------------------------------------------------------------
|
|
@@ -110,36 +110,5 @@ function OxyServicesDeviceBootMixin(Base) {
|
|
|
110
110
|
throw normalized;
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
|
-
/** Mint a one-time hub sync ticket (bearer required). */
|
|
114
|
-
async issueHubTicket(returnOrigin) {
|
|
115
|
-
try {
|
|
116
|
-
const res = await this.makeRequest('POST', '/session/device/hub-ticket', { returnOrigin }, { cache: false });
|
|
117
|
-
const parsed = (0, contracts_1.safeParseContract)(contracts_1.deviceHubTicketIssueResponseSchema, res);
|
|
118
|
-
if (!parsed) {
|
|
119
|
-
throw new Error('session/device/hub-ticket returned an unexpected response shape');
|
|
120
|
-
}
|
|
121
|
-
return parsed;
|
|
122
|
-
}
|
|
123
|
-
catch (error) {
|
|
124
|
-
throw this.handleError(error);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
/** Redeem a hub sync ticket for a fresh device secret (public). */
|
|
128
|
-
async redeemHubTicket(ticket, returnOrigin) {
|
|
129
|
-
try {
|
|
130
|
-
const res = await this.makeRequest('POST', '/session/device/redeem-ticket', { ticket, returnOrigin },
|
|
131
|
-
// Public device-hub sync mint (bearer-less). Same control-plane class as
|
|
132
|
-
// the device-secret mint — bypassQueue so it never waits for a slot.
|
|
133
|
-
{ cache: false, skipAuth: true, bypassQueue: true });
|
|
134
|
-
const parsed = (0, contracts_1.safeParseContract)(contracts_1.deviceHubTicketRedeemResponseSchema, res);
|
|
135
|
-
if (!parsed) {
|
|
136
|
-
throw new Error('session/device/redeem-ticket returned an unexpected response shape');
|
|
137
|
-
}
|
|
138
|
-
return parsed;
|
|
139
|
-
}
|
|
140
|
-
catch (error) {
|
|
141
|
-
throw this.handleError(error);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
113
|
};
|
|
145
114
|
}
|
|
@@ -152,6 +152,9 @@ function OxyServicesPrivacyMixin(Base) {
|
|
|
152
152
|
cache: false,
|
|
153
153
|
});
|
|
154
154
|
this.clearCacheEntry('GET:/privacy/restricted');
|
|
155
|
+
// The restriction changed the viewer's graph (`restrictedIds`) — bust the
|
|
156
|
+
// cached consolidated `GET /users/me/graph` so the next read reflects it.
|
|
157
|
+
this.clearCacheEntry('GET:/users/me/graph');
|
|
155
158
|
return result;
|
|
156
159
|
}
|
|
157
160
|
catch (error) {
|
|
@@ -175,6 +178,9 @@ function OxyServicesPrivacyMixin(Base) {
|
|
|
175
178
|
cache: false,
|
|
176
179
|
});
|
|
177
180
|
this.clearCacheEntry('GET:/privacy/restricted');
|
|
181
|
+
// Symmetric to restrictUser: the unrestrict changed the viewer's
|
|
182
|
+
// `restrictedIds`, so bust the consolidated `GET /users/me/graph` cache.
|
|
183
|
+
this.clearCacheEntry('GET:/users/me/graph');
|
|
178
184
|
return result;
|
|
179
185
|
}
|
|
180
186
|
catch (error) {
|
package/dist/cjs/server/index.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* ```
|
|
17
17
|
*/
|
|
18
18
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
-
exports.
|
|
19
|
+
exports.isOfficialWebOrigin = exports.registrableApex = exports.verifySecret = exports.createOxyCors = exports.UPSTREAM_HEADERS_TIMEOUT_MS = exports.MAX_URL_LENGTH = exports.MAX_REDIRECTS = exports.DEFAULT_USER_AGENT = exports.BLOCKED_HOSTNAMES = exports.ALLOWED_PROTOCOLS = exports.ALLOWED_PORTS = exports.UpstreamError = exports.SsrfRejection = exports.safeFetch = exports.isBlockedIp = exports.assertSafePublicUrl = exports.createOxyRateLimit = exports.OXY_SERVICE_ENVIRONMENTS = exports.requireOxyAuth = exports.isOxyAuthenticated = exports.getRequiredOxyUserId = exports.getOxyUserId = exports.createOxyAuthMiddleware = exports.createOptionalOxyAuth = void 0;
|
|
20
20
|
var auth_1 = require("./auth");
|
|
21
21
|
Object.defineProperty(exports, "createOptionalOxyAuth", { enumerable: true, get: function () { return auth_1.createOptionalOxyAuth; } });
|
|
22
22
|
Object.defineProperty(exports, "createOxyAuthMiddleware", { enumerable: true, get: function () { return auth_1.createOxyAuthMiddleware; } });
|
|
@@ -54,9 +54,4 @@ Object.defineProperty(exports, "verifySecret", { enumerable: true, get: function
|
|
|
54
54
|
var registrableApex_1 = require("../utils/registrableApex");
|
|
55
55
|
Object.defineProperty(exports, "registrableApex", { enumerable: true, get: function () { return registrableApex_1.registrableApex; } });
|
|
56
56
|
var officialOrigins_1 = require("../utils/officialOrigins");
|
|
57
|
-
Object.defineProperty(exports, "buildIdpHubOrigin", { enumerable: true, get: function () { return officialOrigins_1.buildIdpHubOrigin; } });
|
|
58
|
-
Object.defineProperty(exports, "buildHubSyncUrl", { enumerable: true, get: function () { return officialOrigins_1.buildHubSyncUrl; } });
|
|
59
|
-
Object.defineProperty(exports, "isIdpHubOrigin", { enumerable: true, get: function () { return officialOrigins_1.isIdpHubOrigin; } });
|
|
60
57
|
Object.defineProperty(exports, "isOfficialWebOrigin", { enumerable: true, get: function () { return officialOrigins_1.isOfficialWebOrigin; } });
|
|
61
|
-
Object.defineProperty(exports, "normalizeOfficialReturnOrigin", { enumerable: true, get: function () { return officialOrigins_1.normalizeOfficialReturnOrigin; } });
|
|
62
|
-
Object.defineProperty(exports, "parseHubSyncReturnUrl", { enumerable: true, get: function () { return officialOrigins_1.parseHubSyncReturnUrl; } });
|
|
@@ -174,6 +174,9 @@ function createOxyRateLimit(oxy, options = {}) {
|
|
|
174
174
|
standardHeaders: true,
|
|
175
175
|
legacyHeaders: false,
|
|
176
176
|
skip,
|
|
177
|
+
// hashAnonymousIp already buckets IPv6 to /56 before HMAC; disable the v8
|
|
178
|
+
// static source scan that false-positives on req.ip (ERR_ERL_KEY_GEN_IPV6).
|
|
179
|
+
validate: { keyGeneratorIpFallback: false },
|
|
177
180
|
});
|
|
178
181
|
return (req, res, next) => {
|
|
179
182
|
// Skipped paths bypass BOTH session resolution and limiting — cheap and
|
|
@@ -481,9 +481,9 @@ class AccountDialogController {
|
|
|
481
481
|
user: result.user,
|
|
482
482
|
accessToken: result.accessToken,
|
|
483
483
|
}, result.user,
|
|
484
|
-
// A switch is IN-PLACE:
|
|
485
|
-
//
|
|
486
|
-
//
|
|
484
|
+
// A switch is IN-PLACE: use the switch commit funnel (not sign-in).
|
|
485
|
+
// Cross-tab/app propagation rides the server's `session_state` socket
|
|
486
|
+
// broadcast, not a navigation.
|
|
487
487
|
{ fromSwitch: true });
|
|
488
488
|
}
|
|
489
489
|
// Re-project + refetch immediately; the subscription also fires.
|
|
@@ -986,11 +986,9 @@ class AccountDialogController {
|
|
|
986
986
|
* consumer's commit funnel (durable persist + hydration); falls back to
|
|
987
987
|
* `SessionClient.registerAndActivate` (registration + activation only).
|
|
988
988
|
*
|
|
989
|
-
* A SWITCH (`opts.fromSwitch`) uses the IN-PLACE `commitSwitchedSession` funnel
|
|
990
|
-
*
|
|
991
|
-
*
|
|
992
|
-
* switch funnel is not wired it falls back to the sign-in funnel, then to
|
|
993
|
-
* `registerAndActivate`.
|
|
989
|
+
* A SWITCH (`opts.fromSwitch`) uses the IN-PLACE `commitSwitchedSession` funnel;
|
|
990
|
+
* a SIGN-IN uses `commitSession`. When the switch funnel is not wired it falls
|
|
991
|
+
* back to the sign-in funnel, then to `registerAndActivate`.
|
|
994
992
|
*/
|
|
995
993
|
async commitAuthorizedSession(session, user, opts) {
|
|
996
994
|
const commit = opts?.fromSwitch
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* React Native. No `require()`, so the ESM build stays bundler-clean.
|
|
18
18
|
*/
|
|
19
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
-
exports.OXY_OAUTH_RETURN_PATH_STORAGE_KEY = exports.
|
|
20
|
+
exports.OXY_OAUTH_RETURN_PATH_STORAGE_KEY = exports.OXY_OAUTH_REDIRECT_URI_STORAGE_KEY = exports.OXY_OAUTH_CODE_VERIFIER_STORAGE_KEY = exports.OXY_OAUTH_STATE_STORAGE_KEY = exports.DEFAULT_OAUTH_SCOPE = exports.OXY_AUTHORIZE_URL = void 0;
|
|
21
21
|
exports.computeCodeChallenge = computeCodeChallenge;
|
|
22
22
|
exports.generatePkcePair = generatePkcePair;
|
|
23
23
|
exports.generateOAuthState = generateOAuthState;
|
|
@@ -159,10 +159,6 @@ exports.OXY_OAUTH_STATE_STORAGE_KEY = 'oxy_oauth_state';
|
|
|
159
159
|
exports.OXY_OAUTH_CODE_VERIFIER_STORAGE_KEY = 'oxy_oauth_code_verifier';
|
|
160
160
|
/** `sessionStorage` key — the exact `redirect_uri` sent on the authorize request. */
|
|
161
161
|
exports.OXY_OAUTH_REDIRECT_URI_STORAGE_KEY = 'oxy.oauth_redirect_uri';
|
|
162
|
-
/** `sessionStorage` key — at most one silent OAuth attempt per navigation. */
|
|
163
|
-
exports.OXY_SILENT_OAUTH_ATTEMPTED_KEY = 'oxy.silent_oauth_attempted';
|
|
164
|
-
/** `sessionStorage` key — blocks further cross-origin auto-restore in this tab. */
|
|
165
|
-
exports.OXY_CROSS_ORIGIN_RESTORE_ATTEMPTED_KEY = 'oxy.cross_origin_restore_attempted';
|
|
166
162
|
/**
|
|
167
163
|
* `sessionStorage` key for the in-app path to return to after an authorize
|
|
168
164
|
* round trip. See {@link persistOAuthReturnPath}.
|
|
@@ -1,17 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* Official first-party web origin allowlist — shared by
|
|
4
|
-
*
|
|
3
|
+
* Official first-party web origin allowlist — shared by OAuth redirect
|
|
4
|
+
* validation and the server-side trusted-origin checks.
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
exports.isAllowedDeviceJoinOrigin = void 0;
|
|
8
|
-
exports.buildIdpHubOrigin = buildIdpHubOrigin;
|
|
9
|
-
exports.isIdpHubOrigin = isIdpHubOrigin;
|
|
10
8
|
exports.isLoopbackOrigin = isLoopbackOrigin;
|
|
11
9
|
exports.isOfficialWebOrigin = isOfficialWebOrigin;
|
|
12
|
-
exports.normalizeOfficialReturnOrigin = normalizeOfficialReturnOrigin;
|
|
13
|
-
exports.parseHubSyncReturnUrl = parseHubSyncReturnUrl;
|
|
14
|
-
exports.buildHubSyncUrl = buildHubSyncUrl;
|
|
15
10
|
const authWebUrl_1 = require("./authWebUrl");
|
|
16
11
|
const registrableApex_1 = require("./registrableApex");
|
|
17
12
|
/** Official first-party registrable apexes (mirrors API trusted origins). */
|
|
@@ -27,30 +22,9 @@ const OFFICIAL_APEXES = new Set([
|
|
|
27
22
|
'moovo.now',
|
|
28
23
|
'mercaria.co',
|
|
29
24
|
]);
|
|
30
|
-
function buildIdpHubOrigin() {
|
|
31
|
-
return `https://auth.${authWebUrl_1.CENTRAL_IDP_APEX}`;
|
|
32
|
-
}
|
|
33
|
-
/** Whether the current web origin is the central IdP hub (`auth.oxy.so`). */
|
|
34
|
-
function isIdpHubOrigin() {
|
|
35
|
-
if (typeof globalThis === 'undefined') {
|
|
36
|
-
return false;
|
|
37
|
-
}
|
|
38
|
-
const location = globalThis.location;
|
|
39
|
-
if (!location) {
|
|
40
|
-
return false;
|
|
41
|
-
}
|
|
42
|
-
try {
|
|
43
|
-
const { hostname } = new URL(location.href);
|
|
44
|
-
return hostname === `auth.${authWebUrl_1.CENTRAL_IDP_APEX}`;
|
|
45
|
-
}
|
|
46
|
-
catch {
|
|
47
|
-
return false;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
25
|
/**
|
|
51
26
|
* Whether an origin is a loopback / local-dev origin (`localhost`, `127.0.0.1`,
|
|
52
|
-
* or `[::1]` on any port, http or https).
|
|
53
|
-
* hosted IdP for cross-origin session restore.
|
|
27
|
+
* or `[::1]` on any port, http or https).
|
|
54
28
|
*/
|
|
55
29
|
function isLoopbackOrigin(origin) {
|
|
56
30
|
try {
|
|
@@ -86,49 +60,5 @@ function isOfficialWebOrigin(origin) {
|
|
|
86
60
|
return false;
|
|
87
61
|
}
|
|
88
62
|
}
|
|
89
|
-
/** Normalize and validate a return URL against official origins. Returns origin only. */
|
|
90
|
-
function normalizeOfficialReturnOrigin(raw) {
|
|
91
|
-
try {
|
|
92
|
-
const parsed = new URL(raw);
|
|
93
|
-
if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {
|
|
94
|
-
return null;
|
|
95
|
-
}
|
|
96
|
-
if (!isOfficialWebOrigin(parsed.origin)) {
|
|
97
|
-
return null;
|
|
98
|
-
}
|
|
99
|
-
return parsed.origin;
|
|
100
|
-
}
|
|
101
|
-
catch {
|
|
102
|
-
return null;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
/** Validate a hub-sync return URL; returns the full normalized URL string. */
|
|
106
|
-
function parseHubSyncReturnUrl(raw) {
|
|
107
|
-
if (!raw) {
|
|
108
|
-
return null;
|
|
109
|
-
}
|
|
110
|
-
try {
|
|
111
|
-
const parsed = new URL(raw);
|
|
112
|
-
if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {
|
|
113
|
-
return null;
|
|
114
|
-
}
|
|
115
|
-
if (!isOfficialWebOrigin(parsed.origin)) {
|
|
116
|
-
return null;
|
|
117
|
-
}
|
|
118
|
-
return parsed.toString();
|
|
119
|
-
}
|
|
120
|
-
catch {
|
|
121
|
-
return null;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
/** Build auth.oxy.so/sync redirect URL with a one-time hub ticket. */
|
|
125
|
-
function buildHubSyncUrl(ticket, returnUrl) {
|
|
126
|
-
const url = new URL('/sync', buildIdpHubOrigin());
|
|
127
|
-
url.searchParams.set('ticket', ticket);
|
|
128
|
-
if (returnUrl) {
|
|
129
|
-
url.searchParams.set('return', returnUrl);
|
|
130
|
-
}
|
|
131
|
-
return url.toString();
|
|
132
|
-
}
|
|
133
63
|
/** @deprecated Use {@link isOfficialWebOrigin}. */
|
|
134
64
|
exports.isAllowedDeviceJoinOrigin = isOfficialWebOrigin;
|