fix(desktop): offer remote sign-in on a gated-gateway boot failure (#39402)

When a remote gateway with username/password (or OAuth) auth restarts, its
session cookie lapses and Desktop boots into the recovery overlay with a
session-expired error. That overlay only exposed local-recovery actions —
Retry (resets the local bootstrap latch) and Repair (re-runs the installer) —
neither of which can re-establish a remote session, so the user is stuck in a
no-op Retry loop with no way to sign in again.

The overlay now detects a remote-reauth boot failure from the saved connection
config (remote + gated + not currently connected + has a URL) and surfaces a
primary 'Sign in to remote gateway' button that opens the gateway login window
(the username/password form for a basic gateway, the OAuth redirect otherwise)
and reloads on success. Button copy is driven by a best-effort provider probe,
matching the gateway-settings page. Detection and copy logic live in a pure
helper module with unit coverage.
This commit is contained in:
Teknium
2026-06-04 17:28:29 -07:00
committed by GitHub
parent 82c157b267
commit 2c98dc0a96
3 changed files with 296 additions and 14 deletions
@@ -0,0 +1,99 @@
import { describe, expect, it } from 'vitest'
import type { DesktopConnectionConfig } from '@/global'
import { deriveProviderShape, isRemoteReauthFailure, signInLabel } from './boot-failure-reauth'
function config(overrides: Partial<DesktopConnectionConfig> = {}): DesktopConnectionConfig {
return {
envOverride: false,
mode: 'remote',
remoteAuthMode: 'oauth',
remoteOauthConnected: false,
remoteTokenPreview: null,
remoteTokenSet: false,
remoteUrl: 'https://box:9119',
...overrides
}
}
describe('isRemoteReauthFailure', () => {
it('true for a remote, gated, disconnected gateway with a URL', () => {
expect(isRemoteReauthFailure(config())).toBe(true)
})
it('false when the oauth session is still connected', () => {
expect(isRemoteReauthFailure(config({ remoteOauthConnected: true }))).toBe(false)
})
it('false for a local gateway', () => {
expect(isRemoteReauthFailure(config({ mode: 'local' }))).toBe(false)
})
it('false for a token (non-gated) remote gateway', () => {
expect(isRemoteReauthFailure(config({ remoteAuthMode: 'token' }))).toBe(false)
})
it('false when there is no remote URL to sign in against', () => {
expect(isRemoteReauthFailure(config({ remoteUrl: '' }))).toBe(false)
})
it('false for null/undefined config', () => {
expect(isRemoteReauthFailure(null)).toBe(false)
expect(isRemoteReauthFailure(undefined)).toBe(false)
})
})
describe('deriveProviderShape', () => {
it('generic copy when there are no providers', () => {
expect(deriveProviderShape([])).toEqual({ isPassword: false, providerLabel: 'your identity provider' })
expect(deriveProviderShape(null)).toEqual({ isPassword: false, providerLabel: 'your identity provider' })
})
it('password shape when the sole provider supports password', () => {
expect(
deriveProviderShape([{ name: 'basic', displayName: 'Username & Password', supportsPassword: true }])
).toEqual({ isPassword: true, providerLabel: 'Username & Password' })
})
it('OAuth shape when the provider is a redirect IDP', () => {
expect(deriveProviderShape([{ name: 'nous', displayName: 'Nous Research', supportsPassword: false }])).toEqual({
isPassword: false,
providerLabel: 'Nous Research'
})
})
it('mixed deployment keeps generic OAuth copy (not every provider is password)', () => {
const shape = deriveProviderShape([
{ name: 'basic', displayName: 'Username & Password', supportsPassword: true },
{ name: 'nous', displayName: 'Nous Research', supportsPassword: false }
])
expect(shape.isPassword).toBe(false)
expect(shape.providerLabel).toBe('Username & Password / Nous Research')
})
it('falls back to name when displayName is empty', () => {
expect(deriveProviderShape([{ name: 'basic', displayName: '', supportsPassword: true }]).providerLabel).toBe(
'basic'
)
})
})
describe('signInLabel', () => {
it('password gateway gets the plain "Sign in to remote gateway" copy', () => {
expect(signInLabel({ url: 'x', isPassword: true, providerLabel: 'Username & Password' })).toBe(
'Sign in to remote gateway'
)
})
it('OAuth gateway names the provider', () => {
expect(signInLabel({ url: 'x', isPassword: false, providerLabel: 'Nous Research' })).toBe(
'Sign in with Nous Research'
)
})
it('null reauth falls back to the generic provider phrase', () => {
expect(signInLabel(null)).toBe('Sign in with your identity provider')
})
})