Commit Graph

1407 Commits

Author SHA1 Message Date
Dries Peeters bcb3cf6fdb Merge branch 'main' into fix/upstream-uploads-and-validator-drift 2026-05-06 06:40:56 +02:00
Dries Peeters a6e0b59b70 Merge pull request #605 from MacJediWizard/fix/upstream-kanban-validator-fallback
fix(kanban): validator falls back to global columns when project has no specifics
2026-05-06 06:40:14 +02:00
Dries Peeters 078c840257 Merge pull request #604 from MacJediWizard/fix/upstream-runtime-bugs
fix: two runtime bugs flagged by flake8 (NameError in auth, UnboundLocalError in timer)
2026-05-06 06:39:55 +02:00
Dries Peeters 346ab886fe Merge pull request #603 from MacJediWizard/ui/hide-donate-when-licensed
fix(ui): hide donate UI on instances with an activated supporter license
2026-05-06 06:39:40 +02:00
MacJediWizard 101eb4abf4 fix(uploads,kanban): write all attachment routes to mounted volume; eliminate validator-drift bugs
Two distinct fix sets in one commit, both extending the kanban validator
fix in PR #605 and the project_attachments path-resolution fix already
discussed in this repo's history.

PHASE A — five upload routes joined current_app.root_path + ".." +
"uploads/<X>", which on a deployed instance with the standard
docker-compose layout resolves to /app/uploads/<X>. That path is
outside the mounted app_uploads volume, so every upload returns 500
with PermissionError. Same defect as project_attachments.

  - app/routes/team_chat.py:470 (chat attachments)
  - app/routes/clients.py:1257 (client attachments)
  - app/routes/comments.py:279 (comment attachments)
  - app/routes/quotes.py:1120 (quote attachments)
  - app/routes/client_portal.py:1330,1347 (legacy "uploads/" download
    fallback branches — same join, same bug)

Fix prepends "app/static/" so the resolved path lands inside the
mounted volume at /app/app/static/uploads/<X>. Mirrors the
invoice_images and quote_images patterns elsewhere in the same files.

PHASE B — validator/UI drift bugs, same class as the kanban fix in #605.

  - app/models/kanban_column.py
      * new helper get_columns_with_global_fallback() — returns
        project columns or falls back to globals; mirrors
        get_valid_status_keys behaviour for templates
      * last-ditch hardcoded fallback in get_valid_status_keys now
        includes "on_hold" so the table-not-yet-seeded path matches
        the keys initialize_default_columns seeds
  - app/routes/tasks.py
      * task_counts now initialises from kanban_columns instead of
        the hardcoded 4 keys; tasks in cancelled/on_hold/custom
        columns are counted in the summary cards
      * create-task validator now calls get_valid_status_keys(project_id)
        instead of a hardcoded 5-key tuple; users creating a task in
        on_hold no longer silently get clamped to todo
      * every render_template("tasks/create.html", ...) and
        ("tasks/edit.html", ...) now passes kanban_columns
  - app/templates/tasks/create.html and tasks/edit.html
      * status <option> list now loops over kanban_columns instead of
        hardcoding 5 keys
  - app/routes/invoices.py:832
      * bulk-update validator now accepts "issued", mirroring the
        single-update validator at line 623; the model supports it
  - app/routes/quotes.py:920, 1026, 1320
      * admin-notification queries now use User.is_admin (which
        considers both the legacy role column AND Role rows) instead
        of User.query.filter_by(role="admin", ...). RBAC-only admins
        granted via the Role table are now notified on quote.sent,
        quote.accepted, and quote.approval.requested.

10 files, +60 / -41. No schema change. No data migration.
2026-05-01 16:00:14 -04:00
MacJediWizard 945340f609 fix(kanban): validator falls back to global columns when project has no specifics
PUT /api/tasks/<id>/status returns 400 "Invalid status" whenever the
task belongs to a project that has no project-specific kanban_columns
rows AND the user drops it into a configured global column other than
the four hardcoded fallback keys.

Reproduction:

  1. Project has no project-specific kanban_columns rows.
  2. The instance has 5 globals (project_id IS NULL): todo, in_progress,
     review, done, on_hold.
  3. The kanban UI renders the 5 globals as drop targets for that
     project's tasks.
  4. User drops a task into "On Hold". Frontend sends status="on_hold".
  5. app/routes/tasks.py:1519 calls
       KanbanColumn.get_valid_status_keys(project_id=task.project_id)
     with the project's id.
  6. get_active_columns(project_id=<id>) filters strictly on project_id
     and returns [].
  7. get_valid_status_keys then falls back to the hardcoded list
       ["todo", "in_progress", "review", "done", "cancelled"]
     which is missing "on_hold" (and includes "cancelled", which isn't
     even a configured column).
  8. "on_hold" is not in that list -> 400.

