diff --git a/RestApia.Shared.sln b/RestApia.Shared.sln index 88bd555..57eac8f 100644 --- a/RestApia.Shared.sln +++ b/RestApia.Shared.sln @@ -8,10 +8,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RestApia.Extensions.Auth.Ba EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RestApia.Extensions.Auth.OAuth2", "src\Extensions\RestApia.Extensions.Auth.OAuth2\RestApia.Extensions.Auth.OAuth2.csproj", "{AE97EEF8-CE4E-0976-B0FB-8C4FC7616E01}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Builder", "Builder", "{07A0437C-C5B5-4FC9-AA80-71C96A11FF54}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Builder.Shared", ".build\Builder.Shared.csproj", "{35A67EB2-CF2D-40B2-B598-685D70F9D94D}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RestApia.Extensions.Import.Postman", "src\Extensions\RestApia.Extensions.Import.Postman\RestApia.Extensions.Import.Postman.csproj", "{00CAA372-8954-4A50-94C5-6574EBACC467}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Playground", "Playground", "{B9D6F201-FDF7-42BB-A688-C8CBC51B4718}" @@ -22,6 +18,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RestApia.Extensions.ValuesP EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RestApia.Extensions.ValuesProvider.CollectionValuesProvider", "src\Extensions\RestApia.Extensions.ValuesProvider.CollectionValuesProvider\RestApia.Extensions.ValuesProvider.CollectionValuesProvider.csproj", "{47CBF40F-4929-4FA4-A119-054E04AFF40C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RestApia.Experiments.Desktop", "src\Playground\RestApia.Experiments.Desktop\RestApia.Experiments.Desktop.csproj", "{D34CE22C-21F9-4464-BE88-93D6E0DC41ED}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -40,10 +38,6 @@ Global {AE97EEF8-CE4E-0976-B0FB-8C4FC7616E01}.Debug|Any CPU.Build.0 = Debug|Any CPU {AE97EEF8-CE4E-0976-B0FB-8C4FC7616E01}.Release|Any CPU.ActiveCfg = Release|Any CPU {AE97EEF8-CE4E-0976-B0FB-8C4FC7616E01}.Release|Any CPU.Build.0 = Release|Any CPU - {35A67EB2-CF2D-40B2-B598-685D70F9D94D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {35A67EB2-CF2D-40B2-B598-685D70F9D94D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {35A67EB2-CF2D-40B2-B598-685D70F9D94D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {35A67EB2-CF2D-40B2-B598-685D70F9D94D}.Release|Any CPU.Build.0 = Release|Any CPU {00CAA372-8954-4A50-94C5-6574EBACC467}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {00CAA372-8954-4A50-94C5-6574EBACC467}.Debug|Any CPU.Build.0 = Debug|Any CPU {00CAA372-8954-4A50-94C5-6574EBACC467}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -60,14 +54,18 @@ Global {47CBF40F-4929-4FA4-A119-054E04AFF40C}.Debug|Any CPU.Build.0 = Debug|Any CPU {47CBF40F-4929-4FA4-A119-054E04AFF40C}.Release|Any CPU.ActiveCfg = Release|Any CPU {47CBF40F-4929-4FA4-A119-054E04AFF40C}.Release|Any CPU.Build.0 = Release|Any CPU + {D34CE22C-21F9-4464-BE88-93D6E0DC41ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D34CE22C-21F9-4464-BE88-93D6E0DC41ED}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D34CE22C-21F9-4464-BE88-93D6E0DC41ED}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D34CE22C-21F9-4464-BE88-93D6E0DC41ED}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {9B2C67A1-83B9-157D-5EAF-ADD07B0E8D3F} = {E2CF6B65-E284-39DA-C77D-4FDAB55B16AD} {AE97EEF8-CE4E-0976-B0FB-8C4FC7616E01} = {E2CF6B65-E284-39DA-C77D-4FDAB55B16AD} - {35A67EB2-CF2D-40B2-B598-685D70F9D94D} = {07A0437C-C5B5-4FC9-AA80-71C96A11FF54} {00CAA372-8954-4A50-94C5-6574EBACC467} = {E2CF6B65-E284-39DA-C77D-4FDAB55B16AD} {200B00D8-9D09-464F-B4A8-B7DEC68EE9E8} = {B9D6F201-FDF7-42BB-A688-C8CBC51B4718} {63845E5A-2440-4322-B06B-4AB9EA415DAE} = {E2CF6B65-E284-39DA-C77D-4FDAB55B16AD} {47CBF40F-4929-4FA4-A119-054E04AFF40C} = {E2CF6B65-E284-39DA-C77D-4FDAB55B16AD} + {D34CE22C-21F9-4464-BE88-93D6E0DC41ED} = {B9D6F201-FDF7-42BB-A688-C8CBC51B4718} EndGlobalSection EndGlobal diff --git a/src/Extensions/RestApia.Extensions.ValuesProvider.AzureKeyVault/KeyVaultValuesProvider.cs b/src/Extensions/RestApia.Extensions.ValuesProvider.AzureKeyVault/KeyVaultValuesProvider.cs index b1d1b0b..6fbe0fb 100644 --- a/src/Extensions/RestApia.Extensions.ValuesProvider.AzureKeyVault/KeyVaultValuesProvider.cs +++ b/src/Extensions/RestApia.Extensions.ValuesProvider.AzureKeyVault/KeyVaultValuesProvider.cs @@ -60,22 +60,30 @@ public async Task ReloadValuesAsync(IReadOnlyCollection(inputValues, out var settings)) return ReloadValuesResults.Failed; + var values = await GetRemoteValuesAsync(settings); + if (values == null) return ReloadValuesResults.Failed; + return new ReloadValuesResults - { Values = await GetRemoteValuesAsync(settings), Status = ValueReloadResultType.Success }; + { + Values = values, + Status = ValueReloadResultType.Success, + }; } - private async Task> GetRemoteValuesAsync(KeyVaultSettings settings) + private async Task?> GetRemoteValuesAsync(KeyVaultSettings settings) { if (!Uri.TryCreate(settings.KeyVaultUrl, UriKind.Absolute, out var keyVaultUrl)) { _dialogs.ShowError("Cannot read KeyVault secret values. KeyVault URL is not valid."); - return []; + return null; } var credentials = BuildCredentials(settings); var client = BuildClient(keyVaultUrl, credentials); var result = await ReadValuesAsync(client, settings); + if (result == null) return null; + return [ new () @@ -88,14 +96,13 @@ private async Task> GetRemoteValuesAsync(KeyVaul ]; } - private async Task> ReadValuesAsync(SecretClient client, KeyVaultSettings settings) + private async Task?> ReadValuesAsync(SecretClient client, KeyVaultSettings settings) { - var result = new List(); - try { // get list of secrets var secretProperties = client.GetPropertiesOfSecrets(); + var result = new List(); foreach (var secretProperty in secretProperties) { @@ -107,14 +114,16 @@ private async Task> ReadValuesAsync(SecretClient Type = ValueTypeEnum.Variable, }); } + + return result; } catch (Exception ex) { _logger.Fail(ex, $"Cannot read KeyVault secret values from remote. {ex.Message}"); _dialogs.ShowError("Cannot read KeyVault secret values from remote."); - } - return result; + return null; + } } private static SecretClient BuildClient(Uri url, TokenCredential credentials) diff --git a/src/Playground/RestApia.Experiments.Desktop/App.axaml b/src/Playground/RestApia.Experiments.Desktop/App.axaml new file mode 100644 index 0000000..e21ed78 --- /dev/null +++ b/src/Playground/RestApia.Experiments.Desktop/App.axaml @@ -0,0 +1,16 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Playground/RestApia.Experiments.Desktop/App.axaml.cs b/src/Playground/RestApia.Experiments.Desktop/App.axaml.cs new file mode 100644 index 0000000..5055785 --- /dev/null +++ b/src/Playground/RestApia.Experiments.Desktop/App.axaml.cs @@ -0,0 +1,33 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Markup.Xaml; +using AvaloniaWebView; +using RestApia.Experiments.Desktop.Views; + +namespace RestApia.Experiments.Desktop; + +public class App : Application +{ + public override void Initialize() + { + AvaloniaXamlLoader.Load(this); + } + + public override void OnFrameworkInitializationCompleted() + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + desktop.MainWindow = new MainWindow(); + AppWindow = desktop.MainWindow; + AvaloniaWebViewBuilder.Initialize(config => + { + // some settings could be here + }); + } + + base.OnFrameworkInitializationCompleted(); + } + + public static Window AppWindow { get; private set; } = null!; +} diff --git a/src/Playground/RestApia.Experiments.Desktop/Assets/avalonia-logo.ico b/src/Playground/RestApia.Experiments.Desktop/Assets/avalonia-logo.ico new file mode 100644 index 0000000..da8d49f Binary files /dev/null and b/src/Playground/RestApia.Experiments.Desktop/Assets/avalonia-logo.ico differ diff --git a/src/Playground/RestApia.Experiments.Desktop/Experiments/Auth/BasicAuthExperiments.cs b/src/Playground/RestApia.Experiments.Desktop/Experiments/Auth/BasicAuthExperiments.cs new file mode 100644 index 0000000..58e9e5f --- /dev/null +++ b/src/Playground/RestApia.Experiments.Desktop/Experiments/Auth/BasicAuthExperiments.cs @@ -0,0 +1,41 @@ +using Avalonia.Controls; +using CustomMessageBox.Avalonia; +using RestApia.Extensions.Auth.Basic; +using RestApia.Shared.Common.Enums; +using RestApia.Shared.Extensions.ValuesProviderService.Enums; +namespace RestApia.Experiments.Desktop.Experiments.Auth; + +public class BasicAuthExperiments +{ + public static async Task RunBasicAsync() + { + var service = new BasicAuthService(); + var result = await service.ReloadValuesAsync([ + new () { Name = nameof(BasicAuthSettings.Name), Value = "User", Type = ValueTypeEnum.Variable }, + new () { Name = nameof(BasicAuthSettings.Password), Value = "Pa$$word", Type = ValueTypeEnum.Variable }, + ], ValuesReloadMode.Interactive); + + if (!string.IsNullOrWhiteSpace(result.ErrorMessage)) + { + await MessageBox.Show(result.ErrorMessage, "Authorization Failed", MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + + if (result.Status != ValueReloadResultType.Success) + { + await MessageBox.Show($"Authorization status: {result.Status}", "Authorization", MessageBoxButtons.OK, MessageBoxIcon.Warning); + return; + } + + var message = result.Values.Select(x => $"{x.Name}: {x.Value}"); + message = [$"Expired: {result.ExpiredAt}", ..message]; + + await MessageBox.Show(new SelectableTextBlock + { + Text = string.Join("\n", message), + }, + "Authorization", + MessageBoxButtons.OK, + MessageBoxIcon.Information); + } +} diff --git a/src/Playground/RestApia.Experiments.Desktop/Experiments/Auth/OAuth2Experiments.cs b/src/Playground/RestApia.Experiments.Desktop/Experiments/Auth/OAuth2Experiments.cs new file mode 100644 index 0000000..f84f5db --- /dev/null +++ b/src/Playground/RestApia.Experiments.Desktop/Experiments/Auth/OAuth2Experiments.cs @@ -0,0 +1,61 @@ +using CustomMessageBox.Avalonia; +using RestApia.Experiments.Desktop.Modules.Dialogs; +using RestApia.Experiments.Desktop.Modules.Logger; +using RestApia.Extensions.Auth.OAuth2.AuthCode; +using RestApia.Extensions.Auth.OAuth2.Implicit; +using RestApia.Shared.Common.Enums; +using RestApia.Shared.Common.Models; +using RestApia.Shared.Common.Services; +using RestApia.Shared.Extensions.ValuesProviderService.Enums; +namespace RestApia.Experiments.Desktop.Experiments.Auth; + +public static class OAuth2Experiments +{ + public static async Task RunAuthCodeAsync() + { + var service = new OAuth2AuthCodeService(ConsoleExtensionLogger.Instance, new ExtensionDialogs()); + var settings = LocalSettings.Get(); + var result = await service.ReloadValuesAsync(VariablesConverter.Serialize(settings), ValuesReloadMode.Interactive); + + if (!string.IsNullOrWhiteSpace(result.ErrorMessage)) + { + await MessageBox.Show(result.ErrorMessage, "Authorization Failed", MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + + if (result.Status != ValueReloadResultType.Success) + { + await MessageBox.Show($"Authorization status: {result.Status}", "Authorization", MessageBoxButtons.OK, MessageBoxIcon.Warning); + return; + } + + await MessageGrid.ShowAsync(result + .Values + .Prepend(new ValueModel { Name = "Expired At", Value = result.ExpiredAt?.ToString() ?? "Never expired", Type = ValueTypeEnum.Other }) + .Select(x => new { x.Name, Value = x.Value.ToString() })); + } + + public static async Task RunImplicitAsync() + { + var service = new OAuth2ImplicitService(ConsoleExtensionLogger.Instance, new ExtensionDialogs()); + var settings = LocalSettings.Get(); + var result = await service.ReloadValuesAsync(VariablesConverter.Serialize(settings), ValuesReloadMode.Interactive); + + if (!string.IsNullOrWhiteSpace(result.ErrorMessage)) + { + await MessageBox.Show(result.ErrorMessage, "Authorization Failed", MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + + if (result.Status != ValueReloadResultType.Success) + { + await MessageBox.Show($"Authorization status: {result.Status}", "Authorization", MessageBoxButtons.OK, MessageBoxIcon.Warning); + return; + } + + await MessageGrid.ShowAsync(result + .Values + .Prepend(new ValueModel { Name = "Expired At", Value = result.ExpiredAt?.ToString() ?? "Never expired", Type = ValueTypeEnum.Other }) + .Select(x => new { x.Name, Value = x.Value.ToString() })); + } +} diff --git a/src/Playground/RestApia.Experiments.Desktop/Experiments/Secrets/AzureKeyVaultExperiments.cs b/src/Playground/RestApia.Experiments.Desktop/Experiments/Secrets/AzureKeyVaultExperiments.cs new file mode 100644 index 0000000..400db0e --- /dev/null +++ b/src/Playground/RestApia.Experiments.Desktop/Experiments/Secrets/AzureKeyVaultExperiments.cs @@ -0,0 +1,33 @@ +using CustomMessageBox.Avalonia; +using RestApia.Experiments.Desktop.Modules.Dialogs; +using RestApia.Experiments.Desktop.Modules.Logger; +using RestApia.Extensions.ValuesProvider.AzureKeyVault; +using RestApia.Shared.Common.Services; +using RestApia.Shared.Extensions.ValuesProviderService.Enums; +namespace RestApia.Experiments.Desktop.Experiments.Secrets; + +public static class AzureKeyVaultExperiments +{ + public static async Task RunAsync() + { + var settings = LocalSettings.Get(); + var service = new KeyVaultValuesProvider(ConsoleExtensionLogger.Instance, new ExtensionDialogs()); + var result = await service.ReloadValuesAsync(VariablesConverter.Serialize(settings), ValuesReloadMode.Interactive); + + if (!string.IsNullOrWhiteSpace(result.ErrorMessage)) + { + await MessageBox.Show(result.ErrorMessage, "Secrets reading Failed", MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + + if (result.Status != ValueReloadResultType.Success) + { + await MessageBox.Show($"Secrets status: {result.Status}", "Authorization", MessageBoxButtons.OK, MessageBoxIcon.Warning); + return; + } + + await MessageGrid.ShowAsync(result + .Values + .Select(x => new { x.Name, Value = x.Value.ToString() })); + } +} diff --git a/src/Playground/RestApia.Experiments.Desktop/Modules/Auth/AuthWindow.axaml b/src/Playground/RestApia.Experiments.Desktop/Modules/Auth/AuthWindow.axaml new file mode 100644 index 0000000..38eb216 --- /dev/null +++ b/src/Playground/RestApia.Experiments.Desktop/Modules/Auth/AuthWindow.axaml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/src/Playground/RestApia.Experiments.Desktop/Modules/Auth/AuthWindow.axaml.cs b/src/Playground/RestApia.Experiments.Desktop/Modules/Auth/AuthWindow.axaml.cs new file mode 100644 index 0000000..2cb78ce --- /dev/null +++ b/src/Playground/RestApia.Experiments.Desktop/Modules/Auth/AuthWindow.axaml.cs @@ -0,0 +1,72 @@ +using System.Diagnostics.CodeAnalysis; +using Avalonia.Controls; +using Avalonia.Interactivity; +using Avalonia.Threading; +using Microsoft.Web.WebView2.Core; +using RestApia.Shared.Common.Models; +using WebViewCore.Events; + +namespace RestApia.Experiments.Desktop.Modules.Auth; + +public partial class AuthWindow : Window +{ + private readonly string _startUrl; + private readonly string _stopUrl; + + [SuppressMessage("ReSharper", "UnusedMember.Global")] + public AuthWindow() + { + if (!Design.IsDesignMode) + throw new Exception("This constructor is only for design mode"); + + _stopUrl = string.Empty; + _startUrl = string.Empty; + + InitializeComponent(); + } + + public AuthWindow(string startUrl, string stopUrl) + { + _startUrl = startUrl; + _stopUrl = stopUrl; + + InitializeComponent(); + } + + protected override void OnLoaded(RoutedEventArgs e) + { + base.OnLoaded(e); + + if (Design.IsDesignMode) return; + WebViewControl.NavigationStarting += NavigateStarting; + WebViewControl.Url = new Uri(_startUrl); + } + + protected override void OnUnloaded(RoutedEventArgs e) + { + WebViewControl.NavigationStarting -= NavigateStarting; + base.OnUnloaded(e); + } + + private void NavigateStarting(object? sender, WebViewUrlLoadingEventArg e) + { + var url = e.Url?.ToString(); + if (string.IsNullOrWhiteSpace(url)) + throw new ("Browser navigation url is empty"); + + if (!url.StartsWith(_stopUrl, StringComparison.OrdinalIgnoreCase)) return; + + // stop URL detected + e.Cancel = true; + + var headers = new Dictionary(); + if (e.RawArgs is CoreWebView2NavigationStartingEventArgs args) + headers = args.RequestHeaders.ToDictionary(header => header.Key, header => header.Value); + + Dispatcher.UIThread.Post(() => Close(new BrowserDialogResult + { + Url = url, + Headers = headers, + })); + } +} diff --git a/src/Playground/RestApia.Experiments.Desktop/Modules/Common/LocalSettings.cs b/src/Playground/RestApia.Experiments.Desktop/Modules/Common/LocalSettings.cs new file mode 100644 index 0000000..e575b77 --- /dev/null +++ b/src/Playground/RestApia.Experiments.Desktop/Modules/Common/LocalSettings.cs @@ -0,0 +1,31 @@ +using Newtonsoft.Json.Linq; +namespace RestApia.Experiments.Desktop.Modules.Common; + +public static class LocalSettings +{ + private const string SettingsFileName = "settings.local.json5"; + private static readonly JObject JsonInternal = ReadSettings(); + + public static T Get() => Get(typeof(T).Name); + public static T Get(string key) + { + var json = JsonInternal[key]; + if (json == null) throw new KeyNotFoundException($"Key '{key}' not found in settings"); + + var result = json.ToObject(); + if (result == null) throw new InvalidCastException($"Cannot convert value for key '{key}' to type '{typeof(T).Name}'"); + + return result; + } + + private static JObject ReadSettings() + { + var filePath = Path.Combine(Directory.GetCurrentDirectory(), SettingsFileName); + + if (!File.Exists(filePath)) + return new JObject(); + + var json = File.ReadAllText(filePath); + return JObject.Parse(json); + } +} diff --git a/src/Playground/RestApia.Experiments.Desktop/Modules/Common/MessageGrid.cs b/src/Playground/RestApia.Experiments.Desktop/Modules/Common/MessageGrid.cs new file mode 100644 index 0000000..b3844ba --- /dev/null +++ b/src/Playground/RestApia.Experiments.Desktop/Modules/Common/MessageGrid.cs @@ -0,0 +1,32 @@ +using System.Collections; +using Avalonia.Controls; +using Avalonia.Data; +using CustomMessageBox.Avalonia; +namespace RestApia.Experiments.Desktop.Modules.Common; + +public static class MessageGrid +{ + public static Task ShowAsync(IEnumerable data) + { + var dataGrid = new DataGrid + { + AutoGenerateColumns = false, + CanUserSortColumns = false, + IsReadOnly = true, + Width = 700, + MaxHeight = 800, + + Columns = + { + new DataGridTextColumn { Header = "Name", Binding = new Binding("Name"), Width = DataGridLength.Auto }, + new DataGridTextColumn { Header = "Value", Binding = new Binding("Value") }, + }, + }; + + dataGrid.ItemsSource = data; + + return MessageBox.Show( + dataGrid, + "Authorization"); + } +} diff --git a/src/Playground/RestApia.Experiments.Desktop/Modules/Common/TaskExtensions.cs b/src/Playground/RestApia.Experiments.Desktop/Modules/Common/TaskExtensions.cs new file mode 100644 index 0000000..ae5e957 --- /dev/null +++ b/src/Playground/RestApia.Experiments.Desktop/Modules/Common/TaskExtensions.cs @@ -0,0 +1,14 @@ +using Avalonia.Threading; +using CustomMessageBox.Avalonia; +namespace RestApia.Experiments.Desktop.Modules.Common; + +public static class TaskExtensions +{ + public static Task AlertOnError(this Task task) => task + .ContinueWith(t => + { + var exception = t.Exception!.InnerException ?? t.Exception; + Dispatcher.UIThread.Post(() => MessageBox.Show(exception.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)); + }, + TaskContinuationOptions.OnlyOnFaulted); +} diff --git a/src/Playground/RestApia.Experiments.Desktop/Modules/Dialogs/ExtensionDialogs.cs b/src/Playground/RestApia.Experiments.Desktop/Modules/Dialogs/ExtensionDialogs.cs new file mode 100644 index 0000000..09aa4d2 --- /dev/null +++ b/src/Playground/RestApia.Experiments.Desktop/Modules/Dialogs/ExtensionDialogs.cs @@ -0,0 +1,16 @@ +using CustomMessageBox.Avalonia; +using RestApia.Experiments.Desktop.Modules.Auth; +using RestApia.Shared.Common.Interfaces; +using RestApia.Shared.Common.Models; +namespace RestApia.Experiments.Desktop.Modules.Dialogs; + +public class ExtensionDialogs : IExtensionDialogs +{ + public void ShowError(string message) => MessageBox.Show(message, "Error -_-", MessageBoxButtons.OK, MessageBoxIcon.Error); + + public Task OpenAuthBrowserAsync(string url, string stopUrl, string title) + { + var dialog = new AuthWindow(url, stopUrl); + return dialog.ShowDialog(App.AppWindow); + } +} diff --git a/src/Playground/RestApia.Experiments.Desktop/Modules/Logger/ConsoleExtensionLogger.cs b/src/Playground/RestApia.Experiments.Desktop/Modules/Logger/ConsoleExtensionLogger.cs new file mode 100644 index 0000000..25ed8d4 --- /dev/null +++ b/src/Playground/RestApia.Experiments.Desktop/Modules/Logger/ConsoleExtensionLogger.cs @@ -0,0 +1,13 @@ +using RestApia.Shared.Common.Interfaces; +namespace RestApia.Experiments.Desktop.Modules.Logger; + +public class ConsoleExtensionLogger : ILogger +{ + public void Debug(string message) => Console.WriteLine($"[DBG] {message}"); + public void Info(string message) => Console.WriteLine($"[INF] {message}"); + public void Warn(string message) => Console.WriteLine($"[WRN] {message}"); + public void Fail(string message) => Console.WriteLine($"[ERR] {message}"); + public void Fail(Exception? ex, string message = "") => Console.WriteLine($"[ERR] {message} {ex?.Message}"); + + public static ConsoleExtensionLogger Instance { get; } = new (); +} diff --git a/src/Playground/RestApia.Experiments.Desktop/Program.cs b/src/Playground/RestApia.Experiments.Desktop/Program.cs new file mode 100644 index 0000000..cd961a4 --- /dev/null +++ b/src/Playground/RestApia.Experiments.Desktop/Program.cs @@ -0,0 +1,24 @@ +using Avalonia; +using Avalonia.ReactiveUI; +using Avalonia.WebView.Desktop; + +namespace RestApia.Experiments.Desktop; + +internal sealed class Program +{ + // Initialization code. Don't use any Avalonia, third-party APIs or any + // SynchronizationContext-reliant code before AppMain is called: things aren't initialized + // yet and stuff might break. + [STAThread] + public static void Main(string[] args) => BuildAvaloniaApp() + .StartWithClassicDesktopLifetime(args); + + // Avalonia configuration, don't remove; also used by visual designer. + public static AppBuilder BuildAvaloniaApp() + => AppBuilder.Configure() + .UsePlatformDetect() + .WithInterFont() + .LogToTrace() + .UseReactiveUI() + .UseDesktopWebView(); +} diff --git a/src/Playground/RestApia.Experiments.Desktop/README.md b/src/Playground/RestApia.Experiments.Desktop/README.md new file mode 100644 index 0000000..4dc6ef3 --- /dev/null +++ b/src/Playground/RestApia.Experiments.Desktop/README.md @@ -0,0 +1,3 @@ +# How to use +- Before starting, make sure you have created `settings.local.json5` file in root of project (same place where this README.md file) +- You can use `settings.example.json5` as template for `settings.local.json5` \ No newline at end of file diff --git a/src/Playground/RestApia.Experiments.Desktop/RestApia.Experiments.Desktop.csproj b/src/Playground/RestApia.Experiments.Desktop/RestApia.Experiments.Desktop.csproj new file mode 100644 index 0000000..31a2cd5 --- /dev/null +++ b/src/Playground/RestApia.Experiments.Desktop/RestApia.Experiments.Desktop.csproj @@ -0,0 +1,43 @@ + + + WinExe + net8.0 + enable + true + app.manifest + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PreserveNewest + + + diff --git a/src/Playground/RestApia.Experiments.Desktop/Usings.cs b/src/Playground/RestApia.Experiments.Desktop/Usings.cs new file mode 100644 index 0000000..7242d42 --- /dev/null +++ b/src/Playground/RestApia.Experiments.Desktop/Usings.cs @@ -0,0 +1 @@ +global using RestApia.Experiments.Desktop.Modules.Common; diff --git a/src/Playground/RestApia.Experiments.Desktop/ViewLocator.cs b/src/Playground/RestApia.Experiments.Desktop/ViewLocator.cs new file mode 100644 index 0000000..b26da8d --- /dev/null +++ b/src/Playground/RestApia.Experiments.Desktop/ViewLocator.cs @@ -0,0 +1,31 @@ +using Avalonia.Controls; +using Avalonia.Controls.Templates; +using RestApia.Experiments.Desktop.ViewModels; + +namespace RestApia.Experiments.Desktop; + +public class ViewLocator : IDataTemplate +{ + public Control? Build(object? data) + { + if (data is null) + return null; + + var name = data.GetType().FullName!.Replace("ViewModel", "View", StringComparison.Ordinal); + var type = Type.GetType(name); + + if (type != null) + { + var control = (Control) Activator.CreateInstance(type)!; + control.DataContext = data; + return control; + } + + return new TextBlock { Text = "Not Found: " + name }; + } + + public bool Match(object? data) + { + return data is ViewModelBase; + } +} diff --git a/src/Playground/RestApia.Experiments.Desktop/ViewModels/ViewModelBase.cs b/src/Playground/RestApia.Experiments.Desktop/ViewModels/ViewModelBase.cs new file mode 100644 index 0000000..ef6fde3 --- /dev/null +++ b/src/Playground/RestApia.Experiments.Desktop/ViewModels/ViewModelBase.cs @@ -0,0 +1,7 @@ +using ReactiveUI; + +namespace RestApia.Experiments.Desktop.ViewModels; + +public class ViewModelBase : ReactiveObject +{ +} diff --git a/src/Playground/RestApia.Experiments.Desktop/Views/MainWindow.axaml b/src/Playground/RestApia.Experiments.Desktop/Views/MainWindow.axaml new file mode 100644 index 0000000..11338bd --- /dev/null +++ b/src/Playground/RestApia.Experiments.Desktop/Views/MainWindow.axaml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/Playground/RestApia.Experiments.Desktop/Views/MainWindow.axaml.cs b/src/Playground/RestApia.Experiments.Desktop/Views/MainWindow.axaml.cs new file mode 100644 index 0000000..32441a1 --- /dev/null +++ b/src/Playground/RestApia.Experiments.Desktop/Views/MainWindow.axaml.cs @@ -0,0 +1,19 @@ +using Avalonia.Controls; +using Avalonia.Interactivity; +using RestApia.Experiments.Desktop.Experiments.Auth; +using RestApia.Experiments.Desktop.Experiments.Secrets; + +namespace RestApia.Experiments.Desktop.Views; + +public partial class MainWindow : Window +{ + public MainWindow() + { + InitializeComponent(); + } + + private void Auth_OAuth2_AuthCode_OnClick(object? sender, RoutedEventArgs e) => OAuth2Experiments.RunAuthCodeAsync().AlertOnError(); + private void Auth_OAuth2_Implicit_OnClick(object? sender, RoutedEventArgs e) => OAuth2Experiments.RunImplicitAsync().AlertOnError(); + private void Auth_Basic_OnClick(object? sender, RoutedEventArgs e) => BasicAuthExperiments.RunBasicAsync().AlertOnError(); + private void Secrets_AzureKeyVault_OnClick(object? sender, RoutedEventArgs e) => AzureKeyVaultExperiments.RunAsync().AlertOnError(); +} diff --git a/src/Playground/RestApia.Experiments.Desktop/app.manifest b/src/Playground/RestApia.Experiments.Desktop/app.manifest new file mode 100644 index 0000000..1c8a4f2 --- /dev/null +++ b/src/Playground/RestApia.Experiments.Desktop/app.manifest @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + diff --git a/src/Playground/RestApia.Experiments.Desktop/settings.example.json5 b/src/Playground/RestApia.Experiments.Desktop/settings.example.json5 new file mode 100644 index 0000000..266e1d6 --- /dev/null +++ b/src/Playground/RestApia.Experiments.Desktop/settings.example.json5 @@ -0,0 +1,27 @@ +// this is an example of the settings.local.json5 file +// that should be created in the project directory + +{ + // OAuth2 Implicit Flow settings + "OAuth2ImplicitSettings": { + "AuthUrl": "https://example.com/oauth2/authorize", + "RedirectUrl": "https://example.com/oauth2/callback", + "ClientId": "example-client-id", + "Scopes": "openid profile email", + }, + + // OAuth2 Authorization Code Flow settings + "OAuth2AuthCodeSettings": { + "AuthUrl": "https://example.com/oauth2/authorize", + "TokenUrl": "https://example.com/oauth2/token", + "RedirectUrl": "https://example.com/oauth2/callback", + "ClientId": "example-client-id", + "SendMethod": "Body", + "Scopes": "openid profile email", + }, + + // Azure KeyVault settings + "KeyVaultSettings":{ + "KeyVaultUrl": "https://example.vault.azure.net/", + } +} \ No newline at end of file