From 9f82cf491c4f150186d7f5151ac4a21342c43a1c Mon Sep 17 00:00:00 2001 From: Andre Beging Date: Thu, 11 Dec 2025 18:44:00 +0100 Subject: [PATCH] Refactor authentication logic to use appsettings configuration. Add settings helpers, example config file, and dynamic API base URL support. --- .gitignore | 3 ++ Endpoints.cs | 28 ++++++++------- FsTool.csproj | 12 +++++++ Helper/AuthHelper.cs | 1 + Helper/Settings.cs | 77 ++++++++++++++++++++++++++++++++++++++++ Tasks/UserTasks.cs | 48 +++++++++++++++++-------- appsettings.example.json | 10 ++++++ 7 files changed, 152 insertions(+), 27 deletions(-) create mode 100644 Helper/Settings.cs create mode 100644 appsettings.example.json diff --git a/.gitignore b/.gitignore index 240595a..0a3a5b7 100644 --- a/.gitignore +++ b/.gitignore @@ -42,6 +42,9 @@ out/ !.vscode/launch.json !.vscode/extensions.json +# App settings +appsettings.json + # OS artifacts .DS_Store Thumbs.db diff --git a/Endpoints.cs b/Endpoints.cs index b52921d..72f5510 100644 --- a/Endpoints.cs +++ b/Endpoints.cs @@ -1,17 +1,21 @@ -namespace FsTool +using FsTool.Helpers; + +namespace FsTool { public static class Endpoints { - public const string UserLogin = "https://beta.foodsharing.de/api/user/login"; - - public const string UserCurrent = "https://beta.foodsharing.de/api/user/current"; - - public const string StorePickups = "https://beta.foodsharing.de/api/stores/{0}/pickups"; - - public const string StoreMembers = "https://beta.foodsharing.de/api/stores/{0}/member"; - - public const string StorePickupsSlot = "https://beta.foodsharing.de/api/stores/{0}/pickups/{1}/{2}"; - - public const string RegionStores = "https://beta.foodsharing.de/api/region/{0}/stores"; + private static string ApiBase => SettingsProvider.Current.Api.BaseUrl.TrimEnd('/'); + + public static string UserLogin => $"{ApiBase}/api/user/login"; + + public static string UserCurrent => $"{ApiBase}/api/user/current"; + + public static string StorePickups => $"{ApiBase}/api/stores/{{0}}/pickups"; + + public static string StoreMembers => $"{ApiBase}/api/stores/{{0}}/member"; + + public static string StorePickupsSlot => $"{ApiBase}/api/stores/{{0}}/pickups/{{1}}/{{2}}"; + + public static string RegionStores => $"{ApiBase}/api/region/{{0}}/stores"; } } \ No newline at end of file diff --git a/FsTool.csproj b/FsTool.csproj index ed9781c..14590a3 100644 --- a/FsTool.csproj +++ b/FsTool.csproj @@ -7,4 +7,16 @@ enable + + + + + + + + + PreserveNewest + + + diff --git a/Helper/AuthHelper.cs b/Helper/AuthHelper.cs index 296ff86..14bcfa9 100644 --- a/Helper/AuthHelper.cs +++ b/Helper/AuthHelper.cs @@ -63,6 +63,7 @@ public static class AuthHelper // Try to load CSRF token from file var csrfToken = await LoadCsrfTokenAsync(); + csrfToken = null; // If no valid token found, call login endpoint to get a new one if (string.IsNullOrWhiteSpace(csrfToken)) diff --git a/Helper/Settings.cs b/Helper/Settings.cs new file mode 100644 index 0000000..803b693 --- /dev/null +++ b/Helper/Settings.cs @@ -0,0 +1,77 @@ +using Microsoft.Extensions.Configuration; + +namespace FsTool.Helpers +{ + /// + /// Represents the root configuration settings for the application, including API and credentials. + /// + public class AppSettings + { + /// + /// Gets the API-related settings. + /// + public ApiSettings Api { get; init; } = new(); + + /// + /// Gets the credentials settings for authentication. + /// + public CredentialsSettings Credentials { get; init; } = new(); + } + + /// + /// Contains settings related to the API endpoints. + /// + public class ApiSettings + { + /// + /// Gets the base URL for the API, without the /api path segment. + /// + public string BaseUrl { get; init; } = "https://beta.foodsharing.de"; + } + + /// + /// Contains credentials and authentication settings. + /// + public class CredentialsSettings + { + /// + /// Gets the email address used for login. + /// + public string Email { get; init; } = string.Empty; + + /// + /// Gets the password used for login. + /// + public string Password { get; init; } = string.Empty; + + /// + /// Gets a value indicating whether two-factor authentication is enabled. + /// + public bool TwoFactorEnabled { get; init; } + } + + /// + /// Provides access to the current application settings loaded from configuration. + /// + public static class SettingsProvider + { + private static AppSettings? _current; + + /// + /// Gets the current application settings instance. + /// + public static AppSettings Current => _current ??= Load(); + + private static AppSettings Load() + { + var configuration = new ConfigurationBuilder() + .SetBasePath(AppContext.BaseDirectory) + .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) + .Build(); + + var settings = new AppSettings(); + configuration.Bind(settings); + return settings; + } + } +} \ No newline at end of file diff --git a/Tasks/UserTasks.cs b/Tasks/UserTasks.cs index 4a0edae..51dff4d 100644 --- a/Tasks/UserTasks.cs +++ b/Tasks/UserTasks.cs @@ -9,32 +9,50 @@ namespace FsTool.Tasks public static class UserTasks { /// - /// Performs a login request using two-factor authentication and returns the CSRF token from the response. + /// Performs a login request using configured credentials and optional two-factor authentication. /// /// The HTTP client used to send the request. /// The CSRF token when login succeeds; otherwise, null. public static async Task CallLoginEndpointAsync(HttpClient httpClient) { - string? csrfToken = null; + var credentials = SettingsProvider.Current.Credentials; - // prompt for 2FA code - Console.Write("Enter 2FA code: "); - var authCode = Console.ReadLine()?.Trim(); - - // validate input - if (string.IsNullOrWhiteSpace(authCode)) + if (string.IsNullOrWhiteSpace(credentials.Email) || string.IsNullOrWhiteSpace(credentials.Password)) { - Console.WriteLine("A valid 2FA code is required."); + Console.WriteLine("Email and password must be configured in appsettings.json."); return null; } - var payload = new + string? authCode = null; + if (credentials.TwoFactorEnabled) { - email = "foodsharing@beging.de", - password = "z+hc@Ox9Zu4~MXzkB:Z@O.-S1AvsT&mc!oyQA?NK1jckl1Dzi2^-)+.H.AJKBLoi", - code = authCode, - remember_me = true - }; + Console.Write("Enter 2FA code: "); + authCode = Console.ReadLine()?.Trim(); + + if (string.IsNullOrWhiteSpace(authCode)) + { + Console.WriteLine("A valid 2FA code is required when two-factor authentication is enabled."); + return null; + } + } + + string? csrfToken = null; + + object payload = credentials.TwoFactorEnabled + ? new + { + email = credentials.Email, + password = credentials.Password, + code = authCode, + remember_me = true + } + : new + { + email = credentials.Email, + password = credentials.Password, + remember_me = true + }; + var response = await httpClient.PostAsJsonAsync(Endpoints.UserLogin, payload); // handle unsuccessful response diff --git a/appsettings.example.json b/appsettings.example.json new file mode 100644 index 0000000..5977370 --- /dev/null +++ b/appsettings.example.json @@ -0,0 +1,10 @@ +{ + "Api": { + "BaseUrl": "https://beta.foodsharing.de" + }, + "Credentials": { + "Email": "demo@example.com", + "Password": "demo-password", + "TwoFactorEnabled": false + } +} \ No newline at end of file