@oxyhq/services 22.12.4 → 22.13.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 +16 -46
- package/lib/commonjs/index.js +18 -12
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/ui/boot/runProviderColdBoot.js +51 -9
- package/lib/commonjs/ui/boot/runProviderColdBoot.js.map +1 -1
- package/lib/commonjs/ui/client.js +0 -13
- package/lib/commonjs/ui/client.js.map +1 -1
- package/lib/commonjs/ui/components/OxyAccountDialogScreen.js +27 -4
- package/lib/commonjs/ui/components/OxyAccountDialogScreen.js.map +1 -1
- package/lib/commonjs/ui/components/OxyAuthChooser.js +72 -1123
- package/lib/commonjs/ui/components/OxyAuthChooser.js.map +1 -1
- package/lib/commonjs/ui/components/OxyConsentScreen.js +50 -12
- package/lib/commonjs/ui/components/OxyConsentScreen.js.map +1 -1
- package/lib/commonjs/ui/components/OxyProvider.js +4 -4
- package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
- package/lib/commonjs/ui/components/OxySignInButton.js +74 -40
- package/lib/commonjs/ui/components/OxySignInButton.js.map +1 -1
- package/lib/commonjs/ui/components/authChooser/AccountsMenuView.js +527 -0
- package/lib/commonjs/ui/components/authChooser/AccountsMenuView.js.map +1 -0
- package/lib/commonjs/ui/components/authChooser/SignInEntryView.js +90 -0
- package/lib/commonjs/ui/components/authChooser/SignInEntryView.js.map +1 -0
- package/lib/commonjs/ui/components/authChooser/SignInRequestView.js +144 -0
- package/lib/commonjs/ui/components/authChooser/SignInRequestView.js.map +1 -0
- package/lib/commonjs/ui/components/authChooser/SignUpView.js +188 -0
- package/lib/commonjs/ui/components/authChooser/SignUpView.js.map +1 -0
- package/lib/commonjs/ui/components/authChooser/TroubleDisclosure.js +63 -0
- package/lib/commonjs/ui/components/authChooser/TroubleDisclosure.js.map +1 -0
- package/lib/commonjs/ui/components/authChooser/primitives.js +169 -0
- package/lib/commonjs/ui/components/authChooser/primitives.js.map +1 -0
- package/lib/commonjs/ui/components/authChooser/requestSurfaces.js +149 -0
- package/lib/commonjs/ui/components/authChooser/requestSurfaces.js.map +1 -0
- package/lib/commonjs/ui/components/authChooser/signInProgress.js +54 -0
- package/lib/commonjs/ui/components/authChooser/signInProgress.js.map +1 -0
- package/lib/commonjs/ui/components/{oxyAuthChooserStyles.js → authChooser/styles.js} +19 -1
- package/lib/commonjs/ui/components/authChooser/styles.js.map +1 -0
- package/lib/commonjs/ui/components/authChooser/types.js +59 -0
- package/lib/commonjs/ui/components/authChooser/types.js.map +1 -0
- package/lib/commonjs/ui/components/authChooser/useUsernameAvailability.js +50 -0
- package/lib/commonjs/ui/components/authChooser/useUsernameAvailability.js.map +1 -0
- package/lib/commonjs/ui/context/OxyContext.js +205 -30
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/context/commitSessionFlow.js +46 -0
- package/lib/commonjs/ui/context/commitSessionFlow.js.map +1 -1
- package/lib/commonjs/ui/context/hooks/useAuthOperations.js +15 -2
- package/lib/commonjs/ui/context/hooks/useAuthOperations.js.map +1 -1
- package/lib/commonjs/ui/context/useOxyAccountGraph.js +13 -5
- package/lib/commonjs/ui/context/useOxyAccountGraph.js.map +1 -1
- package/lib/commonjs/ui/hooks/useSwitchableAccounts.js +6 -1
- package/lib/commonjs/ui/hooks/useSwitchableAccounts.js.map +1 -1
- package/lib/commonjs/ui/index.js +0 -13
- package/lib/commonjs/ui/index.js.map +1 -1
- package/lib/commonjs/ui/oauth/browserAuthTransport.js +170 -0
- package/lib/commonjs/ui/oauth/browserAuthTransport.js.map +1 -0
- package/lib/commonjs/ui/oauth/completeOAuthCode.js +78 -0
- package/lib/commonjs/ui/oauth/completeOAuthCode.js.map +1 -0
- package/lib/commonjs/ui/oauth/legacyRedirectLanes.js +48 -0
- package/lib/commonjs/ui/oauth/legacyRedirectLanes.js.map +1 -0
- package/lib/commonjs/ui/oauth/oauthHandshake.js +53 -0
- package/lib/commonjs/ui/oauth/oauthHandshake.js.map +1 -0
- package/lib/commonjs/ui/oauth/oauthPopup.js +276 -0
- package/lib/commonjs/ui/oauth/oauthPopup.js.map +1 -0
- package/lib/commonjs/ui/oauth/oauthPopupMessages.js +141 -0
- package/lib/commonjs/ui/oauth/oauthPopupMessages.js.map +1 -0
- package/lib/commonjs/ui/oauth/types.js +2 -0
- package/lib/commonjs/ui/oauth/types.js.map +1 -0
- package/lib/commonjs/ui/server.js +2 -5
- package/lib/commonjs/ui/server.js.map +1 -1
- package/lib/commonjs/ui/session/authStore.js +6 -75
- package/lib/commonjs/ui/session/authStore.js.map +1 -1
- package/lib/commonjs/ui/session/createSessionClient.js +11 -3
- package/lib/commonjs/ui/session/createSessionClient.js.map +1 -1
- package/lib/commonjs/ui/session/identityBinding.js +95 -0
- package/lib/commonjs/ui/session/identityBinding.js.map +1 -0
- package/lib/commonjs/ui/session/index.js +13 -0
- package/lib/commonjs/ui/session/index.js.map +1 -1
- package/lib/commonjs/ui/session/nativeSecureStorage.js +90 -0
- package/lib/commonjs/ui/session/nativeSecureStorage.js.map +1 -0
- package/lib/commonjs/ui/session/tokenTransport.js +11 -1
- package/lib/commonjs/ui/session/tokenTransport.js.map +1 -1
- package/lib/commonjs/ui/utils/crossOriginRestore.js +5 -4
- package/lib/commonjs/ui/utils/crossOriginRestore.js.map +1 -1
- package/lib/commonjs/ui/utils/deliveryPlatform.js +58 -0
- package/lib/commonjs/ui/utils/deliveryPlatform.js.map +1 -0
- package/lib/commonjs/ui/utils/oauthReturn.js +72 -46
- package/lib/commonjs/ui/utils/oauthReturn.js.map +1 -1
- package/lib/module/index.js +9 -4
- package/lib/module/index.js.map +1 -1
- package/lib/module/ui/boot/runProviderColdBoot.js +51 -9
- package/lib/module/ui/boot/runProviderColdBoot.js.map +1 -1
- package/lib/module/ui/client.js +0 -1
- package/lib/module/ui/client.js.map +1 -1
- package/lib/module/ui/components/OxyAccountDialogScreen.js +27 -4
- package/lib/module/ui/components/OxyAccountDialogScreen.js.map +1 -1
- package/lib/module/ui/components/OxyAuthChooser.js +76 -1128
- package/lib/module/ui/components/OxyAuthChooser.js.map +1 -1
- package/lib/module/ui/components/OxyConsentScreen.js +50 -12
- package/lib/module/ui/components/OxyConsentScreen.js.map +1 -1
- package/lib/module/ui/components/OxyProvider.js +4 -4
- package/lib/module/ui/components/OxyProvider.js.map +1 -1
- package/lib/module/ui/components/OxySignInButton.js +76 -42
- package/lib/module/ui/components/OxySignInButton.js.map +1 -1
- package/lib/module/ui/components/authChooser/AccountsMenuView.js +523 -0
- package/lib/module/ui/components/authChooser/AccountsMenuView.js.map +1 -0
- package/lib/module/ui/components/authChooser/SignInEntryView.js +85 -0
- package/lib/module/ui/components/authChooser/SignInEntryView.js.map +1 -0
- package/lib/module/ui/components/authChooser/SignInRequestView.js +139 -0
- package/lib/module/ui/components/authChooser/SignInRequestView.js.map +1 -0
- package/lib/module/ui/components/authChooser/SignUpView.js +184 -0
- package/lib/module/ui/components/authChooser/SignUpView.js.map +1 -0
- package/lib/module/ui/components/authChooser/TroubleDisclosure.js +59 -0
- package/lib/module/ui/components/authChooser/TroubleDisclosure.js.map +1 -0
- package/lib/module/ui/components/authChooser/primitives.js +163 -0
- package/lib/module/ui/components/authChooser/primitives.js.map +1 -0
- package/lib/module/ui/components/authChooser/requestSurfaces.js +139 -0
- package/lib/module/ui/components/authChooser/requestSurfaces.js.map +1 -0
- package/lib/module/ui/components/authChooser/signInProgress.js +50 -0
- package/lib/module/ui/components/authChooser/signInProgress.js.map +1 -0
- package/lib/module/ui/components/{oxyAuthChooserStyles.js → authChooser/styles.js} +19 -1
- package/lib/module/ui/components/authChooser/styles.js.map +1 -0
- package/lib/module/ui/components/authChooser/types.js +55 -0
- package/lib/module/ui/components/authChooser/types.js.map +1 -0
- package/lib/module/ui/components/authChooser/useUsernameAvailability.js +46 -0
- package/lib/module/ui/components/authChooser/useUsernameAvailability.js.map +1 -0
- package/lib/module/ui/context/OxyContext.js +208 -33
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/context/commitSessionFlow.js +45 -0
- package/lib/module/ui/context/commitSessionFlow.js.map +1 -1
- package/lib/module/ui/context/hooks/useAuthOperations.js +15 -2
- package/lib/module/ui/context/hooks/useAuthOperations.js.map +1 -1
- package/lib/module/ui/context/useOxyAccountGraph.js +13 -5
- package/lib/module/ui/context/useOxyAccountGraph.js.map +1 -1
- package/lib/module/ui/hooks/useSwitchableAccounts.js +6 -1
- package/lib/module/ui/hooks/useSwitchableAccounts.js.map +1 -1
- package/lib/module/ui/index.js +0 -1
- package/lib/module/ui/index.js.map +1 -1
- package/lib/module/ui/oauth/browserAuthTransport.js +167 -0
- package/lib/module/ui/oauth/browserAuthTransport.js.map +1 -0
- package/lib/module/ui/oauth/completeOAuthCode.js +75 -0
- package/lib/module/ui/oauth/completeOAuthCode.js.map +1 -0
- package/lib/module/ui/oauth/legacyRedirectLanes.js +44 -0
- package/lib/module/ui/oauth/legacyRedirectLanes.js.map +1 -0
- package/lib/module/ui/oauth/oauthHandshake.js +50 -0
- package/lib/module/ui/oauth/oauthHandshake.js.map +1 -0
- package/lib/module/ui/oauth/oauthPopup.js +268 -0
- package/lib/module/ui/oauth/oauthPopup.js.map +1 -0
- package/lib/module/ui/oauth/oauthPopupMessages.js +135 -0
- package/lib/module/ui/oauth/oauthPopupMessages.js.map +1 -0
- package/lib/module/ui/oauth/types.js +2 -0
- package/lib/module/ui/oauth/types.js.map +1 -0
- package/lib/module/ui/server.js +0 -2
- package/lib/module/ui/server.js.map +1 -1
- package/lib/module/ui/session/authStore.js +5 -75
- package/lib/module/ui/session/authStore.js.map +1 -1
- package/lib/module/ui/session/createSessionClient.js +11 -3
- package/lib/module/ui/session/createSessionClient.js.map +1 -1
- package/lib/module/ui/session/identityBinding.js +89 -0
- package/lib/module/ui/session/identityBinding.js.map +1 -0
- package/lib/module/ui/session/index.js +5 -2
- package/lib/module/ui/session/index.js.map +1 -1
- package/lib/module/ui/session/nativeSecureStorage.js +88 -0
- package/lib/module/ui/session/nativeSecureStorage.js.map +1 -0
- package/lib/module/ui/session/tokenTransport.js +11 -1
- package/lib/module/ui/session/tokenTransport.js.map +1 -1
- package/lib/module/ui/utils/crossOriginRestore.js +6 -5
- package/lib/module/ui/utils/crossOriginRestore.js.map +1 -1
- package/lib/module/ui/utils/deliveryPlatform.js +55 -0
- package/lib/module/ui/utils/deliveryPlatform.js.map +1 -0
- package/lib/module/ui/utils/oauthReturn.js +73 -47
- package/lib/module/ui/utils/oauthReturn.js.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +4 -1
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/boot/runProviderColdBoot.d.ts +34 -2
- package/lib/typescript/commonjs/ui/boot/runProviderColdBoot.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/client.d.ts +0 -1
- package/lib/typescript/commonjs/ui/client.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/OxyAccountDialogScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/OxyAuthChooser.d.ts +21 -29
- package/lib/typescript/commonjs/ui/components/OxyAuthChooser.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/OxyConsentScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/OxyProvider.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/OxySignInButton.d.ts +17 -12
- package/lib/typescript/commonjs/ui/components/OxySignInButton.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/authChooser/AccountsMenuView.d.ts +38 -0
- package/lib/typescript/commonjs/ui/components/authChooser/AccountsMenuView.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/authChooser/SignInEntryView.d.ts +32 -0
- package/lib/typescript/commonjs/ui/components/authChooser/SignInEntryView.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/authChooser/SignInRequestView.d.ts +34 -0
- package/lib/typescript/commonjs/ui/components/authChooser/SignInRequestView.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/authChooser/SignUpView.d.ts +34 -0
- package/lib/typescript/commonjs/ui/components/authChooser/SignUpView.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/authChooser/TroubleDisclosure.d.ts +43 -0
- package/lib/typescript/commonjs/ui/components/authChooser/TroubleDisclosure.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/authChooser/primitives.d.ts +55 -0
- package/lib/typescript/commonjs/ui/components/authChooser/primitives.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/authChooser/requestSurfaces.d.ts +54 -0
- package/lib/typescript/commonjs/ui/components/authChooser/requestSurfaces.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/authChooser/signInProgress.d.ts +26 -0
- package/lib/typescript/commonjs/ui/components/authChooser/signInProgress.d.ts.map +1 -0
- package/lib/typescript/{module/ui/components/oxyAuthChooserStyles.d.ts → commonjs/ui/components/authChooser/styles.d.ts} +19 -1
- package/lib/typescript/commonjs/ui/components/authChooser/styles.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/authChooser/types.d.ts +85 -0
- package/lib/typescript/commonjs/ui/components/authChooser/types.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/authChooser/useUsernameAvailability.d.ts +16 -0
- package/lib/typescript/commonjs/ui/components/authChooser/useUsernameAvailability.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/commitSessionFlow.d.ts +40 -0
- package/lib/typescript/commonjs/ui/context/commitSessionFlow.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts +11 -2
- package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/oxyContextTypes.d.ts +44 -1
- package/lib/typescript/commonjs/ui/context/oxyContextTypes.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/useOxyAccountGraph.d.ts +7 -1
- package/lib/typescript/commonjs/ui/context/useOxyAccountGraph.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/useSwitchableAccounts.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/index.d.ts +0 -1
- package/lib/typescript/commonjs/ui/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/oauth/browserAuthTransport.d.ts +56 -0
- package/lib/typescript/commonjs/ui/oauth/browserAuthTransport.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/oauth/completeOAuthCode.d.ts +49 -0
- package/lib/typescript/commonjs/ui/oauth/completeOAuthCode.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/oauth/legacyRedirectLanes.d.ts +40 -0
- package/lib/typescript/commonjs/ui/oauth/legacyRedirectLanes.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/oauth/oauthHandshake.d.ts +50 -0
- package/lib/typescript/commonjs/ui/oauth/oauthHandshake.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/oauth/oauthPopup.d.ts +83 -0
- package/lib/typescript/commonjs/ui/oauth/oauthPopup.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/oauth/oauthPopupMessages.d.ts +67 -0
- package/lib/typescript/commonjs/ui/oauth/oauthPopupMessages.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/oauth/types.d.ts +176 -0
- package/lib/typescript/commonjs/ui/oauth/types.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/server.d.ts +0 -2
- package/lib/typescript/commonjs/ui/server.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/session/authStore.d.ts +3 -7
- package/lib/typescript/commonjs/ui/session/authStore.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/session/createSessionClient.d.ts +8 -1
- package/lib/typescript/commonjs/ui/session/createSessionClient.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/session/identityBinding.d.ts +66 -0
- package/lib/typescript/commonjs/ui/session/identityBinding.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/session/index.d.ts +6 -2
- package/lib/typescript/commonjs/ui/session/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/session/nativeSecureStorage.d.ts +25 -0
- package/lib/typescript/commonjs/ui/session/nativeSecureStorage.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/session/tokenTransport.d.ts +8 -1
- package/lib/typescript/commonjs/ui/session/tokenTransport.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/types/navigation.d.ts +44 -0
- package/lib/typescript/commonjs/ui/types/navigation.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/utils/crossOriginRestore.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/utils/deliveryPlatform.d.ts +33 -0
- package/lib/typescript/commonjs/ui/utils/deliveryPlatform.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/utils/oauthReturn.d.ts +30 -16
- package/lib/typescript/commonjs/ui/utils/oauthReturn.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +4 -1
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/ui/boot/runProviderColdBoot.d.ts +34 -2
- package/lib/typescript/module/ui/boot/runProviderColdBoot.d.ts.map +1 -1
- package/lib/typescript/module/ui/client.d.ts +0 -1
- package/lib/typescript/module/ui/client.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/OxyAccountDialogScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/OxyAuthChooser.d.ts +21 -29
- package/lib/typescript/module/ui/components/OxyAuthChooser.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/OxyConsentScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/OxyProvider.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/OxySignInButton.d.ts +17 -12
- package/lib/typescript/module/ui/components/OxySignInButton.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/authChooser/AccountsMenuView.d.ts +38 -0
- package/lib/typescript/module/ui/components/authChooser/AccountsMenuView.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/authChooser/SignInEntryView.d.ts +32 -0
- package/lib/typescript/module/ui/components/authChooser/SignInEntryView.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/authChooser/SignInRequestView.d.ts +34 -0
- package/lib/typescript/module/ui/components/authChooser/SignInRequestView.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/authChooser/SignUpView.d.ts +34 -0
- package/lib/typescript/module/ui/components/authChooser/SignUpView.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/authChooser/TroubleDisclosure.d.ts +43 -0
- package/lib/typescript/module/ui/components/authChooser/TroubleDisclosure.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/authChooser/primitives.d.ts +55 -0
- package/lib/typescript/module/ui/components/authChooser/primitives.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/authChooser/requestSurfaces.d.ts +54 -0
- package/lib/typescript/module/ui/components/authChooser/requestSurfaces.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/authChooser/signInProgress.d.ts +26 -0
- package/lib/typescript/module/ui/components/authChooser/signInProgress.d.ts.map +1 -0
- package/lib/typescript/{commonjs/ui/components/oxyAuthChooserStyles.d.ts → module/ui/components/authChooser/styles.d.ts} +19 -1
- package/lib/typescript/module/ui/components/authChooser/styles.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/authChooser/types.d.ts +85 -0
- package/lib/typescript/module/ui/components/authChooser/types.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/authChooser/useUsernameAvailability.d.ts +16 -0
- package/lib/typescript/module/ui/components/authChooser/useUsernameAvailability.d.ts.map +1 -0
- package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/commitSessionFlow.d.ts +40 -0
- package/lib/typescript/module/ui/context/commitSessionFlow.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts +11 -2
- package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/oxyContextTypes.d.ts +44 -1
- package/lib/typescript/module/ui/context/oxyContextTypes.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/useOxyAccountGraph.d.ts +7 -1
- package/lib/typescript/module/ui/context/useOxyAccountGraph.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/useSwitchableAccounts.d.ts.map +1 -1
- package/lib/typescript/module/ui/index.d.ts +0 -1
- package/lib/typescript/module/ui/index.d.ts.map +1 -1
- package/lib/typescript/module/ui/oauth/browserAuthTransport.d.ts +56 -0
- package/lib/typescript/module/ui/oauth/browserAuthTransport.d.ts.map +1 -0
- package/lib/typescript/module/ui/oauth/completeOAuthCode.d.ts +49 -0
- package/lib/typescript/module/ui/oauth/completeOAuthCode.d.ts.map +1 -0
- package/lib/typescript/module/ui/oauth/legacyRedirectLanes.d.ts +40 -0
- package/lib/typescript/module/ui/oauth/legacyRedirectLanes.d.ts.map +1 -0
- package/lib/typescript/module/ui/oauth/oauthHandshake.d.ts +50 -0
- package/lib/typescript/module/ui/oauth/oauthHandshake.d.ts.map +1 -0
- package/lib/typescript/module/ui/oauth/oauthPopup.d.ts +83 -0
- package/lib/typescript/module/ui/oauth/oauthPopup.d.ts.map +1 -0
- package/lib/typescript/module/ui/oauth/oauthPopupMessages.d.ts +67 -0
- package/lib/typescript/module/ui/oauth/oauthPopupMessages.d.ts.map +1 -0
- package/lib/typescript/module/ui/oauth/types.d.ts +176 -0
- package/lib/typescript/module/ui/oauth/types.d.ts.map +1 -0
- package/lib/typescript/module/ui/server.d.ts +0 -2
- package/lib/typescript/module/ui/server.d.ts.map +1 -1
- package/lib/typescript/module/ui/session/authStore.d.ts +3 -7
- package/lib/typescript/module/ui/session/authStore.d.ts.map +1 -1
- package/lib/typescript/module/ui/session/createSessionClient.d.ts +8 -1
- package/lib/typescript/module/ui/session/createSessionClient.d.ts.map +1 -1
- package/lib/typescript/module/ui/session/identityBinding.d.ts +66 -0
- package/lib/typescript/module/ui/session/identityBinding.d.ts.map +1 -0
- package/lib/typescript/module/ui/session/index.d.ts +6 -2
- package/lib/typescript/module/ui/session/index.d.ts.map +1 -1
- package/lib/typescript/module/ui/session/nativeSecureStorage.d.ts +25 -0
- package/lib/typescript/module/ui/session/nativeSecureStorage.d.ts.map +1 -0
- package/lib/typescript/module/ui/session/tokenTransport.d.ts +8 -1
- package/lib/typescript/module/ui/session/tokenTransport.d.ts.map +1 -1
- package/lib/typescript/module/ui/types/navigation.d.ts +44 -0
- package/lib/typescript/module/ui/types/navigation.d.ts.map +1 -1
- package/lib/typescript/module/ui/utils/crossOriginRestore.d.ts.map +1 -1
- package/lib/typescript/module/ui/utils/deliveryPlatform.d.ts +33 -0
- package/lib/typescript/module/ui/utils/deliveryPlatform.d.ts.map +1 -0
- package/lib/typescript/module/ui/utils/oauthReturn.d.ts +30 -16
- package/lib/typescript/module/ui/utils/oauthReturn.d.ts.map +1 -1
- package/package.json +6 -8
- package/src/index.ts +19 -5
- package/src/ui/boot/__tests__/coldBootWebAuthMode.test.ts +281 -0
- package/src/ui/boot/runProviderColdBoot.ts +83 -13
- package/src/ui/client.ts +0 -1
- package/src/ui/components/OxyAccountDialogScreen.tsx +33 -4
- package/src/ui/components/OxyAuthChooser.tsx +100 -1230
- package/src/ui/components/OxyConsentScreen.tsx +56 -13
- package/src/ui/components/OxyProvider.tsx +4 -4
- package/src/ui/components/OxySignInButton.tsx +119 -78
- package/src/ui/components/__tests__/OxyConsentScreen.test.tsx +27 -0
- package/src/ui/components/authChooser/AccountsMenuView.tsx +569 -0
- package/src/ui/components/authChooser/SignInEntryView.tsx +115 -0
- package/src/ui/components/authChooser/SignInRequestView.tsx +163 -0
- package/src/ui/components/authChooser/SignUpView.tsx +174 -0
- package/src/ui/components/authChooser/TroubleDisclosure.tsx +81 -0
- package/src/ui/components/authChooser/primitives.tsx +144 -0
- package/src/ui/components/authChooser/requestSurfaces.tsx +116 -0
- package/src/ui/components/authChooser/signInProgress.ts +54 -0
- package/src/ui/components/{oxyAuthChooserStyles.ts → authChooser/styles.ts} +18 -0
- package/src/ui/components/authChooser/types.ts +101 -0
- package/src/ui/components/authChooser/useUsernameAvailability.ts +60 -0
- package/src/ui/context/OxyContext.tsx +226 -24
- package/src/ui/context/__tests__/commitSessionFlow.test.ts +102 -0
- package/src/ui/context/commitSessionFlow.ts +64 -0
- package/src/ui/context/hooks/useAuthOperations.ts +26 -1
- package/src/ui/context/oxyContextTypes.ts +45 -1
- package/src/ui/context/useOxyAccountGraph.ts +19 -5
- package/src/ui/hooks/useSwitchableAccounts.ts +12 -1
- package/src/ui/index.ts +0 -1
- package/src/ui/oauth/__tests__/browserAuthTransport.test.ts +335 -0
- package/src/ui/oauth/__tests__/completeOAuthCode.test.ts +142 -0
- package/src/ui/oauth/__tests__/oauthPopup.test.ts +352 -0
- package/src/ui/oauth/__tests__/oauthPopupMessages.test.ts +189 -0
- package/src/ui/oauth/__tests__/sharedCompletionPath.test.ts +174 -0
- package/src/ui/oauth/browserAuthTransport.ts +193 -0
- package/src/ui/oauth/completeOAuthCode.ts +93 -0
- package/src/ui/oauth/legacyRedirectLanes.ts +43 -0
- package/src/ui/oauth/oauthHandshake.ts +74 -0
- package/src/ui/oauth/oauthPopup.ts +277 -0
- package/src/ui/oauth/oauthPopupMessages.ts +136 -0
- package/src/ui/oauth/types.ts +150 -0
- package/src/ui/server.ts +0 -2
- package/src/ui/session/__tests__/createSessionClient.test.ts +26 -0
- package/src/ui/session/__tests__/tokenTransport.test.ts +34 -0
- package/src/ui/session/authStore.ts +5 -89
- package/src/ui/session/createSessionClient.ts +15 -2
- package/src/ui/session/identityBinding.ts +116 -0
- package/src/ui/session/index.ts +6 -2
- package/src/ui/session/nativeSecureStorage.ts +98 -0
- package/src/ui/session/tokenTransport.ts +14 -1
- package/src/ui/types/navigation.ts +44 -1
- package/src/ui/utils/__tests__/crossOriginRestore.test.ts +17 -0
- package/src/ui/utils/crossOriginRestore.ts +9 -13
- package/src/ui/utils/deliveryPlatform.ts +65 -0
- package/src/ui/utils/oauthReturn.ts +79 -65
- package/lib/commonjs/assets/assets/fonts/Inter/Inter_18pt-Black.ttf +0 -0
- package/lib/commonjs/assets/assets/fonts/Inter/Inter_18pt-Bold.ttf +0 -0
- package/lib/commonjs/assets/assets/fonts/Inter/Inter_18pt-ExtraBold.ttf +0 -0
- package/lib/commonjs/assets/assets/fonts/Inter/Inter_18pt-Light.ttf +0 -0
- package/lib/commonjs/assets/assets/fonts/Inter/Inter_18pt-Medium.ttf +0 -0
- package/lib/commonjs/assets/assets/fonts/Inter/Inter_18pt-Regular.ttf +0 -0
- package/lib/commonjs/assets/assets/fonts/Inter/Inter_18pt-SemiBold.ttf +0 -0
- package/lib/commonjs/assets/fonts/Inter/Inter_18pt-Black.ttf +0 -0
- package/lib/commonjs/assets/fonts/Inter/Inter_18pt-Bold.ttf +0 -0
- package/lib/commonjs/assets/fonts/Inter/Inter_18pt-ExtraBold.ttf +0 -0
- package/lib/commonjs/assets/fonts/Inter/Inter_18pt-Light.ttf +0 -0
- package/lib/commonjs/assets/fonts/Inter/Inter_18pt-Medium.ttf +0 -0
- package/lib/commonjs/assets/fonts/Inter/Inter_18pt-Regular.ttf +0 -0
- package/lib/commonjs/assets/fonts/Inter/Inter_18pt-SemiBold.ttf +0 -0
- package/lib/commonjs/ui/components/FontLoader.js +0 -35
- package/lib/commonjs/ui/components/FontLoader.js.map +0 -1
- package/lib/commonjs/ui/components/FontLoader.native.js +0 -88
- package/lib/commonjs/ui/components/FontLoader.native.js.map +0 -1
- package/lib/commonjs/ui/components/oxyAuthChooserStyles.js.map +0 -1
- package/lib/module/assets/assets/fonts/Inter/Inter_18pt-Black.ttf +0 -0
- package/lib/module/assets/assets/fonts/Inter/Inter_18pt-Bold.ttf +0 -0
- package/lib/module/assets/assets/fonts/Inter/Inter_18pt-ExtraBold.ttf +0 -0
- package/lib/module/assets/assets/fonts/Inter/Inter_18pt-Light.ttf +0 -0
- package/lib/module/assets/assets/fonts/Inter/Inter_18pt-Medium.ttf +0 -0
- package/lib/module/assets/assets/fonts/Inter/Inter_18pt-Regular.ttf +0 -0
- package/lib/module/assets/assets/fonts/Inter/Inter_18pt-SemiBold.ttf +0 -0
- package/lib/module/assets/fonts/Inter/Inter_18pt-Black.ttf +0 -0
- package/lib/module/assets/fonts/Inter/Inter_18pt-Bold.ttf +0 -0
- package/lib/module/assets/fonts/Inter/Inter_18pt-ExtraBold.ttf +0 -0
- package/lib/module/assets/fonts/Inter/Inter_18pt-Light.ttf +0 -0
- package/lib/module/assets/fonts/Inter/Inter_18pt-Medium.ttf +0 -0
- package/lib/module/assets/fonts/Inter/Inter_18pt-Regular.ttf +0 -0
- package/lib/module/assets/fonts/Inter/Inter_18pt-SemiBold.ttf +0 -0
- package/lib/module/ui/components/FontLoader.js +0 -29
- package/lib/module/ui/components/FontLoader.js.map +0 -1
- package/lib/module/ui/components/FontLoader.native.js +0 -83
- package/lib/module/ui/components/FontLoader.native.js.map +0 -1
- package/lib/module/ui/components/oxyAuthChooserStyles.js.map +0 -1
- package/lib/typescript/commonjs/ui/components/FontLoader.d.ts +0 -24
- package/lib/typescript/commonjs/ui/components/FontLoader.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/components/FontLoader.native.d.ts +0 -13
- package/lib/typescript/commonjs/ui/components/FontLoader.native.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/components/oxyAuthChooserStyles.d.ts.map +0 -1
- package/lib/typescript/module/ui/components/FontLoader.d.ts +0 -24
- package/lib/typescript/module/ui/components/FontLoader.d.ts.map +0 -1
- package/lib/typescript/module/ui/components/FontLoader.native.d.ts +0 -13
- package/lib/typescript/module/ui/components/FontLoader.native.d.ts.map +0 -1
- package/lib/typescript/module/ui/components/oxyAuthChooserStyles.d.ts.map +0 -1
- package/src/assets/fonts/Inter/Inter_18pt-Black.ttf +0 -0
- package/src/assets/fonts/Inter/Inter_18pt-Bold.ttf +0 -0
- package/src/assets/fonts/Inter/Inter_18pt-ExtraBold.ttf +0 -0
- package/src/assets/fonts/Inter/Inter_18pt-Light.ttf +0 -0
- package/src/assets/fonts/Inter/Inter_18pt-Medium.ttf +0 -0
- package/src/assets/fonts/Inter/Inter_18pt-Regular.ttf +0 -0
- package/src/assets/fonts/Inter/Inter_18pt-SemiBold.ttf +0 -0
- package/src/ui/components/FontLoader.native.tsx +0 -88
- package/src/ui/components/FontLoader.tsx +0 -27
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Popup lifecycle for the web OAuth transport.
|
|
3
|
+
*
|
|
4
|
+
* Owns the window itself — opening it, navigating it, watching it, and tearing
|
|
5
|
+
* every listener and timer down — and nothing else: message validation lives in
|
|
6
|
+
* `oauthPopupMessages.ts`, and the code exchange in `completeOAuthCode.ts`.
|
|
7
|
+
*
|
|
8
|
+
* Three browser realities shape this module:
|
|
9
|
+
*
|
|
10
|
+
* - **Gesture attribution.** `window.open` only succeeds while the user's click
|
|
11
|
+
* is still being handled. Anything awaited first (resolving the application,
|
|
12
|
+
* generating PKCE) loses it and the popup is silently blocked. So the window
|
|
13
|
+
* is opened EMPTY up front and navigated once the authorize URL exists —
|
|
14
|
+
* the same shape as `passkeyHubPopup.ts`.
|
|
15
|
+
* - **No close event.** A cross-origin popup fires nothing when the user
|
|
16
|
+
* dismisses it; polling `closed` is the only mechanism (as in core's
|
|
17
|
+
* `AccountDialogController.watchPopup`).
|
|
18
|
+
* - **No cross-origin reads.** The popup's URL, title, and document are all
|
|
19
|
+
* opaque. The authorization result arrives ONLY as a `postMessage`.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { canonicalizeOAuthRedirectUri, logger } from '@oxyhq/core';
|
|
23
|
+
import { readOAuthPopupMessage } from './oauthPopupMessages';
|
|
24
|
+
import type { OAuthPopupHandle, OAuthPopupOutcome } from './types';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Window name for the sign-in popup. Naming the target means a second press
|
|
28
|
+
* REUSES the same window instead of stacking a new one behind the first.
|
|
29
|
+
*/
|
|
30
|
+
export const OXY_OAUTH_POPUP_WINDOW_NAME = 'oxy-oauth';
|
|
31
|
+
|
|
32
|
+
/** Chrome-free window roughly sized for the IdP's sign-in / QR surface. */
|
|
33
|
+
const POPUP_FEATURES = 'popup=yes,width=460,height=700,menubar=no,toolbar=no,location=no,status=no';
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* How long a popup sign-in may stay unanswered (ms).
|
|
37
|
+
*
|
|
38
|
+
* Generous on purpose: the IdP surface can involve scanning a QR and approving
|
|
39
|
+
* on a phone, so this tracks the authorize handle's own ~5-minute lifetime
|
|
40
|
+
* rather than a UI-scale timeout. It is a backstop against a wedged popup, not
|
|
41
|
+
* a pace-setter — a user who gives up closes the window and gets the faster
|
|
42
|
+
* `closed` outcome.
|
|
43
|
+
*/
|
|
44
|
+
export const DEFAULT_OAUTH_POPUP_TIMEOUT_MS = 300_000;
|
|
45
|
+
|
|
46
|
+
/** How often the popup's `closed` flag is sampled (ms). */
|
|
47
|
+
const CLOSE_POLL_INTERVAL_MS = 1000;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Grace period (ms) after `closed` flips before reporting cancellation.
|
|
51
|
+
*
|
|
52
|
+
* The IdP posts its result and THEN closes itself. Both land as separate tasks,
|
|
53
|
+
* so a poll tick that happens to fall between them would otherwise turn a
|
|
54
|
+
* successful sign-in into a "user cancelled". One short grace window lets the
|
|
55
|
+
* already-queued message win.
|
|
56
|
+
*/
|
|
57
|
+
const CLOSE_GRACE_MS = 400;
|
|
58
|
+
|
|
59
|
+
/** The `message` half of the DOM host, absent on native and during SSR. */
|
|
60
|
+
interface MessageEventHost {
|
|
61
|
+
addEventListener(type: 'message', listener: (event: MessageEvent) => void): void;
|
|
62
|
+
removeEventListener(type: 'message', listener: (event: MessageEvent) => void): void;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** The `open` half of the DOM host, absent on native and during SSR. */
|
|
66
|
+
interface PopupOpenerHost {
|
|
67
|
+
open?: (url: string, target: string, features: string) => OAuthPopupHandle | null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function messageEventHost(): MessageEventHost | null {
|
|
71
|
+
const win = (globalThis as { window?: Partial<MessageEventHost> }).window;
|
|
72
|
+
if (!win || typeof win.addEventListener !== 'function' || typeof win.removeEventListener !== 'function') {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
return win as MessageEventHost;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Open a new, EMPTY sign-in popup.
|
|
80
|
+
*
|
|
81
|
+
* MUST be called SYNCHRONOUSLY from a user-gesture handler, before any `await`.
|
|
82
|
+
* Returns `null` when the browser blocked it, when `window.open` threw, and on
|
|
83
|
+
* hosts with no DOM (native / SSR) — never throws, because "no popup" is an
|
|
84
|
+
* ordinary outcome the caller answers by falling back to a redirect.
|
|
85
|
+
*/
|
|
86
|
+
export function openOAuthPopup(): OAuthPopupHandle | null {
|
|
87
|
+
const win = (globalThis as { window?: PopupOpenerHost }).window;
|
|
88
|
+
if (!win || typeof win.open !== 'function') return null;
|
|
89
|
+
try {
|
|
90
|
+
return win.open('', OXY_OAUTH_POPUP_WINDOW_NAME, POPUP_FEATURES) ?? null;
|
|
91
|
+
} catch (error) {
|
|
92
|
+
logger.warn('Could not open the OAuth sign-in popup', { component: 'oauthPopup' }, error);
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Point an already-open popup at the authorize URL. Returns `false` when the
|
|
99
|
+
* assignment threw (a window closed between opening and navigating), so the
|
|
100
|
+
* caller can fall back to a redirect instead of waiting on a dead window.
|
|
101
|
+
*/
|
|
102
|
+
export function navigateOAuthPopup(popup: OAuthPopupHandle, url: string): boolean {
|
|
103
|
+
try {
|
|
104
|
+
popup.location.href = url;
|
|
105
|
+
return true;
|
|
106
|
+
} catch (error) {
|
|
107
|
+
logger.warn('Could not navigate the OAuth sign-in popup', { component: 'oauthPopup' }, error);
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Close the popup if it is still open. Idempotent, null-safe, never throws. */
|
|
113
|
+
export function closeOAuthPopup(popup: OAuthPopupHandle | null): void {
|
|
114
|
+
if (!popup || popup.closed) return;
|
|
115
|
+
try {
|
|
116
|
+
popup.close();
|
|
117
|
+
} catch (error) {
|
|
118
|
+
logger.debug('OAuth popup close failed', { component: 'oauthPopup' }, error);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* When the IdP falls back to a top-level redirect inside the popup (no usable
|
|
124
|
+
* `window.opener`), a same-origin `redirect_uri` is readable from the opener.
|
|
125
|
+
* Cross-origin redirects remain opaque and are not handled here.
|
|
126
|
+
*/
|
|
127
|
+
function readSameOriginRedirectOutcome(
|
|
128
|
+
popup: OAuthPopupHandle,
|
|
129
|
+
redirectUri: string,
|
|
130
|
+
expectedState: string,
|
|
131
|
+
): OAuthPopupOutcome | null {
|
|
132
|
+
try {
|
|
133
|
+
const href = popup.location.href;
|
|
134
|
+
if (!href) return null;
|
|
135
|
+
|
|
136
|
+
const popupUrl = new URL(href);
|
|
137
|
+
const expected = new URL(canonicalizeOAuthRedirectUri(redirectUri));
|
|
138
|
+
if (popupUrl.origin !== expected.origin) return null;
|
|
139
|
+
if (popupUrl.pathname !== expected.pathname) return null;
|
|
140
|
+
|
|
141
|
+
const oauthError = popupUrl.searchParams.get('error');
|
|
142
|
+
if (oauthError) {
|
|
143
|
+
const errorDescription = popupUrl.searchParams.get('error_description') ?? undefined;
|
|
144
|
+
return {
|
|
145
|
+
kind: 'oauth-error',
|
|
146
|
+
error: oauthError,
|
|
147
|
+
...(errorDescription ? { errorDescription } : {}),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const code = popupUrl.searchParams.get('code');
|
|
152
|
+
if (!code) return null;
|
|
153
|
+
|
|
154
|
+
const state = popupUrl.searchParams.get('state');
|
|
155
|
+
if (!state || state !== expectedState) return { kind: 'state-mismatch' };
|
|
156
|
+
return { kind: 'code', code, state };
|
|
157
|
+
} catch {
|
|
158
|
+
// Cross-origin navigation — the popup URL is opaque to the opener.
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Inputs for {@link awaitOAuthPopupResult}. */
|
|
164
|
+
export interface AwaitOAuthPopupOptions {
|
|
165
|
+
/** The exact window reference returned by {@link openOAuthPopup}. */
|
|
166
|
+
popup: OAuthPopupHandle;
|
|
167
|
+
/** Exact origin the IdP will post from (`new URL(authorizeUrl).origin`). */
|
|
168
|
+
expectedOrigin: string;
|
|
169
|
+
/** The CSRF `state` sent on the authorize request. */
|
|
170
|
+
expectedState: string;
|
|
171
|
+
/**
|
|
172
|
+
* EXACT registered redirect URI for this attempt. When the IdP cannot
|
|
173
|
+
* `postMessage` (no usable opener) it falls back to redirecting the popup;
|
|
174
|
+
* if that lands on the same origin, the result is read from the popup URL.
|
|
175
|
+
*/
|
|
176
|
+
redirectUri: string;
|
|
177
|
+
/** @default DEFAULT_OAUTH_POPUP_TIMEOUT_MS */
|
|
178
|
+
timeoutMs?: number;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Wait for the popup to deliver an authorization result.
|
|
183
|
+
*
|
|
184
|
+
* Settles EXACTLY once, with a typed outcome for every path — code, IdP error,
|
|
185
|
+
* state mismatch, user-closed, timeout — and removes its listener, its close
|
|
186
|
+
* poll, and both timers before resolving, whatever the outcome. Messages that
|
|
187
|
+
* arrive after settling reach nothing, so a duplicate delivery is inert.
|
|
188
|
+
*
|
|
189
|
+
* Never rejects: closing the window and running out of time are expected
|
|
190
|
+
* outcomes, not exceptions.
|
|
191
|
+
*/
|
|
192
|
+
export function awaitOAuthPopupResult(
|
|
193
|
+
options: AwaitOAuthPopupOptions,
|
|
194
|
+
): Promise<OAuthPopupOutcome> {
|
|
195
|
+
const {
|
|
196
|
+
popup,
|
|
197
|
+
expectedOrigin,
|
|
198
|
+
expectedState,
|
|
199
|
+
redirectUri,
|
|
200
|
+
timeoutMs = DEFAULT_OAUTH_POPUP_TIMEOUT_MS,
|
|
201
|
+
} = options;
|
|
202
|
+
|
|
203
|
+
const host = messageEventHost();
|
|
204
|
+
if (!host) return Promise.resolve({ kind: 'unsupported' });
|
|
205
|
+
|
|
206
|
+
return new Promise<OAuthPopupOutcome>((resolve) => {
|
|
207
|
+
let settled = false;
|
|
208
|
+
let timeoutTimer: ReturnType<typeof setTimeout> | null = null;
|
|
209
|
+
let closeGraceTimer: ReturnType<typeof setTimeout> | null = null;
|
|
210
|
+
let closePoll: ReturnType<typeof setInterval> | null = null;
|
|
211
|
+
|
|
212
|
+
const onMessage = (event: MessageEvent): void => {
|
|
213
|
+
const verdict = readOAuthPopupMessage(event, { expectedOrigin, expectedState, popup });
|
|
214
|
+
switch (verdict.kind) {
|
|
215
|
+
case 'ignore':
|
|
216
|
+
return;
|
|
217
|
+
case 'code':
|
|
218
|
+
settle({ kind: 'code', code: verdict.code, state: verdict.state });
|
|
219
|
+
return;
|
|
220
|
+
case 'oauth-error':
|
|
221
|
+
settle({
|
|
222
|
+
kind: 'oauth-error',
|
|
223
|
+
error: verdict.error,
|
|
224
|
+
...(verdict.errorDescription ? { errorDescription: verdict.errorDescription } : {}),
|
|
225
|
+
});
|
|
226
|
+
return;
|
|
227
|
+
case 'state-mismatch':
|
|
228
|
+
settle({ kind: 'state-mismatch' });
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const cleanup = (): void => {
|
|
234
|
+
host.removeEventListener('message', onMessage);
|
|
235
|
+
if (timeoutTimer !== null) {
|
|
236
|
+
clearTimeout(timeoutTimer);
|
|
237
|
+
timeoutTimer = null;
|
|
238
|
+
}
|
|
239
|
+
if (closeGraceTimer !== null) {
|
|
240
|
+
clearTimeout(closeGraceTimer);
|
|
241
|
+
closeGraceTimer = null;
|
|
242
|
+
}
|
|
243
|
+
if (closePoll !== null) {
|
|
244
|
+
clearInterval(closePoll);
|
|
245
|
+
closePoll = null;
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
function settle(outcome: OAuthPopupOutcome): void {
|
|
250
|
+
if (settled) return;
|
|
251
|
+
settled = true;
|
|
252
|
+
cleanup();
|
|
253
|
+
resolve(outcome);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
host.addEventListener('message', onMessage);
|
|
257
|
+
|
|
258
|
+
timeoutTimer = setTimeout(() => settle({ kind: 'timed-out' }), timeoutMs);
|
|
259
|
+
|
|
260
|
+
closePoll = setInterval(() => {
|
|
261
|
+
const redirectOutcome = readSameOriginRedirectOutcome(popup, redirectUri, expectedState);
|
|
262
|
+
if (redirectOutcome) {
|
|
263
|
+
settle(redirectOutcome);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (!popup.closed) return;
|
|
268
|
+
// Stop sampling immediately: the window is gone and only the in-flight
|
|
269
|
+
// message (if any) can still change the outcome.
|
|
270
|
+
if (closePoll !== null) {
|
|
271
|
+
clearInterval(closePoll);
|
|
272
|
+
closePoll = null;
|
|
273
|
+
}
|
|
274
|
+
closeGraceTimer = setTimeout(() => settle({ kind: 'closed' }), CLOSE_GRACE_MS);
|
|
275
|
+
}, CLOSE_POLL_INTERVAL_MS);
|
|
276
|
+
});
|
|
277
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime validation for the popup OAuth `postMessage` channel.
|
|
3
|
+
*
|
|
4
|
+
* Everything arriving on `window`'s `message` event is untrusted: any frame on
|
|
5
|
+
* the page, any other popup, and any browser extension can post to us. This
|
|
6
|
+
* module is the ONLY place that decides whether an incoming event is a genuine
|
|
7
|
+
* authorization response, and it answers with a value — never a thrown error.
|
|
8
|
+
*
|
|
9
|
+
* A message is accepted only when ALL of these hold:
|
|
10
|
+
* 1. `event.origin` is EXACTLY the authorize endpoint's origin,
|
|
11
|
+
* 2. `event.source` is the exact `Window` reference we opened,
|
|
12
|
+
* 3. `event.data` matches one of the two frozen message shapes,
|
|
13
|
+
* 4. the `state` it carries matches the one this RP sent.
|
|
14
|
+
*
|
|
15
|
+
* Failing (1)–(3) is indistinguishable from unrelated page traffic, so the
|
|
16
|
+
* message is IGNORED and the flow keeps waiting. Failing (4) means our own
|
|
17
|
+
* popup answered a different request than the one in flight, which is terminal:
|
|
18
|
+
* it is reported as a mismatch rather than left to hang until the timeout.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { logger } from '@oxyhq/core';
|
|
22
|
+
import type { OAuthPopupHandle, OxyOAuthMessage } from './types';
|
|
23
|
+
|
|
24
|
+
/** `postMessage` discriminator for a successful authorization. */
|
|
25
|
+
export const OXY_OAUTH_CODE_MESSAGE_TYPE = 'oxy:oauth:code';
|
|
26
|
+
|
|
27
|
+
/** `postMessage` discriminator for a typed OAuth failure. */
|
|
28
|
+
export const OXY_OAUTH_ERROR_MESSAGE_TYPE = 'oxy:oauth:error';
|
|
29
|
+
|
|
30
|
+
/** Verdict for one incoming `message` event. */
|
|
31
|
+
export type OAuthPopupMessageVerdict =
|
|
32
|
+
/** Not ours (wrong origin/source) or not a valid message — keep waiting. */
|
|
33
|
+
| { kind: 'ignore' }
|
|
34
|
+
| { kind: 'code'; code: string; state: string }
|
|
35
|
+
| { kind: 'oauth-error'; error: string; errorDescription?: string }
|
|
36
|
+
/** Well-formed, from our popup, but bound to a different authorize request. */
|
|
37
|
+
| { kind: 'state-mismatch' };
|
|
38
|
+
|
|
39
|
+
/** What {@link readOAuthPopupMessage} validates an event against. */
|
|
40
|
+
export interface OAuthPopupMessageContext {
|
|
41
|
+
/** Exact origin of the authorize endpoint (`new URL(authorizeUrl).origin`). */
|
|
42
|
+
expectedOrigin: string;
|
|
43
|
+
/** The CSRF `state` this RP sent on the authorize request. */
|
|
44
|
+
expectedState: string;
|
|
45
|
+
/** The exact popup reference `window.open()` returned. */
|
|
46
|
+
popup: OAuthPopupHandle;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function isNonEmptyString(value: unknown): value is string {
|
|
50
|
+
return typeof value === 'string' && value.length > 0;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Reference equality across two `unknown` values.
|
|
55
|
+
*
|
|
56
|
+
* `event.source` is a `WindowProxy` and {@link OAuthPopupHandle} is a narrow
|
|
57
|
+
* structural type, so comparing them directly is a type error. Widening both to
|
|
58
|
+
* `unknown` keeps the check a plain identity comparison — which is exactly the
|
|
59
|
+
* security property we want — without a cast.
|
|
60
|
+
*/
|
|
61
|
+
function isSameWindowReference(a: unknown, b: unknown): boolean {
|
|
62
|
+
return a === b;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Parse untrusted `event.data` into one of the two frozen message shapes, or
|
|
67
|
+
* `null` when it is anything else (including `null`, primitives, arrays, and
|
|
68
|
+
* objects with missing/wrong-typed fields).
|
|
69
|
+
*
|
|
70
|
+
* Unknown extra properties are tolerated but never read: only the fields below
|
|
71
|
+
* are ever surfaced, so a hostile sender cannot smuggle values past this point.
|
|
72
|
+
*/
|
|
73
|
+
export function parseOAuthPopupMessage(data: unknown): OxyOAuthMessage | null {
|
|
74
|
+
if (typeof data !== 'object' || data === null || Array.isArray(data)) return null;
|
|
75
|
+
const record = data as Record<string, unknown>;
|
|
76
|
+
|
|
77
|
+
if (record.type === OXY_OAUTH_CODE_MESSAGE_TYPE) {
|
|
78
|
+
if (!isNonEmptyString(record.code) || !isNonEmptyString(record.state)) return null;
|
|
79
|
+
return { type: OXY_OAUTH_CODE_MESSAGE_TYPE, code: record.code, state: record.state };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (record.type === OXY_OAUTH_ERROR_MESSAGE_TYPE) {
|
|
83
|
+
if (!isNonEmptyString(record.error)) return null;
|
|
84
|
+
return {
|
|
85
|
+
type: OXY_OAUTH_ERROR_MESSAGE_TYPE,
|
|
86
|
+
error: record.error,
|
|
87
|
+
...(isNonEmptyString(record.errorDescription)
|
|
88
|
+
? { errorDescription: record.errorDescription }
|
|
89
|
+
: {}),
|
|
90
|
+
...(isNonEmptyString(record.state) ? { state: record.state } : {}),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Classify one `message` event against the in-flight authorize request.
|
|
99
|
+
*
|
|
100
|
+
* Never throws. See the module header for the acceptance rules.
|
|
101
|
+
*/
|
|
102
|
+
export function readOAuthPopupMessage(
|
|
103
|
+
event: MessageEvent,
|
|
104
|
+
context: OAuthPopupMessageContext,
|
|
105
|
+
): OAuthPopupMessageVerdict {
|
|
106
|
+
if (event.origin !== context.expectedOrigin) return { kind: 'ignore' };
|
|
107
|
+
if (!isSameWindowReference(event.source, context.popup)) return { kind: 'ignore' };
|
|
108
|
+
|
|
109
|
+
const message = parseOAuthPopupMessage(event.data);
|
|
110
|
+
if (!message) {
|
|
111
|
+
// Origin and source matched, so this came from the IdP window we opened —
|
|
112
|
+
// a shape we cannot read is worth a breadcrumb, unlike ordinary page noise.
|
|
113
|
+
logger.debug(
|
|
114
|
+
'Ignored an unrecognised message from the OAuth popup',
|
|
115
|
+
{ component: 'oauthPopupMessages' },
|
|
116
|
+
);
|
|
117
|
+
return { kind: 'ignore' };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (message.type === OXY_OAUTH_CODE_MESSAGE_TYPE) {
|
|
121
|
+
if (message.state !== context.expectedState) return { kind: 'state-mismatch' };
|
|
122
|
+
return { kind: 'code', code: message.code, state: message.state };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// An error MAY carry `state` (the IdP omits it when the request never got far
|
|
126
|
+
// enough to bind one). When present it must match: a foreign error must not be
|
|
127
|
+
// able to end an unrelated in-flight sign-in.
|
|
128
|
+
if (message.state !== undefined && message.state !== context.expectedState) {
|
|
129
|
+
return { kind: 'state-mismatch' };
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
kind: 'oauth-error',
|
|
133
|
+
error: message.error,
|
|
134
|
+
...(message.errorDescription ? { errorDescription: message.errorDescription } : {}),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed contracts shared by the browser "Sign in with Oxy" OAuth transports.
|
|
3
|
+
*
|
|
4
|
+
* Two transports deliver the same authorization code to the same completion
|
|
5
|
+
* path (see `completeOAuthCode.ts`):
|
|
6
|
+
*
|
|
7
|
+
* - `redirect` — the historical full-page hand-off to `auth.oxy.so/authorize`.
|
|
8
|
+
* The RP tab navigates away and comes back to its registered `redirect_uri`
|
|
9
|
+
* with `?code=…&state=…`; cold boot finishes the exchange.
|
|
10
|
+
* - `popup` — a small `auth.oxy.so` window opened from the user's click. The RP
|
|
11
|
+
* stays mounted with its route and unsaved state intact; the IdP delivers the
|
|
12
|
+
* result to `window.opener` via `postMessage` and closes itself.
|
|
13
|
+
*
|
|
14
|
+
* Only the authorization code, the CSRF `state`, and a typed OAuth error ever
|
|
15
|
+
* cross the `postMessage` channel. Access tokens, device secrets, and the PKCE
|
|
16
|
+
* `code_verifier` NEVER do — the opener keeps the verifier and performs the code
|
|
17
|
+
* exchange itself.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/** How a web relying party hands the user to the IdP for third-party sign-in. */
|
|
21
|
+
export type WebAuthMode = 'popup' | 'redirect';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Minimal structural handle over a popup `Window` — exactly what the popup
|
|
25
|
+
* transport needs: navigate it once the authorize URL is built, detect the user
|
|
26
|
+
* closing it (`closed`, polled — there is no cross-origin close event), and
|
|
27
|
+
* close it programmatically on completion. Satisfied directly by the value
|
|
28
|
+
* `window.open()` returns.
|
|
29
|
+
*
|
|
30
|
+
* Deliberately NOT `Window`: the transport must never read cross-origin
|
|
31
|
+
* properties off the popup, and a narrow handle makes that impossible to do by
|
|
32
|
+
* accident (and trivial to fake in tests).
|
|
33
|
+
*/
|
|
34
|
+
export interface OAuthPopupHandle {
|
|
35
|
+
readonly closed: boolean;
|
|
36
|
+
close(): void;
|
|
37
|
+
location: { href: string };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The RP-side OAuth handshake for one authorize request: the CSRF `state` sent
|
|
42
|
+
* to the IdP and the PKCE `code_verifier` replayed on the token exchange.
|
|
43
|
+
*
|
|
44
|
+
* In `redirect` mode it is persisted to `sessionStorage` across the full-page
|
|
45
|
+
* navigation; in `popup` mode it never leaves memory.
|
|
46
|
+
*/
|
|
47
|
+
export interface OAuthHandshake {
|
|
48
|
+
/** Opaque CSRF token echoed back by the IdP and validated before any exchange. */
|
|
49
|
+
state: string;
|
|
50
|
+
/** The PKCE secret. Never crosses `postMessage`, a URL, or storage in popup mode. */
|
|
51
|
+
codeVerifier: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Successful authorization delivered by the IdP over `postMessage`. */
|
|
55
|
+
export interface OxyOAuthCodeMessage {
|
|
56
|
+
type: 'oxy:oauth:code';
|
|
57
|
+
code: string;
|
|
58
|
+
state: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Typed OAuth failure delivered by the IdP over `postMessage`. */
|
|
62
|
+
export interface OxyOAuthErrorMessage {
|
|
63
|
+
type: 'oxy:oauth:error';
|
|
64
|
+
error: string;
|
|
65
|
+
errorDescription?: string;
|
|
66
|
+
state?: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** The ONLY two message shapes that may cross the popup channel. */
|
|
70
|
+
export type OxyOAuthMessage = OxyOAuthCodeMessage | OxyOAuthErrorMessage;
|
|
71
|
+
|
|
72
|
+
/** Session material handed to the provider's commit funnel after an exchange. */
|
|
73
|
+
export interface OAuthSessionCommitInput {
|
|
74
|
+
sessionId: string;
|
|
75
|
+
accessToken?: string;
|
|
76
|
+
deviceId?: string;
|
|
77
|
+
deviceSecret?: string;
|
|
78
|
+
userId: string;
|
|
79
|
+
expiresAt?: string;
|
|
80
|
+
user?: { id: string; username?: string; avatar?: string };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Terminal outcome of one popup sign-in attempt. Every expected path — the user
|
|
85
|
+
* closing the window, a timeout, an IdP error — is a value, never a thrown
|
|
86
|
+
* exception.
|
|
87
|
+
*/
|
|
88
|
+
export type OAuthPopupOutcome =
|
|
89
|
+
/** The IdP delivered an authorization code whose `state` matched. */
|
|
90
|
+
| { kind: 'code'; code: string; state: string }
|
|
91
|
+
/** The IdP delivered a typed OAuth error (`access_denied`, `login_required`, …). */
|
|
92
|
+
| { kind: 'oauth-error'; error: string; errorDescription?: string }
|
|
93
|
+
/** A well-formed message from our popup carried the wrong `state`. */
|
|
94
|
+
| { kind: 'state-mismatch' }
|
|
95
|
+
/** The user dismissed the popup without completing sign-in. */
|
|
96
|
+
| { kind: 'closed' }
|
|
97
|
+
/** No result arrived within the allotted window. */
|
|
98
|
+
| { kind: 'timed-out' }
|
|
99
|
+
/** No DOM message host (SSR / native) — the popup transport does not apply. */
|
|
100
|
+
| { kind: 'unsupported' };
|
|
101
|
+
|
|
102
|
+
/** Why the shared completion path could not produce a session. */
|
|
103
|
+
export type OAuthCompletionFailure = 'state-mismatch' | 'exchange-failed';
|
|
104
|
+
|
|
105
|
+
/** Result of the ONE completion path both transports run. */
|
|
106
|
+
export type OAuthCompletionResult =
|
|
107
|
+
| { ok: true }
|
|
108
|
+
| { ok: false; reason: OAuthCompletionFailure };
|
|
109
|
+
|
|
110
|
+
/** Why the transport fell back to a full-page redirect. */
|
|
111
|
+
export type WebOAuthRedirectReason =
|
|
112
|
+
/** `webAuthMode: 'redirect'` — the configured transport. */
|
|
113
|
+
| 'redirect-mode'
|
|
114
|
+
/** The browser refused `window.open` (blocker, or no gesture attribution). */
|
|
115
|
+
| 'popup-blocked'
|
|
116
|
+
/** The popup opened but could not be navigated to the authorize URL. */
|
|
117
|
+
| 'popup-navigation-failed';
|
|
118
|
+
|
|
119
|
+
/** Why a web sign-in attempt failed outright. */
|
|
120
|
+
export type WebOAuthFailureReason =
|
|
121
|
+
/** The handshake could not be persisted for the redirect return (no/blocked storage). */
|
|
122
|
+
| 'handshake-storage'
|
|
123
|
+
/** The `state` returned by the IdP did not match the one we sent. */
|
|
124
|
+
| 'state-mismatch'
|
|
125
|
+
/** `POST /auth/oauth/token` or the session commit failed. */
|
|
126
|
+
| 'exchange-failed'
|
|
127
|
+
/** The IdP reported a typed OAuth error. */
|
|
128
|
+
| 'idp-error';
|
|
129
|
+
|
|
130
|
+
/** Why a web sign-in attempt was not applicable at all. */
|
|
131
|
+
export type WebOAuthUnsupportedReason =
|
|
132
|
+
/** Not a browser (native / SSR) — native uses `openAuthorizeUrlNative`. */
|
|
133
|
+
| 'no-browser'
|
|
134
|
+
/** `sessionMode: 'identity'` has no third-party OAuth lane by construction. */
|
|
135
|
+
| 'identity-bound'
|
|
136
|
+
/** No `clientId` is configured on the provider. */
|
|
137
|
+
| 'missing-client-id';
|
|
138
|
+
|
|
139
|
+
/** Terminal, typed outcome of a web third-party sign-in attempt. */
|
|
140
|
+
export type WebOAuthSignInResult =
|
|
141
|
+
/** A session was committed; the RP is authenticated without a reload. */
|
|
142
|
+
| { status: 'signed-in' }
|
|
143
|
+
/** The tab is navigating to the IdP; nothing else will resolve in this document. */
|
|
144
|
+
| { status: 'redirecting'; via: WebOAuthRedirectReason }
|
|
145
|
+
/** The user dismissed the popup. */
|
|
146
|
+
| { status: 'cancelled' }
|
|
147
|
+
/** No result arrived within the allotted window. */
|
|
148
|
+
| { status: 'timed-out' }
|
|
149
|
+
| { status: 'failed'; reason: WebOAuthFailureReason; description?: string }
|
|
150
|
+
| { status: 'unsupported'; reason: WebOAuthUnsupportedReason };
|
package/src/ui/server.ts
CHANGED
|
@@ -20,8 +20,6 @@ export const LogoIcon = noopComponent;
|
|
|
20
20
|
export const LogoText = noopComponent;
|
|
21
21
|
export const FollowButton = noopComponent;
|
|
22
22
|
export const OxyPayButton = noopComponent;
|
|
23
|
-
export const FontLoader = noopComponent;
|
|
24
|
-
export const setupFonts = async (): Promise<boolean> => true;
|
|
25
23
|
|
|
26
24
|
// Context
|
|
27
25
|
export const useOxy = noopHook;
|
|
@@ -83,4 +83,30 @@ describe('createSessionClient', () => {
|
|
|
83
83
|
expect(ioMock).toHaveBeenCalledTimes(1);
|
|
84
84
|
client.stop();
|
|
85
85
|
});
|
|
86
|
+
|
|
87
|
+
test('threads the pinned-account resolver through: a pinned registerAndActivate only ADDS', async () => {
|
|
88
|
+
// `sessionMode: 'identity'`. The pin reaching `SessionClient` is observable
|
|
89
|
+
// here: a pinned client's own session is minted for the pinned account
|
|
90
|
+
// explicitly, so registering it must never SWITCH the shared device session
|
|
91
|
+
// (which would re-elect the active account under every other app on it).
|
|
92
|
+
const oxy = fakeOxy();
|
|
93
|
+
|
|
94
|
+
const { client } = createSessionClient(oxy as never, undefined, () => 'pinned-account');
|
|
95
|
+
await client.registerAndActivate('pinned-account');
|
|
96
|
+
|
|
97
|
+
const paths = oxy.makeRequest.mock.calls.map((call) => call[1]);
|
|
98
|
+
expect(paths).toContain('/session/device/add');
|
|
99
|
+
expect(paths).not.toContain('/session/device/switch');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('a resolver reporting no pin (account mode) leaves registerAndActivate switching', async () => {
|
|
103
|
+
const oxy = fakeOxy();
|
|
104
|
+
|
|
105
|
+
const { client } = createSessionClient(oxy as never, undefined, () => null);
|
|
106
|
+
await client.registerAndActivate('u1');
|
|
107
|
+
|
|
108
|
+
const paths = oxy.makeRequest.mock.calls.map((call) => call[1]);
|
|
109
|
+
expect(paths).toContain('/session/device/add');
|
|
110
|
+
expect(paths).toContain('/session/device/switch');
|
|
111
|
+
});
|
|
86
112
|
});
|
|
@@ -106,6 +106,40 @@ describe('createTokenTransport', () => {
|
|
|
106
106
|
await expect(Promise.all([first, second])).resolves.toEqual([undefined, undefined]);
|
|
107
107
|
});
|
|
108
108
|
|
|
109
|
+
test('never mints while identity-bound, even when the device switched to another account', async () => {
|
|
110
|
+
// `sessionMode: 'identity'`: the bearer belongs to the PINNED account and is
|
|
111
|
+
// minted for it by the cold boot / re-mint lane. Converging it on
|
|
112
|
+
// `state.activeAccountId` — here a DIFFERENT account another app switched the
|
|
113
|
+
// device to — is exactly the drift the mode prevents, so the transport must
|
|
114
|
+
// short-circuit even though the bearer/active-account check disagrees.
|
|
115
|
+
const refreshAccessToken = jest.fn(async () => 'minted-token');
|
|
116
|
+
const oxy = fakeOxy('pinned-account', refreshAccessToken);
|
|
117
|
+
const transport = createTokenTransport(oxy as never, () => 'pinned-account');
|
|
118
|
+
|
|
119
|
+
await transport.ensureActiveToken({
|
|
120
|
+
...state,
|
|
121
|
+
accounts: [
|
|
122
|
+
{ accountId: 'pinned-account', sessionId: 'sess-pinned', authuser: 0 },
|
|
123
|
+
{ accountId: 'a1', sessionId: 'sess-a1', authuser: 1 },
|
|
124
|
+
],
|
|
125
|
+
activeAccountId: 'a1',
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
expect(refreshAccessToken).not.toHaveBeenCalled();
|
|
129
|
+
expect(oxy.getCurrentUserId).not.toHaveBeenCalled();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test('a resolver reporting no pin (every account-mode provider) behaves exactly as an absent one', async () => {
|
|
133
|
+
const refreshAccessToken = jest.fn(async () => 'minted-token');
|
|
134
|
+
const oxy = fakeOxy('previous-account', refreshAccessToken);
|
|
135
|
+
const transport = createTokenTransport(oxy as never, () => null);
|
|
136
|
+
|
|
137
|
+
await transport.ensureActiveToken(state);
|
|
138
|
+
|
|
139
|
+
expect(refreshAccessToken).toHaveBeenCalledTimes(1);
|
|
140
|
+
expect(refreshAccessToken).toHaveBeenCalledWith('preflight');
|
|
141
|
+
});
|
|
142
|
+
|
|
109
143
|
test('treats a throwing bearer-account check as a mismatch and still mints', async () => {
|
|
110
144
|
const refreshAccessToken = jest.fn(async () => 'minted-token');
|
|
111
145
|
const oxy = fakeOxy(null, refreshAccessToken);
|