fix: Bug - Login screen shows up on unreachable signal servers

Skip authorize login navigation when a signal server endpoint is offline or
unreachable; gate connection and credential flows on online status only.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-11 22:08:53 +02:00
parent 6b9a39fe4a
commit cb59af6b6c
8 changed files with 207 additions and 9 deletions

View File

@@ -5,6 +5,8 @@ import { firstValueFrom } from 'rxjs';
import { selectCurrentUser } from '../../../../store/users/users.selectors';
import { ServerDirectoryFacade } from '../../../server-directory';
import { AUTH_MODE_AUTHORIZE, buildLoginReturnQueryParams } from '../../domain/logic/auth-navigation.rules';
import { isEndpointOnlineForConnection } from '../../../server-directory/domain/logic/server-endpoint-connectivity.rules';
import { shouldNavigateToAuthorizeSignalServer } from '../../domain/logic/signal-server-authorize.rules';
import { SignalServerAuthService } from './signal-server-auth.service';
@Injectable({ providedIn: 'root' })
@@ -25,25 +27,43 @@ export class SignalServerAuthorizeService {
return false;
}
const result = await this.signalServerAuth.ensureProvisioned(serverUrl, currentUser);
let result;
try {
result = await this.signalServerAuth.ensureProvisioned(serverUrl, currentUser);
} catch {
return false;
}
if (result.kind === 'existing' || result.kind === 'provisioned') {
return true;
}
if (result.kind === 'collision') {
await this.navigateToAuthorize(serverUrl, this.router.url);
return false;
}
const endpointStatus = await this.resolveEndpointStatusForAuthorize(serverUrl);
if (result.kind === 'skipped' && result.reason === 'no-provision-secret') {
if (shouldNavigateToAuthorizeSignalServer(endpointStatus, result)) {
await this.navigateToAuthorize(serverUrl, this.router.url);
return false;
}
return false;
}
private async resolveEndpointStatusForAuthorize(serverUrl: string) {
const endpoint = this.serverDirectory.findServerByUrl(serverUrl);
if (!endpoint) {
return null;
}
if (isEndpointOnlineForConnection(endpoint.status) || endpoint.status === 'offline' || endpoint.status === 'incompatible') {
return endpoint.status;
}
await this.serverDirectory.testServer(endpoint.id);
return this.serverDirectory.servers().find((candidate) => candidate.id === endpoint.id)?.status ?? endpoint.status;
}
async navigateToAuthorize(serverUrl: string, returnUrl: string): Promise<void> {
const endpoint = this.serverDirectory.ensureServerEndpoint({
name: this.buildEndpointName(serverUrl),

View File

@@ -0,0 +1,44 @@
import {
describe,
expect,
it
} from 'vitest';
import { shouldNavigateToAuthorizeSignalServer } from './signal-server-authorize.rules';
describe('signal-server-authorize rules', () => {
it('does not navigate to authorize when the signal server is offline', () => {
expect(shouldNavigateToAuthorizeSignalServer('offline', {
kind: 'skipped',
reason: 'no-provision-secret'
})).toBe(false);
expect(shouldNavigateToAuthorizeSignalServer('offline', {
kind: 'collision',
error: new Error('collision') as never
})).toBe(false);
});
it('navigates to authorize on online servers that need manual sign-in', () => {
expect(shouldNavigateToAuthorizeSignalServer('online', {
kind: 'skipped',
reason: 'no-provision-secret'
})).toBe(true);
expect(shouldNavigateToAuthorizeSignalServer('online', {
kind: 'collision',
error: new Error('collision') as never
})).toBe(true);
});
it('does not navigate for unknown endpoint status or non-authorize provision outcomes', () => {
expect(shouldNavigateToAuthorizeSignalServer('unknown', {
kind: 'skipped',
reason: 'no-provision-secret'
})).toBe(false);
expect(shouldNavigateToAuthorizeSignalServer('online', {
kind: 'skipped',
reason: 'no-home-user'
})).toBe(false);
});
});

View File

@@ -0,0 +1,18 @@
import type { EnsureProvisionedResult } from '../../application/services/signal-server-auth.service';
import type { ServerEndpointStatus } from '../../../server-directory/domain/models/server-directory.model';
import { isEndpointOnlineForConnection } from '../../../server-directory/domain/logic/server-endpoint-connectivity.rules';
export function shouldNavigateToAuthorizeSignalServer(
endpointStatus: ServerEndpointStatus | undefined | null,
provisionResult: EnsureProvisionedResult
): boolean {
if (!isEndpointOnlineForConnection(endpointStatus)) {
return false;
}
if (provisionResult.kind === 'collision') {
return true;
}
return provisionResult.kind === 'skipped' && provisionResult.reason === 'no-provision-secret';
}

View File

@@ -26,6 +26,7 @@ import {
type RoomSignalSource,
type RoomSignalSourceInput
} from '../../domain/logic/room-signal-source.logic';
import { isEndpointOnlineForConnection } from '../../domain/logic/server-endpoint-connectivity.rules';
import { ServerEndpointCompatibilityService } from '../../infrastructure/services/server-endpoint-compatibility.service';
import { ServerEndpointHealthService } from '../../infrastructure/services/server-endpoint-health.service';
import { ServerEndpointStateService } from './server-endpoint-state.service';
@@ -110,7 +111,7 @@ export class ServerDirectoryService {
const clientVersion = await this.endpointCompatibility.getClientVersion();
if (!clientVersion) {
if (!clientVersion && isEndpointOnlineForConnection(endpoint.status)) {
return true;
}
@@ -118,7 +119,7 @@ export class ServerDirectoryService {
const refreshedEndpoint = this.servers().find((candidate) => candidate.id === endpoint.id);
return !!refreshedEndpoint && refreshedEndpoint.status !== 'incompatible';
return isEndpointOnlineForConnection(refreshedEndpoint?.status);
}
resolveRoomEndpoint(

View File

@@ -0,0 +1,17 @@
import {
describe,
expect,
it
} from 'vitest';
import { isEndpointOnlineForConnection } from './server-endpoint-connectivity.rules';
describe('server-endpoint-connectivity rules', () => {
it('treats only online endpoints as reachable for connection', () => {
expect(isEndpointOnlineForConnection('online')).toBe(true);
expect(isEndpointOnlineForConnection('offline')).toBe(false);
expect(isEndpointOnlineForConnection('checking')).toBe(false);
expect(isEndpointOnlineForConnection('unknown')).toBe(false);
expect(isEndpointOnlineForConnection('incompatible')).toBe(false);
expect(isEndpointOnlineForConnection(undefined)).toBe(false);
});
});

View File

@@ -0,0 +1,8 @@
import type { ServerEndpointStatus } from '../models/server-directory.model';
/** True only when health checks report the endpoint is reachable. */
export function isEndpointOnlineForConnection(
status: ServerEndpointStatus | undefined | null
): boolean {
return status === 'online';
}