BEST
DEALS
OF THE
YEAR!
SAVE UP TO $3,000
24 NOV
1 DEC
00 Days
00 Hrs
00 Min
00 Sec

Activities of "cstobler"

Thanks for the help on this. I was able to implement the custom Stripe gateway and pass the email in through an extra property.

Ok, that makes sense. I am not using any tenant resolvers like subdomain tenant resolver, so I decided to just set the tenant cookie when logging in. This seems to have resolved my issue.

Thanks for your help.

Ok, I made a lot of progress towards figuring out why this is occurring, but I'm not sure how to fix it with my current implementation (will explain below)

First, I created the fresh template solution to attempt to recreate the problem, and of course, it worked in the new project. However, I didn't have anything that was intentionally blocking the ChangePassword page, so I started testing a bunch of different configurations to see if I could get it to work.

I found out that in my existing project, using the default login model rather than my overridden login model, it worked as expected. I further tracked this down to the tenant switcher section. In my custom login page, I removed the tenant switcher, instead opting to infer the tenant from the email entered and use that to switch to the corresponding tenant in the code. See my custom login model OnPostAsync:

public override async Task<IActionResult> OnPostAsync(string action)
{
    try
    {
        if (LoginInput.UserNameOrEmailAddress == "admin")
        {
            return await base.OnPostAsync(action);
        }

        Guid tenantId = await _multiTenancyAppService.GetTenantIdByEmailAsync(LoginInput.UserNameOrEmailAddress);
        if (tenantId == Guid.Empty)
        {
            ModelState.AddModelError(string.Empty, "Account not found. Please register if you do not have an account.");
            return Page();
        }

        using (CurrentTenant.Change(tenantId))
        {
            return await base.OnPostAsync(action);
        }
    }
    catch (UserFriendlyException ex)
    {
        ViewData["ErrorMessage"] = ex.Message;
        await base.OnGetAsync();
        return Page();
    }
}

That specific section:

using (CurrentTenant.Change(tenantId))
{
    return await base.OnPostAsync(action);
}

works normally to log the user in to their tenant without requiring them to switch tenants in the login page (I want my users to be as multitenancy-unaware as possible)

However, that doesn't work with the change password page. If I have the tenant null and log into a normal account, it will properly infer the tenant by the email, change tenant in code, and login. If I have the tenant null and attempt to log into an account that has ShouldChangePasswordOnNextLogin set to true, it kicks me back to login page. If I manually switch the tenant using the tenant switcher in the UI, then it works.

So here is the question: Is this a bug? Why does using (CurrentTenant.Change(tenantId)) work for logged in, but not for being redirected to the /ChangePassword page?

It will take me a little while to create a new project and add the necessary code to recreate this. I will try to get to this tomorrow.

Thank you for that. I attempted it, but it didn't work, so I tried removing all of my authorization code (so I can access host pages and am not locked out of anything by default) and tried to see if I can at least get to the ChangePassword page, but it still isn't working. At this point, I don't think there is anything on my end that is preventing this, though I could be wrong. The main thing that is different is that I am creating users through my own appservice:

public async Task UpsertUserAsync(Guid userId, string email, string? password, bool forceChangePassword)
{
    if (userId != Guid.Empty)
    {
        IdentityUserDto existingUser = await _identityUserAppService.GetAsync(userId);
        if (existingUser == null)
        {
            throw new UserFriendlyException("User not found.");
        }

        string username = existingUser.UserName == "admin" ? "admin" : email;

        IdentityUserUpdateDto updateDto = new IdentityUserUpdateDto
        {
            UserName = username,
            Email = email,
            ShouldChangePasswordOnNextLogin = forceChangePassword,
            IsActive = true,
            EmailConfirmed = true,
            PhoneNumberConfirmed = true,
            RoleNames = ["admin"]
        };
        await _identityUserAppService.UpdateAsync(userId, updateDto);
        return;
    }

    // Make sure email is unique
    bool isEmailUnique = await IsEmailUnique(email);
    if (!isEmailUnique)
    {
        throw new UserFriendlyException("Email address is already registered. Please use another email address.");
    }

    await _identityUserAppService.CreateAsync(new IdentityUserCreateDto
    {
        UserName = email,
        Email = email,
        Password = password,
        ShouldChangePasswordOnNextLogin = forceChangePassword,
        IsActive = true,
        EmailConfirmed = true,
        PhoneNumberConfirmed = true,
        RoleNames = ["admin"]
    });
}

Could there be something wrong with how I am creating the users here which might impact their ability to reach that page? There are some things I will likely need to resolve here like verifying emails, etc, but as a proof of concept, I think this should work, and it does when ShouldChangePasswordOnNextLogin is set to false, but not when it is set to true.

Let me know if I can provide any more info.

I have modified a fair amount at this point, so I am not able to get the "Administration" section to show up in Host, only when logged in. I have var administration = context.Menu.GetAdministration(); in my menu contributor, and I have commented out the code i provided above that authorizes the whole app. It allows me to access Host but I still cannot access Administration.

Here is my MenuContributor:

