I was able to get this working a few months back and forgot to post how I was able to do it.
in Core/Authorization/Users folder I edited User.cs with the following...
public virtual bool LockoutEnabled { get; set; }
public virtual int AccessFailedCount { get; set; }
public virtual DateTime? LockoutEndDateUtc { get; set; }
Then in UserStore the following ...
public class UserStore : AbpUserStore<Tenant, Role, User>, IUserLockoutStore<User, long>
{
private readonly IRepository<User, long> _userRepository;
public UserStore(
IRepository<User, long> userRepository,
IRepository<UserLogin, long> userLoginRepository,
IRepository<UserRole, long> userRoleRepository,
IRepository<Role> roleRepository,
IRepository<UserPermissionSetting, long> userPermissionSettingRepository,
IUnitOfWorkManager unitOfWorkManager
)
: base(
userRepository,
userLoginRepository,
userRoleRepository,
roleRepository,
userPermissionSettingRepository,
unitOfWorkManager
)
{
_userRepository = userRepository;
}
/// <summary>
/// Get user lock out end date
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
public Task<DateTimeOffset> GetLockoutEndDateAsync(User user)
{
return
Task.FromResult(user.LockoutEndDateUtc.HasValue
? new DateTimeOffset(DateTime.SpecifyKind(user.LockoutEndDateUtc.Value, DateTimeKind.Utc))
: new DateTimeOffset());
}
/// <summary>
/// Set user lockout end date
/// </summary>
/// <param name="user"></param>
/// <param name="lockoutEnd"></param>
/// <returns></returns>
public Task SetLockoutEndDateAsync(User user, DateTimeOffset lockoutEnd)
{
user.LockoutEndDateUtc = lockoutEnd.UtcDateTime;
_userRepository.Update(user);
return Task.FromResult(0);
}
/// <summary>
/// Increment failed access count
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
public async Task<int> IncrementAccessFailedCountAsync(User user)
{
user.AccessFailedCount++;
_userRepository.Update(user);
return await Task.FromResult(user.AccessFailedCount);
}
/// <summary>
/// Reset failed access count
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
public Task ResetAccessFailedCountAsync(User user)
{
user.AccessFailedCount = 0;
_userRepository.Update(user);
return Task.FromResult(0);
}
/// <summary>
/// Get failed access count
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
public Task<int> GetAccessFailedCountAsync(User user)
{
return Task.FromResult(user.AccessFailedCount);
}
/// <summary>
/// Get if lockout is enabled for the user
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
public Task<bool> GetLockoutEnabledAsync(User user)
{
return Task.FromResult(user.LockoutEnabled);
}
/// <summary>
/// Set lockout enabled for user
/// </summary>
/// <param name="user"></param>
/// <param name="enabled"></param>
/// <returns></returns>
public Task SetLockoutEnabledAsync(User user, bool enabled)
{
user.LockoutEnabled = enabled;
_userRepository.Update(user);
return Task.FromResult(0);
}
}
In the Application/Authorization/Users folder, I added this to the UserAppService.cs in the CreateUserAsync method under the line user.ShouldChangePasswordOnNextLogin = input.User.ShouldChangePasswordOnNextLogin; ...
//Always enable user Lockout module
user.LockoutEnabled = true;
Then in the Web/Controllers/AccountController.cs file, I made a bunch of changes. In the constructor, I added...
_userManager.UserLockoutEnabledByDefault = Convert.ToBoolean(ConfigurationManager.AppSettings["UserLockoutEnabledByDefault"]);
_userManager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(double.Parse(ConfigurationManager.AppSettings["DefaultAccountLockoutTimeSpan"]));
_userManager.MaxFailedAccessAttemptsBeforeLockout = Convert.ToInt32(ConfigurationManager.AppSettings["MaxFailedAccessAttemptsBeforeLockout"]);
Then in the Login method here is my entire Login method. There may be some better ways to do this but this worked for my particular need.
[HttpPost]
[Honeypot]
[UnitOfWork]
[ValidateAntiForgeryToken]
[DisableAuditing]
public virtual async Task<JsonResult> Login(LoginViewModel loginModel, string returnUrl = "", string returnUrlHash = "")
{
CheckModelState();
var user = await _userManager.FindByNameAsync(loginModel.UsernameOrEmailAddress);
if (user != null)
{
string errorMessage = null;
var validCredentials = await _userManager.FindAsync(loginModel.UsernameOrEmailAddress, loginModel.Password);
if (await _userManager.IsLockedOutAsync(user.Id))
{
errorMessage = string.Format("Your account has been locked out for {0} minutes due to multiple failed login attempts.", ConfigurationManager.AppSettings["DefaultAccountLockoutTimeSpan"]);
throw new UserFriendlyException(errorMessage);
}
else if (await _userManager.GetLockoutEnabledAsync(user.Id) && validCredentials == null)
{
await _userManager.AccessFailedAsync(user.Id);
if (await _userManager.IsLockedOutAsync(user.Id))
{
errorMessage = string.Format("Your account has been locked out for {0} minutes due to multiple failed login attempts.", ConfigurationManager.AppSettings["DefaultAccountLockoutTimeSpan"]);
return Json(new MvcAjaxResponse { Success = false, Error = new ErrorInfo(errorMessage) });
}
else
{
var accessFailedCount = await _userManager.GetAccessFailedCountAsync(user.Id);
var attemptsLeft =
Convert.ToInt32(
ConfigurationManager.AppSettings["MaxFailedAccessAttemptsBeforeLockout"]) -
accessFailedCount;
errorMessage = string.Format("Invalid credentials. You have {0} more attempt(s) before your account gets locked out.", attemptsLeft);
return Json(new MvcAjaxResponse { Success = false, Error = new ErrorInfo(errorMessage) });
}
}
else if (validCredentials == null)
{
ModelState.AddModelError("", "Invalid credentials. Please try again.");
errorMessage = string.Format("Invalid credentials.Please try again.");
throw new UserFriendlyException(errorMessage);
}
else
{
_unitOfWorkManager.Current.DisableFilter(AbpDataFilters.MayHaveTenant);
var loginResult = await GetLoginResultAsync(loginModel.UsernameOrEmailAddress, loginModel.Password, loginModel.TenancyName);
if (loginResult.User.ShouldChangePasswordOnNextLogin)
{
loginResult.User.SetNewPasswordResetCode();
return Json(new MvcAjaxResponse
{
TargetUrl = Url.Action(
"ResetPassword",
new ResetPasswordViewModel
{
UserId = SimpleStringCipher.Encrypt(loginResult.User.Id.ToString()),
ResetCode = loginResult.User.PasswordResetCode
})
});
}
await SignInAsync(loginResult.User, loginResult.Identity, loginModel.RememberMe);
await _userManager.ResetAccessFailedCountAsync(user.Id);
if (string.IsNullOrWhiteSpace(returnUrl))
{
returnUrl = Url.Action("Index", "Application");
}
if (!string.IsNullOrWhiteSpace(returnUrlHash))
{
returnUrl = returnUrl + returnUrlHash;
}
return Json(new MvcAjaxResponse { TargetUrl = returnUrl});
}
}
return Json(new MvcAjaxResponse { TargetUrl = returnUrl });
}
Then in the Web.config file I added these...
<add key="UserLockoutEnabledByDefault" value="true" />
<add key="DefaultAccountLockoutTimeSpan" value="15" />
<add key="MaxFailedAccessAttemptsBeforeLockout" value="3" />
Finally in the Web/Views/Account folder in the Login.js file I added to the loginForm.submit function the below directly after the data: $loginForm.serialize(), line ...
error: function(message) {
abp.message.error(message.error);
}
Is there a way to turn off and on certain features for the single tenant? For example be able to toggle on off the entire chat feature?
Good call, that worked. Thanks
I have Visual Studio 2015 update 3.
I downloaded the new Core version of ASP.NET Zero and it's not restoring the packages correctly. I have attached a screen shot of an example of what I am talking about. Its has done this for all the projects.
When I try to Update-Database through the Package manager it just says EntityFramework is not installed in the EntityFramework project. I am sure this is because it isn't restoring packages correctly.
Has anyone had the same issues? And if so have you figured out why this is happening?
Thats a good idea. I do like the expiring idea for a future release but for now the permissions based way should work good.
Thank you
I love the new Chat feature. I to implement this into our app but I only want the Admin to be able to chat with the users. So basically the Admin would be able to look up a username and chat with them but the basic user would not be able to find any of the users to chat back with.
What would be the best way to implement something like this?
I also want to create an message inbox type of functionality with similar functionality. Where the Admin communicates with the basic users but they cannot communicate with anyone but the Admin.
Thank you
Thank you, I haven't heard of Table-Per-Hierarchy approach. Do you know of any good articles that explain it?
Hello I want to create different types of users. They will have different types of profiles and functionality. For example a Landlord and a Renter user. What would be the best way to do this? Obviously I could create different Roles for them which I will do as well but a Landlord will have different classes associated with them such as a Listing and Rental properties.
Any suggestions would be greatly appreciated.