< Summary - Heimdall API

Information
Class: ArturRios.Heimdall.WebApi.Security.MfaPendingGuardFilter
Assembly: ArturRios.Heimdall.WebApi
File(s): /home/runner/work/heimdall-api/heimdall-api/src/Presentation/ArturRios.Heimdall.WebApi/Security/MfaPendingGuardFilter.cs
Line coverage
100%
Covered lines: 7
Uncovered lines: 0
Coverable lines: 7
Total lines: 49
Line coverage: 100%
Branch coverage
100%
Covered branches: 4
Total branches: 4
Branch coverage: 100%
Method coverage
100%
Covered methods: 1
Fully covered methods: 1
Total methods: 1
Method coverage: 100%
Full method coverage: 100%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
OnAuthorization(...)100%44100%

File(s)

/home/runner/work/heimdall-api/heimdall-api/src/Presentation/ArturRios.Heimdall.WebApi/Security/MfaPendingGuardFilter.cs

#LineLine coverage
 1using ArturRios.Output;
 2using ArturRios.Util.Http;
 3using ArturRios.Util.WebApi.Security.Extensions;
 4using Microsoft.AspNetCore.Mvc;
 5using Microsoft.AspNetCore.Mvc.Filters;
 6
 7namespace ArturRios.Heimdall.WebApi.Security;
 8
 9/// <summary>
 10///     Global authorization filter enforcing FR-2F-10/NFR-17: a request authenticated with a UC-38
 11///     challenge token — <see cref="IdentityUser.MfaPending" /> set — is rejected with
 12///     <c>401 Unauthorized</c>, with no exceptions.
 13/// </summary>
 14/// <remarks>
 15///     <para>
 16///         Registered globally (<c>Startup.ConfigureWebApi</c>), so it runs on every controller
 17///         action, including <c>POST /api/auth/2fa/verify</c> itself — that endpoint never actually
 18///         trips it, though, because it is <c>[AllowAnonymous]</c> and reads the challenge token as a
 19///         request body field (<c>VerifyTwoFactorAuthCommand.ChallengeToken</c>), never as a bearer
 20///         credential, so <c>AuthenticationMiddleware</c> never attaches an <see cref="IdentityUser" />
 21///         with <c>MfaPending</c> set for that call in the first place. This filter only ever fires
 22///         when a challenge token is misused as an <c>Authorization: Bearer</c> header against some
 23///         other endpoint — exactly the case FR-2F-10 wants blocked.
 24///     </para>
 25///     <para>
 26///         A request that carries no bearer token at all — every anonymous endpoint, and every
 27///         unauthenticated call to a protected one — resolves <see cref="HttpContextExtensions.GetUser{TUser}" />
 28///         to <c>null</c> here, which does not match the <c>MfaPending: true</c> pattern below, so
 29///         the filter is a no-op for it. It neither blocks nor requires authentication itself; it
 30///         only ever narrows an identity the pipeline already attached.
 31///     </para>
 32/// </remarks>
 33public class MfaPendingGuardFilter : IAuthorizationFilter
 34{
 35    public void OnAuthorization(AuthorizationFilterContext context)
 36    {
 141137        var user = context.HttpContext.GetUser<IdentityUser>();
 38
 141139        if (user is not { MfaPending: true })
 40        {
 137741            return;
 42        }
 43
 3444        var output = ProcessOutput.New.WithError(
 3445            "A two-factor challenge is still pending completion; this token is not valid here.");
 46
 3447        context.Result = new ObjectResult(output) { StatusCode = HttpStatusCodes.Unauthorized };
 3448    }
 49}