private static Task ConfigureMainMenuAsync(MenuConfigurationContext context)
{
    var l = context.GetLocalizer<ArmadaResource>();

    var currentUser = context.ServiceProvider.GetRequiredService<ICurrentUser>();
    var currentTenant = context.ServiceProvider.GetRequiredService<ICurrentTenant>();
    bool isHostAdmin = currentUser.IsAuthenticated && currentTenant.Id == null;

    //Home
    context.Menu.AddItem(
        new ApplicationMenuItem(
            ArmadaIOMenus.Home,
            l["Menu:Home"],
            "~/",
            icon: "fa fa-home",
            order: 1
        )
    );

    //HostDashboard
    context.Menu.AddItem(
        new ApplicationMenuItem(
            ArmadaIOMenus.HostDashboard,
            l["Menu:Dashboard"],
            "~/HostDashboard",
            icon: "fa fa-line-chart",
            order: 2
        ).RequirePermissions(ArmadaIOPermissions.Dashboard.Host)
    );

    //TenantDashboard
    //context.Menu.AddItem(
    //    new ApplicationMenuItem(
    //        ArmadaIOMenus.TenantDashboard,
    //        l["Menu:Dashboard"],
    //        "~/Dashboard",
    //        icon: "fa fa-line-chart",
    //        order: 2
    //    ).RequirePermissions(ArmadaIOPermissions.Dashboard.Tenant)
    //);

    context.Menu.AddItem(
        new ApplicationMenuItem(
            ArmadaIOMenus.Forms,
            l[ArmadaIOMenus.Forms],
            url: "/Forms",
            icon: "fas fa-file",
            order: 1
        )
    );

    context.Menu.AddItem(
        new ApplicationMenuItem(
            ArmadaIOMenus.Calendar,
            l[ArmadaIOMenus.Calendar],
            url: "/Calendar",
            icon: "fas fa-calendar-alt",
            order: 2
        )
    );

    context.Menu.AddItem(
        new ApplicationMenuItem(
            ArmadaIOMenus.Settings,
            l[ArmadaIOMenus.Settings],
            icon: "fas fa-gear",
            order: 2
        ).AddItem(
            new ApplicationMenuItem(
                ArmadaIOMenus.GeneralSettings,
                l[ArmadaIOMenus.GeneralSettings],
                url: "/Settings"
            )
        ).AddItem(
            new ApplicationMenuItem(
                ArmadaIOMenus.CompanyLocations,
                l[ArmadaIOMenus.CompanyLocations],
                url: "/Settings/CompanyLocations"
            )
        ).AddItem(
            new ApplicationMenuItem(
                ArmadaIOMenus.Drivers,
                l[ArmadaIOMenus.Drivers],
                url: "/Settings/Drivers"
            )
        ).AddItem(
            new ApplicationMenuItem(
                ArmadaIOMenus.LimitedItems,
                l[ArmadaIOMenus.LimitedItems],
                url: "/LimitedResources"
            )
        ).AddItem(
            new ApplicationMenuItem(
                ArmadaIOMenus.Payments,
                l[ArmadaIOMenus.Payments],
                url: "/Settings/Payments/StripeAccount"
            )
        ).AddItem(
            new ApplicationMenuItem(
                ArmadaIOMenus.Users,
                l[ArmadaIOMenus.Users],
                url: "/Settings/Users"
            )
        )
    );

    //Saas
    context.Menu.SetSubItemOrder(SaasHostMenuNames.GroupName, 3);



    //Administration
    var administration = context.Menu.GetAdministration();
    //if (!isHostAdmin)
    //{
    //    context.Menu.Items.Remove(administration);
    //}

    administration.Order = 6;

    //Administration->Identity
    administration.SetSubItemOrder(IdentityMenuNames.GroupName, 2);

    //Administration->OpenIddict
    administration.SetSubItemOrder(OpenIddictProMenus.GroupName, 3);

    //Administration->Language Management
    administration.SetSubItemOrder(LanguageManagementMenuNames.GroupName, 4);

    //Administration->Text Template Management
    administration.SetSubItemOrder(TextTemplateManagementMainMenuNames.GroupName, 5);

    //Administration->Audit Logs
    administration.SetSubItemOrder(AbpAuditLoggingMainMenuNames.GroupName, 6);

    //Administration->Settings
    administration.SetSubItemOrder(SettingManagementMenuNames.GroupName, 7);
    
    return Task.CompletedTask;
}

However, why do you think I should move away from my existing solution? I would at least like to be able to get the path to the resetpassword page so I can whitelist it as an option in case I need it.

Thanks for sending the source code.

Where do I put this file so that it overrides the default? And how do I actually populate that field in StripePaymentGateway with the registration email? My confusion stems from the fact that I am only interfacing with _subscriptionAppService.CreateSubscriptionAsync() to create the PaymentRequest, and then passing the PaymentRequest.Id to the GatewaySelection URL. If I extend StripePaymentGateway, I have no idea how to interface with this in a meaningful way.

Any guidance you can provide would be greatly appreciated.

Thanks, I emailed you to request the source code.

So I understand, how would that be implemented then? Would the gateway pull in the email from the registration into the payment request, since that is what is being passed to Stripe?

Can you explain this in more detail please? If self-registration is disabled on the host side, will it still allow self-registration for tenants? I need tenants to be able to sign up themselves, but I don't want anyone accessing the host side.

If this will work for my purposes, then is there some code I can implement that will set that rather than configuring it in the host settings in the app? Mainly so it is more streamlined when I need to deploy elsewhere.

Well, I set it up this way because I didn't want to utilize the "host" section of the app (as in, I wanted all users to be tenants), and I wanted to make sure that whenever someone was not logged in, they were redirected to the login page. This seemed like a good way to do it at the time since it forced login before users could access the app and it forced them to be tenants.

Is there a better way to go about this?

Showing 1 to 10 of 58 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.1.0-preview. Updated on November 11, 2025, 06:29
1
ABP Assistant
🔐 You need to be logged in to use the chatbot. Please log in first.