< Summary - Heimdall API

Information
Class: ArturRios.Heimdall.Query.Handlers.CheckApplicationTokenQueryHandler
Assembly: ArturRios.Heimdall.Query
File(s): /home/runner/work/heimdall-api/heimdall-api/src/Application/ArturRios.Heimdall.Query/Handlers/CheckApplicationTokenQueryHandler.cs
Line coverage
100%
Covered lines: 28
Uncovered lines: 0
Coverable lines: 28
Total lines: 63
Line coverage: 100%
Branch coverage
100%
Covered branches: 4
Total branches: 4
Branch coverage: 100%
Method coverage
100%
Covered methods: 2
Fully covered methods: 2
Total methods: 2
Method coverage: 100%
Full method coverage: 100%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
HandleAsync()100%44100%

File(s)

/home/runner/work/heimdall-api/heimdall-api/src/Application/ArturRios.Heimdall.Query/Handlers/CheckApplicationTokenQueryHandler.cs

#LineLine coverage
 1using ArturRios.Data.Relational.Core.Interfaces;
 2using ArturRios.Heimdall.Domain.Entities;
 3using ArturRios.Heimdall.Query.Input;
 4using ArturRios.Heimdall.Query.Output;
 5using ArturRios.Heimdall.Shared.Messages;
 6using ArturRios.Heimdall.Shared.Security;
 7using ArturRios.Mediator.Query.Interfaces;
 8using ArturRios.Output;
 9using Microsoft.EntityFrameworkCore;
 10
 11namespace ArturRios.Heimdall.Query.Handlers;
 12
 13/// <summary>
 14///     Handles <see cref="CheckApplicationTokenQuery" /> (UC-57, FR-AP-15/16): validates the token
 15///     offline, then confirms against the database that its application and scope are live and
 16///     agree with it, and that the credential it was issued from is still live.
 17/// </summary>
 18/// <remarks>
 19///     This is the documented revocation policy: revoking a credential (UC-55), letting it expire, or
 20///     deleting the application or its scope takes effect here at once, for tokens already issued as
 21///     much as for new ones. A consumer re-checks before every sensitive release. Every refusal is
 22///     <see cref="ApplicationTokenMessages.TokenNotAccepted" />, with no detail.
 23/// </remarks>
 1924public class CheckApplicationTokenQueryHandler(
 1925    IApplicationTokenValidator validator,
 1926    IAsyncReadOnlyRepository<ApplicationCredential, long> credentialReader)
 27    : IQueryHandlerAsync<CheckApplicationTokenQuery, ApplicationTokenCheckOutput>
 28{
 29    public async Task<DataOutput<ApplicationTokenCheckOutput?>> HandleAsync(
 30        CheckApplicationTokenQuery query, CancellationToken cancellationToken = default)
 31    {
 1932        var output = DataOutput<ApplicationTokenCheckOutput?>.New;
 33
 1934        var principal = await validator.ValidateAsync(query.Token);
 35
 1936        if (principal is null)
 37        {
 538            return output.WithError(ApplicationTokenMessages.TokenNotAccepted);
 39        }
 40
 1441        var now = DateTime.UtcNow;
 1442        var check = await credentialReader.Query()
 1443            .Where(ApplicationCredential.LiveAt(now))
 1444            .Where(x => x.PublicId == principal.CredentialId &&
 1445                        x.Application.PublicId == principal.ApplicationId &&
 1446                        !x.Application.IsDeleted &&
 1447                        x.Application.Scope.PublicId == principal.ScopeId &&
 1448                        !x.Application.Scope.IsDeleted)
 1449            .Select(x => new ApplicationTokenCheckOutput
 1450            {
 1451                ApplicationId = x.Application.PublicId,
 1452                Name = x.Application.Name,
 1453                ScopeId = x.Application.Scope.PublicId,
 1454                CredentialId = x.PublicId,
 1455                ExpiresAt = principal.ExpiresAt
 1456            })
 1457            .FirstOrDefaultAsync(cancellationToken);
 58
 1459        return check is null
 1460            ? output.WithError(ApplicationTokenMessages.TokenNotAccepted)
 1461            : output.WithData(check).WithMessage(ApplicationTokenMessages.TokenAccepted);
 1962    }
 63}