a simpler UI

This commit is contained in:
Paul Schneider 2026-06-21 04:32:44 +01:00
commit 2263311e1b
4 changed files with 122 additions and 5 deletions

View file

@ -59,11 +59,20 @@ public partial class Settings : ObservableObject
/// (no client secret). The browser implementation should be supplied
/// per-platform by the caller.
/// </summary>
/// <remarks>
/// <see cref="AuthenticationSettings.Authority"/> is normalised by
/// trimming any trailing slash before being handed to <c>OidcClient</c>.
/// <c>OidcClient</c> derives the discovery URL from
/// <c>Authority + "/.well-known/openid-configuration"</c>; leaving a
/// trailing slash in place would produce a double-slash URL that some
/// servers reject with 404.
/// </remarks>
internal OidcClientOptions GetOidcClientOptions(IdentityModel.OidcClient.Browser.IBrowser? browser = null)
{
var authority = Authentication.Authority?.TrimEnd('/') ?? string.Empty;
var options = new OidcClientOptions
{
Authority = Authentication.Authority,
Authority = authority,
ClientId = Authentication.ClientId,
RedirectUri = RedirectUri,
Scope = string.Join(' ', this.Scopes),

View file

@ -31,6 +31,23 @@ public partial class LoginPageViewModel : ViewModelBase
public bool HasRegisterUrl => !string.IsNullOrEmpty(RegisterUrl);
public bool HasForgotPasswordUrl => !string.IsNullOrEmpty(ForgotPasswordUrl);
/// <summary>
/// Canonical <see cref="Settings.Authentication"/> authority with any trailing
/// slash removed. Used as the base for both the OIDC discovery URL and the
/// human-facing Account URLs (Register / Forgot password). Empty when the
/// authority is not configured.
/// </summary>
public string ExternalUrl => BuildExternalUrl(string.Empty);
/// <summary>
/// OIDC discovery URL the client actually calls during login:
/// <c>ExternalUrl + "/.well-known/openid-configuration"</c>. Surfaced in
/// <see cref="StatusMessage"/> on failure so the operator can copy it
/// verbatim and verify reachability from a browser.
/// </summary>
public string DiscoveryUrl =>
string.IsNullOrEmpty(ExternalUrl) ? string.Empty : ExternalUrl + "/.well-known/openid-configuration";
/// <summary>
/// True when the settings file is missing or <c>Authentication.Authority</c>
/// is empty. The LoginPage surfaces a banner in that case and disables
@ -112,12 +129,21 @@ public partial class LoginPageViewModel : ViewModelBase
? Platform.DefaultRedirectUri
: Settings.RedirectUri;
// Surface the discovery URL the client is about to call, so a
// failure (DNS, TLS, 404) can be diagnosed by pasting the URL
// straight into a browser. OidcClient computes the discovery
// URL as `Authority + /.well-known/openid-configuration`; we
// normalise the trailing slash here so the printed URL is
// exactly what IdentityModel will fetch.
if (!string.IsNullOrEmpty(DiscoveryUrl))
StatusMessage = $"Discovering {DiscoveryUrl}";
var browser = BrowserFactoryOverride is not null
? BrowserFactoryOverride.Invoke()
: Platform.CreateBrowser?.Invoke();
if (browser is null)
{
StatusMessage = "No browser is available on this platform.";
StatusMessage = $"No browser is available on this platform. (discovery: {DiscoveryUrl})";
return;
}
@ -126,7 +152,7 @@ public partial class LoginPageViewModel : ViewModelBase
if (loginResult.IsError)
{
StatusMessage = loginResult.Error;
StatusMessage = $"{loginResult.Error} (discovery: {DiscoveryUrl})";
return;
}
@ -144,7 +170,8 @@ public partial class LoginPageViewModel : ViewModelBase
catch (Exception ex)
{
this.IsBusy = false;
StatusMessage = "Error: "+ex.Message;
var suffix = !string.IsNullOrEmpty(DiscoveryUrl) ? $" (discovery: {DiscoveryUrl})" : string.Empty;
StatusMessage = $"Error: {ex.Message}{suffix}";
}
}
}

View file

@ -54,7 +54,12 @@
IsEnabled="{Binding HasForgotPasswordUrl}"
Click="OnForgotPasswordClick"/>
<TextBlock Name="StatusText" Text="{Binding StatusMessage}" TextWrapping="Wrap" />
<TextBox Name="StatusText"
Text="{Binding StatusMessage}"
TextWrapping="Wrap"
IsReadOnly="True"
BorderThickness="0"
Background="Transparent"/>
<ProgressBar IsIndeterminate="{Binding IsBusy}" />
</StackPanel>