@@ -23,6 +23,10 @@ export interface UserSession extends cognitoModule.UserSession {
2323 * `true` when this session is a placeholder synthesized after `users/me` failed with
2424 * a non-auth error. The user is signed in to Cognito and can use local projects, but
2525 * the `user` field is a stub — cloud features must be disabled by callers.
26+ *
27+ * Only set on deployments that have a local backend; cloud-only builds fall back to
28+ * the existing redirect-to-login path when `users/me` fails, so this flag is never
29+ * `true` there.
2630 */
2731 readonly isCloudDataUnavailable ?: boolean
2832}
@@ -72,6 +76,12 @@ export function createUsersMeQuery(
7276 let refetchCount = 0
7377 return vueQuery . queryOptions ( {
7478 queryKey : createUsersMeQueryKey ( session , remoteBackend ) ,
79+ // Disable the default 3-retry backoff: a failed `users/me` should surface immediately
80+ // so the degraded-auth UI can render instead of stalling navigation for ~10 s while
81+ // the query retries. Unauthorized errors have a dedicated recovery flow in
82+ // {@link useUnauthorizedRecovery }; transient network errors are handled by the
83+ // user clicking the "Retry" button.
84+ retry : false ,
7585 queryFn : async ( ) : Promise < UserSession | null > => {
7686 const sessionVal = toValue ( session )
7787 if ( ! sessionVal ) {
@@ -102,11 +112,14 @@ export function createUsersMeQuery(
102112 * response is unavailable. All cloud features must be treated as disabled — the
103113 * placeholder mirrors a real user without a licence (`isEnabled: false`,
104114 * `plan: Plan.free`) so the existing "not-enabled" rendering paths apply.
115+ *
116+ * Identifier fields embed the Cognito email so the placeholder is distinct per signed-in
117+ * user (the Cognito app `clientId` is a deployment-wide constant and would alias users).
105118 */
106119export function makeSyntheticUser ( cognitoSession : cognitoModule . UserSession ) : backendModule . User {
107- const clientIdSuffix = cognitoSession . clientId || 'unknown'
120+ const identitySuffix = cognitoSession . email || 'unknown'
108121 return {
109- userId : backendModule . UserId ( `user-cloud-unavailable-${ clientIdSuffix } ` ) ,
122+ userId : backendModule . UserId ( `user-cloud-unavailable-${ identitySuffix } ` ) ,
110123 organizationId : backendModule . OrganizationId (
111124 'organization-00000000000000000000000000' ,
112125 ) ,
@@ -162,7 +175,13 @@ function createAuthStore(
162175 } )
163176
164177 const setUsername = async ( username : string ) => {
165- if ( userData . value != null ) {
178+ if ( isCloudDataUnavailable . value ) {
179+ throw new Error ( 'Cannot set username while Enso Cloud is unavailable.' )
180+ }
181+ // Branch on the real `users/me` result, not the (possibly synthetic) `userData`:
182+ // a synthetic placeholder would otherwise route us into the update path and hit
183+ // the failing remote.
184+ if ( usersMeQuery . data . value != null ) {
166185 await updateUserMutation . mutateAsync ( { username } )
167186 } else {
168187 const orgId = await organizationId ( )
@@ -190,10 +209,13 @@ function createAuthStore(
190209
191210 const usersMeQuery = vueQuery . useQuery ( usersMeQueryOptions )
192211
193- let syntheticUserCache : { clientId : string ; user : backendModule . User } | null = null
212+ // Keyed on `email`, not `clientId`: `clientId` is the Cognito app integration ID and is
213+ // identical across users on the same deployment, so caching on it would surface user A's
214+ // placeholder for user B after a sign-out/sign-in.
215+ let syntheticUserCache : { email : string ; user : backendModule . User } | null = null
194216 const getSyntheticUser = ( cognitoSession : cognitoModule . UserSession ) => {
195- if ( syntheticUserCache ?. clientId !== cognitoSession . clientId ) {
196- syntheticUserCache = { clientId : cognitoSession . clientId , user : makeSyntheticUser ( cognitoSession ) }
217+ if ( syntheticUserCache ?. email !== cognitoSession . email ) {
218+ syntheticUserCache = { email : cognitoSession . email , user : makeSyntheticUser ( cognitoSession ) }
197219 }
198220 return syntheticUserCache . user
199221 }
@@ -202,7 +224,8 @@ function createAuthStore(
202224 * `true` when Cognito sign-in succeeded but the subsequent `users/me` fetch failed
203225 * with a non-auth error. Auth (401/403) failures are owned by `useUnauthorizedRecovery`
204226 * and excluded here. Requires a local backend so the synthesised session has somewhere
205- * to land — without one, callers fall back to the redirect-to-login path.
227+ * to land — on cloud-only deployments without `localBackend`, this stays `false` and
228+ * the user falls through to the existing redirect-to-login path.
206229 */
207230 const isCloudDataUnavailable = computed ( ( ) => {
208231 const cognitoSession = session . value
0 commit comments