| | | 1 | | namespace ArturRios.Heimdall.Shared.Security; |
| | | 2 | | |
| | | 3 | | /// <summary> |
| | | 4 | | /// The lifetimes a two-factor login attempt runs on: the emailed code's (FR-2F-03) and the |
| | | 5 | | /// challenge token's (NFR-17). They are declared together, in one type, because between AF-11g |
| | | 6 | | /// issuing them and UC-38 redeeming them they are not two clocks but one deadline. |
| | | 7 | | /// </summary> |
| | | 8 | | /// <remarks> |
| | | 9 | | /// <para> |
| | | 10 | | /// They were previously three separate constants — one in <c>LoginCommandHandler</c>, one in |
| | | 11 | | /// <c>EnableTwoFactorAuthCommandHandler</c>, one in <c>JwtTwoFactorChallengeTokenIssuer</c> — |
| | | 12 | | /// and the last of them disagreed with the other two. Both artefacts are stamped in the same |
| | | 13 | | /// request, so a challenge token shorter than its code meant that for the second half of |
| | | 14 | | /// every code's life the code was genuinely correct and there was nowhere left to present |
| | | 15 | | /// it: FR-2F-10 makes second-factor verification the only endpoint that accepts a challenge |
| | | 16 | | /// token, so once the token lapsed the code lapsed with it. Nothing was wrong with either |
| | | 17 | | /// artefact; the login simply stopped being completable halfway through the window the |
| | | 18 | | /// specification promised. |
| | | 19 | | /// </para> |
| | | 20 | | /// <para> |
| | | 21 | | /// Defining <see cref="ChallengeToken" /> <em>as</em> <see cref="EmailCode" /> rather than |
| | | 22 | | /// repeating the number is what keeps that from reappearing. A later change to one moves |
| | | 23 | | /// both, which is the only relationship between them that is ever correct. |
| | | 24 | | /// </para> |
| | | 25 | | /// <para> |
| | | 26 | | /// <b>Why ten minutes and not five.</b> Ten is the tolerance FR-2F-03 chose for the one part |
| | | 27 | | /// of this flow outside anyone's control — how long an email takes to arrive. The App method |
| | | 28 | | /// does not need it: an authenticator generates a code on demand, so its holder never races |
| | | 29 | | /// delivery and would be served just as well by a much shorter window. The Email method |
| | | 30 | | /// cannot do without it, and the two methods share one challenge token, so the window has to |
| | | 31 | | /// be the longer of what they need. The cost is that a stolen challenge token is useful for |
| | | 32 | | /// ten minutes rather than five — which is a smaller concession than it reads as, since the |
| | | 33 | | /// token authorises nothing by itself: it is refused at every endpoint but second-factor |
| | | 34 | | /// verification (FR-2F-10), and there it still buys nothing without a second factor. |
| | | 35 | | /// </para> |
| | | 36 | | /// <para> |
| | | 37 | | /// Both remain fixed constants rather than configuration, for the reason NFR-17 gives: a |
| | | 38 | | /// full login token's lifetime is meant to be tuned per deployment, but these two are |
| | | 39 | | /// security properties of the use case itself, and an operator who could move them could |
| | | 40 | | /// reintroduce the disagreement this type exists to prevent. |
| | | 41 | | /// </para> |
| | | 42 | | /// </remarks> |
| | | 43 | | public static class TwoFactorLifetimes |
| | | 44 | | { |
| | | 45 | | /// <summary> |
| | | 46 | | /// How long an emailed 6-digit code stays redeemable after issue (FR-2F-03). |
| | | 47 | | /// </summary> |
| | 4 | 48 | | public static readonly TimeSpan EmailCode = TimeSpan.FromMinutes(10); |
| | | 49 | | |
| | | 50 | | /// <summary> |
| | | 51 | | /// How long the challenge token issued alongside that code stays usable (NFR-17). Equal to |
| | | 52 | | /// <see cref="EmailCode" /> by definition — see the remarks. |
| | | 53 | | /// </summary> |
| | 4 | 54 | | public static readonly TimeSpan ChallengeToken = EmailCode; |
| | | 55 | | } |