Activities of "Hon-Tre_IFS"

Are you talking about the auto login once the session is expired

During automatic logout, the web app is redirecting to default department host login screen instead of tenant login screen.

This is not allowing user to navigate to the tenant portal:

** For Reference Loging Paged Overridden code **

using Hon.IFS.DepartmentHost.Pages.Account; using Hon.IFS.DeptManagement.FireDepartments; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Options; using Owl.reCAPTCHA; using Microsoft.Extensions.Logging; using System; using System.Linq; using System.Threading.Tasks; using Volo.Abp.Account.ExternalProviders; using Volo.Abp.Account.Public.Web; using Volo.Abp.Account.Public.Web.Pages.Account; using Volo.Abp.Account.Security.Recaptcha; using Volo.Abp.DependencyInjection; using Volo.Abp.Identity; using Volo.Abp.Security.Claims; using Volo.Saas.Tenants;

namespace Hon.IFS.DepartmentHost.Pages.Account { [ExposeServices(typeof(CustomLoginModel), typeof(LoginModel))] public class CustomLoginModel : LoginModel { private readonly ITenantRepository _tenantRepository; private readonly DepartmentHostBrandingProvider _brandingProvider; private readonly IFireDepartmentRepository _fireDepartmentRepository; private readonly IConfiguration _configuration;

    public string? TenantHostName { get; set; }

    [BindProperty]
    public new LoginInput LoginInput { get; set; } = new();

    public CustomLoginModel(
        IAuthenticationSchemeProvider schemeProvider,
        IOptions<AbpAccountOptions> accountOptions,
        IAccountExternalProviderAppService accountExternalProviderAppService,
        ICurrentPrincipalAccessor currentPrincipalAccessor,
        IAbpRecaptchaValidatorFactory recaptchaValidatorFactory,
        IOptions<IdentityOptions> identityOptions,
        IOptionsSnapshot<reCAPTCHAOptions> reCaptchaOptions,
        ITenantRepository tenantRepository,
        DepartmentHostBrandingProvider brandingProvider,
        IFireDepartmentRepository fireDepartmentRepository,
        IConfiguration configuration)
        : base(schemeProvider, accountOptions, recaptchaValidatorFactory,
               accountExternalProviderAppService, currentPrincipalAccessor,
               identityOptions, reCaptchaOptions)
    {
        _tenantRepository = tenantRepository;
        _brandingProvider = brandingProvider;
        _fireDepartmentRepository = fireDepartmentRepository;
        _configuration = configuration;
    }

    public override async Task<IActionResult> OnGetAsync()
    {
        ParseTenantHostName();

        // If tenantName is not in the query string but __tenant exists in the ReturnUrl
        // (passed through the OIDC authorize URL), redirect with tenantName so the
        // branding provider and tenant resolution work correctly.
        if (string.IsNullOrWhiteSpace(TenantHostName) && !string.IsNullOrWhiteSpace(ReturnUrl))
        {
            var tenantFromReturnUrl = ExtractTenantFromReturnUrl(ReturnUrl);
            if (!string.IsNullOrWhiteSpace(tenantFromReturnUrl))
            {
                // Strip _tenantHost from ReturnUrl — it was only needed to resolve the tenant.
                // Keeping it would leak it into the final URL the user sees after login.
                var cleanReturnUrl = StripTenantHostFromUrl(ReturnUrl);
                var redirectUrl = $"{Request.Path}?tenantName={Uri.EscapeDataString(tenantFromReturnUrl)}&ReturnUrl={Uri.EscapeDataString(cleanReturnUrl)}";
                if (!string.IsNullOrWhiteSpace(ReturnUrlHash))
                {
                    redirectUrl += $"&ReturnUrlHash={Uri.EscapeDataString(ReturnUrlHash)}";
                }
                return Redirect(redirectUrl);
            }
        }

        return await base.OnGetAsync();
    }

    public override async Task<IActionResult> OnPostAsync(string action)
    {
        ParseTenantHostName();

        try
        {
            var tenant = await ResolveTenantAsync();
            if (tenant == null && !string.IsNullOrEmpty(_brandingProvider.TenantAppName))
            {
                return AddErrorAndReturnPage("LoginInput.UserNameOrEmailAddress", "Invalid tenant configuration.");
            }

            if (!ValidateCredentials(out var username, out var password))
            {
                return Page();
            }

            return await ProcessLoginAsync(tenant, username, password, action);
        }
        catch (Exception ex)
        {
            Logger.LogWarning(ex, "An error occurred during login.");
            Alerts.Danger("An error occurred during login. Please try again.");
            return Page();
        }
    }

