refactoring the login
This commit is contained in:
parent
66c48329cc
commit
2fd799c09f
13 changed files with 129 additions and 196 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -23,6 +23,7 @@ package-lock.json
|
||||||
data/
|
data/
|
||||||
appsettings.*.json
|
appsettings.*.json
|
||||||
appsettings-*.*.json
|
appsettings-*.*.json
|
||||||
|
*-settings.json
|
||||||
|
|
||||||
generated/
|
generated/
|
||||||
*.tmp
|
*.tmp
|
||||||
|
|
|
||||||
84
.vscode/launch.json
vendored
84
.vscode/launch.json
vendored
|
|
@ -5,97 +5,17 @@
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"configurations": [
|
"configurations": [
|
||||||
{
|
{
|
||||||
"name": ".NET API",
|
"name": "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",
|
|
||||||
"type": "dotnet",
|
"type": "dotnet",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"projectPath": "${workspaceFolder}/src/Api/Api.csproj"
|
"projectPath": "${workspaceFolder}/src/Api/Api.csproj"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Blogs",
|
"name": "Yavsc.Org",
|
||||||
"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",
|
|
||||||
"type": "dotnet",
|
"type": "dotnet",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"projectPath": "${workspaceFolder}/src/Yavsc.Org/Yavsc.Org.csproj"
|
"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",
|
"name": "PostIt",
|
||||||
"type": "dotnet",
|
"type": "dotnet",
|
||||||
|
|
|
||||||
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
|
|
@ -13,6 +13,7 @@
|
||||||
"envsubst",
|
"envsubst",
|
||||||
"Newtonsoft",
|
"Newtonsoft",
|
||||||
"Npgsql",
|
"Npgsql",
|
||||||
|
"postit",
|
||||||
"pschneider",
|
"pschneider",
|
||||||
"SLNDIR",
|
"SLNDIR",
|
||||||
"validable",
|
"validable",
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,8 @@
|
||||||
<ProjectReference Include="../../Yavsc.Abstract/Yavsc.Abstract.csproj" />
|
<ProjectReference Include="../../Yavsc.Abstract/Yavsc.Abstract.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Include="settings.json">
|
<Content Include="postit-settings.json">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ namespace PostIt;
|
||||||
|
|
||||||
public partial class Settings : ObservableObject
|
public partial class Settings : ObservableObject
|
||||||
{
|
{
|
||||||
const string SettingsFileName = "settings.json";
|
const string SettingsFileName = "postit-settings.json";
|
||||||
IStorageFolder? folder = null;
|
IStorageFolder? folder = null;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
|
|
@ -42,8 +42,13 @@ public partial class Settings : ObservableObject
|
||||||
|
|
||||||
internal async Task Load()
|
internal async Task Load()
|
||||||
{
|
{
|
||||||
String configPath =
|
string configDir = Path.Combine(
|
||||||
Path.Combine(AppContext.BaseDirectory, SettingsFileName);
|
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||||||
|
"PostIt"
|
||||||
|
);
|
||||||
|
Directory.CreateDirectory(configDir);
|
||||||
|
|
||||||
|
string configPath = Path.Combine(configDir, SettingsFileName);
|
||||||
|
|
||||||
FileInfo configFileInfo = new FileInfo(configPath);
|
FileInfo configFileInfo = new FileInfo(configPath);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ public class ViewLocator : IDataTemplate
|
||||||
{
|
{
|
||||||
MainPageViewModel => _services.GetRequiredService<MainPage>(),
|
MainPageViewModel => _services.GetRequiredService<MainPage>(),
|
||||||
SettingsPageViewModel => _services.GetRequiredService<SettingsPage>(),
|
SettingsPageViewModel => _services.GetRequiredService<SettingsPage>(),
|
||||||
|
LoginPageViewModel => _services.GetRequiredService<LoginPage>(),
|
||||||
_ => new TextBlock { Text = $"No view for {data.GetType().Name}" }
|
_ => new TextBlock { Text = $"No view for {data.GetType().Name}" }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
67
src/PostIt/PostIt/ViewModels/LoginPageViewModel.cs
Normal file
67
src/PostIt/PostIt/ViewModels/LoginPageViewModel.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -107,44 +107,6 @@ public partial class MainPageViewModel : ViewModelBase
|
||||||
ApplyFilter();
|
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]
|
[RelayCommand]
|
||||||
internal async Task Save()
|
internal async Task Save()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,37 +1,39 @@
|
||||||
<ContentPage xmlns="https://github.com/avaloniaui"
|
<ContentPage xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
x:Class="PostIt.Views.LoginPage"
|
xmlns:vm="using:PostIt.ViewModels"
|
||||||
Header="Login">
|
x:Class="PostIt.Views.LoginPage"
|
||||||
|
x:DataType="vm:LoginPageViewModel"
|
||||||
|
Header="Login">
|
||||||
|
|
||||||
<StackPanel HorizontalAlignment="Center"
|
<StackPanel HorizontalAlignment="Center"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Spacing="20">
|
Spacing="20">
|
||||||
|
|
||||||
<TextBlock Text="Sign In"
|
<TextBlock Text="Sign In"
|
||||||
FontSize="24"
|
FontSize="24"
|
||||||
HorizontalAlignment="Center"/>
|
HorizontalAlignment="Center"/>
|
||||||
|
|
||||||
<StackPanel Spacing="4">
|
<StackPanel Spacing="4">
|
||||||
<TextBlock Text="Email"/>
|
<TextBlock Text="Email"/>
|
||||||
<TextBox Name="EmailBox"
|
<TextBox Name="EmailBox"
|
||||||
PlaceholderText="Enter your email"/>
|
PlaceholderText="Enter your email"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<StackPanel Spacing="4">
|
<StackPanel Spacing="4">
|
||||||
<TextBlock Text="Password"/>
|
<TextBlock Text="Password"/>
|
||||||
<TextBox Name="PasswordBox"
|
<TextBox Name="PasswordBox"
|
||||||
PlaceholderText="Enter your password"
|
PlaceholderText="Enter your password"
|
||||||
PasswordChar="•"/>
|
PasswordChar="•"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<Button Content="Login"
|
<Button Content="Login"
|
||||||
Click="OnLoginClick"/>
|
Click="OnLoginClickAsync"/>
|
||||||
|
|
||||||
<Button Content="Cancel"
|
<Button Content="Cancel"
|
||||||
Click="OnCancelClick"/>
|
Click="OnCancelClick"/>
|
||||||
|
|
||||||
<TextBlock Name="StatusText" Text="Starting authentication..." />
|
<TextBlock Name="StatusText" Text="Starting authentication..." />
|
||||||
<ProgressBar IsIndeterminate="True" />
|
<ProgressBar IsIndeterminate="{Binding IsBusy}" />
|
||||||
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ContentPage>
|
</ContentPage>
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ namespace PostIt.Views;
|
||||||
public partial class LoginPage : ContentPage
|
public partial class LoginPage : ContentPage
|
||||||
{
|
{
|
||||||
private TaskCompletionSource<LoginResult?> _tcs = new();
|
private TaskCompletionSource<LoginResult?> _tcs = new();
|
||||||
|
private LoginResult loginResult;
|
||||||
|
|
||||||
|
|
||||||
public Settings Settings { get; }
|
public Settings Settings { get; }
|
||||||
|
|
||||||
|
|
@ -19,25 +21,22 @@ public partial class LoginPage : ContentPage
|
||||||
InitializeComponent();
|
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
|
try
|
||||||
{
|
{
|
||||||
Settings.Load().Wait();
|
Settings.Load().Wait();
|
||||||
|
|
||||||
var client = new OidcClient(options);
|
var client = new OidcClient(Settings.GetOidcClientOptions());
|
||||||
var loginResult = await client.LoginAsync(new LoginRequest());
|
var loginResult = await client.LoginAsync(new LoginRequest());
|
||||||
return loginResult;
|
this.loginResult = loginResult;
|
||||||
}
|
}
|
||||||
catch (Exception)
|
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)
|
if (Navigation is not null)
|
||||||
await Navigation.PopModalAsync();
|
await Navigation.PopModalAsync();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,9 @@
|
||||||
xmlns:AvaloniaEdit="clr-namespace:AvaloniaEdit;assembly=AvaloniaEdit"
|
xmlns:AvaloniaEdit="clr-namespace:AvaloniaEdit;assembly=AvaloniaEdit"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
x:Class="PostIt.Views.MainPage"
|
x:Class="PostIt.Views.MainPage"
|
||||||
x:DataType="vm:MainPageViewModel" HorizontalAlignment="Center" VerticalAlignment="Center" >
|
x:DataType="vm:MainPageViewModel"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center" >
|
||||||
<Design.DataContext>
|
<Design.DataContext>
|
||||||
<vm:MainPageViewModel />
|
<vm:MainPageViewModel />
|
||||||
</Design.DataContext>
|
</Design.DataContext>
|
||||||
|
|
@ -17,7 +19,6 @@
|
||||||
<TextBlock Text="PostIt Blog API Interface" FontSize="20" FontWeight="Bold" />
|
<TextBlock Text="PostIt Blog API Interface" FontSize="20" FontWeight="Bold" />
|
||||||
|
|
||||||
<StackPanel Orientation="Horizontal" Spacing="8" >
|
<StackPanel Orientation="Horizontal" Spacing="8" >
|
||||||
<Button Command="{Binding Login}" Content="Acquire token" />
|
|
||||||
<Button Command="{Binding LoadPosts}" Content="Load posts" />
|
<Button Command="{Binding LoadPosts}" Content="Load posts" />
|
||||||
<Button Command="{Binding Search}" Content="Filter" />
|
<Button Command="{Binding Search}" Content="Filter" />
|
||||||
<Button Command="{Binding New}" Content="New post" />
|
<Button Command="{Binding New}" Content="New post" />
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue