@oxyhq/core 12.7.0 → 12.9.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.
Files changed (56) hide show
  1. package/dist/cjs/.tsbuildinfo +1 -1
  2. package/dist/cjs/boot/sessionColdBoot.js +16 -3
  3. package/dist/cjs/crypto/identityMarker.js +255 -0
  4. package/dist/cjs/crypto/keyManager.js +844 -106
  5. package/dist/cjs/index.js +8 -4
  6. package/dist/cjs/mixins/OxyServices.auth.js +21 -6
  7. package/dist/cjs/mixins/OxyServices.deviceBoot.js +9 -1
  8. package/dist/cjs/mixins/OxyServices.utility.js +11 -1
  9. package/dist/cjs/server/auth.js +3 -0
  10. package/dist/cjs/server/index.js +2 -1
  11. package/dist/cjs/utils/oxyServiceEnvironment.js +19 -0
  12. package/dist/esm/.tsbuildinfo +1 -1
  13. package/dist/esm/boot/sessionColdBoot.js +16 -3
  14. package/dist/esm/crypto/identityMarker.js +248 -0
  15. package/dist/esm/crypto/keyManager.js +843 -106
  16. package/dist/esm/index.js +2 -1
  17. package/dist/esm/mixins/OxyServices.auth.js +21 -6
  18. package/dist/esm/mixins/OxyServices.deviceBoot.js +9 -1
  19. package/dist/esm/mixins/OxyServices.utility.js +11 -1
  20. package/dist/esm/server/auth.js +2 -0
  21. package/dist/esm/server/index.js +1 -1
  22. package/dist/esm/utils/oxyServiceEnvironment.js +16 -0
  23. package/dist/types/.tsbuildinfo +1 -1
  24. package/dist/types/boot/sessionColdBoot.d.ts +25 -0
  25. package/dist/types/crypto/identityMarker.d.ts +94 -0
  26. package/dist/types/crypto/keyManager.d.ts +212 -3
  27. package/dist/types/index.d.ts +4 -2
  28. package/dist/types/mixins/OxyServices.auth.d.ts +27 -2
  29. package/dist/types/mixins/OxyServices.deviceBoot.d.ts +8 -0
  30. package/dist/types/mixins/OxyServices.utility.d.ts +3 -0
  31. package/dist/types/server/auth.d.ts +4 -0
  32. package/dist/types/server/index.d.ts +2 -2
  33. package/dist/types/utils/oxyServiceEnvironment.d.ts +17 -0
  34. package/package.json +1 -1
  35. package/src/boot/__tests__/sessionColdBoot.test.ts +113 -0
  36. package/src/boot/sessionColdBoot.ts +42 -3
  37. package/src/crypto/__tests__/identityMocks.ts +125 -0
  38. package/src/crypto/__tests__/keyManager.atomicity.test.ts +79 -94
  39. package/src/crypto/__tests__/keyManager.cacheSafety.test.ts +175 -0
  40. package/src/crypto/__tests__/keyManager.identityStatus.test.ts +217 -0
  41. package/src/crypto/__tests__/keyManager.recoveryLadder.test.ts +179 -0
  42. package/src/crypto/__tests__/keyManager.storageMigration.test.ts +227 -0
  43. package/src/crypto/__tests__/keyManager.test.ts +77 -87
  44. package/src/crypto/identityMarker.ts +291 -0
  45. package/src/crypto/keyManager.ts +1026 -105
  46. package/src/index.ts +7 -1
  47. package/src/mixins/OxyServices.auth.ts +31 -7
  48. package/src/mixins/OxyServices.deviceBoot.ts +9 -1
  49. package/src/mixins/OxyServices.utility.ts +19 -1
  50. package/src/mixins/__tests__/OxyServices.deviceBoot.test.ts +4 -2
  51. package/src/mixins/__tests__/commonsSignIn.test.ts +84 -1
  52. package/src/mixins/__tests__/serviceAuth.test.ts +65 -0
  53. package/src/server/auth.ts +5 -0
  54. package/src/server/index.ts +2 -0
  55. package/src/utils/__tests__/oxyServiceEnvironment.test.ts +7 -0
  56. package/src/utils/oxyServiceEnvironment.ts +17 -0
package/dist/cjs/index.js CHANGED
@@ -18,10 +18,10 @@
18
18
  * If a symbol does not appear here, it is NOT part of the public API.
19
19
  */
20
20
  Object.defineProperty(exports, "__esModule", { value: true });
