Functionality to update a password
This commit is contained in:
parent
6e732dbc69
commit
8f3ea03d75
7 changed files with 104 additions and 1 deletions
|
@ -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>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue