Add admin password reset API
This commit is contained in:
@@ -74,6 +74,9 @@ builder.Services.AddHttpClient<ApiClient>();
|
||||
builder.Services.AddScoped<ApiClient>();
|
||||
|
||||
var app = builder.Build();
|
||||
var adminSettings = app.Configuration.GetSection("AdminSettings");
|
||||
var adminUsername = adminSettings["Username"] ?? "admin";
|
||||
var adminPassword = adminSettings["Password"] ?? string.Empty;
|
||||
|
||||
// Initialize database tables (PostgreSQL-backed repositories)
|
||||
using (var scope = app.Services.CreateScope())
|
||||
@@ -125,20 +128,36 @@ app.MapGet("/", () => Results.Redirect("/login"));
|
||||
app.MapCollectionEndpoints();
|
||||
|
||||
// Login API (API-First for Blazor WASM client authentication)
|
||||
app.MapPost("/api/auth/login", async (LoginRequest request, IWorkspaceRepository workspaceRepo) =>
|
||||
app.MapPost("/api/auth/login", async (JsonElement payload, IWorkspaceRepository workspaceRepo) =>
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(request.Username) || string.IsNullOrWhiteSpace(request.Password))
|
||||
static string? ReadString(JsonElement root, params string[] names)
|
||||
{
|
||||
foreach (var name in names)
|
||||
{
|
||||
if (root.ValueKind == JsonValueKind.Object && root.TryGetProperty(name, out var property) && property.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
return property.GetString();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
var username = ReadString(payload, "Username", "username");
|
||||
var password = ReadString(payload, "Password", "password");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
|
||||
{
|
||||
return Results.BadRequest(new { success = false, error = "missing_credentials" });
|
||||
}
|
||||
|
||||
var account = await workspaceRepo.GetAccountByUsernameAsync(request.Username.Trim());
|
||||
var account = await workspaceRepo.GetAccountByUsernameAsync(username.Trim());
|
||||
if (account is null || !string.Equals(account.IsActive, "true", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return Results.Json(new { success = false, error = "invalid_credentials" }, statusCode: 401);
|
||||
}
|
||||
|
||||
var passwordHash = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(request.Password)));
|
||||
var passwordHash = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(password)));
|
||||
if (!string.Equals(account.PasswordHash, passwordHash, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return Results.Json(new { success = false, error = "invalid_credentials" }, statusCode: 401);
|
||||
@@ -212,6 +231,59 @@ app.MapPost("/api/auth/logout", async (HttpContext context, IWorkspaceRepository
|
||||
return Results.Ok(new { success = true });
|
||||
}).DisableAntiforgery();
|
||||
|
||||
app.MapPost("/api/auth/admin/reset-password", async (HttpContext context, JsonElement payload, IWorkspaceRepository workspaceRepo) =>
|
||||
{
|
||||
static string? ReadString(JsonElement root, params string[] names)
|
||||
{
|
||||
foreach (var name in names)
|
||||
{
|
||||
if (root.ValueKind == JsonValueKind.Object && root.TryGetProperty(name, out var property) && property.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
return property.GetString();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
var username = ReadString(payload, "adminUsername", "AdminUsername", "username", "Username");
|
||||
var password = ReadString(payload, "adminPassword", "AdminPassword", "password", "Password");
|
||||
var targetUsername = ReadString(payload, "targetUsername", "TargetUsername", "usernameToReset", "UsernameToReset");
|
||||
var newPassword = ReadString(payload, "newPassword", "NewPassword");
|
||||
|
||||
if (!string.Equals(username, adminUsername, StringComparison.Ordinal) || !string.Equals(password, adminPassword, StringComparison.Ordinal))
|
||||
{
|
||||
return Results.Unauthorized();
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(targetUsername) || string.IsNullOrWhiteSpace(newPassword))
|
||||
{
|
||||
return Results.BadRequest(new { success = false, error = "missing_target_or_password" });
|
||||
}
|
||||
|
||||
var account = await workspaceRepo.GetAccountByUsernameAsync(targetUsername.Trim());
|
||||
if (account is null)
|
||||
{
|
||||
return Results.NotFound(new { success = false, error = "account_not_found" });
|
||||
}
|
||||
|
||||
var passwordHash = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(newPassword)));
|
||||
account.PasswordHash = passwordHash;
|
||||
account.UpdatedAt = DateTimeOffset.UtcNow.ToString("O");
|
||||
var updated = await workspaceRepo.UpsertAccountAsync(account);
|
||||
if (!updated)
|
||||
{
|
||||
return Results.StatusCode(500);
|
||||
}
|
||||
|
||||
return Results.Ok(new
|
||||
{
|
||||
success = true,
|
||||
username = account.Username,
|
||||
updatedAt = account.UpdatedAt
|
||||
});
|
||||
}).DisableAntiforgery();
|
||||
|
||||
// Operational Report serving API (WASM safe file loading substitute)
|
||||
app.MapGet("/api/operational-report", async (IWebHostEnvironment env) =>
|
||||
{
|
||||
@@ -274,12 +346,6 @@ app.MapRazorComponents<App>()
|
||||
|
||||
app.Run();
|
||||
|
||||
public class LoginRequest
|
||||
{
|
||||
public string Username { get; set; } = "";
|
||||
public string Password { get; set; } = "";
|
||||
}
|
||||
|
||||
internal sealed class QuantAdminAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
|
||||
{
|
||||
public QuantAdminAuthHandler(
|
||||
|
||||
Reference in New Issue
Block a user