21
- exports.getLanguageName = exports.getLanguageMetadata = exports.isSupportedLocale = exports.normalizeLocale = exports.getBaseLanguage = exports.FALLBACK_LOCALE = exports.SUPPORTED_LANGUAGES = exports.TopicSource = 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.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.ServiceCredentialMismatchError = exports.oxyClient = exports.OXY_CLOUD_URL = exports.OxyAuthenticationTimeoutError = exports.OxyAuthenticationError = exports.AssetUrlResolutionError = exports.OxyServices = void 0;
22
- exports.handleHttpError = exports.createApiError = exports.ErrorCodes = exports.safeJsonParse = exports.buildPaginationParams = exports.buildUrl = exports.buildSearchParams = exports.translate = 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 = void 0;
23
- exports.generatePkcePair = exports.generateOAuthState = exports.computeCodeChallenge = exports.buildOAuthAuthorizeUrl = exports.runColdBoot = exports.isOxyRpOrigin = exports.CENTRAL_IDP_APEX = exports.registrableApex = 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 = void 0;
24
- exports.packageInfo = exports.runSessionColdBoot = exports.TOKEN_REFRESH_LEAD_MS = exports.startTokenRefreshScheduler = exports.installAuthRefreshHandler = exports.createAuthRefreshHandler = exports.refreshDeviceSecretArm = exports.refreshPersistedSession = 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.redeemHubTicketOnHub = exports.syncHubAfterSignIn = exports.parseHubSyncReturnUrl = exports.normalizeOfficialReturnOrigin = exports.isAllowedDeviceJoinOrigin = exports.isOfficialWebOrigin = exports.isLoopbackOrigin = exports.isIdpHubOrigin = exports.buildHubSyncUrl = exports.buildIdpHubOrigin = exports.clearOAuthHandshake = exports.readOAuthHandshake = exports.persistOAuthHandshake = exports.normalizeOAuthRedirectUri = exports.OXY_CROSS_ORIGIN_RESTORE_ATTEMPTED_KEY = exports.OXY_SILENT_OAUTH_ATTEMPTED_KEY = exports.OXY_OAUTH_CODE_VERIFIER_STORAGE_KEY = exports.OXY_OAUTH_STATE_STORAGE_KEY = exports.OXY_AUTHORIZE_URL = exports.DEFAULT_OAUTH_SCOPE = void 0;
21
+ exports.normalizeLocale = exports.getBaseLanguage = exports.FALLBACK_LOCALE = exports.SUPPORTED_LANGUAGES = exports.TopicSource = 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.ServiceCredentialMismatchError = exports.oxyClient = exports.OXY_CLOUD_URL = exports.OxyAuthenticationTimeoutError = exports.OxyAuthenticationError = exports.AssetUrlResolutionError = exports.OxyServices = void 0;
22
+ exports.safeJsonParse = exports.buildPaginationParams = exports.buildUrl = exports.buildSearchParams = exports.translate = 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 = void 0;
23
+ exports.buildOAuthAuthorizeUrl = exports.runColdBoot = exports.isOxyRpOrigin = exports.CENTRAL_IDP_APEX = exports.registrableApex = 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 = void 0;
24
+ exports.packageInfo = exports.runSessionColdBoot = exports.TOKEN_REFRESH_LEAD_MS = exports.startTokenRefreshScheduler = exports.installAuthRefreshHandler = exports.createAuthRefreshHandler = exports.refreshDeviceSecretArm = exports.refreshPersistedSession = 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.redeemHubTicketOnHub = exports.syncHubAfterSignIn = exports.parseHubSyncReturnUrl = exports.normalizeOfficialReturnOrigin = exports.isAllowedDeviceJoinOrigin = exports.isOfficialWebOrigin = exports.isLoopbackOrigin = exports.isIdpHubOrigin = exports.buildHubSyncUrl = exports.buildIdpHubOrigin = exports.clearOAuthHandshake = exports.readOAuthHandshake = exports.persistOAuthHandshake = exports.normalizeOAuthRedirectUri = exports.OXY_CROSS_ORIGIN_RESTORE_ATTEMPTED_KEY = exports.OXY_SILENT_OAUTH_ATTEMPTED_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 = void 0;
25
25
  // Ensure crypto polyfills are loaded before anything else
26
26
  require("./crypto/polyfill");
27
27
  // ---------------------------------------------------------------------------
