From 36e179c49466933476c17619a782dc511cb7f391 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Sat, 20 Jun 2026 21:49:32 +0100 Subject: [PATCH] postit: link to register and forgot-password from LoginPage The Yavsc.Org sign-in page and the password-reset page are the canonical entry points for new users and locked-out users; expose both from PostIt's LoginPage by deriving their URLs from the configured Authentication.Authority. * Add RegisterUrl, ForgotPasswordUrl, HasXxxUrl, ConfigMissing and ConfigMissingMessage to LoginPageViewModel. * LoginPage loads settings eagerly in the VM ctor so the URLs are populated when XAML bindings first fire. * Two new buttons (Register a new account, Forgot password?) bind to HasXxxUrl via IsEnabled and fall back to Process.Start on click. * A yellow banner surfaces when Authentication.Authority is empty, pointing the user at ~/.config/PostIt/postit-settings.json. Also drop the duplicated OIDC login logic from LoginPage.axaml.cs: the page now drives Login through LoginPageViewModel.LoginAsync and DataContext is auto-attached when HomePage pushes the page without a VM. Tests cover the happy-path OIDC flow, URL derivation, and the ConfigMissing flag. --- src/PostIt.Tests/LoginPageViewModelTests.cs | 60 ++++++++++++++++ .../PostIt/ViewModels/LoginPageViewModel.cs | 48 +++++++++++++ src/PostIt/PostIt/Views/LoginPage.axaml | 70 ++++++++++++------- src/PostIt/PostIt/Views/LoginPage.axaml.cs | 65 +++++++++-------- 4 files changed, 189 insertions(+), 54 deletions(-) diff --git a/src/PostIt.Tests/LoginPageViewModelTests.cs b/src/PostIt.Tests/LoginPageViewModelTests.cs index 42390121..38f4c76e 100644 --- a/src/PostIt.Tests/LoginPageViewModelTests.cs +++ b/src/PostIt.Tests/LoginPageViewModelTests.cs @@ -41,4 +41,64 @@ public class LoginPageViewModelTests vm.StatusMessage?.StartsWith("Error") == true, $"Login reported error: {vm.StatusMessage}"); } + + [Fact] + public void RegisterUrl_and_ForgotPasswordUrl_are_derived_from_authority() + { + var settings = new PostIt.Settings + { + Authentication = new AuthenticationSettings + { + Authority = "https://yavsc.example.com/", + ClientId = "postit-tests" + }, + RedirectUri = "http://127.0.0.1:7890/", + Scopes = new[] { "openid" } + }; + + var vm = new LoginPageViewModel(settings); + + // Trailing slash on Authority is normalised away. + Assert.Equal( + "https://yavsc.example.com/signin?ReturnUrl=~%2F&AllowRememberLogin=true", + vm.RegisterUrl); + Assert.Equal( + "https://yavsc.example.com/Account/ForgotPassword", + vm.ForgotPasswordUrl); + Assert.True(vm.HasRegisterUrl); + Assert.True(vm.HasForgotPasswordUrl); + } + + [Fact] + public void RegisterUrl_is_empty_when_authority_is_unset() + { + var vm = new LoginPageViewModel(new PostIt.Settings()); + Assert.Equal(string.Empty, vm.RegisterUrl); + Assert.Equal(string.Empty, vm.ForgotPasswordUrl); + Assert.False(vm.HasRegisterUrl); + Assert.False(vm.HasForgotPasswordUrl); + } + + [Fact] + public void ConfigMissing_is_true_when_authority_is_unset() + { + var vm = new LoginPageViewModel(new PostIt.Settings()); + Assert.True(vm.ConfigMissing); + Assert.Contains("~/.config/PostIt/postit-settings.json", vm.ConfigMissingMessage); + } + + [Fact] + public void ConfigMissing_is_false_when_authority_is_set() + { + var settings = new PostIt.Settings + { + Authentication = new AuthenticationSettings + { + Authority = "https://yavsc.example.com/", + ClientId = "postit-tests" + } + }; + var vm = new LoginPageViewModel(settings); + Assert.False(vm.ConfigMissing); + } } \ No newline at end of file diff --git a/src/PostIt/PostIt/ViewModels/LoginPageViewModel.cs b/src/PostIt/PostIt/ViewModels/LoginPageViewModel.cs index f7fb25bc..6a22cbe7 100644 --- a/src/PostIt/PostIt/ViewModels/LoginPageViewModel.cs +++ b/src/PostIt/PostIt/ViewModels/LoginPageViewModel.cs @@ -12,6 +12,49 @@ public partial class LoginPageViewModel : ViewModelBase public string UserEmail { get; set; } public string Password { get; set; } + /// + /// URL of the Yavsc.Org register/sign-in page for new users. + /// Derived from 's Authority. + /// Empty when the authority is not configured. + /// + public string RegisterUrl => + BuildExternalUrl("/signin?ReturnUrl=~%2F&AllowRememberLogin=true"); + + /// + /// URL of the Yavsc.Org password-reset page (open to anonymous users). + /// Derived from 's Authority. + /// Empty when the authority is not configured. + /// + public string ForgotPasswordUrl => + BuildExternalUrl("/Account/ForgotPassword"); + + public bool HasRegisterUrl => !string.IsNullOrEmpty(RegisterUrl); + public bool HasForgotPasswordUrl => !string.IsNullOrEmpty(ForgotPasswordUrl); + + /// + /// True when the settings file is missing or Authentication.Authority + /// is empty. The LoginPage surfaces a banner in that case and disables + /// the Register / Forgot password buttons. + /// + public bool ConfigMissing => + string.IsNullOrWhiteSpace(Settings.Authentication?.Authority); + + /// + /// Localised banner shown when is true. + /// The path follows the XDG spec on Linux (where PostIt.Desktop runs): + /// the file is expected at ~/.config/PostIt/postit-settings.json. + /// + public string ConfigMissingMessage => + $"Configuration PostIt manquante — voir ~/.config/PostIt/postit-settings.json"; + + private string BuildExternalUrl(string path) + { + var authority = Settings.Authentication?.Authority?.TrimEnd('/'); + return string.IsNullOrEmpty(authority) + ? string.Empty + : authority + path; + } + private string _AccessToken; public string AccessToken { get => _AccessToken; private set => this.SetProperty(ref _AccessToken, value); } @@ -36,6 +79,11 @@ public partial class LoginPageViewModel : ViewModelBase public LoginPageViewModel() : this(new Settings(), browserFactoryOverride: null) { + // Load settings eagerly so RegisterUrl / ForgotPasswordUrl are + // populated as soon as the page renders (XAML bindings fire + // before the user clicks Login). + try { Settings.Load().GetAwaiter().GetResult(); } + catch { /* settings may be missing in tests/dev; LoginAsync will surface real errors */ } } /// diff --git a/src/PostIt/PostIt/Views/LoginPage.axaml b/src/PostIt/PostIt/Views/LoginPage.axaml index f7b9fc53..87c8844e 100644 --- a/src/PostIt/PostIt/Views/LoginPage.axaml +++ b/src/PostIt/PostIt/Views/LoginPage.axaml @@ -4,36 +4,58 @@ x:Class="PostIt.Views.LoginPage" x:DataType="vm:LoginPageViewModel" Header="Login"> + + + - + - + + + - - - - + - - - - + + + + -