Activities of "Hon-Tre_IFS"

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

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?

Hi,

We are currently using single layer modular monolithic web app and api.

Issue: After successful login, the page redirects to next page but does not load any data on the web page and in the network tab the response code is 302. But after hard refresh, the page shows the actual data and response code returns 200.

How do you resolve this issue?

This is the code i have added module file

// Configure password requirements Configure<IdentityOptions>(options => { options.Password.RequiredLength = 8; });

I have 3 topics and 3 subscriptions. how to configure in appsettings json. i want to configure in existing below format. { "Azure": { "ServiceBus": { "Connections": { "Default": { "ConnectionString": "Endpoint=sb://sb-my-app.servicebus.windows.net/;SharedAccessKeyName={{Policy Name}};SharedAccessKey={};EntityPath=marketing-consent" } } }, "EventBus": { "ConnectionName": "Default", "SubscriberName": "MySubscriberName", "TopicName": "MyTopicName" } } }

My openaiconfiguration api is returning all the permissions as true but in the database and in the AbpPermissionGrants table the permission does not exist. This is causing the issue in my Angular front end to hide or show a button

Host terminated unexpectedly! Volo.Abp.AbpInitializationException: An error occurred during the initialize Volo.Abp.Modularity.OnApplicationInitializationModuleLifecycleContributor phase of the module Volo.Abp.EventBus.Azure.AbpEventBusAzureModule, Volo.Abp.EventBus.Azure, Version=10.0.0.0, Culture=neutral, PublicKeyToken=null: Value cannot be null. (Parameter 'topicName'). See the inner exception for details. ---> System.ArgumentNullException: Value cannot be null. (Parameter 'topicName') at Volo.Abp.Check.NotNull[T](T value, String parameterName) at Volo.Abp.AzureServiceBus.AzureServiceBusMessageConsumer.Initialize(String topicName, String subscriptionName, String connectionName) at Volo.Abp.AzureServiceBus.AzureServiceBusMessageConsumerFactory.CreateMessageConsumer(String topicName, String subscriptionName, String connectionName) at Volo.Abp.EventBus.Azure.AzureDistributedEventBus.Initialize() at Volo.Abp.EventBus.Azure.AbpEventBusAzureModule.OnApplicationInitialization(ApplicationInitializationContext context) at Volo.Abp.Modularity.AbpModule.OnApplicationInitializationAsync(ApplicationInitializationContext context) at Volo.Abp.Modularity.OnApplicationInitializationModuleLifecycleContributor.InitializeAsync(ApplicationInitializationContext context, IAbpModule module) at Volo.Abp.Modularity.ModuleManager.InitializeModulesAsync(ApplicationInitializationContext context) --- End of inner exception stack trace --- at Volo.Abp.Modularity.ModuleManager.InitializeModulesAsync(ApplicationInitializationContext context) at Volo.Abp.AbpApplicationBase.InitializeModulesAsync() at Volo.Abp.AbpApplicationWithExternalServiceProvider.InitializeAsync(IServiceProvider serviceProvider) at Microsoft.AspNetCore.Builder.AbpApplicationBuilderExtensions.InitializeApplicationAsync(IApplicationBuilder app)

Hi,

We have 3 modular APIs (1 API,2 API,3 API) and we are trying to call (through remote service call) 2 modulars layered api (2 and 3 API) from single layer modular api (1 API). When all these modular APIs were developed in ABP 9, it was working fine. After upgrading to all the modular API from 9 to 10, we are getting 403 forbidden issue when calling the remote service.

The token is generating properly and infact the 2 modular layered api also returning the data but when we try to call from modular single layer api, it is throwing forbidden error. We looked at all the permissions and it looks fine.

Please help us to resolve the issue

Showing 1 to 10 of 23 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.