Skip to content

Commit

Permalink
Associate and Disassociate MFA factors for OOBSMS, OOBEmail and TOTP …
Browse files Browse the repository at this point in the history
…Authenticator
  • Loading branch information
jezzsantos committed Nov 16, 2024
1 parent 547b2ea commit 0bb86d1
Show file tree
Hide file tree
Showing 31 changed files with 889 additions and 464 deletions.
18 changes: 18 additions & 0 deletions src/Application.Interfaces/Audits.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions src/Application.Interfaces/Audits.resx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@
<data name="PasswordCredentialsApplication_Authenticate_Succeeded" xml:space="preserve">
<value>Authentication.Password.Passed</value>
</data>
<data name="PasswordCredentialsApplication_MfaAuthenticate_Succeeded" xml:space="preserve">
<value>Authentication.Password.Mfa.Passed</value>
</data>
<data name="PasswordCredentialsApplication_MfaAuthenticate_Failed" xml:space="preserve">
<value>Authentication.Password.Mfa.Failed.InvalidMfa</value>
</data>
<data name="EndUsersApplication_User_Registered_TermsAccepted" xml:space="preserve">
<value>EndUser.Registered.TermsAccepted</value>
</data>
Expand Down
2 changes: 1 addition & 1 deletion src/Application.Interfaces/UsageConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public static class Generic
public const string UserLogin = "User Login";
public const string UserLogout = "User Logout";
public const string UserPasswordForgotten = "User Password Forgotten";
public const string UserPasswordMfaAssociationCompleted = "User MFA Association Completed";
public const string UserPasswordMfaAuthenticated = "User 2FA Login";
public const string UserPasswordMfaAssociationStarted = "User MFA Association Started";
public const string UserPasswordMfaDisassociated = "User MFA Disassociated";
public const string UserPasswordMfaEnabled = "User MFA Enabled";
Expand Down
9 changes: 8 additions & 1 deletion src/Application.Resources.Shared/PasswordCredential.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,14 @@ public class AssociatedPasswordCredentialMfaAuthenticator

public string? OobCode { get; set; }

public required List<string> RecoveryCodes { get; set; } = new();
public List<string>? RecoveryCodes { get; set; }

public required PasswordCredentialMfaAuthenticatorType Type { get; set; }
}

public class PasswordCredentialChallenge
{
public string? OobCode { get; set; }

public PasswordCredentialMfaAuthenticatorType Type { get; set; }
}
6 changes: 2 additions & 4 deletions src/Domain.Services.Shared/ITokensService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,11 @@ public interface ITokensService

string CreateMfaAuthenticationToken();

string CreateMfaOObAuthenticatorCode();

string CreateMfaOtpAuthenticatorSecret();

string CreatePasswordResetToken();

string CreateRegistrationVerificationToken();

string GenerateRandomToken();

Optional<APIKeyToken> ParseApiKey(string apiKey);
}
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,44 @@ await _application.AssociateMfaAuthenticatorAsync(_caller.Object, null,
"anoobcode", It.IsAny<IReadOnlyList<string>>(), It.IsAny<CancellationToken>()));
}

[Fact]
public async Task WhenAssociateMfaAuthenticatorAsyncForSecondAuthenticator_ThenAssociatesWithoutRecoveryCodes()
{
_caller.Setup(cc => cc.IsAuthenticated)
.Returns(true);
_caller.Setup(cc => cc.CallId)
.Returns("acallid");
_caller.Setup(cc => cc.CallerId)
.Returns("auserid");
var credential = CreateVerifiedCredential();
credential.ChangeMfaEnabled("auserid".ToId(), true);
credential.InitiateMfaAuthentication();
_repository.Setup(s =>
s.FindCredentialsByUserIdAsync(It.IsAny<Identifier>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(credential.ToOptional());
await _application.AssociateMfaAuthenticatorAsync(_caller.Object, null,
PasswordCredentialMfaAuthenticatorType.OobSms, "[email protected]",
CancellationToken.None);

var result =
await _application.AssociateMfaAuthenticatorAsync(_caller.Object, null,
PasswordCredentialMfaAuthenticatorType.OobEmail, null,
CancellationToken.None);

result.Should().BeSuccess();
result.Value.Type.Should().Be(PasswordCredentialMfaAuthenticatorType.OobEmail);
result.Value.RecoveryCodes.Should().BeNull();
_endUsersService.Verify(eus =>
eus.GetUserPrivateAsync(_caller.Object, "auserid",
It.IsAny<CancellationToken>()));
_userProfilesService.Verify(ups =>
ups.GetProfilePrivateAsync(It.Is<ICallerContext>(cc => cc.CallId == "acallid"), "auserid",
It.IsAny<CancellationToken>()));
_userNotificationsService.Verify(ns =>
ns.NotifyPasswordMfaOobEmailAsync(_caller.Object, "[email protected]",
"anoobcode", It.IsAny<IReadOnlyList<string>>(), It.IsAny<CancellationToken>()));
}

private PasswordCredentialRoot CreateUnVerifiedCredential()
{
var credential = CreateCredential();
Expand Down
Loading

0 comments on commit 0bb86d1

Please sign in to comment.