@oxy.so/core 1.18.0 → 3.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.
Files changed (662) hide show
  1. package/README.md +13 -7
  2. package/dist/cjs/HttpService.js +189 -112
  3. package/dist/cjs/OxyServices.errors.js +48 -1
  4. package/dist/cjs/OxyServices.js +263 -60
  5. package/dist/cjs/api/accounts.js +231 -0
  6. package/dist/cjs/api/agency.js +102 -0
  7. package/dist/cjs/api/appData.js +45 -0
  8. package/dist/cjs/api/apps.js +197 -0
  9. package/dist/cjs/api/assets.js +329 -0
  10. package/dist/cjs/api/auth.js +900 -0
  11. package/dist/cjs/api/billing.js +44 -0
  12. package/dist/cjs/api/civic.js +289 -0
  13. package/dist/cjs/api/contacts.js +16 -0
  14. package/dist/cjs/api/devices.js +351 -0
  15. package/dist/cjs/api/follows.js +240 -0
  16. package/dist/cjs/api/identity.js +569 -0
  17. package/dist/cjs/api/linkedAccounts.js +48 -0
  18. package/dist/cjs/api/nodes.js +75 -0
  19. package/dist/cjs/api/notifications.js +75 -0
  20. package/dist/cjs/api/privacy.js +105 -0
  21. package/dist/cjs/api/reputation.js +74 -0
  22. package/dist/cjs/api/session.js +177 -0
  23. package/dist/cjs/api/store.js +199 -0
  24. package/dist/cjs/api/topics.js +66 -0
  25. package/dist/cjs/api/users.js +309 -0
  26. package/dist/cjs/boot/sessionColdBoot.js +4 -4
  27. package/dist/cjs/civic/index.js +16 -0
  28. package/dist/cjs/civic/payloads.js +156 -0
  29. package/dist/cjs/client/context.js +2 -0
  30. package/dist/cjs/crypto/identityLink.js +5 -5
  31. package/dist/cjs/crypto/index.js +44 -0
  32. package/dist/cjs/crypto/internal.js +37 -0
  33. package/dist/cjs/crypto/keyManager.js +2 -2
  34. package/dist/cjs/crypto/polyfill.js +9 -80
  35. package/dist/cjs/crypto/recoveryPhrase.js +1 -0
  36. package/dist/cjs/crypto/registrationPow.js +1 -1
  37. package/dist/cjs/crypto/signatureService.js +1 -0
  38. package/dist/cjs/i18n/accountCategoryLabels.js +3 -3
  39. package/dist/cjs/i18n/accountRoleLabels.js +7 -7
  40. package/dist/cjs/i18n/index.js +130 -52
  41. package/dist/cjs/i18n/locales/{ar-SA.json → ar-SA.js} +134 -52
  42. package/dist/cjs/i18n/locales/{ca-ES.json → ca-ES.js} +134 -52
  43. package/dist/{esm/i18n/locales/de-DE.json → cjs/i18n/locales/de-DE.js} +136 -54
  44. package/dist/{esm/i18n/locales/en-US.json → cjs/i18n/locales/en-US.js} +163 -245
  45. package/dist/{esm/i18n/locales/es-ES.json → cjs/i18n/locales/es-ES.js} +163 -245
  46. package/dist/cjs/i18n/locales/{fr-FR.json → fr-FR.js} +134 -52
  47. package/dist/cjs/i18n/locales/{it-IT.json → it-IT.js} +134 -52
  48. package/dist/{esm/i18n/locales/ja-JP.json → cjs/i18n/locales/ja-JP.js} +134 -52
  49. package/dist/cjs/i18n/locales/{ko-KR.json → ko-KR.js} +134 -52
  50. package/dist/{esm/i18n/locales/pt-PT.json → cjs/i18n/locales/pt-PT.js} +136 -54
  51. package/dist/{esm/i18n/locales/zh-CN.json → cjs/i18n/locales/zh-CN.js} +135 -53
  52. package/dist/cjs/i18n/reputationCategoryLabels.js +2 -2
  53. package/dist/cjs/i18n/trustTierLabels.js +2 -2
  54. package/dist/cjs/index.js +55 -263
  55. package/dist/cjs/inference/index.js +21 -0
  56. package/dist/cjs/server/OxyServer.js +521 -0
  57. package/dist/cjs/server/auth.js +2 -2
  58. package/dist/cjs/server/index.js +23 -6
  59. package/dist/cjs/server/middleware.js +1154 -0
  60. package/dist/cjs/server/namespaces.js +214 -0
  61. package/dist/cjs/server/rateLimit.js +2 -2
  62. package/dist/cjs/server/requesterAssertion.js +5 -5
  63. package/dist/cjs/server/securityHeaders.js +3 -3
  64. package/dist/cjs/server/traffic.js +7 -6
  65. package/dist/cjs/server/trafficNodeHttp.js +9 -3
  66. package/dist/cjs/server/userInvalidation.js +5 -5
  67. package/dist/cjs/session/accountDialogController.js +23 -23
  68. package/dist/cjs/session/authStateStore.js +2 -2
  69. package/dist/cjs/session/identitySession.js +3 -3
  70. package/dist/cjs/session/index.js +131 -0
  71. package/dist/cjs/session/refresh.js +37 -30
  72. package/dist/cjs/session/sessionClientHost.js +5 -5
  73. package/dist/cjs/session/sharedDeviceCredential.js +3 -2
  74. package/dist/cjs/shared/utils/networkUtils.js +2 -2
  75. package/dist/cjs/utils/accountCacheSweep.js +11 -15
  76. package/dist/cjs/utils/asyncUtils.js +22 -163
  77. package/dist/cjs/utils/authHelpers.js +2 -2
  78. package/dist/cjs/utils/authWebUrl.js +3 -4
  79. package/dist/cjs/utils/avatarUtils.js +2 -2
  80. package/dist/cjs/utils/cache.js +32 -2
  81. package/dist/cjs/utils/cacheKey.js +5 -10
  82. package/dist/cjs/utils/deviceManager.js +17 -16
  83. package/dist/cjs/utils/edgeRegion.js +2 -3
  84. package/dist/cjs/utils/identityCacheSweep.js +6 -10
  85. package/dist/cjs/utils/oxyServiceEnvironment.js +3 -7
  86. package/dist/cjs/utils/requestUtils.js +40 -33
  87. package/dist/cjs/utils/sessionUtils.js +0 -2
  88. package/dist/cjs/utils/tokenClaims.js +34 -0
  89. package/dist/esm/HttpService.js +190 -113
  90. package/dist/esm/OxyServices.errors.js +45 -0
  91. package/dist/esm/OxyServices.js +262 -57
  92. package/dist/esm/api/accounts.js +218 -0
  93. package/dist/esm/api/agency.js +95 -0
  94. package/dist/esm/api/appData.js +40 -0
  95. package/dist/esm/api/apps.js +191 -0
  96. package/dist/esm/api/assets.js +324 -0
  97. package/dist/esm/api/auth.js +858 -0
  98. package/dist/esm/api/billing.js +40 -0
  99. package/dist/esm/api/civic.js +250 -0
  100. package/dist/esm/api/contacts.js +12 -0
  101. package/dist/esm/api/devices.js +346 -0
  102. package/dist/esm/api/follows.js +236 -0
  103. package/dist/esm/api/identity.js +527 -0
  104. package/dist/esm/api/linkedAccounts.js +44 -0
  105. package/dist/esm/api/nodes.js +71 -0
  106. package/dist/esm/api/notifications.js +71 -0
  107. package/dist/esm/api/privacy.js +101 -0
  108. package/dist/esm/api/reputation.js +70 -0
  109. package/dist/esm/api/session.js +173 -0
  110. package/dist/esm/api/store.js +192 -0
  111. package/dist/esm/api/topics.js +62 -0
  112. package/dist/esm/api/users.js +272 -0
  113. package/dist/esm/boot/sessionColdBoot.js +4 -4
  114. package/dist/esm/civic/index.js +10 -0
  115. package/dist/esm/civic/payloads.js +118 -0
  116. package/dist/esm/client/context.js +1 -0
  117. package/dist/esm/crypto/identityLink.js +5 -5
  118. package/dist/esm/crypto/index.js +24 -0
  119. package/dist/esm/crypto/internal.js +19 -0
  120. package/dist/esm/crypto/keyManager.js +2 -2
  121. package/dist/esm/crypto/polyfill.js +10 -81
  122. package/dist/esm/crypto/recoveryPhrase.js +1 -0
  123. package/dist/esm/crypto/registrationPow.js +1 -1
  124. package/dist/esm/crypto/signatureService.js +1 -0
  125. package/dist/esm/i18n/accountCategoryLabels.js +2 -2
  126. package/dist/esm/i18n/accountRoleLabels.js +1 -1
  127. package/dist/esm/i18n/index.js +93 -52
  128. package/dist/esm/i18n/locales/{ar-SA.json → ar-SA.js} +132 -52
  129. package/dist/esm/i18n/locales/{ca-ES.json → ca-ES.js} +132 -52
  130. package/dist/{cjs/i18n/locales/de-DE.json → esm/i18n/locales/de-DE.js} +134 -54
  131. package/dist/{cjs/i18n/locales/en-US.json → esm/i18n/locales/en-US.js} +161 -245
  132. package/dist/{cjs/i18n/locales/es-ES.json → esm/i18n/locales/es-ES.js} +161 -245
  133. package/dist/esm/i18n/locales/{fr-FR.json → fr-FR.js} +132 -52
  134. package/dist/esm/i18n/locales/{it-IT.json → it-IT.js} +132 -52
  135. package/dist/{cjs/i18n/locales/ja-JP.json → esm/i18n/locales/ja-JP.js} +132 -52
  136. package/dist/esm/i18n/locales/{ko-KR.json → ko-KR.js} +132 -52
  137. package/dist/{cjs/i18n/locales/pt-PT.json → esm/i18n/locales/pt-PT.js} +134 -54
  138. package/dist/{cjs/i18n/locales/zh-CN.json → esm/i18n/locales/zh-CN.js} +133 -53
  139. package/dist/esm/i18n/reputationCategoryLabels.js +1 -1
  140. package/dist/esm/i18n/trustTierLabels.js +1 -1
  141. package/dist/esm/index.js +30 -166
  142. package/dist/esm/inference/index.js +13 -0
  143. package/dist/esm/server/OxyServer.js +516 -0
  144. package/dist/esm/server/auth.js +2 -2
  145. package/dist/esm/server/index.js +10 -5
  146. package/dist/esm/server/middleware.js +1147 -0
  147. package/dist/esm/server/namespaces.js +206 -0
  148. package/dist/esm/server/rateLimit.js +2 -2
  149. package/dist/esm/server/requesterAssertion.js +5 -5
  150. package/dist/esm/server/securityHeaders.js +3 -3
  151. package/dist/esm/server/traffic.js +7 -6
  152. package/dist/esm/server/trafficNodeHttp.js +9 -3
  153. package/dist/esm/server/userInvalidation.js +5 -5
  154. package/dist/esm/session/accountDialogController.js +23 -23
  155. package/dist/esm/session/authStateStore.js +2 -2
  156. package/dist/esm/session/identitySession.js +3 -3
  157. package/dist/esm/session/index.js +82 -0
  158. package/dist/esm/session/refresh.js +37 -30
  159. package/dist/esm/session/sessionClientHost.js +5 -5
  160. package/dist/esm/session/sharedDeviceCredential.js +3 -2
  161. package/dist/esm/shared/utils/networkUtils.js +2 -2
  162. package/dist/esm/utils/accountCacheSweep.js +11 -15
  163. package/dist/esm/utils/asyncUtils.js +22 -151
  164. package/dist/esm/utils/authHelpers.js +2 -2
  165. package/dist/esm/utils/authWebUrl.js +3 -4
  166. package/dist/esm/utils/avatarUtils.js +2 -2
  167. package/dist/esm/utils/cache.js +32 -2
  168. package/dist/esm/utils/cacheKey.js +5 -10
  169. package/dist/esm/utils/deviceManager.js +17 -16
  170. package/dist/esm/utils/edgeRegion.js +1 -2
  171. package/dist/esm/utils/identityCacheSweep.js +6 -10
  172. package/dist/esm/utils/oxyServiceEnvironment.js +3 -7
  173. package/dist/esm/utils/requestUtils.js +40 -33
  174. package/dist/esm/utils/sessionUtils.js +0 -2
  175. package/dist/esm/utils/tokenClaims.js +31 -0
  176. package/dist/types/HttpService.d.ts +47 -9
  177. package/dist/types/OxyServices.d.ts +160 -131
  178. package/dist/types/OxyServices.errors.d.ts +42 -0
  179. package/dist/types/api/accounts.d.ts +415 -0
  180. package/dist/types/api/agency.d.ts +135 -0
  181. package/dist/types/api/appData.d.ts +27 -0
  182. package/dist/types/api/apps.d.ts +433 -0
  183. package/dist/types/api/assets.d.ts +174 -0
  184. package/dist/types/api/auth.d.ts +766 -0
  185. package/dist/types/api/billing.d.ts +113 -0
  186. package/dist/types/api/civic.d.ts +203 -0
  187. package/dist/types/api/contacts.d.ts +31 -0
  188. package/{src/mixins/OxyServices.deviceBoot.ts → dist/types/api/devices.d.ts} +130 -110
  189. package/dist/types/api/follows.d.ts +236 -0
  190. package/dist/types/api/identity.d.ts +275 -0
  191. package/dist/types/api/linkedAccounts.d.ts +48 -0
  192. package/dist/types/api/nodes.d.ts +102 -0
  193. package/dist/types/api/notifications.d.ts +77 -0
  194. package/dist/types/api/privacy.d.ts +43 -0
  195. package/dist/types/api/reputation.d.ts +50 -0
  196. package/dist/types/api/session.d.ts +85 -0
  197. package/dist/types/api/store.d.ts +278 -0
  198. package/dist/types/api/topics.d.ts +34 -0
  199. package/dist/types/api/users.d.ts +134 -0
  200. package/dist/types/boot/sessionColdBoot.d.ts +1 -1
  201. package/dist/types/civic/index.d.ts +10 -0
  202. package/dist/types/civic/payloads.d.ts +83 -0
  203. package/dist/types/client/context.d.ts +33 -0
  204. package/dist/types/crypto/identityLink.d.ts +5 -5
  205. package/dist/types/crypto/index.d.ts +24 -0
  206. package/dist/types/crypto/internal.d.ts +19 -0
  207. package/dist/types/crypto/polyfill.d.ts +6 -9
  208. package/dist/types/crypto/registrationPow.d.ts +1 -1
  209. package/dist/types/i18n/accountCategoryLabels.d.ts +1 -1
  210. package/dist/types/i18n/accountRoleLabels.d.ts +1 -1
  211. package/dist/types/i18n/index.d.ts +20 -1
  212. package/dist/types/i18n/locales/ar-SA.d.ts +386 -0
  213. package/dist/types/i18n/locales/ca-ES.d.ts +386 -0
  214. package/dist/types/i18n/locales/de-DE.d.ts +386 -0
  215. package/dist/types/i18n/locales/en-US.d.ts +2034 -0
  216. package/dist/types/i18n/locales/es-ES.d.ts +2034 -0
  217. package/dist/types/i18n/locales/fr-FR.d.ts +386 -0
  218. package/dist/types/i18n/locales/it-IT.d.ts +386 -0
  219. package/dist/types/i18n/locales/ja-JP.d.ts +386 -0
  220. package/dist/types/i18n/locales/ko-KR.d.ts +386 -0
  221. package/dist/types/i18n/locales/pt-PT.d.ts +386 -0
  222. package/dist/types/i18n/locales/zh-CN.d.ts +386 -0
  223. package/dist/types/index.d.ts +46 -95
  224. package/dist/types/inference/index.d.ts +22 -0
  225. package/dist/types/models/interfaces.d.ts +1 -1
  226. package/dist/types/server/OxyServer.d.ts +183 -0
  227. package/dist/types/server/auth.d.ts +10 -7
  228. package/dist/types/server/index.d.ts +10 -4
  229. package/dist/types/server/middleware.d.ts +291 -0
  230. package/dist/types/server/namespaces.d.ts +98 -0
  231. package/dist/types/server/rateLimit.d.ts +7 -5
  232. package/dist/types/server/requesterAssertion.d.ts +14 -11
  233. package/dist/types/server/securityHeaders.d.ts +4 -5
  234. package/dist/types/server/userInvalidation.d.ts +10 -8
  235. package/dist/types/session/accountDelegationAccess.d.ts +1 -1
  236. package/dist/types/session/accountDialogController.d.ts +8 -8
  237. package/dist/types/session/accountSwitchTargets.d.ts +1 -1
  238. package/dist/types/session/authStateStore.d.ts +7 -6
  239. package/dist/types/session/index.d.ts +40 -0
  240. package/dist/types/utils/accountCacheSweep.d.ts +7 -10
  241. package/dist/types/utils/apiUtils.d.ts +3 -3
  242. package/dist/types/utils/asyncUtils.d.ts +11 -58
  243. package/dist/types/utils/authWebUrl.d.ts +3 -4
  244. package/dist/types/utils/avatarUtils.d.ts +5 -6
  245. package/dist/types/utils/cache.d.ts +11 -3
  246. package/dist/types/utils/commonsApproval.d.ts +1 -1
  247. package/dist/types/utils/edgeRegion.d.ts +1 -2
  248. package/dist/types/utils/identityCacheSweep.d.ts +6 -4
  249. package/dist/types/utils/oauthPkce.d.ts +2 -2
  250. package/dist/types/utils/oxyServiceEnvironment.d.ts +3 -7
  251. package/dist/types/utils/requestUtils.d.ts +18 -14
  252. package/dist/types/utils/tokenClaims.d.ts +9 -0
  253. package/package.json +68 -7
  254. package/src/HttpService.ts +201 -132
  255. package/src/OxyServices.errors.ts +73 -0
  256. package/src/OxyServices.ts +289 -164
  257. package/src/api/accounts.ts +579 -0
  258. package/src/api/agency.ts +277 -0
  259. package/src/api/appData.ts +63 -0
  260. package/src/api/apps.ts +596 -0
  261. package/src/api/assets.ts +416 -0
  262. package/src/api/auth.ts +1457 -0
  263. package/src/api/billing.ts +144 -0
  264. package/src/api/civic.ts +428 -0
  265. package/src/api/contacts.ts +41 -0
  266. package/src/api/devices.ts +438 -0
  267. package/src/api/follows.ts +414 -0
  268. package/src/api/identity.ts +727 -0
  269. package/src/api/linkedAccounts.ts +78 -0
  270. package/src/api/nodes.ts +169 -0
  271. package/src/api/notifications.ts +135 -0
  272. package/src/api/privacy.ts +120 -0
  273. package/src/api/reputation.ts +134 -0
  274. package/src/api/session.ts +210 -0
  275. package/src/api/store.ts +435 -0
  276. package/src/api/topics.ts +83 -0
  277. package/src/api/users.ts +357 -0
  278. package/src/boot/sessionColdBoot.ts +4 -4
  279. package/src/civic/index.ts +12 -0
  280. package/src/civic/payloads.ts +175 -0
  281. package/src/client/context.ts +34 -0
  282. package/src/crypto/identityLink.ts +5 -5
  283. package/src/crypto/index.ts +39 -0
  284. package/src/crypto/internal.ts +20 -0
  285. package/src/crypto/keyManager.ts +2 -2
  286. package/src/crypto/polyfill.ts +9 -89
  287. package/src/crypto/recoveryPhrase.ts +1 -0
  288. package/src/crypto/registrationPow.ts +1 -1
  289. package/src/crypto/signatureService.ts +1 -0
  290. package/src/i18n/accountCategoryLabels.ts +2 -2
  291. package/src/i18n/accountRoleLabels.ts +2 -2
  292. package/src/i18n/index.ts +105 -54
  293. package/src/i18n/locales/{ar-SA.json → ar-SA.ts} +132 -52
  294. package/src/i18n/locales/{ca-ES.json → ca-ES.ts} +132 -52
  295. package/{dist/cjs/i18n/locales/locales/de-DE.json → src/i18n/locales/de-DE.ts} +134 -54
  296. package/src/i18n/locales/{en-US.json → en-US.ts} +161 -245
  297. package/{dist/cjs/i18n/locales/locales/es-ES.json → src/i18n/locales/es-ES.ts} +161 -245
  298. package/src/i18n/locales/{fr-FR.json → fr-FR.ts} +132 -52
  299. package/src/i18n/locales/{it-IT.json → it-IT.ts} +132 -52
  300. package/{dist/cjs/i18n/locales/locales/ja-JP.json → src/i18n/locales/ja-JP.ts} +132 -52
  301. package/src/i18n/locales/{ko-KR.json → ko-KR.ts} +132 -52
  302. package/src/i18n/locales/{pt-PT.json → pt-PT.ts} +134 -54
  303. package/src/i18n/locales/{zh-CN.json → zh-CN.ts} +133 -53
  304. package/src/i18n/reputationCategoryLabels.ts +1 -1
  305. package/src/i18n/trustTierLabels.ts +1 -1
  306. package/src/index.ts +170 -501
  307. package/src/inference/index.ts +48 -0
  308. package/src/models/interfaces.ts +1 -1
  309. package/src/server/OxyServer.ts +637 -0
  310. package/src/server/auth.ts +12 -8
  311. package/src/server/index.ts +28 -5
  312. package/src/server/middleware.ts +1520 -0
  313. package/src/server/namespaces.ts +257 -0
  314. package/src/server/rateLimit.ts +10 -9
  315. package/src/server/requesterAssertion.ts +13 -10
  316. package/src/server/securityHeaders.ts +7 -8
  317. package/src/server/traffic.ts +6 -6
  318. package/src/server/trafficNodeHttp.ts +7 -3
  319. package/src/server/userInvalidation.ts +9 -9
  320. package/src/session/accountDelegationAccess.ts +1 -1
  321. package/src/session/accountDialogController.ts +24 -24
  322. package/src/session/accountSwitchTargets.ts +1 -1
  323. package/src/session/authStateStore.ts +9 -8
  324. package/src/session/identitySession.ts +3 -3
  325. package/src/session/index.ts +210 -0
  326. package/src/session/refresh.ts +36 -30
  327. package/src/session/sessionClientHost.ts +5 -5
  328. package/src/session/sharedDeviceCredential.ts +3 -2
  329. package/src/shared/utils/networkUtils.ts +2 -2
  330. package/src/utils/accountCacheSweep.ts +11 -15
  331. package/src/utils/apiUtils.ts +3 -3
  332. package/src/utils/asyncUtils.ts +38 -235
  333. package/src/utils/authHelpers.ts +2 -2
  334. package/src/utils/authWebUrl.ts +3 -4
  335. package/src/utils/avatarUtils.ts +4 -7
  336. package/src/utils/cache.ts +36 -5
  337. package/src/utils/cacheKey.ts +5 -9
  338. package/src/utils/commonsApproval.ts +1 -1
  339. package/src/utils/deviceManager.ts +17 -16
  340. package/src/utils/edgeRegion.ts +1 -2
  341. package/src/utils/identityCacheSweep.ts +7 -12
  342. package/src/utils/oauthPkce.ts +2 -2
  343. package/src/utils/oxyServiceEnvironment.ts +3 -7
  344. package/src/utils/requestUtils.ts +48 -44
  345. package/src/utils/sessionUtils.ts +0 -2
  346. package/src/utils/tokenClaims.ts +38 -0
  347. package/dist/cjs/.tsbuildinfo +0 -1
  348. package/dist/cjs/OxyServices.base.js +0 -392
  349. package/dist/cjs/i18n/locales/locales/ar-SA.json +0 -306
  350. package/dist/cjs/i18n/locales/locales/ca-ES.json +0 -306
  351. package/dist/cjs/i18n/locales/locales/en-US.json +0 -2127
  352. package/dist/cjs/i18n/locales/locales/fr-FR.json +0 -306
  353. package/dist/cjs/i18n/locales/locales/it-IT.json +0 -306
  354. package/dist/cjs/i18n/locales/locales/ko-KR.json +0 -306
  355. package/dist/cjs/i18n/locales/locales/pt-PT.json +0 -306
  356. package/dist/cjs/i18n/locales/locales/zh-CN.json +0 -306
  357. package/dist/cjs/mixins/OxyServices.accounts.js +0 -555
  358. package/dist/cjs/mixins/OxyServices.agency.js +0 -154
  359. package/dist/cjs/mixins/OxyServices.analytics.js +0 -49
  360. package/dist/cjs/mixins/OxyServices.appData.js +0 -108
  361. package/dist/cjs/mixins/OxyServices.assets.js +0 -854
  362. package/dist/cjs/mixins/OxyServices.auth.js +0 -1440
  363. package/dist/cjs/mixins/OxyServices.chains.js +0 -73
  364. package/dist/cjs/mixins/OxyServices.civic.js +0 -611
  365. package/dist/cjs/mixins/OxyServices.connectedApps.js +0 -95
  366. package/dist/cjs/mixins/OxyServices.contacts.js +0 -50
  367. package/dist/cjs/mixins/OxyServices.deviceBoot.js +0 -191
  368. package/dist/cjs/mixins/OxyServices.devices.js +0 -116
  369. package/dist/cjs/mixins/OxyServices.features.js +0 -335
  370. package/dist/cjs/mixins/OxyServices.followGraph.js +0 -221
  371. package/dist/cjs/mixins/OxyServices.identity.js +0 -562
  372. package/dist/cjs/mixins/OxyServices.identityBackup.js +0 -172
  373. package/dist/cjs/mixins/OxyServices.inference.js +0 -59
  374. package/dist/cjs/mixins/OxyServices.language.js +0 -125
  375. package/dist/cjs/mixins/OxyServices.linkedAccounts.js +0 -79
  376. package/dist/cjs/mixins/OxyServices.location.js +0 -43
  377. package/dist/cjs/mixins/OxyServices.nodes.js +0 -175
  378. package/dist/cjs/mixins/OxyServices.notifications.js +0 -63
  379. package/dist/cjs/mixins/OxyServices.payment.js +0 -158
  380. package/dist/cjs/mixins/OxyServices.privacy.js +0 -199
  381. package/dist/cjs/mixins/OxyServices.reputation.js +0 -289
  382. package/dist/cjs/mixins/OxyServices.security.js +0 -89
  383. package/dist/cjs/mixins/OxyServices.store.js +0 -266
  384. package/dist/cjs/mixins/OxyServices.topics.js +0 -137
  385. package/dist/cjs/mixins/OxyServices.user.js +0 -954
  386. package/dist/cjs/mixins/OxyServices.utility.js +0 -1402
  387. package/dist/cjs/mixins/index.js +0 -136
  388. package/dist/cjs/mixins/mixinHelpers.js +0 -53
  389. package/dist/cjs/utils/webauthnOrigin.js +0 -51
  390. package/dist/esm/.tsbuildinfo +0 -1
  391. package/dist/esm/OxyServices.base.js +0 -388
  392. package/dist/esm/i18n/locales/locales/ar-SA.json +0 -306
  393. package/dist/esm/i18n/locales/locales/ca-ES.json +0 -306
  394. package/dist/esm/i18n/locales/locales/de-DE.json +0 -306
  395. package/dist/esm/i18n/locales/locales/en-US.json +0 -2127
  396. package/dist/esm/i18n/locales/locales/es-ES.json +0 -2127
  397. package/dist/esm/i18n/locales/locales/fr-FR.json +0 -306
  398. package/dist/esm/i18n/locales/locales/it-IT.json +0 -306
  399. package/dist/esm/i18n/locales/locales/ja-JP.json +0 -306
  400. package/dist/esm/i18n/locales/locales/ko-KR.json +0 -306
  401. package/dist/esm/i18n/locales/locales/pt-PT.json +0 -306
  402. package/dist/esm/i18n/locales/locales/zh-CN.json +0 -306
  403. package/dist/esm/mixins/OxyServices.accounts.js +0 -543
  404. package/dist/esm/mixins/OxyServices.agency.js +0 -151
  405. package/dist/esm/mixins/OxyServices.analytics.js +0 -46
  406. package/dist/esm/mixins/OxyServices.appData.js +0 -103
  407. package/dist/esm/mixins/OxyServices.assets.js +0 -851
  408. package/dist/esm/mixins/OxyServices.auth.js +0 -1400
  409. package/dist/esm/mixins/OxyServices.chains.js +0 -70
  410. package/dist/esm/mixins/OxyServices.civic.js +0 -605
  411. package/dist/esm/mixins/OxyServices.connectedApps.js +0 -92
  412. package/dist/esm/mixins/OxyServices.contacts.js +0 -47
  413. package/dist/esm/mixins/OxyServices.deviceBoot.js +0 -186
  414. package/dist/esm/mixins/OxyServices.devices.js +0 -113
  415. package/dist/esm/mixins/OxyServices.features.js +0 -332
  416. package/dist/esm/mixins/OxyServices.followGraph.js +0 -218
  417. package/dist/esm/mixins/OxyServices.identity.js +0 -558
  418. package/dist/esm/mixins/OxyServices.identityBackup.js +0 -169
  419. package/dist/esm/mixins/OxyServices.inference.js +0 -56
  420. package/dist/esm/mixins/OxyServices.language.js +0 -122
  421. package/dist/esm/mixins/OxyServices.linkedAccounts.js +0 -76
  422. package/dist/esm/mixins/OxyServices.location.js +0 -40
  423. package/dist/esm/mixins/OxyServices.nodes.js +0 -172
  424. package/dist/esm/mixins/OxyServices.notifications.js +0 -60
  425. package/dist/esm/mixins/OxyServices.payment.js +0 -155
  426. package/dist/esm/mixins/OxyServices.privacy.js +0 -196
  427. package/dist/esm/mixins/OxyServices.reputation.js +0 -286
  428. package/dist/esm/mixins/OxyServices.security.js +0 -86
  429. package/dist/esm/mixins/OxyServices.store.js +0 -263
  430. package/dist/esm/mixins/OxyServices.topics.js +0 -134
  431. package/dist/esm/mixins/OxyServices.user.js +0 -951
  432. package/dist/esm/mixins/OxyServices.utility.js +0 -1397
  433. package/dist/esm/mixins/index.js +0 -133
  434. package/dist/esm/mixins/mixinHelpers.js +0 -48
  435. package/dist/esm/utils/webauthnOrigin.js +0 -48
  436. package/dist/types/.tsbuildinfo +0 -1
  437. package/dist/types/OxyServices.base.d.ts +0 -189
  438. package/dist/types/mixins/OxyServices.accounts.d.ts +0 -868
  439. package/dist/types/mixins/OxyServices.agency.d.ts +0 -176
  440. package/dist/types/mixins/OxyServices.analytics.d.ts +0 -72
  441. package/dist/types/mixins/OxyServices.appData.d.ts +0 -108
  442. package/dist/types/mixins/OxyServices.assets.d.ts +0 -371
  443. package/dist/types/mixins/OxyServices.auth.d.ts +0 -965
  444. package/dist/types/mixins/OxyServices.chains.d.ts +0 -156
  445. package/dist/types/mixins/OxyServices.civic.d.ts +0 -520
  446. package/dist/types/mixins/OxyServices.connectedApps.d.ts +0 -184
  447. package/dist/types/mixins/OxyServices.contacts.d.ts +0 -100
  448. package/dist/types/mixins/OxyServices.deviceBoot.d.ts +0 -185
  449. package/dist/types/mixins/OxyServices.devices.d.ts +0 -103
  450. package/dist/types/mixins/OxyServices.features.d.ts +0 -250
  451. package/dist/types/mixins/OxyServices.followGraph.d.ts +0 -223
  452. package/dist/types/mixins/OxyServices.identity.d.ts +0 -337
  453. package/dist/types/mixins/OxyServices.identityBackup.d.ts +0 -129
  454. package/dist/types/mixins/OxyServices.inference.d.ts +0 -95
  455. package/dist/types/mixins/OxyServices.language.d.ts +0 -87
  456. package/dist/types/mixins/OxyServices.linkedAccounts.d.ts +0 -107
  457. package/dist/types/mixins/OxyServices.location.d.ts +0 -70
  458. package/dist/types/mixins/OxyServices.nodes.d.ts +0 -198
  459. package/dist/types/mixins/OxyServices.notifications.d.ts +0 -143
  460. package/dist/types/mixins/OxyServices.payment.d.ts +0 -117
  461. package/dist/types/mixins/OxyServices.privacy.d.ts +0 -146
  462. package/dist/types/mixins/OxyServices.reputation.d.ts +0 -202
  463. package/dist/types/mixins/OxyServices.security.d.ts +0 -84
  464. package/dist/types/mixins/OxyServices.store.d.ts +0 -334
  465. package/dist/types/mixins/OxyServices.topics.d.ts +0 -110
  466. package/dist/types/mixins/OxyServices.user.d.ts +0 -600
  467. package/dist/types/mixins/OxyServices.utility.d.ts +0 -526
  468. package/dist/types/mixins/index.d.ts +0 -87
  469. package/dist/types/mixins/mixinHelpers.d.ts +0 -31
  470. package/dist/types/utils/webauthnOrigin.d.ts +0 -32
  471. package/src/OxyServices.base.ts +0 -480
  472. package/src/__tests__/authSocket.test.ts +0 -96
  473. package/src/__tests__/fixEsmImports.test.ts +0 -132
  474. package/src/__tests__/httpServiceAbort.test.ts +0 -308
  475. package/src/__tests__/httpServiceActivityId.test.ts +0 -49
  476. package/src/__tests__/httpServiceAuthSelfAwait.test.ts +0 -108
  477. package/src/__tests__/httpServiceCache.test.ts +0 -318
  478. package/src/__tests__/httpServiceFormEncoded.test.ts +0 -139
  479. package/src/__tests__/httpServiceQueueDeadlock.test.ts +0 -128
  480. package/src/__tests__/httpServiceRateLimitedRefresh.test.ts +0 -160
  481. package/src/__tests__/httpServiceUnwrapEnvelope.test.ts +0 -115
  482. package/src/__tests__/httpServiceWrites.test.ts +0 -175
  483. package/src/__tests__/inSessionRefresh.test.ts +0 -313
  484. package/src/__tests__/linkedClient.test.ts +0 -363
  485. package/src/__tests__/nearExpiryMintStorm.test.ts +0 -165
  486. package/src/__tests__/parseHttpErrorBody.test.ts +0 -116
  487. package/src/__tests__/publicInterfaceParity.test.ts +0 -75
  488. package/src/__tests__/serverValueImportsDeclared.test.ts +0 -120
  489. package/src/__tests__/serviceTokenWorkloadFallback.test.ts +0 -85
  490. package/src/__tests__/userIdentity.test.ts +0 -143
  491. package/src/boot/__tests__/sessionColdBoot.identity.test.ts +0 -393
  492. package/src/boot/__tests__/sessionColdBoot.sharedDevice.test.ts +0 -325
  493. package/src/boot/__tests__/sessionColdBoot.test.ts +0 -570
  494. package/src/crypto/__tests__/backupMaterial.test.ts +0 -86
  495. package/src/crypto/__tests__/cryptoPrimitives.test.ts +0 -226
  496. package/src/crypto/__tests__/identityLink.test.ts +0 -18
  497. package/src/crypto/__tests__/identityMocks.ts +0 -125
  498. package/src/crypto/__tests__/identityProof.test.ts +0 -81
  499. package/src/crypto/__tests__/keyManager.atomicity.test.ts +0 -272
  500. package/src/crypto/__tests__/keyManager.cacheSafety.test.ts +0 -175
  501. package/src/crypto/__tests__/keyManager.deviceBackup.test.ts +0 -310
  502. package/src/crypto/__tests__/keyManager.identityStatus.test.ts +0 -217
  503. package/src/crypto/__tests__/keyManager.recoveryLadder.test.ts +0 -179
  504. package/src/crypto/__tests__/keyManager.recoveryMnemonic.test.ts +0 -138
  505. package/src/crypto/__tests__/keyManager.sharedSlotSync.test.ts +0 -150
  506. package/src/crypto/__tests__/keyManager.storageMigration.test.ts +0 -227
  507. package/src/crypto/__tests__/keyManager.test.ts +0 -329
  508. package/src/crypto/__tests__/polyfill.test.ts +0 -109
  509. package/src/crypto/__tests__/polyfillOrder.test.ts +0 -114
  510. package/src/crypto/__tests__/recoveryPhrase.test.ts +0 -61
  511. package/src/crypto/__tests__/registrationPow.test.ts +0 -61
  512. package/src/crypto/__tests__/scopedSeed.test.ts +0 -126
  513. package/src/crypto/__tests__/signChallengeShared.test.ts +0 -63
  514. package/src/crypto/__tests__/signedRecord.test.ts +0 -230
  515. package/src/i18n/__tests__/accountCategoryLabels.test.ts +0 -62
  516. package/src/i18n/__tests__/accountRoleLabels.test.ts +0 -54
  517. package/src/i18n/__tests__/reputationCategoryLabels.test.ts +0 -56
  518. package/src/i18n/__tests__/trustTierLabels.test.ts +0 -47
  519. package/src/i18n/locales/de-DE.json +0 -306
  520. package/src/i18n/locales/es-ES.json +0 -2127
  521. package/src/i18n/locales/ja-JP.json +0 -306
  522. package/src/inference/__tests__/OxyInferenceClient.test.ts +0 -899
  523. package/src/logger/__tests__/logger.test.ts +0 -207
  524. package/src/mixins/OxyServices.accounts.ts +0 -1330
  525. package/src/mixins/OxyServices.agency.ts +0 -353
  526. package/src/mixins/OxyServices.analytics.ts +0 -53
  527. package/src/mixins/OxyServices.appData.ts +0 -158
  528. package/src/mixins/OxyServices.assets.ts +0 -964
  529. package/src/mixins/OxyServices.auth.ts +0 -2028
  530. package/src/mixins/OxyServices.chains.ts +0 -134
  531. package/src/mixins/OxyServices.civic.ts +0 -945
  532. package/src/mixins/OxyServices.connectedApps.ts +0 -212
  533. package/src/mixins/OxyServices.contacts.ts +0 -73
  534. package/src/mixins/OxyServices.devices.ts +0 -120
  535. package/src/mixins/OxyServices.features.ts +0 -454
  536. package/src/mixins/OxyServices.followGraph.ts +0 -293
  537. package/src/mixins/OxyServices.identity.ts +0 -787
  538. package/src/mixins/OxyServices.identityBackup.ts +0 -250
  539. package/src/mixins/OxyServices.inference.ts +0 -57
  540. package/src/mixins/OxyServices.language.ts +0 -144
  541. package/src/mixins/OxyServices.linkedAccounts.ts +0 -129
  542. package/src/mixins/OxyServices.location.ts +0 -46
  543. package/src/mixins/OxyServices.nodes.ts +0 -337
  544. package/src/mixins/OxyServices.notifications.ts +0 -142
  545. package/src/mixins/OxyServices.payment.ts +0 -163
  546. package/src/mixins/OxyServices.privacy.ts +0 -219
  547. package/src/mixins/OxyServices.reputation.ts +0 -436
  548. package/src/mixins/OxyServices.security.ts +0 -110
  549. package/src/mixins/OxyServices.store.ts +0 -585
  550. package/src/mixins/OxyServices.topics.ts +0 -169
  551. package/src/mixins/OxyServices.user.ts +0 -1208
  552. package/src/mixins/OxyServices.utility.ts +0 -1803
  553. package/src/mixins/__tests__/OxyServices.civic.test.ts +0 -1095
  554. package/src/mixins/__tests__/OxyServices.deviceBoot.test.ts +0 -124
  555. package/src/mixins/__tests__/OxyServices.identity.test.ts +0 -301
  556. package/src/mixins/__tests__/OxyServices.nodes.test.ts +0 -341
  557. package/src/mixins/__tests__/OxyServices.rotateKey.test.ts +0 -323
  558. package/src/mixins/__tests__/OxyServices.serviceAssetMetadata.test.ts +0 -192
  559. package/src/mixins/__tests__/OxyServices.serviceAssetMetadataBySha.test.ts +0 -135
  560. package/src/mixins/__tests__/OxyServices.serviceLinkedDownloadUrl.test.ts +0 -139
  561. package/src/mixins/__tests__/accountEvents.test.ts +0 -158
  562. package/src/mixins/__tests__/accounts.test.ts +0 -727
  563. package/src/mixins/__tests__/agency.test.ts +0 -120
  564. package/src/mixins/__tests__/appData.test.ts +0 -203
  565. package/src/mixins/__tests__/assetCredentials.test.ts +0 -47
  566. package/src/mixins/__tests__/assetUpload.test.ts +0 -191
  567. package/src/mixins/__tests__/authRefusalObservability.test.ts +0 -396
  568. package/src/mixins/__tests__/backgroundCredential.httpIntegration.test.ts +0 -130
  569. package/src/mixins/__tests__/chains.test.ts +0 -113
  570. package/src/mixins/__tests__/commonsSignIn.test.ts +0 -1147
  571. package/src/mixins/__tests__/connectedApps.test.ts +0 -146
  572. package/src/mixins/__tests__/deviceTokenMintPinned.test.ts +0 -113
  573. package/src/mixins/__tests__/discoveryErrorHandling.test.ts +0 -265
  574. package/src/mixins/__tests__/followCacheInvalidation.test.ts +0 -173
  575. package/src/mixins/__tests__/followGraph.test.ts +0 -147
  576. package/src/mixins/__tests__/followGraphPagination.test.ts +0 -271
  577. package/src/mixins/__tests__/getFileDownloadUrl.test.ts +0 -346
  578. package/src/mixins/__tests__/getFollowStatuses.test.ts +0 -95
  579. package/src/mixins/__tests__/getUsersByIds.test.ts +0 -176
  580. package/src/mixins/__tests__/identityBackup.test.ts +0 -315
  581. package/src/mixins/__tests__/identityLink.test.ts +0 -89
  582. package/src/mixins/__tests__/identityWriteCacheInvalidation.test.ts +0 -407
  583. package/src/mixins/__tests__/inferenceFactory.test.ts +0 -58
  584. package/src/mixins/__tests__/linkedAccounts.test.ts +0 -109
  585. package/src/mixins/__tests__/notifications.read.test.ts +0 -82
  586. package/src/mixins/__tests__/onTokensChanged.test.ts +0 -129
  587. package/src/mixins/__tests__/preSessionSkipAuth.test.ts +0 -284
  588. package/src/mixins/__tests__/privacyCacheInvalidation.test.ts +0 -155
  589. package/src/mixins/__tests__/reputation.test.ts +0 -522
  590. package/src/mixins/__tests__/serviceAuth.test.ts +0 -889
  591. package/src/mixins/__tests__/serviceTokenJwks.test.ts +0 -208
  592. package/src/mixins/__tests__/store.test.ts +0 -304
  593. package/src/mixins/__tests__/topics.test.ts +0 -155
  594. package/src/mixins/__tests__/userReadCacheBypass.test.ts +0 -129
  595. package/src/mixins/__tests__/userTokenAuth.test.ts +0 -746
  596. package/src/mixins/__tests__/verifyChallenge.test.ts +0 -161
  597. package/src/mixins/__tests__/verifyServiceActingAs.test.ts +0 -167
  598. package/src/mixins/__tests__/webauthnAuth.test.ts +0 -279
  599. package/src/mixins/index.ts +0 -203
  600. package/src/mixins/mixinHelpers.ts +0 -69
  601. package/src/server/__tests__/auth.test.ts +0 -78
  602. package/src/server/__tests__/capabilityTicket.test.ts +0 -111
  603. package/src/server/__tests__/cors.socket.test.ts +0 -225
  604. package/src/server/__tests__/cors.test.ts +0 -158
  605. package/src/server/__tests__/rateLimit.test.ts +0 -257
  606. package/src/server/__tests__/requesterAssertion.test.ts +0 -359
  607. package/src/server/__tests__/safeFetch.test.ts +0 -232
  608. package/src/server/__tests__/securityHeaders.test.ts +0 -387
  609. package/src/server/__tests__/serviceTokenAttribution.test.ts +0 -396
  610. package/src/server/__tests__/traffic.test.ts +0 -180
  611. package/src/server/__tests__/trafficNodeHttp.test.ts +0 -38
  612. package/src/server/__tests__/userInvalidation.test.ts +0 -203
  613. package/src/server/__tests__/verifySecret.test.ts +0 -40
  614. package/src/server/__tests__/workloadIdentity.client.test.ts +0 -16
  615. package/src/server/__tests__/workloadIdentity.test.ts +0 -128
  616. package/src/session/__tests__/SessionClient.additive.test.ts +0 -153
  617. package/src/session/__tests__/SessionClient.broadcastChannel.test.ts +0 -114
  618. package/src/session/__tests__/SessionClient.diagnostics.test.ts +0 -91
  619. package/src/session/__tests__/SessionClient.directory.test.ts +0 -688
  620. package/src/session/__tests__/SessionClient.httpIntegration.test.ts +0 -65
  621. package/src/session/__tests__/SessionClient.identityPin.test.ts +0 -175
  622. package/src/session/__tests__/SessionClient.rest.test.ts +0 -119
  623. package/src/session/__tests__/SessionClient.serverEvents.test.ts +0 -72
  624. package/src/session/__tests__/SessionClient.socket.test.ts +0 -202
  625. package/src/session/__tests__/SessionClient.socketFactory.test.ts +0 -81
  626. package/src/session/__tests__/SessionClient.state.test.ts +0 -78
  627. package/src/session/__tests__/SessionClient.switchTokenOrder.test.ts +0 -170
  628. package/src/session/__tests__/accountDelegationAccess.test.ts +0 -104
  629. package/src/session/__tests__/accountDialogController.test.ts +0 -2502
  630. package/src/session/__tests__/accountDialogShape.test.ts +0 -120
  631. package/src/session/__tests__/accountSwitchTargets.test.ts +0 -137
  632. package/src/session/__tests__/authStateStore.test.ts +0 -451
  633. package/src/session/__tests__/deviceDirectory.test.ts +0 -422
  634. package/src/session/__tests__/deviceSwitcherRows.test.ts +0 -223
  635. package/src/session/__tests__/identityPin.test.ts +0 -131
  636. package/src/session/__tests__/identitySession.test.ts +0 -194
  637. package/src/session/__tests__/projectSessionState.test.ts +0 -105
  638. package/src/session/__tests__/refresh.identityPin.test.ts +0 -307
  639. package/src/session/__tests__/refresh.test.ts +0 -606
  640. package/src/session/__tests__/sessionIntegration.test.ts +0 -209
  641. package/src/session/__tests__/sharedDeviceCredential.test.ts +0 -300
  642. package/src/utils/__tests__/accountUtils.test.ts +0 -151
  643. package/src/utils/__tests__/activityId.test.ts +0 -45
  644. package/src/utils/__tests__/asyncUtils.test.ts +0 -279
  645. package/src/utils/__tests__/authWebUrl.test.ts +0 -15
  646. package/src/utils/__tests__/cacheKey.test.ts +0 -0
  647. package/src/utils/__tests__/cacheLifecycle.test.ts +0 -65
  648. package/src/utils/__tests__/coldBoot.test.ts +0 -496
  649. package/src/utils/__tests__/commonsApproval.test.ts +0 -60
  650. package/src/utils/__tests__/identityCacheSweep.test.ts +0 -151
  651. package/src/utils/__tests__/languageUtils.test.ts +0 -244
  652. package/src/utils/__tests__/oauthPkce.test.ts +0 -303
  653. package/src/utils/__tests__/officialOrigins.test.ts +0 -31
  654. package/src/utils/__tests__/oxyServiceEnvironment.test.ts +0 -7
  655. package/src/utils/__tests__/profileLinks.test.ts +0 -180
  656. package/src/utils/__tests__/redactUrl.test.ts +0 -33
  657. package/src/utils/__tests__/registrableApex.test.ts +0 -62
  658. package/src/utils/__tests__/requestUtilsAbort.test.ts +0 -155
  659. package/src/utils/__tests__/textNormalization.test.ts +0 -205
  660. package/src/utils/__tests__/validationUtils.test.ts +0 -703
  661. package/src/utils/__tests__/webauthnOrigin.test.ts +0 -83
  662. package/src/utils/webauthnOrigin.ts +0 -52
