Repository navigation
v5.1.0
5.1 is a minor release. Delivery gets a Members audience with viewer tokens and a Private site mode, roles and groups can be deleted and restored, a workflow can be run by hand and report its health, an administrator can reset a user's two-factor authentication, and a tenant can map identity provider claims to roles. The API contract and the delivery contract both move from 8 to 9. The module contract and every module's HTTP contract stay as they are. Read Upgrade before deploying.
Upgrade
From 5.0.x. A 4.x install upgrades to 5.0 first with docs/upgrading-to-5.0.md.
- Take a backup and check it restores (
docs/backup-and-restore.md). - Stop the 5.0 API. Every file below is applied with the API stopped.
- Run
db-migratewith the 5.1.0 image, thendb-assert.FROM_VERSION=5.0.0 scripts/upgrade-check.sh --listprints each file as apsqlline in the order CI applies them. Undermigrations/5.1.0/:- Core, first:
1158-content-type-name-index-ignore-case.sql(rebuilds the unique index on a content type's name on the lowercased name; if two names in one tenant differ only by case it changes nothing, says how many names clash, and stops, so delete the extra definitions and run it again),seed-markers.sql(the table that records which one-time seeder steps have run, empty),workflow-run-updated-at-index.sqlandworkflow-run-workflow-index.sql(two indexes on workflow runs, for the changed-since cursor and the workflow filter). - Module, after the core files; a host without BarakoCMS.ExternalAuth skips it:
915-external-auth-role-maps.sql(BarakoCMS.ExternalAuth 4.7.0, the per-tenant claim to role map table, empty).
- Core, first:
- Check the defaults. No config default changed. Site
ModePrivate, claim to role maps, tenant mail overrides and viewer tokens are opt-in, and a group holds no roles until someone sets them. - Start 5.1.0. New capabilities:
run_workflowsgoes to every role holdingmanage_workflows, andview_module_configurationis held by SuperAdmin only.
Breaking HTTP changes. ApiContract.Version moves from 8 to 9, and ApiContract.DeliveryVersion from 8 to 9 (#912). Admin: the sensitivity of a field or an entry can now be Members, a value a console written against 8 does not know. Delivery: a junk X-Viewer-Token header on a delivery route answers 401 where it used to answer 200, and anonymous delivery now sends Vary: X-Tenant, X-Viewer-Token. Adding a member to a group that holds roles is refused unless the caller may grant those roles (#919). Released barakoBrew 1.10.0 and barakoPress 0.14.1 read 9: upgrade them together with the API.
Package changes. BarakoCMS.Abstractions 5.1.0 adds IBarakoModule.ConfigurationView, IModuleSchema.EnableEventHeaders() and the capability gate types (CapabilityGate, RequiredCapability, CheckedCapability), moved from the core with the namespace unchanged. A module built against 4.x or 5.0 still loads. Nothing is removed and nothing new is obsolete.
Rolling back to 5.0. Stop 5.1, run the rollback lines --list prints, newest first, then remove each rolled-back file's ledger row with db-migrate --forget <key> and start 5.0.x. Every file above has a rollback file. Two things need care:
- Before going back, erase every deleted role, user group and saved query (
GET /api/roles/deleted,GET /api/user-groups/deleted,GET /api/queries?deleted=true, then theeraseroute for each). 5.0 ignores the deleted mark, so a deleted role or group would grant again. - Lost on rollback: every tenant's claim to role map (roles it already gave stay), the record of which seeder steps ran (a step runs again after the next upgrade and can grant back a capability an administrator removed), and the
UpdatedAtandHistoryvalues on workflow runs, which 5.0 drops the next time it saves a run. The content type name index goes back to exact case, with nothing lost.
Added
- An administrator can reset another user's two-factor authentication. A user who lost their
authenticator and their recovery codes had no way back in.POST /api/users/{userId}/mfa/reset
takes a reason, removes the user's MFA enrollment, revokes their refresh tokens and writes an
auth.mfa.resetaudit row. It needsmanage_usersin one of the caller's global roles (a tenant membership is not enough), only a SuperAdmin can reset a user holding a platform role, and you cannot reset your own. - Site
Modecan bePrivate. While it is set, every/api/public/*route of that tenant
answers 404 unless the request carries a link to the whole site inX-Share-Keyor comes from a
signed-in viewer who may read thesitetype.GET /api/tenants/{handle}/publicfollows the same
rule. Another tenant is unaffected, and a tenant with noModeisLive. This is opt-in per
tenant, so no existing route changes unless a site is set toPrivate, and neither contract
number moves. Asitetype made before this release gets the option withset-field-optionson
Mode. Seedocs/site-settings.md.
An openGET /api/public/eventsstream asks again every 30 seconds
(Delivery:Events:GateRecheckSeconds) and ends when the caller no longer qualifies. - A tenant can override the mail sender and API key.
GETandPUT /api/tenant-settings/emailset a tenant's ownfromAddressandapiKey(write-only, encrypted). Mail for that tenant uses them field by field, and any tenant without an override keeps the host default. Seedocs/tenant-settings-896.md(#896). Account mail (sign-in codes, verification, resets, lockout notices) always uses the host key and sender, and a tenant sender is used only together with that tenant's own key. SMTP always sends from the host sender. Because the form email verification code is account mail, a Resend bounce on one is now recorded only for global admins and no longer for the form's tenant: the tenant-recorded send path is what would hand the code to the tenant's own provider. - API keys can reach files, and can be rotated with an overlap window. New scopes
files:readandfiles:writecover/api/files.POST /api/api-keys/{id}/rotateissues a replacement key and keeps the old one valid foroverlapMinutes(default 60, at most 10080). A*key does not imply the files scopes, so existing keys gain no file access. Service principals are still to do, seedocs/api-key-service-principals.md. - A tenant can map an identity provider claim to roles.
PUT /api/tenants/external-role-maptakes a claim name (for examplegroups) and rules from a claim value, read from one configured OpenID Connect provider, to a role in that tenant, withGETandDELETEbeside it, and each change is audited. When someone signs in through that provider for that tenant, each rule their token matches adds its role to their membership, creating the membership if they have none, in the same commit as the sign-in. A person with a second factor enrolled is given the mapped roles when/api/auth/mfa/verifypasses, from a grant carried in the signed MFA challenge, checked again there and spent with the sign-in, so one challenge grants at most once. It only adds, never touches a removed or suspended member, and refuses SuperAdmin, platform roles, other tenants' roles and rules on profile claims a person can edit (name, email, phone_number, address and the like). A rule takes an optionalissuer, matched exactly against the token's issuer, and it is required for a provider whose issuer is a template (Entra common or organizations), since the provider name alone matches every directory. A map can create a membership in a tenant whose sign-up is Closed, for someone who already has an account. Sign-in already finds a returning person by issuer and subject, so a changed email keeps the same account.GET /api/users/{userId}/external-identitiesandDELETE /api/users/{userId}/external-identities/{id}list and remove a user's provider links, for a platform administrator (manage_users) only, a removal is audited, and a holder ofmanage_userswho is not a SuperAdmin cannot reach a SuperAdmin's links.BarakoCMS.ExternalAuthis 4.7.0 and adds theexternal_role_mapstable (migrations/5.1.0/915-external-auth-role-maps.sql). Per-tenant provider configuration is still open on #915. - A user group can hold roles, and its members hold them too.
UserGroup.RoleIdsis new and empty by default, so no existing group changes anyone's access.POST /api/user-groupsandPUT /api/user-groups/{id}accept an optionalroleIds(absent on update leaves them alone) and the responses return it. Setting them follows the rules for assigning a global role to a user. Removing a user from a group now clears both sides of the link. Seedocs/user-groups-grant-roles-919.md. - A journal entry can be posted without saving.
LedgerService.StageAsyncvalidates and numbers an entry and stages it on the caller's session, then returns. The caller's ownSaveChangesAsynccommits the entry with everything else the request staged, so a request that posts two entries and updates a document commits once.PostAsyncisStageAsyncfollowed by the save, so existing callers see no change. Two entries staged on one session get consecutive numbers. - An admin can read the configuration a module runs with.
GET /api/modules/{name}/configurationreturns each key a module lists as safe, with its value, its scope (deployment or tenant) and where it came from (appsettings, environment, default, unknown). A key the module did not list, a key named like a credential and a value holding a credential come back redacted. The route needs the newview_module_configurationcapability, held by SuperAdmin only by default, and a caller holding it through a tenant role sees tenant scope keys only. A module declares its keys with the new optionalIBarakoModule.ConfigurationView; the default shows nothing.CredentialRedactiongainedIsCredentialNameandValueHoldsCredentialfor it. - A module can turn on event headers.
IModuleSchema.EnableEventHeaders()sets Marten'sEvents.MetadataConfig.HeadersEnabled, so a module can store its own headers (for example a branch id or an origin node id) on the events it appends withIEvent.SetHeader. The setting is shared by every module. It adds aheaderscolumn tomt_eventsand changesmt_quick_append_events: a new database gets it at first start, and an existing database needsdb-patchbefore a host that calls it starts. A host that does not have the method throwsNotSupportedExceptionnaming it. - Workflow runs keep every try of an action, and a client can ask what changed. Each action holds a
historyof its newest 20 tries (withdroppedAttemptsfor the rest), errors are redacted before they are stored, and runs carryupdatedAt.GET /api/workflow-runstakeschangedSince, returns acursor, and addsstatusCountsand the retention cutoffs. A migration adds an index on the new field. - A workflow can be run by hand, and its health can be read.
POST /api/workflows/{id}/runstarts a run on one entry (triggerManual,startedByset,Idempotency-Keysupported) and needs the newrun_workflowscapability, which every role holdingmanage_workflowsis granted.GET /api/workflows/healthgives each workflow's last 10 outcomes, success rate and average action time.GET /api/workflow-runstakesworkflowId.
Changed
- Deleting a saved query is recoverable.
DELETE /api/queries/{slug}now marks the query
deleted instead of removing it.GET /api/queries?deleted=truelists them,POST /api/queries/{slug}/restorebrings one back, andDELETE /api/queries/{slug}/eraseremoves a
deleted query for good. Saving over a deleted slug still works and replaces it. Connectors and
requests are still hard deletes (#625). - Deleting a role or a user group is recoverable.
DELETE /api/roles/{id}and
DELETE /api/user-groups/{id}mark the item deleted instead of removing it. A deleted role grants
nothing and cannot be listed, read, edited or assigned. A deleted group stops granting its roles
to its members at once, and restoring it grants them again.GET .../deletedlists them,POST .../{id}/restorebrings one back, andDELETE .../{id}/eraseremoves a deleted one for good.
Creating a role with the name of a deleted one erases the deleted one in the same save, as a
create after a delete always worked. Role and group responses gain an optionaldeletedAt(#625).
Before rolling back to a release without this change, erase every deleted role
in every tenant and on every page (GET /api/roles/deleted, thenDELETE /api/roles/{id}/erase
for each), because an older
release ignores the deleted mark and the roles would grant again. Do the same for groups
(GET /api/user-groups/deleted) and queries (GET /api/queries?deleted=true). - The coding standard says which contract surface a change moves. CLAUDE.md section 6 names
three: admin, delivery and a module's own HTTP version. The numbers, headers and describe
listing already existed. - A Members audience between Public and staff, and a viewer token for delivery. Entries and fields can be marked
Members.GET /api/public/{type}andGET /api/public/{type}/{slug}serve them when the request carries anX-Viewer-Tokenheader, minted withPOST /api/viewer-tokens(needsmanage_public_delivery, at most 480 minutes, audited). Without the header delivery answers as before. A response built for a viewer isprivate, no-store, and a viewer token opens no authoring route. Anonymous delivery now sendsVary: X-Tenant, X-Viewer-Token. Admin contract moves from 8 to 9 and delivery contract from 8 to 9. Admin: field and entrysensitivityresponses can now carryMembers, a value a console written against 8 does not know. Delivery: a junkX-Viewer-Tokenheader on a delivery route now answers 401 where it used to answer 200. See Members and viewer tokens. CapabilityGate,RequiredCapabilityandCheckedCapabilitymoved intoBarakoCMS.Abstractions. A module can declare a capability gate without referencing the core. Namespace is unchanged andbarakoCMSforwardsCapabilityGate, so modules built against 4.x or 5.0 still load. Part of #972.
Fixed
- Two content types whose names differ only by case could be stored. The unique index on the
name was exact-case while every lookup ignores case, so such a pair made both spellings fail as
ambiguous. The index is on the lowercased name now. An existing database needs
migrations/5.1.0/1158-content-type-name-index-ignore-case.sql, which refuses and lists the
clashing names if there are any. - Posting a journal entry returned an id that matched nothing stored.
POST /api/accounting/journal-entriesanswered with a fresh random id, soGET /api/contents/{id}returned 404 and the entry could not be voided by that id. It returns the stored content id now, and entries and accounts read back from content carry their content id instead of a new one on every read.BarakoCMS.Accountingis 4.4.1.
Security
- A user group could be given roles by a caller who could not grant them (#919). Adding a member to a
group that holds roles now checks the caller's right to grant those roles, and a group's roles
reach its members. - An API key could be rotated by a caller who could not have created it (#906). Rotation now refuses such
a key, fails when the key changed under it, and audits denied rotations. Thefiles:readand
files:writescopes are outside the*wildcard. - A tenant's mail settings could change who sends account mail (#896). Sign-in codes, verification, resets
and lockout notices always use the host sender and key, and a tenant sender is used only with that
tenant's own key. SMTP always sends from the host sender. - A claim to role map could grant roles without a second factor, or more than once (#915). Mapped
roles are given after the second factor passes, from a grant that is spent with the sign-in, and
rolled back when the token is refused. A map refuses SuperAdmin, platform roles, other tenants' roles
and rules on claims a person can edit. - The SMTP email module pulled in System.Security.Cryptography.Pkcs 10.0.0, which carries
GHSA-846f-f764-76mx (High) and GHSA-2fmv-rv2c-59x7 (Moderate), through MailKit and MimeKit. It is
pinned to 10.0.13 now.