@oxy.so/core 1.18.1 → 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.
- package/README.md +13 -7
- package/dist/cjs/HttpService.js +159 -111
- package/dist/cjs/OxyServices.errors.js +48 -1
- package/dist/cjs/OxyServices.js +263 -60
- package/dist/cjs/api/accounts.js +231 -0
- package/dist/cjs/api/agency.js +102 -0
- package/dist/cjs/api/appData.js +45 -0
- package/dist/cjs/api/apps.js +197 -0
- package/dist/cjs/api/assets.js +329 -0
- package/dist/cjs/api/auth.js +900 -0
- package/dist/cjs/api/billing.js +44 -0
- package/dist/cjs/api/civic.js +289 -0
- package/dist/cjs/api/contacts.js +16 -0
- package/dist/cjs/api/devices.js +351 -0
- package/dist/cjs/api/follows.js +240 -0
- package/dist/cjs/api/identity.js +569 -0
- package/dist/cjs/api/linkedAccounts.js +48 -0
- package/dist/cjs/api/nodes.js +75 -0
- package/dist/cjs/api/notifications.js +75 -0
- package/dist/cjs/api/privacy.js +105 -0
- package/dist/cjs/api/reputation.js +74 -0
- package/dist/cjs/api/session.js +177 -0
- package/dist/cjs/api/store.js +199 -0
- package/dist/cjs/api/topics.js +66 -0
- package/dist/cjs/api/users.js +309 -0
- package/dist/cjs/boot/sessionColdBoot.js +4 -4
- package/dist/cjs/civic/index.js +16 -0
- package/dist/cjs/civic/payloads.js +156 -0
- package/dist/cjs/client/context.js +2 -0
- package/dist/cjs/crypto/identityLink.js +5 -5
- package/dist/cjs/crypto/index.js +44 -0
- package/dist/cjs/crypto/internal.js +37 -0
- package/dist/cjs/crypto/keyManager.js +2 -2
- package/dist/cjs/crypto/polyfill.js +9 -80
- package/dist/cjs/crypto/recoveryPhrase.js +1 -0
- package/dist/cjs/crypto/registrationPow.js +1 -1
- package/dist/cjs/crypto/signatureService.js +1 -0
- package/dist/cjs/i18n/accountCategoryLabels.js +3 -3
- package/dist/cjs/i18n/accountRoleLabels.js +7 -7
- package/dist/cjs/i18n/index.js +130 -52
- package/dist/cjs/i18n/locales/{ar-SA.json → ar-SA.js} +134 -52
- package/dist/cjs/i18n/locales/{ca-ES.json → ca-ES.js} +134 -52
- package/dist/{esm/i18n/locales/de-DE.json → cjs/i18n/locales/de-DE.js} +136 -54
- package/dist/{esm/i18n/locales/en-US.json → cjs/i18n/locales/en-US.js} +163 -245
- package/dist/{esm/i18n/locales/es-ES.json → cjs/i18n/locales/es-ES.js} +163 -245
- package/dist/cjs/i18n/locales/{fr-FR.json → fr-FR.js} +134 -52
- package/dist/cjs/i18n/locales/{it-IT.json → it-IT.js} +134 -52
- package/dist/{esm/i18n/locales/ja-JP.json → cjs/i18n/locales/ja-JP.js} +134 -52
- package/dist/cjs/i18n/locales/{ko-KR.json → ko-KR.js} +134 -52
- package/dist/{esm/i18n/locales/pt-PT.json → cjs/i18n/locales/pt-PT.js} +136 -54
- package/dist/{esm/i18n/locales/zh-CN.json → cjs/i18n/locales/zh-CN.js} +135 -53
- package/dist/cjs/i18n/reputationCategoryLabels.js +2 -2
- package/dist/cjs/i18n/trustTierLabels.js +2 -2
- package/dist/cjs/index.js +55 -263
- package/dist/cjs/inference/index.js +21 -0
- package/dist/cjs/server/OxyServer.js +521 -0
- package/dist/cjs/server/auth.js +2 -2
- package/dist/cjs/server/index.js +23 -6
- package/dist/cjs/server/middleware.js +1154 -0
- package/dist/cjs/server/namespaces.js +214 -0
- package/dist/cjs/server/rateLimit.js +2 -2
- package/dist/cjs/server/requesterAssertion.js +5 -5
- package/dist/cjs/server/securityHeaders.js +3 -3
- package/dist/cjs/server/traffic.js +7 -6
- package/dist/cjs/server/trafficNodeHttp.js +9 -3
- package/dist/cjs/server/userInvalidation.js +5 -5
- package/dist/cjs/session/accountDialogController.js +23 -23
- package/dist/cjs/session/authStateStore.js +2 -2
- package/dist/cjs/session/identitySession.js +3 -3
- package/dist/cjs/session/index.js +131 -0
- package/dist/cjs/session/refresh.js +37 -30
- package/dist/cjs/session/sessionClientHost.js +5 -5
- package/dist/cjs/session/sharedDeviceCredential.js +3 -2
- package/dist/cjs/shared/utils/networkUtils.js +2 -2
- package/dist/cjs/utils/accountCacheSweep.js +11 -15
- package/dist/cjs/utils/asyncUtils.js +22 -163
- package/dist/cjs/utils/authHelpers.js +2 -2
- package/dist/cjs/utils/authWebUrl.js +3 -4
- package/dist/cjs/utils/avatarUtils.js +2 -2
- package/dist/cjs/utils/cache.js +32 -2
- package/dist/cjs/utils/cacheKey.js +5 -10
- package/dist/cjs/utils/deviceManager.js +17 -16
- package/dist/cjs/utils/edgeRegion.js +2 -3
- package/dist/cjs/utils/identityCacheSweep.js +6 -10
- package/dist/cjs/utils/oxyServiceEnvironment.js +3 -7
- package/dist/cjs/utils/requestUtils.js +40 -33
- package/dist/cjs/utils/sessionUtils.js +0 -2
- package/dist/cjs/utils/tokenClaims.js +34 -0
- package/dist/esm/HttpService.js +160 -112
- package/dist/esm/OxyServices.errors.js +45 -0
- package/dist/esm/OxyServices.js +262 -57
- package/dist/esm/api/accounts.js +218 -0
- package/dist/esm/api/agency.js +95 -0
- package/dist/esm/api/appData.js +40 -0
- package/dist/esm/api/apps.js +191 -0
- package/dist/esm/api/assets.js +324 -0
- package/dist/esm/api/auth.js +858 -0
- package/dist/esm/api/billing.js +40 -0
- package/dist/esm/api/civic.js +250 -0
- package/dist/esm/api/contacts.js +12 -0
- package/dist/esm/api/devices.js +346 -0
- package/dist/esm/api/follows.js +236 -0
- package/dist/esm/api/identity.js +527 -0
- package/dist/esm/api/linkedAccounts.js +44 -0
- package/dist/esm/api/nodes.js +71 -0
- package/dist/esm/api/notifications.js +71 -0
- package/dist/esm/api/privacy.js +101 -0
- package/dist/esm/api/reputation.js +70 -0
- package/dist/esm/api/session.js +173 -0
- package/dist/esm/api/store.js +192 -0
- package/dist/esm/api/topics.js +62 -0
- package/dist/esm/api/users.js +272 -0
- package/dist/esm/boot/sessionColdBoot.js +4 -4
- package/dist/esm/civic/index.js +10 -0
- package/dist/esm/civic/payloads.js +118 -0
- package/dist/esm/client/context.js +1 -0
- package/dist/esm/crypto/identityLink.js +5 -5
- package/dist/esm/crypto/index.js +24 -0
- package/dist/esm/crypto/internal.js +19 -0
- package/dist/esm/crypto/keyManager.js +2 -2
- package/dist/esm/crypto/polyfill.js +10 -81
- package/dist/esm/crypto/recoveryPhrase.js +1 -0
- package/dist/esm/crypto/registrationPow.js +1 -1
- package/dist/esm/crypto/signatureService.js +1 -0
- package/dist/esm/i18n/accountCategoryLabels.js +2 -2
- package/dist/esm/i18n/accountRoleLabels.js +1 -1
- package/dist/esm/i18n/index.js +93 -52
- package/dist/esm/i18n/locales/{ar-SA.json → ar-SA.js} +132 -52
- package/dist/esm/i18n/locales/{ca-ES.json → ca-ES.js} +132 -52
- package/dist/{cjs/i18n/locales/de-DE.json → esm/i18n/locales/de-DE.js} +134 -54
- package/dist/{cjs/i18n/locales/en-US.json → esm/i18n/locales/en-US.js} +161 -245
- package/dist/{cjs/i18n/locales/es-ES.json → esm/i18n/locales/es-ES.js} +161 -245
- package/dist/esm/i18n/locales/{fr-FR.json → fr-FR.js} +132 -52
- package/dist/esm/i18n/locales/{it-IT.json → it-IT.js} +132 -52
- package/dist/{cjs/i18n/locales/ja-JP.json → esm/i18n/locales/ja-JP.js} +132 -52
- package/dist/esm/i18n/locales/{ko-KR.json → ko-KR.js} +132 -52
- package/dist/{cjs/i18n/locales/pt-PT.json → esm/i18n/locales/pt-PT.js} +134 -54
- package/dist/{cjs/i18n/locales/zh-CN.json → esm/i18n/locales/zh-CN.js} +133 -53
- package/dist/esm/i18n/reputationCategoryLabels.js +1 -1
- package/dist/esm/i18n/trustTierLabels.js +1 -1
- package/dist/esm/index.js +30 -166
- package/dist/esm/inference/index.js +13 -0
- package/dist/esm/server/OxyServer.js +516 -0
- package/dist/esm/server/auth.js +2 -2
- package/dist/esm/server/index.js +10 -5
- package/dist/esm/server/middleware.js +1147 -0
- package/dist/esm/server/namespaces.js +206 -0
- package/dist/esm/server/rateLimit.js +2 -2
- package/dist/esm/server/requesterAssertion.js +5 -5
- package/dist/esm/server/securityHeaders.js +3 -3
- package/dist/esm/server/traffic.js +7 -6
- package/dist/esm/server/trafficNodeHttp.js +9 -3
- package/dist/esm/server/userInvalidation.js +5 -5
- package/dist/esm/session/accountDialogController.js +23 -23
- package/dist/esm/session/authStateStore.js +2 -2
- package/dist/esm/session/identitySession.js +3 -3
- package/dist/esm/session/index.js +82 -0
- package/dist/esm/session/refresh.js +37 -30
- package/dist/esm/session/sessionClientHost.js +5 -5
- package/dist/esm/session/sharedDeviceCredential.js +3 -2
- package/dist/esm/shared/utils/networkUtils.js +2 -2
- package/dist/esm/utils/accountCacheSweep.js +11 -15
- package/dist/esm/utils/asyncUtils.js +22 -151
- package/dist/esm/utils/authHelpers.js +2 -2
- package/dist/esm/utils/authWebUrl.js +3 -4
- package/dist/esm/utils/avatarUtils.js +2 -2
- package/dist/esm/utils/cache.js +32 -2
- package/dist/esm/utils/cacheKey.js +5 -10
- package/dist/esm/utils/deviceManager.js +17 -16
- package/dist/esm/utils/edgeRegion.js +1 -2
- package/dist/esm/utils/identityCacheSweep.js +6 -10
- package/dist/esm/utils/oxyServiceEnvironment.js +3 -7
- package/dist/esm/utils/requestUtils.js +40 -33
- package/dist/esm/utils/sessionUtils.js +0 -2
- package/dist/esm/utils/tokenClaims.js +31 -0
- package/dist/types/HttpService.d.ts +26 -9
- package/dist/types/OxyServices.d.ts +160 -131
- package/dist/types/OxyServices.errors.d.ts +42 -0
- package/dist/types/api/accounts.d.ts +415 -0
- package/dist/types/api/agency.d.ts +135 -0
- package/dist/types/api/appData.d.ts +27 -0
- package/dist/types/api/apps.d.ts +433 -0
- package/dist/types/api/assets.d.ts +174 -0
- package/dist/types/api/auth.d.ts +766 -0
- package/dist/types/api/billing.d.ts +113 -0
- package/dist/types/api/civic.d.ts +203 -0
- package/dist/types/api/contacts.d.ts +31 -0
- package/{src/mixins/OxyServices.deviceBoot.ts → dist/types/api/devices.d.ts} +130 -110
- package/dist/types/api/follows.d.ts +236 -0
- package/dist/types/api/identity.d.ts +275 -0
- package/dist/types/api/linkedAccounts.d.ts +48 -0
- package/dist/types/api/nodes.d.ts +102 -0
- package/dist/types/api/notifications.d.ts +77 -0
- package/dist/types/api/privacy.d.ts +43 -0
- package/dist/types/api/reputation.d.ts +50 -0
- package/dist/types/api/session.d.ts +85 -0
- package/dist/types/api/store.d.ts +278 -0
- package/dist/types/api/topics.d.ts +34 -0
- package/dist/types/api/users.d.ts +134 -0
- package/dist/types/boot/sessionColdBoot.d.ts +1 -1
- package/dist/types/civic/index.d.ts +10 -0
- package/dist/types/civic/payloads.d.ts +83 -0
- package/dist/types/client/context.d.ts +33 -0
- package/dist/types/crypto/identityLink.d.ts +5 -5
- package/dist/types/crypto/index.d.ts +24 -0
- package/dist/types/crypto/internal.d.ts +19 -0
- package/dist/types/crypto/polyfill.d.ts +6 -9
- package/dist/types/crypto/registrationPow.d.ts +1 -1
- package/dist/types/i18n/accountCategoryLabels.d.ts +1 -1
- package/dist/types/i18n/accountRoleLabels.d.ts +1 -1
- package/dist/types/i18n/index.d.ts +20 -1
- package/dist/types/i18n/locales/ar-SA.d.ts +386 -0
- package/dist/types/i18n/locales/ca-ES.d.ts +386 -0
- package/dist/types/i18n/locales/de-DE.d.ts +386 -0
- package/dist/types/i18n/locales/en-US.d.ts +2034 -0
- package/dist/types/i18n/locales/es-ES.d.ts +2034 -0
- package/dist/types/i18n/locales/fr-FR.d.ts +386 -0
- package/dist/types/i18n/locales/it-IT.d.ts +386 -0
- package/dist/types/i18n/locales/ja-JP.d.ts +386 -0
- package/dist/types/i18n/locales/ko-KR.d.ts +386 -0
- package/dist/types/i18n/locales/pt-PT.d.ts +386 -0
- package/dist/types/i18n/locales/zh-CN.d.ts +386 -0
- package/dist/types/index.d.ts +46 -95
- package/dist/types/inference/index.d.ts +22 -0
- package/dist/types/models/interfaces.d.ts +1 -22
- package/dist/types/server/OxyServer.d.ts +183 -0
- package/dist/types/server/auth.d.ts +10 -7
- package/dist/types/server/index.d.ts +10 -4
- package/dist/types/server/middleware.d.ts +291 -0
- package/dist/types/server/namespaces.d.ts +98 -0
- package/dist/types/server/rateLimit.d.ts +7 -5
- package/dist/types/server/requesterAssertion.d.ts +14 -11
- package/dist/types/server/securityHeaders.d.ts +4 -5
- package/dist/types/server/userInvalidation.d.ts +10 -8
- package/dist/types/session/accountDelegationAccess.d.ts +1 -1
- package/dist/types/session/accountDialogController.d.ts +8 -8
- package/dist/types/session/accountSwitchTargets.d.ts +1 -1
- package/dist/types/session/authStateStore.d.ts +7 -6
- package/dist/types/session/index.d.ts +40 -0
- package/dist/types/utils/accountCacheSweep.d.ts +7 -10
- package/dist/types/utils/apiUtils.d.ts +3 -3
- package/dist/types/utils/asyncUtils.d.ts +11 -58
- package/dist/types/utils/authWebUrl.d.ts +3 -4
- package/dist/types/utils/avatarUtils.d.ts +5 -6
- package/dist/types/utils/cache.d.ts +11 -3
- package/dist/types/utils/commonsApproval.d.ts +1 -1
- package/dist/types/utils/edgeRegion.d.ts +1 -2
- package/dist/types/utils/identityCacheSweep.d.ts +6 -4
- package/dist/types/utils/oauthPkce.d.ts +2 -2
- package/dist/types/utils/oxyServiceEnvironment.d.ts +3 -7
- package/dist/types/utils/requestUtils.d.ts +18 -14
- package/dist/types/utils/tokenClaims.d.ts +9 -0
- package/package.json +68 -7
- package/src/HttpService.ts +166 -131
- package/src/OxyServices.errors.ts +73 -0
- package/src/OxyServices.ts +289 -164
- package/src/api/accounts.ts +579 -0
- package/src/api/agency.ts +277 -0
- package/src/api/appData.ts +63 -0
- package/src/api/apps.ts +596 -0
- package/src/api/assets.ts +416 -0
- package/src/api/auth.ts +1457 -0
- package/src/api/billing.ts +144 -0
- package/src/api/civic.ts +428 -0
- package/src/api/contacts.ts +41 -0
- package/src/api/devices.ts +438 -0
- package/src/api/follows.ts +414 -0
- package/src/api/identity.ts +727 -0
- package/src/api/linkedAccounts.ts +78 -0
- package/src/api/nodes.ts +169 -0
- package/src/api/notifications.ts +135 -0
- package/src/api/privacy.ts +120 -0
- package/src/api/reputation.ts +134 -0
- package/src/api/session.ts +210 -0
- package/src/api/store.ts +435 -0
- package/src/api/topics.ts +83 -0
- package/src/api/users.ts +357 -0
- package/src/boot/sessionColdBoot.ts +4 -4
- package/src/civic/index.ts +12 -0
- package/src/civic/payloads.ts +175 -0
- package/src/client/context.ts +34 -0
- package/src/crypto/identityLink.ts +5 -5
- package/src/crypto/index.ts +39 -0
- package/src/crypto/internal.ts +20 -0
- package/src/crypto/keyManager.ts +2 -2
- package/src/crypto/polyfill.ts +9 -89
- package/src/crypto/recoveryPhrase.ts +1 -0
- package/src/crypto/registrationPow.ts +1 -1
- package/src/crypto/signatureService.ts +1 -0
- package/src/i18n/accountCategoryLabels.ts +2 -2
- package/src/i18n/accountRoleLabels.ts +2 -2
- package/src/i18n/index.ts +105 -54
- package/src/i18n/locales/{ar-SA.json → ar-SA.ts} +132 -52
- package/src/i18n/locales/{ca-ES.json → ca-ES.ts} +132 -52
- package/{dist/cjs/i18n/locales/locales/de-DE.json → src/i18n/locales/de-DE.ts} +134 -54
- package/src/i18n/locales/{en-US.json → en-US.ts} +161 -245
- package/{dist/cjs/i18n/locales/locales/es-ES.json → src/i18n/locales/es-ES.ts} +161 -245
- package/src/i18n/locales/{fr-FR.json → fr-FR.ts} +132 -52
- package/src/i18n/locales/{it-IT.json → it-IT.ts} +132 -52
- package/{dist/cjs/i18n/locales/locales/ja-JP.json → src/i18n/locales/ja-JP.ts} +132 -52
- package/src/i18n/locales/{ko-KR.json → ko-KR.ts} +132 -52
- package/src/i18n/locales/{pt-PT.json → pt-PT.ts} +134 -54
- package/src/i18n/locales/{zh-CN.json → zh-CN.ts} +133 -53
- package/src/i18n/reputationCategoryLabels.ts +1 -1
- package/src/i18n/trustTierLabels.ts +1 -1
- package/src/index.ts +170 -501
- package/src/inference/index.ts +48 -0
- package/src/models/interfaces.ts +1 -22
- package/src/server/OxyServer.ts +637 -0
- package/src/server/auth.ts +12 -8
- package/src/server/index.ts +28 -5
- package/src/server/middleware.ts +1520 -0
- package/src/server/namespaces.ts +257 -0
- package/src/server/rateLimit.ts +10 -9
- package/src/server/requesterAssertion.ts +13 -10
- package/src/server/securityHeaders.ts +7 -8
- package/src/server/traffic.ts +6 -6
- package/src/server/trafficNodeHttp.ts +7 -3
- package/src/server/userInvalidation.ts +9 -9
- package/src/session/accountDelegationAccess.ts +1 -1
- package/src/session/accountDialogController.ts +24 -24
- package/src/session/accountSwitchTargets.ts +1 -1
- package/src/session/authStateStore.ts +9 -8
- package/src/session/identitySession.ts +3 -3
- package/src/session/index.ts +210 -0
- package/src/session/refresh.ts +36 -30
- package/src/session/sessionClientHost.ts +5 -5
- package/src/session/sharedDeviceCredential.ts +3 -2
- package/src/shared/utils/networkUtils.ts +2 -2
- package/src/utils/accountCacheSweep.ts +11 -15
- package/src/utils/apiUtils.ts +3 -3
- package/src/utils/asyncUtils.ts +38 -235
- package/src/utils/authHelpers.ts +2 -2
- package/src/utils/authWebUrl.ts +3 -4
- package/src/utils/avatarUtils.ts +4 -7
- package/src/utils/cache.ts +36 -5
- package/src/utils/cacheKey.ts +5 -9
- package/src/utils/commonsApproval.ts +1 -1
- package/src/utils/deviceManager.ts +17 -16
- package/src/utils/edgeRegion.ts +1 -2
- package/src/utils/identityCacheSweep.ts +7 -12
- package/src/utils/oauthPkce.ts +2 -2
- package/src/utils/oxyServiceEnvironment.ts +3 -7
- package/src/utils/requestUtils.ts +48 -44
- package/src/utils/sessionUtils.ts +0 -2
- package/src/utils/tokenClaims.ts +38 -0
- package/dist/cjs/.tsbuildinfo +0 -1
- package/dist/cjs/OxyServices.base.js +0 -392
- package/dist/cjs/i18n/locales/locales/ar-SA.json +0 -306
- package/dist/cjs/i18n/locales/locales/ca-ES.json +0 -306
- package/dist/cjs/i18n/locales/locales/en-US.json +0 -2127
- package/dist/cjs/i18n/locales/locales/fr-FR.json +0 -306
- package/dist/cjs/i18n/locales/locales/it-IT.json +0 -306
- package/dist/cjs/i18n/locales/locales/ko-KR.json +0 -306
- package/dist/cjs/i18n/locales/locales/pt-PT.json +0 -306
- package/dist/cjs/i18n/locales/locales/zh-CN.json +0 -306
- package/dist/cjs/mixins/OxyServices.accounts.js +0 -555
- package/dist/cjs/mixins/OxyServices.agency.js +0 -154
- package/dist/cjs/mixins/OxyServices.analytics.js +0 -49
- package/dist/cjs/mixins/OxyServices.appData.js +0 -108
- package/dist/cjs/mixins/OxyServices.assets.js +0 -854
- package/dist/cjs/mixins/OxyServices.auth.js +0 -1495
- package/dist/cjs/mixins/OxyServices.chains.js +0 -73
- package/dist/cjs/mixins/OxyServices.civic.js +0 -611
- package/dist/cjs/mixins/OxyServices.connectedApps.js +0 -95
- package/dist/cjs/mixins/OxyServices.contacts.js +0 -50
- package/dist/cjs/mixins/OxyServices.deviceBoot.js +0 -191
- package/dist/cjs/mixins/OxyServices.devices.js +0 -116
- package/dist/cjs/mixins/OxyServices.features.js +0 -335
- package/dist/cjs/mixins/OxyServices.followGraph.js +0 -221
- package/dist/cjs/mixins/OxyServices.identity.js +0 -562
- package/dist/cjs/mixins/OxyServices.identityBackup.js +0 -172
- package/dist/cjs/mixins/OxyServices.inference.js +0 -59
- package/dist/cjs/mixins/OxyServices.language.js +0 -125
- package/dist/cjs/mixins/OxyServices.linkedAccounts.js +0 -79
- package/dist/cjs/mixins/OxyServices.location.js +0 -43
- package/dist/cjs/mixins/OxyServices.nodes.js +0 -175
- package/dist/cjs/mixins/OxyServices.notifications.js +0 -63
- package/dist/cjs/mixins/OxyServices.payment.js +0 -158
- package/dist/cjs/mixins/OxyServices.privacy.js +0 -199
- package/dist/cjs/mixins/OxyServices.reputation.js +0 -289
- package/dist/cjs/mixins/OxyServices.security.js +0 -89
- package/dist/cjs/mixins/OxyServices.store.js +0 -266
- package/dist/cjs/mixins/OxyServices.topics.js +0 -137
- package/dist/cjs/mixins/OxyServices.user.js +0 -954
- package/dist/cjs/mixins/OxyServices.utility.js +0 -1402
- package/dist/cjs/mixins/index.js +0 -136
- package/dist/cjs/mixins/mixinHelpers.js +0 -53
- package/dist/cjs/utils/webauthnOrigin.js +0 -51
- package/dist/esm/.tsbuildinfo +0 -1
- package/dist/esm/OxyServices.base.js +0 -388
- package/dist/esm/i18n/locales/locales/ar-SA.json +0 -306
- package/dist/esm/i18n/locales/locales/ca-ES.json +0 -306
- package/dist/esm/i18n/locales/locales/de-DE.json +0 -306
- package/dist/esm/i18n/locales/locales/en-US.json +0 -2127
- package/dist/esm/i18n/locales/locales/es-ES.json +0 -2127
- package/dist/esm/i18n/locales/locales/fr-FR.json +0 -306
- package/dist/esm/i18n/locales/locales/it-IT.json +0 -306
- package/dist/esm/i18n/locales/locales/ja-JP.json +0 -306
- package/dist/esm/i18n/locales/locales/ko-KR.json +0 -306
- package/dist/esm/i18n/locales/locales/pt-PT.json +0 -306
- package/dist/esm/i18n/locales/locales/zh-CN.json +0 -306
- package/dist/esm/mixins/OxyServices.accounts.js +0 -543
- package/dist/esm/mixins/OxyServices.agency.js +0 -151
- package/dist/esm/mixins/OxyServices.analytics.js +0 -46
- package/dist/esm/mixins/OxyServices.appData.js +0 -103
- package/dist/esm/mixins/OxyServices.assets.js +0 -851
- package/dist/esm/mixins/OxyServices.auth.js +0 -1455
- package/dist/esm/mixins/OxyServices.chains.js +0 -70
- package/dist/esm/mixins/OxyServices.civic.js +0 -605
- package/dist/esm/mixins/OxyServices.connectedApps.js +0 -92
- package/dist/esm/mixins/OxyServices.contacts.js +0 -47
- package/dist/esm/mixins/OxyServices.deviceBoot.js +0 -186
- package/dist/esm/mixins/OxyServices.devices.js +0 -113
- package/dist/esm/mixins/OxyServices.features.js +0 -332
- package/dist/esm/mixins/OxyServices.followGraph.js +0 -218
- package/dist/esm/mixins/OxyServices.identity.js +0 -558
- package/dist/esm/mixins/OxyServices.identityBackup.js +0 -169
- package/dist/esm/mixins/OxyServices.inference.js +0 -56
- package/dist/esm/mixins/OxyServices.language.js +0 -122
- package/dist/esm/mixins/OxyServices.linkedAccounts.js +0 -76
- package/dist/esm/mixins/OxyServices.location.js +0 -40
- package/dist/esm/mixins/OxyServices.nodes.js +0 -172
- package/dist/esm/mixins/OxyServices.notifications.js +0 -60
- package/dist/esm/mixins/OxyServices.payment.js +0 -155
- package/dist/esm/mixins/OxyServices.privacy.js +0 -196
- package/dist/esm/mixins/OxyServices.reputation.js +0 -286
- package/dist/esm/mixins/OxyServices.security.js +0 -86
- package/dist/esm/mixins/OxyServices.store.js +0 -263
- package/dist/esm/mixins/OxyServices.topics.js +0 -134
- package/dist/esm/mixins/OxyServices.user.js +0 -951
- package/dist/esm/mixins/OxyServices.utility.js +0 -1397
- package/dist/esm/mixins/index.js +0 -133
- package/dist/esm/mixins/mixinHelpers.js +0 -48
- package/dist/esm/utils/webauthnOrigin.js +0 -48
- package/dist/types/.tsbuildinfo +0 -1
- package/dist/types/OxyServices.base.d.ts +0 -189
- package/dist/types/mixins/OxyServices.accounts.d.ts +0 -868
- package/dist/types/mixins/OxyServices.agency.d.ts +0 -176
- package/dist/types/mixins/OxyServices.analytics.d.ts +0 -72
- package/dist/types/mixins/OxyServices.appData.d.ts +0 -108
- package/dist/types/mixins/OxyServices.assets.d.ts +0 -371
- package/dist/types/mixins/OxyServices.auth.d.ts +0 -998
- package/dist/types/mixins/OxyServices.chains.d.ts +0 -156
- package/dist/types/mixins/OxyServices.civic.d.ts +0 -520
- package/dist/types/mixins/OxyServices.connectedApps.d.ts +0 -184
- package/dist/types/mixins/OxyServices.contacts.d.ts +0 -100
- package/dist/types/mixins/OxyServices.deviceBoot.d.ts +0 -185
- package/dist/types/mixins/OxyServices.devices.d.ts +0 -103
- package/dist/types/mixins/OxyServices.features.d.ts +0 -250
- package/dist/types/mixins/OxyServices.followGraph.d.ts +0 -223
- package/dist/types/mixins/OxyServices.identity.d.ts +0 -337
- package/dist/types/mixins/OxyServices.identityBackup.d.ts +0 -129
- package/dist/types/mixins/OxyServices.inference.d.ts +0 -95
- package/dist/types/mixins/OxyServices.language.d.ts +0 -87
- package/dist/types/mixins/OxyServices.linkedAccounts.d.ts +0 -107
- package/dist/types/mixins/OxyServices.location.d.ts +0 -70
- package/dist/types/mixins/OxyServices.nodes.d.ts +0 -198
- package/dist/types/mixins/OxyServices.notifications.d.ts +0 -143
- package/dist/types/mixins/OxyServices.payment.d.ts +0 -117
- package/dist/types/mixins/OxyServices.privacy.d.ts +0 -146
- package/dist/types/mixins/OxyServices.reputation.d.ts +0 -202
- package/dist/types/mixins/OxyServices.security.d.ts +0 -84
- package/dist/types/mixins/OxyServices.store.d.ts +0 -334
- package/dist/types/mixins/OxyServices.topics.d.ts +0 -110
- package/dist/types/mixins/OxyServices.user.d.ts +0 -600
- package/dist/types/mixins/OxyServices.utility.d.ts +0 -526
- package/dist/types/mixins/index.d.ts +0 -87
- package/dist/types/mixins/mixinHelpers.d.ts +0 -31
- package/dist/types/utils/webauthnOrigin.d.ts +0 -32
- package/src/OxyServices.base.ts +0 -480
- package/src/__tests__/authSocket.test.ts +0 -96
- package/src/__tests__/fixEsmImports.test.ts +0 -132
- package/src/__tests__/httpServiceAbort.test.ts +0 -308
- package/src/__tests__/httpServiceActivityId.test.ts +0 -49
- package/src/__tests__/httpServiceAuthSelfAwait.test.ts +0 -108
- package/src/__tests__/httpServiceCache.test.ts +0 -318
- package/src/__tests__/httpServiceFormEncoded.test.ts +0 -139
- package/src/__tests__/httpServiceQueueDeadlock.test.ts +0 -128
- package/src/__tests__/httpServiceRateLimitedRefresh.test.ts +0 -160
- package/src/__tests__/httpServiceUnwrapEnvelope.test.ts +0 -115
- package/src/__tests__/httpServiceWrites.test.ts +0 -175
- package/src/__tests__/inSessionRefresh.test.ts +0 -313
- package/src/__tests__/linkedClient.test.ts +0 -363
- package/src/__tests__/nearExpiryMintStorm.test.ts +0 -165
- package/src/__tests__/parseHttpErrorBody.test.ts +0 -116
- package/src/__tests__/publicInterfaceParity.test.ts +0 -75
- package/src/__tests__/serverValueImportsDeclared.test.ts +0 -120
- package/src/__tests__/serviceIdentityWhenAnonymous.test.ts +0 -159
- package/src/__tests__/serviceTokenWorkloadFallback.test.ts +0 -85
- package/src/__tests__/userIdentity.test.ts +0 -143
- package/src/boot/__tests__/sessionColdBoot.identity.test.ts +0 -393
- package/src/boot/__tests__/sessionColdBoot.sharedDevice.test.ts +0 -325
- package/src/boot/__tests__/sessionColdBoot.test.ts +0 -570
- package/src/crypto/__tests__/backupMaterial.test.ts +0 -86
- package/src/crypto/__tests__/cryptoPrimitives.test.ts +0 -226
- package/src/crypto/__tests__/identityLink.test.ts +0 -18
- package/src/crypto/__tests__/identityMocks.ts +0 -125
- package/src/crypto/__tests__/identityProof.test.ts +0 -81
- package/src/crypto/__tests__/keyManager.atomicity.test.ts +0 -272
- package/src/crypto/__tests__/keyManager.cacheSafety.test.ts +0 -175
- package/src/crypto/__tests__/keyManager.deviceBackup.test.ts +0 -310
- package/src/crypto/__tests__/keyManager.identityStatus.test.ts +0 -217
- package/src/crypto/__tests__/keyManager.recoveryLadder.test.ts +0 -179
- package/src/crypto/__tests__/keyManager.recoveryMnemonic.test.ts +0 -138
- package/src/crypto/__tests__/keyManager.sharedSlotSync.test.ts +0 -150
- package/src/crypto/__tests__/keyManager.storageMigration.test.ts +0 -227
- package/src/crypto/__tests__/keyManager.test.ts +0 -329
- package/src/crypto/__tests__/polyfill.test.ts +0 -109
- package/src/crypto/__tests__/polyfillOrder.test.ts +0 -114
- package/src/crypto/__tests__/recoveryPhrase.test.ts +0 -61
- package/src/crypto/__tests__/registrationPow.test.ts +0 -61
- package/src/crypto/__tests__/scopedSeed.test.ts +0 -126
- package/src/crypto/__tests__/signChallengeShared.test.ts +0 -63
- package/src/crypto/__tests__/signedRecord.test.ts +0 -230
- package/src/i18n/__tests__/accountCategoryLabels.test.ts +0 -62
- package/src/i18n/__tests__/accountRoleLabels.test.ts +0 -54
- package/src/i18n/__tests__/reputationCategoryLabels.test.ts +0 -56
- package/src/i18n/__tests__/trustTierLabels.test.ts +0 -47
- package/src/i18n/locales/de-DE.json +0 -306
- package/src/i18n/locales/es-ES.json +0 -2127
- package/src/i18n/locales/ja-JP.json +0 -306
- package/src/inference/__tests__/OxyInferenceClient.test.ts +0 -899
- package/src/logger/__tests__/logger.test.ts +0 -207
- package/src/mixins/OxyServices.accounts.ts +0 -1330
- package/src/mixins/OxyServices.agency.ts +0 -353
- package/src/mixins/OxyServices.analytics.ts +0 -53
- package/src/mixins/OxyServices.appData.ts +0 -158
- package/src/mixins/OxyServices.assets.ts +0 -964
- package/src/mixins/OxyServices.auth.ts +0 -2083
- package/src/mixins/OxyServices.chains.ts +0 -134
- package/src/mixins/OxyServices.civic.ts +0 -945
- package/src/mixins/OxyServices.connectedApps.ts +0 -212
- package/src/mixins/OxyServices.contacts.ts +0 -73
- package/src/mixins/OxyServices.devices.ts +0 -120
- package/src/mixins/OxyServices.features.ts +0 -454
- package/src/mixins/OxyServices.followGraph.ts +0 -293
- package/src/mixins/OxyServices.identity.ts +0 -787
- package/src/mixins/OxyServices.identityBackup.ts +0 -250
- package/src/mixins/OxyServices.inference.ts +0 -57
- package/src/mixins/OxyServices.language.ts +0 -144
- package/src/mixins/OxyServices.linkedAccounts.ts +0 -129
- package/src/mixins/OxyServices.location.ts +0 -46
- package/src/mixins/OxyServices.nodes.ts +0 -337
- package/src/mixins/OxyServices.notifications.ts +0 -142
- package/src/mixins/OxyServices.payment.ts +0 -163
- package/src/mixins/OxyServices.privacy.ts +0 -219
- package/src/mixins/OxyServices.reputation.ts +0 -436
- package/src/mixins/OxyServices.security.ts +0 -110
- package/src/mixins/OxyServices.store.ts +0 -585
- package/src/mixins/OxyServices.topics.ts +0 -169
- package/src/mixins/OxyServices.user.ts +0 -1208
- package/src/mixins/OxyServices.utility.ts +0 -1803
- package/src/mixins/__tests__/OxyServices.civic.test.ts +0 -1095
- package/src/mixins/__tests__/OxyServices.deviceBoot.test.ts +0 -124
- package/src/mixins/__tests__/OxyServices.identity.test.ts +0 -301
- package/src/mixins/__tests__/OxyServices.nodes.test.ts +0 -341
- package/src/mixins/__tests__/OxyServices.rotateKey.test.ts +0 -323
- package/src/mixins/__tests__/OxyServices.serviceAssetMetadata.test.ts +0 -192
- package/src/mixins/__tests__/OxyServices.serviceAssetMetadataBySha.test.ts +0 -135
- package/src/mixins/__tests__/OxyServices.serviceLinkedDownloadUrl.test.ts +0 -139
- package/src/mixins/__tests__/accountEvents.test.ts +0 -158
- package/src/mixins/__tests__/accounts.test.ts +0 -727
- package/src/mixins/__tests__/agency.test.ts +0 -120
- package/src/mixins/__tests__/appData.test.ts +0 -203
- package/src/mixins/__tests__/assetCredentials.test.ts +0 -47
- package/src/mixins/__tests__/assetUpload.test.ts +0 -191
- package/src/mixins/__tests__/authRefusalObservability.test.ts +0 -396
- package/src/mixins/__tests__/backgroundCredential.httpIntegration.test.ts +0 -130
- package/src/mixins/__tests__/chains.test.ts +0 -113
- package/src/mixins/__tests__/commonsSignIn.test.ts +0 -1147
- package/src/mixins/__tests__/connectedApps.test.ts +0 -146
- package/src/mixins/__tests__/deviceTokenMintPinned.test.ts +0 -113
- package/src/mixins/__tests__/discoveryErrorHandling.test.ts +0 -265
- package/src/mixins/__tests__/followCacheInvalidation.test.ts +0 -173
- package/src/mixins/__tests__/followGraph.test.ts +0 -147
- package/src/mixins/__tests__/followGraphPagination.test.ts +0 -271
- package/src/mixins/__tests__/getFileDownloadUrl.test.ts +0 -346
- package/src/mixins/__tests__/getFollowStatuses.test.ts +0 -95
- package/src/mixins/__tests__/getUsersByIds.test.ts +0 -176
- package/src/mixins/__tests__/identityBackup.test.ts +0 -315
- package/src/mixins/__tests__/identityLink.test.ts +0 -89
- package/src/mixins/__tests__/identityWriteCacheInvalidation.test.ts +0 -407
- package/src/mixins/__tests__/inferenceFactory.test.ts +0 -58
- package/src/mixins/__tests__/linkedAccounts.test.ts +0 -109
- package/src/mixins/__tests__/notifications.read.test.ts +0 -82
- package/src/mixins/__tests__/onTokensChanged.test.ts +0 -129
- package/src/mixins/__tests__/preSessionSkipAuth.test.ts +0 -284
- package/src/mixins/__tests__/privacyCacheInvalidation.test.ts +0 -155
- package/src/mixins/__tests__/reputation.test.ts +0 -522
- package/src/mixins/__tests__/serviceAuth.test.ts +0 -889
- package/src/mixins/__tests__/serviceTokenJwks.test.ts +0 -208
- package/src/mixins/__tests__/store.test.ts +0 -304
- package/src/mixins/__tests__/topics.test.ts +0 -155
- package/src/mixins/__tests__/userReadCacheBypass.test.ts +0 -129
- package/src/mixins/__tests__/userTokenAuth.test.ts +0 -746
- package/src/mixins/__tests__/verifyChallenge.test.ts +0 -161
- package/src/mixins/__tests__/verifyServiceActingAs.test.ts +0 -167
- package/src/mixins/__tests__/webauthnAuth.test.ts +0 -279
- package/src/mixins/index.ts +0 -203
- package/src/mixins/mixinHelpers.ts +0 -69
- package/src/server/__tests__/auth.test.ts +0 -78
- package/src/server/__tests__/capabilityTicket.test.ts +0 -111
- package/src/server/__tests__/cors.socket.test.ts +0 -225
- package/src/server/__tests__/cors.test.ts +0 -158
- package/src/server/__tests__/rateLimit.test.ts +0 -257
- package/src/server/__tests__/requesterAssertion.test.ts +0 -359
- package/src/server/__tests__/safeFetch.test.ts +0 -232
- package/src/server/__tests__/securityHeaders.test.ts +0 -387
- package/src/server/__tests__/serviceTokenAttribution.test.ts +0 -396
- package/src/server/__tests__/traffic.test.ts +0 -180
- package/src/server/__tests__/trafficNodeHttp.test.ts +0 -38
- package/src/server/__tests__/userInvalidation.test.ts +0 -203
- package/src/server/__tests__/verifySecret.test.ts +0 -40
- package/src/server/__tests__/workloadIdentity.client.test.ts +0 -16
- package/src/server/__tests__/workloadIdentity.test.ts +0 -128
- package/src/session/__tests__/SessionClient.additive.test.ts +0 -153
- package/src/session/__tests__/SessionClient.broadcastChannel.test.ts +0 -114
- package/src/session/__tests__/SessionClient.diagnostics.test.ts +0 -91
- package/src/session/__tests__/SessionClient.directory.test.ts +0 -688
- package/src/session/__tests__/SessionClient.httpIntegration.test.ts +0 -65
- package/src/session/__tests__/SessionClient.identityPin.test.ts +0 -175
- package/src/session/__tests__/SessionClient.rest.test.ts +0 -119
- package/src/session/__tests__/SessionClient.serverEvents.test.ts +0 -72
- package/src/session/__tests__/SessionClient.socket.test.ts +0 -202
- package/src/session/__tests__/SessionClient.socketFactory.test.ts +0 -81
- package/src/session/__tests__/SessionClient.state.test.ts +0 -78
- package/src/session/__tests__/SessionClient.switchTokenOrder.test.ts +0 -170
- package/src/session/__tests__/accountDelegationAccess.test.ts +0 -104
- package/src/session/__tests__/accountDialogController.test.ts +0 -2502
- package/src/session/__tests__/accountDialogShape.test.ts +0 -120
- package/src/session/__tests__/accountSwitchTargets.test.ts +0 -137
- package/src/session/__tests__/authStateStore.test.ts +0 -451
- package/src/session/__tests__/deviceDirectory.test.ts +0 -422
- package/src/session/__tests__/deviceSwitcherRows.test.ts +0 -223
- package/src/session/__tests__/identityPin.test.ts +0 -131
- package/src/session/__tests__/identitySession.test.ts +0 -194
- package/src/session/__tests__/projectSessionState.test.ts +0 -105
- package/src/session/__tests__/refresh.identityPin.test.ts +0 -307
- package/src/session/__tests__/refresh.test.ts +0 -606
- package/src/session/__tests__/sessionIntegration.test.ts +0 -209
- package/src/session/__tests__/sharedDeviceCredential.test.ts +0 -300
- package/src/utils/__tests__/accountUtils.test.ts +0 -151
- package/src/utils/__tests__/activityId.test.ts +0 -45
- package/src/utils/__tests__/asyncUtils.test.ts +0 -279
- package/src/utils/__tests__/authWebUrl.test.ts +0 -15
- package/src/utils/__tests__/cacheKey.test.ts +0 -0
- package/src/utils/__tests__/cacheLifecycle.test.ts +0 -65
- package/src/utils/__tests__/coldBoot.test.ts +0 -496
- package/src/utils/__tests__/commonsApproval.test.ts +0 -60
- package/src/utils/__tests__/identityCacheSweep.test.ts +0 -151
- package/src/utils/__tests__/languageUtils.test.ts +0 -244
- package/src/utils/__tests__/oauthPkce.test.ts +0 -303
- package/src/utils/__tests__/officialOrigins.test.ts +0 -31
- package/src/utils/__tests__/oxyServiceEnvironment.test.ts +0 -7
- package/src/utils/__tests__/profileLinks.test.ts +0 -180
- package/src/utils/__tests__/redactUrl.test.ts +0 -33
- package/src/utils/__tests__/registrableApex.test.ts +0 -62
- package/src/utils/__tests__/requestUtilsAbort.test.ts +0 -155
- package/src/utils/__tests__/textNormalization.test.ts +0 -205
- package/src/utils/__tests__/validationUtils.test.ts +0 -703
- package/src/utils/__tests__/webauthnOrigin.test.ts +0 -83
- package/src/utils/webauthnOrigin.ts +0 -52
|
@@ -0,0 +1,858 @@
|
|
|
1
|
+
import { emailSignInLinkResponseSchema, emailSignInPendingSchema, emailSignInStartResponseSchema, emailVerificationConfirmResponseSchema, emailVerificationStartResponseSchema, loginResultSchema, safeParseContract, secondFactorRequiredSchema, signInMethodsSchema, totpBackupCodesResponseSchema, totpEnrollResponseSchema, } from '@oxy.so/contracts';
|
|
2
|
+
import { OxyAuthenticationError } from '../OxyServices.errors.js';
|
|
3
|
+
import { logger } from '../logger/index.js';
|
|
4
|
+
import { normalizeUserIdentity } from '../utils/userIdentity.js';
|
|
5
|
+
// The identity key, signatures and the registration proof-of-work load on first
|
|
6
|
+
// use: an app that never signs anything must not ship secp256k1.
|
|
7
|
+
const loadKeyManager = async () => (await import('../crypto/internal.js')).KeyManager;
|
|
8
|
+
const loadSignatureService = async () => (await import('../crypto/internal.js')).SignatureService;
|
|
9
|
+
/**
|
|
10
|
+
* Default lifetime of a "Sign in with Oxy" device-flow session / authorize code.
|
|
11
|
+
* Matches the authorize-code TTL the server enforces (5 minutes). The server's
|
|
12
|
+
* returned `expiresAt` is authoritative; this is only the client-proposed value.
|
|
13
|
+
*/
|
|
14
|
+
const COMMONS_SIGN_IN_EXPIRY_MS = 5 * 60 * 1000;
|
|
15
|
+
/**
|
|
16
|
+
* Fallback access-token lifetime used only if the token endpoint ever omits the
|
|
17
|
+
* RFC 6749 `expires_in` member. Matches the server's current 15-minute access
|
|
18
|
+
* token; the server's value always wins when present.
|
|
19
|
+
*/
|
|
20
|
+
const DEFAULT_ACCESS_TOKEN_TTL_SECONDS = 15 * 60;
|
|
21
|
+
/**
|
|
22
|
+
* @internal Narrow an untrusted `requesterLabel` from the approve-info response.
|
|
23
|
+
*
|
|
24
|
+
* Returns the trimmed label only when the server sent a real, non-empty string;
|
|
25
|
+
* everything else — absent (an API that predates the field), `null` (a native
|
|
26
|
+
* requester the server could not describe), or a non-string — degrades to
|
|
27
|
+
* `null`, so the approval UI drops the line instead of rendering a blank or a
|
|
28
|
+
* coerced value under the app it is about to authorize.
|
|
29
|
+
*/
|
|
30
|
+
function parseCommonsRequesterLabel(value) {
|
|
31
|
+
if (typeof value !== 'string') {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
const label = value.trim();
|
|
35
|
+
return label.length > 0 ? label : null;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* @internal Narrow an untrusted `subjectAccount` from the approve-info response.
|
|
39
|
+
*
|
|
40
|
+
* Returns `null` unless the value is an object carrying a non-empty string `id`
|
|
41
|
+
* AND `username` — the two fields the approval UI needs to name the delegated
|
|
42
|
+
* account. A half-populated object is rejected whole rather than rendered with
|
|
43
|
+
* blanks, and `displayName` is only carried through when it is a string.
|
|
44
|
+
*/
|
|
45
|
+
function parseCommonsSubjectAccount(value) {
|
|
46
|
+
if (value === null || typeof value !== 'object') {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
const raw = value;
|
|
50
|
+
const { id, username, displayName } = raw;
|
|
51
|
+
if (typeof id !== 'string' || !id || typeof username !== 'string' || !username) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
id,
|
|
56
|
+
username,
|
|
57
|
+
...(typeof displayName === 'string' ? { displayName } : {}),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* @internal Narrow an untrusted delivery-progress timestamp from the status
|
|
62
|
+
* response.
|
|
63
|
+
*
|
|
64
|
+
* Returns the ISO-8601 string unchanged when it is a real, parseable instant,
|
|
65
|
+
* and `null` for everything else — absent (an older API that has no delivery
|
|
66
|
+
* progress at all), empty, non-string, or unparseable. Progress is advisory, so
|
|
67
|
+
* degrading to "no progress yet" is always safe; surfacing a garbage timestamp
|
|
68
|
+
* to the waiting UI is not.
|
|
69
|
+
*/
|
|
70
|
+
function parseCommonsProgressTimestamp(value) {
|
|
71
|
+
if (typeof value !== 'string' || !value) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
return Number.isFinite(Date.parse(value)) ? value : null;
|
|
75
|
+
}
|
|
76
|
+
/** The body fields a session-ending call sends: the options, plus the device proof. */
|
|
77
|
+
async function signInEnvelope(ctx, options = {}) {
|
|
78
|
+
const { device: explicit, deviceName, deviceFingerprint } = options;
|
|
79
|
+
const device = explicit === undefined ? await ctx.oxy.session.readDeviceProof() : explicit;
|
|
80
|
+
return {
|
|
81
|
+
...(deviceName ? { deviceName } : {}),
|
|
82
|
+
...(deviceFingerprint ? { deviceFingerprint } : {}),
|
|
83
|
+
...(device ? { device } : {}),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/** Parse a session, planting its token. */
|
|
87
|
+
function plantSession(ctx, res, path) {
|
|
88
|
+
const parsed = safeParseContract(loginResultSchema, res);
|
|
89
|
+
if (!parsed)
|
|
90
|
+
throw new Error(`${path} returned an unexpected response shape`);
|
|
91
|
+
if (parsed.accessToken)
|
|
92
|
+
ctx.oxy.session.setAccessToken(parsed.accessToken);
|
|
93
|
+
return parsed;
|
|
94
|
+
}
|
|
95
|
+
/** Parse a first factor's answer: the second-factor step, or a session. */
|
|
96
|
+
function signInStep(ctx, res, path) {
|
|
97
|
+
const challenge = safeParseContract(secondFactorRequiredSchema, res);
|
|
98
|
+
if (challenge)
|
|
99
|
+
return challenge;
|
|
100
|
+
return plantSession(ctx, res, path);
|
|
101
|
+
}
|
|
102
|
+
/** `oxy.auth.email` — email sign-in (code or link) and email confirmation codes. */
|
|
103
|
+
export class AuthEmailApi {
|
|
104
|
+
constructor(ctx) {
|
|
105
|
+
this.ctx = ctx;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Send a 6-digit code to the email of a new account
|
|
109
|
+
* (`purpose: 'signup'`). The answer is the same whether or not the address
|
|
110
|
+
* already has an account; nothing is revealed about who has one.
|
|
111
|
+
*/
|
|
112
|
+
async startVerification(request) {
|
|
113
|
+
const res = await this.ctx.request('POST', '/auth/email/verify/start', request, {
|
|
114
|
+
cache: false,
|
|
115
|
+
skipAuth: true,
|
|
116
|
+
});
|
|
117
|
+
const parsed = safeParseContract(emailVerificationStartResponseSchema, res);
|
|
118
|
+
if (!parsed)
|
|
119
|
+
throw new Error('auth/email/verify/start returned an unexpected response shape');
|
|
120
|
+
return parsed;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Confirm the code {@link AuthEmailApi.startVerification} sent. Resolves to a
|
|
124
|
+
* short-lived one-use ticket, which a sign-up passes (with the email) to
|
|
125
|
+
* {@link AuthApi.signUp}.
|
|
126
|
+
*/
|
|
127
|
+
async confirmVerification(verificationId, code) {
|
|
128
|
+
const res = await this.ctx.request('POST', '/auth/email/verify/confirm', { verificationId, code }, { cache: false, skipAuth: true });
|
|
129
|
+
const parsed = safeParseContract(emailVerificationConfirmResponseSchema, res);
|
|
130
|
+
if (!parsed)
|
|
131
|
+
throw new Error('auth/email/verify/confirm returned an unexpected response shape');
|
|
132
|
+
return parsed;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Send the sign-in email (a code and a link) for a username or email. The
|
|
136
|
+
* answer is the same whether or not the account exists. Keep
|
|
137
|
+
* `requestSecret` in memory only.
|
|
138
|
+
*/
|
|
139
|
+
async start(identifier, options = {}) {
|
|
140
|
+
const device = options.device === undefined ? await this.ctx.oxy.session.readDeviceProof() : options.device;
|
|
141
|
+
const res = await this.ctx.request('POST', '/auth/signin/email/start', { identifier, ...(device ? { device } : {}) }, { cache: false, skipAuth: true });
|
|
142
|
+
const parsed = safeParseContract(emailSignInStartResponseSchema, res);
|
|
143
|
+
if (!parsed)
|
|
144
|
+
throw new Error('auth/signin/email/start returned an unexpected response shape');
|
|
145
|
+
return parsed;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* The code from the email → a session, or the second-factor step. The code
|
|
149
|
+
* is 6 digits, or the 10-character long code (`XXXXX-XXXXX`) an account
|
|
150
|
+
* gets after too many guesses in a day — accept both in one field and pass
|
|
151
|
+
* it as typed.
|
|
152
|
+
*/
|
|
153
|
+
async confirm(request) {
|
|
154
|
+
const { requestId, requestSecret, code, ...options } = request;
|
|
155
|
+
const res = await this.ctx.request('POST', '/auth/signin/email/confirm', { requestId, requestSecret, code, ...(await signInEnvelope(this.ctx, options)) }, { cache: false, skipAuth: true });
|
|
156
|
+
return signInStep(this.ctx, res, 'auth/signin/email/confirm');
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Whether the email's link was opened in this browser: `{ status: 'pending' }`
|
|
160
|
+
* until it was, then a session (or the second-factor step). Poll it.
|
|
161
|
+
*/
|
|
162
|
+
async collect(request) {
|
|
163
|
+
const { requestId, requestSecret, ...options } = request;
|
|
164
|
+
const res = await this.ctx.request('POST', '/auth/signin/email/collect', { requestId, requestSecret, ...(await signInEnvelope(this.ctx, options)) }, { cache: false, skipAuth: true });
|
|
165
|
+
const pending = safeParseContract(emailSignInPendingSchema, res);
|
|
166
|
+
if (pending)
|
|
167
|
+
return pending;
|
|
168
|
+
return signInStep(this.ctx, res, 'auth/signin/email/collect');
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* auth.oxy.so's link page: approve the request with THIS client's device
|
|
172
|
+
* proof. It approves only in the browser that asked, and never returns a
|
|
173
|
+
* session here — the app's dialog collects it.
|
|
174
|
+
*/
|
|
175
|
+
async approveLink(token, device) {
|
|
176
|
+
const proof = device ?? (await this.ctx.oxy.session.readDeviceProof());
|
|
177
|
+
if (!proof)
|
|
178
|
+
throw new Error('This browser holds no Oxy device to approve the sign-in with');
|
|
179
|
+
const res = await this.ctx.request('POST', '/auth/signin/email/link', { token, device: proof }, { cache: false, skipAuth: true });
|
|
180
|
+
const parsed = safeParseContract(emailSignInLinkResponseSchema, res);
|
|
181
|
+
if (!parsed)
|
|
182
|
+
throw new Error('auth/signin/email/link returned an unexpected response shape');
|
|
183
|
+
return parsed;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/** `oxy.auth.password` — password sign-in and setting the password. */
|
|
187
|
+
export class AuthPasswordApi {
|
|
188
|
+
constructor(ctx) {
|
|
189
|
+
this.ctx = ctx;
|
|
190
|
+
}
|
|
191
|
+
async signIn(request) {
|
|
192
|
+
const { identifier, password, ...options } = request;
|
|
193
|
+
const res = await this.ctx.request('POST', '/auth/signin/password', { identifier, password, ...(await signInEnvelope(this.ctx, options)) }, { cache: false, skipAuth: true });
|
|
194
|
+
return signInStep(this.ctx, res, 'auth/signin/password');
|
|
195
|
+
}
|
|
196
|
+
async set(request) {
|
|
197
|
+
return this.ctx.request('PUT', '/users/me/password', request, { cache: false });
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
/** `oxy.auth.totp` — the account’s authenticator app. */
|
|
201
|
+
export class AuthTotpApi {
|
|
202
|
+
constructor(ctx) {
|
|
203
|
+
this.ctx = ctx;
|
|
204
|
+
}
|
|
205
|
+
async enroll() {
|
|
206
|
+
const res = await this.ctx.request('POST', '/users/me/totp/enroll', undefined, { cache: false });
|
|
207
|
+
const parsed = safeParseContract(totpEnrollResponseSchema, res);
|
|
208
|
+
if (!parsed)
|
|
209
|
+
throw new Error('users/me/totp/enroll returned an unexpected response shape');
|
|
210
|
+
return parsed;
|
|
211
|
+
}
|
|
212
|
+
async confirm(code, reauth) {
|
|
213
|
+
const res = await this.ctx.request('POST', '/users/me/totp/confirm', { code, reauth }, { cache: false });
|
|
214
|
+
const parsed = safeParseContract(totpBackupCodesResponseSchema, res);
|
|
215
|
+
if (!parsed)
|
|
216
|
+
throw new Error('users/me/totp/confirm returned an unexpected response shape');
|
|
217
|
+
return parsed.backupCodes;
|
|
218
|
+
}
|
|
219
|
+
async disable(reauth) {
|
|
220
|
+
return this.ctx.request('POST', '/users/me/totp/disable', { reauth }, { cache: false });
|
|
221
|
+
}
|
|
222
|
+
async regenerateBackupCodes(reauth) {
|
|
223
|
+
const res = await this.ctx.request('POST', '/users/me/totp/backup-codes', { reauth }, { cache: false });
|
|
224
|
+
const parsed = safeParseContract(totpBackupCodesResponseSchema, res);
|
|
225
|
+
if (!parsed)
|
|
226
|
+
throw new Error('users/me/totp/backup-codes returned an unexpected response shape');
|
|
227
|
+
return parsed.backupCodes;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
/** `oxy.auth.oauth` — a relying party’s OAuth authorization-code exchange. */
|
|
231
|
+
export class AuthOAuthApi {
|
|
232
|
+
constructor(ctx) {
|
|
233
|
+
this.ctx = ctx;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Exchange an OAuth authorization code (returned to the RP redirect URI
|
|
237
|
+
* after sign-in at auth.oxy.so) for a device-first session.
|
|
238
|
+
* Public first-party clients use PKCE (`codeVerifier`); the access token is
|
|
239
|
+
* planted immediately on success.
|
|
240
|
+
*
|
|
241
|
+
* Speaks the standard RFC 6749 §4.1.3 token request — a form-urlencoded
|
|
242
|
+
* body with snake_case parameters and `grant_type=authorization_code` — and
|
|
243
|
+
* reads the flat §5.1 response. The camelCase JSON request and `{ data }`
|
|
244
|
+
* response this method used before were an Oxy invention no OAuth library
|
|
245
|
+
* could interoperate with; the endpoint no longer accepts them. The method's
|
|
246
|
+
* OWN signature is unchanged, so callers are unaffected.
|
|
247
|
+
*
|
|
248
|
+
* `deviceId` + `deviceSecret` are OPTIONAL and their absence is a valid
|
|
249
|
+
* outcome, not an error. A third-party grant is meant to be isolated from the
|
|
250
|
+
* browser's shared DeviceSession, so the token endpoint must be free to return
|
|
251
|
+
* no device credential at all — the guard that used to require the pair made
|
|
252
|
+
* that omission unshippable, since it turned every third-party sign-in through
|
|
253
|
+
* the SDK into a silent `exchange-failed`.
|
|
254
|
+
*
|
|
255
|
+
* The cost is real and deliberate: a DEVICE-LESS session cannot use the
|
|
256
|
+
* zero-cookie mint lane (`POST /session/device/token`), because that lane's
|
|
257
|
+
* whole proof is possession of a `deviceSecret`. Its lifetime is therefore the
|
|
258
|
+
* access token itself — nothing persists a restore credential, the cold boot's
|
|
259
|
+
* `device-secret-mint` step reports `no-secret` and skips, and the refresh
|
|
260
|
+
* scheduler has nothing to re-mint from. When the token expires the session
|
|
261
|
+
* ends LOUDLY: the 401 lane clears the tokens and the provider resolves signed
|
|
262
|
+
* out, so the app can run the OAuth flow again. It never degrades into a
|
|
263
|
+
* session that looks alive and cannot refresh.
|
|
264
|
+
*/
|
|
265
|
+
async exchangeCode(params) {
|
|
266
|
+
const form = new URLSearchParams({
|
|
267
|
+
grant_type: 'authorization_code',
|
|
268
|
+
code: params.code,
|
|
269
|
+
redirect_uri: params.redirectUri,
|
|
270
|
+
client_id: params.clientId,
|
|
271
|
+
code_verifier: params.codeVerifier,
|
|
272
|
+
});
|
|
273
|
+
const res = await this.ctx.request('POST', '/auth/oauth/token', form, { cache: false, skipAuth: true });
|
|
274
|
+
if (!res || typeof res !== 'object') {
|
|
275
|
+
throw new Error('auth/oauth/token returned an unexpected response shape');
|
|
276
|
+
}
|
|
277
|
+
// RFC 6749 §5.1: every member sits at the TOP LEVEL of the document.
|
|
278
|
+
const record = res;
|
|
279
|
+
const accessToken = typeof record.access_token === 'string' ? record.access_token : undefined;
|
|
280
|
+
const sessionId = typeof record.session_id === 'string' ? record.session_id : undefined;
|
|
281
|
+
const deviceId = typeof record.deviceId === 'string' ? record.deviceId : undefined;
|
|
282
|
+
const deviceSecret = typeof record.deviceSecret === 'string' ? record.deviceSecret : undefined;
|
|
283
|
+
const userRaw = record.user;
|
|
284
|
+
// The device pair is NOT part of this guard — see the note above. What is
|
|
285
|
+
// still mandatory is what identifies the session at all.
|
|
286
|
+
if (!sessionId || !userRaw || typeof userRaw !== 'object') {
|
|
287
|
+
throw new Error('auth/oauth/token returned an incomplete session payload');
|
|
288
|
+
}
|
|
289
|
+
const userObj = userRaw;
|
|
290
|
+
const userId = typeof userObj.id === 'string' ? userObj.id : undefined;
|
|
291
|
+
if (!userId) {
|
|
292
|
+
throw new Error('auth/oauth/token returned a session without user.id');
|
|
293
|
+
}
|
|
294
|
+
const expiresInSec = typeof record.expires_in === 'number' ? record.expires_in : DEFAULT_ACCESS_TOKEN_TTL_SECONDS;
|
|
295
|
+
const expiresAt = new Date(Date.now() + expiresInSec * 1000).toISOString();
|
|
296
|
+
if (accessToken) {
|
|
297
|
+
this.ctx.oxy.session.setAccessToken(accessToken);
|
|
298
|
+
}
|
|
299
|
+
if (!deviceId || !deviceSecret) {
|
|
300
|
+
logger.debug('auth/oauth/token returned no device credential — this session lives only as long as its access token', { component: 'oxy.auth', method: 'exchangeOAuthCode' });
|
|
301
|
+
}
|
|
302
|
+
return {
|
|
303
|
+
sessionId,
|
|
304
|
+
expiresAt,
|
|
305
|
+
accessToken,
|
|
306
|
+
// Omitted rather than set to `undefined` when the server sent no device
|
|
307
|
+
// credential, so a device-less grant serializes as the absence it is.
|
|
308
|
+
...(deviceId ? { deviceId } : {}),
|
|
309
|
+
...(deviceSecret ? { deviceSecret } : {}),
|
|
310
|
+
user: {
|
|
311
|
+
id: userId,
|
|
312
|
+
username: typeof userObj.username === 'string' ? userObj.username : undefined,
|
|
313
|
+
avatar: typeof userObj.avatar === 'string' ? userObj.avatar : undefined,
|
|
314
|
+
},
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Fetch OpenID Connect userinfo for the current bearer (`GET /auth/oauth/userinfo`).
|
|
319
|
+
* The response is a flat JSON document — no `{ data }` wrapper.
|
|
320
|
+
*/
|
|
321
|
+
async userInfo() {
|
|
322
|
+
const res = await this.ctx.request('GET', '/auth/oauth/userinfo', undefined, { cache: false });
|
|
323
|
+
if (!res || typeof res !== 'object') {
|
|
324
|
+
throw new Error('auth/oauth/userinfo returned an unexpected response shape');
|
|
325
|
+
}
|
|
326
|
+
const record = res;
|
|
327
|
+
const sub = typeof record.sub === 'string' ? record.sub : undefined;
|
|
328
|
+
if (!sub) {
|
|
329
|
+
throw new Error('auth/oauth/userinfo returned a response without sub');
|
|
330
|
+
}
|
|
331
|
+
return {
|
|
332
|
+
sub,
|
|
333
|
+
...(typeof record.preferred_username === 'string'
|
|
334
|
+
? { preferred_username: record.preferred_username }
|
|
335
|
+
: {}),
|
|
336
|
+
...(typeof record.name === 'string' ? { name: record.name } : {}),
|
|
337
|
+
...(typeof record.picture === 'string' ? { picture: record.picture } : {}),
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* `oxy.auth.commons` — "Sign in with Oxy": a relying party hands a sign-in to
|
|
343
|
+
* Commons (QR, push, deep link, popup), and Commons approves or denies it.
|
|
344
|
+
*
|
|
345
|
+
* Relying party: {@link AuthCommonsApi.start} → {@link AuthCommonsApi.poll}
|
|
346
|
+
* (or the auth socket) → finalize with `oxy.auth.claimSession` (device sign-in)
|
|
347
|
+
* or {@link AuthCommonsApi.finalizeOAuth} (OAuth). Approver (Commons):
|
|
348
|
+
* {@link AuthCommonsApi.approvalInfo} → {@link AuthCommonsApi.approve} /
|
|
349
|
+
* {@link AuthCommonsApi.deny}. The approver signs with its PRIMARY local key;
|
|
350
|
+
* the relying party never sees the private key.
|
|
351
|
+
*/
|
|
352
|
+
export class AuthCommonsApi {
|
|
353
|
+
constructor(ctx) {
|
|
354
|
+
this.ctx = ctx;
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* MECHANISM B (relying party) — begin a "Sign in with Oxy" handoff.
|
|
358
|
+
*
|
|
359
|
+
* Generates a secret device-flow `sessionToken` client-side (it never
|
|
360
|
+
* appears in the QR), registers it with `POST /auth/session/create`, and
|
|
361
|
+
* returns the server-issued public `authorizeCode` + ready-to-render
|
|
362
|
+
* `qrPayload`. Render the QR (web) / open the deep-link (same-device); the
|
|
363
|
+
* approver resolves the code and authorizes. Then poll with
|
|
364
|
+
* {@link AuthCommonsApi.poll} and finalize.
|
|
365
|
+
*
|
|
366
|
+
* ONE request serves every delivery surface, and how it finalizes is decided
|
|
367
|
+
* here by whether an OAuth binding is attached:
|
|
368
|
+
* - no `oauth` (the default): a `device_sign_in` request — on `authorized`,
|
|
369
|
+
* exchange the `sessionToken` via the existing `auth.claimSession`.
|
|
370
|
+
* - with `oauth`: an `oauth_authorization` request — on `authorized`, call
|
|
371
|
+
* {@link AuthCommonsApi.finalizeOAuth} with the same `sessionToken` to mint the
|
|
372
|
+
* single-use authorization code, then exchange it with PKCE.
|
|
373
|
+
*
|
|
374
|
+
* @param params.clientId - The RP's registered OAuth client id
|
|
375
|
+
* (ApplicationCredential publicKey); required so the server can resolve the
|
|
376
|
+
* requesting application's identity.
|
|
377
|
+
* @param params.oauth - Optional OAuth binding ({@link CommonsOAuthContext}).
|
|
378
|
+
* Carries only the redirect URI, the PKCE S256 challenge, the requested
|
|
379
|
+
* scope, and an optional delegated `subjectAccountId` — never the PKCE
|
|
380
|
+
* verifier, the OAuth `state`, or any token.
|
|
381
|
+
*/
|
|
382
|
+
async start(params) {
|
|
383
|
+
// High-entropy opaque secret token (256-bit hex). Generated client-side
|
|
384
|
+
// and held only here; the server stores it but never returns it in the
|
|
385
|
+
// QR. Reuses the platform-safe random generator.
|
|
386
|
+
const sessionToken = await (await loadSignatureService()).generateChallenge();
|
|
387
|
+
const expiresAt = Date.now() + COMMONS_SIGN_IN_EXPIRY_MS;
|
|
388
|
+
const res = await this.ctx.request('POST', '/auth/session/create', {
|
|
389
|
+
sessionToken,
|
|
390
|
+
expiresAt,
|
|
391
|
+
clientId: params.clientId,
|
|
392
|
+
// Omitted entirely when absent so the device-sign-in body stays
|
|
393
|
+
// exactly what it has always been (the server reads presence, not a
|
|
394
|
+
// null, to decide the request's purpose).
|
|
395
|
+
...(params.oauth ? { oauth: params.oauth } : {}),
|
|
396
|
+
},
|
|
397
|
+
// Public/pre-session (no bearer): skip the preflight so a stale
|
|
398
|
+
// near-expiry token cannot re-enter refreshAccessToken while the
|
|
399
|
+
// refresh handler is already in flight (self-await hang).
|
|
400
|
+
{ cache: false, skipAuth: true });
|
|
401
|
+
return {
|
|
402
|
+
sessionToken,
|
|
403
|
+
authorizeCode: res.authorizeCode,
|
|
404
|
+
qrPayload: res.qrPayload,
|
|
405
|
+
expiresAt: res.expiresAt ?? expiresAt,
|
|
406
|
+
status: res.status,
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* MECHANISM B (relying party) — poll a device-flow session for approval.
|
|
411
|
+
*
|
|
412
|
+
* Backstop for the auth socket. On `authorized` (with a `sessionId`), the
|
|
413
|
+
* caller finalizes: `auth.claimSession` for a `device_sign_in` request,
|
|
414
|
+
* {@link AuthCommonsApi.finalizeOAuth} for an `oauth_authorization` one.
|
|
415
|
+
*
|
|
416
|
+
* Every field is narrowed fail-safe. `authorized` counts only as a literal
|
|
417
|
+
* `true`, the identifiers only as non-empty strings, and the delivery
|
|
418
|
+
* progress timestamps degrade to `null` when absent or unparseable — so a
|
|
419
|
+
* partial or older-API payload can advance the waiting UI at most, never
|
|
420
|
+
* make it believe a request was approved.
|
|
421
|
+
*
|
|
422
|
+
* @param sessionToken - The secret token from {@link AuthCommonsApi.start}.
|
|
423
|
+
*/
|
|
424
|
+
async poll(sessionToken) {
|
|
425
|
+
const res = await this.ctx.request('GET', `/auth/session/status/${encodeURIComponent(sessionToken)}`, undefined,
|
|
426
|
+
// Public/pre-session (no bearer): a preflight here is wrong per se and,
|
|
427
|
+
// if ever reached while a refresh is pending, would re-enter
|
|
428
|
+
// refreshAccessToken and await the very promise it runs inside.
|
|
429
|
+
{ cache: false, retry: false, skipAuth: true });
|
|
430
|
+
if (res === null || typeof res !== 'object') {
|
|
431
|
+
throw new Error('auth/session/status returned an unexpected response shape');
|
|
432
|
+
}
|
|
433
|
+
const { authorized, sessionId, publicKey, status, purpose, pushSentAt, openedAt } = res;
|
|
434
|
+
return {
|
|
435
|
+
authorized: authorized === true,
|
|
436
|
+
...(typeof sessionId === 'string' && sessionId ? { sessionId } : {}),
|
|
437
|
+
...(typeof publicKey === 'string' && publicKey ? { publicKey } : {}),
|
|
438
|
+
...(typeof status === 'string' && status ? { status } : {}),
|
|
439
|
+
purpose: purpose === 'oauth_authorization' ? 'oauth_authorization' : 'device_sign_in',
|
|
440
|
+
pushSentAt: parseCommonsProgressTimestamp(pushSentAt),
|
|
441
|
+
openedAt: parseCommonsProgressTimestamp(openedAt),
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* MECHANISM B (relying party) — ask Oxy to DELIVER a pending sign-in request
|
|
446
|
+
* to the identity's known Commons installations.
|
|
447
|
+
*
|
|
448
|
+
* This is the automatic half of "one intention, one primary action": rather
|
|
449
|
+
* than offering the user a menu of transports, the caller asks for delivery
|
|
450
|
+
* and lets the answer pick the route. Pass the returned `targets` to
|
|
451
|
+
* `selectCommonsDelivery` (`utils/commonsDelivery`) — `targets: 0` means no
|
|
452
|
+
* capable Commons installation is registered, which is a NORMAL outcome that
|
|
453
|
+
* resolves to the QR route, not an error to surface.
|
|
454
|
+
*
|
|
455
|
+
* **Requires a bearer.** Delivery is only allowed when Oxy already knows the
|
|
456
|
+
* intended identity from a trusted authenticated context — a request that
|
|
457
|
+
* merely carries a username or email typed into an unauthenticated browser
|
|
458
|
+
* must never be able to ring somebody's phone.
|
|
459
|
+
*
|
|
460
|
+
* The push it sends carries only `{ type, approvalUrl }` where the URL holds
|
|
461
|
+
* the public `authorizeCode` — no display data, no secrets. Commons resolves
|
|
462
|
+
* everything it shows from `auth.commons.approvalInfo`.
|
|
463
|
+
*
|
|
464
|
+
* @param authorizeCode - The public code from {@link AuthCommonsApi.start}.
|
|
465
|
+
*/
|
|
466
|
+
async deliver(authorizeCode) {
|
|
467
|
+
const res = await this.ctx.request('POST', `/auth/session/deliver/${encodeURIComponent(authorizeCode)}`, undefined,
|
|
468
|
+
// Bearer REQUIRED (unlike its public siblings): the identity to
|
|
469
|
+
// deliver to comes from the authenticated caller, never the code.
|
|
470
|
+
{ cache: false });
|
|
471
|
+
if (res === null || typeof res !== 'object') {
|
|
472
|
+
throw new Error('auth/session/deliver returned an unexpected response shape');
|
|
473
|
+
}
|
|
474
|
+
const { delivered, targets } = res;
|
|
475
|
+
// Both fields drive the route choice, so a partial payload is rejected
|
|
476
|
+
// outright rather than defaulted into a route the server never chose.
|
|
477
|
+
if (typeof delivered !== 'boolean' ||
|
|
478
|
+
typeof targets !== 'number' ||
|
|
479
|
+
!Number.isInteger(targets) ||
|
|
480
|
+
targets < 0) {
|
|
481
|
+
throw new Error('auth/session/deliver returned an incomplete delivery result');
|
|
482
|
+
}
|
|
483
|
+
return { delivered, targets };
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* MECHANISM B (approver / Commons) — report that the approval route was
|
|
487
|
+
* OPENED, so the waiting relying party can show "Opened in Commons".
|
|
488
|
+
*
|
|
489
|
+
* Progress only. It is idempotent, applies to a `pending` request alone, and
|
|
490
|
+
* records a timestamp (`openedAt`) — it never approves, authorizes, or
|
|
491
|
+
* advances the authorization state machine. Public, like the other approver
|
|
492
|
+
* handles: the approver has only the public `authorizeCode` at this point
|
|
493
|
+
* and has not yet signed anything.
|
|
494
|
+
*
|
|
495
|
+
* Best-effort by nature — a failure here costs the user only a progress
|
|
496
|
+
* line, so callers are free to ignore a rejection and continue to the
|
|
497
|
+
* approval screen.
|
|
498
|
+
*
|
|
499
|
+
* @param authorizeCode - The public code scanned from the QR / deep-link / push.
|
|
500
|
+
*/
|
|
501
|
+
async markOpened(authorizeCode) {
|
|
502
|
+
await this.ctx.request('POST', `/auth/session/opened/${encodeURIComponent(authorizeCode)}`, undefined,
|
|
503
|
+
// Public (no bearer) — skip the preflight, exactly like approve-info.
|
|
504
|
+
{ cache: false, skipAuth: true });
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* MECHANISM B (relying party) — finalize an APPROVED, OAuth-bound request
|
|
508
|
+
* into a single-use OAuth authorization code.
|
|
509
|
+
*
|
|
510
|
+
* The OAuth counterpart of `auth.claimSession`: same secret credential,
|
|
511
|
+
* same single-use semantics, different output. Call it once the request the
|
|
512
|
+
* RP started with an `oauth` binding reports `authorized`; the server
|
|
513
|
+
* atomically mints exactly ONE `AuthCode` bound to the redirect URI, PKCE
|
|
514
|
+
* challenge, scopes, approving identity, and any delegated subject account
|
|
515
|
+
* the request was created with. A second call cannot mint another code.
|
|
516
|
+
*
|
|
517
|
+
* The result is an authorization CODE, never a token — the caller completes
|
|
518
|
+
* the flow with the ordinary PKCE exchange (`auth.oauth.exchangeCode`) using the
|
|
519
|
+
* `codeVerifier` it never sent anywhere. Nothing here is exposed to the
|
|
520
|
+
* popup: the code travels back through the registered callback, and the
|
|
521
|
+
* main window owns the verifier.
|
|
522
|
+
*
|
|
523
|
+
* Like `auth.claimSession`, this needs no Authorization header — the
|
|
524
|
+
* high-entropy SECRET `sessionToken` IS the credential. Never pass the
|
|
525
|
+
* public `authorizeCode` here; it is the approver's handle, not the
|
|
526
|
+
* initiator's. Every server-side failure (wrong/expired/already-finalized
|
|
527
|
+
* request, non-OAuth purpose, missing permission for the delegated account)
|
|
528
|
+
* surfaces as one generic error, so nothing about the request's state can be
|
|
529
|
+
* probed from outside.
|
|
530
|
+
*
|
|
531
|
+
* @param sessionToken - The secret token from {@link AuthCommonsApi.start}.
|
|
532
|
+
*/
|
|
533
|
+
async finalizeOAuth(sessionToken) {
|
|
534
|
+
const res = await this.ctx.request('POST', `/auth/session/finalize/${encodeURIComponent(sessionToken)}`, undefined,
|
|
535
|
+
// Body-authenticated by the path's secret token (no bearer) — skip the
|
|
536
|
+
// preflight, exactly like the device-flow claim.
|
|
537
|
+
{ cache: false, skipAuth: true });
|
|
538
|
+
if (res === null || typeof res !== 'object') {
|
|
539
|
+
throw new Error('auth/session/finalize returned an unexpected response shape');
|
|
540
|
+
}
|
|
541
|
+
const { code, redirectUri, expiresIn } = res;
|
|
542
|
+
// All three fields are load-bearing for the exchange that follows, so a
|
|
543
|
+
// partial payload is rejected outright rather than returned half-parsed.
|
|
544
|
+
if (typeof code !== 'string' ||
|
|
545
|
+
!code ||
|
|
546
|
+
typeof redirectUri !== 'string' ||
|
|
547
|
+
!redirectUri ||
|
|
548
|
+
typeof expiresIn !== 'number' ||
|
|
549
|
+
!Number.isFinite(expiresIn)) {
|
|
550
|
+
throw new Error('auth/session/finalize returned an incomplete authorization code');
|
|
551
|
+
}
|
|
552
|
+
return { code, redirectUri, expiresIn };
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* MECHANISM B (approver / Commons) — resolve the TRUSTED identity of a
|
|
556
|
+
* sign-in request from its public `authorizeCode`.
|
|
557
|
+
*
|
|
558
|
+
* The returned `application` and `subjectAccount` are resolved server-side
|
|
559
|
+
* and are the only safe things to display in the approval UI — NEVER trust
|
|
560
|
+
* the app/name/origin strings carried in the QR payload. Public (no auth
|
|
561
|
+
* required).
|
|
562
|
+
*
|
|
563
|
+
* @param authorizeCode - The public code scanned from the QR / deep-link.
|
|
564
|
+
*/
|
|
565
|
+
async approvalInfo(authorizeCode) {
|
|
566
|
+
const raw = await this.ctx.request('GET', `/auth/session/approve-info/${encodeURIComponent(authorizeCode)}`, undefined,
|
|
567
|
+
// Public (no auth required) — skip the bearer preflight (avoids the
|
|
568
|
+
// pre-session self-await class).
|
|
569
|
+
{ cache: false, skipAuth: true });
|
|
570
|
+
return {
|
|
571
|
+
application: raw.application,
|
|
572
|
+
scopes: raw.scopes,
|
|
573
|
+
boundOrigin: raw.boundOrigin,
|
|
574
|
+
// Fail-safe: only a literal boolean `true` counts as verified. A
|
|
575
|
+
// missing or non-boolean value (older server, malformed response)
|
|
576
|
+
// coerces to `false` so a stale server can never imply trust.
|
|
577
|
+
originVerified: raw.originVerified === true,
|
|
578
|
+
// Same discipline: a missing/blank/non-string label degrades to null
|
|
579
|
+
// (an older API, or a native requester with no browser to describe),
|
|
580
|
+
// and the approver simply omits the "where from" line.
|
|
581
|
+
requesterLabel: parseCommonsRequesterLabel(raw.requesterLabel),
|
|
582
|
+
// Same discipline: only the literal OAuth purpose opts into OAuth
|
|
583
|
+
// finalization. Anything else — including a server that predates this
|
|
584
|
+
// field — is the plain device sign-in it has always been.
|
|
585
|
+
purpose: raw.purpose === 'oauth_authorization' ? 'oauth_authorization' : 'device_sign_in',
|
|
586
|
+
// A missing/partial delegated account degrades to "no delegation"
|
|
587
|
+
// rather than a half-rendered "will act as" line.
|
|
588
|
+
subjectAccount: parseCommonsSubjectAccount(raw.subjectAccount),
|
|
589
|
+
expiresAt: raw.expiresAt,
|
|
590
|
+
status: raw.status,
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* MECHANISM B (approver / Commons) — approve a sign-in request by signing a
|
|
595
|
+
* fresh challenge with the PRIMARY local identity key.
|
|
596
|
+
*
|
|
597
|
+
* Commons holds the user's identity as its primary key (not the shared
|
|
598
|
+
* key), so this uses `signChallenge`. The signed-but-cookieless authorize
|
|
599
|
+
* endpoint resolves the user from the verified signer — the RP that started
|
|
600
|
+
* the flow then claims its session. Native-only (requires a local identity).
|
|
601
|
+
*
|
|
602
|
+
* @param params.authorizeCode - The public code being approved.
|
|
603
|
+
* @param params.deviceName - Optional human-readable device label.
|
|
604
|
+
* @param params.deviceFingerprint - Optional device fingerprint.
|
|
605
|
+
*/
|
|
606
|
+
async approve(params) {
|
|
607
|
+
const publicKey = await (await loadKeyManager()).getPublicKey();
|
|
608
|
+
if (!publicKey) {
|
|
609
|
+
throw new Error('No identity found on this device. Create or import an identity first.');
|
|
610
|
+
}
|
|
611
|
+
const { challenge } = await this.ctx.oxy.auth.requestChallenge(publicKey);
|
|
612
|
+
const signed = await (await loadSignatureService()).signChallenge(challenge);
|
|
613
|
+
return this.ctx.request('POST', `/auth/session/authorize-signed/${encodeURIComponent(params.authorizeCode)}`, {
|
|
614
|
+
// `signed.challenge` carries the SIGNATURE; `challenge` is the
|
|
615
|
+
// original server-issued challenge string.
|
|
616
|
+
publicKey: signed.publicKey,
|
|
617
|
+
challenge,
|
|
618
|
+
signature: signed.challenge,
|
|
619
|
+
timestamp: signed.timestamp,
|
|
620
|
+
...(params.deviceName ? { deviceName: params.deviceName } : {}),
|
|
621
|
+
...(params.deviceFingerprint ? { deviceFingerprint: params.deviceFingerprint } : {}),
|
|
622
|
+
},
|
|
623
|
+
// Key-signed, cookieless (no bearer) — skip the preflight.
|
|
624
|
+
{ cache: false, skipAuth: true });
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* MECHANISM B (approver / Commons) — deny a sign-in request, cancelling the
|
|
628
|
+
* device-flow session so the RP stops waiting.
|
|
629
|
+
*
|
|
630
|
+
* @param authorizeCode - The public code being denied.
|
|
631
|
+
* @param reason - Optional closed-set reason ({@link CommonsDenyReason}).
|
|
632
|
+
* Pass `'not_me'` ONLY when the user actually reported the request as one
|
|
633
|
+
* they did not start — the server records it as a suspicious denial rather
|
|
634
|
+
* than an ordinary cancel. Omitting it sends the exact body this endpoint
|
|
635
|
+
* has always received.
|
|
636
|
+
*/
|
|
637
|
+
async deny(authorizeCode, reason) {
|
|
638
|
+
return this.ctx.request('POST', `/auth/session/deny/${encodeURIComponent(authorizeCode)}`, reason ? { reason } : undefined,
|
|
639
|
+
// Public (no auth required) — skip the bearer preflight.
|
|
640
|
+
{ cache: false, skipAuth: true });
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
export class AuthApi {
|
|
644
|
+
constructor(ctx) {
|
|
645
|
+
this.ctx = ctx;
|
|
646
|
+
this.email = new AuthEmailApi(ctx);
|
|
647
|
+
this.password = new AuthPasswordApi(ctx);
|
|
648
|
+
this.totp = new AuthTotpApi(ctx);
|
|
649
|
+
this.oauth = new AuthOAuthApi(ctx);
|
|
650
|
+
this.commons = new AuthCommonsApi(ctx);
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* Register a new identity with public key authentication
|
|
654
|
+
* Identity is purely cryptographic - username and profile data are optional
|
|
655
|
+
*
|
|
656
|
+
* @param publicKey - The user's ECDSA public key (hex)
|
|
657
|
+
* @param signature - Signature of the registration request
|
|
658
|
+
* @param timestamp - Timestamp when the signature was created
|
|
659
|
+
*/
|
|
660
|
+
async registerKey(publicKey, signature, timestamp) {
|
|
661
|
+
// Advisory for now (server soft-enforces — see
|
|
662
|
+
// `SessionController.register`), but solved unconditionally so every
|
|
663
|
+
// client is already sending it once the server starts requiring it.
|
|
664
|
+
const { solveRegistrationPow } = await import('../crypto/internal.js');
|
|
665
|
+
const powNonce = await solveRegistrationPow(publicKey, timestamp);
|
|
666
|
+
const res = await this.ctx.request('POST', '/auth/register', {
|
|
667
|
+
publicKey,
|
|
668
|
+
signature,
|
|
669
|
+
timestamp,
|
|
670
|
+
powNonce,
|
|
671
|
+
}, { cache: false, skipAuth: true });
|
|
672
|
+
if (!res || (typeof res === 'object' && Object.keys(res).length === 0)) {
|
|
673
|
+
throw new OxyAuthenticationError('Registration failed', 'REGISTER_FAILED', 400);
|
|
674
|
+
}
|
|
675
|
+
return res;
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* Request an authentication challenge
|
|
679
|
+
* The client must sign this challenge with their private key
|
|
680
|
+
*
|
|
681
|
+
* @param publicKey - The user's public key
|
|
682
|
+
* @param requestOptions - Optional per-call transport overrides (`retry`,
|
|
683
|
+
* `timeout`). Interactive callers omit it (defaults keep retries); the
|
|
684
|
+
* cold-boot `shared-key-signin` step passes `{ retry: false }` so a slow
|
|
685
|
+
* network cannot multiply boot latency via the inner retry loop.
|
|
686
|
+
*/
|
|
687
|
+
async requestChallenge(publicKey, requestOptions) {
|
|
688
|
+
return this.ctx.request('POST', '/auth/challenge', {
|
|
689
|
+
publicKey,
|
|
690
|
+
}, { cache: false, skipAuth: true, ...requestOptions });
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* Verify a signed challenge and create a session
|
|
694
|
+
*
|
|
695
|
+
* @param publicKey - The user's public key
|
|
696
|
+
* @param challenge - The challenge string from requestChallenge
|
|
697
|
+
* @param signature - Signature of the auth message
|
|
698
|
+
* @param timestamp - Timestamp when the signature was created
|
|
699
|
+
* @param deviceName - Optional device name
|
|
700
|
+
* @param deviceFingerprint - Optional device fingerprint
|
|
701
|
+
* @param requestOptions - Optional per-call transport overrides (`retry`,
|
|
702
|
+
* `timeout`). Interactive callers omit it (defaults keep retries); the
|
|
703
|
+
* cold-boot `shared-key-signin` step passes `{ retry: false }` so a slow
|
|
704
|
+
* network cannot multiply boot latency via the inner retry loop.
|
|
705
|
+
* @param options.plantTokens - Install the returned bearer on this client
|
|
706
|
+
* (default `true`). A caller that must decide LATER whether the session is
|
|
707
|
+
* still wanted (the account dialog, whose user may have cancelled while the
|
|
708
|
+
* request was in flight) passes `false` and plants it itself.
|
|
709
|
+
*/
|
|
710
|
+
async verifyChallenge(publicKey, challenge, signature, timestamp, deviceName, deviceFingerprint, requestOptions, options = {}) {
|
|
711
|
+
const res = await this.ctx.request('POST', '/auth/verify', {
|
|
712
|
+
publicKey,
|
|
713
|
+
challenge,
|
|
714
|
+
signature,
|
|
715
|
+
timestamp,
|
|
716
|
+
deviceName,
|
|
717
|
+
deviceFingerprint,
|
|
718
|
+
}, { cache: false, skipAuth: true, ...requestOptions });
|
|
719
|
+
// Plant the freshly-minted tokens, mirroring `auth.claimSession`.
|
|
720
|
+
// `/auth/verify` returns the first access token (and refresh token) in
|
|
721
|
+
// its body, so installing it here means callers get an authenticated
|
|
722
|
+
// client without a second round-trip. Refresh stays in the httpOnly
|
|
723
|
+
// cookie slot set by the API.
|
|
724
|
+
if (res?.accessToken && options.plantTokens !== false) {
|
|
725
|
+
this.ctx.oxy.session.setAccessToken(res.accessToken);
|
|
726
|
+
}
|
|
727
|
+
return {
|
|
728
|
+
...res,
|
|
729
|
+
user: normalizeUserIdentity(res.user),
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
/**
|
|
733
|
+
* Check if a public key is already registered
|
|
734
|
+
*/
|
|
735
|
+
async isKeyRegistered(publicKey) {
|
|
736
|
+
return this.ctx.request('GET', `/auth/check-publickey/${encodeURIComponent(publicKey)}`, undefined, { cache: false, skipAuth: true });
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* Exchange a device-flow sessionToken for the first access token.
|
|
740
|
+
*
|
|
741
|
+
* The originating client holds a 128-bit `sessionToken` that nobody
|
|
742
|
+
* else has seen — it was generated client-side, sent once on
|
|
743
|
+
* `POST /auth/session/create`, and is never echoed back. After
|
|
744
|
+
* another authenticated device approves the session via
|
|
745
|
+
* `POST /auth/session/authorize/{sessionToken}` (bearer-authed) and
|
|
746
|
+
* the auth socket / poll loop notifies this client, the client
|
|
747
|
+
* exchanges its `sessionToken` here for the first access token,
|
|
748
|
+
* refresh token, sessionId, and the authorized user.
|
|
749
|
+
*
|
|
750
|
+
* This call requires no Authorization header — the high-entropy
|
|
751
|
+
* `sessionToken` IS the credential (RFC 8628 §3.4). The exchange is
|
|
752
|
+
* single-use; replay attempts are rejected with 401.
|
|
753
|
+
*
|
|
754
|
+
* @param sessionToken - The same sessionToken the SDK passed to
|
|
755
|
+
* `POST /auth/session/create` at the start of the flow.
|
|
756
|
+
* @param options.deviceFingerprint - Optional fingerprint of the
|
|
757
|
+
* originating client device.
|
|
758
|
+
* @param options.plantTokens - Install the claimed bearer on this client
|
|
759
|
+
* (default `true`). Pass `false` to install it yourself once you know the
|
|
760
|
+
* session is still wanted — see `verifyChallenge`.
|
|
761
|
+
*/
|
|
762
|
+
async claimSession(sessionToken, options = {}) {
|
|
763
|
+
// The device this client holds, so an official app's claim joins it
|
|
764
|
+
// (ADR 0029 D2). `null` opts out explicitly.
|
|
765
|
+
const device = options.device === undefined ? await this.ctx.oxy.session.readDeviceProof() : options.device;
|
|
766
|
+
const res = await this.ctx.request('POST', '/auth/session/claim', {
|
|
767
|
+
sessionToken,
|
|
768
|
+
...(options.deviceFingerprint ? { deviceFingerprint: options.deviceFingerprint } : {}),
|
|
769
|
+
...(device ? { device } : {}),
|
|
770
|
+
},
|
|
771
|
+
// Body-authenticated device-flow claim (no bearer) — skip the preflight.
|
|
772
|
+
{ cache: false, retry: false, skipAuth: true });
|
|
773
|
+
if (options.plantTokens !== false) {
|
|
774
|
+
this.ctx.oxy.session.setAccessToken(res.accessToken);
|
|
775
|
+
}
|
|
776
|
+
return res;
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* MECHANISM A — same-device shared-keychain SSO.
|
|
780
|
+
*
|
|
781
|
+
* Native-only. If this device holds a shared identity (the cross-app
|
|
782
|
+
* `group.so.oxy.shared` keychain key), prove control of it and mint a
|
|
783
|
+
* session: `requestChallenge(sharedPublicKey)` → `signChallengeWithSharedKey`
|
|
784
|
+
* → `verifyChallenge` (which plants the tokens). Returns `null` on web or
|
|
785
|
+
* when no shared identity is present — never throws for the absent-identity
|
|
786
|
+
* case, so a cold-boot caller can fall through to the next step.
|
|
787
|
+
*
|
|
788
|
+
* The cold-boot wiring that CALLS this lives in `OxyContext`
|
|
789
|
+
* (`@oxy.so/services`); this method just performs the exchange.
|
|
790
|
+
*
|
|
791
|
+
* @param opts.requestOptions - Optional per-call transport overrides
|
|
792
|
+
* (`retry`, `timeout`) forwarded to BOTH the `requestChallenge` and
|
|
793
|
+
* `verifyChallenge` round-trips. Interactive flows omit it (defaults keep
|
|
794
|
+
* retries); the cold-boot `shared-key-signin` step passes `{ retry: false }`
|
|
795
|
+
* so this network step cannot multiply boot latency via the inner retry
|
|
796
|
+
* loop. The token-refresh scheduler / 401 lane still retry later.
|
|
797
|
+
* @param opts.plantTokens - Forwarded to `verifyChallenge` (default `true`).
|
|
798
|
+
*/
|
|
799
|
+
async signInWithSharedIdentity(opts = {}) {
|
|
800
|
+
// `hasSharedIdentity()` already returns false on web (the shared
|
|
801
|
+
// keychain is native-only), so this short-circuits the web case without
|
|
802
|
+
// a wasted challenge round-trip.
|
|
803
|
+
const KeyManager = await loadKeyManager();
|
|
804
|
+
if (!(await KeyManager.hasSharedIdentity())) {
|
|
805
|
+
return null;
|
|
806
|
+
}
|
|
807
|
+
const sharedPublicKey = await KeyManager.getSharedPublicKey();
|
|
808
|
+
if (!sharedPublicKey) {
|
|
809
|
+
return null;
|
|
810
|
+
}
|
|
811
|
+
const { challenge } = await this.requestChallenge(sharedPublicKey, opts.requestOptions);
|
|
812
|
+
const signed = await (await loadSignatureService()).signChallengeWithSharedKey(challenge);
|
|
813
|
+
// `signed.challenge` carries the SIGNATURE (mirrors `signChallenge`).
|
|
814
|
+
return await this.verifyChallenge(signed.publicKey, challenge, signed.challenge, signed.timestamp, opts.deviceName, opts.deviceFingerprint, opts.requestOptions, { plantTokens: opts.plantTokens });
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
817
|
+
* Check username availability
|
|
818
|
+
*/
|
|
819
|
+
async checkUsername(username) {
|
|
820
|
+
// Public availability lookup (pre-session) — skip the bearer preflight.
|
|
821
|
+
return this.ctx.request('GET', `/auth/check-username/${username}`, undefined, { cache: false, skipAuth: true });
|
|
822
|
+
}
|
|
823
|
+
/**
|
|
824
|
+
* The authenticator's code (or a backup code) for a first factor's
|
|
825
|
+
* challenge → the session. Send the same device proof the first factor did
|
|
826
|
+
* (the default does).
|
|
827
|
+
*/
|
|
828
|
+
async completeSecondFactor(request) {
|
|
829
|
+
const { challengeId, code, ...options } = request;
|
|
830
|
+
const res = await this.ctx.request('POST', '/auth/signin/second-factor', { challengeId, code, ...(await signInEnvelope(this.ctx, options)) }, { cache: false, skipAuth: true });
|
|
831
|
+
return plantSession(this.ctx, res, 'auth/signin/second-factor');
|
|
832
|
+
}
|
|
833
|
+
async signUp(request) {
|
|
834
|
+
const { username, email, emailTicket, ...options } = request;
|
|
835
|
+
const res = await this.ctx.request('POST', '/auth/signup', { username, email, emailTicket, ...(await signInEnvelope(this.ctx, options)) }, { cache: false, skipAuth: true });
|
|
836
|
+
return plantSession(this.ctx, res, 'auth/signup');
|
|
837
|
+
}
|
|
838
|
+
async methods() {
|
|
839
|
+
const res = await this.ctx.request('GET', '/users/me/sign-in-methods', undefined, { cache: false });
|
|
840
|
+
const parsed = safeParseContract(signInMethodsSchema, res);
|
|
841
|
+
if (!parsed)
|
|
842
|
+
throw new Error('users/me/sign-in-methods returned an unexpected response shape');
|
|
843
|
+
return parsed;
|
|
844
|
+
}
|
|
845
|
+
/**
|
|
846
|
+
* Send a confirmation code to the signed-in account's email, for a
|
|
847
|
+
* `reauth: { emailCode: { verificationId, code } }` proof of ONE change:
|
|
848
|
+
* the code works only for the `action` it was asked for, and the email
|
|
849
|
+
* names it.
|
|
850
|
+
*/
|
|
851
|
+
async requestReauthCode(action) {
|
|
852
|
+
const res = await this.ctx.request('POST', '/users/me/reauth/email', { action }, { cache: false });
|
|
853
|
+
const parsed = safeParseContract(emailVerificationStartResponseSchema, res);
|
|
854
|
+
if (!parsed)
|
|
855
|
+
throw new Error('users/me/reauth/email returned an unexpected response shape');
|
|
856
|
+
return parsed;
|
|
857
|
+
}
|
|
858
|
+
}
|