ConfirmationService.error(...) internally). If your server returns an error message that contains HTML, the safest approach is to override the HTTP error handler and force the message to be treated as plain text (i.e., never bind it as HTML / never pass a “safeHtml” object).HTTP_ERROR_HANDLER and sanitize/escape the message before showing it.// error.handler.ts
import { Injector } from '@angular/core';
import { HttpErrorResponse } from '@angular/common/http';
import { throwError } from 'rxjs';
import { ConfirmationService } from '@abp/ng.theme.shared';
// Minimal HTML escaping (renders tags as text)
function escapeHtml(input: any): string {
const s = String(input ?? '');
return s
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
export function handleHttpErrors(injector: Injector, httpError: HttpErrorResponse) {
// Example: specifically handle 400 validation errors
if (httpError?.status === 400 && httpError.error?.error?.validationErrors?.length) {
const confirmationService = injector.get(ConfirmationService);
const rawMessage = httpError.error.error.validationErrors[0].message;
const rawTitle = httpError.error.error.message;
return confirmationService.error(
escapeHtml(rawMessage), // prevent HTML injection
escapeHtml(rawTitle), // prevent HTML injection
{ hideCancelBtn: true, yesText: 'AbpAccount::Close' }
);
}
return throwError(() => httpError);
}
// app.module.ts
import { HTTP_ERROR_HANDLER } from '@abp/ng.theme.shared';
import { handleHttpErrors } from './error.handler';
@NgModule({
// ...
providers: [
{ provide: HTTP_ERROR_HANDLER, useValue: handleHttpErrors },
],
})
export class AppModule {}
This ensures that even if the backend returns <b>...</b> / <script>...</script> inside a validation message, it will be displayed as text, not executed/interpreted as HTML.
Links (verified):
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
(1) Is this expected/by-design?
In ABP’s EF Core save pipeline, modification audit properties are applied based on EntityEntry.State == Modified, not based on “did any non-audit business property change”. So if EF Core marks an entity as Modified (even due to fixup/DetectChanges side effects), ABP will set LastModificationTime / LastModifierId for that entry. There is no documented built-in rule that says “skip stamping if only audit/concurrency properties are dirty”.
(2) Supported/documented way to skip audit stamping when only audit/concurrency props are dirty?
ABP provides a property-level mechanism, but it is for a different scenario: you can mark a specific property with [DisableAuditing(UpdateModificationProps = false)] so changing that property won’t update modification audit props. This does not directly solve your case (where the entity is already Modified for reasons you don’t want).
Example from docs:
public class MyUser : Entity<Guid>
{
public string Name { get; set; }
[DisableAuditing(UpdateModificationProps = false, PublishEntityEvent = false)]
public string ReadCount { get; set; }
}
In your scenario (only audit props appear modified), there is no documented ABP option that automatically ignores/rolls back such “audit-only modifications” at the entity level.
(3) Could EF Core / ABP changes be making this more visible?
Yes, this symptom is consistent with EF Core deciding an entity is Modified due to relationship fixup / FK synchronization / DetectChanges behavior changes across versions. Once EF Core marks Patient as Modified, ABP will treat it as a real update and stamp audit props. ABP itself also has options around “navigation changes” for entity change events, which indicates the framework has had to account for EF Core navigation/FK change detection differences:
PublishEntityUpdatedEventWhenNavigationChanges (default true) and related behavior are documented under entity change events.(4) Correct extension point?
Given there’s no documented switch for “audit-only modified entries”, the most deterministic approach is exactly what you already implemented: intercept in your DbContext save pipeline (e.g., HandlePropertiesBeforeSave) and reset the entry to Unchanged when the only modified properties are audit/concurrency-stamp ones.
Overriding/replacing IAuditPropertySetter could work, but ABP doesn’t document it as a supported “filter-out audit-only modifications” feature, and without ABP source verification available here, I can’t provide a safe/accurate replacement registration snippet.
Validated references (closest official knobs relevant to this behavior):
DisableAuditing(UpdateModificationProps = false)): https://abp.io/docs/10.5/framework/infrastructure/audit-loggingPublishEntityUpdatedEventWhenNavigationChanges): https://abp.io/docs/10.5/framework/infrastructure/event-bus/local/This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
Please contact us at https://abp.io/contact.
(For reference, ABP’s FAQ entry about end-of-license period is here: https://abp.io/faq#what-happens-when-my-license-period-ends)
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
Given your “heavily modified get-source modules” setup, the safest/lowest-surprise path is:
net9.0, get everything compiling/tests passing.net10.0 and upgrade the remaining non-ABP dependencies (EF Core 10, ASP.NET Core 10, etc.).Do not do “.NET 10 first on ABP 9.0.4” (option b). ABP 9 is tied to the .NET 9 wave; forcing net10 first typically creates a larger, harder-to-debug break surface.
Do not do “ABP + net10 in one pass” (option c) for your case. With ~1,000 modified module files, isolating failure domains is critical.
net9.0, then change to net10.0.net10.0) means you can isolate:
abp update (run at solution root). Verified doc:This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
/.well-known/openid-configuration behind AWS API Gateway) are:GET and OPTIONS/.well-known/openid-configuration is mapped to the same integration/target as /Account/Login.OPTIONS) the discovery endpoint; if OPTIONS gets 404 at the gateway, the Angular OIDC init fails even if GET would work.Request starting ... /.well-known/openid-configurationRequest finished ... /.well-known/openid-configuration - <status> ...Request starting for that path, the problem is definitively API Gateway routing / path rewrite / wrong upstream (not ABP/OpenIddict).using Microsoft.AspNetCore.HttpOverrides;
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders =
ForwardedHeaders.XForwardedFor |
ForwardedHeaders.XForwardedProto |
ForwardedHeaders.XForwardedHost
});
// continue with ABP pipeline...
}
X-Forwarded-Proto: httpsX-Forwarded-Host: api-product.dev.<company_domain>This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
ABP Studio’s “Download source code” feature does not download the Angular npm package sources for modules like CmsKit / CmsKit.Pro as @abp/ng.cms-kit and @volo/abp.ng.cms-kit-pro in the way you expect. That’s why you see:
@abp/ng.cms-kit and@volo/abp.ng.cms-kit-pro.To get the Angular package sources, download the module source code itself (server-side module package), because ABP delivers the related Angular package sources inside the module’s source bundle under an Angular folder.
abp get-source Volo.CmsKit
abp get-source Volo.CmsKit.Pro
After download, check the extracted module folder for an Angular directory; that is where the sources for the corresponding Angular packages are included.
If you previously downloaded and it’s missing files, clear the local cached zip(s) and retry download (same approach documented for other commercial Angular packages):
%USERPROFILE%\.abp\templatesabp get-source ... / re-download from the Modules UI.If you are on a license type that doesn’t include source access (only Business/Enterprise can download commercial sources), you won’t be able to download CmsKit.Pro sources; in that case, please contact sales/support.
Links (verified):
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
In ABP 10.6 (OpenIddict), when your AuthServer is behind a reverse proxy/API Gateway that terminates TLS, the AuthServer process may see the incoming request as HTTP (or with a different host), unless forwarded headers are applied correctly.
/.well-known/openid-configuration) behaving unexpectedly in hosted environments (404 via unmatched route/proxy mapping, or 504 due to gateway retries/timeouts), while still working locally.Fix it by ensuring the AuthServer trusts and applies forwarded headers (scheme/host), before ABP endpoints are executed.
In your AuthServer project module (*.HttpApi.Host / AuthServer host module), add UseForwardedHeaders() early in OnApplicationInitialization.
Example:
using Microsoft.AspNetCore.HttpOverrides;
using Volo.Abp;
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders =
ForwardedHeaders.XForwardedFor |
ForwardedHeaders.XForwardedProto |
ForwardedHeaders.XForwardedHost
});
// If your proxy is not in KnownNetworks/KnownProxies, you may need:
// options.KnownNetworks.Clear();
// options.KnownProxies.Clear();
// Continue with the standard ABP pipeline...
}
This ensures OpenIddict sees the request as https://api-product.dev.<company_domain> (instead of http://<internal-host>:<port>), so discovery generation/routing matches what the Angular OIDC client expects.
Configure API Gateway / VPC Link integration so the backend receives:
X-Forwarded-Proto: httpsX-Forwarded-Host: api-product.dev.<company_domain>X-Forwarded-For: <client-ip>Without these, UseForwardedHeaders() can’t reconstruct the external URL correctly.
SetConfigurationEndpointUris unless necessaryABP/OpenIddict already exposes the discovery endpoint by default. Keeping custom endpoint URI setup is usually fine, but if your proxy rewrites paths or strips /.well-known/*, remove the explicit endpoint overrides and retest to eliminate path-mapping issues at the gateway layer.
For scheduling/support coordination (non-technical/support process), please contact us at: https://abp.io/contact
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
The error you see in the Identity Management → Users → Actions → Two-Factor dialog is expected when the target user has no available 2FA providers configured. ABP checks whether the user supports two-factor (i.e., has at least one usable provider such as confirmed phone, confirmed email, or an authenticator key). If not, enabling 2FA for that user is rejected with the message:
“You can not enable two factor for this user because the user does not configured any two factor providers.”
Fix (per user):
Fix (tenant-level “Two factor behaviour” not being applied until you click Save):
About “Forced” / “Disabled” behavior not taking effect after Save:
(If you share your ABP version and whether you use Account Pro / which providers (Email/SMS/Authenticator) are configured, I can map the exact expected behavior and where to override/seed it—but I’m not allowed to ask for more info per the response policy.)
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
Do not upgrade Scriban independently in an ABP 10.0.0 solution. ABP’s Scriban integration (Volo.Abp.TextTemplating.Scriban) is built/tested against the Scriban version that ABP ships with. Forcing Scriban 7.x via your own PackageReference can cause runtime/binding issues that may surface when rendering email templates (so emails fail while other parts still work).
What to do about the vulnerability warning (recommended path):
abp update or “Upgrade ABP Packages” in ABP Studio.Mitigation if you allow untrusted users to edit templates (or you want defense-in-depth without changing Scriban yet):
array.insert_at) from the Scriban runtime, so templates can’t trigger the problematic behavior.Why your emails break after upgrading Scriban to 7:
Links (validated):
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
IdentitySessionCleanupBackgroundWorker / session cleanup runs in host context and (as you observed from the generated SQL) the multi-tenant filter limits the delete to TenantId IS NULL. So tenant-owned rows are not deleted unless the worker explicitly changes the tenant context.Supported workaround (recommended): implement your own cleanup worker/service that loops tenants and runs the delete inside ICurrentTenant.Change(...). This is the same pattern ABP support recommends for background workers.
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Volo.Abp.BackgroundWorkers;
using Volo.Abp.MultiTenancy;
using Volo.Abp.TenantManagement; // ITenantRepository (or use ITenantStore if you prefer)
using Volo.Abp.Domain.Repositories;
public class TenantAwareIdentitySessionCleanupWorker : AsyncPeriodicBackgroundWorkerBase
{
private readonly ICurrentTenant _currentTenant;
private readonly ITenantRepository _tenantRepository;
private readonly IIdentitySessionRepository _identitySessionRepository;
private readonly IdentitySessionCleanupOptions _options;
public TenantAwareIdentitySessionCleanupWorker(
AbpAsyncTimer timer,
ICurrentTenant currentTenant,
ITenantRepository tenantRepository,
IIdentitySessionRepository identitySessionRepository,
IOptions<IdentitySessionCleanupOptions> options)
: base(timer)
{
_currentTenant = currentTenant;
_tenantRepository = tenantRepository;
_identitySessionRepository = identitySessionRepository;
_options = options.Value;
Timer.Period = (int)_options.CleanupPeriod.TotalMilliseconds; // or your desired period
}
protected override async Task DoWorkAsync(PeriodicBackgroundWorkerContext workerContext)
{
// 1) Host cleanup
using (_currentTenant.Change(null))
{
await _identitySessionRepository.DeleteAllAsync(_options.InactiveTimeSpan);
}
// 2) Tenant cleanup (important for shared-db and database-per-tenant)
var tenants = await _tenantRepository.GetListAsync(includeDetails: false);
foreach (var tenant in tenants)
{
using (_currentTenant.Change(tenant.Id))
{
await _identitySessionRepository.DeleteAllAsync(_options.InactiveTimeSpan);
}
}
}
}
IdentitySessionCleanupService instead?(links validated)
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.