Drops to the four hardcoded keys all returned 200; only "On Hold"
failed, exactly matching the live 200/400 alternation observed in
production logs.

Fix: when there are no project-specific columns, fall back to the
configured global columns from the database (which is the set the UI
is already rendering). The hardcoded list is only used as a last-ditch
fallback when even the globals table is empty - this preserves the
table-not-yet-seeded safety net during fresh migrations.

Pure validator change; no schema change, no behavioural change beyond
accepting the statuses the UI is already offering.
2026-05-01 15:02:50 -04:00
MacJediWizard 49a4a26b78 fix: two runtime bugs flagged by flake8 in v5.5.2
These were caught by the project's own flake8 step but the failing
checks have been red on a number of recent runs, suggesting it's worth
fixing the underlying defects rather than ignoring the rule.

1. app/routes/auth.py — F821: undefined name 'datetime'

   `current_user.two_factor_confirmed_at = datetime.utcnow()` (line ~620)
   used `datetime` without importing it. Confirming 2FA raises
   `NameError: name 'datetime' is not defined` at runtime.
   Adds `from datetime import datetime` to the imports.

2. app/routes/timer.py — F823: local variable '_' referenced before assignment

   `from flask_babel import gettext as _` is imported at module scope.
   Four functions then unpack `can_start, _ = TimeTrackingService().can_start_timer(...)`
   which makes `_` a function-local for the entire enclosing scope and
   shadows the i18n alias. Three earlier `flash(_("..."))` calls in the
   same functions (lines 171, 449, 2019) reference the local before it
   exists and raise `UnboundLocalError` at runtime.

   Fix: rename the throwaway slot from `_` to `_unused` in all four
   `can_start_timer` unpackings. The translation alias resolves cleanly
   in every flash() call again.

Total: +6 / -4 across two files.
2026-05-01 14:46:37 -04:00
MacJediWizard a24776131b fix(ui): hide donate UI on instances with an activated supporter license
The header support button, the user-menu support link, and the donate /
buy-license buttons inside the support modal were rendered for every
authenticated user, including instances with `donate_ui_hidden = true`
(an activated supporter license). Other donate prompts (sidebar, dashboard,
about, reports, help) already gated on `is_license_activated`; these three
spots slipped through.

Wrap each in `{% if not is_license_activated %}` so a licensed instance
gets a clean UI. The "Love TimeTracker? Share it" button stays visible —
sharing is still useful regardless of license state. Modal title copy
already adapts via the existing `is_license_activated` branch.
2026-05-01 14:24:38 -04:00
Dries Peeters 9773d57725 Merge pull request #602 from DRYTRIX/rc/v5.5.2
Rc/v5.5.2
v5.5.2
2026-04-30 06:23:19 +02:00
Dries Peeters f442bff433 Release 5.5.2
Bump version in setup.py; document release in CHANGELOG and align
BUILD_CONFIGURATION examples with the current patch version.
2026-04-30 06:21:45 +02:00
Dries Peeters 115af37168 feat(admin): undo/redo and wheel zoom for invoice and quote PDF editors
Extract snapshot reload from saved-design loading and reuse it for
history restore so undo matches save semantics.

Keep a capped stack of stage.toJSON() snapshots with debounced pushes
after drags, transforms, property panel edits, alignment/layer moves,
adds, deletes, and related actions.

Wire Ctrl/Cmd+Z and Ctrl/Cmd+Y (plus Ctrl/Cmd+Shift+Z for redo)
outside focused inputs; add non-passive wheel handling on the canvas
container to zoom within existing scale limits.

Document shortcuts and wheel zoom in the editor info box (i18n-ready).
2026-04-30 06:17:24 +02:00
Dries Peeters 9d4be6feec fix(admin): prefer form template_json for invoice PDF preview
When the layout editor posts template_json with the preview request,
use it instead of loading only the saved database template. Preview
then matches unsaved canvas edits and avoids stale layouts.

Normalize page width/height from the selected page size when parsing
form JSON; fall back to the stored template if the body is missing
or invalid.

