v0.6.0-beta
The main themes of this release are authorization and spec expressiveness (enums, filters, visibility, optional relations): until now generated services only asked "are you signed in?"; roles and record ownership can now be defined from the spec and the Designer. In addition, every bug found and hand-patched in production projects has been moved into the generator.
Upgrade note
The generator and the library must be upgraded together — generated code uses new BaseController helpers in BaseForge.API 0.6.0-beta. See Breaking changes.
New features
Role and ownership based authorization (access / ownerField)
Details and rationale: Architecture §6.1.
auth:
defaultAccess: authenticated # actions not listed (default)
superRoles: [SuperAdmin] # passes every rule automatically (SaaS platform owner)
entities:
Order:
ownerField: BuyerId
access:
list: [Admin, owner] # Admin sees all, others only their own records
create: authenticated
update: [owner]
delete: [Admin]- Per action:
anonymous,authenticatedor a role list;ownerin the list adds the record's owner. ownerFieldis stamped from the token on create (the value sent by the client is ignored) and cannot be changed on update.- Non-owners: update/delete → 403, someone else's record in getById → 404 (existence is not leaked), list → only their own records.
- Image upload (
/api/media) follows the service's default rule. - The rule is computed in the controller and enforced in the handler; gRPC's user-context-less service-to-service reads are not affected.
anonymousActionskeeps working for backward compatibility. Existing specs that don't useaccessgenerate exactly the same code.- If a role used in a service spec is not defined in Identity (a typo), a warning is shown during generation.
Identity: roles and registration setting
roles: [Editor, SuperAdmin] # Admin and User always exist
registration:
enabled: false # default: CLOSED
defaultRole: User- While registration is closed
/api/account/registerreturns 404 and no account is opened for a user arriving for the first time via an external provider (Google, etc.) either. - The admin panel can assign all defined roles (previously only Admin/User).
- The sign-in screen hides the "Register" link while registration is closed.
Identity: user profile fields (userProfile)
userProfile:
props:
Specialty: string
DiplomaNo: { type: string, nullable: true, maxLength: 32 }
VerificationStatus: { type: enum, values: [Pending, Approved, Rejected], default: Pending, editableBy: admin, inToken: true }- Fields are added directly to the user (
ApplicationUser) instead of a separate table; the definition is the same as serviceprops(enum, nullable, maxLength, default). editableBy: self(default) → the user edits it from the profile page;admin→ admin panel only.inToken: true→ the field becomes a JWT claim./api/account/meand the admin user list returnprofile;PUT /api/account/profile(selffields) andPUT /api/admin/users/{id}/profile(all) validate by type. Profile and admin forms are rendered automatically in the sign-in SPA.user.protois now generated from auth.yaml; services with anidentity/Userexternal reference read auth.yaml from the same workspace and receive the profile fields inUserReference.- Columns for fields added later to an existing Identity database are created at startup (
ADD COLUMN IF NOT EXISTS). - See Architecture §6.3.
Enum field type
Status:
type: enum
values: [Draft, Pending, Active, Sold]
default: Draft- A real C# enum in code (
ListingStatus) — type-safe comparison in hand-written code. - Stored as a string by value name in the database and in JSON (API + RabbitMQ events); old records don't break if values are added or reordered.
- Numeric values are rejected (
BaseForge.Core.Serialization.StrictStringEnumConverter) — the standard converter accepted an undefined99and wrote it to the DB.
List filters and read visibility
Post:
filterable: [Status, AuthorId] # ?status=Live&authorId=...
readFilter:
where: { IsPublished: true } # everyone sees only published posts
bypassRoles: [Admin]
bypassOwner: true # the author also sees their own drafts- Filters generate equality filters for props, relation FKs and external references.
readFilteris applied to list and getById (an invisible record is a 404). gRPC service-to-service reads are not affected. See Architecture §6.2.
Optional relations
nullable: true under relations → FK becomes Guid?; records without a target, such as a root category with no parent, can now be created (previously an FK violation).
Loki + Grafana in the workspace
On the first generation observability/ is added to the workspace root: Loki, Grafana, a ready datasource and a "BaseForge - Service Logs" dashboard (random admin password in .env). Previously there was no Loki running in the user's workspace and logs silently went only to the console.
Designer
- Enum value list, optional relation checkbox, list filter and visibility filter sections.
- Default access and super roles in service settings; owner field and a per-action access table in the entity editor; roles, registration toggle and profile fields in the Identity panel.
- Where logging goes (Loki/Grafana) is explained in the service settings.
baseforge new|update <service> --no-browser: starts without opening the browser automatically.
Added since the previous beta
- API Gateway (YARP) generation, Sign in with Apple provider, UI Designer infrastructure, local CLI update script.
Bug fixes
| Area | Problem | Impact |
|---|---|---|
| API / gRPC | CorrelationIdClientInterceptor was not registered in DI | Build succeeded, crash on the first real gRPC call. AddBaseForge now registers it — existing projects are fixed by a package update |
| Gateway | The PathPattern transform encoded / | Multi-segment paths such as /api/gateway/x/Listings/{id} returned 404 |
| Identity | Secrets were written to the auth.yaml copy, the signing certificate password to appsettings.json | Passwords/secrets in committed files. Now only in .env; secrets left empty are restored from .env (CLI and Designer) |
| Identity | External sign-in auto-linked to a password-protected account with a matching email | Risk of account takeover via providers that don't verify email. Password-less (admin-added) accounts continue to be linked |
| Codegen | The schema was only created in Development | Tables were never created in Production |
| Codegen | jwt.Authority was hardcoded | Can now be overridden with Auth:Authority |
| API | No fallback issuer in Authority mode | Mass 401s with IDX10204 while Identity restarts. If Auth:Issuer is given it becomes a valid fallback issuer |
| API | The request log was written before the exception | Failed requests appeared as 200 in the logs |
| Codegen | New services had no wwwroot | Image upload returned 500 in local dotnet run |
| Identity | The admin panel's role list was fixed | New roles couldn't be assigned from the panel |
| Dependency | Microsoft.OpenApi 2.0.0 (GHSA-v5pm-xwqc-g5wc) | Generated services now get the patched version via Microsoft.AspNetCore.OpenApi 10.0.12 |
| Dependency | SSH.NET 2025.1.0 in tests (high) | Testcontainers 4.15.0 |
| Codegen / Identity | KnownNetworks deprecated (ASPDEPR005) | KnownIPNetworks |
| Designer | No favicon | A 404 in the console on every load |
| API | Database constraint violations were not handled | A record missing a required relation / a duplicate unique field returned 500; now 400 / 409 (table/constraint names are not leaked) |
| Codegen | host.docker.internal did not resolve in local dotnet run | Logs didn't reach Loki and JWT validation didn't reach Identity; launchSettings.json now provides the localhost equivalents |
Breaking changes
- JWT claim mapping:
EnableJwtnow usesMapInboundClaims = false,RoleClaimType = "role",NameClaimType = "sub". Code in services that looks up claims withClaimTypes.Role/ClaimTypes.NameIdentifiermust look for the short names (role/sub).[Authorize(Roles = ...)]andUser.IsInRolenow work without extra code; hand-writtenAdminAuth-style classes in projects can be removed. - Registration closed by default: registration is closed in a regenerated Identity. Projects that need registration must add
registration: { enabled: true }toauth.yaml. - Middleware order: inside
UseBaseForge, the request log is now outside the exception handler.
Upgrade
- Upgrade the packages to
0.6.0-betaand update the CLI:dotnet tool update -g BaseForge.CodeGen --prerelease. - Regenerate services (
baseforge update <service>); addaccess/ownerFieldfor authorization. - In production, give the service environment
Auth__Authorityand anAuth__Issueridentical to Identity's issuer. - In Identity instances that require registration, set
registration.enabled: true.
Known limitations
superRolesdoes not bypass the tenant filter in multi-tenant services.- Counter (increment) endpoints are always public.
- The Identity admin panel is only open to the
Adminrole (a SuperAdmin must also be an Admin). EnsureCreateddoesn't update an existing database; when a field is added to a service spec, an EF migration is needed for the existing DB (except Identity profile fields — those are added as columns automatically; drops/type changes are manual).- Profile fields are not asked on the registration form (filled on the profile page after registration). If the field order changes,
user.protonumbers change — Identity andidentity/Userconsumers must be regenerated together.