Say which half of signing in is happening, and which half failed

A sign-in against a production Keycloak was reported as the shell hanging on "Opening your
browser to sign in…" and then, some time later, saying "The server returned 500". Both
halves of that are the message's fault. The browser flow had already succeeded — the
provider authenticated the user, the code came back, the tokens were exchanged — and what
was actually happening was a round trip to the DodoSSH server for the account. The screen
went on describing a browser nobody was waiting for.

The status now moves when the browser half ends, so the wait that follows is attributed to
the server being asked rather than to the browser that has already answered.

The failure gets the same treatment. "The server returned 500" is the API client's phrase
for any server it talks to, and read underneath a sign-in button it is naturally taken as
the sign-in having failed — which sends somebody to their identity provider's logs to find
out why a thing that worked did not work. It now says signing in succeeded, names the host
that failed afterwards, and says the reason is in that server's logs, because this side
cannot know more than that.

Nothing here fixes the 500. It changes which of the two servers the next person goes and
looks at, which was the actual cost of the old message.

Verified against the shell suite, including the case that asserts a failed command leaves
the window enabled.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-01 21:11:38 +02:00
co-authored by Claude Opus 5
parent 1db8bed872
commit 39b7e5620f
@@ -5,6 +5,7 @@ using System.Security.Authentication;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using DodoSSH.Client.Api;
using DodoSSH.Client.Auth;
using DodoSSH.Client.Import;
using DodoSSH.Client.ObjectStore;
@@ -1012,6 +1013,12 @@ internal sealed partial class MainWindowViewModel : ObservableObject, IAsyncDisp
OnPropertyChanged(nameof(IsOnline));
RaiseSyncState();
// The browser is finished with, and what follows is a round trip to the DodoSSH server
// that can take a while or fail on its own. Saying so is the difference between a wait
// and a hang: a screen still reading "Opening your browser to sign in…" while the server
// is the thing struggling sends the user back to a browser that did nothing wrong.
StatusMessage = $"Signed in. Asking {url.Host} about your account…";
var outcome = await Provisioner()!
.RefreshAsync(ServerUrl, cancellationToken)
.ConfigureAwait(true);
@@ -1846,13 +1853,32 @@ internal sealed partial class MainWindowViewModel : ObservableObject, IAsyncDisp
}
/// <remarks>
/// One case earns a translation rather than the exception's own words: pointing an HTTPS client at a
/// plaintext port reports "The SSL connection could not be established", which sends people looking
/// for a certificate problem. The scheme is the mistake, and the development stack serves HTTP, so
/// this is the first thing a new user will hit.
/// <para>
/// Two cases earn a translation rather than the exception's own words, and both are cases where the
/// exception names a symptom belonging to somebody else's process.
/// </para>
/// <para>
/// Pointing an HTTPS client at a plaintext port reports "The SSL connection could not be
/// established", which sends people looking for a certificate problem. The scheme is the mistake, and
/// the development stack serves HTTP, so this is the first thing a new user will hit.
/// </para>
/// <para>
/// A 5xx from the DodoSSH server is the other. By the time it arrives the browser flow has already
/// succeeded — the identity provider authenticated the user and the tokens are in hand — so "The
/// server returned 500" read underneath a sign-in button is naturally taken as the sign-in having
/// failed, and the search starts in the wrong place. Naming which server, and saying that its own
/// logs hold the reason, is the whole content of the fix; the client cannot know more than that.
/// </para>
/// </remarks>
private static string ExplainSignInFailure(Exception exception, Uri server)
{
if (exception is DodoSshApiException api && (int)api.StatusCode >= 500)
{
return $"Signing in worked. The DodoSSH server at {server.Host} then failed while answering "
+ $"for your account ({(int)api.StatusCode}), which is a fault on the server rather than "
+ "anything to fix here — its own logs carry the reason.";
}
var secureChannelFailed = exception is HttpRequestException
&& exception.GetBaseException() is AuthenticationException;