    private void ParseTenantHostName()
    {
        var tenantName = Request.Query["tenantName"].ToString();
        TenantHostName = string.IsNullOrWhiteSpace(tenantName) ? null : Uri.UnescapeDataString(tenantName);
    }

    /// <summary>
    /// Extracts the _tenantHost parameter from the OIDC authorize URL embedded in ReturnUrl.
    /// This is used when the notification deep link passes _tenantHost through the OIDC flow.
    /// </summary>
    private string? ExtractTenantFromReturnUrl(string returnUrl)
    {
        try
        {
            var queryIndex = returnUrl.IndexOf('?');
            if (queryIndex < 0) return null;

            var queryString = returnUrl[(queryIndex + 1)..];
            var queryParams = QueryHelpers.ParseQuery(queryString);

            if (queryParams.TryGetValue("_tenantHost", out var tenantValues) && tenantValues.Count > 0)
            {
                var tenant = tenantValues.First();
                return string.IsNullOrWhiteSpace(tenant) ? null : tenant;
            }
        }
        catch (Exception ex)
        {
            Logger.LogWarning(ex, "Failed to extract tenant from ReturnUrl: {ReturnUrl}", returnUrl);
        }

        return null;
    }

    /// <summary>
    /// Removes the _tenantHost query parameter from a URL after it has been used
    /// for tenant resolution, so it doesn't leak into the final redirect URL.
    /// </summary>
    private string StripTenantHostFromUrl(string url)
    {
        if (string.IsNullOrWhiteSpace(url))
        {
            return url;
        }

        try
        {
            var queryIndex = url.IndexOf('?');
            if (queryIndex < 0)
            {
                return url;
            }

            var path = url[..queryIndex];
            var queryParams = QueryHelpers.ParseQuery(url[(queryIndex + 1)..]);

            if (!queryParams.Remove("_tenantHost"))
            {
                return url;
            }

            if (queryParams.Count == 0)
            {
                return path;
            }

            var result = path;
            foreach (var param in queryParams)
            {
                foreach (var value in param.Value)
                {
                    result = QueryHelpers.AddQueryString(result, param.Key, value ?? string.Empty);
                }
            }

            return result;
        }
        catch (Exception ex)
        {
            Logger.LogWarning(ex, "Failed to strip _tenantHost from URL: {Url}", url);
            return url;
        }
    }

    private async Task<Tenant?> ResolveTenantAsync()
    {
        var tenantKey = _brandingProvider.TenantAppName;
        return string.IsNullOrEmpty(tenantKey) ? null : await _tenantRepository.FindByNameAsync(tenantKey);
    }

    private bool ValidateCredentials(out string username, out string password)
    {
        username = LoginInput?.UserNameOrEmailAddress?.Trim() ?? string.Empty;
        password = LoginInput?.Password?.Trim() ?? string.Empty;

        var isValid = true;

        if (string.IsNullOrEmpty(username))
        {
            ModelState.AddModelError("LoginInput.UserNameOrEmailAddress", "Username is required.");
            isValid = false;
        }

        if (string.IsNullOrEmpty(password))
        {
            ModelState.AddModelError("LoginInput.Password", "Password is required.");
            isValid = false;
        }

        return isValid;
    }

    private async Task<IActionResult> ProcessLoginAsync(Tenant? tenant, string username, string password, string action)
    {
        using (CurrentTenant.Change(tenant?.Id, tenant?.Name))
        {
            var user = await FindUserAsync(username);
            if (user == null)
            {
                return AddErrorAndReturnPage("LoginInput.UserNameOrEmailAddress", "Username is incorrect.");
            }

            if (!await UserManager.CheckPasswordAsync(user, password))
            {
                return AddErrorAndReturnPage("LoginInput.Password", "Invalid password.");
            }

            SyncBaseLoginInput();

            // Strip _tenantHost from ReturnUrl before the OIDC flow — it was only
            // needed for tenant resolution and shouldn't appear in the final URL.
            if (!string.IsNullOrWhiteSpace(ReturnUrl))
            {
                ReturnUrl = StripTenantHostFromUrl(ReturnUrl);
            }

            var result = await base.OnPostAsync(action);
            if (IsLoginFailed(result))
            {
                return AddErrorAndReturnPage("LoginInput.UserNameOrEmailAddress", "The username or password is incorrect.");
            }

            user = await UserManager.FindByIdAsync(user.Id.ToString());
            if (user == null)
            {
                Alerts.Danger("User not found after login. Please contact support.");
                return Page();
            }

            if (user.ShouldChangePasswordOnNextLogin)
            {
                return await RedirectToChangePasswordAsync(user);
            }

            // If ReturnUrl is set (e.g., from OIDC/OAuth flow), respect it
            // so the user is redirected back to the originally requested page
            if (!string.IsNullOrWhiteSpace(ReturnUrl) && result != null)
            {
                return result;
            }

            return await RedirectAfterLoginAsync(tenant);
        }
    }

