using Abp.Runtime.Security; using Abp.UI; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using VberAdmin.Authentication.External; using VberAdmin.Authentication.JwtBearer; using VberAdmin.Authorization; using VberAdmin.Authorization.Users; using VberAdmin.Models.TokenAuth; using VberZero.AppService.Authorization; using VberZero.Authorization; using VberZero.Authorization.Users; using VberZero.BaseSystem.Users; using VberZero.MultiTenancy; namespace VberAdmin.Controllers; [Route("api/[controller]/[action]")] public class TokenAuthController : VberAdminControllerBase { private readonly LogInManager _logInManager; private readonly ITenantCache _tenantCache; private readonly VzLoginResultTypeHelper _vzLoginResultTypeHelper; private readonly TokenAuthConfiguration _configuration; private readonly IExternalAuthConfiguration _externalAuthConfiguration; private readonly IExternalAuthManager _externalAuthManager; private readonly UserRegistrationManager _userRegistrationManager; private readonly UserManager _userManager; public TokenAuthController( LogInManager logInManager, ITenantCache tenantCache, VzLoginResultTypeHelper vzLoginResultTypeHelper, TokenAuthConfiguration configuration, IExternalAuthConfiguration externalAuthConfiguration, IExternalAuthManager externalAuthManager, UserRegistrationManager userRegistrationManager, UserManager userManager) { _logInManager = logInManager; _tenantCache = tenantCache; _vzLoginResultTypeHelper = vzLoginResultTypeHelper; _configuration = configuration; _externalAuthConfiguration = externalAuthConfiguration; _externalAuthManager = externalAuthManager; _userRegistrationManager = userRegistrationManager; _userManager = userManager; } [HttpPost] public async Task Authenticate([FromBody] AuthenticateModel model) { var tenancyName = await _userManager.GetTenancyNameAsync(model.UsernameOrEmailOrPhone); var loginResult = await GetLoginResultAsync( model.UsernameOrEmailOrPhone, model.Password, tenancyName ); var accessToken = CreateAccessToken(CreateJwtClaims(loginResult.Identity)); return new AuthenticateResultModel { AccessToken = accessToken, EncryptedAccessToken = GetEncryptedAccessToken(accessToken), ExpireInSeconds = (int)_configuration.Expiration.TotalSeconds, UserId = loginResult.User.Id }; } [HttpGet] public List GetExternalAuthenticationProviders() { return ObjectMapper.Map>(_externalAuthConfiguration.Providers); } [HttpPost] public async Task ExternalAuthenticate([FromBody] ExternalAuthenticateModel model) { var externalUser = await GetExternalUserInfo(model); var loginResult = await _logInManager.LoginAsync(new UserLoginInfo(model.AuthProvider, model.ProviderKey, model.AuthProvider), GetTenancyNameOrNull()); switch (loginResult.Result) { case VzLoginResultType.Success: { var accessToken = CreateAccessToken(CreateJwtClaims(loginResult.Identity)); return new ExternalAuthenticateResultModel { AccessToken = accessToken, EncryptedAccessToken = GetEncryptedAccessToken(accessToken), ExpireInSeconds = (int)_configuration.Expiration.TotalSeconds }; } case VzLoginResultType.UnknownExternalLogin: { var newUser = await RegisterExternalUserAsync(externalUser); if (!newUser.IsActive) { return new ExternalAuthenticateResultModel { WaitingForActivation = true }; } // Try to login again with newly registered user! loginResult = await _logInManager.LoginAsync(new UserLoginInfo(model.AuthProvider, model.ProviderKey, model.AuthProvider), GetTenancyNameOrNull()); if (loginResult.Result != VzLoginResultType.Success) { throw _vzLoginResultTypeHelper.CreateExceptionForFailedLoginAttempt( loginResult.Result, model.ProviderKey, GetTenancyNameOrNull() ); } var accessToken = CreateAccessToken(CreateJwtClaims(loginResult.Identity)); return new ExternalAuthenticateResultModel { AccessToken = accessToken, EncryptedAccessToken = GetEncryptedAccessToken(accessToken), ExpireInSeconds = (int)_configuration.Expiration.TotalSeconds }; } default: { throw _vzLoginResultTypeHelper.CreateExceptionForFailedLoginAttempt( loginResult.Result, model.ProviderKey, GetTenancyNameOrNull() ); } } } private async Task RegisterExternalUserAsync(ExternalAuthUserInfo externalUser) { var user = await _userRegistrationManager.RegisterAsync( externalUser.Name, externalUser.Surname, externalUser.EmailAddress, externalUser.EmailAddress, VberZero.BaseSystem.Users.User.CreateRandomPassword(), true ); user.Logins = new List { new UserLogin { LoginProvider = externalUser.Provider, ProviderKey = externalUser.ProviderKey, TenantId = user.TenantId } }; await CurrentUnitOfWork.SaveChangesAsync(); return user; } private async Task GetExternalUserInfo(ExternalAuthenticateModel model) { var userInfo = await _externalAuthManager.GetUserInfo(model.AuthProvider, model.ProviderAccessCode); if (userInfo.ProviderKey != model.ProviderKey) { throw new UserFriendlyException(L("CouldNotValidateExternalUser")); } return userInfo; } private string GetTenancyNameOrNull() { if (!AbpSession.TenantId.HasValue) { return null; } return _tenantCache.GetOrNull(AbpSession.TenantId.Value)?.TenancyName; } private async Task GetLoginResultAsync(string usernameOrEmailAddress, string password, string tenancyName) { var loginResult = await _logInManager.LoginAsync(usernameOrEmailAddress, password, tenancyName); switch (loginResult.Result) { case VzLoginResultType.Success: return loginResult; default: throw _vzLoginResultTypeHelper.CreateExceptionForFailedLoginAttempt(loginResult.Result, usernameOrEmailAddress, tenancyName); } } private string CreateAccessToken(IEnumerable claims, TimeSpan? expiration = null) { var now = DateTime.UtcNow; var jwtSecurityToken = new JwtSecurityToken( issuer: _configuration.Issuer, audience: _configuration.Audience, claims: claims, notBefore: now, expires: now.Add(expiration ?? _configuration.Expiration), signingCredentials: _configuration.SigningCredentials ); return new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken); } private static List CreateJwtClaims(ClaimsIdentity identity) { var claims = identity.Claims.ToList(); var nameIdClaim = claims.First(c => c.Type == ClaimTypes.NameIdentifier); // Specifically add the jti (random nonce), iat (issued timestamp), and sub (subject/user) claims. claims.AddRange(new[] { new Claim(JwtRegisteredClaimNames.Sub, nameIdClaim.Value), new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), new Claim(JwtRegisteredClaimNames.Iat, DateTimeOffset.Now.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64) }); return claims; } private string GetEncryptedAccessToken(string accessToken) { return SimpleStringCipher.Instance.Encrypt(accessToken); } }