Functionality to update a password

This commit is contained in:
Robert Marshall 2021-06-06 09:34:05 +01:00
parent 6e732dbc69
commit 8f3ea03d75
7 changed files with 104 additions and 1 deletions

View file

@ -63,5 +63,38 @@ namespace Robware.Api.Auth.Tests.Controllers {
var controller = new UserController(logger, authenticator); var controller = new UserController(logger, authenticator);
(await controller.Authenticate(request)).Result.Should().BeOfType<NotFoundResult>(); (await controller.Authenticate(request)).Result.Should().BeOfType<NotFoundResult>();
} }
[Fact]
public async Task UpdatePassword_WithExistingUserAndCorrectPassword_Returns204() {
var logger = Substitute.For<ILogger<UserController>>();
var authenticator = Substitute.For<IAuthenticator>();
var controller = new UserController(logger, authenticator);
authenticator.UpdateUserPassword("valid", "correct", "new").Returns(AuthenticationResult.Success);
(await controller.UpdatePassword("valid", "correct", "new")).Should().BeOfType<NoContentResult>();
}
[Fact]
public async Task UpdatePassword_WithExistingUserAndIncorrectPassword_Returns400() {
var logger = Substitute.For<ILogger<UserController>>();
var authenticator = Substitute.For<IAuthenticator>();
var controller = new UserController(logger, authenticator);
authenticator.UpdateUserPassword("valid", "incorrect", "new").Returns(AuthenticationResult.IncorrectPassword);
(await controller.UpdatePassword("valid", "incorrect", "new")).Should().BeOfType<BadRequestResult>();
}
[Fact]
public async Task UpdatePassword_WithNonExistingUser_Returns400() {
var logger = Substitute.For<ILogger<UserController>>();
var authenticator = Substitute.For<IAuthenticator>();
var controller = new UserController(logger, authenticator);
authenticator.UpdateUserPassword("invalid", "incorrect", "new").Returns(AuthenticationResult.NotFound);
(await controller.UpdatePassword("invalid", "incorrect", "new")).Should().BeOfType<BadRequestResult>();
}
} }
} }

View file

@ -31,5 +31,11 @@ namespace Robware.Api.Auth.Controllers {
throw new ArgumentOutOfRangeException(); throw new ArgumentOutOfRangeException();
} }
} }
[HttpPatch(nameof(UpdatePassword))]
public async Task<ActionResult> UpdatePassword(string username, string oldPassword, string newPassword) {
var result = await _authenticator.UpdateUserPassword(username, oldPassword, newPassword);
return result == AuthenticationResult.Success ? (ActionResult)NoContent() : BadRequest();
}
} }
} }

View file

@ -46,5 +46,48 @@ namespace Robware.Auth.Tests.Users {
(await auth.Authenticate("test", "password")).Should().BeEquivalentTo((AuthenticationResult.NotFound, null as User)); (await auth.Authenticate("test", "password")).Should().BeEquivalentTo((AuthenticationResult.NotFound, null as User));
} }
[Fact]
public async Task UpdateUserPassword_WithExistingUserAndCorrectPassword_ReturnsSuccess() {
var users = Substitute.For<IUsers>();
var crypto = Substitute.For<ICryptographyProvider>();
crypto.Encrypt("correct").Returns("correct");
crypto.Encrypt("new").Returns("encrypted");
var user = new TestUser("valid", "correct");
users.GetByEmail("valid").Returns(user);
users.UpdateUserPassword("valid", "encrypted").Returns(true);
var auth = new Authenticator(users, crypto);
(await auth.UpdateUserPassword("valid", "correct", "new")).Should().Be(AuthenticationResult.Success);
await users.Received(1).UpdateUserPassword("valid", "encrypted");
}
[Fact]
public async Task UpdateUserPassword_WithExistingUserAndIncorrectPassword_ReturnsIncorrectPassword() {
var users = Substitute.For<IUsers>();
var crypto = Substitute.For<ICryptographyProvider>();
crypto.Encrypt("correct").Returns("correct");
var user = new TestUser("valid", "correct");
users.GetByEmail("valid").Returns(user);
users.UpdateUserPassword("valid", "encrypted").Returns(true);
var auth = new Authenticator(users, crypto);
(await auth.UpdateUserPassword("valid", "incorrect", "new")).Should().Be(AuthenticationResult.IncorrectPassword);
await users.Received(0).UpdateUserPassword("valid", "encrypted");
}
[Fact]
public async Task UpdateUserPassword_WithNonExistentUser_ReturnsNotFound() {
var users = Substitute.For<IUsers>();
var crypto = Substitute.For<ICryptographyProvider>();
users.GetByEmail("invalid").Throws(new UserNotFoundException(""));
var auth = new Authenticator(users, crypto);
(await auth.UpdateUserPassword("invalid", "correct", "new")).Should().Be(AuthenticationResult.NotFound);
await users.Received(0).UpdateUserPassword("valid", "encrypted");
}
} }
} }

View file

@ -19,6 +19,20 @@ namespace Robware.Auth.Users {
return (AuthenticationResult.NotFound, null); return (AuthenticationResult.NotFound, null);
} }
} }
public async Task<AuthenticationResult> UpdateUserPassword(string username, string oldPassword, string newPassword) {
try {
var user = await _users.GetByEmail(username);
if (_crypto.Encrypt(oldPassword) != user.Password)
return AuthenticationResult.IncorrectPassword;
await _users.UpdateUserPassword(username, _crypto.Encrypt(newPassword));
return AuthenticationResult.Success;
}
catch (UserNotFoundException) {
return AuthenticationResult.NotFound;
}
}
} }
public enum AuthenticationResult { public enum AuthenticationResult {

View file

@ -3,5 +3,6 @@
namespace Robware.Auth.Users { namespace Robware.Auth.Users {
public interface IAuthenticator { public interface IAuthenticator {
Task<(AuthenticationResult Result, User User)> Authenticate(string username, string password); Task<(AuthenticationResult Result, User User)> Authenticate(string username, string password);
Task<AuthenticationResult> UpdateUserPassword(string username, string oldPassword, string newPassword);
} }
} }

View file

@ -3,5 +3,6 @@
namespace Robware.Auth.Users { namespace Robware.Auth.Users {
public interface IUsers { public interface IUsers {
Task<User> GetByEmail(string email); Task<User> GetByEmail(string email);
Task<bool> UpdateUserPassword(string email, string newPassword);
} }
} }

View file

@ -6,7 +6,7 @@ using System.Threading.Tasks;
namespace Robware.Data { namespace Robware.Data {
public class UserRepository : IUsers { public class UserRepository : IUsers {
IMongoCollection<UserState> _collection; private readonly IMongoCollection<UserState> _collection;
public UserRepository(IMongoDatabase database) { public UserRepository(IMongoDatabase database) {
_collection = database.GetCollection<UserState>("users"); _collection = database.GetCollection<UserState>("users");
@ -19,5 +19,10 @@ namespace Robware.Data {
return new DatabaseUser(result); return new DatabaseUser(result);
} }
public async Task<bool> UpdateUserPassword(string email, string newPassword) {
var result = (await _collection.UpdateOneAsync(state => state.Email == email, Builders<UserState>.Update.Set(state => state.Password, newPassword)));
return result.ModifiedCount == 1;
}
} }
} }