Skip to content

Justflows 0.3.6

Latest

Choose a tag to compare

@NOOBBase NOOBBase released this 13 Oct 18:04
9fc5eab

[0.3.6]

Added

  • Signed-in state on the site. The session is shared between the admin and the site, so staff signed in at the admin are signed in on the site too. /login, /register and /forgot-password now redirect anyone with a session to their account page. Every sign-in link on a page — menu items, the header's sign-in buttons, footer and content links — becomes My account for a signed-in visitor, register links disappear, and the header's sign-in widget gains Sign out; cached pages stay cached (/js/site-session.js, GET /api/auth/status, readable jf_signed_in hint cookie). See docs/AUTH.md.

  • Themed sign-in and password pages for members. /login, /register, /forgot-password and /reset-password now render inside the site's theme from four editable pages of the new built-in auth content type (Content → Sign-in), each with an Account form block (core.auth-form). They are never cached, indexed, exported, searched or listed. Existing sites get the pages on first visit; a missing or renamed page falls back to the plain form. See docs/AUTH.md.

  • Public signup email protection. Platform → Public signup supports email and domain allow/block lists, disposable-email blocking, and optional allow-list-only signup. Explicit blocks take priority; domains cover subdomains. Checks run before workspace provisioning. (#23)

  • Categories, tags and custom taxonomies. Content → Categories & Tags manages terms per taxonomy: add, rename, re-slug, nest categories, and delete (child categories move up a level). Every site gets category and tag for posts. Administrators can add their own taxonomies and choose which content types use each one. The editor sidebar has a picker per taxonomy, saved with the entry, where editors can also create new terms. A post's first category now fills %category% in permalinks, and term archives such as /category/news list its entries. Changing a post's category or a term's slug redirects the old URLs. Also available through the management API (/taxonomies, /content/{id}/terms) and MCP tools (taxonomies_*, terms_*, content_terms_get, content_terms_set). Adds migration 0047_taxonomy_content_types. The taxonomies plugin API namespace is now reserved for core.

  • Durable plugin job queue. ctx.jobs.enqueue() now stores each run in the new job_queue table, so enqueued work survives a restart, and every call is its own run with its own payload (before, a second call replaced a pending run's payload). Server processes claim due runs with a lease and heartbeat, so a run executes on one process at a time and one interrupted by a crash is picked up again. Failed runs (a thrown error or success: false) are retried after 1, 5, 25 and then 60 minutes up to maxAttempts, and stay listed under Diagnostics → Job queue, where administrators can retry them; payloads are never shown. A perSite job enqueued for a site now runs only on that site. enqueue() returns a promise that resolves once the run is stored. Adds migration 0048_job_queue; without it, runs stay in memory as before.

  • Agents and keys cannot change other workspaces, whatever a plugin does. The host now refuses workspace operations (ctx.tenancy: listing, creating, suspending, reactivating and deleting workspaces, adding sites) and limit changes (ctx.quotas.set) when an AI agent, API key or connected app is behind the call: MCP and assistant tool calls, management API requests, and key requests to plugin routes, including the hooks they trigger. These operations also run only on the installation's root site now. Before, a plugin with platform:tenancy active on a hosted site could manage every workspace, and raise its own workspace's limits.

  • AI agents can use plugins, where the plugin allows it. A plugin route can now be offered to agents as an MCP tool with the new mcp route option (name, description, capability, optional inputSchema and annotations). The tool runs the route's own handler with the agent's session, so the plugin's checks apply unchanged. It is listed only on sites where the plugin is active and only to sessions with its capability, non-read-only calls are audited, and the route's rate limit applies per agent. Routes without the option are never offered. See docs/AI.md.

  • API keys follow new plugins. A key with site:admin now also holds every capability plugins register, including plugins installed after the key was made, as long as its owner still has them. Before, a "Full admin" key only held the capabilities that existed when it was created, so it could never use a plugin installed later. Core capabilities are never widened this way. Plugin capabilities can still be ticked on a key one by one.

  • API keys work on plugin routes. A management API key (Authorization: Bearer jfk_…) now authenticates plugin routes (/ext/<plugin>/… and plugin API aliases such as /api/shop/v1/…), with the management API's checks: the switch under Settings → API, revocation and expiry, the key's site, IP and Origin allowlists, and its rate limit. Failed attempts are audited. Plugins see role: "api-key" and the capabilities the key may use. Keys limited to content types, locales, sites or ownership are refused on plugin routes, because those routes do not check scopes. Key requests skip CSRF and ignore a session cookie sent with them.

  • Plugin tables upgrade with the plugin. ctx.databases.ensureSchema() now adds declared columns and indexes that existing plugin tables lack, on PostgreSQL, MySQL, and MariaDB, and reports them as columnsAdded and indexesAdded. Added columns are nullable and not unique; types are never changed and nothing is dropped. A unique index that existing rows break fails the call instead of being skipped. Before, only missing tables were created, so a column added in a plugin update never reached existing installations.

Fixed

  • Rate-limit the install request. POST /api/install checks the setup key, but had no request limit, so on a fresh site the key could be guessed without pause. It now allows 20 requests per 15 minutes per IP.

  • Member sign-in closes with the site. While the site is in maintenance mode or not yet public, /login, /register, /forgot-password, /reset-password and the account pages show the maintenance or under-construction page, and the member side of the auth API answers 503 before looking up any account. Before, all of them stayed reachable. The public content API now also answers 503 during maintenance (it already answered 404 while the site was not public). Only the staff pages under the admin path, the management API and MCP stay open. See docs/AUTH.md.

  • A custom admin path now hides the admin sign-in too. Before, /login stayed open to staff and returned the hidden admin path after a successful sign-in, so moving the admin neither stopped password guessing nor kept the path secret. Staff now sign in, request a reset and reset their password only at <admin path>/login, /forgot-password and /reset-password, in the admin's own design; /admin/* answers 404 once the path is moved. The API enforces it: a staff account on the member pages gets exactly the answer an unknown account or a wrong password gets, before any two-factor prompt; the member pages never mail staff a reset link; and a reset link only works on the surface it was mailed for. Staff invitations, sign-out and the workspace owner's site links point to the admin's sign-in. This applies on the default /admin path as well, so staff who bookmarked /login must now use /admin/login. See docs/AUTH.md.

  • Workspace suspension schedules full cleanup. Suspension starts a stable retention clock; reactivation cancels it. The purge job processes suspended and deleted workspaces, cleans plugin tables and queued payloads, and retries failed plugin, upload, domain-provider or database cleanup. Plugins receive a required workspace.beforePurge gate and a final workspace.purged action. Shop stops linked subscriptions and mandates without assigning a replacement plan. (#23)

  • Constrain agents to site APIs. Disable arbitrary mcp.tools handlers, replace disk-backed MCP documentation with an embedded guide, reject foreign site selectors, preserve the selected database during internal dispatch, and block delegated platform, cache and infrastructure diagnostics operations. Direct filesystem and source-code access remains forbidden, including themes/ and plugins/, with no custom-development exception. (#159)

  • Filter security-sensitive AI output. MCP and assistant tool results, including plugin results, redact credential fields, environment-file text, private keys, authorization values and known installation secrets. MCP content-type resources use the same filter, and block documentation is embedded without disk reads. (#159)

  • Hide unavailable security overview. When security headers are disabled for a site, Security shows only allowed tabs and blocked security URLs open the first allowed security page, such as Your account. (#23)

  • Sub-sites send email through the platform. A sub-site never uses the host's sendmail any more (it failed with spawn /usr/sbin/sendmail ENOENT where the host has none). Its Sendmail choice is now Platform mail: email goes through the root site's transport and sender address, under the sub-site's own sender name, with replies to its reply-to address or administration email. The new Own email settings feature (feature.ownMail, on by default) under Platform → Defaults → Features decides whether a sub-site may use its own SMTP or plugin transport; when it is off, the sub-site's email settings are hidden and ignored. The root site is unchanged.

  • Count active plugins for site limits. Inactive plugins made available by the root no longer consume a subsite’s allowance. Activation checks the site’s active count before running the plugin, and quota labels now say Active plugins. (#23)

  • Broken links no longer block static export deploys. A page that returns 404 or 410 during the crawl is now reported as a warning in the export log and the run summary's new warnings list instead of failing the run. Before, one broken link (for example in the footer) failed every run, so object-storage exports were never uploaded and pages were never served with X-Justflows-Render: static-export.