Add a regression test ensuring form JSON overrides DB content.
2026-04-30 06:17:21 +02:00
Dries Peeters f55de4f579 Merge pull request #599 from DRYTRIX/rc/v5.5.1
Rc/v5.5.1
v5.5.1
2026-04-29 13:25:14 +02:00
Dries Peeters 282e7deb57 Merge remote-tracking branch 'Origin/main' into rc/v5.5.1 2026-04-29 13:23:15 +02:00
Dries Peeters 135dba9a85 chore(release): bump version to 5.5.1
Update build/versioning docs to match setup.py.
2026-04-29 12:53:15 +02:00
Dries Peeters fb734fa91c chore(docs): align API and permissions docs with implemented behavior
Fix stale build-guide links, document the implemented quotes API scopes/endpoints, and clarify quote access plus permission-denial behavior so docs match route and test-backed behavior.
2026-04-29 10:42:49 +02:00
Dries Peeters 278bb666ff chore(docs): document quote edit redirect fix in changelog
Record the Unreleased note describing the quote visibility alignment for users with edit permissions and the related regression coverage so release notes stay accurate.
2026-04-29 10:27:28 +02:00
Dries Peeters 887c93f00c fix(quotes): align list/detail scope with quote edit permissions
Ensure quote list/detail access uses shared quote scope resolution so users with quote-management permissions can view records they can edit, including post-edit redirects in web and API flows. Add regression coverage for non-admin edit_quotes behavior and document the scope-alignment requirement in advanced permissions docs.
2026-04-29 10:27:00 +02:00
Dries Peeters 443ecd8258 fix(i18n): repair Portuguese PO format placeholders for runtime gettext
Argos and similar MT often corrupt %(name)s (e.g. "% (horas)") or swap in
positional %s, causing ValueError during dashboard render.

- Add scripts/sanitize_po_format_strings.py to clear invalid msgstr / plural
  strings so gettext falls back to English msgids.
- Run sanitizer on translations/pt; msgfmt --check-format now passes.
- Document sanitizer + msgfmt after bulk fill in TRANSLATION_SYSTEM and
  fill_po_argos header.
2026-04-29 09:05:31 +02:00
Dries Peeters 0275be9013 chore(i18n): sync gettext catalogs and fill Portuguese via Argos
- Fix babel.cfg with [extractors] so pybabel resolves jinja2 templates on
  toolchains where babel.extractors entry points are not loaded.
- Regenerate messages from source: extract POT, update all locales, drop
  obsolete entries (--ignore-obsolete). Portuguese msgstr filled with
  offline Argos en→pt (machine output; human QA still recommended).
- Add scripts/fill_po_argos.py for optional first-pass locale fills.
- Gitignore root messages.pot; document extract/update/Argos in
  TRANSLATION_SYSTEM and CONTRIBUTING_TRANSLATIONS.
2026-04-29 08:23:21 +02:00
Dries Peeters eb7b1be05f feat(i18n): add Portuguese (pt) locale and translation scaffold
Register Português in LANGUAGES and normalize pt-BR/pt-PT (and similar)
to pt in _normalize_locale so Accept-Language and stored preferences resolve
to translations/pt/.

Add translations/pt/LC_MESSAGES/messages.po seeded from English msgids;
translators can fill msgstr incrementally.

Extend i18n tests for pt presence and catalog file. Update translation
docs (TRANSLATION_SYSTEM, CONTRIBUTING_TRANSLATIONS, implementation note).
2026-04-29 06:53:24 +02:00
Dries Peeters d7a9260c93 Merge pull request #596 from DRYTRIX/rc/v5.5.0
Rc/v5.5.0
v5.5.0
2026-04-27 22:24:52 +02:00
Dries Peeters 6b771537d4 chore(release): version 5.5.0 and documentation sync
- Fix setup.py version string (missing quote).
- Promote CHANGELOG [Unreleased] entries to [5.5.0] (2026-04-27); leave empty [Unreleased].
- Update BUILD_CONFIGURATION.md example version to match setup.py.
2026-04-27 22:23:44 +02:00
Dries Peeters ac74218fc9 refactor(ui): unify bottom-right FAB dock and refresh docs
Replace the separate plus and bolt floating controls with a single Actions menu inside #fabDock, driven by app/static/floating-actions.js. The dock stacks Actions, optional team chat, and AI Helper using shared CSS variables for spacing; the AI control is a circular FAB matching the other buttons.

