Refactor authentication logic to use appsettings configuration. Add settings helpers, example config file, and dynamic API base URL support.

This commit is contained in:
Andre Beging
2025-12-11 18:44:00 +01:00
parent 67260ae450
commit 9f82cf491c
7 changed files with 152 additions and 27 deletions

3
.gitignore vendored
View File

@@ -42,6 +42,9 @@ out/
!.vscode/launch.json
!.vscode/extensions.json
# App settings
appsettings.json
# OS artifacts
.DS_Store
Thumbs.db

View File

@@ -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";
}
}

View File

@@ -7,4 +7,16 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
</ItemGroup>
<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@@ -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))

77
Helper/Settings.cs Normal file
View File

@@ -0,0 +1,77 @@
using Microsoft.Extensions.Configuration;
namespace FsTool.Helpers
{
/// <summary>
/// Represents the root configuration settings for the application, including API and credentials.
/// </summary>
public class AppSettings
{
/// <summary>
/// Gets the API-related settings.
/// </summary>
public ApiSettings Api { get; init; } = new();
/// <summary>
/// Gets the credentials settings for authentication.
/// </summary>
public CredentialsSettings Credentials { get; init; } = new();
}
/// <summary>
/// Contains settings related to the API endpoints.
/// </summary>
public class ApiSettings
{
/// <summary>
/// Gets the base URL for the API, without the /api path segment.
/// </summary>
public string BaseUrl { get; init; } = "https://beta.foodsharing.de";
}
/// <summary>
/// Contains credentials and authentication settings.
/// </summary>
public class CredentialsSettings
{
/// <summary>
/// Gets the email address used for login.
/// </summary>
public string Email { get; init; } = string.Empty;
/// <summary>
/// Gets the password used for login.
/// </summary>
public string Password { get; init; } = string.Empty;
/// <summary>
/// Gets a value indicating whether two-factor authentication is enabled.
/// </summary>
public bool TwoFactorEnabled { get; init; }
}
/// <summary>
/// Provides access to the current application settings loaded from configuration.
/// </summary>
public static class SettingsProvider
{
private static AppSettings? _current;
/// <summary>
/// Gets the current application settings instance.
/// </summary>
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;
}
}
}

View File

@@ -9,32 +9,50 @@ namespace FsTool.Tasks
public static class UserTasks
{
/// <summary>
/// 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.
/// </summary>
/// <param name="httpClient">The HTTP client used to send the request.</param>
/// <returns>The CSRF token when login succeeds; otherwise, <c>null</c>.</returns>
public static async Task<string?> 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

10
appsettings.example.json Normal file
View File

@@ -0,0 +1,10 @@
{
"Api": {
"BaseUrl": "https://beta.foodsharing.de"
},
"Credentials": {
"Email": "demo@example.com",
"Password": "demo-password",
"TwoFactorEnabled": false
}
}