Skip to content

v0.6.5-beta ​

Multi-tenant SaaS support, end to end. Identity now manages companies and memberships and puts the active company in the token. Users can sign in with a phone code, verify their phone and e-mail, and reset their password over SMTP. Events carry the tenant to their consumers, and generated lists get range and multi-value filters, unique constraints and validation rules. Every Identity feature is off by default, so existing auth.yaml files behave as before.

Identity ​

Companies (multiTenancy) ​

  • Opening a company: self-service. The user who opens it becomes its admin (adminRole). With requireApproval, the company stays pending until a platform role (approverRoles, e.g. SuperAdmin) approves it. Platform roles can also suspend and reactivate companies.

  • Joining a company: two ways.

    • The person asks with the company code, and a company admin approves them with roles.
    • The company invites by e-mail or phone, and the person accepts.

    An invitation to someone who has no account yet waits until a user with that verified e-mail or phone appears, so nobody can claim it by signing up with another person's address.

  • One person, many companies: the token carries tenant_id, tenant_name and the roles of the active company. Switch companies with tenant_id on a refresh; the chosen company stays on later refreshes. A user can set a default company.

  • Rules:

    • Company roles (e.g. TenantAdmin, Operator) come only from the membership and can never be granted globally.
    • A company's last admin cannot leave or be demoted.
    • A suspended company drops out of the token on the next refresh.
  • Company details: a company admin can update the name and tax number.

  • Existing databases: an existing identity_db gets the new tables automatically.

One-time codes (otp) ​

  • Phone sign-in and sign-up through the grant urn:baseforge:grant-type:otp. Add otp to the client's grants. The app can pass a self-assignable role, e.g. Driver.
  • Verification: a user can verify a phone number or e-mail address on their account, and pending company invitations are attached when they do.
  • Code security: codes are stored hashed and expire. There is an attempt limit, a resend cooldown, an hourly cap and a per-IP limit.
  • Phone-only accounts: an account may have no e-mail address; if it has one, the address must still be unique.

E-mail and password reset ​

  • messaging: e-mail goes out through SMTP (Resend, Google Workspace, Brevo, SES…) or the development Log provider. The SMTP password stays in .env (Auth__Messaging__Smtp__Password).
  • SMS: there is an ISmsSender interface; for now the only provider is Log.
  • passwordReset: the forgot-password flow.
    • The response is the same for every address, and each address gets at most one e-mail per cooldown.
    • The link is time-limited.
    • A successful reset confirms the e-mail, clears any lockout and signs the user out everywhere.
  • registration.selfAssignableRoles: roles an app may give a user at sign-up (POST /api/account/roles/{role}).
  • accessTokenLifetimeMinutes: sets the access token lifetime.

Fixes ​

  • External login rules are now in one place. The reference Razor login page could open an account while registration was closed, and could link to a password account by matching e-mail. Generated services did not include that page.
  • Errors: /api/* errors are ProblemDetails, the same shape the services use. A duplicate record returns 409.
  • Security: System.Security.Cryptography.Pkcs is pinned to 10.0.14 (OpenIddict pulled in a vulnerable 10.0.7).
  • Client settings: grants and scopes added to auth.yaml later now reach existing clients too.

Services ​

  • Tenant in events: the outbox envelope carries the publishing tenant, and the consumer handles each event in that tenant. Handlers in multi-tenant services can read and write without hand-written tenant plumbing.
  • Early TenantId: a new tenant entity gets its TenantId as soon as it is added, not only at SaveChanges.
  • auth.crossTenantRoles: users in these roles whose token has no tenant read every tenant's records. This applies to GET only; writes still need a tenant.
  • RabbitMQ down at startup: the service keeps running and retries the connection with backoff. Before, the whole service stopped.
  • List filters: rangeFilters (?xFrom=&xTo=), inFilters (?xIn=A&xIn=B) and defaultSort.
  • unique: field groups that must be unique. In multi-tenant services uniqueness is per tenant, and deleted records are ignored.
  • Validation: min, max, minLength and pattern on props are checked on create and update.
  • externalRefs.x.nullable makes a reference to another service optional.
  • ProgramHooks.cs: add your own services, middleware and RabbitMQ subscriptions (including non-CRUD events) in Custom/ProgramHooks.Custom.cs. They survive regeneration.

Designer ​

  • New fields: the Identity panel covers companies, OTP, e-mail/SMTP, password reset, self-assignable roles and token lifetime. Entities get range/multi-value filters, default sort, unique fields, validation rules and optional external references. Services get cross-tenant read roles.
  • Stale-form guard: Generate no longer silently overwrites a spec.yaml/auth.yaml that changed on disk after the page was opened. It asks first.
  • Port check: Generate refuses host ports that another service in the workspace already uses.

Breaking changes ​

  • sortBy and new list filters: behavior is unchanged unless you use the new keys.
  • Production with OTP or password reset: a non-Development Identity with OTP or password reset turned on refuses to start while the e-mail or SMS provider is Log.
  • Unique indexes: EnsureCreated creates them only in new databases. For an existing database, add them yourself.

Upgrade ​

bash
dotnet tool update -g BaseForge.CodeGen --prerelease
baseforge update <service>   # opens the Designer with the spec loaded → Generate
baseforge new-identity --spec identity/auth.yaml --output identity

Generated services reference the BaseForge.* 0.6.5-beta packages. Hand-written code under Custom/ is untouched. Details are in the architecture decisions (§5.2.1, §5.5, §6.2, §6.6, §6.7).

Released under the MIT License.