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/launch.json
|
||||||
!.vscode/extensions.json
|
!.vscode/extensions.json
|
||||||
|
|
||||||
|
# App settings
|
||||||
|
appsettings.json
|
||||||
|
|
||||||
# OS artifacts
|
# OS artifacts
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|||||||
28
Endpoints.cs
28
Endpoints.cs
@@ -1,17 +1,21 @@
|
|||||||
namespace FsTool
|
using FsTool.Helpers;
|
||||||
|
|
||||||
|
namespace FsTool
|
||||||
{
|
{
|
||||||
public static class Endpoints
|
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>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</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>
|
</Project>
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ public static class AuthHelper
|
|||||||
|
|
||||||
// Try to load CSRF token from file
|
// Try to load CSRF token from file
|
||||||
var csrfToken = await LoadCsrfTokenAsync();
|
var csrfToken = await LoadCsrfTokenAsync();
|
||||||
|
csrfToken = null;
|
||||||
|
|
||||||
// If no valid token found, call login endpoint to get a new one
|
// If no valid token found, call login endpoint to get a new one
|
||||||
if (string.IsNullOrWhiteSpace(csrfToken))
|
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
|
public static class UserTasks
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
/// <param name="httpClient">The HTTP client used to send the request.</param>
|
/// <param name="httpClient">The HTTP client used to send the request.</param>
|
||||||
/// <returns>The CSRF token when login succeeds; otherwise, <c>null</c>.</returns>
|
/// <returns>The CSRF token when login succeeds; otherwise, <c>null</c>.</returns>
|
||||||
public static async Task<string?> CallLoginEndpointAsync(HttpClient httpClient)
|
public static async Task<string?> CallLoginEndpointAsync(HttpClient httpClient)
|
||||||
{
|
{
|
||||||
string? csrfToken = null;
|
var credentials = SettingsProvider.Current.Credentials;
|
||||||
|
|
||||||
// prompt for 2FA code
|
if (string.IsNullOrWhiteSpace(credentials.Email) || string.IsNullOrWhiteSpace(credentials.Password))
|
||||||
Console.Write("Enter 2FA code: ");
|
|
||||||
var authCode = Console.ReadLine()?.Trim();
|
|
||||||
|
|
||||||
// validate input
|
|
||||||
if (string.IsNullOrWhiteSpace(authCode))
|
|
||||||
{
|
{
|
||||||
Console.WriteLine("A valid 2FA code is required.");
|
Console.WriteLine("Email and password must be configured in appsettings.json.");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var payload = new
|
string? authCode = null;
|
||||||
|
if (credentials.TwoFactorEnabled)
|
||||||
{
|
{
|
||||||
email = "foodsharing@beging.de",
|
Console.Write("Enter 2FA code: ");
|
||||||
password = "z+hc@Ox9Zu4~MXzkB:Z@O.-S1AvsT&mc!oyQA?NK1jckl1Dzi2^-)+.H.AJKBLoi",
|
authCode = Console.ReadLine()?.Trim();
|
||||||
code = authCode,
|
|
||||||
remember_me = true
|
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);
|
var response = await httpClient.PostAsJsonAsync(Endpoints.UserLogin, payload);
|
||||||
|
|
||||||
// handle unsuccessful response
|
// 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