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