diff --git a/Directory.Packages.props b/Directory.Packages.props
index 77bdfb45..19d2fdf8 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -3,9 +3,55 @@
true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -14,49 +60,14 @@
+
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -68,7 +79,6 @@
-
diff --git a/src/PostIt/Directory.Packages.props b/src/PostIt/Directory.Packages.props
deleted file mode 100644
index 0db241e6..00000000
--- a/src/PostIt/Directory.Packages.props
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
- true
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/PostIt/PostIt.slnx b/src/PostIt/PostIt.slnx
deleted file mode 100644
index dab49d68..00000000
--- a/src/PostIt/PostIt.slnx
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/PostIt/PostIt/App.axaml b/src/PostIt/PostIt/App.axaml
index b947594e..b179024a 100644
--- a/src/PostIt/PostIt/App.axaml
+++ b/src/PostIt/PostIt/App.axaml
@@ -1,10 +1,8 @@
-
-
+ x:Class="PostIt.App">
+
@@ -14,4 +12,4 @@
-
\ No newline at end of file
+
diff --git a/src/PostIt/PostIt/App.axaml.cs b/src/PostIt/PostIt/App.axaml.cs
index 2377bd71..6919f09d 100644
--- a/src/PostIt/PostIt/App.axaml.cs
+++ b/src/PostIt/PostIt/App.axaml.cs
@@ -6,11 +6,16 @@ using System.Linq;
using Avalonia.Markup.Xaml;
using PostIt.ViewModels;
using PostIt.Views;
+using Avalonia.Controls;
namespace PostIt;
public partial class App : Application
{
+ public App()
+ {
+ }
+
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
@@ -39,4 +44,4 @@ public partial class App : Application
base.OnFrameworkInitializationCompleted();
}
-}
\ No newline at end of file
+}
diff --git a/src/PostIt/PostIt/PostIt.csproj b/src/PostIt/PostIt/PostIt.csproj
index cbd2a8be..725a6c46 100644
--- a/src/PostIt/PostIt/PostIt.csproj
+++ b/src/PostIt/PostIt/PostIt.csproj
@@ -18,6 +18,15 @@
All
+
+
+
+
+
+
+
+ PreserveNewest
+
diff --git a/src/PostIt/PostIt/Services/BlogApiClient.cs b/src/PostIt/PostIt/Services/BlogApiClient.cs
index 7ce7a9e4..ee5d77e5 100644
--- a/src/PostIt/PostIt/Services/BlogApiClient.cs
+++ b/src/PostIt/PostIt/Services/BlogApiClient.cs
@@ -3,8 +3,10 @@ using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Http.Json;
+using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
+using IdentityModel.OidcClient;
using PostIt.Models;
namespace PostIt.Services;
@@ -14,8 +16,8 @@ public sealed class BlogApiClient : IDisposable
private readonly HttpClient _httpClient;
private readonly JsonSerializerOptions _serializerOptions;
- public BlogApiClient(string baseUrl, string? bearerToken = null)
- : this(CreateHttpClient(baseUrl, bearerToken))
+ public BlogApiClient(string baseUrl, string? accessToken = null)
+ : this(CreateHttpClient(baseUrl, accessToken))
{
}
@@ -28,18 +30,13 @@ public sealed class BlogApiClient : IDisposable
};
}
- private static HttpClient CreateHttpClient(string baseUrl, string? bearerToken)
+ private static HttpClient CreateHttpClient(string baseUrl, string? accessToken)
{
- var client = new HttpClient
+ var client = new HttpClient { BaseAddress = new Uri(baseUrl) };
+ if (!string.IsNullOrWhiteSpace(accessToken))
{
- BaseAddress = new Uri(baseUrl.TrimEnd('/') + "/")
- };
-
- if (!string.IsNullOrWhiteSpace(bearerToken))
- {
- client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", bearerToken.Trim());
+ client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
}
-
return client;
}
diff --git a/src/PostIt/PostIt/Services/LoopbackBrowser.cs b/src/PostIt/PostIt/Services/LoopbackBrowser.cs
new file mode 100644
index 00000000..c9dd06b1
--- /dev/null
+++ b/src/PostIt/PostIt/Services/LoopbackBrowser.cs
@@ -0,0 +1,56 @@
+using System;
+using System.Diagnostics;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using IdentityModel.OidcClient.Browser;
+
+namespace PostIt.Services;
+
+ public class LoopbackBrowser : IBrowser
+ {
+ public async Task InvokeAsync(BrowserOptions options, CancellationToken cancellationToken = default)
+ {
+ if (!Uri.TryCreate(options.EndUrl, UriKind.Absolute, out var endUri))
+ {
+ return new BrowserResult { ResultType = BrowserResultType.UnknownError, Error = "Invalid end URL" };
+ }
+
+ var prefix = endUri.GetLeftPart(UriPartial.Path);
+ if (!prefix.EndsWith("/")) prefix += "/";
+
+ using var listener = new HttpListener();
+ listener.Prefixes.Add(prefix);
+ listener.Start();
+
+ try
+ {
+ Process.Start(new ProcessStartInfo(options.StartUrl) { UseShellExecute = true });
+
+ var context = await listener.GetContextAsync().ConfigureAwait(false);
+ var response = context.Response;
+ var responseString = "Authentication complete. You can close this window.";
+ var buffer = Encoding.UTF8.GetBytes(responseString);
+ response.ContentLength64 = buffer.Length;
+ await response.OutputStream.WriteAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
+ response.OutputStream.Close();
+
+ var raw = context.Request.Url!.ToString();
+ return new BrowserResult
+ {
+ ResultType = BrowserResultType.Success,
+ Response = raw
+ };
+ }
+ catch (Exception ex)
+ {
+ return new BrowserResult { ResultType = BrowserResultType.UnknownError, Error = ex.Message };
+ }
+ finally
+ {
+ try { listener.Stop(); } catch { }
+ }
+ }
+ }
diff --git a/src/PostIt/PostIt/Settings/AuthenticationSettings.cs b/src/PostIt/PostIt/Settings/AuthenticationSettings.cs
new file mode 100644
index 00000000..38927ec2
--- /dev/null
+++ b/src/PostIt/PostIt/Settings/AuthenticationSettings.cs
@@ -0,0 +1,17 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+using System;
+
+public partial class AuthenticationSettings : ObservableObject
+{
+
+ [ObservableProperty]
+ public partial string Authority { get; set; }
+
+ [ObservableProperty]
+ public partial string ClientId { get; set; }
+
+ [ObservableProperty]
+ public partial string ClientSecret { get; set; }
+
+
+}
diff --git a/src/PostIt/PostIt/Settings/Settings.cs b/src/PostIt/PostIt/Settings/Settings.cs
new file mode 100644
index 00000000..462faedd
--- /dev/null
+++ b/src/PostIt/PostIt/Settings/Settings.cs
@@ -0,0 +1,84 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Platform.Storage;
+using CommunityToolkit.Mvvm.ComponentModel;
+using IdentityModel.OidcClient;
+using System;
+using System.IO;
+using System.Text.Json;
+using System.Threading.Tasks;
+
+namespace PostIt;
+
+public partial class Settings : ObservableObject
+{
+ const string SettingsFileName = "settings.json";
+ IStorageFolder? folder = null;
+
+ [ObservableProperty]
+ public partial AuthenticationSettings Authentication { get; set; } = new();
+
+ [ObservableProperty]
+ public partial bool DarkMode { get; set; } = false;
+
+ [ObservableProperty]
+ public partial string ApiUrl { get; set; } = "https://blogs.pschneider.fr/api/v1/";
+
+
+ [ObservableProperty]
+ public partial string[] Scopes { get; set; }
+
+ internal OidcClientOptions GetOidcClientOptions()
+ {
+ return new OidcClientOptions
+ {
+ Authority = Authentication.Authority,
+ ClientId = Authentication.ClientId,
+ ClientSecret = Authentication.ClientSecret,
+ Scope = string.Join(' ', this.Scopes)
+ };
+
+ }
+
+ internal async Task Load(IStorageProvider storageProvider)
+ {
+ var configFile = await storageProvider.TryGetFileFromPathAsync(
+ Path.Combine(AppContext.BaseDirectory, SettingsFileName));
+
+ if (configFile is null)
+ {
+ Console.Error.WriteLine("🩎 No settings file found.");
+ return; // no settings file
+ }
+ try {
+ using var stream = await configFile.OpenReadAsync();
+
+
+ using var reader = new StreamReader( stream);
+ var json = await reader.ReadToEndAsync();
+ if (string.IsNullOrWhiteSpace(json))
+ {
+ Console.Error.WriteLine("🩎 Settings file is empty.");
+ return ;
+ }
+
+ var settings = JsonSerializer.Deserialize(json);
+
+ if (settings is null)
+ {
+ Console.Error.WriteLine("🩎 Settings file is invalid.");
+ return ;
+ }
+ this.Authentication = settings.Authentication;
+ this.DarkMode = settings.DarkMode;
+ this.ApiUrl = settings.ApiUrl;
+ this.Scopes = settings.Scopes;
+
+
+ }
+ catch (Exception ex)
+ {
+ Console.Error.WriteLine($"🩎 Error loading settings: {ex.Message}");
+ }
+ }
+}
diff --git a/src/PostIt/PostIt/ViewModels/MainViewModel.cs b/src/PostIt/PostIt/ViewModels/MainViewModel.cs
index 124251b4..6dcc951d 100644
--- a/src/PostIt/PostIt/ViewModels/MainViewModel.cs
+++ b/src/PostIt/PostIt/ViewModels/MainViewModel.cs
@@ -3,9 +3,7 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Net.Http;
-using System.Net.Http.Headers;
using System.Net.Http.Json;
-using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
@@ -13,49 +11,50 @@ using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using PostIt.Models;
using PostIt.Services;
+using Avalonia.Styling;
namespace PostIt.ViewModels;
public partial class MainViewModel : ViewModelBase
{
- [ObservableProperty]
- private string _authority = "https://localhost:5001";
[ObservableProperty]
- private string _clientId = "postit";
+ public partial string StatusMessage { get; set; }
[ObservableProperty]
- private string _clientSecret = "postit-secret";
+ public partial string SearchText { get; set; }
[ObservableProperty]
- private string _scope = "blog";
+ public partial string BearerToken { get; set; }
[ObservableProperty]
- private string _apiUrl = "http://localhost:5000";
+ public partial ObservableCollection Posts { get; set; }
[ObservableProperty]
- private string _searchText = string.Empty;
+ public partial ObservableCollection FilteredPosts { get; set; }
[ObservableProperty]
- private string? _bearerToken;
+ public partial BlogPost? SelectedPost{ get; set; }
[ObservableProperty]
- private ObservableCollection _posts = new();
+ public partial bool IsBusy{ get; set; }
[ObservableProperty]
- private ObservableCollection _filteredPosts = new();
-
+ ThemeVariant themeVariant = ThemeVariant.Default;
+
[ObservableProperty]
- private BlogPost? _selectedPost;
-
- [ObservableProperty]
- private string _statusMessage = "Ready";
-
- [ObservableProperty]
- private bool _isBusy;
+ public partial Settings Settings { get; private set; }
public MainViewModel()
{
+ SearchText = string.Empty;
+ Posts = new ObservableCollection();
+ FilteredPosts = new ObservableCollection();
+ SelectedPost = null;
+ BearerToken = string.Empty;
+ IsBusy = false;
+ StatusMessage = "Ready";
+ Settings = new Settings();
}
partial void OnSearchTextChanged(string value)
@@ -74,7 +73,7 @@ public partial class MainViewModel : ViewModelBase
}
[RelayCommand]
- public async Task LoadPostsAsync()
+ internal async Task LoadPosts()
{
await ExecuteAsync(async () =>
{
@@ -92,26 +91,51 @@ public partial class MainViewModel : ViewModelBase
}
[RelayCommand]
- public void Search()
+ internal void Search()
{
ApplyFilter();
}
[RelayCommand]
- public async Task LoginAsync()
+ internal async Task Login()
{
await ExecuteAsync(async () =>
{
+ // Try interactive OIDC login first
+ try
+ {
+
+ var loginWin = new PostIt.Views.LoginWindow();
+ 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."
+ ? "Bearer token acquired (client credentials)."
: $"Token acquired with warning: {tokenResponse.ErrorDescription}";
});
}
[RelayCommand]
- public async Task SaveAsync()
+ internal async Task Save()
{
if (SelectedPost is null)
{
@@ -146,7 +170,7 @@ public partial class MainViewModel : ViewModelBase
}
[RelayCommand]
- public async Task DeleteAsync()
+ internal async Task Delete()
{
if (SelectedPost is null || SelectedPost.Id == 0)
{
@@ -165,7 +189,7 @@ public partial class MainViewModel : ViewModelBase
}
[RelayCommand]
- public void New()
+ internal void New()
{
SelectedPost = new BlogPost
{
@@ -178,6 +202,7 @@ public partial class MainViewModel : ViewModelBase
StatusMessage = "New blog post ready.";
}
+
private async Task RefreshPostsAsync()
{
using var client = CreateClient();
@@ -198,6 +223,8 @@ public partial class MainViewModel : ViewModelBase
private void ApplyFilter()
{
+ if (Posts is null) return;
+
var query = SearchText?.Trim();
var filtered = string.IsNullOrWhiteSpace(query)
? Posts.OrderByDescending(p => p.DateModified)
@@ -234,7 +261,7 @@ public partial class MainViewModel : ViewModelBase
private async Task RequestClientCredentialsTokenAsync()
{
using var client = new HttpClient();
- var discoveryUrl = Authority.TrimEnd('/') + "/.well-known/openid-configuration";
+ var discoveryUrl = Settings.Authentication.Authority.TrimEnd('/') + "/.well-known/openid-configuration";
var discoveryDocument = await client.GetFromJsonAsync(discoveryUrl, JsonOptions);
if (discoveryDocument is null || string.IsNullOrWhiteSpace(discoveryDocument.TokenEndpoint))
@@ -247,9 +274,9 @@ public partial class MainViewModel : ViewModelBase
Content = new FormUrlEncodedContent(new Dictionary
{
["grant_type"] = "client_credentials",
- ["client_id"] = ClientId,
- ["client_secret"] = ClientSecret,
- ["scope"] = Scope,
+ ["client_id"] = Settings.Authentication.ClientId,
+ ["client_secret"] = Settings.Authentication.ClientSecret,
+ ["scope"] = string.Join(' ', Settings.Scopes),
})
};
@@ -278,7 +305,7 @@ public partial class MainViewModel : ViewModelBase
};
private BlogApiClient CreateClient()
- => new BlogApiClient(ApiUrl, BearerToken);
+ => new BlogApiClient(Settings.ApiUrl, BearerToken);
private void UpdateCommandStates()
{
diff --git a/src/PostIt/PostIt/ViewModels/SettingsViewModel.cs b/src/PostIt/PostIt/ViewModels/SettingsViewModel.cs
new file mode 100644
index 00000000..aab3b2af
--- /dev/null
+++ b/src/PostIt/PostIt/ViewModels/SettingsViewModel.cs
@@ -0,0 +1,19 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+
+namespace PostIt.ViewModels;
+
+public partial class SettingsViewModel : ViewModelBase
+{
+ [ObservableProperty]
+ public partial bool DarkMode { get; set; }
+
+ [ObservableProperty]
+ public partial string Authority { get; set; }
+
+ [ObservableProperty]
+ public partial string ClientId { get; set; }
+
+ [ObservableProperty]
+ public partial string ClientSecret { get; set; }
+
+}
diff --git a/src/PostIt/PostIt/ViewModels/ViewModelBase.cs b/src/PostIt/PostIt/ViewModels/ViewModelBase.cs
index 9e159fa3..3d28c6dd 100644
--- a/src/PostIt/PostIt/ViewModels/ViewModelBase.cs
+++ b/src/PostIt/PostIt/ViewModels/ViewModelBase.cs
@@ -1,7 +1,8 @@
-using CommunityToolkit.Mvvm.ComponentModel;
+using Avalonia.Styling;
+using CommunityToolkit.Mvvm.ComponentModel;
namespace PostIt.ViewModels;
-public abstract class ViewModelBase : ObservableObject
+public abstract partial class ViewModelBase : ObservableObject
{
}
diff --git a/src/PostIt/PostIt/Views/LoginWindow.axaml b/src/PostIt/PostIt/Views/LoginWindow.axaml
new file mode 100644
index 00000000..6d80609b
--- /dev/null
+++ b/src/PostIt/PostIt/Views/LoginWindow.axaml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/PostIt/PostIt/Views/LoginWindow.axaml.cs b/src/PostIt/PostIt/Views/LoginWindow.axaml.cs
new file mode 100644
index 00000000..f80a2698
--- /dev/null
+++ b/src/PostIt/PostIt/Views/LoginWindow.axaml.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Threading.Tasks;
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+using IdentityModel.OidcClient;
+using PostIt.Services;
+
+namespace PostIt.Views;
+
+public partial class LoginWindow : Window
+{
+ private TaskCompletionSource _tcs = new();
+
+ public LoginWindow()
+ {
+ InitializeComponent();
+ CancelButton.Click += (_, __) => Close(null);
+ }
+
+ public async Task StartLoginAsync(OidcClientOptions options)
+ {
+ try
+ {
+ var client = new OidcClient(options);
+ var loginResult = await client.LoginAsync(new LoginRequest());
+ return loginResult;
+ }
+ catch (Exception)
+ {
+ return null;
+ }
+ }
+}
diff --git a/src/PostIt/PostIt/Views/MainView.axaml b/src/PostIt/PostIt/Views/MainView.axaml
index 5e70425a..d1aae9de 100644
--- a/src/PostIt/PostIt/Views/MainView.axaml
+++ b/src/PostIt/PostIt/Views/MainView.axaml
@@ -16,35 +16,16 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
diff --git a/src/PostIt/PostIt/Views/MainView.axaml.cs b/src/PostIt/PostIt/Views/MainView.axaml.cs
index 2407b12d..0ed68cf1 100644
--- a/src/PostIt/PostIt/Views/MainView.axaml.cs
+++ b/src/PostIt/PostIt/Views/MainView.axaml.cs
@@ -10,4 +10,5 @@ public partial class MainView : UserControl
{
InitializeComponent();
}
+
}
\ No newline at end of file
diff --git a/src/PostIt/PostIt/Views/MainWindow.axaml b/src/PostIt/PostIt/Views/MainWindow.axaml
index 3812a7bb..e9f1966a 100644
--- a/src/PostIt/PostIt/Views/MainWindow.axaml
+++ b/src/PostIt/PostIt/Views/MainWindow.axaml
@@ -4,9 +4,10 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:views="using:PostIt.Views"
- mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="PostIt.Views.MainWindow"
+ x:DataType="vm:MainViewModel"
Icon="/Assets/yavsc-logo.ico"
- Title="PostIt">
-
+ Title="PostIt"
+ >
+
diff --git a/src/PostIt/PostIt/Views/MainWindow.axaml.cs b/src/PostIt/PostIt/Views/MainWindow.axaml.cs
index 9cb85f55..831fb6e0 100644
--- a/src/PostIt/PostIt/Views/MainWindow.axaml.cs
+++ b/src/PostIt/PostIt/Views/MainWindow.axaml.cs
@@ -1,11 +1,35 @@
using Avalonia.Controls;
+using Avalonia.Styling;
+using PostIt.ViewModels;
+using System;
+using System.Threading.Tasks;
namespace PostIt.Views;
public partial class MainWindow : Window
{
+ internal Settings Settings { get; private set; }
+
public MainWindow()
{
+
InitializeComponent();
+
+ this.Settings = new Settings();
+
+
}
+
+ protected override void OnDataContextChanged(EventArgs e)
+ {
+ base.OnDataContextChanged(e);
+ if (DataContext is MainViewModel vm)
+ {
+ Task.Run(async () => await this.Settings.Load(this.StorageProvider)).Wait();
+ this.RequestedThemeVariant = Settings.DarkMode ? ThemeVariant.Dark : ThemeVariant.Light;
+ MainView.DataContext = new MainViewModel();
+ vm.Settings.Load(this.StorageProvider).Wait();
+ }
+ }
+
}
\ No newline at end of file
diff --git a/src/PostIt/PostIt/Views/SettingsView.axaml b/src/PostIt/PostIt/Views/SettingsView.axaml
new file mode 100644
index 00000000..40a403ac
--- /dev/null
+++ b/src/PostIt/PostIt/Views/SettingsView.axaml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/PostIt/PostIt/Views/SettingsView.axaml.cs b/src/PostIt/PostIt/Views/SettingsView.axaml.cs
new file mode 100644
index 00000000..20e8c1c2
--- /dev/null
+++ b/src/PostIt/PostIt/Views/SettingsView.axaml.cs
@@ -0,0 +1,12 @@
+
+
+using Avalonia.Controls;
+
+namespace PostIt.Views;
+public partial class SettingsView: Window
+{
+ public SettingsView()
+ {
+ InitializeComponent();
+ }
+}
\ No newline at end of file
diff --git a/src/PostIt/PostIt/settings.json b/src/PostIt/PostIt/settings.json
new file mode 100644
index 00000000..d0dcfd90
--- /dev/null
+++ b/src/PostIt/PostIt/settings.json
@@ -0,0 +1,16 @@
+{
+ "Authentication": {
+ "ClientId": "postit",
+ "ClientSecret": "postit-secret",
+ "Authority": "https://blogs.pschneider.fr/auth/realms/master",
+ },
+ "DarkMode": false,
+ "ApiUrl": "https://blogs.pschneider.fr/api/v1/",
+ "Scopes": [
+ "openid",
+ "profile",
+ "email",
+ "offline_access",
+ "blogs"
+ ]
+}
diff --git a/src/Yavsc.Blogs/Controllers/BlogApiController.cs b/src/Yavsc.Blogs/Controllers/BlogApiController.cs
index e4f5f382..e442d814 100644
--- a/src/Yavsc.Blogs/Controllers/BlogApiController.cs
+++ b/src/Yavsc.Blogs/Controllers/BlogApiController.cs
@@ -7,7 +7,7 @@ using Yavsc.Models.Blog;
using Yavsc.Server.Exceptions;
using Yavsc.Server.Helpers;
-namespace Yavsc.Controllers
+namespace Yavsc.Blogs.Controllers
{
[Authorize("BlogScope")]
[Produces("application/json")]
diff --git a/src/Yavsc.Blogs/Controllers/BlogTagsApiController.cs b/src/Yavsc.Blogs/Controllers/BlogTagsApiController.cs
index a5f9cbac..b6053856 100644
--- a/src/Yavsc.Blogs/Controllers/BlogTagsApiController.cs
+++ b/src/Yavsc.Blogs/Controllers/BlogTagsApiController.cs
@@ -6,7 +6,7 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Yavsc.Models;
using Yavsc.Models.Blog;
-namespace Yavsc.Controllers
+namespace Yavsc.Blogs.Controllers
{
[Produces("application/json")]
[Route("api/blogtags")]
diff --git a/src/Yavsc.Blogs/Controllers/CommentsApiController.cs b/src/Yavsc.Blogs/Controllers/CommentsApiController.cs
index 039c52cf..e5694c55 100644
--- a/src/Yavsc.Blogs/Controllers/CommentsApiController.cs
+++ b/src/Yavsc.Blogs/Controllers/CommentsApiController.cs
@@ -8,7 +8,7 @@ using Yavsc.Models;
using Yavsc.Models.Blog;
using Yavsc.Server.Helpers;
-namespace Yavsc.Controllers
+namespace Yavsc.Blogs.Controllers
{
[Authorize]
[Produces("application/json")]
diff --git a/src/Yavsc.Blogs/Controllers/FileSystemApiController.cs b/src/Yavsc.Blogs/Controllers/FileSystemApiController.cs
index 7896efc2..29d6c879 100644
--- a/src/Yavsc.Blogs/Controllers/FileSystemApiController.cs
+++ b/src/Yavsc.Blogs/Controllers/FileSystemApiController.cs
@@ -2,20 +2,15 @@
using System.Security.Claims;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
-using Yavsc.Models;
-namespace Yavsc.ApiControllers
+namespace Yavsc.Blogs.Controllers
{
- using System.Threading.Tasks;
- using Microsoft.Extensions.Logging;
- using Yavsc.Helpers;
- using Yavsc.Models.FileSystem;
- using System.ComponentModel.DataAnnotations;
using Yavsc.Attributes.Validation;
- using System.IO;
+ using Yavsc.Models;
using Yavsc.Exceptions;
using Yavsc.Server.Helpers;
using Yavsc.Abstract.Helpers;
+ using Yavsc.Server.Models.FileSystem;
[Authorize,Route("api/fs")]
public partial class FileSystemApiController : Controller
diff --git a/src/Yavsc.Blogs/Controllers/FileSystemStream.cs b/src/Yavsc.Blogs/Controllers/FileSystemStream.cs
index ee6aed0c..58dc3e2a 100644
--- a/src/Yavsc.Blogs/Controllers/FileSystemStream.cs
+++ b/src/Yavsc.Blogs/Controllers/FileSystemStream.cs
@@ -10,7 +10,7 @@ using Yavsc.Services;
using Microsoft.AspNetCore.SignalR;
using Yavsc.Server.Helpers;
-namespace Yavsc.ApiControllers
+namespace Yavsc.Blogs.Controllers
{
[Authorize, Route("api/stream")]
public partial class FileSystemStreamController : Controller
diff --git a/src/Yavsc.Blogs/Controllers/PostTagsApiController.cs b/src/Yavsc.Blogs/Controllers/PostTagsApiController.cs
index bd2a9e2a..646fcee6 100644
--- a/src/Yavsc.Blogs/Controllers/PostTagsApiController.cs
+++ b/src/Yavsc.Blogs/Controllers/PostTagsApiController.cs
@@ -3,7 +3,7 @@ using System.Linq;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
-namespace Yavsc.Controllers
+namespace Yavsc.Blogs.Controllers
{
using System.Security.Claims;
using Microsoft.EntityFrameworkCore;
diff --git a/src/Yavsc.Blogs/Program.cs b/src/Yavsc.Blogs/Program.cs
index b3dbc5d6..745333ab 100644
--- a/src/Yavsc.Blogs/Program.cs
+++ b/src/Yavsc.Blogs/Program.cs
@@ -15,12 +15,12 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Yavsc;
-using Yavsc.Helpers;
using Yavsc.Interface;
using Yavsc.Models;
using Yavsc.Services;
using Yavsc.Server.Helpers;
using Yavsc.Extensions;
+namespace Yavsc.Blogs;
internal class Program
{
@@ -114,15 +114,9 @@ internal class Program
.UseAuthentication()
.UseAuthorization()
.UseCors("default")
- /* .UseEndpoints(endpoints =>
- {
- endpoints.MapDefaultControllerRoute()
- .RequireAuthorization();
- })*/
-
;
- // app.MapIdentityApi().RequireAuthorization("ApiScope");
- app.MapDefaultControllerRoute();
+ app.MapIdentityApi().RequireAuthorization("blog");
+
app.MapGet("/identity", (HttpContext context) =>
new JsonResult(context?.User?.Claims.Select(c => new { c.Type, c.Value }))
);
diff --git a/src/Yavsc.Blogs/Yavsc.Blogs.csproj b/src/Yavsc.Blogs/Yavsc.Blogs.csproj
index 88b632dd..1ae604a8 100644
--- a/src/Yavsc.Blogs/Yavsc.Blogs.csproj
+++ b/src/Yavsc.Blogs/Yavsc.Blogs.csproj
@@ -3,7 +3,7 @@
net10.0
enable
1c73094f-959f-4211-b1a1-6a69b236c283
- Yavsc.Api
+ Yavsc.Blogs
https://github.com/pazof/yavsc
true
diff --git a/src/Yavsc.Org/Controllers/Accounting/AccountController.cs b/src/Yavsc.Org/Controllers/Accounting/AccountController.cs
index 2c73575d..72f99b4f 100644
--- a/src/Yavsc.Org/Controllers/Accounting/AccountController.cs
+++ b/src/Yavsc.Org/Controllers/Accounting/AccountController.cs
@@ -666,7 +666,7 @@ IHtmlLocalizerFactory htmlLocalizerFactory,
await _signInManager.SignOutAsync();
HttpContext.Session.Clear();
_logger.LogInformation(4, "User logged out.");
- if (returnUrl == null) return RedirectToAction(nameof(HomeController.Index), "Home");
+ if (returnUrl == null) return RedirectToAction(nameof(AccountController.Index), "Home");
return Redirect(returnUrl);
}
diff --git a/src/Yavsc.Org/Extensions/HostingExtensions.cs b/src/Yavsc.Org/Extensions/HostingExtensions.cs
index 9296c2ba..3d21a5c0 100644
--- a/src/Yavsc.Org/Extensions/HostingExtensions.cs
+++ b/src/Yavsc.Org/Extensions/HostingExtensions.cs
@@ -31,6 +31,7 @@ using Yavsc.Services;
using Yavsc.Services.Kyc;
using Yavsc.Settings;
using Yavsc.ViewModels.Auth;
+using static IdentityServer8.IdentityServerConstants;
namespace Yavsc.Extensions;
@@ -360,10 +361,10 @@ public static class HostingExtensions
{
EnsureDefaultApplicationScopes()(context, _);
- var existingClient = context.Set().FirstOrDefault(c => c.ClientId == "postit");
+ var existingClient = context.Set().FirstOrDefault(c => c.ClientId == "postit");
if (existingClient == null)
{
- var client = new Client
+ var client = new IdentityServer8.EntityFramework.Entities.Client
{
ClientId = "postit",
Enabled = true,
@@ -373,21 +374,44 @@ public static class HostingExtensions
};
context.Set().Add(client);
- context.Set().Add(new ClientGrantType
+ // allow authorization code (interactive) and client credentials (m2m)
+ context.Set().Add(new IdentityServer8.EntityFramework.Entities.ClientGrantType
+ {
+ Client = client,
+ GrantType = "authorization_code"
+ });
+ context.Set().Add(new IdentityServer8.EntityFramework.Entities.ClientGrantType
{
Client = client,
GrantType = "client_credentials"
});
- context.Set().Add(new ClientScope
+
+ context.Set().Add(new IdentityServer8.EntityFramework.Entities.ClientScope
{
Client = client,
Scope = "blog"
});
- context.Set().Add(new ClientSecret
+ context.Set().Add(new IdentityServer8.EntityFramework.Entities.ClientScope
{
Client = client,
- Value = "postit-secret".Sha256(),
- Type = IdentityServer8.Models.IdentityServerConstants.SecretTypes.SharedSecret
+ Scope = IdentityServer8.IdentityServerConstants.StandardScopes.OpenId
+ });
+ context.Set().Add(new IdentityServer8.EntityFramework.Entities.ClientScope
+ {
+ Client = client,
+ Scope = IdentityServer8.IdentityServerConstants.StandardScopes.Profile
+ });
+
+ context.Set().Add(new IdentityServer8.EntityFramework.Entities.ClientRedirectUri
+ {
+ Client = client,
+ RedirectUri = "http://127.0.0.1:7890/"
+ });
+
+ context.Set().Add(new IdentityServer8.EntityFramework.Entities.ClientSecret
+ {
+ Client = client,
+ Value = "postit-secret".ToSha256(),
});
context.SaveChanges();
diff --git a/src/Yavsc.Server/Helpers/FileSystemHelpers.cs b/src/Yavsc.Server/Helpers/FileSystemHelpers.cs
index ccdf7a1f..f53ce0e8 100644
--- a/src/Yavsc.Server/Helpers/FileSystemHelpers.cs
+++ b/src/Yavsc.Server/Helpers/FileSystemHelpers.cs
@@ -12,6 +12,7 @@ using Yavsc.Exceptions;
using Yavsc.Helpers;
using Yavsc.Abstract.Helpers;
using ImageMagick;
+using Yavsc.Server.Models.FileSystem;
namespace Yavsc.Server.Helpers
{
public static class FileSystemHelpers
diff --git a/src/Yavsc.Server/Models/FileSystem/FileReceivedInfo.cs b/src/Yavsc.Server/Models/FileSystem/FileReceivedInfo.cs
index 0f7dc498..63627c11 100644
--- a/src/Yavsc.Server/Models/FileSystem/FileReceivedInfo.cs
+++ b/src/Yavsc.Server/Models/FileSystem/FileReceivedInfo.cs
@@ -23,7 +23,7 @@
using Yavsc.Abstract.FileSystem;
-namespace Yavsc.Models.FileSystem
+namespace Yavsc.Server.Models.FileSystem
{
public class FileReceivedInfo : IFileReceivedInfo
{
diff --git a/src/Yavsc.Blogs/Controllers/RenameFileQuery.cs b/src/Yavsc.Server/Models/FileSystem/RenameFileQuery.cs
similarity index 88%
rename from src/Yavsc.Blogs/Controllers/RenameFileQuery.cs
rename to src/Yavsc.Server/Models/FileSystem/RenameFileQuery.cs
index 3cde9d98..c2248d92 100644
--- a/src/Yavsc.Blogs/Controllers/RenameFileQuery.cs
+++ b/src/Yavsc.Server/Models/FileSystem/RenameFileQuery.cs
@@ -1,5 +1,5 @@
using Yavsc.Attributes.Validation;
-namespace Yavsc.Models.FileSystem
+namespace Yavsc.Server.Models.FileSystem
{
public class RenameFileQuery
{
diff --git a/src/Yavsc.Server/ViewModels/LiveCastHandler.cs b/src/Yavsc.Server/ViewModels/LiveCastHandler.cs
index f9384c4b..f3ce00cb 100644
--- a/src/Yavsc.Server/ViewModels/LiveCastHandler.cs
+++ b/src/Yavsc.Server/ViewModels/LiveCastHandler.cs
@@ -11,6 +11,7 @@ using Yavsc.Helpers;
using Yavsc.Models;
using Yavsc.Models.FileSystem;
using Yavsc.Server.Helpers;
+using Yavsc.Server.Models.FileSystem;
namespace Yavsc.ViewModels.Streaming
{
diff --git a/src/cli/Program.cs b/src/cli/Program.cs
index c4c4d050..4be09d05 100644
--- a/src/cli/Program.cs
+++ b/src/cli/Program.cs
@@ -6,8 +6,8 @@ using Microsoft.Extensions.Hosting;
internal class Program
{
public static IHost? AppHost { get; private set; }
- public static IConfigurationRoot? AppConfiguration { get; private set; }
- public static IHostEnvironment? AppEnvironment { get; private set; }
+ public static IConfigurationRoot AppConfiguration { get; private set; }
+ public static IHostEnvironment AppEnvironment { get; private set; }
private static void Main(string[] args)
{