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