Refactor authentication logic to use appsettings configuration. Add settings helpers, example config file, and dynamic API base URL support.
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -42,6 +42,9 @@ out/
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
|
||||
# App settings
|
||||
appsettings.json
|
||||
|
||||
# OS artifacts
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
18
Endpoints.cs
18
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";
|
||||
private static string ApiBase => SettingsProvider.Current.Api.BaseUrl.TrimEnd('/');
|
||||
|
||||
public const string UserCurrent = "https://beta.foodsharing.de/api/user/current";
|
||||
public static string UserLogin => $"{ApiBase}/api/user/login";
|
||||
|
||||
public const string StorePickups = "https://beta.foodsharing.de/api/stores/{0}/pickups";
|
||||
public static string UserCurrent => $"{ApiBase}/api/user/current";
|
||||
|
||||
public const string StoreMembers = "https://beta.foodsharing.de/api/stores/{0}/member";
|
||||
public static string StorePickups => $"{ApiBase}/api/stores/{{0}}/pickups";
|
||||
|
||||
public const string StorePickupsSlot = "https://beta.foodsharing.de/api/stores/{0}/pickups/{1}/{2}";
|
||||
public static string StoreMembers => $"{ApiBase}/api/stores/{{0}}/member";
|
||||
|
||||
public const string RegionStores = "https://beta.foodsharing.de/api/region/{0}/stores";
|
||||
public static string StorePickupsSlot => $"{ApiBase}/api/stores/{{0}}/pickups/{{1}}/{{2}}";
|
||||
|
||||
public static string RegionStores => $"{ApiBase}/api/region/{{0}}/stores";
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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
77
Helper/Settings.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
10
appsettings.example.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"Api": {
|
||||
"BaseUrl": "https://beta.foodsharing.de"
|
||||
},
|
||||
"Credentials": {
|
||||
"Email": "demo@example.com",
|
||||
"Password": "demo-password",
|
||||
"TwoFactorEnabled": false
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user