Activities of "anurag.tyagi"

okay I upgraded to @volosoft/abp.ng.theme.lepton-x": "3.1.4" and the frontend works wonderfully!

Issue was that by doingabp update I got the wrong version for @volosoft/abp.ng.theme.lepton-x and then I created a new project with 8.1.4 and I saw that the @volosoft/abp.ng.theme.lepton-x version there was 3.1.4.

thanks for the help!

hi, thanks a lot for your response and support Unfortunately when I moved up the lepton-x version to ~3.1.0 I now get a new couple of errors:

Thanks

public class MyappHttpApiHostModule : AbpModule
{
    public override void PreConfigureServices(ServiceConfigurationContext context)
    {
        var hostingEnvironment = context.Services.GetHostingEnvironment();
        var configuration = context.Services.GetConfiguration();

        PreConfigure<OpenIddictBuilder>(builder =>
        {
            builder.AddValidation(options =>
            {
                options.AddAudiences("Myapp");
                options.UseLocalServer();
                options.UseAspNetCore();
            });
        });
        
        PreConfigure<AbpOpenIddictWildcardDomainOptions>(options =>
        {
            options.EnableWildcardDomainSupport = true;
            options.WildcardDomainsFormat.Add("https://{0}.mybackend.com/");
            options.WildcardDomainsFormat.Add("https://{0}.mybackend.com/signin-oidc");
            options.WildcardDomainsFormat.Add("https://{0}.mybackend.com/signout-callback-oidc");
            options.WildcardDomainsFormat.Add("https://{0}.mybackend.com/authentication/login-callback");
            options.WildcardDomainsFormat.Add("https://{0}.myfrontend.com/");
        });

#if DEBUG
        PreConfigure<OpenIddictServerBuilder>(options => { options.UseAspNetCore().DisableTransportSecurityRequirement(); });
#endif
        
        if (!hostingEnvironment.IsDevelopment())
        {
            PreConfigure<AbpOpenIddictAspNetCoreOptions>(options =>
            {
                options.AddDevelopmentEncryptionAndSigningCertificate = false;
            });

            PreConfigure<OpenIddictServerBuilder>(builder =>
            {
                builder.AddSigningCertificate(GetSigningCertificate(hostingEnvironment, configuration));
                builder.AddEncryptionCertificate(GetSigningCertificate(hostingEnvironment, configuration));
            });
        }
        
        PreConfigure<IdentityBuilder>(identityBuilder=>{identityBuilder.AddSignInManager<MyAbpSignInManager>();});
    }

    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        var configuration = context.Services.GetConfiguration();
        var hostingEnvironment = context.Services.GetHostingEnvironment();

        if (!Convert.ToBoolean(configuration["App:DisablePII"]))
        {
            Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true;
        }

        Configure<AbpTenantResolveOptions>(options =>
        {
            options.AddDomainTenantResolver(configuration["App:Domain"]!);
        });
        
        if (!Convert.ToBoolean(configuration["AuthServer:RequireHttpsMetadata"]))
        {
            Configure<OpenIddictServerAspNetCoreOptions>(options =>
            {
                options.DisableTransportSecurityRequirement = true;
            });
        }
        
        Configure<AbpTenantResolveOptions>(options =>
        {
            options.AddDomainTenantResolver(configuration["App:TenantResolverDomain"]!);
        });
        
        
        context.Services.AddHangfire(config =>
        {
            config.UseStorage(new MySqlStorage(configuration.GetConnectionString("Default"), new MySqlStorageOptions
            {
            }));
        });

        ConfigureAuthentication(context);
        ConfigureUrls(configuration);
        ConfigureBundles();
        ConfigureConventionalControllers();
        ConfigureImpersonation(context, configuration);
        ConfigureSwagger(context, configuration);
        ConfigureVirtualFileSystem(context);
        ConfigureCors(context, configuration);
        ConfigureExternalProviders(context);
        ConfigureHealthChecks(context);
        ConfigureTheme();
        