    private void SyncBaseLoginInput()
    {
        var baseProperty = typeof(LoginModel).GetProperty("LoginInput");
        var baseLoginInput = baseProperty?.GetValue(this);

        if (baseLoginInput == null)
        {
            var loginInputType = baseProperty?.PropertyType;
            if (loginInputType != null)
            {
                baseLoginInput = Activator.CreateInstance(loginInputType);
                baseProperty?.SetValue(this, baseLoginInput);
            }
        }

        if (baseLoginInput != null)
        {
            var baseType = baseLoginInput.GetType();
            baseType.GetProperty("UserNameOrEmailAddress")?.SetValue(baseLoginInput, LoginInput.UserNameOrEmailAddress);
            baseType.GetProperty("Password")?.SetValue(baseLoginInput, LoginInput.Password);
            baseType.GetProperty("RememberMe")?.SetValue(baseLoginInput, LoginInput.RememberMe);
        }
    }

    private static bool IsLoginFailed(IActionResult? result)
        => result == null || (result is RedirectToPageResult redirect && redirect.PageName == "./Login");

    private IActionResult AddErrorAndReturnPage(string key, string message)
    {
        ModelState.AddModelError(key, message);
        return Page();
    }

    private async Task<IActionResult> RedirectToChangePasswordAsync(Volo.Abp.Identity.IdentityUser user)
    {
        var resetToken = await UserManager.GeneratePasswordResetTokenAsync(user);
        return RedirectToPage("./ChangePassword", new
        {
            userId = user.Id,
            resetToken,
            tenantId = CurrentTenant.Id
        });
    }

    private async Task<IActionResult> RedirectAfterLoginAsync(Tenant? tenant)
    {
        var angularUrl = _configuration["App:AngularUrl"]
            ?? throw new InvalidOperationException("App:AngularUrl is not configured.");

        var baseUrl = angularUrl.TrimEnd('/');

        if (tenant?.Id == null)
        {
            return Redirect($"{baseUrl}/dashboard");
        }

        var departments = await _fireDepartmentRepository.GetCountAsync();
        return Redirect(departments > 0 ? $"{baseUrl}/dashboard" : $"{baseUrl}/onboarding");
    }

    protected virtual async Task<Volo.Abp.Identity.IdentityUser?> FindUserAsync(string usernameOrEmail)
    {
        return await UserManager.FindByNameAsync(usernameOrEmail)
               ?? await UserManager.FindByEmailAsync(usernameOrEmail);
    }
}

}

Logout

using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Volo.Abp.Account.Public.Web.Pages.Account; using Volo.Abp.DependencyInjection; using Volo.Saas.Tenants;

namespace Hon.IFS.DepartmentHost.Pages.Account { [ExposeServices(typeof(CustomLogoutModel), typeof(LogoutModel))] public class CustomLogoutModel : LogoutModel { private readonly ITenantRepository _tenantRepository;

    public CustomLogoutModel(ITenantRepository tenantRepository)
    {
        _tenantRepository = tenantRepository;
    }

    public override async Task<IActionResult> OnGetAsync()
    {
        // Resolved before base signs out, while the cookie still carries the tenant.
        var tenantHostName = await GetTenantHostNameAsync(CurrentTenant.Id ?? CurrentUser.TenantId);

        var result = await base.OnGetAsync();

        if (!string.IsNullOrWhiteSpace(tenantHostName) &&
            result is RedirectToPageResult redirect &&
            redirect.PageName?.EndsWith("Login", StringComparison.OrdinalIgnoreCase) == true)
        {
            return RedirectToPage("/Account/Login", new { tenantName = tenantHostName });
        }

        return result;
    }

    private async Task<string?> GetTenantHostNameAsync(Guid? tenantId)
    {
        if (!tenantId.HasValue)
        {
            return null;
        }

        var tenant = await _tenantRepository.FindByIdAsync(tenantId.Value);
        return tenant?.ExtraProperties.TryGetValue("tenantHostName", out var tenantHostName) == true
            ? tenantHostName?.ToString()
            : null;
    }
}

}

Abp text-templating language Package 'Scriban' 6.3.0 is troughing severity vulnerability Current Abp version 10.0.0 While updating Scriban to 7 this package is causing error on sending Emails

I have tried to reproduce the issue in New fresh Abp Template project I did not found any issue. I have done the customizations Change password Screen I am adding Latest code of Change password model Please verify does it need any change

using Hon.IFS.SiteHost; using Hon.IFS.SiteHost.EmailNotifications; using Hon.IFS.SiteHost.Pages.Account; using Hon.IFS.SiteHost.StringEncryption; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Serilog.Core; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Reflection; using System.Threading.Tasks; using Volo.Abp.Account.Public.Web.Pages.Account; using Volo.Abp.Auditing; using Volo.Abp.Identity; using Volo.Abp.Security.Encryption; using Volo.Abp.Validation; using Volo.Saas.Tenants; using static Volo.Abp.Identity.Settings.IdentitySettingNames; using static Volo.Saas.Host.SaasHostPermissions; using IdentityUser = Volo.Abp.Identity.IdentityUser;

namespace Volo.Abp.Account.Web.Pages.Account;

public class ChangePasswordModel : AccountPageModel { private readonly SignInManager<IdentityUser> _signInManager; private readonly UserManager<IdentityUser> _userManager; public IIdentityUserRepository _identityUserRepository; private readonly IConfiguration _configuration; private readonly IEmailNotificationAppService? _emailService; private readonly ITenantRepository _tenantRepository; private readonly ILogger<ChangePasswordModel> _logger;

[BindProperty]
[Required(ErrorMessage = "Current password is required.")]
[DataType(DataType.Password)]
public string CurrentPassword { get; set; } = null!;

[BindProperty]
[Required(ErrorMessage = "New password is required.")]
[DataType(DataType.Password)]
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPasswordLength))]
public string NewPassword { get; set; } = null!;

[Required]
[HiddenInput]
[BindProperty(SupportsGet = true)]
public Guid UserId { get; set; }

[Required]
[HiddenInput]
[BindProperty(SupportsGet = true)]
public string TenantId { get; set; } = null!;

[BindProperty]
[Required(ErrorMessage = "Confirm new password is required.")]
[DataType(DataType.Password)]
[Compare("NewPassword", ErrorMessage = "Passwords do not match.")]
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPasswordLength))]
public string ConfirmNewPassword { get; set; } = null!;

[HiddenInput]
[BindProperty(SupportsGet = true)]
public string ReturnUrl { get; set; } = null!;

[HiddenInput]
[BindProperty(SupportsGet = true)]
public string ReturnUrlHash { get; set; } = null!;

[BindProperty(SupportsGet = false)]
public string? TimezoneId { get; set; }

public ChangePasswordModel(
    UserManager&lt;IdentityUser&gt; userManager,
    SignInManager&lt;IdentityUser&gt; signInManager, IIdentityUserRepository identityUserRepository,
    IConfiguration configuration, ITenantRepository tenantRepository, IEmailNotificationAppService emailService, ILogger&lt;ChangePasswordModel&gt; logger)
{
    _userManager = userManager;
    _signInManager = signInManager;
    _identityUserRepository = identityUserRepository;
    _configuration = configuration;
    _tenantRepository = tenantRepository;
    _emailService = emailService;
    _logger = logger;
    
}

public IActionResult OnGet([FromQuery] Guid userId, [FromQuery] Guid tenantId)
{
    UserId = userId;

    return Page();
}

