@@ -370,7 +370,7 @@ public async Task<TwoFactorResult> TwoFactorRequest(
370370Replace the ` Login ` component. The following version of the ` Login ` component:
371371
372372* Accepts a user's email address (user ID) and password for an initial login attempt.
373- * If login is successful (2FA is diabled ), the component notifies the user that they're authenticated.
373+ * If login is successful (2FA is disabled ), the component notifies the user that they're authenticated.
374374* If the login attempt results in a response indicating that 2FA is required, a 2FA input element appears to receive either a 2FA TOTP code from an authenticator app or a recovery code. Depending on which code the user enters, login is attempted again by calling either ` LoginTwoFactorCodeAsync ` for a TOTP code or ` LoginTwoFactorRecoveryCodeAsync ` for a recovery code.
375375
376376` Components/Identity/Login.razor ` :
@@ -393,6 +393,12 @@ Replace the `Login` component. The following version of the `Login` component:
393393 <div class="alert alert-success">
394394 You're logged in as @context.User.Identity?.Name.
395395 </div>
396+ @if (!string.IsNullOrEmpty(recoveryCodesRemainingMessage))
397+ {
398+ <div class="alert alert-warning">
399+ @recoveryCodesRemainingMessage
400+ </div>
401+ }
396402 </Authorized>
397403 <NotAuthorized>
398404 @foreach (var error in formResult.ErrorList)
@@ -468,6 +474,7 @@ Replace the `Login` component. The following version of the `Login` component:
468474@code {
469475 private FormResult formResult = new();
470476 private bool requiresTwoFactor;
477+ private string? recoveryCodesRemainingMessage;
471478
472479 [SupplyParameterFromForm]
473480 private InputModel Input { get; set; } = new();
@@ -490,6 +497,12 @@ Replace the `Login` component. The following version of the `Login` component:
490497 {
491498 formResult = await Acct.LoginTwoFactorRecoveryCodeAsync(
492499 Input.Email, Input.Password, Input.TwoFactorCode);
500+
501+ recoveryCodesRemainingMessage =
502+ $"You have {twoFactorResult.RecoveryCodesLeft} recovery " +
503+ "codes remaining.";
504+
505+ StateHasChanged();
493506 }
494507 }
495508 }
@@ -573,34 +586,6 @@ If 2FA isn't enabled, the component loads a form with a QR code to enable 2FA wi
573586
574587If 2FA is enabled, a button appears to disable 2FA.
575588
576- <!-- NOTE TO REVIEWERS!
577-
578- I'm not discussing recovery code generation in this text yet
579- because I'm not clear on how that's supposed to work because
580- submitting such a request doesn't just re-gen the recovery
581- codes ... it also disables 2FA. That might be a bug in the
582- API, but it's more likely that I don't understand the logic
583- of recovery code re-gen. I've sent an email to discuss
584- this point.
585-
586- What we might be doing for this is simply removing the
587- recovery code re-gen (including from the cookie auth state
588- provider code method for `/manage/2fa` management).
589- Leave it so that the codes are shown once when 2FA is
590- enabled, and then don't permit re-gen from the app. If the
591- user hasn't resolved their 2FA/TOTP app login situation
592- (new phone, new TOTP app, etc.) and they've used all of
593- their recovery codes, then they're simply locked out until
594- customer service/IT reactivates their account.
595-
596- BTW ... The 'forget machine' piece also doesn't seem
597- particularly relevant. Machines are always remembered by
598- the current API AFAICT. Perhaps, the only way to require
599- TOTP every login attempt is to call for forgetting the
600- machine after every successful login ... not sure ... this
601- subject will be my follow-up question on the email convo.
602- -->
603-
604589` Components/Identity/Manage2fa.razor ` :
605590
606591``` razor
@@ -631,7 +616,6 @@ If 2FA is enabled, a button appears to disable 2FA.
631616 {
632617 <div class="alert alert-danger">@error</div>
633618 }
634-
635619 @if (twoFactorResult.IsTwoFactorEnabled)
636620 {
637621 <div class="alert alert-success" role="alert">
@@ -644,7 +628,7 @@ If 2FA is enabled, a button appears to disable 2FA.
644628 </button>
645629 </div>
646630
647- @if (recoveryCodes is null)
631+ @if (twoFactorResult.RecoveryCodes is null)
648632 {
649633 <div class="m-1">
650634 <button @onclick="GenerateNewCodes"
@@ -656,7 +640,7 @@ If 2FA is enabled, a button appears to disable 2FA.
656640 else
657641 {
658642 <ShowRecoveryCodes
659- RecoveryCodes="twoFactorResult.RecoveryCodes.ToArray() " />
643+ RecoveryCodes="twoFactorResult.RecoveryCodes" />
660644 }
661645 }
662646 else
@@ -710,10 +694,10 @@ If 2FA is enabled, a button appears to disable 2FA.
710694 <div class="row">
711695 <div class="col-xl-6">
712696 <EditForm
713- Model="Input"
714- FormName="send-code"
715- OnValidSubmit="OnValidSubmitAsync"
716- method="post">
697+ Model="Input"
698+ FormName="send-code"
699+ OnValidSubmit="OnValidSubmitAsync"
700+ method="post">
717701 <DataAnnotationsValidator />
718702 <div class="form-floating mb-3">
719703 <InputText
@@ -722,8 +706,7 @@ If 2FA is enabled, a button appears to disable 2FA.
722706 class="form-control"
723707 autocomplete="off"
724708 placeholder="Enter the code" />
725- <label for="Input.Code"
726- class="control-label form-label">
709+ <label for="Input.Code" class="control-label form-label">
727710 Verification Code
728711 </label>
729712 <ValidationMessage
@@ -781,7 +764,7 @@ If 2FA is enabled, a button appears to disable 2FA.
781764 "otpauth://totp/{0}:{1}?secret={2}&issuer={0}&digits=6",
782765 UrlEncoder.Default.Encode(Config["TotpOrganizationName"]!),
783766 email,
784- twoFactorResult? .SharedKey);
767+ twoFactorResult.SharedKey);
785768
786769 await module.InvokeVoidAsync("setQrCode", qrCodeElement, uri);
787770 }
@@ -790,20 +773,20 @@ If 2FA is enabled, a button appears to disable 2FA.
790773 private async Task Disable2FA()
791774 {
792775 twoFactorResult =
793- await Acct.TwoFactorRequest(enable: false, resetSharedKey: true);
776+ await Acct.TwoFactorRequest(resetSharedKey: true);
794777 }
795778
796779 private async Task GenerateNewCodes()
797780 {
798781 twoFactorResult =
799- await Acct.TwoFactorRequest(enable: false, resetRecoveryCodes: true);
782+ await Acct.TwoFactorRequest(resetRecoveryCodes: true);
800783 }
801784
802785 private async Task OnValidSubmitAsync()
803786 {
804787 twoFactorResult = await Acct.TwoFactorRequest(enable: true,
805788 twoFactorCode: Input.Code);
806- recoveryCodes = twoFactorResult.RecoveryCodes ;
789+ Input.Code = string.Empty ;
807790 }
808791
809792 private sealed class InputModel
@@ -832,7 +815,7 @@ Add the following [collocated JavaScript file](xref:blazor/js-interop/javascript
832815
833816``` javascript
834817export function setQrCode (qrCodeElement , uri ) {
835- if (qrCodeElement !== null ) {
818+ if (qrCodeElement !== null && ! qrCodeElement . innerHTML . trim () ) {
836819 QrCreator .render ({
837820 text: uri,
838821 radius: 0 ,
0 commit comments