        context.Services.AddSignalR(hubOptions =>
        {
            hubOptions.MaximumParallelInvocationsPerClient = 5;
        });
    }

    private void ConfigureTheme()
    {
        Configure<LeptonXThemeOptions>(options =>
        {
            options.DefaultStyle = LeptonXStyleNames.System;
        });
    }

    private void ConfigureAuthentication(ServiceConfigurationContext context)
    {
        context.Services.ForwardIdentityAuthenticationForBearer(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme);
    }

    private void ConfigureHealthChecks(ServiceConfigurationContext context)
    {
        context.Services.AddMyappHealthChecks();
    }

    private void ConfigureUrls(IConfiguration configuration)
    {
        Configure<AppUrlOptions>(options =>
        {
            options.Applications["MVC"].RootUrl = configuration["App:SelfUrl"];
            options.Applications["Angular"].RootUrl = configuration["App:AngularUrl"];
            options.Applications["Angular"].Urls[AccountUrlNames.PasswordReset] = "account/reset-password";
            options.Applications["Angular"].Urls[AccountUrlNames.EmailConfirmation] = "account/email-confirmation";
            options.RedirectAllowedUrls.AddRange(configuration["App:RedirectAllowedUrls"]?.Split(',') ?? Array.Empty<string>());
        });
    }

    private void ConfigureBundles()
    {
        Configure<AbpBundlingOptions>(options =>
        {
            options.StyleBundles.Configure(
                LeptonXThemeBundles.Styles.Global,
                bundle =>
                {
                    bundle.AddFiles("/global-styles.css");
                }
            );
        });
    }


    private void ConfigureVirtualFileSystem(ServiceConfigurationContext context)
    {
        var hostingEnvironment = context.Services.GetHostingEnvironment();

        if (hostingEnvironment.IsDevelopment())
        {
            Configure<AbpVirtualFileSystemOptions>(options =>
            {
                options.FileSets.ReplaceEmbeddedByPhysical<MyappDomainSharedModule>(Path.Combine(hostingEnvironment.ContentRootPath, $"..{Path.DirectorySeparatorChar}Pm.Myapp.Domain.Shared"));
                options.FileSets.ReplaceEmbeddedByPhysical<MyappDomainModule>(Path.Combine(hostingEnvironment.ContentRootPath, $"..{Path.DirectorySeparatorChar}Pm.Myapp.Domain"));
                options.FileSets.ReplaceEmbeddedByPhysical<MyappApplicationContractsModule>(Path.Combine(hostingEnvironment.ContentRootPath, $"..{Path.DirectorySeparatorChar}Pm.Myapp.Application.Contracts"));
                options.FileSets.ReplaceEmbeddedByPhysical<MyappApplicationModule>(Path.Combine(hostingEnvironment.ContentRootPath, $"..{Path.DirectorySeparatorChar}Pm.Myapp.Application"));
            });
        }
    }

    private void ConfigureConventionalControllers()
    {
        Configure<AbpAspNetCoreMvcOptions>(options =>
        {
            options.ConventionalControllers.Create(typeof(MyappApplicationModule).Assembly);
        });
    }

    private static void ConfigureSwagger(ServiceConfigurationContext context, IConfiguration configuration)
    {
        context.Services.AddAbpSwaggerGenWithOAuth(
            configuration["AuthServer:Authority"]!,
            new Dictionary<string, string>
            {
                    {"Myapp", "Myapp API"}
            },
            options =>
            {
                options.SwaggerDoc("v1", new OpenApiInfo { Title = "Myapp API", Version = "v1" });
                options.DocInclusionPredicate((docName, description) => true);
                options.CustomSchemaIds(type => type.FullName);
            });
    }

    private void ConfigureCors(ServiceConfigurationContext context, IConfiguration configuration)
    {
        context.Services.AddCors(options =>
        {
            options.AddDefaultPolicy(builder =>
            {
                builder
                    .WithOrigins(
                        configuration["App:CorsOrigins"]?
                            .Split(",", StringSplitOptions.RemoveEmptyEntries)
                            .Select(o => o.Trim().RemovePostFix("/"))
                            .ToArray() ?? Array.Empty<string>()
                    )
                    .WithAbpExposedHeaders()
                    .SetIsOriginAllowedToAllowWildcardSubdomains()
                    .AllowAnyHeader()
                    .AllowAnyMethod()
                    .AllowCredentials();
            });
        });
    }

    private void ConfigureExternalProviders(ServiceConfigurationContext context)
    {
        var configuration = context.Services.GetConfiguration();
        context.Services.AddAuthentication()
            .AddJwtBearer(options =>
            {
                options.Authority = configuration["AuthServer:Authority"];
                options.RequireHttpsMetadata = Convert.ToBoolean(configuration["AuthServer:RequireHttpsMetadata"]);
                options.Audience = "Myapp";
        
                options.TokenValidationParameters.IssuerValidator = TokenWildcardIssuerValidator.IssuerValidator;
                options.TokenValidationParameters.ValidIssuers = new[]
                {
                    configuration["App:CommonUrl"]
                };
            })
            .AddGoogle(GoogleDefaults.AuthenticationScheme, _ => { })
            .WithDynamicOptions<GoogleOptions, GoogleHandler>(
                GoogleDefaults.AuthenticationScheme,
                options =>
                {
                    options.WithProperty(x => x.ClientId);
                    options.WithProperty(x => x.ClientSecret, isSecret: true);
                }
            )
            .AddMicrosoftAccount(MicrosoftAccountDefaults.AuthenticationScheme, options =>
            {
                    //Personal Microsoft accounts as an example.
                    options.AuthorizationEndpoint = "https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize";
                options.TokenEndpoint = "https://login.microsoftonline.com/consumers/oauth2/v2.0/token";
            })
            .WithDynamicOptions<MicrosoftAccountOptions, MicrosoftAccountHandler>(
                MicrosoftAccountDefaults.AuthenticationScheme,
                options =>
                {
                    options.WithProperty(x => x.ClientId);
                    options.WithProperty(x => x.ClientSecret, isSecret: true);
                }
            )
            .AddTwitter(TwitterDefaults.AuthenticationScheme, options => options.RetrieveUserDetails = true)
            .WithDynamicOptions<TwitterOptions, TwitterHandler>(
                TwitterDefaults.AuthenticationScheme,
                options =>
                {
                    options.WithProperty(x => x.ConsumerKey);
                    options.WithProperty(x => x.ConsumerSecret, isSecret: true);
                }
            );
    }

    private void ConfigureImpersonation(ServiceConfigurationContext context, IConfiguration configuration)
    {
        context.Services.Configure<AbpAccountOptions>(options =>
        {
            options.TenantAdminUserName = "admin";
            options.ImpersonationTenantPermission = SaasHostPermissions.Tenants.Impersonation;
            options.ImpersonationUserPermission = IdentityPermissions.Users.Impersonation;
        });
    }

    public override void OnApplicationInitialization(ApplicationInitializationContext context)
    {
        var app = context.GetApplicationBuilder();
        var env = context.GetEnvironment();

        var forwardOptions = new ForwardedHeadersOptions {
            ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost, RequireHeaderSymmetry = false
        };

        forwardOptions.KnownNetworks.Clear();
        forwardOptions.KnownProxies.Clear();

    // ref: https://github.com/aspnet/Docs/issues/2384
        app.UseForwardedHeaders(forwardOptions);

        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseAbpRequestLocalization();

        if (!env.IsDevelopment())
        {
            app.UseErrorPage();
        }

        app.UseAbpSecurityHeaders();
        app.UseStaticFiles();
        app.UseRouting();
        app.UseCors();
        app.UseAuthentication();
        app.UseAbpOpenIddictValidation();

        if (MultiTenancyConsts.IsEnabled)
        {
            app.UseMultiTenancy();
        }

        app.UseUnitOfWork();

        app.UseAuthorization();



        if (!env.IsDevelopment())
        {
            app.Use(async (httpContext, next) =>
            {
                if (httpContext.Request.Path.StartsWithSegments("/swagger") && !httpContext.User.IsInRole("admin"))
                {
                    httpContext.Response.StatusCode = StatusCodes.Status403Forbidden;
                    return;
                }

                await next.Invoke();
            });
        }


        app.UseSwagger();
        app.UseAbpSwaggerUI(options =>
        {
            options.SwaggerEndpoint("/swagger/v1/swagger.json", "Myapp API");

            var configuration = context.ServiceProvider.GetRequiredService<IConfiguration>();
            options.OAuthClientId(configuration["AuthServer:SwaggerClientId"]);
        });
        
        RecurringJob.AddOrUpdate<ResetMonthlyQuestionsCountJob>("reset-monthly-questions",
            job => job.ExecuteAsync(new ResetMonthlyQuestionsCountArgs()), Cron.Monthly);
        
        
        app.UseAuditing();
        app.UseAbpSerilogEnrichers();
        app.UseConfiguredEndpoints();
    }

    private X509Certificate2 GetSigningCertificate(IWebHostEnvironment hostingEnv, IConfiguration configuration)
    {
        var fileName = "authserver.pfx";
        var passPhrase = "f123a4f5-678a-4b4d-a2c4-3f62b6033d4d";
        var file = Path.Combine(hostingEnv.ContentRootPath, fileName);

        if (!File.Exists(file))
        {
            throw new FileNotFoundException($"Signing Certificate couldn't found: {file}");
        }

        return new X509Certificate2(file, passPhrase);
    }
}