@@ -100,6 +100,10 @@ var keyManager_1 = require("./crypto/keyManager");
100
100
  Object.defineProperty(exports, "KeyManager", { enumerable: true, get: function () { return keyManager_1.KeyManager; } });
101
101
  Object.defineProperty(exports, "IdentityAlreadyExistsError", { enumerable: true, get: function () { return keyManager_1.IdentityAlreadyExistsError; } });
102
102
  Object.defineProperty(exports, "IdentityPersistError", { enumerable: true, get: function () { return keyManager_1.IdentityPersistError; } });
103
+ Object.defineProperty(exports, "IdentityUnavailableError", { enumerable: true, get: function () { return keyManager_1.IdentityUnavailableError; } });
104
+ var identityMarker_1 = require("./crypto/identityMarker");
105
+ Object.defineProperty(exports, "readIdentityMarker", { enumerable: true, get: function () { return identityMarker_1.readIdentityMarker; } });
106
+ Object.defineProperty(exports, "updateIdentityMarker", { enumerable: true, get: function () { return identityMarker_1.updateIdentityMarker; } });
103
107
  var signatureService_1 = require("./crypto/signatureService");
104
108
  Object.defineProperty(exports, "SignatureService", { enumerable: true, get: function () { return signatureService_1.SignatureService; } });
105
109
  var recoveryPhrase_1 = require("./crypto/recoveryPhrase");