@@ -0,0 +1,1457 @@
1
+ /**
2
+ * `oxy.auth` — signing in, signing up, and the account's credentials.
3
+ *
4
+ * - Key sign-in: {@link AuthApi.requestChallenge} → sign → {@link AuthApi.verifyChallenge}.
5
+ * - Email: `auth.email` — a 6-digit code and a one-use link; also the code that
6
+ * confirms an email for sign-up.
7
+ * - Password and authenticator: `auth.password`, `auth.totp`; a first factor on
8
+ * an account with an authenticator answers the second-factor step, finished
9
+ * with {@link AuthApi.completeSecondFactor}.
10
+ * - OAuth (a relying party's PKCE code exchange): `auth.oauth`.
11
+ * - "Sign in with Oxy" handoff to Commons (QR, push, deep link): `auth.commons`.
12
+ *
13
+ * Every call that can end in a session attaches THIS client's device proof
14
+ * (`oxy.session.readDeviceProof()`, ADR 0029 D2) unless the caller passes
15
+ * `device` itself (`null` opts out), and a session result plants its access
16
+ * token on `oxy.session`.
17
+ */
18
+ import type { User } from '../models/interfaces';
19
+ import type { CommonsDenyReason, DeviceProof, LoginResult } from '@oxy.so/contracts';
20
+ import {
21
+ emailSignInLinkResponseSchema,
22
+ emailSignInPendingSchema,
23
+ emailSignInStartResponseSchema,
24
+ emailVerificationConfirmResponseSchema,
25
+ emailVerificationStartResponseSchema,
26
+ loginResultSchema,
27
+ safeParseContract,
28
+ secondFactorRequiredSchema,
29
+ signInMethodsSchema,
30
+ totpBackupCodesResponseSchema,
31
+ totpEnrollResponseSchema,
32
+ type EmailSignInPending,
33
+ type EmailSignInStartResponse,
34
+ type EmailVerificationConfirmResponse,
35
+ type EmailVerificationStartRequest,
36
+ type EmailVerificationStartResponse,
37
+ type ReauthAction,
38
+ type ReauthProof,
39
+ type SignInMethods,
40
+ type SignInStepResult,
41
+ type TotpEnrollResponse,
42
+ } from '@oxy.so/contracts';
43
+ import type { OxyContext } from '../client/context';
44
+ import type { SessionLoginResponse } from '../models/session';
45
+ import type { PublicApplication } from './apps';
46
+ import { OxyAuthenticationError } from '../OxyServices.errors';
47
+ import { logger } from '../logger';
48
+ import { normalizeUserIdentity } from '../utils/userIdentity';
49
+
50
+ // The identity key, signatures and the registration proof-of-work load on first
51
+ // use: an app that never signs anything must not ship secp256k1.
52
+ const loadKeyManager = async () => (await import('../crypto/internal')).KeyManager;
53
+ const loadSignatureService = async () => (await import('../crypto/internal')).SignatureService;
54
+
55
+ /**
56
+ * Default lifetime of a "Sign in with Oxy" device-flow session / authorize code.
57
+ * Matches the authorize-code TTL the server enforces (5 minutes). The server's
58
+ * returned `expiresAt` is authoritative; this is only the client-proposed value.
59
+ */
60
+ const COMMONS_SIGN_IN_EXPIRY_MS = 5 * 60 * 1000;
61
+
62
+ /**
63
+ * Fallback access-token lifetime used only if the token endpoint ever omits the
64
+ * RFC 6749 `expires_in` member. Matches the server's current 15-minute access
65
+ * token; the server's value always wins when present.
66
+ */
67
+ const DEFAULT_ACCESS_TOKEN_TTL_SECONDS = 15 * 60;
68
+
69
+ export interface ChallengeResponse {
70
+ challenge: string;
71
+ expiresAt: string;
72
+ }
73
+
74
+ export interface RegistrationRequest {
75
+ publicKey: string;
76
+ username: string;
77
+ email?: string;
78
+ signature: string;
79
+ timestamp: number;
80
+ }
81
+
82
+ export interface ChallengeVerifyRequest {
83
+ publicKey: string;
84
+ challenge: string;
85
+ signature: string;
86
+ timestamp: number;
87
+ deviceName?: string;
88
+ deviceFingerprint?: string;
89
+ }
90
+
91
+ export interface PublicKeyCheckResponse {
92
+ registered: boolean;
93
+ message: string;
94
+ }
95
+
96
+ /** OpenID Connect userinfo claims returned by `GET /auth/oauth/userinfo`. */
97
+ export interface OAuthUserInfoResponse {
98
+ sub: string;
99
+ preferred_username?: string;
100
+ name?: string;
101
+ picture?: string;
102
+ }
103
+
104
+ /**
105
+ * The session an OAuth authorization-code exchange yields.
106
+ *
107
+ * Deliberately NOT `LoginSessionResult`. That type mirrors the API's
108
+ * `buildSessionAuthResponse`, which every FIRST-PARTY sign-in lane emits, and it
109
+ * requires `deviceId` because those lanes always join the origin's DeviceSession.
110
+ * `POST /auth/oauth/token` is the RFC 6749 token endpoint and serves third
111
+ * parties, whose grant is deliberately ISOLATED: an untrusted application must be
112
+ * able to receive a session carrying NO DeviceSession credential at all.
113
+ *
114
+ * Both device fields are therefore optional here, and a response omitting them is
115
+ * a well-formed device-less grant rather than a malformed payload. What that
116
+ * costs the session is spelled out on `auth.oauth.exchangeCode` below.
117
+ */
118
+ export interface OAuthTokenExchangeResult {
119
+ sessionId: string;
120
+ /** ISO-8601 expiry of {@link accessToken}, derived from RFC 6749 `expires_in`. */
121
+ expiresAt: string;
122
+ accessToken?: string;
123
+ /**
124
+ * The DeviceSession this grant joined, when the server issued one. ABSENT for
125
+ * an isolated third-party grant — never assume a string.
126
+ */
127
+ deviceId?: string;
128
+ /**
129
+ * The zero-cookie mint credential for {@link deviceId}. Present only alongside
130
+ * it; absent for an isolated third-party grant.
131
+ */
132
+ deviceSecret?: string;
133
+ user: {
134
+ id: string;
135
+ username?: string;
136
+ avatar?: string;
137
+ };
138
+ }
139
+
140
+ // ===========================================================================
141
+ // "Sign in with Oxy" — cross-device QR / app-to-app handoff (Workstream C)
142
+ // ===========================================================================
143
+
144
+ /**
145
+ * How a "Sign in with Oxy" request finalizes once the approver authorizes it.
146
+ *
147
+ * ONE request (`AuthSession`) serves every delivery surface — popup, push, QR,
148
+ * deep link — so the purpose describes the FINALIZATION, never the transport:
149
+ *
150
+ * - `device_sign_in` — the classic device flow. The initiator exchanges its
151
+ * secret `sessionToken` for the first access token via `auth.claimSession`.
152
+ * - `oauth_authorization` — the request additionally carries an OAuth binding
153
+ * ({@link CommonsOAuthContext}), so it finalizes into a single-use
154
+ * authorization CODE via {@link AuthCommonsApi.finalizeOAuth}.
155
+ * The caller still performs the PKCE token exchange itself.
156
+ */
157
+ export type CommonsSignInPurpose = 'device_sign_in' | 'oauth_authorization';
158
+
159
+ /**
160
+ * OAuth binding attached to a "Sign in with Oxy" request so a single
161
+ * `AuthSession` can finalize into a standard OAuth authorization code instead of
162
+ * a device-flow session.
163
+ *
164
+ * Only the minimum request binding is carried here — everything else (the app's
165
+ * name, icon, registered redirect URIs, trust flags) is owned server-side by the
166
+ * `Application` the `clientId` resolves to and is never client-supplied.
167
+ *
168
+ * The PKCE `codeVerifier` NEVER appears here: only its S256 `codeChallenge`
169
+ * crosses the wire, exactly as in the redirect flow. The RP-owned OAuth `state`
170
+ * also stays with the relying party, which validates it locally.
171
+ */
172
+ export interface CommonsOAuthContext {
173
+ /** Exact registered redirect URI the authorization code will be returned to. */
174
+ redirectUri: string;
175
+ /** PKCE `BASE64URL(SHA-256(codeVerifier))` (RFC 7636 §4.2); the verifier stays client-side. */
176
+ codeChallenge: string;
177
+ /** PKCE transformation method. Always `S256` — `plain` is not accepted. */
178
+ codeChallengeMethod: 'S256';
179
+ /** Space-delimited OAuth scope string; the server normalizes and validates it. */
180
+ scope?: string;
181
+ /**
182
+ * Optional delegated account the application will act AS (an organization or
183
+ * project the identity is a member of). The identity approving the request
184
+ * does not change; the server verifies the identity's permission to act as
185
+ * this account before finalizing.
186
+ */
187
+ subjectAccountId?: string;
188
+ }
189
+
190
+ /**
191
+ * Handle returned by {@link AuthCommonsApi.start} for a
192
+ * relying-party app initiating a "Sign in with Oxy" flow.
193
+ *
194
+ * `sessionToken` is the SECRET, high-entropy device-flow credential — it stays
195
+ * on the initiating client, is exchanged once via `auth.claimSession` (device
196
+ * sign-in) or {@link AuthCommonsApi.finalizeOAuth} (OAuth), and is
197
+ * NEVER placed in the QR/deep-link. `authorizeCode` is the PUBLIC handle carried
198
+ * in `qrPayload`; the approver (Commons) resolves it via
199
+ * {@link AuthCommonsApi.approvalInfo}.
200
+ */
201
+ export interface CommonsSignInHandle {
202
+ /** Secret device-flow token (held by the initiator; exchanged via `auth.claimSession`). */
203
+ sessionToken: string;
204
+ /** Public, single-use authorize code carried in the QR / deep-link. */
205
+ authorizeCode: string;
206
+ /** Ready-to-render deep-link / universal-link string (`oxycommons://approve?...`). */
207
+ qrPayload: string;
208
+ /** Server-authoritative expiry (epoch milliseconds). */
209
+ expiresAt: number;
210
+ /** Session lifecycle status as reported by the server (e.g. `'pending'`). */
211
+ status: string;
212
+ }
213
+
214
+ /**
215
+ * Poll result for a "Sign in with Oxy" device-flow session
216
+ * (`GET /auth/session/status`).
217
+ *
218
+ * The authoritative state machine stays small — `pending → authorized →
219
+ * consumed`, plus `cancelled` / `expired` — and lives in `status`. Delivery
220
+ * PROGRESS (`pushSentAt`, `openedAt`) is carried as timestamps beside it, never
221
+ * as competing statuses, so a progress signal can never be mistaken for an
222
+ * authorization.
223
+ */
224
+ export interface CommonsSignInStatus {
225
+ /** True once an approver has authorized the session. */
226
+ authorized: boolean;
227
+ /** The authorized session id (present once `authorized` for device sign-in). */
228
+ sessionId?: string;
229
+ /** The approving identity's public key (present once `authorized`). */
230
+ publicKey?: string;
231
+ /** Lifecycle status (`'pending'` | `'authorized'` | `'cancelled'` | `'expired'`). */
232
+ status?: string;
233
+ /**
234
+ * How this request finalizes. Unrecognized/missing values degrade to
235
+ * `device_sign_in` so an older API never misroutes an OAuth finalize.
236
+ */
237
+ purpose?: CommonsSignInPurpose;
238
+ /**
239
+ * ISO-8601 timestamp of when the request was pushed to a known Commons
240
+ * installation, or `null` when no push has been sent (including on a server
241
+ * that predates delivery progress). Progress only — it never implies the push
242
+ * was received, opened, or approved.
243
+ */
244
+ pushSentAt: string | null;
245
+ /**
246
+ * ISO-8601 timestamp of when the approval route was opened in Commons, or
247
+ * `null` when it has not been opened. Reported by the approver via
248
+ * {@link AuthCommonsApi.markOpened}; it is an
249
+ * un-authenticated progress hint used only to advance the waiting UI, and is
250
+ * NEVER evidence that the request was approved.
251
+ */
252
+ openedAt: string | null;
253
+ }
254
+
255
+ /**
256
+ * Outcome of asking Oxy to deliver a pending sign-in request to the identity's
257
+ * known Commons installations (`POST /auth/session/deliver/:authorizeCode`).
258
+ *
259
+ * `targets: 0` is a NORMAL outcome, not a failure: it simply means no capable
260
+ * Commons installation is registered, so push is not a usable route and the
261
+ * caller shows the QR instead. Feed `targets` into `selectCommonsDelivery`
262
+ * (`utils/commonsDelivery`) rather than branching on it ad hoc.
263
+ */
264
+ export interface CommonsDeliveryResult {
265
+ /** Whether the server dispatched the request to at least one installation. */
266
+ delivered: boolean;
267
+ /** How many eligible Commons installations it was dispatched to (`0` is normal). */
268
+ targets: number;
269
+ }
270
+
271
+ /**
272
+ * The account an application will act AS once the request is approved, when the
273
+ * request delegates to an organization/project rather than the approver's own
274
+ * personal account. Resolved and sanitized server-side from the request's
275
+ * `subjectAccountId`, so it is safe to display in the approval UI.
276
+ *
277
+ * The identity approving stays the identity: Commons renders this as a distinct
278
+ * "will act as" line, never as a change of who is signing.
279
+ */
280
+ export interface CommonsApprovalSubjectAccount {
281
+ /** The delegated account's id. */
282
+ id: string;
283
+ /** The delegated account's handle. */
284
+ username: string;
285
+ /** Optional human-readable name; absent when the account has no real name. */
286
+ displayName?: string;
287
+ }
288
+
289
+ /**
290
+ * Server-resolved approval context shown by the approver (Commons) before
291
+ * authorizing — the TRUSTED identity of the requesting app, resolved from the
292
+ * `authorizeCode` server-side (never from the QR string).
293
+ */
294
+ export interface CommonsApprovalInfo {
295
+ /** Sanitized, display-safe identity of the requesting application. */
296
+ application: PublicApplication | null;
297
+ /** OAuth scopes the application is requesting. */
298
+ scopes: string[];
299
+ /** The origin the session is bound to (the RP web origin), when applicable. */
300
+ boundOrigin?: string;
301
+ /**
302
+ * Server-authoritative anti-phishing flag: `true` only when this device-flow
303
+ * sign-in was started from a verified, registered origin of a trusted app.
304
+ * The approver (Commons) shows a warning when this is `false`. Always present
305
+ * — a missing/non-boolean server value is coerced to `false` (fail-safe to
306
+ * "not verified") by {@link AuthCommonsApi.approvalInfo}.
307
+ */
308
+ originVerified: boolean;
309
+ /**
310
+ * COARSE, display-only label of the client that STARTED the request
311
+ * (`"Chrome on Windows"`), resolved server-side from the requesting browser —
312
+ * NEVER from the QR payload. Render it verbatim as a secondary line under the
313
+ * origin; it is the whole descriptor the platform has (no raw User-Agent, no
314
+ * IP, no location is ever collected for it).
315
+ *
316
+ * `null` whenever the server has no browser context to describe: native
317
+ * requesters, unidentifiable User-Agents, and any API that predates the field.
318
+ * Omit the line entirely in that case — never substitute a guess.
319
+ */
320
+ requesterLabel: string | null;
321
+ /**
322
+ * How this request finalizes. Always present — an unrecognized or missing
323
+ * server value degrades to `'device_sign_in'`, the behaviour every server has
324
+ * always had, so an older API never makes the approver believe it is granting
325
+ * an OAuth authorization.
326
+ */
327
+ purpose: CommonsSignInPurpose;
328
+ /**
329
+ * The delegated account the application will act as, or `null` when the
330
+ * request is for the approver's own account. Always present — a missing or
331
+ * malformed server value degrades to `null` (fail-safe to "no delegation"),
332
+ * so a partial payload can never imply a broader grant than was requested.
333
+ */
334
+ subjectAccount: CommonsApprovalSubjectAccount | null;
335
+ /** Server-authoritative expiry (epoch ms or ISO-8601 string from the API). */
336
+ expiresAt: number | string;
337
+ /** Session lifecycle status. */
338
+ status: string;
339
+ }
340
+
341
+ /**
342
+ * @internal Raw server response of `GET /auth/session/approve-info/:code`.
343
+ * `originVerified`, `purpose` and `subjectAccount` are typed loosely here
344
+ * because older servers may omit them (or send an unexpected shape); the SDK
345
+ * narrows each one fail-safe when mapping into {@link CommonsApprovalInfo}.
346
+ */
347
+ interface CommonsApprovalInfoResponse {
348
+ application: PublicApplication | null;
349
+ scopes: string[];
350
+ boundOrigin?: string;
351
+ originVerified?: unknown;
352
+ requesterLabel?: unknown;
353
+ purpose?: unknown;
354
+ subjectAccount?: unknown;
355
+ expiresAt: number | string;
356
+ status: string;
357
+ }
358
+
359
+ /**
360
+ * @internal Narrow an untrusted `requesterLabel` from the approve-info response.
361
+ *
362
+ * Returns the trimmed label only when the server sent a real, non-empty string;
363
+ * everything else — absent (an API that predates the field), `null` (a native
364
+ * requester the server could not describe), or a non-string — degrades to
365
+ * `null`, so the approval UI drops the line instead of rendering a blank or a
366
+ * coerced value under the app it is about to authorize.
367
+ */
368
+ function parseCommonsRequesterLabel(value: unknown): string | null {
369
+ if (typeof value !== 'string') {
370
+ return null;
371
+ }
372
+ const label = value.trim();
373
+ return label.length > 0 ? label : null;
374
+ }
375
+
376
+ /**
377
+ * @internal Narrow an untrusted `subjectAccount` from the approve-info response.
378
+ *
379
+ * Returns `null` unless the value is an object carrying a non-empty string `id`
380
+ * AND `username` — the two fields the approval UI needs to name the delegated
381
+ * account. A half-populated object is rejected whole rather than rendered with
382
+ * blanks, and `displayName` is only carried through when it is a string.
383
+ */
384
+ function parseCommonsSubjectAccount(value: unknown): CommonsApprovalSubjectAccount | null {
385
+ if (value === null || typeof value !== 'object') {
386
+ return null;
387
+ }
388
+ const raw = value as Record<string, unknown>;
389
+ const { id, username, displayName } = raw;
390
+ if (typeof id !== 'string' || !id || typeof username !== 'string' || !username) {
391
+ return null;
392
+ }
393
+ return {
394
+ id,
395
+ username,
396
+ ...(typeof displayName === 'string' ? { displayName } : {}),
397
+ };
398
+ }
399
+
400
+ /**
401
+ * @internal Narrow an untrusted delivery-progress timestamp from the status
402
+ * response.
403
+ *
404
+ * Returns the ISO-8601 string unchanged when it is a real, parseable instant,
405
+ * and `null` for everything else — absent (an older API that has no delivery
406
+ * progress at all), empty, non-string, or unparseable. Progress is advisory, so
407
+ * degrading to "no progress yet" is always safe; surfacing a garbage timestamp
408
+ * to the waiting UI is not.
409
+ */
410
+ function parseCommonsProgressTimestamp(value: unknown): string | null {
411
+ if (typeof value !== 'string' || !value) {
412
+ return null;
413
+ }
414
+ return Number.isFinite(Date.parse(value)) ? value : null;
415
+ }
416
+
417
+ /** Result of approving / denying a "Sign in with Oxy" request. */
418
+ export interface CommonsSignInActionResult {
419
+ success: boolean;
420
+ }
421
+
422
+ /**
423
+ * Result of finalizing an approved, OAuth-bound "Sign in with Oxy" request.
424
+ *
425
+ * This is an authorization CODE, not a session: the caller still performs the
426
+ * standard PKCE token exchange (`auth.oauth.exchangeCode`) with the `codeVerifier` it
427
+ * has held all along. No access token, refresh token, or device secret is ever
428
+ * produced by finalization.
429
+ */
430
+ export interface CommonsOAuthFinalizeResult {
431
+ /** Single-use OAuth authorization code. */
432
+ code: string;
433
+ /** The exact registered redirect URI the request was bound to. */
434
+ redirectUri: string;
435
+ /** Lifetime of the authorization code, in seconds. */
436
+ expiresIn: number;
437
+ }
438
+
439
+ /** @internal Response shape of the extended `POST /auth/session/create`. */
440
+ interface CommonsSessionCreateResponse {
441
+ authorizeCode: string;
442
+ qrPayload: string;
443
+ status: string;
444
+ /** Optional server-authoritative expiry; falls back to the client-proposed value. */
445
+ expiresAt?: number;
446
+ /** Optional server echo of the session token (the client-supplied value is authoritative). */
447
+ sessionToken?: string;
448
+ }
449
+
450
+
451
+ /** The device-session fields a sign-in may carry. */
452
+ export interface SignInDeviceOptions {
453
+ deviceName?: string;
454
+ deviceFingerprint?: string;
455
+ /** The device the session joins; defaults to the one this client holds. `null` opts out. */
456
+ device?: DeviceProof | null;
457
+ }
458
+
459
+ /** The session a device-flow claim yields. */
460
+ export interface ClaimedSession {
461
+ accessToken: string;
462
+ sessionId: string;
463
+ deviceId: string;
464
+ expiresAt: string;
465
+ user: User;
466
+ deviceSecret?: string;
467
+ }
468
+
469
+ /** The body fields a session-ending call sends: the options, plus the device proof. */
470
+ async function signInEnvelope(ctx: OxyContext, options: SignInDeviceOptions = {}): Promise<Record<string, unknown>> {
471
+ const { device: explicit, deviceName, deviceFingerprint } = options;
472
+ const device = explicit === undefined ? await ctx.oxy.session.readDeviceProof() : explicit;
473
+ return {
474
+ ...(deviceName ? { deviceName } : {}),
475
+ ...(deviceFingerprint ? { deviceFingerprint } : {}),
476
+ ...(device ? { device } : {}),
477
+ };
478
+ }
479
+
480
+ /** Parse a session, planting its token. */
481
+ function plantSession(ctx: OxyContext, res: unknown, path: string): LoginResult {
482
+ const parsed = safeParseContract(loginResultSchema, res);
483
+ if (!parsed) throw new Error(`${path} returned an unexpected response shape`);
484
+ if (parsed.accessToken) ctx.oxy.session.setAccessToken(parsed.accessToken);
485
+ return parsed;
486
+ }
487
+
488
+ /** Parse a first factor's answer: the second-factor step, or a session. */
489
+ function signInStep(ctx: OxyContext, res: unknown, path: string): SignInStepResult {
490
+ const challenge = safeParseContract(secondFactorRequiredSchema, res);
491
+ if (challenge) return challenge;
492
+ return plantSession(ctx, res, path);
493
+ }
494
+
495
+ /** `oxy.auth.email` — email sign-in (code or link) and email confirmation codes. */
496
+ export class AuthEmailApi {
497
+ constructor(private readonly ctx: OxyContext) {}
498
+
499
+ /**
500
+ * Send a 6-digit code to the email of a new account
501
+ * (`purpose: 'signup'`). The answer is the same whether or not the address
502
+ * already has an account; nothing is revealed about who has one.
503
+ */
504
+ async startVerification(request: EmailVerificationStartRequest): Promise<EmailVerificationStartResponse> {
505
+ const res = await this.ctx.request<unknown>('POST', '/auth/email/verify/start', request, {
506
+ cache: false,
507
+ skipAuth: true,
508
+ });
509
+ const parsed = safeParseContract(emailVerificationStartResponseSchema, res);
510
+ if (!parsed) throw new Error('auth/email/verify/start returned an unexpected response shape');
511
+ return parsed;
512
+ }
513
+
514
+ /**
515
+ * Confirm the code {@link AuthEmailApi.startVerification} sent. Resolves to a
516
+ * short-lived one-use ticket, which a sign-up passes (with the email) to
517
+ * {@link AuthApi.signUp}.
518
+ */
519
+ async confirmVerification(verificationId: string, code: string): Promise<EmailVerificationConfirmResponse> {
520
+ const res = await this.ctx.request<unknown>(
521
+ 'POST',
522
+ '/auth/email/verify/confirm',
523
+ { verificationId, code },
524
+ { cache: false, skipAuth: true },
525
+ );
526
+ const parsed = safeParseContract(emailVerificationConfirmResponseSchema, res);
527
+ if (!parsed) throw new Error('auth/email/verify/confirm returned an unexpected response shape');
528
+ return parsed;
529
+ }
530
+
531
+ /**
532
+ * Send the sign-in email (a code and a link) for a username or email. The
533
+ * answer is the same whether or not the account exists. Keep
534
+ * `requestSecret` in memory only.
535
+ */
536
+ async start(identifier: string, options: { device?: DeviceProof | null } = {}): Promise<EmailSignInStartResponse> {
537
+ const device = options.device === undefined ? await this.ctx.oxy.session.readDeviceProof() : options.device;
538
+ const res = await this.ctx.request<unknown>(
539
+ 'POST',
540
+ '/auth/signin/email/start',
541
+ { identifier, ...(device ? { device } : {}) },
542
+ { cache: false, skipAuth: true },
543
+ );
544
+ const parsed = safeParseContract(emailSignInStartResponseSchema, res);
545
+ if (!parsed) throw new Error('auth/signin/email/start returned an unexpected response shape');
546
+ return parsed;
547
+ }
548
+
549
+ /**
550
+ * The code from the email → a session, or the second-factor step. The code
551
+ * is 6 digits, or the 10-character long code (`XXXXX-XXXXX`) an account
552
+ * gets after too many guesses in a day — accept both in one field and pass
553
+ * it as typed.
554
+ */
555
+ async confirm(
556
+ request: { requestId: string; requestSecret: string; code: string } & SignInDeviceOptions,
557
+ ): Promise<SignInStepResult> {
558
+ const { requestId, requestSecret, code, ...options } = request;
559
+ const res = await this.ctx.request<unknown>(
560
+ 'POST',
561
+ '/auth/signin/email/confirm',
562
+ { requestId, requestSecret, code, ...(await signInEnvelope(this.ctx, options)) },
563
+ { cache: false, skipAuth: true },
564
+ );
565
+ return signInStep(this.ctx, res, 'auth/signin/email/confirm');
566
+ }
567
+
568
+ /**
569
+ * Whether the email's link was opened in this browser: `{ status: 'pending' }`
570
+ * until it was, then a session (or the second-factor step). Poll it.
571
+ */
572
+ async collect(
573
+ request: { requestId: string; requestSecret: string } & SignInDeviceOptions,
574
+ ): Promise<SignInStepResult | EmailSignInPending> {
575
+ const { requestId, requestSecret, ...options } = request;
576
+ const res = await this.ctx.request<unknown>(
577
+ 'POST',
578
+ '/auth/signin/email/collect',
579
+ { requestId, requestSecret, ...(await signInEnvelope(this.ctx, options)) },
580
+ { cache: false, skipAuth: true },
581
+ );
582
+ const pending = safeParseContract(emailSignInPendingSchema, res);
583
+ if (pending) return pending;
584
+ return signInStep(this.ctx, res, 'auth/signin/email/collect');
585
+ }
586
+
587
+ /**
588
+ * auth.oxy.so's link page: approve the request with THIS client's device
589
+ * proof. It approves only in the browser that asked, and never returns a
590
+ * session here — the app's dialog collects it.
591
+ */
592
+ async approveLink(token: string, device?: DeviceProof): Promise<{ approved: true }> {
593
+ const proof = device ?? (await this.ctx.oxy.session.readDeviceProof());
594
+ if (!proof) throw new Error('This browser holds no Oxy device to approve the sign-in with');
595
+ const res = await this.ctx.request<unknown>(
596
+ 'POST',
597
+ '/auth/signin/email/link',
598
+ { token, device: proof },
599
+ { cache: false, skipAuth: true },
600
+ );
601
+ const parsed = safeParseContract(emailSignInLinkResponseSchema, res);
602
+ if (!parsed) throw new Error('auth/signin/email/link returned an unexpected response shape');
603
+ return parsed;
604
+ }
605
+
606
+ }
607
+
608
+ /** `oxy.auth.password` — password sign-in and setting the password. */
609
+ export class AuthPasswordApi {
610
+ constructor(private readonly ctx: OxyContext) {}
611
+
612
+ async signIn(request: { identifier: string; password: string } & SignInDeviceOptions): Promise<SignInStepResult> {
613
+ const { identifier, password, ...options } = request;
614
+ const res = await this.ctx.request<unknown>(
615
+ 'POST',
616
+ '/auth/signin/password',
617
+ { identifier, password, ...(await signInEnvelope(this.ctx, options)) },
618
+ { cache: false, skipAuth: true },
619
+ );
620
+ return signInStep(this.ctx, res, 'auth/signin/password');
621
+ }
622
+
623
+ async set(request: { newPassword: string; reauth: ReauthProof; revokeOtherSessions?: boolean }): Promise<{ success: true }> {
624
+ return this.ctx.request<{ success: true }>('PUT', '/users/me/password', request, { cache: false });
625
+ }
626
+
627
+ }
628
+
629
+ /** `oxy.auth.totp` — the account’s authenticator app. */
630
+ export class AuthTotpApi {
631
+ constructor(private readonly ctx: OxyContext) {}
632
+
633
+ async enroll(): Promise<TotpEnrollResponse> {
634
+ const res = await this.ctx.request<unknown>('POST', '/users/me/totp/enroll', undefined, { cache: false });
635
+ const parsed = safeParseContract(totpEnrollResponseSchema, res);
636
+ if (!parsed) throw new Error('users/me/totp/enroll returned an unexpected response shape');
637
+ return parsed;
638
+ }
639
+
640
+ async confirm(code: string, reauth: ReauthProof): Promise<string[]> {
641
+ const res = await this.ctx.request<unknown>('POST', '/users/me/totp/confirm', { code, reauth }, { cache: false });
642
+ const parsed = safeParseContract(totpBackupCodesResponseSchema, res);
643
+ if (!parsed) throw new Error('users/me/totp/confirm returned an unexpected response shape');
644
+ return parsed.backupCodes;
645
+ }
646
+
647
+ async disable(reauth: ReauthProof): Promise<{ success: true }> {
648
+ return this.ctx.request<{ success: true }>('POST', '/users/me/totp/disable', { reauth }, { cache: false });
649
+ }
650
+
651
+ async regenerateBackupCodes(reauth: ReauthProof): Promise<string[]> {
652
+ const res = await this.ctx.request<unknown>('POST', '/users/me/totp/backup-codes', { reauth }, { cache: false });
653
+ const parsed = safeParseContract(totpBackupCodesResponseSchema, res);
654
+ if (!parsed) throw new Error('users/me/totp/backup-codes returned an unexpected response shape');
655
+ return parsed.backupCodes;
656
+ }
657
+
658
+ }
659
+
660
+ /** `oxy.auth.oauth` — a relying party’s OAuth authorization-code exchange. */
661
+ export class AuthOAuthApi {
662
+ constructor(private readonly ctx: OxyContext) {}
663
+
664
+ /**
665
+ * Exchange an OAuth authorization code (returned to the RP redirect URI
666
+ * after sign-in at auth.oxy.so) for a device-first session.
667
+ * Public first-party clients use PKCE (`codeVerifier`); the access token is
668
+ * planted immediately on success.
669
+ *
670
+ * Speaks the standard RFC 6749 §4.1.3 token request — a form-urlencoded
671
+ * body with snake_case parameters and `grant_type=authorization_code` — and
672
+ * reads the flat §5.1 response. The camelCase JSON request and `{ data }`
673
+ * response this method used before were an Oxy invention no OAuth library
674
+ * could interoperate with; the endpoint no longer accepts them. The method's
675
+ * OWN signature is unchanged, so callers are unaffected.
676
+ *
677
+ * `deviceId` + `deviceSecret` are OPTIONAL and their absence is a valid
678
+ * outcome, not an error. A third-party grant is meant to be isolated from the
679
+ * browser's shared DeviceSession, so the token endpoint must be free to return
680
+ * no device credential at all — the guard that used to require the pair made
681
+ * that omission unshippable, since it turned every third-party sign-in through
682
+ * the SDK into a silent `exchange-failed`.
683
+ *
684
+ * The cost is real and deliberate: a DEVICE-LESS session cannot use the
685
+ * zero-cookie mint lane (`POST /session/device/token`), because that lane's
686
+ * whole proof is possession of a `deviceSecret`. Its lifetime is therefore the
687
+ * access token itself — nothing persists a restore credential, the cold boot's
688
+ * `device-secret-mint` step reports `no-secret` and skips, and the refresh
689
+ * scheduler has nothing to re-mint from. When the token expires the session
690
+ * ends LOUDLY: the 401 lane clears the tokens and the provider resolves signed
691
+ * out, so the app can run the OAuth flow again. It never degrades into a
692
+ * session that looks alive and cannot refresh.
693
+ */
694
+ async exchangeCode(params: {
695
+ code: string;
696
+ clientId: string;
697
+ redirectUri: string;
698
+ codeVerifier: string;
699
+ }): Promise<OAuthTokenExchangeResult> {
700
+ const form = new URLSearchParams({
701
+ grant_type: 'authorization_code',
702
+ code: params.code,
703
+ redirect_uri: params.redirectUri,
704
+ client_id: params.clientId,
705
+ code_verifier: params.codeVerifier,
706
+ });
707
+ const res = await this.ctx.request<unknown>(
708
+ 'POST',
709
+ '/auth/oauth/token',
710
+ form,
711
+ { cache: false, skipAuth: true },
712
+ );
713
+ if (!res || typeof res !== 'object') {
714
+ throw new Error('auth/oauth/token returned an unexpected response shape');
715
+ }
716
+ // RFC 6749 §5.1: every member sits at the TOP LEVEL of the document.
717
+ const record = res as Record<string, unknown>;
718
+ const accessToken = typeof record.access_token === 'string' ? record.access_token : undefined;
719
+ const sessionId = typeof record.session_id === 'string' ? record.session_id : undefined;
720
+ const deviceId = typeof record.deviceId === 'string' ? record.deviceId : undefined;
721
+ const deviceSecret = typeof record.deviceSecret === 'string' ? record.deviceSecret : undefined;
722
+ const userRaw = record.user;
723
+ // The device pair is NOT part of this guard — see the note above. What is
724
+ // still mandatory is what identifies the session at all.
725
+ if (!sessionId || !userRaw || typeof userRaw !== 'object') {
726
+ throw new Error('auth/oauth/token returned an incomplete session payload');
727
+ }
728
+ const userObj = userRaw as Record<string, unknown>;
729
+ const userId = typeof userObj.id === 'string' ? userObj.id : undefined;
730
+ if (!userId) {
731
+ throw new Error('auth/oauth/token returned a session without user.id');
732
+ }
733
+ const expiresInSec =
734
+ typeof record.expires_in === 'number' ? record.expires_in : DEFAULT_ACCESS_TOKEN_TTL_SECONDS;
735
+ const expiresAt = new Date(Date.now() + expiresInSec * 1000).toISOString();
736
+ if (accessToken) {
737
+ this.ctx.oxy.session.setAccessToken(accessToken);
738
+ }
739
+ if (!deviceId || !deviceSecret) {
740
+ logger.debug(
741
+ 'auth/oauth/token returned no device credential — this session lives only as long as its access token',
742
+ { component: 'oxy.auth', method: 'exchangeOAuthCode' },
743
+ );
744
+ }
745
+ return {
746
+ sessionId,
747
+ expiresAt,
748
+ accessToken,
749
+ // Omitted rather than set to `undefined` when the server sent no device
750
+ // credential, so a device-less grant serializes as the absence it is.
751
+ ...(deviceId ? { deviceId } : {}),
752
+ ...(deviceSecret ? { deviceSecret } : {}),
753
+ user: {
754
+ id: userId,
755
+ username: typeof userObj.username === 'string' ? userObj.username : undefined,
756
+ avatar: typeof userObj.avatar === 'string' ? userObj.avatar : undefined,
757
+ },
758
+ };
759
+ }
760
+
761
+ /**
762
+ * Fetch OpenID Connect userinfo for the current bearer (`GET /auth/oauth/userinfo`).
763
+ * The response is a flat JSON document — no `{ data }` wrapper.
764
+ */
765
+ async userInfo(): Promise<OAuthUserInfoResponse> {
766
+ const res = await this.ctx.request<unknown>(
767
+ 'GET',
768
+ '/auth/oauth/userinfo',
769
+ undefined,
770
+ { cache: false },
771
+ );
772
+ if (!res || typeof res !== 'object') {
773
+ throw new Error('auth/oauth/userinfo returned an unexpected response shape');
774
+ }
775
+ const record = res as Record<string, unknown>;
776
+ const sub = typeof record.sub === 'string' ? record.sub : undefined;
777
+ if (!sub) {
778
+ throw new Error('auth/oauth/userinfo returned a response without sub');
779
+ }
780
+ return {
781
+ sub,
782
+ ...(typeof record.preferred_username === 'string'
783
+ ? { preferred_username: record.preferred_username }
784
+ : {}),
785
+ ...(typeof record.name === 'string' ? { name: record.name } : {}),
786
+ ...(typeof record.picture === 'string' ? { picture: record.picture } : {}),
787
+ };
788
+ }
789
+
790
+ }
791
+
792
+ /**
793
+ * `oxy.auth.commons` — "Sign in with Oxy": a relying party hands a sign-in to
794
+ * Commons (QR, push, deep link, popup), and Commons approves or denies it.
795
+ *
796
+ * Relying party: {@link AuthCommonsApi.start} → {@link AuthCommonsApi.poll}
797
+ * (or the auth socket) → finalize with `oxy.auth.claimSession` (device sign-in)
798
+ * or {@link AuthCommonsApi.finalizeOAuth} (OAuth). Approver (Commons):
799
+ * {@link AuthCommonsApi.approvalInfo} → {@link AuthCommonsApi.approve} /
800
+ * {@link AuthCommonsApi.deny}. The approver signs with its PRIMARY local key;
801
+ * the relying party never sees the private key.
802
+ */
803
+ export class AuthCommonsApi {
804
+ constructor(private readonly ctx: OxyContext) {}
805
+
806
+ /**
807
+ * MECHANISM B (relying party) — begin a "Sign in with Oxy" handoff.
808
+ *
809
+ * Generates a secret device-flow `sessionToken` client-side (it never
810
+ * appears in the QR), registers it with `POST /auth/session/create`, and
811
+ * returns the server-issued public `authorizeCode` + ready-to-render
812
+ * `qrPayload`. Render the QR (web) / open the deep-link (same-device); the
813
+ * approver resolves the code and authorizes. Then poll with
814
+ * {@link AuthCommonsApi.poll} and finalize.
815
+ *
816
+ * ONE request serves every delivery surface, and how it finalizes is decided
817
+ * here by whether an OAuth binding is attached:
818
+ * - no `oauth` (the default): a `device_sign_in` request — on `authorized`,
819
+ * exchange the `sessionToken` via the existing `auth.claimSession`.
820
+ * - with `oauth`: an `oauth_authorization` request — on `authorized`, call
821
+ * {@link AuthCommonsApi.finalizeOAuth} with the same `sessionToken` to mint the
822
+ * single-use authorization code, then exchange it with PKCE.
823
+ *
824
+ * @param params.clientId - The RP's registered OAuth client id
825
+ * (ApplicationCredential publicKey); required so the server can resolve the
826
+ * requesting application's identity.
827
+ * @param params.oauth - Optional OAuth binding ({@link CommonsOAuthContext}).
828
+ * Carries only the redirect URI, the PKCE S256 challenge, the requested
829
+ * scope, and an optional delegated `subjectAccountId` — never the PKCE
830
+ * verifier, the OAuth `state`, or any token.
831
+ */
832
+ async start(params: {
833
+ clientId: string;
834
+ oauth?: CommonsOAuthContext;
835
+ }): Promise<CommonsSignInHandle> {
836
+ // High-entropy opaque secret token (256-bit hex). Generated client-side
837
+ // and held only here; the server stores it but never returns it in the
838
+ // QR. Reuses the platform-safe random generator.
839
+ const sessionToken = await (await loadSignatureService()).generateChallenge();
840
+ const expiresAt = Date.now() + COMMONS_SIGN_IN_EXPIRY_MS;
841
+
842
+ const res = await this.ctx.request<CommonsSessionCreateResponse>(
843
+ 'POST',
844
+ '/auth/session/create',
845
+ {
846
+ sessionToken,
847
+ expiresAt,
848
+ clientId: params.clientId,
849
+ // Omitted entirely when absent so the device-sign-in body stays
850
+ // exactly what it has always been (the server reads presence, not a
851
+ // null, to decide the request's purpose).
852
+ ...(params.oauth ? { oauth: params.oauth } : {}),
853
+ },
854
+ // Public/pre-session (no bearer): skip the preflight so a stale
855
+ // near-expiry token cannot re-enter refreshAccessToken while the
856
+ // refresh handler is already in flight (self-await hang).
857
+ { cache: false, skipAuth: true },
858
+ );
859
+
860
+ return {
861
+ sessionToken,
862
+ authorizeCode: res.authorizeCode,
863
+ qrPayload: res.qrPayload,
864
+ expiresAt: res.expiresAt ?? expiresAt,
865
+ status: res.status,
866
+ };
867
+ }
868
+
869
+ /**
870
+ * MECHANISM B (relying party) — poll a device-flow session for approval.
871
+ *
872
+ * Backstop for the auth socket. On `authorized` (with a `sessionId`), the
873
+ * caller finalizes: `auth.claimSession` for a `device_sign_in` request,
874
+ * {@link AuthCommonsApi.finalizeOAuth} for an `oauth_authorization` one.
875
+ *
876
+ * Every field is narrowed fail-safe. `authorized` counts only as a literal
877
+ * `true`, the identifiers only as non-empty strings, and the delivery
878
+ * progress timestamps degrade to `null` when absent or unparseable — so a
879
+ * partial or older-API payload can advance the waiting UI at most, never
880
+ * make it believe a request was approved.
881
+ *
882
+ * @param sessionToken - The secret token from {@link AuthCommonsApi.start}.
883
+ */
884
+ async poll(sessionToken: string): Promise<CommonsSignInStatus> {
885
+ const res = await this.ctx.request<unknown>(
886
+ 'GET',
887
+ `/auth/session/status/${encodeURIComponent(sessionToken)}`,
888
+ undefined,
889
+ // Public/pre-session (no bearer): a preflight here is wrong per se and,
890
+ // if ever reached while a refresh is pending, would re-enter
891
+ // refreshAccessToken and await the very promise it runs inside.
892
+ { cache: false, retry: false, skipAuth: true }
893
+ );
894
+
895
+ if (res === null || typeof res !== 'object') {
896
+ throw new Error('auth/session/status returned an unexpected response shape');
897
+ }
898
+ const { authorized, sessionId, publicKey, status, purpose, pushSentAt, openedAt } =
899
+ res as Record<string, unknown>;
900
+
901
+ return {
902
+ authorized: authorized === true,
903
+ ...(typeof sessionId === 'string' && sessionId ? { sessionId } : {}),
904
+ ...(typeof publicKey === 'string' && publicKey ? { publicKey } : {}),
905
+ ...(typeof status === 'string' && status ? { status } : {}),
906
+ purpose: purpose === 'oauth_authorization' ? 'oauth_authorization' : 'device_sign_in',
907
+ pushSentAt: parseCommonsProgressTimestamp(pushSentAt),
908
+ openedAt: parseCommonsProgressTimestamp(openedAt),
909
+ };
910
+ }
911
+
912
+ /**
913
+ * MECHANISM B (relying party) — ask Oxy to DELIVER a pending sign-in request
914
+ * to the identity's known Commons installations.
915
+ *
916
+ * This is the automatic half of "one intention, one primary action": rather
917
+ * than offering the user a menu of transports, the caller asks for delivery
918
+ * and lets the answer pick the route. Pass the returned `targets` to
919
+ * `selectCommonsDelivery` (`utils/commonsDelivery`) — `targets: 0` means no
920
+ * capable Commons installation is registered, which is a NORMAL outcome that
921
+ * resolves to the QR route, not an error to surface.
922
+ *
923
+ * **Requires a bearer.** Delivery is only allowed when Oxy already knows the
924
+ * intended identity from a trusted authenticated context — a request that
925
+ * merely carries a username or email typed into an unauthenticated browser
926
+ * must never be able to ring somebody's phone.
927
+ *
928
+ * The push it sends carries only `{ type, approvalUrl }` where the URL holds
929
+ * the public `authorizeCode` — no display data, no secrets. Commons resolves
930
+ * everything it shows from `auth.commons.approvalInfo`.
931
+ *
932
+ * @param authorizeCode - The public code from {@link AuthCommonsApi.start}.
933
+ */
934
+ async deliver(authorizeCode: string): Promise<CommonsDeliveryResult> {
935
+ const res = await this.ctx.request<unknown>(
936
+ 'POST',
937
+ `/auth/session/deliver/${encodeURIComponent(authorizeCode)}`,
938
+ undefined,
939
+ // Bearer REQUIRED (unlike its public siblings): the identity to
940
+ // deliver to comes from the authenticated caller, never the code.
941
+ { cache: false },
942
+ );
943
+
944
+ if (res === null || typeof res !== 'object') {
945
+ throw new Error('auth/session/deliver returned an unexpected response shape');
946
+ }
947
+ const { delivered, targets } = res as Record<string, unknown>;
948
+ // Both fields drive the route choice, so a partial payload is rejected
949
+ // outright rather than defaulted into a route the server never chose.
950
+ if (
951
+ typeof delivered !== 'boolean' ||
952
+ typeof targets !== 'number' ||
953
+ !Number.isInteger(targets) ||
954
+ targets < 0
955
+ ) {
956
+ throw new Error('auth/session/deliver returned an incomplete delivery result');
957
+ }
958
+
959
+ return { delivered, targets };
960
+ }
961
+
962
+ /**
963
+ * MECHANISM B (approver / Commons) — report that the approval route was
964
+ * OPENED, so the waiting relying party can show "Opened in Commons".
965
+ *
966
+ * Progress only. It is idempotent, applies to a `pending` request alone, and
967
+ * records a timestamp (`openedAt`) — it never approves, authorizes, or
968
+ * advances the authorization state machine. Public, like the other approver
969
+ * handles: the approver has only the public `authorizeCode` at this point
970
+ * and has not yet signed anything.
971
+ *
972
+ * Best-effort by nature — a failure here costs the user only a progress
973
+ * line, so callers are free to ignore a rejection and continue to the
974
+ * approval screen.
975
+ *
976
+ * @param authorizeCode - The public code scanned from the QR / deep-link / push.
977
+ */
978
+ async markOpened(authorizeCode: string): Promise<void> {
979
+ await this.ctx.request<unknown>(
980
+ 'POST',
981
+ `/auth/session/opened/${encodeURIComponent(authorizeCode)}`,
982
+ undefined,
983
+ // Public (no bearer) — skip the preflight, exactly like approve-info.
984
+ { cache: false, skipAuth: true },
985
+ );
986
+ }
987
+
988
+ /**
989
+ * MECHANISM B (relying party) — finalize an APPROVED, OAuth-bound request
990
+ * into a single-use OAuth authorization code.
991
+ *
992
+ * The OAuth counterpart of `auth.claimSession`: same secret credential,
993
+ * same single-use semantics, different output. Call it once the request the
994
+ * RP started with an `oauth` binding reports `authorized`; the server
995
+ * atomically mints exactly ONE `AuthCode` bound to the redirect URI, PKCE
996
+ * challenge, scopes, approving identity, and any delegated subject account
997
+ * the request was created with. A second call cannot mint another code.
998
+ *
999
+ * The result is an authorization CODE, never a token — the caller completes
1000
+ * the flow with the ordinary PKCE exchange (`auth.oauth.exchangeCode`) using the
1001
+ * `codeVerifier` it never sent anywhere. Nothing here is exposed to the
1002
+ * popup: the code travels back through the registered callback, and the
1003
+ * main window owns the verifier.
1004
+ *
1005
+ * Like `auth.claimSession`, this needs no Authorization header — the
1006
+ * high-entropy SECRET `sessionToken` IS the credential. Never pass the
1007
+ * public `authorizeCode` here; it is the approver's handle, not the
1008
+ * initiator's. Every server-side failure (wrong/expired/already-finalized
1009
+ * request, non-OAuth purpose, missing permission for the delegated account)
1010
+ * surfaces as one generic error, so nothing about the request's state can be
1011
+ * probed from outside.
1012
+ *
1013
+ * @param sessionToken - The secret token from {@link AuthCommonsApi.start}.
1014
+ */
1015
+ async finalizeOAuth(sessionToken: string): Promise<CommonsOAuthFinalizeResult> {
1016
+ const res = await this.ctx.request<unknown>(
1017
+ 'POST',
1018
+ `/auth/session/finalize/${encodeURIComponent(sessionToken)}`,
1019
+ undefined,
1020
+ // Body-authenticated by the path's secret token (no bearer) — skip the
1021
+ // preflight, exactly like the device-flow claim.
1022
+ { cache: false, skipAuth: true },
1023
+ );
1024
+
1025
+ if (res === null || typeof res !== 'object') {
1026
+ throw new Error('auth/session/finalize returned an unexpected response shape');
1027
+ }
1028
+ const { code, redirectUri, expiresIn } = res as Record<string, unknown>;
1029
+ // All three fields are load-bearing for the exchange that follows, so a
1030
+ // partial payload is rejected outright rather than returned half-parsed.
1031
+ if (
1032
+ typeof code !== 'string' ||
1033
+ !code ||
1034
+ typeof redirectUri !== 'string' ||
1035
+ !redirectUri ||
1036
+ typeof expiresIn !== 'number' ||
1037
+ !Number.isFinite(expiresIn)
1038
+ ) {
1039
+ throw new Error('auth/session/finalize returned an incomplete authorization code');
1040
+ }
1041
+
1042
+ return { code, redirectUri, expiresIn };
1043
+ }
1044
+
1045
+ /**
1046
+ * MECHANISM B (approver / Commons) — resolve the TRUSTED identity of a
1047
+ * sign-in request from its public `authorizeCode`.
1048
+ *
1049
+ * The returned `application` and `subjectAccount` are resolved server-side
1050
+ * and are the only safe things to display in the approval UI — NEVER trust
1051
+ * the app/name/origin strings carried in the QR payload. Public (no auth
1052
+ * required).
1053
+ *
1054
+ * @param authorizeCode - The public code scanned from the QR / deep-link.
1055
+ */
1056
+ async approvalInfo(authorizeCode: string): Promise<CommonsApprovalInfo> {
1057
+ const raw = await this.ctx.request<CommonsApprovalInfoResponse>(
1058
+ 'GET',
1059
+ `/auth/session/approve-info/${encodeURIComponent(authorizeCode)}`,
1060
+ undefined,
1061
+ // Public (no auth required) — skip the bearer preflight (avoids the
1062
+ // pre-session self-await class).
1063
+ { cache: false, skipAuth: true }
1064
+ );
1065
+ return {
1066
+ application: raw.application,
1067
+ scopes: raw.scopes,
1068
+ boundOrigin: raw.boundOrigin,
1069
+ // Fail-safe: only a literal boolean `true` counts as verified. A
1070
+ // missing or non-boolean value (older server, malformed response)
1071
+ // coerces to `false` so a stale server can never imply trust.
1072
+ originVerified: raw.originVerified === true,
1073
+ // Same discipline: a missing/blank/non-string label degrades to null
1074
+ // (an older API, or a native requester with no browser to describe),
1075
+ // and the approver simply omits the "where from" line.
1076
+ requesterLabel: parseCommonsRequesterLabel(raw.requesterLabel),
1077
+ // Same discipline: only the literal OAuth purpose opts into OAuth
1078
+ // finalization. Anything else — including a server that predates this
1079
+ // field — is the plain device sign-in it has always been.
1080
+ purpose: raw.purpose === 'oauth_authorization' ? 'oauth_authorization' : 'device_sign_in',
1081
+ // A missing/partial delegated account degrades to "no delegation"
1082
+ // rather than a half-rendered "will act as" line.
1083
+ subjectAccount: parseCommonsSubjectAccount(raw.subjectAccount),
1084
+ expiresAt: raw.expiresAt,
1085
+ status: raw.status,
1086
+ };
1087
+ }
1088
+
1089
+ /**
1090
+ * MECHANISM B (approver / Commons) — approve a sign-in request by signing a
1091
+ * fresh challenge with the PRIMARY local identity key.
1092
+ *
1093
+ * Commons holds the user's identity as its primary key (not the shared
1094
+ * key), so this uses `signChallenge`. The signed-but-cookieless authorize
1095
+ * endpoint resolves the user from the verified signer — the RP that started
1096
+ * the flow then claims its session. Native-only (requires a local identity).
1097
+ *
1098
+ * @param params.authorizeCode - The public code being approved.
1099
+ * @param params.deviceName - Optional human-readable device label.
1100
+ * @param params.deviceFingerprint - Optional device fingerprint.
1101
+ */
1102
+ async approve(params: {
1103
+ authorizeCode: string;
1104
+ deviceName?: string;
1105
+ deviceFingerprint?: string;
1106
+ }): Promise<CommonsSignInActionResult> {
1107
+ const publicKey = await (await loadKeyManager()).getPublicKey();
1108
+ if (!publicKey) {
1109
+ throw new Error('No identity found on this device. Create or import an identity first.');
1110
+ }
1111
+
1112
+ const { challenge } = await this.ctx.oxy.auth.requestChallenge(publicKey);
1113
+ const signed = await (await loadSignatureService()).signChallenge(challenge);
1114
+
1115
+ return this.ctx.request<CommonsSignInActionResult>(
1116
+ 'POST',
1117
+ `/auth/session/authorize-signed/${encodeURIComponent(params.authorizeCode)}`,
1118
+ {
1119
+ // `signed.challenge` carries the SIGNATURE; `challenge` is the
1120
+ // original server-issued challenge string.
1121
+ publicKey: signed.publicKey,
1122
+ challenge,
1123
+ signature: signed.challenge,
1124
+ timestamp: signed.timestamp,
1125
+ ...(params.deviceName ? { deviceName: params.deviceName } : {}),
1126
+ ...(params.deviceFingerprint ? { deviceFingerprint: params.deviceFingerprint } : {}),
1127
+ },
1128
+ // Key-signed, cookieless (no bearer) — skip the preflight.
1129
+ { cache: false, skipAuth: true }
1130
+ );
1131
+ }
1132
+
1133
+ /**
1134
+ * MECHANISM B (approver / Commons) — deny a sign-in request, cancelling the
1135
+ * device-flow session so the RP stops waiting.
1136
+ *
1137
+ * @param authorizeCode - The public code being denied.
1138
+ * @param reason - Optional closed-set reason ({@link CommonsDenyReason}).
1139
+ * Pass `'not_me'` ONLY when the user actually reported the request as one
1140
+ * they did not start — the server records it as a suspicious denial rather
1141
+ * than an ordinary cancel. Omitting it sends the exact body this endpoint
1142
+ * has always received.
1143
+ */
1144
+ async deny(
1145
+ authorizeCode: string,
1146
+ reason?: CommonsDenyReason,
1147
+ ): Promise<CommonsSignInActionResult> {
1148
+ return this.ctx.request<CommonsSignInActionResult>(
1149
+ 'POST',
1150
+ `/auth/session/deny/${encodeURIComponent(authorizeCode)}`,
1151
+ reason ? { reason } : undefined,
1152
+ // Public (no auth required) — skip the bearer preflight.
1153
+ { cache: false, skipAuth: true }
1154
+ );
1155
+ }
1156
+
1157
+ }
1158
+
1159
+ export class AuthApi {
1160
+ /** Email sign-in and email confirmation codes. */
1161
+ readonly email: AuthEmailApi;
1162
+ /** Password sign-in and setting the password. */
1163
+ readonly password: AuthPasswordApi;
1164
+ /** The account's authenticator app. */
1165
+ readonly totp: AuthTotpApi;
1166
+ /** OAuth authorization-code exchange. */
1167
+ readonly oauth: AuthOAuthApi;
1168
+ /** "Sign in with Oxy" handoff to Commons. */
1169
+ readonly commons: AuthCommonsApi;
1170
+
1171
+ constructor(private readonly ctx: OxyContext) {
1172
+ this.email = new AuthEmailApi(ctx);
1173
+ this.password = new AuthPasswordApi(ctx);
1174
+ this.totp = new AuthTotpApi(ctx);
1175
+ this.oauth = new AuthOAuthApi(ctx);
1176
+ this.commons = new AuthCommonsApi(ctx);
1177
+ }
1178
+
1179
+ /**
1180
+ * Register a new identity with public key authentication
1181
+ * Identity is purely cryptographic - username and profile data are optional
1182
+ *
1183
+ * @param publicKey - The user's ECDSA public key (hex)
1184
+ * @param signature - Signature of the registration request
1185
+ * @param timestamp - Timestamp when the signature was created
1186
+ */
1187
+ async registerKey(
1188
+ publicKey: string,
1189
+ signature: string,
1190
+ timestamp: number
1191
+ ): Promise<{ message: string; user: User }> {
1192
+ // Advisory for now (server soft-enforces — see
1193
+ // `SessionController.register`), but solved unconditionally so every
1194
+ // client is already sending it once the server starts requiring it.
1195
+ const { solveRegistrationPow } = await import('../crypto/internal');
1196
+ const powNonce = await solveRegistrationPow(publicKey, timestamp);
1197
+
1198
+ const res = await this.ctx.request<{ message: string; user: User }>('POST', '/auth/register', {
1199
+ publicKey,
1200
+ signature,
1201
+ timestamp,
1202
+ powNonce,
1203
+ }, { cache: false, skipAuth: true });
1204
+
1205
+ if (!res || (typeof res === 'object' && Object.keys(res).length === 0)) {
1206
+ throw new OxyAuthenticationError('Registration failed', 'REGISTER_FAILED', 400);
1207
+ }
1208
+
1209
+ return res;
1210
+ }
1211
+
1212
+ /**
1213
+ * Request an authentication challenge
1214
+ * The client must sign this challenge with their private key
1215
+ *
1216
+ * @param publicKey - The user's public key
1217
+ * @param requestOptions - Optional per-call transport overrides (`retry`,
1218
+ * `timeout`). Interactive callers omit it (defaults keep retries); the
1219
+ * cold-boot `shared-key-signin` step passes `{ retry: false }` so a slow
1220
+ * network cannot multiply boot latency via the inner retry loop.
1221
+ */
1222
+ async requestChallenge(
1223
+ publicKey: string,
1224
+ requestOptions?: { retry?: boolean; timeout?: number },
1225
+ ): Promise<ChallengeResponse> {
1226
+ return this.ctx.request<ChallengeResponse>('POST', '/auth/challenge', {
1227
+ publicKey,
1228
+ }, { cache: false, skipAuth: true, ...requestOptions });
1229
+ }
1230
+
1231
+ /**
1232
+ * Verify a signed challenge and create a session
1233
+ *
1234
+ * @param publicKey - The user's public key
1235
+ * @param challenge - The challenge string from requestChallenge
1236
+ * @param signature - Signature of the auth message
1237
+ * @param timestamp - Timestamp when the signature was created
1238
+ * @param deviceName - Optional device name
1239
+ * @param deviceFingerprint - Optional device fingerprint
1240
+ * @param requestOptions - Optional per-call transport overrides (`retry`,
1241
+ * `timeout`). Interactive callers omit it (defaults keep retries); the
1242
+ * cold-boot `shared-key-signin` step passes `{ retry: false }` so a slow
1243
+ * network cannot multiply boot latency via the inner retry loop.
1244
+ * @param options.plantTokens - Install the returned bearer on this client
1245
+ * (default `true`). A caller that must decide LATER whether the session is
1246
+ * still wanted (the account dialog, whose user may have cancelled while the
1247
+ * request was in flight) passes `false` and plants it itself.
1248
+ */
1249
+ async verifyChallenge(
1250
+ publicKey: string,
1251
+ challenge: string,
1252
+ signature: string,
1253
+ timestamp: number,
1254
+ deviceName?: string,
1255
+ deviceFingerprint?: string,
1256
+ requestOptions?: { retry?: boolean; timeout?: number },
1257
+ options: { plantTokens?: boolean } = {},
1258
+ ): Promise<SessionLoginResponse> {
1259
+ const res = await this.ctx.request<SessionLoginResponse>('POST', '/auth/verify', {
1260
+ publicKey,
1261
+ challenge,
1262
+ signature,
1263
+ timestamp,
1264
+ deviceName,
1265
+ deviceFingerprint,
1266
+ }, { cache: false, skipAuth: true, ...requestOptions });
1267
+
1268
+ // Plant the freshly-minted tokens, mirroring `auth.claimSession`.
1269
+ // `/auth/verify` returns the first access token (and refresh token) in
1270
+ // its body, so installing it here means callers get an authenticated
1271
+ // client without a second round-trip. Refresh stays in the httpOnly
1272
+ // cookie slot set by the API.
1273
+ if (res?.accessToken && options.plantTokens !== false) {
1274
+ this.ctx.oxy.session.setAccessToken(res.accessToken);
1275
+ }
1276
+
1277
+ return {
1278
+ ...res,
1279
+ user: normalizeUserIdentity(res.user),
1280
+ };
1281
+ }
1282
+
1283
+ /**
1284
+ * Check if a public key is already registered
1285
+ */
1286
+ async isKeyRegistered(publicKey: string): Promise<PublicKeyCheckResponse> {
1287
+ return this.ctx.request<PublicKeyCheckResponse>(
1288
+ 'GET',
1289
+ `/auth/check-publickey/${encodeURIComponent(publicKey)}`,
1290
+ undefined,
1291
+ { cache: false, skipAuth: true }
1292
+ );
1293
+ }
1294
+
1295
+ /**
1296
+ * Exchange a device-flow sessionToken for the first access token.
1297
+ *
1298
+ * The originating client holds a 128-bit `sessionToken` that nobody
1299
+ * else has seen — it was generated client-side, sent once on
1300
+ * `POST /auth/session/create`, and is never echoed back. After
1301
+ * another authenticated device approves the session via
1302
+ * `POST /auth/session/authorize/{sessionToken}` (bearer-authed) and
1303
+ * the auth socket / poll loop notifies this client, the client
1304
+ * exchanges its `sessionToken` here for the first access token,
1305
+ * refresh token, sessionId, and the authorized user.
1306
+ *
1307
+ * This call requires no Authorization header — the high-entropy
1308
+ * `sessionToken` IS the credential (RFC 8628 §3.4). The exchange is
1309
+ * single-use; replay attempts are rejected with 401.
1310
+ *
1311
+ * @param sessionToken - The same sessionToken the SDK passed to
1312
+ * `POST /auth/session/create` at the start of the flow.
1313
+ * @param options.deviceFingerprint - Optional fingerprint of the
1314
+ * originating client device.
1315
+ * @param options.plantTokens - Install the claimed bearer on this client
1316
+ * (default `true`). Pass `false` to install it yourself once you know the
1317
+ * session is still wanted — see `verifyChallenge`.
1318
+ */
1319
+ async claimSession(
1320
+ sessionToken: string,
1321
+ options: { deviceFingerprint?: string; plantTokens?: boolean; device?: DeviceProof | null } = {}
1322
+ ): Promise<ClaimedSession> {
1323
+ // The device this client holds, so an official app's claim joins it
1324
+ // (ADR 0029 D2). `null` opts out explicitly.
1325
+ const device = options.device === undefined ? await this.ctx.oxy.session.readDeviceProof() : options.device;
1326
+ const res = await this.ctx.request<ClaimedSession>(
1327
+ 'POST',
1328
+ '/auth/session/claim',
1329
+ {
1330
+ sessionToken,
1331
+ ...(options.deviceFingerprint ? { deviceFingerprint: options.deviceFingerprint } : {}),
1332
+ ...(device ? { device } : {}),
1333
+ },
1334
+ // Body-authenticated device-flow claim (no bearer) — skip the preflight.
1335
+ { cache: false, retry: false, skipAuth: true }
1336
+ );
1337
+
1338
+ if (options.plantTokens !== false) {
1339
+ this.ctx.oxy.session.setAccessToken(res.accessToken);
1340
+ }
1341
+
1342
+ return res;
1343
+ }
1344
+
1345
+ /**
1346
+ * MECHANISM A — same-device shared-keychain SSO.
1347
+ *
1348
+ * Native-only. If this device holds a shared identity (the cross-app
1349
+ * `group.so.oxy.shared` keychain key), prove control of it and mint a
1350
+ * session: `requestChallenge(sharedPublicKey)` → `signChallengeWithSharedKey`
1351
+ * → `verifyChallenge` (which plants the tokens). Returns `null` on web or
1352
+ * when no shared identity is present — never throws for the absent-identity
1353
+ * case, so a cold-boot caller can fall through to the next step.
1354
+ *
1355
+ * The cold-boot wiring that CALLS this lives in `OxyContext`
1356
+ * (`@oxy.so/services`); this method just performs the exchange.
1357
+ *
1358
+ * @param opts.requestOptions - Optional per-call transport overrides
1359
+ * (`retry`, `timeout`) forwarded to BOTH the `requestChallenge` and
1360
+ * `verifyChallenge` round-trips. Interactive flows omit it (defaults keep
1361
+ * retries); the cold-boot `shared-key-signin` step passes `{ retry: false }`
1362
+ * so this network step cannot multiply boot latency via the inner retry
1363
+ * loop. The token-refresh scheduler / 401 lane still retry later.
1364
+ * @param opts.plantTokens - Forwarded to `verifyChallenge` (default `true`).
1365
+ */
1366
+ async signInWithSharedIdentity(
1367
+ opts: {
1368
+ deviceName?: string;
1369
+ deviceFingerprint?: string;
1370
+ requestOptions?: { retry?: boolean; timeout?: number };
1371
+ plantTokens?: boolean;
1372
+ } = {}
1373
+ ): Promise<SessionLoginResponse | null> {
1374
+ // `hasSharedIdentity()` already returns false on web (the shared
1375
+ // keychain is native-only), so this short-circuits the web case without
1376
+ // a wasted challenge round-trip.
1377
+ const KeyManager = await loadKeyManager();
1378
+ if (!(await KeyManager.hasSharedIdentity())) {
1379
+ return null;
1380
+ }
1381
+ const sharedPublicKey = await KeyManager.getSharedPublicKey();
1382
+ if (!sharedPublicKey) {
1383
+ return null;
1384
+ }
1385
+
1386
+ const { challenge } = await this.requestChallenge(sharedPublicKey, opts.requestOptions);
1387
+ const signed = await (await loadSignatureService()).signChallengeWithSharedKey(challenge);
1388
+
1389
+ // `signed.challenge` carries the SIGNATURE (mirrors `signChallenge`).
1390
+ return await this.verifyChallenge(
1391
+ signed.publicKey,
1392
+ challenge,
1393
+ signed.challenge,
1394
+ signed.timestamp,
1395
+ opts.deviceName,
1396
+ opts.deviceFingerprint,
1397
+ opts.requestOptions,
1398
+ { plantTokens: opts.plantTokens },
1399
+ );
1400
+ }
1401
+
1402
+ /**
1403
+ * Check username availability
1404
+ */
1405
+ async checkUsername(username: string): Promise<{ available: boolean; message: string }> {
1406
+ // Public availability lookup (pre-session) — skip the bearer preflight.
1407
+ return this.ctx.request('GET', `/auth/check-username/${username}`, undefined, { cache: false, skipAuth: true });
1408
+ }
1409
+
1410
+ /**
1411
+ * The authenticator's code (or a backup code) for a first factor's
1412
+ * challenge → the session. Send the same device proof the first factor did
1413
+ * (the default does).
1414
+ */
1415
+ async completeSecondFactor(request: { challengeId: string; code: string } & SignInDeviceOptions): Promise<LoginResult> {
1416
+ const { challengeId, code, ...options } = request;
1417
+ const res = await this.ctx.request<unknown>(
1418
+ 'POST',
1419
+ '/auth/signin/second-factor',
1420
+ { challengeId, code, ...(await signInEnvelope(this.ctx, options)) },
1421
+ { cache: false, skipAuth: true },
1422
+ );
1423
+ return plantSession(this.ctx, res, 'auth/signin/second-factor');
1424
+ }
1425
+
1426
+ async signUp(request: { username: string; email: string; emailTicket: string } & SignInDeviceOptions): Promise<LoginResult> {
1427
+ const { username, email, emailTicket, ...options } = request;
1428
+ const res = await this.ctx.request<unknown>(
1429
+ 'POST',
1430
+ '/auth/signup',
1431
+ { username, email, emailTicket, ...(await signInEnvelope(this.ctx, options)) },
1432
+ { cache: false, skipAuth: true },
1433
+ );
1434
+ return plantSession(this.ctx, res, 'auth/signup');
1435
+ }
1436
+
1437
+ async methods(): Promise<SignInMethods> {
1438
+ const res = await this.ctx.request<unknown>('GET', '/users/me/sign-in-methods', undefined, { cache: false });
1439
+ const parsed = safeParseContract(signInMethodsSchema, res);
1440
+ if (!parsed) throw new Error('users/me/sign-in-methods returned an unexpected response shape');
1441
+ return parsed;
1442
+ }
1443
+
1444
+ /**
1445
+ * Send a confirmation code to the signed-in account's email, for a
1446
+ * `reauth: { emailCode: { verificationId, code } }` proof of ONE change:
1447
+ * the code works only for the `action` it was asked for, and the email
1448
+ * names it.
1449
+ */
1450
+ async requestReauthCode(action: ReauthAction): Promise<EmailVerificationStartResponse> {
1451
+ const res = await this.ctx.request<unknown>('POST', '/users/me/reauth/email', { action }, { cache: false });
1452
+ const parsed = safeParseContract(emailVerificationStartResponseSchema, res);
1453
+ if (!parsed) throw new Error('users/me/reauth/email returned an unexpected response shape');
1454
+ return parsed;
1455
+ }
1456
+
1457
+ }