diff --git a/src/Microsoft.AspNet.Identity/IdentityOptions.cs b/src/Microsoft.AspNet.Identity/IdentityOptions.cs
index 0705a4dbd5..26e4092f38 100644
--- a/src/Microsoft.AspNet.Identity/IdentityOptions.cs
+++ b/src/Microsoft.AspNet.Identity/IdentityOptions.cs
@@ -29,6 +29,8 @@ namespace Microsoft.AspNet.Identity
public string PasswordResetTokenProvider { get; set; } = Resources.DefaultTokenProvider;
+ public string ChangeEmailTokenProvider { get; set; } = Resources.DefaultTokenProvider;
+
public static string ApplicationCookieAuthenticationType { get; set; } = typeof(IdentityOptions).Namespace + ".Application";
public static string ExternalCookieAuthenticationType { get; set; } = typeof(IdentityOptions).Namespace + ".External";
public static string TwoFactorUserIdCookieAuthenticationType { get; set; } = typeof(IdentityOptions).Namespace + ".TwoFactorUserId";
diff --git a/src/Microsoft.AspNet.Identity/UserManager.cs b/src/Microsoft.AspNet.Identity/UserManager.cs
index a42fa4c49f..3b18d534fd 100644
--- a/src/Microsoft.AspNet.Identity/UserManager.cs
+++ b/src/Microsoft.AspNet.Identity/UserManager.cs
@@ -716,7 +716,7 @@ namespace Microsoft.AspNet.Identity
}
///
- /// GenerateAsync a new security stamp for a user, used for SignOutEverywhere functionality
+ /// Generate a new security stamp for a user, used for SignOutEverywhere functionality
///
///
///
@@ -1337,6 +1337,52 @@ namespace Microsoft.AspNet.Identity
return await store.GetEmailConfirmedAsync(user, cancellationToken);
}
+ private static string GetChangeEmailPurpose(string newEmail)
+ {
+ return "ChangeEmail:" + newEmail;
+ }
+
+ ///
+ /// Generate a change email token for the user using the UserTokenProvider
+ ///
+ ///
+ ///
+ ///
+ public virtual async Task GenerateChangeEmailTokenAsync(TUser user, string newEmail,
+ CancellationToken cancellationToken = default(CancellationToken))
+ {
+ ThrowIfDisposed();
+ return await GenerateUserTokenAsync(user, Options.ChangeEmailTokenProvider, GetChangeEmailPurpose(newEmail), cancellationToken);
+ }
+
+ ///
+ /// Change a user's email using a change email token
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public virtual async Task ChangeEmailAsync(TUser user, string newEmail, string token,
+ CancellationToken cancellationToken = default(CancellationToken))
+ {
+ ThrowIfDisposed();
+ if (user == null)
+ {
+ throw new ArgumentNullException("user");
+ }
+ // Make sure the token is valid and the stamp matches
+ if (!await VerifyUserTokenAsync(user, Options.ChangeEmailTokenProvider, GetChangeEmailPurpose(newEmail), token, cancellationToken))
+ {
+ return IdentityResult.Failed(Resources.InvalidToken);
+ }
+ var store = GetEmailStore();
+ await store.SetEmailAsync(user, newEmail, cancellationToken);
+ await store.SetEmailConfirmedAsync(user, true, cancellationToken);
+ await UpdateSecurityStampInternal(user, cancellationToken);
+ return await UpdateAsync(user, cancellationToken);
+ }
+
// IUserPhoneNumberStore methods
internal IUserPhoneNumberStore GetPhoneNumberStore()
{
diff --git a/test/Shared/UserManagerTestBase.cs b/test/Shared/UserManagerTestBase.cs
index 9f1fa064ce..2ba65a828a 100644
--- a/test/Shared/UserManagerTestBase.cs
+++ b/test/Shared/UserManagerTestBase.cs
@@ -1224,6 +1224,23 @@ namespace Microsoft.AspNet.Identity.Test
Assert.Equal(stamp, user.SecurityStamp);
}
+ [Fact]
+ public async Task ChangePhoneNumberFailsWithWrongPhoneNumber()
+ {
+ var manager = CreateManager();
+ var user = CreateTestUser();
+ user.PhoneNumber = "123-456-7890";
+ IdentityResultAssert.IsSuccess(await manager.CreateAsync(user));
+ Assert.False(await manager.IsPhoneNumberConfirmedAsync(user));
+ var stamp = await manager.GetSecurityStampAsync(user);
+ var token1 = await manager.GenerateChangePhoneNumberTokenAsync(user, "111-111-1111");
+ IdentityResultAssert.IsFailure(await manager.ChangePhoneNumberAsync(user, "bogus", token1),
+ "Invalid token.");
+ Assert.False(await manager.IsPhoneNumberConfirmedAsync(user));
+ Assert.Equal(await manager.GetPhoneNumberAsync(user), "123-456-7890");
+ Assert.Equal(stamp, user.SecurityStamp);
+ }
+
[Fact]
public async Task CanVerifyPhoneNumber()
{
@@ -1241,6 +1258,58 @@ namespace Microsoft.AspNet.Identity.Test
Assert.False(await manager.VerifyChangePhoneNumberTokenAsync(user, token1, num2));
}
+ [Fact]
+ public async Task CanChangeEmail()
+ {
+ var manager = CreateManager();
+ var user = CreateTestUser();
+ user.Email = user.UserName + "@diddly.bop";
+ IdentityResultAssert.IsSuccess(await manager.CreateAsync(user));
+ Assert.False(await manager.IsEmailConfirmedAsync(user));
+ var stamp = await manager.GetSecurityStampAsync(user);
+ string newEmail = user.UserName + "@en.vec";
+ var token1 = await manager.GenerateChangeEmailTokenAsync(user, newEmail);
+ IdentityResultAssert.IsSuccess(await manager.ChangeEmailAsync(user, newEmail, token1));
+ Assert.True(await manager.IsEmailConfirmedAsync(user));
+ Assert.Equal(await manager.GetEmailAsync(user), newEmail);
+ Assert.NotEqual(stamp, user.SecurityStamp);
+ }
+
+ [Fact]
+ public async Task ChangeEmailFailsWithWrongToken()
+ {
+ var manager = CreateManager();
+ var user = CreateTestUser();
+ user.Email = user.UserName + "@diddly.bop";
+ string oldEmail = user.Email;
+ IdentityResultAssert.IsSuccess(await manager.CreateAsync(user));
+ Assert.False(await manager.IsEmailConfirmedAsync(user));
+ var stamp = await manager.GetSecurityStampAsync(user);
+ IdentityResultAssert.IsFailure(await manager.ChangeEmailAsync(user, "whatevah@foo.barf", "bogus"),
+ "Invalid token.");
+ Assert.False(await manager.IsEmailConfirmedAsync(user));
+ Assert.Equal(await manager.GetEmailAsync(user), oldEmail);
+ Assert.Equal(stamp, user.SecurityStamp);
+ }
+
+ [Fact]
+ public async Task ChangeEmailFailsWithEmail()
+ {
+ var manager = CreateManager();
+ var user = CreateTestUser();
+ user.Email = user.UserName + "@diddly.bop";
+ string oldEmail = user.Email;
+ IdentityResultAssert.IsSuccess(await manager.CreateAsync(user));
+ Assert.False(await manager.IsEmailConfirmedAsync(user));
+ var stamp = await manager.GetSecurityStampAsync(user);
+ var token1 = await manager.GenerateChangeEmailTokenAsync(user, "forgot@alrea.dy");
+ IdentityResultAssert.IsFailure(await manager.ChangeEmailAsync(user, "oops@foo.barf", token1),
+ "Invalid token.");
+ Assert.False(await manager.IsEmailConfirmedAsync(user));
+ Assert.Equal(await manager.GetEmailAsync(user), oldEmail);
+ Assert.Equal(stamp, user.SecurityStamp);
+ }
+
[Fact]
public async Task CanEmailTwoFactorToken()
{