Hi,

Can you please tell me which file specifically you need?

Hi, please see the logs below:

20:03:07 [18:03:07 INF] Request finished HTTP/1.1 GET https://mybackend.com/api/saas/tenants/3a114414-b352-6751-1023-c40755a4a1bb/image - 500 null application/json; charset=utf-8 194.2802ms 20/06/2024
20:03:07 [18:03:07 INF] Request starting HTTP/1.1 GET http://mybackend.com/connect/authorize?response_type=code&client_id=Myapp_App&state=MFFzdn5-NENLRkFKTlZOLlZuRDhNVFdiUC4zVmRlMC1UX3RxV00zWkVvSk05%3B%252Ffree-inquiry&redirect_uri=https%3A%2F%2Fmyfrontend.com&scope=openid%20offline_access%20Myapp&code_challenge=0bxO4MWionxUKMExy-9tJkcC-sN8WJhDjQts2c6LHbo&code_challenge_method=S256&nonce=MFFzdn5-NENLRkFKTlZOLlZuRDhNVFdiUC4zVmRlMC1UX3RxV00zWkVvSk05&culture=en&ui-culture=en&returnUrl=%2Ffree-inquiry - null null 20/06/2024
20:03:07 [18:03:07 INF] The request URI matched a server endpoint: Authorization. 20/06/2024
20:03:07 [18:03:07 INF] The authorization request was successfully extracted: { 20/06/2024
20:03:07   "response_type": "code", 20/06/2024
20:03:07   "client_id": "Myapp_App", 20/06/2024
20:03:07   "state": "MFFzdn5-NENLRkFKTlZOLlZuRDhNVFdiUC4zVmRlMC1UX3RxV00zWkVvSk05;%2Ffree-inquiry", 20/06/2024
20:03:07   "redirect_uri": "https://myfrontend.com", 20/06/2024
20:03:07   "scope": "openid offline_access Myapp", 20/06/2024
20:03:07   "code_challenge": "0bxO4MWionxUKMExy-9tJkcC-sN8WJhDjQts2c6LHbo", 20/06/2024
20:03:07   "code_challenge_method": "S256", 20/06/2024
20:03:07   "nonce": "MFFzdn5-NENLRkFKTlZOLlZuRDhNVFdiUC4zVmRlMC1UX3RxV00zWkVvSk05", 20/06/2024
20:03:07   "culture": "en", 20/06/2024
20:03:07   "ui-culture": "en", 20/06/2024
20:03:07   "returnUrl": "/free-inquiry" 20/06/2024
20:03:07 }. 20/06/2024
20:03:08 [18:03:08 INF] Client validation failed because 'https://myfrontend.com' was not a valid redirect_uri for Myapp_App. 20/06/2024
20:03:08 [18:03:08 INF] The authorization request was rejected because the redirect_uri was invalid: 'https://myfrontend.com'. 20/06/2024
20:03:08 [18:03:08 INF] Request finished HTTP/1.1 GET https://mybackend.com/connect/authorize?response_type=code&client_id=Myapp_App&state=MFFzdn5-NENLRkFKTlZOLlZuRDhNVFdiUC4zVmRlMC1UX3RxV00zWkVvSk05%3B%252Ffree-inquiry&redirect_uri=https%3A%2F%2Fmyfrontend.com&scope=openid%20offline_access%20Myapp&code_challenge=0bxO4MWionxUKMExy-9tJkcC-sN8WJhDjQts2c6LHbo&code_challenge_method=S256&nonce=MFFzdn5-NENLRkFKTlZOLlZuRDhNVFdiUC4zVmRlMC1UX3RxV00zWkVvSk05&culture=en&ui-culture=en&returnUrl=%2Ffree-inquiry - 302 0 null 269.0968ms 20/06/2024

