diff --git a/src/PostIt/PostIt/Assets/avalonia-logo.ico b/src/PostIt/PostIt/Assets/avalonia-logo.ico
deleted file mode 100644
index f7da8bb5..00000000
Binary files a/src/PostIt/PostIt/Assets/avalonia-logo.ico and /dev/null differ
diff --git a/src/PostIt/PostIt/Assets/yavsc-logo.ico b/src/PostIt/PostIt/Assets/yavsc-logo.ico
new file mode 100644
index 00000000..eeb0b9ea
Binary files /dev/null and b/src/PostIt/PostIt/Assets/yavsc-logo.ico differ
diff --git a/src/PostIt/PostIt/Assets/yavsc-logo.svg b/src/PostIt/PostIt/Assets/yavsc-logo.svg
new file mode 100644
index 00000000..e38de501
--- /dev/null
+++ b/src/PostIt/PostIt/Assets/yavsc-logo.svg
@@ -0,0 +1,21 @@
+
+
diff --git a/src/PostIt/PostIt/ViewModels/MainViewModel.cs b/src/PostIt/PostIt/ViewModels/MainViewModel.cs
index 38be6e34..124251b4 100644
--- a/src/PostIt/PostIt/ViewModels/MainViewModel.cs
+++ b/src/PostIt/PostIt/ViewModels/MainViewModel.cs
@@ -2,6 +2,12 @@
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;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
@@ -12,6 +18,18 @@ namespace PostIt.ViewModels;
public partial class MainViewModel : ViewModelBase
{
+ [ObservableProperty]
+ private string _authority = "https://localhost:5001";
+
+ [ObservableProperty]
+ private string _clientId = "postit";
+
+ [ObservableProperty]
+ private string _clientSecret = "postit-secret";
+
+ [ObservableProperty]
+ private string _scope = "blog";
+
[ObservableProperty]
private string _apiUrl = "http://localhost:5000";
@@ -79,6 +97,19 @@ public partial class MainViewModel : ViewModelBase
ApplyFilter();
}
+ [RelayCommand]
+ public async Task LoginAsync()
+ {
+ await ExecuteAsync(async () =>
+ {
+ var tokenResponse = await RequestClientCredentialsTokenAsync();
+ BearerToken = tokenResponse.AccessToken;
+ StatusMessage = string.IsNullOrWhiteSpace(tokenResponse.Error)
+ ? "Bearer token acquired."
+ : $"Token acquired with warning: {tokenResponse.ErrorDescription}";
+ });
+ }
+
[RelayCommand]
public async Task SaveAsync()
{
@@ -200,6 +231,52 @@ public partial class MainViewModel : ViewModelBase
}
}
+ private async Task RequestClientCredentialsTokenAsync()
+ {
+ using var client = new HttpClient();
+ var discoveryUrl = Authority.TrimEnd('/') + "/.well-known/openid-configuration";
+ var discoveryDocument = await client.GetFromJsonAsync(discoveryUrl, JsonOptions);
+
+ if (discoveryDocument is null || string.IsNullOrWhiteSpace(discoveryDocument.TokenEndpoint))
+ {
+ throw new InvalidOperationException("Unable to discover the token endpoint from the authority.");
+ }
+
+ var request = new HttpRequestMessage(HttpMethod.Post, discoveryDocument.TokenEndpoint)
+ {
+ Content = new FormUrlEncodedContent(new Dictionary
+ {
+ ["grant_type"] = "client_credentials",
+ ["client_id"] = ClientId,
+ ["client_secret"] = ClientSecret,
+ ["scope"] = Scope,
+ })
+ };
+
+ var response = await client.SendAsync(request).ConfigureAwait(false);
+
+ var payload = await response.Content.ReadFromJsonAsync(JsonOptions);
+ if (payload is null)
+ {
+ throw new InvalidOperationException("Invalid token response from the identity provider.");
+ }
+
+ if (!response.IsSuccessStatusCode)
+ {
+ var message = string.IsNullOrWhiteSpace(payload.ErrorDescription)
+ ? payload.Error ?? "Unknown token error"
+ : payload.ErrorDescription;
+ throw new InvalidOperationException(message);
+ }
+
+ return payload;
+ }
+
+ private static readonly JsonSerializerOptions JsonOptions = new()
+ {
+ PropertyNameCaseInsensitive = true
+ };
+
private BlogApiClient CreateClient()
=> new BlogApiClient(ApiUrl, BearerToken);
@@ -213,4 +290,12 @@ public partial class MainViewModel : ViewModelBase
private bool CanSave() => SelectedPost is not null && !IsBusy;
private bool CanDelete() => SelectedPost is not null && SelectedPost.Id != 0 && !IsBusy;
+
+ private sealed record DiscoveryDocument([property: JsonPropertyName("token_endpoint")] string? TokenEndpoint);
+ private sealed record TokenResponse(
+ [property: JsonPropertyName("access_token")] string? AccessToken,
+ [property: JsonPropertyName("token_type")] string? TokenType,
+ [property: JsonPropertyName("expires_in")] int ExpiresIn,
+ [property: JsonPropertyName("error")] string? Error,
+ [property: JsonPropertyName("error_description")] string? ErrorDescription);
}
diff --git a/src/PostIt/PostIt/Views/MainView.axaml b/src/PostIt/PostIt/Views/MainView.axaml
index 9b69880f..5e70425a 100644
--- a/src/PostIt/PostIt/Views/MainView.axaml
+++ b/src/PostIt/PostIt/Views/MainView.axaml
@@ -16,17 +16,27 @@
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/PostIt/PostIt/Views/MainWindow.axaml b/src/PostIt/PostIt/Views/MainWindow.axaml
index 821c4da7..3812a7bb 100644
--- a/src/PostIt/PostIt/Views/MainWindow.axaml
+++ b/src/PostIt/PostIt/Views/MainWindow.axaml
@@ -6,7 +6,7 @@
xmlns:views="using:PostIt.Views"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="PostIt.Views.MainWindow"
- Icon="/Assets/avalonia-logo.ico"
+ Icon="/Assets/yavsc-logo.ico"
Title="PostIt">
diff --git a/src/Yavsc.Org/Extensions/HostingExtensions.cs b/src/Yavsc.Org/Extensions/HostingExtensions.cs
index f75d8838..9296c2ba 100644
--- a/src/Yavsc.Org/Extensions/HostingExtensions.cs
+++ b/src/Yavsc.Org/Extensions/HostingExtensions.cs
@@ -306,7 +306,7 @@ public static class HostingExtensions
sql => sql.MigrationsAssembly(migrationsAssembly));
}
- b.UseSeeding(EnsureDefaultApplicationScopes());
+ b.UseSeeding(EnsureDefaultConfiguration());
};
})
.AddOperationalStore(options =>
@@ -351,7 +351,47 @@ public static class HostingExtensions
context.SaveChanges();
}
}
+ };
+ }
+ private static Action EnsureDefaultConfiguration()
+ {
+ return (context, _) =>
+ {
+ EnsureDefaultApplicationScopes()(context, _);
+
+ var existingClient = context.Set().FirstOrDefault(c => c.ClientId == "postit");
+ if (existingClient == null)
+ {
+ var client = new Client
+ {
+ ClientId = "postit",
+ Enabled = true,
+ RequireClientSecret = true,
+ ProtocolType = "oidc",
+ RequireConsent = false,
+ };
+
+ context.Set().Add(client);
+ context.Set().Add(new ClientGrantType
+ {
+ Client = client,
+ GrantType = "client_credentials"
+ });
+ context.Set().Add(new ClientScope
+ {
+ Client = client,
+ Scope = "blog"
+ });
+ context.Set().Add(new ClientSecret
+ {
+ Client = client,
+ Value = "postit-secret".Sha256(),
+ Type = IdentityServer8.Models.IdentityServerConstants.SecretTypes.SharedSecret
+ });
+
+ context.SaveChanges();
+ }
};
}
diff --git a/src/Yavsc.Org/Views/Shared/_Layout.cshtml b/src/Yavsc.Org/Views/Shared/_Layout.cshtml
index 3f2852db..809383cb 100644
--- a/src/Yavsc.Org/Views/Shared/_Layout.cshtml
+++ b/src/Yavsc.Org/Views/Shared/_Layout.cshtml
@@ -5,8 +5,8 @@
-
-
+
+
diff --git a/src/Yavsc.Org/Views/Shared/_Nav.cshtml b/src/Yavsc.Org/Views/Shared/_Nav.cshtml
index ed9e3d92..576977ab 100644
--- a/src/Yavsc.Org/Views/Shared/_Nav.cshtml
+++ b/src/Yavsc.Org/Views/Shared/_Nav.cshtml
@@ -1,5 +1,8 @@
-
diff --git a/src/Yavsc.Org/appsettings.json b/src/Yavsc.Org/appsettings.json
index e5a6c624..32a6d7b5 100644
--- a/src/Yavsc.Org/appsettings.json
+++ b/src/Yavsc.Org/appsettings.json
@@ -12,11 +12,12 @@
"YavscConnection": "Server=[YOURSERVERNAME];Port=5432;Database=[YOURDBNAME];Username=[YOURDBUSERNAME];Password=[YOURDBPASSW];"
},
"Site": {
- "Title": "Yavsc",
+ "Title": "yavsc",
"Slogan": "Yavsc!",
"StyleSheet": "/css/default.css",
- "Authority": "https://127.0.0.1:5001/",
- "Banner": "/images/arts/concert.jpg",
+ "Authority": "https://localhost:5001",
+ "ExternalUrl": "https://localhost:5001",
+ "Banner": "~/images/arts/PillarOfCreationOil.svg",
"Owner": {
"Name": "[Site owner's name]",
"EMail": "[Site owner's e-mail address]"
diff --git a/src/Yavsc.Org/wwwroot/favicon.ico b/src/Yavsc.Org/wwwroot/favicon.ico
index 0b8d8e63..eeb0b9ea 100644
Binary files a/src/Yavsc.Org/wwwroot/favicon.ico and b/src/Yavsc.Org/wwwroot/favicon.ico differ
diff --git a/src/Yavsc.Org/wwwroot/images/yavsc-logo.svg b/src/Yavsc.Org/wwwroot/images/yavsc-logo.svg
new file mode 100644
index 00000000..e38de501
--- /dev/null
+++ b/src/Yavsc.Org/wwwroot/images/yavsc-logo.svg
@@ -0,0 +1,21 @@
+
+