refactoring the login

This commit is contained in:
Paul Schneider 2026-06-20 15:01:03 +01:00
commit 2fd799c09f
13 changed files with 129 additions and 196 deletions

1
.gitignore vendored
View file

@ -23,6 +23,7 @@ package-lock.json
data/
appsettings.*.json
appsettings-*.*.json
*-settings.json
generated/
*.tmp

84
.vscode/launch.json vendored
View file

@ -5,97 +5,17 @@
"version": "0.2.0",
"configurations": [
{
"name": ".NET API",
"type": "coreclr",
"request": "launch",
"program": "${workspaceFolder}/src/Api/bin/Debug/net10.0/Api.dll",
"args": [],
"cwd": "${workspaceFolder}/src/Api",
"serverReadyAction": {
"pattern": "\\bNow listening on:\\s+(https?://\\S+)"
},
"env": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"envFile": "${workspaceFolder}/src/Api/.env"
},
{
"name": "C#: API Debug",
"name": "API",
"type": "dotnet",
"request": "launch",
"projectPath": "${workspaceFolder}/src/Api/Api.csproj"
},
{
"name": "Blogs",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/src/Yavsc.Blogs/bin/Debug/net10.0/Yavsc.Blogs.dll",
"args": [],
"cwd": "${workspaceFolder}/src/Yavsc.Blogs/",
"stopAtEntry": false,
"serverReadyAction": {
"action": "openExternally",
"pattern": "\\bNow listening on:\\s+(https?://\\S+)"
},
"env": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
{
"name": "C#: Yavsc.Org Debug",
"name": "Yavsc.Org",
"type": "dotnet",
"request": "launch",
"projectPath": "${workspaceFolder}/src/Yavsc.Org/Yavsc.Org.csproj"
},
{
"name": "Web",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build-web",
"program": "${workspaceFolder}/src/Yavsc.Web/bin/Debug/net10.0/Yavsc.Web.dll",
"args": [],
"cwd": "${workspaceFolder}/src/Yavsc.Web",
"stopAtEntry": false,
"serverReadyAction": {
"action": "openExternally",
"pattern": "\\bNow listening on:\\s+(https?://\\S+)"
},
"env": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
{
"name": "cli",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/src/cli/bin/Debug/net10.0/cli.dll",
"args": [],
"cwd": "${workspaceFolder}",
"stopAtEntry": false,
"console": "internalConsole"
},
{
"name": "Web",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/src/Yavsc.Web/bin/Debug/net10.0/Yavsc.Web.dll",
"args": [],
"cwd": "${workspaceFolder}/src/Yavsc.Web",
"stopAtEntry": false,
"serverReadyAction": {
"action": "openExternally",
"pattern": "\\bNow listening on:\\s+(https?://\\S+)"
},
"env": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"sourceFileMap": {
"/Views": "${workspaceFolder}/src/Yavsc.Web/Views"
}
},
{
"name": "PostIt",
"type": "dotnet",

View file

@ -13,6 +13,7 @@
"envsubst",
"Newtonsoft",
"Npgsql",
"postit",
"pschneider",
"SLNDIR",
"validable",

View file

@ -22,8 +22,8 @@
<ProjectReference Include="../../Yavsc.Abstract/Yavsc.Abstract.csproj" />
</ItemGroup>
<ItemGroup>
<Content Include="settings.json">
<Content Include="postit-settings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>
</Project>

View file

@ -12,7 +12,7 @@ namespace PostIt;
public partial class Settings : ObservableObject
{
const string SettingsFileName = "settings.json";
const string SettingsFileName = "postit-settings.json";
IStorageFolder? folder = null;
[ObservableProperty]
@ -42,8 +42,13 @@ public partial class Settings : ObservableObject
internal async Task Load()
{
String configPath =
Path.Combine(AppContext.BaseDirectory, SettingsFileName);
string configDir = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"PostIt"
);
Directory.CreateDirectory(configDir);
string configPath = Path.Combine(configDir, SettingsFileName);
FileInfo configFileInfo = new FileInfo(configPath);

View file

@ -27,6 +27,7 @@ public class ViewLocator : IDataTemplate
{
MainPageViewModel => _services.GetRequiredService<MainPage>(),
SettingsPageViewModel => _services.GetRequiredService<SettingsPage>(),
LoginPageViewModel => _services.GetRequiredService<LoginPage>(),
_ => new TextBlock { Text = $"No view for {data.GetType().Name}" }
};
}

View file

@ -0,0 +1,67 @@
using CommunityToolkit.Mvvm.Input;
using IdentityModel.OidcClient;
using System;
using System.Threading.Tasks;
namespace PostIt.ViewModels;
public partial class LoginPageViewModel : ViewModelBase
{
public string UserEmail { get; set; }
public string Password { get; set; }
private string _AccessToken;
public string AccessToken { get => _AccessToken; private set => this.SetProperty(ref _AccessToken, value); }
public bool RememberMe { get; set; }
public override bool CanNavigateNext { get => false; protected set => throw new System.NotImplementedException(); }
public override bool CanNavigatePrevious { get => true; protected set => throw new System.NotImplementedException(); }
public Settings Settings { get; }
private string _StatusMessage;
public string StatusMessage { get => _StatusMessage; private set => this.SetProperty(ref _StatusMessage, value); }
private bool _IsBusy;
public bool IsBusy { get=> _IsBusy; private set=> this.SetProperty(ref _IsBusy, value); }
public LoginPageViewModel()
{
Settings = new Settings();
StatusMessage = "Ready";
}
[RelayCommand]
public async Task LoginAsync()
{
try
{
Settings.Load().Wait();
var client = new OidcClient(Settings.GetOidcClientOptions());
var loginResult = await client.LoginAsync(new LoginRequest());
if (loginResult.IsError)
{
StatusMessage = loginResult.Error;
return;
}
StatusMessage = "Interactive token acquired.";
this.IsBusy = false;
AccessToken = loginResult.AccessToken;
/* TODO save or not user log and password
await Task.Run(() =>
{
Settings.Save().Wait();
});*/
}
catch (Exception ex)
{
this.IsBusy = false;
StatusMessage = "Error: "+ex.Message;
}
}
}

View file

@ -107,44 +107,6 @@ public partial class MainPageViewModel : ViewModelBase
ApplyFilter();
}
[RelayCommand]
internal async Task Login()
{
await ExecuteAsync(async () =>
{
// Try interactive OIDC login first
try
{
var loginWin = new PostIt.Views.LoginPage();
var result = await loginWin.StartLoginAsync(Settings.GetOidcClientOptions());
if (result is not null && !result.IsError && !string.IsNullOrWhiteSpace(result.AccessToken))
{
BearerToken = result.AccessToken;
StatusMessage = "Interactive token acquired.";
return;
}
}
catch
{
// ignore and fallback to client credentials
}
// Fallback to client credentials if interactive fails
var tokenResponse = await RequestClientCredentialsTokenAsync();
if (string.IsNullOrWhiteSpace(tokenResponse.AccessToken))
{
throw new InvalidOperationException("Failed to acquire a token using client credentials.");
}
BearerToken = tokenResponse.AccessToken;
StatusMessage = string.IsNullOrWhiteSpace(tokenResponse.Error)
? "Bearer token acquired (client credentials)."
: $"Token acquired with warning: {tokenResponse.ErrorDescription}";
});
}
[RelayCommand]
internal async Task Save()
{

View file

@ -1,37 +1,39 @@
<ContentPage xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="PostIt.Views.LoginPage"
Header="Login">
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:PostIt.ViewModels"
x:Class="PostIt.Views.LoginPage"
x:DataType="vm:LoginPageViewModel"
Header="Login">
<StackPanel HorizontalAlignment="Center"
VerticalAlignment="Center"
Spacing="20">
<StackPanel HorizontalAlignment="Center"
VerticalAlignment="Center"
Spacing="20">
<TextBlock Text="Sign In"
FontSize="24"
HorizontalAlignment="Center"/>
<TextBlock Text="Sign In"
FontSize="24"
HorizontalAlignment="Center"/>
<StackPanel Spacing="4">
<TextBlock Text="Email"/>
<TextBox Name="EmailBox"
PlaceholderText="Enter your email"/>
</StackPanel>
<StackPanel Spacing="4">
<TextBlock Text="Email"/>
<TextBox Name="EmailBox"
PlaceholderText="Enter your email"/>
</StackPanel>
<StackPanel Spacing="4">
<TextBlock Text="Password"/>
<TextBox Name="PasswordBox"
PlaceholderText="Enter your password"
PasswordChar="•"/>
</StackPanel>
<StackPanel Spacing="4">
<TextBlock Text="Password"/>
<TextBox Name="PasswordBox"
PlaceholderText="Enter your password"
PasswordChar="•"/>
</StackPanel>
<Button Content="Login"
Click="OnLoginClick"/>
<Button Content="Login"
Click="OnLoginClickAsync"/>
<Button Content="Cancel"
Click="OnCancelClick"/>
<Button Content="Cancel"
Click="OnCancelClick"/>
<TextBlock Name="StatusText" Text="Starting authentication..." />
<ProgressBar IsIndeterminate="True" />
<TextBlock Name="StatusText" Text="Starting authentication..." />
<ProgressBar IsIndeterminate="{Binding IsBusy}" />
</StackPanel>
</StackPanel>
</ContentPage>

View file

@ -10,6 +10,8 @@ namespace PostIt.Views;
public partial class LoginPage : ContentPage
{
private TaskCompletionSource<LoginResult?> _tcs = new();
private LoginResult loginResult;
public Settings Settings { get; }
@ -19,25 +21,22 @@ public partial class LoginPage : ContentPage
InitializeComponent();
}
public async Task<LoginResult?> StartLoginAsync(OidcClientOptions options)
private async void OnLoginClickAsync(object? sender, RoutedEventArgs e)
{
// This is where you specify your actual login auth logic
try
{
Settings.Load().Wait();
var client = new OidcClient(options);
var client = new OidcClient(Settings.GetOidcClientOptions());
var loginResult = await client.LoginAsync(new LoginRequest());
return loginResult;
this.loginResult = loginResult;
}
catch (Exception)
{
return null;
this.loginResult = null;
}
}
private async void OnLoginClick(object? sender, RoutedEventArgs e)
{
// This is where you specify your actual login auth logic
if (Navigation is not null)
await Navigation.PopModalAsync();
}

View file

@ -8,7 +8,9 @@
xmlns:AvaloniaEdit="clr-namespace:AvaloniaEdit;assembly=AvaloniaEdit"
mc:Ignorable="d"
x:Class="PostIt.Views.MainPage"
x:DataType="vm:MainPageViewModel" HorizontalAlignment="Center" VerticalAlignment="Center" >
x:DataType="vm:MainPageViewModel"
HorizontalAlignment="Center"
VerticalAlignment="Center" >
<Design.DataContext>
<vm:MainPageViewModel />
</Design.DataContext>
@ -17,7 +19,6 @@
<TextBlock Text="PostIt Blog API Interface" FontSize="20" FontWeight="Bold" />
<StackPanel Orientation="Horizontal" Spacing="8" >
<Button Command="{Binding Login}" Content="Acquire token" />
<Button Command="{Binding LoadPosts}" Content="Load posts" />
<Button Command="{Binding Search}" Content="Filter" />
<Button Command="{Binding New}" Content="New post" />

View file

@ -1,26 +0,0 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using PostIt.ViewModels;
public partial class MainWindowViewModel : ObservableObject
{
// Stocke le ViewModel de la page actuellement affichée
[ObservableProperty]
private object? _currentPage;
// Instances des pages pour éviter de les recréer à chaque fois (Optionnel)
private readonly HomePageViewModel _homePage = new();
private readonly SettingsPageViewModel _settingsPage = new();
public MainWindowViewModel()
{
// Définir la page de démarrage par défaut
CurrentPage = _homePage;
}
[RelayCommand]
private void NavigateToHome() => CurrentPage = _homePage;
[RelayCommand]
private void NavigateToSettings() => CurrentPage = _settingsPage;
}