< Summary - Heimdall API

Information
Class: ArturRios.Heimdall.Domain.Entities.TwoFactorAuth
Assembly: ArturRios.Heimdall.Domain
File(s): /home/runner/work/heimdall-api/heimdall-api/src/Domain/ArturRios.Heimdall.Domain/Entities/TwoFactorAuth.cs
Line coverage
100%
Covered lines: 14
Uncovered lines: 0
Coverable lines: 14
Total lines: 105
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage
100%
Covered methods: 14
Fully covered methods: 14
Total methods: 14
Method coverage: 100%
Full method coverage: 100%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_PersonId()100%11100%
get_AppEnabled()100%11100%
get_EmailEnabled()100%11100%
get_TotpSecretEncrypted()100%11100%
get_IsActive()100%11100%
get_LastTotpTimeStepUsed()100%11100%
get_EmailCodeReissueCount()100%11100%
get_ChallengeId()100%11100%
get_ChallengeAttempts()100%11100%
get_CreatedAt()100%11100%
get_UpdatedAt()100%11100%
get_Person()100%11100%
get_EmailCodes()100%11100%
get_RecoveryCodes()100%11100%

File(s)

/home/runner/work/heimdall-api/heimdall-api/src/Domain/ArturRios.Heimdall.Domain/Entities/TwoFactorAuth.cs

#LineLine coverage
 1using ArturRios.Data.Relational.Core.Entities;
 2
 3namespace ArturRios.Heimdall.Domain.Entities;
 4
 5/// <summary>
 6///     A person's two-factor authentication configuration (UC-36 – UC-40, FR-2F-01…FR-2F-12). At
 7///     most one row per person. <see cref="IsActive" /> distinguishes a confirmed configuration from
 8///     one still pending confirmation (UC-36 created it, UC-37 has not yet activated it). Uses only
 9///     an internal <c>Id</c> — never addressed by ID in a path; a person's own configuration is
 10///     reached implicitly through their authenticated identity (see §4.1 of the System Requirements
 11///     Document).
 12/// </summary>
 13public class TwoFactorAuth : Entity<long>
 14{
 15    /// <summary>Foreign key to the owning <see cref="Person" /> (internal Id). Required, unique.</summary>
 46216    public long PersonId { get; set; }
 17
 18    /// <summary>Whether the authenticator-app method is configured (FR-2F-02).</summary>
 56019    public bool AppEnabled { get; set; }
 20
 21    /// <summary>Whether the email method is configured (FR-2F-03).</summary>
 50722    public bool EmailEnabled { get; set; }
 23
 24    /// <summary>
 25    ///     The TOTP secret, encrypted at rest via <c>ITotpSecretProtector</c>. Present only when
 26    ///     <see cref="AppEnabled" /> is <c>true</c>; the plaintext secret is never stored.
 27    /// </summary>
 42628    public byte[]? TotpSecretEncrypted { get; set; }
 29
 30    /// <summary>
 31    ///     Set <c>true</c> only once every method selected at setup has been confirmed (UC-37,
 32    ///     FR-2F-04). <c>false</c> with a row present means setup was initiated (UC-36) but not yet
 33    ///     confirmed.
 34    /// </summary>
 52735    public bool IsActive { get; set; }
 36
 37    /// <summary>
 38    ///     The TOTP time step (30-second counter since the Unix epoch) of the most recently accepted
 39    ///     authenticator-app code, or <c>null</c> while none has been accepted. A code is refused
 40    ///     unless its step is strictly greater, which is what makes an app code single-use: without
 41    ///     it the same six digits verify for as long as they are current, and the verification
 42    ///     window widens that to a step either side (FR-2F-14, RFC 6238 §5.2).
 43    /// </summary>
 9944    public long? LastTotpTimeStepUsed { get; set; }
 45
 46    /// <summary>
 47    ///     How many times the current challenge has had its email code reissued (UC-46, FR-2F-13).
 48    ///     Reset to zero every time UC-11 issues a challenge, so the cap counts reissues per
 49    ///     authentication attempt rather than per account — a fresh budget costs a fresh password
 50    ///     check, which is the price FR-2F-13 charged before a resend existed.
 51    /// </summary>
 52    /// <remarks>
 53    ///     Kept here rather than as a claim on the challenge token because UC-46 must never return a
 54    ///     new token: one could only be returned for a genuine challenge, so returning it would tell
 55    ///     an anonymous caller that the challenge they presented was real. A claim that cannot be
 56    ///     reissued cannot be incremented, which leaves the count on the server.
 57    /// </remarks>
 12558    public int EmailCodeReissueCount { get; set; }
 59
 60    /// <summary>
 61    ///     Identifies the one challenge UC-11 most recently issued for this configuration and UC-38 has
 62    ///     not yet redeemed, or <c>null</c> when none is outstanding. The challenge token carries the
 63    ///     same value as a claim, and is honoured only while the two agree (FR-2F-10).
 64    /// </summary>
 65    /// <remarks>
 66    ///     A signature and an expiry say a challenge token is genuine and recent; neither says it has
 67    ///     not already been spent. Without this a redeemed challenge stayed redeemable for the rest of
 68    ///     its ten minutes, so whoever held it could trade any further factor for another full token
 69    ///     without the password. Redeeming clears it, and a new login replaces it, so only the latest
 70    ///     unredeemed challenge is ever accepted — the same "only the newest is live" rule the email
 71    ///     codes issued alongside it already follow.
 72    /// </remarks>
 43873    public Guid? ChallengeId { get; set; }
 74
 75    /// <summary>
 76    ///     Guesses made at the outstanding challenge with an authenticator-app code or a recovery code.
 77    ///     Reset with <see cref="ChallengeId" /> whenever UC-11 issues a challenge; at the cap the
 78    ///     challenge is cleared, so further guessing costs a fresh password check.
 79    /// </summary>
 80    /// <remarks>
 81    ///     The email code has always had its own cap (FR-2F-13): five wrong guesses retire it. An app
 82    ///     code and a recovery code had none — only the per-address rate limit — so a challenge could
 83    ///     be guessed at for its whole ten minutes from as many addresses as an attacker could muster.
 84    ///     This gives them the same five, counted on the challenge because neither has a row of its
 85    ///     own to count on: a TOTP code is computed, and a guessed recovery code matches no row.
 86    /// </remarks>
 27587    public int ChallengeAttempts { get; set; }
 88
 89    /// <summary>Creation timestamp.</summary>
 3590    public DateTime CreatedAt { get; set; }
 91
 92    /// <summary>Last update timestamp.</summary>
 6593    public DateTime UpdatedAt { get; set; }
 94
 95    // Navigation properties
 96
 97    /// <summary>The person this configuration belongs to.</summary>
 3498    public Person Person { get; set; } = null!;
 99
 100    /// <summary>Outstanding email codes issued for this configuration (UC-36 step 4, UC-37 step 2).</summary>
 478101    public ICollection<TwoFactorEmailCode> EmailCodes { get; set; } = new List<TwoFactorEmailCode>();
 102
 103    /// <summary>Recovery codes issued for this configuration (UC-37 step 4, UC-38, UC-40).</summary>
 476104    public ICollection<TwoFactorRecoveryCode> RecoveryCodes { get; set; } = new List<TwoFactorRecoveryCode>();
 105}