FYI, I have substituted the real domains with:

mybackend.com representing our backend domain myfrontend.com in place of the actual frontend domain

Hi,

Thanks for your reply.

I have already implemented the provided configuration in our project at the time of creating this ticket. However, this solution does not resolve the issue.

Could you please provide further assistance or an alternative approach to dynamically handle tenant-specific redirect URIs?

Thanks in advance

Hi,

Is it OK to disable the issuer validation on production?

also, without SetIssuer the login works just fine on all other tenants (tenant1.mydomain.com, tenant2.mydomain.com) but not for mydomain.com, it should work the same way for all URLs, right?

Can I somehow remove SetIssuer and find a solution to the problem described above? or the only way is to bring it back and disable issuer validation?

Many thanks for considering my request.

Hi,

Thanks for your response.

There are no backend logs on this issue, on the frontend console I see:

invalid issuer in discovery document expected: https://tenant.api.mydomain.com current: https://api.mydomain.com/

and when I check the https://api.mydomain.com/.well-known/openid-configuration endpoint, I see the following configuration:

{
  "issuer": "https://api.mydomain.com",
  "authorization_endpoint": "https://tenant.api.mydomain.com/connect/authorize",
  "token_endpoint": "https://tenant.api.mydomain.com/connect/token",
  "introspection_endpoint": "https://tenant.api.mydomain.com/connect/introspect",
  "end_session_endpoint": "https://tenant.api.mydomain.com/connect/logout"
}

FYI, this issue is not reproducible locally as soon as SetIssuer is in the if (!hostingEnvironment.IsDevelopment()) block.

Hi, thanks for the help. It got us quite far. I believe we only have one last issue before subdomains work for us:

we have a frontend angular method that establishes a connection to the backend but it reads the backend url from the appsettings and hence also contains the {0}, which is not resolved and not recognized by the backend.

private createConnection(): void {
    this.hubConnection = new HubConnectionBuilder()
      .withUrl(environment.apis.default.url + '/hubroute', { accessTokenFactory: () => localStorage.getItem('access_token') })
      .withAutomaticReconnect()
      .build();
  }

is there an out of the box way to get the resolved tenant url in the frontend?

I added the code from that example to my project (v8.0.2) and deployed the version to an environment with an actual domain/subdomain, but it did not work. It has the issue that I described in the initial question.

can you maybe give some hints and ideas, on what could I check, and where I could have an issue?

Zobrazeno od 1 do 10 z celkem 14 záznamů
Made with ❤️ on ABP v8.2.0-preview Updated on března 25, 2024, 15:11