@oxyhq/core 12.10.6 → 12.11.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 (126) hide show
  1. package/dist/cjs/.tsbuildinfo +1 -1
  2. package/dist/cjs/boot/sessionColdBoot.js +142 -37
  3. package/dist/cjs/i18n/locales/ar-SA.json +13 -1
  4. package/dist/cjs/i18n/locales/ca-ES.json +13 -1
  5. package/dist/cjs/i18n/locales/de-DE.json +13 -1
  6. package/dist/cjs/i18n/locales/en-US.json +13 -4
  7. package/dist/cjs/i18n/locales/es-ES.json +13 -4
  8. package/dist/cjs/i18n/locales/fr-FR.json +13 -1
  9. package/dist/cjs/i18n/locales/it-IT.json +13 -1
  10. package/dist/cjs/i18n/locales/ja-JP.json +13 -1
  11. package/dist/cjs/i18n/locales/ko-KR.json +13 -1
  12. package/dist/cjs/i18n/locales/locales/ar-SA.json +13 -1
  13. package/dist/cjs/i18n/locales/locales/ca-ES.json +13 -1
  14. package/dist/cjs/i18n/locales/locales/de-DE.json +13 -1
  15. package/dist/cjs/i18n/locales/locales/en-US.json +13 -4
  16. package/dist/cjs/i18n/locales/locales/es-ES.json +13 -4
  17. package/dist/cjs/i18n/locales/locales/fr-FR.json +13 -1
  18. package/dist/cjs/i18n/locales/locales/it-IT.json +13 -1
  19. package/dist/cjs/i18n/locales/locales/ja-JP.json +13 -1
  20. package/dist/cjs/i18n/locales/locales/ko-KR.json +13 -1
  21. package/dist/cjs/i18n/locales/locales/pt-PT.json +13 -1
  22. package/dist/cjs/i18n/locales/locales/zh-CN.json +13 -1
  23. package/dist/cjs/i18n/locales/pt-PT.json +13 -1
  24. package/dist/cjs/i18n/locales/zh-CN.json +13 -1
  25. package/dist/cjs/index.js +31 -5
  26. package/dist/cjs/mixins/OxyServices.auth.js +257 -17
  27. package/dist/cjs/mixins/OxyServices.deviceBoot.js +54 -3
  28. package/dist/cjs/mixins/OxyServices.notifications.js +63 -0
  29. package/dist/cjs/mixins/index.js +5 -0
  30. package/dist/cjs/session/SessionClient.js +40 -4
  31. package/dist/cjs/session/accountDialogController.js +288 -41
  32. package/dist/cjs/session/identityPin.js +256 -0
  33. package/dist/cjs/session/identitySession.js +135 -0
  34. package/dist/cjs/session/projectSessionState.js +54 -15
  35. package/dist/cjs/session/refresh.js +73 -8
  36. package/dist/cjs/utils/commonsDelivery.js +59 -0
  37. package/dist/cjs/utils/oauthPkce.js +33 -3
  38. package/dist/esm/.tsbuildinfo +1 -1
  39. package/dist/esm/boot/sessionColdBoot.js +142 -37
  40. package/dist/esm/i18n/locales/ar-SA.json +13 -1
  41. package/dist/esm/i18n/locales/ca-ES.json +13 -1
  42. package/dist/esm/i18n/locales/de-DE.json +13 -1
  43. package/dist/esm/i18n/locales/en-US.json +13 -4
  44. package/dist/esm/i18n/locales/es-ES.json +13 -4
  45. package/dist/esm/i18n/locales/fr-FR.json +13 -1
  46. package/dist/esm/i18n/locales/it-IT.json +13 -1
  47. package/dist/esm/i18n/locales/ja-JP.json +13 -1
  48. package/dist/esm/i18n/locales/ko-KR.json +13 -1
  49. package/dist/esm/i18n/locales/locales/ar-SA.json +13 -1
  50. package/dist/esm/i18n/locales/locales/ca-ES.json +13 -1
  51. package/dist/esm/i18n/locales/locales/de-DE.json +13 -1
  52. package/dist/esm/i18n/locales/locales/en-US.json +13 -4
  53. package/dist/esm/i18n/locales/locales/es-ES.json +13 -4
  54. package/dist/esm/i18n/locales/locales/fr-FR.json +13 -1
  55. package/dist/esm/i18n/locales/locales/it-IT.json +13 -1
  56. package/dist/esm/i18n/locales/locales/ja-JP.json +13 -1
  57. package/dist/esm/i18n/locales/locales/ko-KR.json +13 -1
  58. package/dist/esm/i18n/locales/locales/pt-PT.json +13 -1
  59. package/dist/esm/i18n/locales/locales/zh-CN.json +13 -1
  60. package/dist/esm/i18n/locales/pt-PT.json +13 -1
  61. package/dist/esm/i18n/locales/zh-CN.json +13 -1
  62. package/dist/esm/index.js +14 -1
  63. package/dist/esm/mixins/OxyServices.auth.js +257 -17
  64. package/dist/esm/mixins/OxyServices.deviceBoot.js +52 -3
  65. package/dist/esm/mixins/OxyServices.notifications.js +60 -0
  66. package/dist/esm/mixins/index.js +5 -0
  67. package/dist/esm/session/SessionClient.js +40 -4
  68. package/dist/esm/session/accountDialogController.js +288 -41
  69. package/dist/esm/session/identityPin.js +249 -0
  70. package/dist/esm/session/identitySession.js +131 -0
  71. package/dist/esm/session/projectSessionState.js +54 -15
  72. package/dist/esm/session/refresh.js +73 -8
  73. package/dist/esm/utils/commonsDelivery.js +54 -0
  74. package/dist/esm/utils/oauthPkce.js +31 -2
  75. package/dist/types/.tsbuildinfo +1 -1
  76. package/dist/types/boot/sessionColdBoot.d.ts +38 -1
  77. package/dist/types/index.d.ts +12 -4
  78. package/dist/types/mixins/OxyServices.auth.d.ts +262 -11
  79. package/dist/types/mixins/OxyServices.deviceBoot.d.ts +31 -1
  80. package/dist/types/mixins/OxyServices.notifications.d.ts +143 -0
  81. package/dist/types/mixins/index.d.ts +2 -1
  82. package/dist/types/session/SessionClient.d.ts +28 -0
  83. package/dist/types/session/accountDialogController.d.ts +187 -9
  84. package/dist/types/session/identityPin.d.ts +87 -0
  85. package/dist/types/session/identitySession.d.ts +105 -0
  86. package/dist/types/session/projectSessionState.d.ts +21 -19
  87. package/dist/types/session/refresh.d.ts +32 -1
  88. package/dist/types/utils/commonsDelivery.d.ts +105 -0
  89. package/dist/types/utils/oauthPkce.d.ts +21 -1
  90. package/package.json +2 -2
  91. package/src/boot/__tests__/sessionColdBoot.identity.test.ts +393 -0
  92. package/src/boot/sessionColdBoot.ts +183 -37
  93. package/src/i18n/locales/ar-SA.json +13 -1
  94. package/src/i18n/locales/ca-ES.json +13 -1
  95. package/src/i18n/locales/de-DE.json +13 -1
  96. package/src/i18n/locales/en-US.json +13 -4
  97. package/src/i18n/locales/es-ES.json +13 -4
  98. package/src/i18n/locales/fr-FR.json +13 -1
  99. package/src/i18n/locales/it-IT.json +13 -1
  100. package/src/i18n/locales/ja-JP.json +13 -1
  101. package/src/i18n/locales/ko-KR.json +13 -1
  102. package/src/i18n/locales/pt-PT.json +13 -1
  103. package/src/i18n/locales/zh-CN.json +13 -1
  104. package/src/index.ts +49 -0
  105. package/src/mixins/OxyServices.auth.ts +464 -24
  106. package/src/mixins/OxyServices.deviceBoot.ts +54 -2
  107. package/src/mixins/OxyServices.notifications.ts +142 -0
  108. package/src/mixins/__tests__/commonsSignIn.test.ts +754 -2
  109. package/src/mixins/__tests__/deviceTokenMintPinned.test.ts +113 -0
  110. package/src/mixins/__tests__/preSessionSkipAuth.test.ts +18 -0
  111. package/src/mixins/index.ts +6 -0
  112. package/src/session/SessionClient.ts +57 -4
  113. package/src/session/__tests__/SessionClient.identityPin.test.ts +175 -0
  114. package/src/session/__tests__/accountDialogController.test.ts +812 -1
  115. package/src/session/__tests__/identityPin.test.ts +131 -0
  116. package/src/session/__tests__/identitySession.test.ts +194 -0
  117. package/src/session/__tests__/projectSessionState.test.ts +88 -0
  118. package/src/session/__tests__/refresh.identityPin.test.ts +306 -0
  119. package/src/session/accountDialogController.ts +435 -44
  120. package/src/session/identityPin.ts +310 -0
  121. package/src/session/identitySession.ts +206 -0
  122. package/src/session/projectSessionState.ts +61 -13
  123. package/src/session/refresh.ts +103 -9
  124. package/src/utils/__tests__/oauthPkce.test.ts +51 -0
  125. package/src/utils/commonsDelivery.ts +132 -0
  126. package/src/utils/oauthPkce.ts +56 -3
