< Summary - Heimdall API

Information
Class: ArturRios.Heimdall.Command.Services.PersonAuthTokenService
Assembly: ArturRios.Heimdall.Command
File(s): /home/runner/work/heimdall-api/heimdall-api/src/Application/ArturRios.Heimdall.Command/Services/PersonAuthTokenService.cs
Line coverage
100%
Covered lines: 21
Uncovered lines: 0
Coverable lines: 21
Total lines: 64
Line coverage: 100%
Branch coverage
100%
Covered branches: 10
Total branches: 10
Branch coverage: 100%
Method coverage
100%
Covered methods: 4
Fully covered methods: 4
Total methods: 4
Method coverage: 100%
Full method coverage: 100%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
TryBuildSubject(...)100%1010100%
IssueAsync(...)100%11100%
IssueSubjectRightsOnlyAsync(...)100%11100%

File(s)

/home/runner/work/heimdall-api/heimdall-api/src/Application/ArturRios.Heimdall.Command/Services/PersonAuthTokenService.cs

#LineLine coverage
 1using ArturRios.Heimdall.Domain.Entities;
 2using ArturRios.Heimdall.Domain.Enums;
 3
 4namespace ArturRios.Heimdall.Command.Services;
 5
 6/// <summary>
 7///     Builds the <see cref="AuthTokenSubject" /> UC-11 step 6 and UC-38 step 5 both need before
 8///     issuing the full authentication token — a <c>User</c> claims the scope they belong to, a
 9///     <c>ScopeAdmin</c> the live scopes they own, a <c>SystemAdmin</c> neither — and forwards to
 10///     <see cref="IAuthTokenIssuer" />. Shared so the scope-eligibility rules (AF-11d/AF-11e) and the
 11///     token-issuing call live in exactly one place, whichever use case is finishing the login:
 12///     <c>LoginCommandHandler</c> directly, or <c>VerifyTwoFactorAuthCommandHandler</c> after a
 13///     second factor checks out.
 14/// </summary>
 27815public class PersonAuthTokenService(IAuthTokenIssuer tokenIssuer)
 16{
 17    /// <summary>
 18    ///     AF-11d/AF-11e: a <c>User</c> whose scope is logically deleted, or a <c>ScopeAdmin</c> with
 19    ///     no live owned scope, is not eligible for a token — <paramref name="subject" /> is
 20    ///     <c>null</c> and this returns <see langword="false" /> in that case. Requires
 21    ///     <paramref name="person" />'s <c>ScopeMembership.Scope</c> and <c>ScopeOwnerships.Scope</c>
 22    ///     navigations to already be loaded.
 23    /// </summary>
 24    public bool TryBuildSubject(Person person, out AuthTokenSubject? subject)
 25    {
 12226        var liveOwnedScopeIds = person.ScopeOwnerships
 1427            .Where(ownership => !ownership.Scope.IsDeleted)
 828            .Select(ownership => ownership.Scope.PublicId)
 12229            .ToList();
 30
 12231        if (person.RoleId == (long)Roles.User && person.ScopeMembership!.Scope.IsDeleted)
 32        {
 333            subject = null;
 334            return false;
 35        }
 36
 11937        if (person.RoleId == (long)Roles.ScopeAdmin && liveOwnedScopeIds.Count == 0)
 38        {
 239            subject = null;
 240            return false;
 41        }
 42
 11743        subject = new AuthTokenSubject(
 11744            person.PublicId,
 11745            person.Name,
 11746            (int)person.RoleId,
 11747            person.RoleId == (long)Roles.User ? person.ScopeMembership!.Scope.PublicId : null,
 11748            liveOwnedScopeIds,
 11749            person.SecurityStamp);
 50
 11751        return true;
 52    }
 53
 54    /// <summary>Issues the full authentication token for a subject already built by <see cref="TryBuildSubject" />.</su
 8455    public Task<AuthToken> IssueAsync(AuthTokenSubject subject) => tokenIssuer.IssueAsync(subject);
 56
 57    /// <summary>
 58    ///     Issues the subject-rights-only token a restricted person receives instead of a full one
 59    ///     (NFR-24). No subject is built first: the scope-eligibility rules decide what a token may
 60    ///     claim, and this one claims no scope at all.
 61    /// </summary>
 62    public Task<AuthToken> IssueSubjectRightsOnlyAsync(Person person) =>
 1463        tokenIssuer.IssueSubjectRightsOnlyAsync(person.PublicId, (int)person.RoleId, person.SecurityStamp);
 64}