Skip to content

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.

yaml
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, authenticated or a role list; owner in the list adds the record's owner.
  • ownerField is 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.
  • anonymousActions keeps working for backward compatibility. Existing specs that don't use access generate 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 ​

yaml
roles: [Editor, SuperAdmin]   # Admin and User always exist
registration:
  enabled: false              # default: CLOSED
  defaultRole: User
  • While registration is closed /api/account/register returns 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) ​

yaml
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 service props (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/me and the admin user list return profile; PUT /api/account/profile (self fields) and PUT /api/admin/users/{id}/profile (all) validate by type. Profile and admin forms are rendered automatically in the sign-in SPA.
  • user.proto is now generated from auth.yaml; services with an identity/User external reference read auth.yaml from the same workspace and receive the profile fields in UserReference.
  • 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 ​

yaml
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 undefined 99 and wrote it to the DB.

List filters and read visibility ​

yaml
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.
  • readFilter is 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 ​

AreaProblemImpact
API / gRPCCorrelationIdClientInterceptor was not registered in DIBuild succeeded, crash on the first real gRPC call. AddBaseForge now registers it — existing projects are fixed by a package update
GatewayThe PathPattern transform encoded /Multi-segment paths such as /api/gateway/x/Listings/{id} returned 404
IdentitySecrets were written to the auth.yaml copy, the signing certificate password to appsettings.jsonPasswords/secrets in committed files. Now only in .env; secrets left empty are restored from .env (CLI and Designer)
IdentityExternal sign-in auto-linked to a password-protected account with a matching emailRisk of account takeover via providers that don't verify email. Password-less (admin-added) accounts continue to be linked
CodegenThe schema was only created in DevelopmentTables were never created in Production
Codegenjwt.Authority was hardcodedCan now be overridden with Auth:Authority
APINo fallback issuer in Authority modeMass 401s with IDX10204 while Identity restarts. If Auth:Issuer is given it becomes a valid fallback issuer
APIThe request log was written before the exceptionFailed requests appeared as 200 in the logs
CodegenNew services had no wwwrootImage upload returned 500 in local dotnet run
IdentityThe admin panel's role list was fixedNew roles couldn't be assigned from the panel
DependencyMicrosoft.OpenApi 2.0.0 (GHSA-v5pm-xwqc-g5wc)Generated services now get the patched version via Microsoft.AspNetCore.OpenApi 10.0.12
DependencySSH.NET 2025.1.0 in tests (high)Testcontainers 4.15.0
Codegen / IdentityKnownNetworks deprecated (ASPDEPR005)KnownIPNetworks
DesignerNo faviconA 404 in the console on every load
APIDatabase constraint violations were not handledA record missing a required relation / a duplicate unique field returned 500; now 400 / 409 (table/constraint names are not leaked)
Codegenhost.docker.internal did not resolve in local dotnet runLogs didn't reach Loki and JWT validation didn't reach Identity; launchSettings.json now provides the localhost equivalents

Breaking changes ​

  • JWT claim mapping: EnableJwt now uses MapInboundClaims = false, RoleClaimType = "role", NameClaimType = "sub". Code in services that looks up claims with ClaimTypes.Role / ClaimTypes.NameIdentifier must look for the short names (role / sub). [Authorize(Roles = ...)] and User.IsInRole now work without extra code; hand-written AdminAuth-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 } to auth.yaml.
  • Middleware order: inside UseBaseForge, the request log is now outside the exception handler.

Upgrade ​

  1. Upgrade the packages to 0.6.0-beta and update the CLI: dotnet tool update -g BaseForge.CodeGen --prerelease.
  2. Regenerate services (baseforge update <service>); add access / ownerField for authorization.
  3. In production, give the service environment Auth__Authority and an Auth__Issuer identical to Identity's issuer.
  4. In Identity instances that require registration, set registration.enabled: true.

Known limitations ​

  • superRoles does not bypass the tenant filter in multi-tenant services.
  • Counter (increment) endpoints are always public.
  • The Identity admin panel is only open to the Admin role (a SuperAdmin must also be an Admin).
  • EnsureCreated doesn'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.proto numbers change — Identity and identity/User consumers must be regenerated together.

Released under the MIT License.