public async Task&lt;IActionResult&gt; OnPostAsync()
{
 
    using (CurrentTenant.Change(new Guid(TenantId)))
    {
        var user = await _identityUserRepository.FindAsync(UserId);
        if (user == null)
        {
            throw new AbpException("User not found.");
        }

        if (string.IsNullOrWhiteSpace(NewPassword))
        {
            ModelState.AddModelError(nameof(NewPassword), "Password cannot be empty.");
            return Page();
        }

        if (string.IsNullOrWhiteSpace(ConfirmNewPassword))
        {
            ModelState.AddModelError(nameof(ConfirmNewPassword), "Confirm password cannot be empty.");
            return Page();
        }

        if (!string.IsNullOrWhiteSpace(NewPassword) && !string.IsNullOrWhiteSpace(ConfirmNewPassword))
        {
            if (NewPassword.Trim() != ConfirmNewPassword.Trim())
            {
                ModelState.AddModelError(
                    nameof(ConfirmNewPassword),
                    L["Password and Confirm password do not match."]
                );
                return Page();
            }
            
        }

        var changePasswordResult = await _userManager.ChangePasswordAsync(user, CurrentPassword, NewPassword);

        // Only proceed if password change was successful
        if (changePasswordResult.Succeeded)
        {
            _logger.LogInformation("Password successfully changed for user {Username} (ID: {UserId})", user.UserName, user.Id);

            // Update user to not require password change on next login
            user.SetShouldChangePasswordOnNextLogin(false);
            await UserManager.UpdateAsync(user);

            // Get tenant information for email
            var tenant = await _tenantRepository.FindAsync(new Guid(TenantId));
            if (tenant == null)
            {
                _logger.LogWarning("Tenant not found for TenantId: {TenantId}", TenantId);
                return BadRequest("Tenant not found");
            }

            var tenantHostName = tenant.ExtraProperties
                                .FirstOrDefault(kvp => kvp.Key.Equals("tenantHostName", StringComparison.OrdinalIgnoreCase))
                                .Value?.ToString();

            // Send confirmation email after successful password change
            _logger.LogInformation("Preparing to send password change confirmation email to {Email} for user {Username}", user.Email, user.UserName);

            var emailModel = new EmailNotificationDto
            {
                MailTo = user.Email,
                MailSubject = EmailNotificationConstants.EmailTypes.ChangePassword,
                IsBodyHtml = true,
                Username = user.UserName,
                Name = user.Name,
                TenantName = tenant.Name,
                TimezoneId = TimezoneId  // Use timezone from client
            };

            try
            {
                if (_emailService == null)
                {
                    _logger.LogWarning("Email service is not available. Skipping email notification.");
                }
                else
                {
                    _logger.LogDebug("Calling email service to send notification...");
                    var sendTask = _emailService.EmailNotification(emailModel);

                    if (await Task.WhenAny(sendTask, Task.Delay(TimeSpan.FromSeconds(30))) == sendTask)
                    {
                        await sendTask; 
                        _logger.LogInformation("Password change email successfully sent to {Email}", user.Email);
                    }
                    else
                    {
                        _logger.LogWarning("Email sending timed out after 30 seconds for {Email}", user.Email);
                    }
                }
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Failed to send password change email to {Email}. Error: {ErrorMessage}", user.Email, ex.Message);
            }

            // Refresh the sign-in
            await _signInManager.RefreshSignInAsync(user);

            return RedirectToPage("/Account/Login", new
            {
                tenantName = tenantHostName,
                returnUrl = ReturnUrl,
                returnUrlHash = ReturnUrlHash
            });
        }

        // Password change failed - add errors to ModelState
        _logger.LogWarning("Password change failed for user {Username} (ID: {UserId})", user.UserName, user.Id);
        foreach (var error in changePasswordResult.Errors)
        {
            _logger.LogDebug("Password change error: {ErrorCode} - {ErrorDescription}", error.Code, error.Description);
            ModelState.AddModelError(string.Empty, error.Description);
        }
        
        return Page();
    }
}

}

Please refer the Abp Setting Table in db

Still it is asking for the 6 characters input for the Tenant Setting as well updated to 8

my Abp Version 10 , Angular

I have Updated the Host setting to 8 Characters Password Input but it still reflecting 6 Characters in model validation

Hi,

Currently I integrated Azure service Bus integration by using AbpEventBusAzureModule for Notification(custom) module. Due to this my user/tenant creation has been stopped working and when I was looking at the Azure service Bus topics, I see user/tenant message creation is happening in Azure and seems like data seeding process from ABP is happening in memory stream and it does not find those user tenant creation message in memory stream. What is the resolution?

we have to redirect to Angular UI / SPA scenario, and it returns 302 and empty data. but after hard refresh it returns the data and response code is 200

Showing 1 to 10 of 61 entries
Boost Your Development
ABP Live Training
Packages
See Trainings
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v10.8.0-preview. Updated on September 16, 2026, 14:50
1
ABP Assistant
🔐 You need to be logged in to use the chatbot. Please log in first.