Skip to content

v0.0.40

Choose a tag to compare

@github-actions github-actions released this 24 Oct 20:29
· 35 commits to main since this release
Immutable release. Only release title and notes can be modified.

Fixed

  • Behaviour change: evict expired site-lock attempt buckets, paginate personal/admin API token lists
    (default and maximum API page size 100, with limit/offset), preserve revoke-all
    across pages, and recover concurrent duplicate share grants without a 500 (#640).
  • Share UTC timestamp helpers across sharing, tenants, background tasks, API tokens
    and users (#640).
  • Users: publish deletion, disabling and new role assignments transactionally through
    the durable outbox (inline cleanup without background tasks). Deletion purges tenant
    memberships, shares to the user and direct permission grants; tenants left without
    an owner are suspended. Existing roles no longer emit RoleAssigned (#627).
    UserDeleted now lives in simple_module_core.user_events (still re-exported from
    users.contracts.events) so modules can subscribe without depending on users.
  • Frontend: verification failures show a generic error unless the server reports a bad
    token or an already-verified account; file downloads bypass Inertia so signed-URL
    redirects work; closed mobile sidebars are inert and hidden from assistive technology,
    while desktop sidebars remain accessible (#638).
  • Settings: reverting a field preserves other unsaved edits; connection-test results
    stay with their module, and unreadable or empty check responses report failure (#637).
    @simple-module-py/ui: readBody now rethrows a body-read failure instead of returning
    null; apiFetch reports it as a network failure (status 0).
  • Release tooling converts Python prereleases to npm SemVer and publishes them
    with the next tag; metadata checks catch missing/stale npm matrix entries.
    Memray loadtests refuse occupied ports and stop only their own process group (#629).
  • Release workflow serializes runs, rejects non-main or stale dispatches before
    publishing, tags the published release commit and merges concurrent main changes
    into it before atomically pushing main and the tag, failing safely on conflicts (#628).
  • CLI: smpy update selects a tag accepted by every sibling's constraint;
    git sources preserve refs containing /, and Python prerelease tags sort before
    their final release (#636).
  • Hosting: derive the production Inertia asset version from the Vite manifest hash so
    stale tabs reload after deployment; include CSS from statically imported chunks (#633).
  • Hosting: unwind started modules and dispose the database engine after lifecycle failures;
    serialize setup-wizard migrations across workers, including fresh databases (#634).
  • Database: discard on_commit callbacks registered inside a rolled-back savepoint,
    including released child savepoints, while preserving callbacks from surviving work (#631).
  • Role-permission edits, module settings updates/clears and system/tenant feature-flag
    overrides now apply only after commit and reload across workers through the
    invalidation bus. Periodic reloads bound staleness when messages are missed (#621).
  • Keycloak user-cache inserts and profile updates now commit in an independent transaction,
    preserving user IDs across logins and bearer requests. Concurrent first logins reuse the
    winning cache row; bearer requests throttle last-login writes to once per five minutes (#618).

Added

  • Weekly Dependabot updates for uv, npm and GitHub Actions (#629).
  • tenants.contracts.UsageMeter: an implementation-free usage-metering protocol next to
    EntitlementProvider (check, consume, record, refund, reserve/commit/release,
    summary) with UsageOutcome/UsageReservation/UsageSummaryRow, UsageExceededError,
    an UnlimitedUsageMeter default on app.state.tenants.usage and get_usage_meter(app) (#616).
  • simple_module_hosting.csrf.rotate_csrf_token(request) for principal changes that keep the
    session (#615).

Security

  • Remove account-status and superuser fields from the users UserUpdate contract;
    self-profile routes already used a narrower input schema (#640).
  • Inertia: Inertia.back() and the exported validation handler now redirect only to a
    same-site path taken from Referer, falling back to /; an off-site or malformed
    Referer previously produced an open redirect (#635).
  • Users: account administration never exceeds the actor's own authority. Outside a full
    admin session, a role can be granted and an account managed (edit, disable, reset link,
    invite) only when its effective permissions (roles plus direct grants) are within the
    actor's: a delegate holding users.manage, or an admin's scoped token, cannot grant
    admin, cannot reach admin/superuser accounts, and cannot grant a role mapped to
    permissions it lacks. Nobody can disable or demote themselves or the last active
    administrator (#624).
  • Users: OAuth sign-in links to an existing account by email, or marks a new account
    verified, only with the provider's proof that the email is verified: OIDC
    email_verified: true, Google's People API metadata.verified on the primary email,
    a verified GitHub address from /user/emails; Microsoft (Entra) email claims are never
    proof. An unverified account (SSO-provisioned ones included, pending invites excepted)
    never absorbs a later OAuth login, closing an account pre-hijacking path (#622).
    Behaviour change: a provider without proof cannot sign in to an existing account by
    matching its email alone (400 OAUTH_USER_ALREADY_EXISTS); existing subject links still
    sign in.
  • Tenants: accepting an invitation requires a verified email (#623). Verification is read
    fresh from the user directory (new DirectoryUser.email_verified; None when the
    provider doesn't track it, in which case Keycloak's email_verified token claim decides),
    so an account verified after signing in can accept without signing in again.
    Behaviour change: with users.require_verification off, self-registered accounts must
    verify their email (or be marked verified by an admin) before accepting an invitation.
  • Users: store access and refresh bearer tokens as SHA-256 digests and prune spent
    credentials in bounded batches on login/refresh, retaining active-family
    reuse evidence (#626). Behaviour change: migration a626b1c8d023 deletes
    existing access/refresh tokens; affected clients must log in once again. Logging out
    (POST /users/logout, POST /api/users/auth/logout) now deletes the presented access
    token server-side instead of only clearing the cookie.
  • Users: atomically consume refresh tokens and revoke their refresh-token family on reuse
    (#625). Migration a625f0b7c912 adds family tracking; existing tokens start separate families.
  • DB: sessions bound to a SQLAlchemy Connection now inherit its engine's tenant
    policy and audit configuration, preserving strict tenant isolation and audit callbacks (#630).
  • Keycloak JWKS fetches now use a per-cache lock and a 30-second minimum interval
    between attempts, including failures, preventing random-kid bearer tokens from
    triggering an outbound request each. Newly rotated keys may take up to 30 seconds
    to be accepted (#620).
  • Keycloak sign-in clears prior identity state while preserving registered browser gates and
    the safe post-login redirect; validates the ID token, nonce and matching subject (#619).
    Behaviour change: browser sessions renew server-side via refresh token at access-token
    expiry, using validated claims for fresh roles. Demoted or disabled users lose access
    at the next access-token expiry; failed refreshes or expired sessions without a refresh
    token require sign-in. The ID token is no longer stored in the signed cookie to keep
    it below browser size limits; logout uses client_id instead of id_token_hint.
    Sessions created before this release carry no refresh token, so every Keycloak browser
    session signs in once after upgrading. Realms with "Revoke Refresh Token" enabled should
    allow a refresh-token reuse count of at least 1, or parallel requests at expiry can sign a
    user out.
  • Sign-in now rotates the session: users' on_after_login (password, OAuth, accept-invite)
    clears it and keeps only the pending post-login redirect, so a CSRF token (or any other
    session state) issued before sign-in no longer survives it. Keycloak sign-in rotates the CSRF
    token (#615). Behaviour change: session keys a module stored
    before sign-in are dropped at sign-in.
    simple_module_hosting.session.preserve_across_sign_in(app, key) lets a module keep browser-bound
    gate state across it; site_lock uses it so the unlock marker survives sign-in.