Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
using Abp.Authorization;
using Abp.Authorization;
using Abp.Authorization.Users;
using Abp.Domain.Repositories;
using Abp.MultiTenancy;
using Abp.Runtime.Security;
using Abp.UI;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Shesha.Authentication.External;
using Shesha.Authentication.JwtBearer;
using Shesha.Authorization.Models;
using Shesha.Authorization.Models;
using Shesha.Authorization.Users;
using Shesha.Controllers;
using Shesha.Domain;
using Shesha.Extensions;
using Shesha.Extensions;
using Shesha.Models.TokenAuth;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;

namespace Shesha.Authorization
{
[Route("api/[controller]/[action]")]
Expand Down Expand Up @@ -68,43 +68,43 @@ public TokenAuthController(
_tokenBlacklistService = tokenBlacklistService;
}

[HttpPost]
public async Task<AuthenticateResultModel> AuthenticateAsync([FromBody] AuthenticateModel model)
{
// Check for user registration status
var registration = await _userRegistration.FirstOrDefaultAsync(e => e.UserNameOrEmailAddress == model.UserNameOrEmailAddress);
if (registration != null && !registration.IsComplete)
{
// Return a result indicating a client-side redirect
return new AuthenticateResultModel
{
ResultType = AuthenticateResultType.RedirectNoAuth,
RedirectModule = registration.AdditionalRegistrationInfoForm.Module,
RedirectForm = registration.AdditionalRegistrationInfoForm.Name,
UserId = registration.UserId
};
}
// Attempt login authentication
var loginResult = await GetLoginResultAsync(
model.UserNameOrEmailAddress,
model.Password,
model.IMEI,
GetTenancyNameOrNull()
);
// Return the authenticate result
var authenticateResult = await GetAuthenticateResultAsync(loginResult, model.IMEI);
return authenticateResult;
[HttpPost]
public async Task<AuthenticateResultModel> AuthenticateAsync([FromBody] AuthenticateModel model)
{
// Check for user registration status
var registration = await _userRegistration.FirstOrDefaultAsync(e => e.UserNameOrEmailAddress == model.UserNameOrEmailAddress);

if (registration != null && !registration.IsComplete)
{
// Return a result indicating a client-side redirect
return new AuthenticateResultModel
{
ResultType = AuthenticateResultType.RedirectNoAuth,
RedirectModule = registration.AdditionalRegistrationInfoForm.Module,
RedirectForm = registration.AdditionalRegistrationInfoForm.Name,
UserId = registration.UserId
};
}

// Attempt login authentication
var loginResult = await GetLoginResultAsync(
model.UserNameOrEmailAddress,
model.Password,
model.IMEI,
GetTenancyNameOrNull()
);

// Return the authenticate result
var authenticateResult = await GetAuthenticateResultAsync(loginResult, model.IMEI);
return authenticateResult;
}

private async Task<AuthenticateResultModel> GetAuthenticateResultAsync(ShaLoginResult<User> loginResult, string imei)
{
var validFrom = DateTime.UtcNow;
var expiresOn = validFrom.Add(_configuration.Expiration);
var expireInSeconds = (int)_configuration.Expiration.TotalSeconds;
{
var validFrom = DateTime.UtcNow;
var expiresOn = validFrom.Add(_configuration.Expiration);
var expireInSeconds = (int)_configuration.Expiration.TotalSeconds;

var accessToken = CreateAccessToken(CreateJwtClaims(loginResult.Identity), validFrom, expiresOn);

var personId = loginResult?.User != null
Expand All @@ -125,6 +125,7 @@ private async Task<AuthenticateResultModel> GetAuthenticateResultAsync(ShaLoginR
ExpireInSeconds = expireInSeconds,
ExpireOn = expiresOn,
UserId = loginResult.User.Id,
RequireChangePassword = loginResult.User.RequireChangePassword,
PersonId = personId,
DeviceName = device?.Name,
ResultType = AuthenticateResultType.Success
Expand All @@ -134,46 +135,46 @@ private async Task<AuthenticateResultModel> GetAuthenticateResultAsync(ShaLoginR
[HttpPost]
public async Task<bool> SignOffAsync()
{
var jti = User.GetTokenId();
if (jti != null)
await _tokenBlacklistService.BlacklistTokenAsync(jti, User.GetTokenExpirationDate());
var jti = User.GetTokenId();
if (jti != null)
await _tokenBlacklistService.BlacklistTokenAsync(jti, User.GetTokenExpirationDate());

return true;
}
}

#region OTP Login
/// <summary>
/// Send authentication one-time pin to the user with a specified <paramref name="userNameOrMobileNo"/>
/// </summary>
[AbpAllowAnonymous]
[HttpPost]
public async Task<OtpAuthenticateSendPinResponse> OtpAuthenticateSendPinAsync(string userNameOrMobileNo)
{
var persons = await _personRepository.GetAll().Where(u => u.MobileNumber1 == userNameOrMobileNo || u.User.UserName == userNameOrMobileNo).ToListAsync();
if (!persons.Any())
throw new UserFriendlyException("User with the specified mobile number not found");
if (persons.Count() > 1)
throw new UserFriendlyException("Found more that one user with the specified mobile number");
var person = persons.First();
if (person.User == null)
throw new UserFriendlyException("User with the specified mobile has no internal account");
var sendPinResponse = await _logInManager.SendLoginOtpAsync(person.User, person.MobileNumber1);
return new OtpAuthenticateSendPinResponse
{
OperationId = sendPinResponse.OperationId
};

/// <summary>
/// Send authentication one-time pin to the user with a specified <paramref name="userNameOrMobileNo"/>
/// </summary>
[AbpAllowAnonymous]
[HttpPost]
public async Task<OtpAuthenticateSendPinResponse> OtpAuthenticateSendPinAsync(string userNameOrMobileNo)
{
var persons = await _personRepository.GetAll().Where(u => u.MobileNumber1 == userNameOrMobileNo || u.User.UserName == userNameOrMobileNo).ToListAsync();
if (!persons.Any())
throw new UserFriendlyException("User with the specified mobile number not found");
if (persons.Count() > 1)
throw new UserFriendlyException("Found more that one user with the specified mobile number");

var person = persons.First();

if (person.User == null)
throw new UserFriendlyException("User with the specified mobile has no internal account");

var sendPinResponse = await _logInManager.SendLoginOtpAsync(person.User, person.MobileNumber1);

return new OtpAuthenticateSendPinResponse
{
OperationId = sendPinResponse.OperationId
};
}

[HttpPost]
public async Task<AuthenticateResultModel> OtpAuthenticateAsync([FromBody] OtpAuthenticateModel model)
{
var tenancyName = GetTenancyNameOrNull();
var loginResult = await _logInManager.LoginViaOtpAsync(model.MobileNo, model.OperationId, model.Code, model.IMEI, tenancyName);
public async Task<AuthenticateResultModel> OtpAuthenticateAsync([FromBody] OtpAuthenticateModel model)
{
var tenancyName = GetTenancyNameOrNull();
var loginResult = await _logInManager.LoginViaOtpAsync(model.MobileNo, model.OperationId, model.Code, model.IMEI, tenancyName);

switch (loginResult.Result)
{
Expand All @@ -185,7 +186,7 @@ public async Task<AuthenticateResultModel> OtpAuthenticateAsync([FromBody] OtpAu
}

#endregion

[HttpGet]
public List<ExternalLoginProviderInfoModel> GetExternalAuthenticationProviders()
{
Expand Down Expand Up @@ -312,9 +313,9 @@ private async Task<ShaLoginResult<User>> GetLoginResultAsync(string usernameOrEm
}

private string CreateAccessToken(IEnumerable<Claim> claims)
{
var validFrom = DateTime.UtcNow;
var expiresOn = validFrom.Add(_configuration.Expiration);
{
var validFrom = DateTime.UtcNow;
var expiresOn = validFrom.Add(_configuration.Expiration);
return CreateAccessToken(claims, validFrom, expiresOn);
}

Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,6 @@ public class UserLoginInfoDto : EntityDto<long>
public string HomeUrl { get; set; }
public bool IsSelfServiceUser { get; set; }
public List<GrantedPermissionDto> GrantedPermissions { get; set; } = new List<GrantedPermissionDto>();
public bool RequireChangePassword { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ public async Task<GetCurrentLoginInfoOutput> GetCurrentLoginInfoAsync()
MobileNumber = user.PhoneNumber,
GrantedPermissions = await GetGrantedPermissionsAsync(),
PersonId = person.Id,
HomeUrl = homeUrl
HomeUrl = homeUrl,
RequireChangePassword = user.RequireChangePassword
};
}

Expand Down
2 changes: 2 additions & 0 deletions shesha-core/src/Shesha.Application/Shesha.Application.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
<None Remove="ConfigMigrations\package20250127_1510.shaconfig" />
<None Remove="ConfigMigrations\package20250130_1900.shaconfig" />
<None Remove="ConfigMigrations\package20250212_0911.shaconfig" />
<None Remove="ConfigMigrations\package20260127_1945.shaconfig" />
<None Remove="Excel\template.xlsx" />
<None Remove="ConfigMigrations\package20230324_1835.shaconfig" />
<None Remove="ConfigMigrations\package20230411_1238.shaconfig" />
Expand Down Expand Up @@ -119,6 +120,7 @@
<EmbeddedResource Include="ConfigMigrations\package20250127_1510.shaconfig" />
<EmbeddedResource Include="ConfigMigrations\package20250130_1900.shaconfig" />
<EmbeddedResource Include="ConfigMigrations\package20250212_0911.shaconfig" />
<EmbeddedResource Include="ConfigMigrations\package20260127_1945.shaconfig" />
<EmbeddedResource Include="Excel\template.xlsx" />
<EmbeddedResource Include="ConfigMigrations\package20230324_1835.shaconfig" />
<EmbeddedResource Include="ConfigMigrations\package20230411_1238.shaconfig" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,6 @@ public class CreatePersonAccountDto
[Required(AllowEmptyStrings = false)]
public virtual string EmailAddress { get; set; }
public virtual ReferenceListItemValueDto TypeOfAccount { get; set; }
public virtual bool RequireChangePassword { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
using Shesha.Configuration.Security;
using Shesha.Domain;
using Shesha.Domain.Enums;
using Shesha.Extensions;
using Shesha.Extensions;
using Shesha.Persons;
using Shesha.UserManagements.Configurations;
using System;
Expand Down Expand Up @@ -112,6 +112,7 @@ public async Task<PersonAccountDto> CreateAsync(CreatePersonAccountDto input)
input.LastName,
input.MobileNumber,
input.EmailAddress,
input.RequireChangePassword,
defaultMethods);

// Creating Person entity
Expand All @@ -125,46 +126,46 @@ public async Task<PersonAccountDto> CreateAsync(CreatePersonAccountDto input)
await _repository.InsertAsync(person);

var personAccount = ObjectMapper.Map<PersonAccountDto>(person);
personAccount.GoToUrlAfterRegistration = registrationSettings.GoToUrlAfterRegistration;
personAccount.UserId = user.Id;
var userRegistration = new ShaUserRegistration
{
UserId = user.Id,
UserNameOrEmailAddress = user.UserName,
GoToUrlAfterRegistration = registrationSettings.GoToUrlAfterRegistration,
AdditionalRegistrationInfoForm = !string.IsNullOrWhiteSpace(registrationSettings.AdditionalRegistrationInfoFormModule) && !string.IsNullOrWhiteSpace(registrationSettings.AdditionalRegistrationInfoFormName)
? new FormIdentifier(registrationSettings.AdditionalRegistrationInfoFormModule,registrationSettings.AdditionalRegistrationInfoFormName) : null,
IsComplete = registrationSettings.AdditionalRegistrationInfo ? false : true
};
await _userRegistration.InsertAsync(userRegistration);
personAccount.GoToUrlAfterRegistration = registrationSettings.GoToUrlAfterRegistration;
personAccount.UserId = user.Id;

var userRegistration = new ShaUserRegistration
{
UserId = user.Id,
UserNameOrEmailAddress = user.UserName,
GoToUrlAfterRegistration = registrationSettings.GoToUrlAfterRegistration,
AdditionalRegistrationInfoForm = !string.IsNullOrWhiteSpace(registrationSettings.AdditionalRegistrationInfoFormModule) && !string.IsNullOrWhiteSpace(registrationSettings.AdditionalRegistrationInfoFormName)
? new FormIdentifier(registrationSettings.AdditionalRegistrationInfoFormModule,registrationSettings.AdditionalRegistrationInfoFormName) : null,
IsComplete = registrationSettings.AdditionalRegistrationInfo ? false : true
};

await _userRegistration.InsertAsync(userRegistration);

await CurrentUnitOfWork.SaveChangesAsync();

return personAccount;
}

/// <summary>
///
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
/// <summary>
///
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public async Task<string> CompleteRegistrationAsync(long userId)
{
var userRegistration = await _userRegistration.FirstOrDefaultAsync(e => e.UserId == userId);
if (userRegistration == null)
throw new Exception("User registration not found");
if (userRegistration.IsComplete)
throw new Exception("User registration already completed");
userRegistration.IsComplete = true;
await _userRegistration.UpdateAsync(userRegistration);
return userRegistration.GoToUrlAfterRegistration;
}
public async Task<string> CompleteRegistrationAsync(long userId)
{
var userRegistration = await _userRegistration.FirstOrDefaultAsync(e => e.UserId == userId);
if (userRegistration == null)
throw new Exception("User registration not found");

if (userRegistration.IsComplete)
throw new Exception("User registration already completed");

userRegistration.IsComplete = true;
await _userRegistration.UpdateAsync(userRegistration);

return userRegistration.GoToUrlAfterRegistration;
}


/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ public class ResetPasswordDto

[Required]
public string NewPassword { get; set; }
public bool RequireChangePassword { get; set; }
}
}
3 changes: 3 additions & 0 deletions shesha-core/src/Shesha.Application/Users/UserAppService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,7 @@ private void ValidateUserPasswordResetMethod(User user, long resetMethod)

#endregion

[AbpAllowAnonymous]
public async Task<bool> ChangePasswordAsync(ChangePasswordDto input)
{
if (_abpSession.UserId == null)
Expand All @@ -647,6 +648,7 @@ public async Task<bool> ChangePasswordAsync(ChangePasswordDto input)
_personRepository.GetAll().FirstOrDefault(x => x.User == user)?.AddHistoryEvent("Password changed", "Password changed");

user.Password = _passwordHasher.HashPassword(user, input.NewPassword);
user.RequireChangePassword = false;
await CurrentUnitOfWork.SaveChangesAsync();
return true;
}
Expand Down Expand Up @@ -678,6 +680,7 @@ public async Task<bool> ResetPasswordAsync(ResetPasswordDto input)
person?.AddHistoryEvent("Password reset", "Password reset");

user.Password = _passwordHasher.HashPassword(user, input.NewPassword);
user.RequireChangePassword = input.RequireChangePassword;
user.IsActive = true;
if (user.LockoutEndDateUtc.HasValue && user.LockoutEndDateUtc > DateTime.Now)
user.LockoutEndDateUtc = DateTime.Now;
Expand Down
Loading