v0.6.3-beta
A security release. Generating a real multi-tenant project (ProKantar) with 0.6.2 surfaced several holes in the generated code: unauthenticated gRPC, CRUD endpoints that could overwrite workflow-owned fields, free-form sortBy, unescaped search and default database passwords. This release closes them. Most changes are on by default; read Breaking changes before upgrading a production service.
Security
Authenticated gRPC
- In services with an
authblock, every gRPC endpoint now requires a valid JWT (MapGrpcService<…>().RequireAuthorization()). - Generated gRPC clients attach credentials automatically (
AddBaseForgeCallCredentials):- inside an HTTP request the caller's Bearer token is forwarded, so the callee sees the real user (roles,
tenant_id); - in background code (RabbitMQ consumers, jobs) a
client_credentialsservice token is fetched from Identity and cached (IServiceTokenProvider).
- inside an HTTP request the caller's Bearer token is forwarded, so the callee sees the real user (roles,
- The tenant travels as
x-tenant-idmetadata, and it is trusted only with a service token: Identity now stamps abf_svcclaim into client-credentials tokens. A user cannot switch tenants by sending the header. - New
TenantScope(BaseForge.Core.MultiTenancy): an ambient tenant for code that runs outside an HTTP request.
Server-managed fields — serverManaged
status: { type: enum, values: [Open, Completed], serverManaged: true }
netKg: { type: decimal, nullable: true, serverManaged: true }The field is left out of the generated Create/Update commands and stays in the DTO. Only your own commands (in Custom/) change it, so status changes and calculated totals can no longer be set by a plain PUT. The Designer has a checkbox for it.
Safe sorting and searching
sortByaccepts onlyField,Field ascorField desc(comma-separated, at most 5) from an allow-list. The default list is all scalar fields plusCreatedAt/UpdatedAt; setsortFields: [...]to narrow it. Anything else returns 400.GenericRepositoryrefusesTenantId,IsDeletedandDeletedAton its own.searchFields: [...]limits which text fieldssearchlooks in (default: all). Keep sensitive fields such as ID numbers out of it.%,_and\in the search term are escaped. Terms shorter than 2 characters are ignored.
Counter endpoints
- New access action:
access: { counters: authenticated | [roles] }. Without it, counters stay public as before. increment-*endpoints are rate-limited to 30 requests per minute per user (or IP), and the 31st request gets 429.
Secrets and ports
- Every generated service (and Identity) gets a
.envwith a random 32-characterPOSTGRES_PASSWORD, plus a committed.env.example..envis gitignored and excluded from the Docker image. appsettings.jsonno longer contains a password.docker-compose.ymlreads${POSTGRES_PASSWORD}and refuses to start without it. Localdotnet runloads.envautomatically.- Postgres is published on
127.0.0.1only. - Outside
Development, a service refuses to start when its database password is empty orchange_me.
Breaking changes
- Production with
change_me: a non-Development service whose connection string still useschange_me(or an empty password) will not start. Set a real password in.env/ the environment first. - Existing volumes are kept: when a service generated by 0.6.2 or earlier is regenerated,
change_meis detected and kept in.env, so local databases keep working. Rotate it deliberately. sortBy: expressions other thanField [asc|desc], or fields outside the allow-list, now return 400 instead of being executed.- Background gRPC calls: to call another service from a consumer or a job, add a confidential client to
identity/auth.yaml(clientId: {service}-service,grants: [client_credentials],scopes: [api]) and put its secret in the calling service's.envasAuth__ServiceClient__ClientSecret. Without it, user-initiated calls keep working, but background calls get 401.
Upgrade
dotnet tool update -g BaseForge.CodeGen --prerelease
baseforge update <service> # opens the Designer with the spec loaded → GenerateRegenerated services reference the BaseForge.* 0.6.3-beta packages. Hand-written code under Custom/ is untouched. Details are in the architecture decisions (§5.1.1, §5.5, §6.2, §6.4, §6.5).
Coming next
Tenant id in integration events and the consumer, per-entity tenantScoped, tenant-aware unique indexes, tenant management in Identity, range/IN filters with defaultSort, a PostGIS geometry type and Designer guards (stale-form check, port conflicts).