diff --git a/server/AyaNova/Controllers/AuthController.cs b/server/AyaNova/Controllers/AuthController.cs index b7a0515d..f90fb600 100644 --- a/server/AyaNova/Controllers/AuthController.cs +++ b/server/AyaNova/Controllers/AuthController.cs @@ -233,6 +233,85 @@ namespace AyaNova.Api.Controllers return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED)); } + + /// + /// Change Password + /// + /// + /// + /// + [HttpPost("ChangePassword")] + public async Task ChangePassword([FromBody] AuthController.ChangePasswordParam changecreds) + { + if (!serverState.IsOpen) + { + return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason)); + } + + if (!ModelState.IsValid) + { + return BadRequest(new ApiErrorResponse(ModelState)); + } + + + + int nFailedAuthDelay = 3000;//should be just long enough to make brute force a hassle but short enough to not annoy people who just mistyped their creds to login + + + if (string.IsNullOrWhiteSpace(changecreds.OldPassword) || string.IsNullOrWhiteSpace(changecreds.LoginName)) + { + metrics.Measure.Meter.Mark(MetricsRegistry.FailedLoginMeter); + //Make a failed pw wait + await Task.Delay(nFailedAuthDelay); + return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED)); + } + + if (string.IsNullOrWhiteSpace(changecreds.NewPassword)) + { + return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_REQUIRED, "NewPassword")); + } + + if (changecreds.NewPassword != changecreds.ConfirmPassword) + { + return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_INVALID_VALUE, "NewPassword", "NewPassword does not match ConfirmPassword")); + } + + + + //Multiple users are allowed the same password and login + //Salt will differentiate them so get all users that match login, then try to match pw + var users = await ct.User.AsNoTracking().Where(m => m.Login == changecreds.LoginName).ToListAsync(); + + foreach (User u in users) + { + string hashed = Hasher.hash(u.Salt, changecreds.OldPassword); + if (hashed == u.Password) + { + + //If the user is inactive they may not login + if (!u.Active) + { + //respond like bad creds so as not to leak information + return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED)); + } + + + //fetch and update user + //Instantiate the business object handler + UserBiz biz = UserBiz.GetBiz(ct, HttpContext); + await biz.ChangePasswordAsync(u.Id, changecreds.NewPassword); + + return NoContent(); + + } + } + + //No users matched, it's a failed login + //Make a failed pw wait + await Task.Delay(nFailedAuthDelay); + return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED)); + } + //------------------------------------------------------ public class CredentialsParam @@ -245,5 +324,19 @@ namespace AyaNova.Api.Controllers } + public class ChangePasswordParam + { + [System.ComponentModel.DataAnnotations.Required] + public string LoginName { get; set; } + [System.ComponentModel.DataAnnotations.Required] + public string OldPassword { get; set; } + [System.ComponentModel.DataAnnotations.Required] + public string NewPassword { get; set; } + [System.ComponentModel.DataAnnotations.Required] + public string ConfirmPassword { get; set; } + + } + + }//eoc }//eons \ No newline at end of file diff --git a/server/AyaNova/biz/UserBiz.cs b/server/AyaNova/biz/UserBiz.cs index dcdbf674..7c80cda3 100644 --- a/server/AyaNova/biz/UserBiz.cs +++ b/server/AyaNova/biz/UserBiz.cs @@ -196,6 +196,21 @@ namespace AyaNova.Biz } + + //put + internal async Task ChangePasswordAsync(long userId, string newPassword) + { + User dbObj = await ct.User.FirstOrDefaultAsync(m => m.Id == userId); + dbObj.Password = Hasher.hash(dbObj.Salt, newPassword); + await ct.SaveChangesAsync(); + + //Log modification and save context + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct); + + return true; + } + + private async Task SearchIndexAsync(User obj, bool isNew) { //SEARCH INDEXING