@@ -124,7 +124,19 @@
124
124
  "commonsNotInstalled": "Commonsがありませんか?アプリを入手してOxy IDでサインインしましょう。",
125
125
  "showQrAnyway": "別のデバイスにCommonsがあります",
126
126
  "switchWhileSignedInAs": "アカウントを切り替え({{name}} としてログイン中)",
127
- "manageOnDevice": "このデバイスのアカウントを管理"
127
+ "manageOnDevice": "このデバイスのアカウントを管理",
128
+ "continueWithOxy": "Oxyで続ける",
129
+ "havingTrouble": "うまくいきませんか?",
130
+ "progress": {
131
+ "preparing": "リクエストを準備しています",
132
+ "awaitingApproval": "承認を待っています",
133
+ "scanWithCommons": "スマートフォンのCommonsでスキャンしてください",
134
+ "continueInCommons": "Commonsで続けてください",
135
+ "checkCommons": "スマートフォンのCommonsを確認してください",
136
+ "openedInCommons": "Commonsで開きました",
137
+ "confirming": "本人確認をしています",
138
+ "confirmed": "本人確認が完了しました"
139
+ }
128
140
  },
129
141
  "common": {
130
142
  "actions": {
@@ -124,7 +124,19 @@
124
124
  "commonsNotInstalled": "Commons가 없으신가요? 앱을 다운로드하여 Oxy ID로 로그인하세요.",
125
125
  "showQrAnyway": "다른 기기에 Commons가 있습니다",
126
126
  "switchWhileSignedInAs": "계정 전환, {{name}}(으)로 로그인됨",
127
- "manageOnDevice": "이 기기의 계정 관리"
127
+ "manageOnDevice": "이 기기의 계정 관리",
128
+ "continueWithOxy": "Oxy로 계속하기",
129
+ "havingTrouble": "문제가 있으신가요?",
130
+ "progress": {
131
+ "preparing": "요청 준비 중",
132
+ "awaitingApproval": "승인 대기 중",
133
+ "scanWithCommons": "휴대폰의 Commons로 스캔하세요",
134
+ "continueInCommons": "Commons에서 계속하세요",
135
+ "checkCommons": "휴대폰에서 Commons를 확인하세요",
136
+ "openedInCommons": "Commons에서 열림",
137
+ "confirming": "신원 확인 중",
138
+ "confirmed": "신원 확인 완료"
139
+ }
128
140
  },
129
141
  "common": {
130
142
  "actions": {
@@ -124,7 +124,19 @@
124
124
  "commonsNotInstalled": "Não tem o Commons? Obtenha a app para iniciar sessão com o seu Oxy ID.",
125
125
  "showQrAnyway": "Tenho o Commons noutro dispositivo",
126
126
  "switchWhileSignedInAs": "Mudar de conta, com sessão iniciada como {{name}}",
127
- "manageOnDevice": "Gerir contas neste dispositivo"
127
+ "manageOnDevice": "Gerir contas neste dispositivo",
128
+ "continueWithOxy": "Continuar com Oxy",
129
+ "havingTrouble": "Algum problema?",
130
+ "progress": {
131
+ "preparing": "A preparar o pedido",
132
+ "awaitingApproval": "A aguardar aprovação",
133
+ "scanWithCommons": "Digitalize com o Commons no seu telemóvel",
134
+ "continueInCommons": "Continue no Commons",
135
+ "checkCommons": "Verifique o Commons no seu telemóvel",
136
+ "openedInCommons": "Aberto no Commons",
137
+ "confirming": "A confirmar a identidade",
138
+ "confirmed": "Identidade confirmada"
139
+ }
128
140
  },
129
141
  "common": {
130
142
  "actions": {
@@ -124,7 +124,19 @@
124
124
  "commonsNotInstalled": "没有 Commons?获取应用以使用您的 Oxy ID 登录。",
125
125
  "showQrAnyway": "我在另一台设备上有 Commons",
126
126
  "switchWhileSignedInAs": "切换账号,当前登录为 {{name}}",
127
- "manageOnDevice": "管理此设备上的账号"
127
+ "manageOnDevice": "管理此设备上的账号",
128
+ "continueWithOxy": "使用 Oxy 继续",
129
+ "havingTrouble": "遇到问题?",
130
+ "progress": {
131
+ "preparing": "正在准备请求",
132
+ "awaitingApproval": "正在等待批准",
133
+ "scanWithCommons": "请用手机上的 Commons 扫描",
134
+ "continueInCommons": "请在 Commons 中继续",
135
+ "checkCommons": "请查看手机上的 Commons",
136
+ "openedInCommons": "已在 Commons 中打开",
137
+ "confirming": "正在确认身份",
138
+ "confirmed": "身份已确认"
139
+ }
128
140
  },
129
141
  "common": {
130
142
  "actions": {
@@ -124,7 +124,19 @@
124
124
  "commonsNotInstalled": "Não tem o Commons? Obtenha a app para iniciar sessão com o seu Oxy ID.",
125
125
  "showQrAnyway": "Tenho o Commons noutro dispositivo",
126
126
  "switchWhileSignedInAs": "Mudar de conta, com sessão iniciada como {{name}}",
127
- "manageOnDevice": "Gerir contas neste dispositivo"
127
+ "manageOnDevice": "Gerir contas neste dispositivo",
128
+ "continueWithOxy": "Continuar com Oxy",
129
+ "havingTrouble": "Algum problema?",
130
+ "progress": {
131
+ "preparing": "A preparar o pedido",
132
+ "awaitingApproval": "A aguardar aprovação",
133
+ "scanWithCommons": "Digitalize com o Commons no seu telemóvel",
134
+ "continueInCommons": "Continue no Commons",
135
+ "checkCommons": "Verifique o Commons no seu telemóvel",
136
+ "openedInCommons": "Aberto no Commons",
137
+ "confirming": "A confirmar a identidade",
138
+ "confirmed": "Identidade confirmada"
139
+ }
128
140
  },
129
141
  "common": {
130
142
  "actions": {
@@ -124,7 +124,19 @@
124
124
  "commonsNotInstalled": "没有 Commons?获取应用以使用您的 Oxy ID 登录。",
125
125
  "showQrAnyway": "我在另一台设备上有 Commons",
126
126
  "switchWhileSignedInAs": "切换账号,当前登录为 {{name}}",
127
- "manageOnDevice": "管理此设备上的账号"
127
+ "manageOnDevice": "管理此设备上的账号",
128
+ "continueWithOxy": "使用 Oxy 继续",
129
+ "havingTrouble": "遇到问题?",
130
+ "progress": {
131
+ "preparing": "正在准备请求",
132
+ "awaitingApproval": "正在等待批准",
133
+ "scanWithCommons": "请用手机上的 Commons 扫描",
134
+ "continueInCommons": "请在 Commons 中继续",
135
+ "checkCommons": "请查看手机上的 Commons",
136
+ "openedInCommons": "已在 Commons 中打开",
137
+ "confirming": "正在确认身份",
138
+ "confirmed": "身份已确认"
139
+ }
128
140
  },
129
141
  "common": {
130
142
  "actions": {
package/dist/cjs/index.js CHANGED
@@ -18,11 +18,11 @@
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.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.parseCommonsApprovalExpiresAt = exports.getCommonsApprovalBlockingReason = exports.ServiceCredentialMismatchError = exports.oxyClient = exports.OXY_CLOUD_URL = exports.OxyAuthenticationTimeoutError = exports.OxyAuthenticationError = exports.AssetUrlResolutionError = exports.OxyServices = void 0;
22
- 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 = exports.normalizeLocale = exports.getBaseLanguage = void 0;
23
- 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 = exports.safeJsonParse = exports.buildPaginationParams = void 0;
24
- 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.consumeOAuthReturnPath = exports.persistOAuthReturnPath = exports.clearOAuthHandshake = exports.readOAuthHandshake = exports.persistOAuthHandshake = exports.normalizeOAuthRedirectUri = exports.OXY_OAUTH_RETURN_PATH_STORAGE_KEY = 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 = exports.buildOAuthAuthorizeUrl = exports.runColdBoot = void 0;
25
- exports.packageInfo = void 0;
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
+ 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
+ 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.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.redeemHubTicketOnHub = exports.syncHubAfterSignIn = exports.parseHubSyncReturnUrl = exports.normalizeOfficialReturnOrigin = exports.isAllowedDeviceJoinOrigin = exports.isOfficialWebOrigin = exports.isLoopbackOrigin = exports.isIdpHubOrigin = exports.buildHubSyncUrl = exports.buildIdpHubOrigin = exports.consumeOAuthReturnPath = exports.persistOAuthReturnPath = exports.clearOAuthHandshake = exports.readOAuthHandshake = exports.persistOAuthHandshake = exports.canonicalizeOAuthRedirectUri = exports.normalizeOAuthRedirectUri = exports.OXY_OAUTH_RETURN_PATH_STORAGE_KEY = exports.OXY_CROSS_ORIGIN_RESTORE_ATTEMPTED_KEY = exports.OXY_SILENT_OAUTH_ATTEMPTED_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 = exports.createAuthRefreshHandler = exports.refreshDeviceSecretArm = exports.refreshPersistedSession = exports.AccountNotOnDeviceError = exports.establishIdentitySession = exports.resolveIdentityPin = exports.IDENTITY_PIN_STORAGE_KEY = exports.identityPinMatches = exports.createMemoryIdentityPinStore = void 0;
26
26
  // Ensure crypto polyfills are loaded before anything else
27
27
  require("./crypto/polyfill");
28
28
  // ---------------------------------------------------------------------------
@@ -44,6 +44,12 @@ Object.defineProperty(exports, "ServiceCredentialMismatchError", { enumerable: t
44
44
  var commonsApproval_1 = require("./utils/commonsApproval");
45
45
  Object.defineProperty(exports, "getCommonsApprovalBlockingReason", { enumerable: true, get: function () { return commonsApproval_1.getCommonsApprovalBlockingReason; } });
46
46
  Object.defineProperty(exports, "parseCommonsApprovalExpiresAt", { enumerable: true, get: function () { return commonsApproval_1.parseCommonsApprovalExpiresAt; } });
47
+ // Automatic "Sign in with Oxy" delivery selection — ONE pure decision that maps
48
+ // the caller's facts onto exactly one primary route (open Commons / await push / QR).
49
+ var commonsDelivery_1 = require("./utils/commonsDelivery");
50
+ Object.defineProperty(exports, "selectCommonsDelivery", { enumerable: true, get: function () { return commonsDelivery_1.selectCommonsDelivery; } });
51
+ Object.defineProperty(exports, "pushTargetsFromDelivery", { enumerable: true, get: function () { return commonsDelivery_1.pushTargetsFromDelivery; } });
52
+ Object.defineProperty(exports, "commonsDeliveryPlatform", { enumerable: true, get: function () { return commonsDelivery_1.commonsDeliveryPlatform; } });
47
53
  var OxyServices_appData_1 = require("./mixins/OxyServices.appData");
48
54
  Object.defineProperty(exports, "OxyAppDataIdentifierError", { enumerable: true, get: function () { return OxyServices_appData_1.OxyAppDataIdentifierError; } });
49
55
  // ---------------------------------------------------------------------------
@@ -312,10 +318,12 @@ Object.defineProperty(exports, "DEFAULT_OAUTH_SCOPE", { enumerable: true, get: f
312
318
  Object.defineProperty(exports, "OXY_AUTHORIZE_URL", { enumerable: true, get: function () { return oauthPkce_1.OXY_AUTHORIZE_URL; } });
313
319
  Object.defineProperty(exports, "OXY_OAUTH_STATE_STORAGE_KEY", { enumerable: true, get: function () { return oauthPkce_1.OXY_OAUTH_STATE_STORAGE_KEY; } });
314
320
  Object.defineProperty(exports, "OXY_OAUTH_CODE_VERIFIER_STORAGE_KEY", { enumerable: true, get: function () { return oauthPkce_1.OXY_OAUTH_CODE_VERIFIER_STORAGE_KEY; } });
321
+ Object.defineProperty(exports, "OXY_OAUTH_REDIRECT_URI_STORAGE_KEY", { enumerable: true, get: function () { return oauthPkce_1.OXY_OAUTH_REDIRECT_URI_STORAGE_KEY; } });
315
322
  Object.defineProperty(exports, "OXY_SILENT_OAUTH_ATTEMPTED_KEY", { enumerable: true, get: function () { return oauthPkce_1.OXY_SILENT_OAUTH_ATTEMPTED_KEY; } });
316
323
  Object.defineProperty(exports, "OXY_CROSS_ORIGIN_RESTORE_ATTEMPTED_KEY", { enumerable: true, get: function () { return oauthPkce_1.OXY_CROSS_ORIGIN_RESTORE_ATTEMPTED_KEY; } });
317
324
  Object.defineProperty(exports, "OXY_OAUTH_RETURN_PATH_STORAGE_KEY", { enumerable: true, get: function () { return oauthPkce_1.OXY_OAUTH_RETURN_PATH_STORAGE_KEY; } });
318
325
  Object.defineProperty(exports, "normalizeOAuthRedirectUri", { enumerable: true, get: function () { return oauthPkce_1.normalizeOAuthRedirectUri; } });
326
+ Object.defineProperty(exports, "canonicalizeOAuthRedirectUri", { enumerable: true, get: function () { return oauthPkce_1.canonicalizeOAuthRedirectUri; } });
319
327
  Object.defineProperty(exports, "persistOAuthHandshake", { enumerable: true, get: function () { return oauthPkce_1.persistOAuthHandshake; } });
320
328
  Object.defineProperty(exports, "readOAuthHandshake", { enumerable: true, get: function () { return oauthPkce_1.readOAuthHandshake; } });
321
329
  Object.defineProperty(exports, "clearOAuthHandshake", { enumerable: true, get: function () { return oauthPkce_1.clearOAuthHandshake; } });
@@ -380,6 +388,24 @@ Object.defineProperty(exports, "createWebAuthStateStore", { enumerable: true, ge
380
388
  Object.defineProperty(exports, "createNativeAuthStateStore", { enumerable: true, get: function () { return authStateStore_1.createNativeAuthStateStore; } });
381
389
  Object.defineProperty(exports, "createMemoryAuthStateStore", { enumerable: true, get: function () { return authStateStore_1.createMemoryAuthStateStore; } });
382
390
  Object.defineProperty(exports, "AUTH_STATE_STORAGE_KEY", { enumerable: true, get: function () { return authStateStore_1.AUTH_STATE_STORAGE_KEY; } });
391
+ // Identity-bound sessions (the identity vault). The pin is the durable
392
+ // `{publicKey, accountId}` binding between this device's PRIMARY identity key
393
+ // and the account it authenticates as; it is what keeps such a client from
394
+ // following the device's mutable `activeAccountId`.
395
+ var identityPin_1 = require("./session/identityPin");
396
+ Object.defineProperty(exports, "createWebIdentityPinStore", { enumerable: true, get: function () { return identityPin_1.createWebIdentityPinStore; } });
397
+ Object.defineProperty(exports, "createNativeIdentityPinStore", { enumerable: true, get: function () { return identityPin_1.createNativeIdentityPinStore; } });
398
+ Object.defineProperty(exports, "createMemoryIdentityPinStore", { enumerable: true, get: function () { return identityPin_1.createMemoryIdentityPinStore; } });
399
+ Object.defineProperty(exports, "identityPinMatches", { enumerable: true, get: function () { return identityPin_1.identityPinMatches; } });
400
+ Object.defineProperty(exports, "IDENTITY_PIN_STORAGE_KEY", { enumerable: true, get: function () { return identityPin_1.IDENTITY_PIN_STORAGE_KEY; } });
401
+ var identitySession_1 = require("./session/identitySession");
402
+ Object.defineProperty(exports, "resolveIdentityPin", { enumerable: true, get: function () { return identitySession_1.resolveIdentityPin; } });
403
+ Object.defineProperty(exports, "establishIdentitySession", { enumerable: true, get: function () { return identitySession_1.establishIdentitySession; } });
404
+ // The typed `401 account_not_on_device` from a PINNED device-secret mint: the
405
+ // pinned account left this device's session set, so the caller re-establishes
406
+ // the identity session instead of dropping a healthy device credential.
407
+ var OxyServices_deviceBoot_1 = require("./mixins/OxyServices.deviceBoot");
408
+ Object.defineProperty(exports, "AccountNotOnDeviceError", { enumerable: true, get: function () { return OxyServices_deviceBoot_1.AccountNotOnDeviceError; } });
383
409
  var refresh_1 = require("./session/refresh");
384
410
  Object.defineProperty(exports, "refreshPersistedSession", { enumerable: true, get: function () { return refresh_1.refreshPersistedSession; } });
385
411
  Object.defineProperty(exports, "refreshDeviceSecretArm", { enumerable: true, get: function () { return refresh_1.refreshDeviceSecretArm; } });
@@ -18,6 +18,61 @@ const userIdentity_1 = require("../utils/userIdentity");
18
18
  * returned `expiresAt` is authoritative; this is only the client-proposed value.
19
19
  */
20
20
  const COMMONS_SIGN_IN_EXPIRY_MS = 5 * 60 * 1000;
21
+ /**
22
+ * @internal Narrow an untrusted `requesterLabel` from the approve-info response.
23
+ *
24
+ * Returns the trimmed label only when the server sent a real, non-empty string;
25
+ * everything else — absent (an API that predates the field), `null` (a native
26
+ * requester the server could not describe), or a non-string — degrades to
27
+ * `null`, so the approval UI drops the line instead of rendering a blank or a
28
+ * coerced value under the app it is about to authorize.
29
+ */
30
+ function parseCommonsRequesterLabel(value) {
31
+ if (typeof value !== 'string') {
32
+ return null;
33
+ }
34
+ const label = value.trim();
35
+ return label.length > 0 ? label : null;
36
+ }
37
+ /**
38
+ * @internal Narrow an untrusted `subjectAccount` from the approve-info response.
39
+ *
40
+ * Returns `null` unless the value is an object carrying a non-empty string `id`
41
+ * AND `username` — the two fields the approval UI needs to name the delegated
42
+ * account. A half-populated object is rejected whole rather than rendered with
43
+ * blanks, and `displayName` is only carried through when it is a string.
44
+ */
45
+ function parseCommonsSubjectAccount(value) {
46
+ if (value === null || typeof value !== 'object') {
47
+ return null;
48
+ }
49
+ const raw = value;
50
+ const { id, username, displayName } = raw;
51
+ if (typeof id !== 'string' || !id || typeof username !== 'string' || !username) {
52
+ return null;
53
+ }
54
+ return {
55
+ id,
56
+ username,
57
+ ...(typeof displayName === 'string' ? { displayName } : {}),
58
+ };
59
+ }
60
+ /**
61
+ * @internal Narrow an untrusted delivery-progress timestamp from the status
62
+ * response.
63
+ *
64
+ * Returns the ISO-8601 string unchanged when it is a real, parseable instant,
65
+ * and `null` for everything else — absent (an older API that has no delivery
66
+ * progress at all), empty, non-string, or unparseable. Progress is advisory, so
67
+ * degrading to "no progress yet" is always safe; surfacing a garbage timestamp
68
+ * to the waiting UI is not.
69
+ */
70
+ function parseCommonsProgressTimestamp(value) {
71
+ if (typeof value !== 'string' || !value) {
72
+ return null;
73
+ }
74
+ return Number.isFinite(Date.parse(value)) ? value : null;
75
+ }
21
76
  /**
22
77
  * Sentinel error raised when getServiceToken() is called with a known apiKey
23
78
  * but a non-matching secret. Indicates either credential drift in the caller
@@ -466,11 +521,16 @@ function OxyServicesAuthMixin(Base) {
466
521
  // A. Same-device shared-keychain SSO (`signInWithSharedIdentity`): a
467
522
  // sibling native app silently mints its own session from the shared
468
523
  // identity key. No user interaction.
469
- // B. QR / app-to-app handoff: a relying party (`startCommonsSignIn` +
470
- // `pollCommonsSignIn` + the existing `claimSessionByToken`) and the
471
- // approver / Commons (`getCommonsApprovalInfo` + `approveCommonsSignIn`
472
- // / `denyCommonsSignIn`). The approver signs with its PRIMARY local
473
- // key; the RP never sees the private key.
524
+ // B. QR / app-to-app / popup / push handoff: a relying party
525
+ // (`startCommonsSignIn` + `pollCommonsSignIn`, finalized with either
526
+ // `claimSessionByToken` or `finalizeCommonsOAuth`) and the approver /
527
+ // Commons (`getCommonsApprovalInfo` + `approveCommonsSignIn` /
528
+ // `denyCommonsSignIn`). The approver signs with its PRIMARY local key;
529
+ // the RP never sees the private key.
530
+ //
531
+ // ONE request serves every delivery surface. The delivery surface is
532
+ // not part of the model: only the request's purpose is, and it is fixed
533
+ // at creation by whether an OAuth binding was attached.
474
534
  // =======================================================================
475
535
  /**
476
536
  * MECHANISM A — same-device shared-keychain SSO.
@@ -521,12 +581,23 @@ function OxyServicesAuthMixin(Base) {
521
581
  * returns the server-issued public `authorizeCode` + ready-to-render
522
582
  * `qrPayload`. Render the QR (web) / open the deep-link (same-device); the
523
583
  * approver resolves the code and authorizes. Then poll with
524
- * {@link pollCommonsSignIn} and, on `authorized`, exchange the
525
- * `sessionToken` via the existing `claimSessionByToken`.
584
+ * {@link pollCommonsSignIn} and finalize.
585
+ *
586
+ * ONE request serves every delivery surface, and how it finalizes is decided
587
+ * here by whether an OAuth binding is attached:
588
+ * - no `oauth` (the default): a `device_sign_in` request — on `authorized`,
589
+ * exchange the `sessionToken` via the existing `claimSessionByToken`.
590
+ * - with `oauth`: an `oauth_authorization` request — on `authorized`, call
591
+ * {@link finalizeCommonsOAuth} with the same `sessionToken` to mint the
592
+ * single-use authorization code, then exchange it with PKCE.
526
593
  *
527
594
  * @param params.clientId - The RP's registered OAuth client id
528
595
  * (ApplicationCredential publicKey); required so the server can resolve the
529
596
  * requesting application's identity.
597
+ * @param params.oauth - Optional OAuth binding ({@link CommonsOAuthContext}).
598
+ * Carries only the redirect URI, the PKCE S256 challenge, the requested
599
+ * scope, and an optional delegated `subjectAccountId` — never the PKCE
600
+ * verifier, the OAuth `state`, or any token.
530
601
  */
531
602
  async startCommonsSignIn(params) {
532
603
  try {
@@ -535,7 +606,15 @@ function OxyServicesAuthMixin(Base) {
535
606
  // QR. Reuses the platform-safe random generator.
536
607
  const sessionToken = await signatureService_1.SignatureService.generateChallenge();
537
608
  const expiresAt = Date.now() + COMMONS_SIGN_IN_EXPIRY_MS;
538
- const res = await this.makeRequest('POST', '/auth/session/create', { sessionToken, expiresAt, clientId: params.clientId },
609
+ const res = await this.makeRequest('POST', '/auth/session/create', {
610
+ sessionToken,
611
+ expiresAt,
612
+ clientId: params.clientId,
613
+ // Omitted entirely when absent so the device-sign-in body stays
614
+ // exactly what it has always been (the server reads presence, not a
615
+ // null, to decide the request's purpose).
616
+ ...(params.oauth ? { oauth: params.oauth } : {}),
617
+ },
539
618
  // Public/pre-session (no bearer): skip the preflight so a stale
540
619
  // near-expiry token cannot re-enter refreshAccessToken while the
541
620
  // refresh handler is already in flight (self-await hang).
@@ -556,18 +635,162 @@ function OxyServicesAuthMixin(Base) {
556
635
  * MECHANISM B (relying party) — poll a device-flow session for approval.
557
636
  *
558
637
  * Backstop for the auth socket. On `authorized` (with a `sessionId`), the
559
- * caller exchanges the secret `sessionToken` via the existing
560
- * `claimSessionByToken` to mint the first access token.
638
+ * caller finalizes: `claimSessionByToken` for a `device_sign_in` request,
639
+ * {@link finalizeCommonsOAuth} for an `oauth_authorization` one.
640
+ *
641
+ * Every field is narrowed fail-safe. `authorized` counts only as a literal
642
+ * `true`, the identifiers only as non-empty strings, and the delivery
643
+ * progress timestamps degrade to `null` when absent or unparseable — so a
644
+ * partial or older-API payload can advance the waiting UI at most, never
645
+ * make it believe a request was approved.
561
646
  *
562
647
  * @param sessionToken - The secret token from {@link startCommonsSignIn}.
563
648
  */
564
649
  async pollCommonsSignIn(sessionToken) {
565
650
  try {
566
- return await this.makeRequest('GET', `/auth/session/status/${encodeURIComponent(sessionToken)}`, undefined,
651
+ const res = await this.makeRequest('GET', `/auth/session/status/${encodeURIComponent(sessionToken)}`, undefined,
567
652
  // Public/pre-session (no bearer): a preflight here is wrong per se and,
568
653
  // if ever reached while a refresh is pending, would re-enter
569
654
  // refreshAccessToken and await the very promise it runs inside.
570
655
  { cache: false, retry: false, skipAuth: true });
656
+ if (res === null || typeof res !== 'object') {
657
+ throw new Error('auth/session/status returned an unexpected response shape');
658
+ }
659
+ const { authorized, sessionId, publicKey, status, purpose, pushSentAt, openedAt } = res;
660
+ return {
661
+ authorized: authorized === true,
662
+ ...(typeof sessionId === 'string' && sessionId ? { sessionId } : {}),
663
+ ...(typeof publicKey === 'string' && publicKey ? { publicKey } : {}),
664
+ ...(typeof status === 'string' && status ? { status } : {}),
665
+ purpose: purpose === 'oauth_authorization' ? 'oauth_authorization' : 'device_sign_in',
666
+ pushSentAt: parseCommonsProgressTimestamp(pushSentAt),
667
+ openedAt: parseCommonsProgressTimestamp(openedAt),
668
+ };
669
+ }
670
+ catch (error) {
671
+ throw this.handleError(error);
672
+ }
673
+ }
674
+ /**
675
+ * MECHANISM B (relying party) — ask Oxy to DELIVER a pending sign-in request
676
+ * to the identity's known Commons installations.
677
+ *
678
+ * This is the automatic half of "one intention, one primary action": rather
679
+ * than offering the user a menu of transports, the caller asks for delivery
680
+ * and lets the answer pick the route. Pass the returned `targets` to
681
+ * `selectCommonsDelivery` (`utils/commonsDelivery`) — `targets: 0` means no
682
+ * capable Commons installation is registered, which is a NORMAL outcome that
683
+ * resolves to the QR route, not an error to surface.
684
+ *
685
+ * **Requires a bearer.** Delivery is only allowed when Oxy already knows the
686
+ * intended identity from a trusted authenticated context — a request that
687
+ * merely carries a username or email typed into an unauthenticated browser
688
+ * must never be able to ring somebody's phone.
689
+ *
690
+ * The push it sends carries only `{ type, approvalUrl }` where the URL holds
691
+ * the public `authorizeCode` — no display data, no secrets. Commons resolves
692
+ * everything it shows from `getCommonsApprovalInfo`.
693
+ *
694
+ * @param authorizeCode - The public code from {@link startCommonsSignIn}.
695
+ */
696
+ async deliverCommonsSignIn(authorizeCode) {
697
+ try {
698
+ const res = await this.makeRequest('POST', `/auth/session/deliver/${encodeURIComponent(authorizeCode)}`, undefined,
699
+ // Bearer REQUIRED (unlike its public siblings): the identity to
700
+ // deliver to comes from the authenticated caller, never the code.
701
+ { cache: false });
702
+ if (res === null || typeof res !== 'object') {
703
+ throw new Error('auth/session/deliver returned an unexpected response shape');
704
+ }
705
+ const { delivered, targets } = res;
706
+ // Both fields drive the route choice, so a partial payload is rejected
707
+ // outright rather than defaulted into a route the server never chose.
708
+ if (typeof delivered !== 'boolean' ||
709
+ typeof targets !== 'number' ||
710
+ !Number.isInteger(targets) ||
711
+ targets < 0) {
712
+ throw new Error('auth/session/deliver returned an incomplete delivery result');
713
+ }
714
+ return { delivered, targets };
715
+ }
716
+ catch (error) {
717
+ throw this.handleError(error);
718
+ }
719
+ }
720
+ /**
721
+ * MECHANISM B (approver / Commons) — report that the approval route was
722
+ * OPENED, so the waiting relying party can show "Opened in Commons".
723
+ *
724
+ * Progress only. It is idempotent, applies to a `pending` request alone, and
725
+ * records a timestamp (`openedAt`) — it never approves, authorizes, or
726
+ * advances the authorization state machine. Public, like the other approver
727
+ * handles: the approver has only the public `authorizeCode` at this point
728
+ * and has not yet signed anything.
729
+ *
730
+ * Best-effort by nature — a failure here costs the user only a progress
731
+ * line, so callers are free to ignore a rejection and continue to the
732
+ * approval screen.
733
+ *
734
+ * @param authorizeCode - The public code scanned from the QR / deep-link / push.
735
+ */
736
+ async markCommonsApprovalOpened(authorizeCode) {
737
+ try {
738
+ await this.makeRequest('POST', `/auth/session/opened/${encodeURIComponent(authorizeCode)}`, undefined,
739
+ // Public (no bearer) — skip the preflight, exactly like approve-info.
740
+ { cache: false, skipAuth: true });
741
+ }
742
+ catch (error) {
743
+ throw this.handleError(error);
744
+ }
745
+ }
746
+ /**
747
+ * MECHANISM B (relying party) — finalize an APPROVED, OAuth-bound request
748
+ * into a single-use OAuth authorization code.
749
+ *
750
+ * The OAuth counterpart of `claimSessionByToken`: same secret credential,
751
+ * same single-use semantics, different output. Call it once the request the
752
+ * RP started with an `oauth` binding reports `authorized`; the server
753
+ * atomically mints exactly ONE `AuthCode` bound to the redirect URI, PKCE
754
+ * challenge, scopes, approving identity, and any delegated subject account
755
+ * the request was created with. A second call cannot mint another code.
756
+ *
757
+ * The result is an authorization CODE, never a token — the caller completes
758
+ * the flow with the ordinary PKCE exchange (`exchangeOAuthCode`) using the
759
+ * `codeVerifier` it never sent anywhere. Nothing here is exposed to the
760
+ * popup: the code travels back through the registered callback, and the
761
+ * main window owns the verifier.
762
+ *
763
+ * Like `claimSessionByToken`, this needs no Authorization header — the
764
+ * high-entropy SECRET `sessionToken` IS the credential. Never pass the
765
+ * public `authorizeCode` here; it is the approver's handle, not the
766
+ * initiator's. Every server-side failure (wrong/expired/already-finalized
767
+ * request, non-OAuth purpose, missing permission for the delegated account)
768
+ * surfaces as one generic error, so nothing about the request's state can be
769
+ * probed from outside.
770
+ *
771
+ * @param sessionToken - The secret token from {@link startCommonsSignIn}.
772
+ */
773
+ async finalizeCommonsOAuth(sessionToken) {
774
+ try {
775
+ const res = await this.makeRequest('POST', `/auth/session/finalize/${encodeURIComponent(sessionToken)}`, undefined,
776
+ // Body-authenticated by the path's secret token (no bearer) — skip the
777
+ // preflight, exactly like the device-flow claim.
778
+ { cache: false, skipAuth: true });
779
+ if (res === null || typeof res !== 'object') {
780
+ throw new Error('auth/session/finalize returned an unexpected response shape');
781
+ }
782
+ const { code, redirectUri, expiresIn } = res;
783
+ // All three fields are load-bearing for the exchange that follows, so a
784
+ // partial payload is rejected outright rather than returned half-parsed.
785
+ if (typeof code !== 'string' ||
786
+ !code ||
787
+ typeof redirectUri !== 'string' ||
788
+ !redirectUri ||
789
+ typeof expiresIn !== 'number' ||
790
+ !Number.isFinite(expiresIn)) {
791
+ throw new Error('auth/session/finalize returned an incomplete authorization code');
792
+ }
793
+ return { code, redirectUri, expiresIn };
571
794
  }
572
795
  catch (error) {
573
796
  throw this.handleError(error);
@@ -577,9 +800,10 @@ function OxyServicesAuthMixin(Base) {
577
800
  * MECHANISM B (approver / Commons) — resolve the TRUSTED identity of a
578
801
  * sign-in request from its public `authorizeCode`.
579
802
  *
580
- * The returned `application` is resolved server-side and is the only safe
581
- * thing to display in the approval UI — NEVER trust the app/name/origin
582
- * strings carried in the QR payload. Public (no auth required).
803
+ * The returned `application` and `subjectAccount` are resolved server-side
804
+ * and are the only safe things to display in the approval UI — NEVER trust
805
+ * the app/name/origin strings carried in the QR payload. Public (no auth
806
+ * required).
583
807
  *
584
808
  * @param authorizeCode - The public code scanned from the QR / deep-link.
585
809
  */
@@ -597,6 +821,17 @@ function OxyServicesAuthMixin(Base) {
597
821
  // missing or non-boolean value (older server, malformed response)
598
822
  // coerces to `false` so a stale server can never imply trust.
599
823
  originVerified: raw.originVerified === true,
824
+ // Same discipline: a missing/blank/non-string label degrades to null
825
+ // (an older API, or a native requester with no browser to describe),
826
+ // and the approver simply omits the "where from" line.
827
+ requesterLabel: parseCommonsRequesterLabel(raw.requesterLabel),
828
+ // Same discipline: only the literal OAuth purpose opts into OAuth
829
+ // finalization. Anything else — including a server that predates this
830
+ // field — is the plain device sign-in it has always been.
831
+ purpose: raw.purpose === 'oauth_authorization' ? 'oauth_authorization' : 'device_sign_in',
832
+ // A missing/partial delegated account degrades to "no delegation"
833
+ // rather than a half-rendered "will act as" line.
834
+ subjectAccount: parseCommonsSubjectAccount(raw.subjectAccount),
600
835
  expiresAt: raw.expiresAt,
601
836
  status: raw.status,
602
837
  };
@@ -648,10 +883,15 @@ function OxyServicesAuthMixin(Base) {
648
883
  * device-flow session so the RP stops waiting.
649
884
  *
650
885
  * @param authorizeCode - The public code being denied.
886
+ * @param reason - Optional closed-set reason ({@link CommonsDenyReason}).
887
+ * Pass `'not_me'` ONLY when the user actually reported the request as one
888
+ * they did not start — the server records it as a suspicious denial rather
889
+ * than an ordinary cancel. Omitting it sends the exact body this endpoint
890
+ * has always received.
651
891
  */
652
- async denyCommonsSignIn(authorizeCode) {
892
+ async denyCommonsSignIn(authorizeCode, reason) {
653
893
  try {
654
- return await this.makeRequest('POST', `/auth/session/deny/${encodeURIComponent(authorizeCode)}`, undefined,
894
+ return await this.makeRequest('POST', `/auth/session/deny/${encodeURIComponent(authorizeCode)}`, reason ? { reason } : undefined,
655
895
  // Public (no auth required) — skip the bearer preflight.
656
896
  { cache: false, skipAuth: true });
657
897
  }
@@ -914,7 +1154,7 @@ function OxyServicesAuthMixin(Base) {
914
1154
  const deviceId = (record.deviceId ?? record.device_id);
915
1155
  const deviceSecret = (record.deviceSecret ?? record.device_secret);
916
1156
  const userRaw = record.user;
917
- if (!sessionId || !deviceId || !userRaw || typeof userRaw !== 'object') {
1157
+ if (!sessionId || !deviceId || !deviceSecret || !userRaw || typeof userRaw !== 'object') {
918
1158
  throw new Error('auth/oauth/token returned an incomplete session payload');
919
1159
  }
920
1160
  const userObj = userRaw;