@@ -298,12 +298,16 @@ function OxyServicesAuthMixin(Base) {
298
298
  * The client must sign this challenge with their private key
299
299
  *
300
300
  * @param publicKey - The user's public key
301
+ * @param requestOptions - Optional per-call transport overrides (`retry`,
302
+ * `timeout`). Interactive callers omit it (defaults keep retries); the
303
+ * cold-boot `shared-key-signin` step passes `{ retry: false }` so a slow
304
+ * network cannot multiply boot latency via the inner retry loop.
301
305
  */
302
- async requestChallenge(publicKey) {
306
+ async requestChallenge(publicKey, requestOptions) {
303
307
  try {
304
308
  return await this.makeRequest('POST', '/auth/challenge', {
305
309
  publicKey,
306
- }, { cache: false });
310
+ }, { cache: false, ...requestOptions });
307
311
  }
308
312
  catch (error) {
309
313
  throw this.handleError(error);
@@ -318,8 +322,12 @@ function OxyServicesAuthMixin(Base) {
318
322
  * @param timestamp - Timestamp when the signature was created
319
323
  * @param deviceName - Optional device name
320
324
  * @param deviceFingerprint - Optional device fingerprint
325
+ * @param requestOptions - Optional per-call transport overrides (`retry`,
326
+ * `timeout`). Interactive callers omit it (defaults keep retries); the
327
+ * cold-boot `shared-key-signin` step passes `{ retry: false }` so a slow
328
+ * network cannot multiply boot latency via the inner retry loop.
321
329
  */
322
- async verifyChallenge(publicKey, challenge, signature, timestamp, deviceName, deviceFingerprint) {
330
+ async verifyChallenge(publicKey, challenge, signature, timestamp, deviceName, deviceFingerprint, requestOptions) {
323
331
  try {
324
332
  const res = await this.makeRequest('POST', '/auth/verify', {
325
333
  publicKey,
@@ -328,7 +336,7 @@ function OxyServicesAuthMixin(Base) {
328
336
  timestamp,
329
337
  deviceName,
330
338
  deviceFingerprint,
331
- }, { cache: false });
339
+ }, { cache: false, ...requestOptions });
332
340
  // Plant the freshly-minted tokens, mirroring `claimSessionByToken`.
333
341
  // `/auth/verify` returns the first access token (and refresh token) in
334
342
  // its body, so installing it here means callers get an authenticated
@@ -466,6 +474,13 @@ function OxyServicesAuthMixin(Base) {
466
474
  *
467
475
  * The cold-boot wiring that CALLS this lives in `OxyContext`
468
476
  * (`@oxyhq/services`); this method just performs the exchange.
477
+ *
478
+ * @param opts.requestOptions - Optional per-call transport overrides
479
+ * (`retry`, `timeout`) forwarded to BOTH the `requestChallenge` and
480
+ * `verifyChallenge` round-trips. Interactive flows omit it (defaults keep
481
+ * retries); the cold-boot `shared-key-signin` step passes `{ retry: false }`
482
+ * so this network step cannot multiply boot latency via the inner retry
483
+ * loop. The token-refresh scheduler / 401 lane still retry later.
469
484
  */
470
485
  async signInWithSharedIdentity(opts = {}) {
471
486
  try {
@@ -479,10 +494,10 @@ function OxyServicesAuthMixin(Base) {
479
494
  if (!sharedPublicKey) {
480
495
  return null;
481
496
  }
482
- const { challenge } = await this.requestChallenge(sharedPublicKey);
497
+ const { challenge } = await this.requestChallenge(sharedPublicKey, opts.requestOptions);
483
498
  const signed = await signatureService_1.SignatureService.signChallengeWithSharedKey(challenge);
484
499
  // `signed.challenge` carries the SIGNATURE (mirrors `signChallenge`).
485
- return await this.verifyChallenge(signed.publicKey, challenge, signed.challenge, signed.timestamp, opts.deviceName, opts.deviceFingerprint);
500
+ return await this.verifyChallenge(signed.publicKey, challenge, signed.challenge, signed.timestamp, opts.deviceName, opts.deviceFingerprint, opts.requestOptions);
486
501
  }
487
502
  catch (error) {
488
503
  throw this.handleError(error);
@@ -32,11 +32,19 @@ function OxyServicesDeviceBootMixin(Base) {
32
32
  * `no_active_session`) to decide whether to drop the secret and fall back or
33
33
  * resolve signed-out.
34
34
  *
35
+ * `retry: false`: the mint is a single logical attempt. The proactive
36
+ * token-refresh scheduler and the reactive 401 lane already own backoff and
37
+ * re-arm, so `HttpService`'s inner retry loop here would only multiply the
38
+ * mint's latency on a slow/black-hole network (3 retries × 5s timeout ≈ 20s
39
+ * per lane) with no correctness benefit — it is the dominant term in the cold
40
+ * boot's worst-case time-to-route. A transient failure surfaces once and the
41
+ * scheduler/401 path retries it later.
42
+ *
35
43
  * @throws if the response does not match {@link deviceTokenMintResponseSchema}.
36
44
  */
37
45
  async mintFromDeviceSecret(deviceId, deviceSecret) {
38
46
  try {
39
- const res = await this.makeRequest('POST', '/session/device/token', { deviceId, deviceSecret }, { cache: false, skipAuth: true });
47
+ const res = await this.makeRequest('POST', '/session/device/token', { deviceId, deviceSecret }, { cache: false, skipAuth: true, retry: false });
40
48
  const parsed = (0, contracts_1.safeParseContract)(contracts_1.deviceTokenMintResponseSchema, res);
41
49
  if (!parsed) {
42
50
  throw new Error('session/device/token returned an unexpected response shape');
@@ -11,6 +11,7 @@ const jwt_decode_1 = require("jwt-decode");
11
11
  const protocol_1 = require("@oxyhq/protocol");
12
12
  const apiUtils_1 = require("../utils/apiUtils");
13
13
  const logger_1 = require("../logger");
14
+ const oxyServiceEnvironment_1 = require("../utils/oxyServiceEnvironment");
14
15
  /**
15
16
  * Expected JWT audience for tokens issued by the Oxy auth service.
16
17
  */
@@ -42,6 +43,10 @@ class ServiceTokenClaimError extends Error {
42
43
  this.name = 'ServiceTokenClaimError';
43
44
  }
44
45
  }
46
+ function isOxyServiceEnvironment(value) {
47
+ return (typeof value === 'string' &&
48
+ oxyServiceEnvironment_1.OXY_SERVICE_ENVIRONMENTS.includes(value));
49
+ }
45
50
  function OxyServicesUtilityMixin(Base) {
46
51
  return class extends Base {
47
52
  // TypeScript's mixin pattern requires `(...args: any[])` here — the
@@ -338,7 +343,11 @@ function OxyServicesUtilityMixin(Base) {
338
343
  // Validate required service token fields
339
344
  const appId = decoded.appId;
340
345
  const credentialId = decoded.credentialId;
341
- if (!appId || typeof credentialId !== 'string' || credentialId.length === 0) {
346
+ const environment = decoded.environment;
347
+ if (!appId ||
348
+ typeof credentialId !== 'string' ||
349
+ credentialId.length === 0 ||
350
+ !isOxyServiceEnvironment(environment)) {
342
351
  if (optional) {
343
352
  req.userId = null;
344
353
  req.user = null;
@@ -391,6 +400,7 @@ function OxyServicesUtilityMixin(Base) {
391
400
  appName: decoded.appName || 'unknown',
392
401
  credentialId,
393
402
  scopes: Array.isArray(decoded.scopes) ? decoded.scopes : [],
403
+ environment,
394
404
  };
395
405
  if (debug) {
396
406
  logger_1.logger.debug(`[oxy.auth] Service token OK app=${decoded.appName} delegateUser=${oxyUserId || '(none)'}`, {
@@ -1,11 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OXY_SERVICE_ENVIRONMENTS = void 0;
3
4
  exports.getOxyUserId = getOxyUserId;
4
5
  exports.isOxyAuthenticated = isOxyAuthenticated;
5
6
  exports.getRequiredOxyUserId = getRequiredOxyUserId;
6
7
  exports.requireOxyAuth = requireOxyAuth;
7
8
  exports.createOptionalOxyAuth = createOptionalOxyAuth;
8
9
  exports.createOxyAuthMiddleware = createOxyAuthMiddleware;
10
+ const oxyServiceEnvironment_1 = require("../utils/oxyServiceEnvironment");
11
+ Object.defineProperty(exports, "OXY_SERVICE_ENVIRONMENTS", { enumerable: true, get: function () { return oxyServiceEnvironment_1.OXY_SERVICE_ENVIRONMENTS; } });
9
12
  function normalizeId(value) {
10
13
  const normalized = value?.trim();
11
14
  return normalized && normalized.length > 0 ? normalized : null;
@@ -16,7 +16,7 @@
16
16
  * ```
17
17
  */
18
18
  Object.defineProperty(exports, "__esModule", { value: true });
19
- exports.parseHubSyncReturnUrl = exports.normalizeOfficialReturnOrigin = exports.isOfficialWebOrigin = exports.isIdpHubOrigin = exports.buildHubSyncUrl = exports.buildIdpHubOrigin = 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.requireOxyAuth = exports.isOxyAuthenticated = exports.getRequiredOxyUserId = exports.getOxyUserId = exports.createOxyAuthMiddleware = exports.createOptionalOxyAuth = void 0;
19
+ exports.parseHubSyncReturnUrl = exports.normalizeOfficialReturnOrigin = exports.isOfficialWebOrigin = exports.isIdpHubOrigin = exports.buildHubSyncUrl = exports.buildIdpHubOrigin = 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; } });
@@ -24,6 +24,7 @@ Object.defineProperty(exports, "getOxyUserId", { enumerable: true, get: function
24
24
  Object.defineProperty(exports, "getRequiredOxyUserId", { enumerable: true, get: function () { return auth_1.getRequiredOxyUserId; } });
25
25
  Object.defineProperty(exports, "isOxyAuthenticated", { enumerable: true, get: function () { return auth_1.isOxyAuthenticated; } });
26
26
  Object.defineProperty(exports, "requireOxyAuth", { enumerable: true, get: function () { return auth_1.requireOxyAuth; } });
27
+ Object.defineProperty(exports, "OXY_SERVICE_ENVIRONMENTS", { enumerable: true, get: function () { return auth_1.OXY_SERVICE_ENVIRONMENTS; } });
27
28
  var rateLimit_1 = require("./rateLimit");
28
29
  Object.defineProperty(exports, "createOxyRateLimit", { enumerable: true, get: function () { return rateLimit_1.createOxyRateLimit; } });
29
30
  // SSRF-safe upstream fetch + URL validation (Node-only).
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OXY_SERVICE_ENVIRONMENTS = void 0;
4
+ /**
5
+ * Environment segregation for Oxy service-token JWTs (test/live isolation).
6
+ * Mirrors `ApplicationCredentialEnvironment` on the API's `ApplicationCredential`
7
+ * model (`packages/api/src/models/ApplicationCredential.ts`) as an INDEPENDENT
8
+ * literal union — `@oxyhq/core` has zero dependency on `@oxyhq/api`, so this is
9
+ * kept in sync by hand, not by import.
10
+ *
11
+ * Defined here (not in `server/auth.ts` or `mixins/OxyServices.utility.ts`
12
+ * directly) because BOTH of those files need it and neither may import from
13
+ * the other: `server/` types import `express` (Node-only, a peer dependency
14
+ * `mixins/` deliberately avoids so it stays safe to bundle into RN/browser
15
+ * consumers — see the "Local request/response/socket typing" comment in
16
+ * `OxyServices.utility.ts`). This file has zero imports, so both sides can
17
+ * depend on it without crossing that boundary.
18
+ */
19
+ exports.OXY_SERVICE_ENVIRONMENTS = ['development', 'staging', 'production'];