namespace PowderCoating.Web.Middleware;
///
/// Intercepts authenticated requests and redirects any user that has the
/// "MustChangePassword" claim to the change-password page before they can
/// access any other part of the application.
///
public class MustChangePasswordMiddleware
{
private readonly RequestDelegate _next;
///
/// URL path prefixes that are reachable even before the mandatory password
/// change is completed. These are the minimum set of routes required to:
///
/// - Display and submit the change-password form itself.
/// - Allow the user to log out if they do not want to proceed.
/// - Load static assets served by the Identity UI (CSS, JS, etc.).
/// - Serve logo and profile images referenced by the layout.
/// - Allow SignalR hub negotiation and API calls that the page may
/// initiate before the redirect is completed client-side.
///
/// All other paths redirect to /Account/ChangeInitialPassword.
///
private static readonly string[] AllowedPrefixes =
[
"/Account/ChangeInitialPassword",
"/Identity/Account/Logout",
"/Identity/",
"/CompanyLogo",
"/Profile/Photo",
"/api/",
"/stripe/",
"/hubs/",
];
///
/// Initialises the middleware with the next request delegate in the pipeline.
///
/// The next middleware component.
public MustChangePasswordMiddleware(RequestDelegate next) => _next = next;
///
/// Intercepts every request. If the authenticated user carries the
/// MustChangePassword=true claim and is requesting a path not in
/// , issues a redirect to the change-password
/// page and short-circuits the rest of the pipeline.
///
/// The claim is set by the admin when creating or resetting a user account.
/// Using a claim (rather than a DB flag) means the check is evaluated on
/// every request without an additional database query, at the cost of a
/// sign-out being required to clear it — which is acceptable since the
/// password change form re-signs the user in automatically.
///
///
/// The current HTTP context.
public async Task InvokeAsync(HttpContext context)
{
if (context.User.Identity?.IsAuthenticated == true &&
context.User.FindFirst("MustChangePassword")?.Value == "true")
{
var path = context.Request.Path.Value ?? string.Empty;
var allowed = AllowedPrefixes.Any(p => path.StartsWith(p, StringComparison.OrdinalIgnoreCase));
if (!allowed)
{
context.Response.Redirect("/Account/ChangeInitialPassword");
return;
}
}
await _next(context);
}
}