using System.Security.Claims;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using SQLInsight.Hub.Data;
namespace SQLInsight.Hub.Portal;
///
/// Form posts from the portal (login and the few actions). Pages are server-rendered Razor components.
///
public static class PortalEndpoints
{
public const string LoginRateLimit = "portal-login";
public static void Map(IEndpointRouteBuilder app)
{
app.MapPost("/portal/login", LoginAsync).AllowAnonymous().RequireRateLimiting(LoginRateLimit);
app.MapPost("/portal/logout", async (HttpContext http) =>
{
await http.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
return Results.Redirect("/portal/login");
});
var portal = app.MapGroup("/portal/actions").RequireAuthorization();
portal.MapPost("/findings/{id:long}/case", LinkCaseAsync);
portal.MapPost("/findings/{id:long}/unlink", async (long id, HubDbContext db, CancellationToken ct) =>
{
var finding = await db.Findings.FindAsync([id], ct);
if (finding is null)
{
return Results.NotFound();
}
finding.SupportCaseId = null;
await db.SaveChangesAsync(ct);
return Results.Redirect($"/portal/fund/{id}");
});
portal.MapPost("/cases/{id:int}/close", async (int id, HubDbContext db, TimeProvider time, CancellationToken ct) =>
{
var supportCase = await db.SupportCases.FindAsync([id], ct);
if (supportCase is null)
{
return Results.NotFound();
}
supportCase.IsOpen = false;
supportCase.ClosedAt = time.GetUtcNow().UtcDateTime;
await db.SaveChangesAsync(ct);
return Results.Redirect("/portal/sager");
});
portal.MapPost("/licenses/{id}/revoke", async (string id, [FromForm] string reason, HubDbContext db, TimeProvider time, CancellationToken ct) =>
{
var license = await db.Licenses.FindAsync([id], ct);
if (license is null)
{
return Results.NotFound();
}
license.RevokedAt = time.GetUtcNow().UtcDateTime;
license.RevokedReason = string.IsNullOrWhiteSpace(reason) ? "Spærret" : reason.Trim();
await db.SaveChangesAsync(ct);
return Results.Redirect("/portal/licenser");
});
}
private static async Task LoginAsync(HttpContext http, StaffAccounts accounts, [FromForm] string userName, [FromForm] string password, [FromForm] string? returnUrl)
{
var user = await accounts.VerifyAsync(userName, password, http.RequestAborted);
if (user is null)
{
return Results.Redirect("/portal/login?fejl=1");
}
var identity = new ClaimsIdentity(
[new Claim(ClaimTypes.NameIdentifier, user.Id.ToString(System.Globalization.CultureInfo.InvariantCulture)), new Claim(ClaimTypes.Name, user.DisplayName), new Claim("username", user.UserName)],
CookieAuthenticationDefaults.AuthenticationScheme);
await http.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(identity));
// Only local redirects: never an open redirect to another site.
return Results.LocalRedirect(returnUrl is { Length: > 0 } && returnUrl.StartsWith("/portal", StringComparison.Ordinal) && !returnUrl.StartsWith("//", StringComparison.Ordinal) ? returnUrl : "/portal");
}
private static async Task LinkCaseAsync(long id, [FromForm] int? caseId, [FromForm] string? reference, [FromForm] string? title,
HubDbContext db, TimeProvider time, CancellationToken ct)
{
var finding = await db.Findings.Include(f => f.Instance).SingleOrDefaultAsync(f => f.Id == id, ct);
if (finding is null)
{
return Results.NotFound();
}
if (caseId is { } existing)
{
var supportCase = await db.SupportCases.SingleOrDefaultAsync(c => c.Id == existing && c.CustomerId == finding.Instance!.CustomerId, ct);
if (supportCase is null)
{
return Results.BadRequest("Sagen tilhører en anden kunde.");
}
finding.SupportCaseId = supportCase.Id;
}
else if (!string.IsNullOrWhiteSpace(reference))
{
var supportCase = new SupportCase
{
CustomerId = finding.Instance!.CustomerId,
Reference = reference.Trim(),
Title = string.IsNullOrWhiteSpace(title) ? finding.Title : title.Trim(),
CreatedAt = time.GetUtcNow().UtcDateTime,
};
db.SupportCases.Add(supportCase);
await db.SaveChangesAsync(ct);
finding.SupportCaseId = supportCase.Id;
}
await db.SaveChangesAsync(ct);
return Results.Redirect($"/portal/fund/{id}");
}
}