Move the chat widget panel to a fixed viewport overlay so dock z-index no longer paints controls over the open panel, and lift the panel bottom when the admin version banner or mobile bottom nav applies. Fade non-actions dock children while the actions menu is open (fab-dock--menu-open).

Update README.md, docs/UI_GUIDELINES.md, and the advanced-features implementation summaries so contributors describe the floating hub instead of global-fab.js. Keep app/static/quick-actions.js aligned with the retired mount pattern for any remaining references.
2026-04-27 22:22:00 +02:00
Dries Peeters bf4c34ff83 feat(docker): bundle Ollama and wire AI helper in compose
- Add ollama and ollama-init services with ollama_data volume; app waits for the model pull and receives AI_* defaults (AI_BASE_URL=http://ollama:11434).

- Document bundled stack, env vars, and Ollama vs host base URL in README and DOCKER_COMPOSE_SETUP.

- Align env.example AI defaults with the compose stack.

- fix(ai): include api_key_set on AIProviderConfig so from_settings(**get_ai_config()) matches the settings dict.
2026-04-27 21:54:03 +02:00
Dries Peeters 5d4e693a2b Add LDAP setup wizard on Integrations and admin routes
Introduce a guided LDAP configuration wizard mirroring the OIDC flow:
five-step UI with server/TLS, bind, directory layout, groups and
AUTH_METHOD, then optional connection test and .env / Docker Compose
generation for copy-paste deployment.

- Refactor LDAPService.test_connection to accept an optional config
  mapping so the wizard can test draft values without merging live env
  secrets; keep POST /admin/ldap/test on current_app.config.
- Add GET /admin/ldap/setup-wizard plus POST endpoints for test,
  validate, and generate-config (manage_settings, rate limited).
- Surface an LDAP card with status badge and wizard link on the
  integrations list for admins and manage_settings users.
- Add tests for validate, generate, and wizard test delegation.
2026-04-27 20:21:34 +02:00
Dries Peeters 6c57ba775a fix(templates): remove stray closing divs on import/export and list pages
Extra or unmatched </div> tags inside {% block content %} closed layout
ancestors early, which broke the centered main column and stacked modals
and scripts incorrectly.

- import_export/index.html: drop duplicate grid closer
- saved_filters/list.html: remove orphan closer after page body
- time_entry_templates/list.html: same orphan pattern as saved filters
2026-04-27 20:21:28 +02:00
Dries Peeters 6c8e86cd01 fix(timer): respect Settings.single_active_timer at runtime
Timer starts always blocked a second running entry and never read the\nadmin-controlled Settings flag.\n\n- Add TimeTrackingService.can_start_timer() using Settings.get_settings()\n  and wire it into start_timer, web timer routes, kiosk start, and\n  legacy POST /api/timer/resume.\n- POST /api/v1/timer/start returns 409 with error_code\n  timer_already_running when single-active mode is on and a timer\n  is already running.\n- Deduplicate start_timer template handling in the service.\n\nTests: tests/test_single_active_timer_setting.py.\nDocs: REST_API (responses), GETTING_STARTED, REQUIREMENTS, Docker env\nnotes, TESTING_STRATEGY, env.example comment; CHANGELOG entry.
2026-04-27 19:16:25 +02:00
Dries Peeters e34a668ddc feat(auth): add LDAP directory authentication
Introduce AUTH_METHOD values ldap and all, with LDAP_* environment settings, ldap3-based LDAPService (search, optional groupOfNames checks, user bind, DB sync), and users.auth_provider (local|oidc|ldap) via migration 153_add_user_auth_provider.

Login supports LDAP-only and combined all (local then LDAP where appropriate); OIDC callback sets auth_provider. Forgot/reset/change password flows skip LDAP-managed accounts. Admin System Settings gains a read-only LDAP summary and POST /admin/ldap/test. Production env validation requires core LDAP variables when LDAP is enabled; OIDC registration and docs recognize all.

Documentation: new docs/admin/configuration/LDAP_SETUP.md; updates to OIDC_SETUP, GETTING_STARTED, Docker guides, Render deploy notes, docs README, and CHANGELOG. Tests: tests/test_ldap_auth.py; test_oidc_logout allows auth_method all.
2026-04-27 19:08:08 +02:00
Dries Peeters 8fc823c252 feat(pwa): static manifest, root-scoped worker, offline fallback
Add app/static/manifest.json (TimeTracker / Tracker, indigo theme) and PNG install icons via scripts/generate_pwa_icons.py.

Replace inline Flask service worker with app/static/js/sw.js served at /service-worker.js for full-site scope. Cache name timetracker-v1: cache-first for /static, network-first for HTML and non-v1 /api, no interception of /api/v1/* (preserves Authorization).

Add public GET /offline and offline.html for SW navigation fallback; redirect /manifest.webmanifest to the static manifest.

Wire base.html (manifest link, theme-color #4F46E5, SW registration) and pwa-enhancements.js (ready/update/push without duplicate registration). Remove legacy app/static/service-worker.js and manifest.webmanifest.

Tests: service worker and offline routes, manifest redirect, TestPWA expectations; drop duplicate test_enhanced_ui app/client fixtures in favor of conftest.

Docs: ASSETS.md, BUILD_CONFIGURATION.md, implementation notes, and incomplete-features analysis updated for new paths.
2026-04-27 18:43:14 +02:00
Dries Peeters f2e81dfaef feat(billing): invoice all unbilled time for a client from API and UI
Add POST /api/v1/clients/{client_id}/invoice-unbilled to build one draft
invoice from completed billable time not yet on any invoice line, grouped
by project with RateOverride-based rates. Supports API tokens
(write:invoices) or a logged-in user with create_invoices; enforces client
and module access.

InvoiceService gains shared preview/state helpers and
create_client_unbilled_invoice with safe_commit and invoice webhook.

Client detail page adds a confirmation flow and redirect to the new
invoice. Document read:invoices / write:invoices and the new route in
docs/api/API_TOKEN_SCOPES.md; expose the path on the v1 discovery payload.

Tests: two-project client creates two line items; second call returns
no_unbilled_entries.
2026-04-27 18:15:24 +02:00
Dries Peeters 07dbbd54a3 docs(api): document GET /api/reports/week-comparison
Describe the session JSON endpoint used by the main dashboard week
comparison chart: partial Monday-to-today window, parallel prior week,
dense by_day series, and null change_percent when last week has no hours.

Note the path with other internal dashboard routes in API_VERSIONING.md.
2026-04-27 17:53:54 +02:00
Dries Peeters d7acfb9ff2 Add global time FAB, inline time-entry edits, and week comparison chart
Global quick actions (FAB):
- Add a fixed primary FAB with an animated popover (Start Timer, Log Time,
  New Task) for authenticated users in base.html.
- Start Timer uses the dashboard control when present, otherwise opens the
  dashboard with #start-timer so the existing start modal appears.
- Hide the FAB from the md breakpoint up while the header floating timer is
  active (floating-timer-bar.js toggles body.fab-hide-desktop-timer-active).

Time entries overview:
- Make Notes and Duration editable in the table where permissions allow;
  save via same-origin PUT/PATCH /api/entry/<id> (time-entries-inline-edit.js).
- Register PATCH on the session update_entry route alongside PUT.

Dashboard:
- Add a this-week vs last-week hours chart fed by GET /api/reports/week-comparison
  and dashboard-enhancements.js.

Documentation:
- Extend UI_GUIDELINES (FAB, inline edit, file reference) and ARCHITECTURE
  (session JSON endpoints) to match the new behavior.
2026-04-27 17:53:12 +02:00
Dries Peeters b4e0b69796 feat(web): mobile bottom navigation with More drawer
Add an authenticated-only bottom bar below the md breakpoint with
Heroicon-style tabs for Dashboard, Timer, Time entries, Projects, and
More. More opens a slide-up sheet (backdrop, close, Escape) for
Invoices, Clients, Reports, and user Settings, gated by module flags
where applicable.

Align shell layout to Tailwind md (768px): sidebar hidden md:flex,
main md:ml-64 / md:ml-16 when collapsed, mobile hamburger md:hidden,
RTL mainContent margin reset at 767px. Main column uses pb-16 on
small screens so content clears the bar; bar and sheet use pb-safe
(env safe-area) with a Tailwind safelist and @layer utilities rule.

Remove the legacy six-slot FAB bottom nav from base.html.

Docs: README UI overview, CHANGELOG [Unreleased], UI_GUIDELINES layout
and file reference.
2026-04-26 09:16:51 +02:00
Dries Peeters 4eabe3cabd feat(ui): add global Ctrl/Cmd+K command palette
Adds a Tailwind-styled global command palette (Ctrl/Cmd+K) with fuzzy search and a Start Timer flow that picks a project then POSTs /api/timer/start with CSRF.
Updates docs to reflect the new shortcut.
2026-04-26 09:03:54 +02:00
Dries Peeters df475ae437 refactor(ui): refresh Tailwind design system tokens
Adopt an indigo brand palette with slate neutrals, add semantic colors, and introduce base/component layers (buttons, cards, badges). Move Inter loading to the Tailwind input CSS and update brand guidelines accordingly.
2026-04-26 08:34:08 +02:00
Dries Peeters dc76042ab8 docs(install): document settings encryption key
Add install-time guidance for SETTINGS_ENCRYPTION_KEY generation to support encrypting stored secrets.
2026-04-26 07:56:01 +02:00
Dries Peeters ea913c6c4b feat(ai,security): add web AI helper, secret encryption, and 2FA
Introduce a web-first AI helper with admin-configurable providers (Ollama or hosted OpenAI-compatible), server-side context building, and confirmed write actions. Expose the feature via session /api/ai/* endpoints and scoped /api/v1/ai/* endpoints.

Harden security by requiring a strong SECRET_KEY for Docker Compose, adding optional settings encryption-at-rest (Fernet), and introducing TOTP-based 2FA plus password reset flows. Update admin UI, API docs, and install documentation.
2026-04-26 07:55:47 +02:00
Dries Peeters 7777de7ba2 Merge pull request #594 from DRYTRIX/rc/v5.4.0
Rc/v5.4.0
v5.4.0
2026-04-25 18:10:27 +02:00
Dries Peeters ff8fddeea7 Merge pull request #595 from DRYTRIX/develop
fix(desktop): use modern Node for Vite builds
2026-04-25 18:10:04 +02:00
Dries Peeters d52266370f fix(desktop): use modern Node for Vite builds
Run desktop packaging workflows on Node 24 and load Vite through an ESM config so macOS, Linux, and Windows builds use a runtime compatible with Vite 7.
2026-04-25 17:56:33 +02:00
Dries Peeters d161b399b9 Version Bump 2026-04-25 17:38:35 +02:00
Dries Peeters 053dff1a01 Update 2026-04-25 17:37:24 +02:00
Dries Peeters f9fc427d80 chore(desktop): normalize renderer file modes
Keep the new React renderer sources as regular tracked files so packaging changes do not include accidental executable bits.
2026-04-25 17:34:31 +02:00
Dries Peeters e6e49dedd7 feat(desktop): rebuild renderer with React and Vite
Move the desktop app onto a Vite-powered React shell with username/password setup, diagnostics, themed core views, offline sync queueing, and tighter Electron runtime boundaries.
2026-04-25 17:34:20 +02:00
Dries Peeters 443a6b87bf fix(api): support desktop app login and CORS
Allow the desktop renderer to authenticate through the app login endpoint and call API routes from its packaged origin without weakening non-API responses.
2026-04-25 17:34:15 +02:00
Dries Peeters bd5d4d0cc7 fix: client portal projects layout and desktop app refresh
Client portal: add min-w-0, break-words, and flex gap/shrink utilities on
the projects grid cards so long project names no longer force horizontal
overflow and clip against the viewport edge.

Desktop: add app and tray icons, adjust Electron main process (window,
tray, lifecycle), renderer connection and API client updates, build script
and package metadata, and regenerate the bundled renderer script.
2026-04-25 08:07:16 +02:00
Dries Peeters 89623682c8 fix(security): sandbox Jinja2 for database-backed PDF and email templates
Add render_sandboxed_string() using SandboxedEnvironment so stored invoice and
quote HTML, ReportLab text templates, admin PDF previews, and invoice email
HTML are not evaluated with Flask's full template globals (mitigating SSTI).

Add regression tests for sandbox behavior and demo user permissions.
2026-04-24 21:13:29 +02:00
Dries Peeters 5a89d5f5df fix(security): treat DEMO_MODE account as a standard user, not admin
Create the auto-provisioned demo user with legacy role "user" and the RBAC
user role so public demos cannot reach settings or the PDF layout editor.
On startup, downgrade an existing demo user that still has admin or
super_admin roles left over from older releases.

Document behavior in docs/deploy/RENDER.md and README.md.
2026-04-24 21:13:25 +02:00
Dries Peeters 80d97f1514 Merge pull request #593 from DRYTRIX/rc/v5.3.2
Rc/v5.3.2
v5.3.2
2026-04-24 16:20:02 +02:00