| Name | Modified | Size | Downloads / Week |
|---|---|---|---|
| Parent folder | |||
| opensparrow-4.1.zip | 2026-10-10 | 3.8 MB | |
| opensparrow-4.1.zip.sha256 | 2026-10-10 | 86 Bytes | |
| README.md | 2026-10-10 | 6.9 kB | |
| Totals: 3 Items | 3.8 MB | 0 | |
Added
- Two-factor login (email code) — an optional second factor, toggled in System → Settings → Login Security and off by default. After a correct password, accounts with a contact email set receive a 6-digit code (valid 60 seconds, 5 attempts, sending throttled to 3/min per IP and per username) and must enter it to finish logging in; accounts without an email keep password-only login. Only the HMAC hash of the code is stored in the login session, wrong codes feed the existing
spw_login_attemptslockout, and a delivery failure fails closed with a generic message instead of silently skipping the factor. Delivery reuses the existing email settings (Cron → Email Delivery / SMTP); new i18n strings are added in all 20 languages. - Streaming answers in Ask AI —
POST api/rag.php?action=querywithstream:truenow replies as NDJSON: ametaevent with the sources first, token-by-tokendeltaevents while the model works, and a finaldoneevent carrying the post-processed answer, extracted follow-up suggestions and cleaned-up record links. The panel renders the preview live and replaces it with the answer of record ondone; the Stop button aborts the stream and releases the concurrency slot. Non-streaming callers keep the old single JSON response, and demo mode still answers200with plain JSON. - Record links in Ask AI answers —
[View: table:id]markers in an answer now render as clickable links to the exact record. The grid context prepends the hiddenidcolumn (it is excluded from the displayed columns by design), so the model is able to cite real identifiers instead of guessing from labels; markers for unknown tables are dropped at render time. - View support in the External API — an API key from System → API can now bind a registered PostgreSQL view (ordinary or materialized) in addition to a table: columns and filter values are re-introspected against the live database on every save and every request, filter type coercion uses the shared
ApiFilterValueCoercerfor both tables and views, and views sort by their first selected column (they have no guaranteedid). A source that no longer fits the live schema answers404fail-closed — never a500from stale configuration. The save response now returns a flat list, so server-assigned ids and generated keys are adopted correctly instead of regenerating on every re-save.
Changed
- Admin CSRF is header-only —
public/admin/api.phpreads the token exclusively fromX-CSRF-Token; the body/form fallback is gone, and raw$_GETreads in admin endpoints were replaced withos_request(). - CSV import refactored into
App\Service—CsvReader,CsvFileValidator,RowCaster,ImportRepository,CsvImportServiceandCopyImportServicemoved out of the endpoint, which is now a pure router. - Menu rendering extracted — the 400-line access-filtering/catalog logic moved from
templates/menu.phptoincludes/menu_builder.php; templates and public entry pages read the session throughos_session_get()instead of$_SESSION. - Cron output is plain text — ETL, notifications and anonymization scripts emit no HTML, share the new
etl_finish_job_log()/etl_persist_watermark_if_changed()helpers, and print generic messages while driver details go to the server error log only; the admin Cron tab renders the output withtextContent. - Version info moved to the footer — the login page lost its version block; the footer now shows the version and a linked logo.
Removed
- The
mocks/design mockup —mocks/share-tables-mock.html(450 lines of unused HTML) is deleted.
Fixed
- Enum values are whitelisted server-side on every write path — grid
PATCH/INSERT, mass edit and the record form (UpdateMapper) reject values outside the column's option list with a specific message; a hand-crafted POST can no longer bypass the<select>into the database. Pinned byEnumWhitelistGuardTestacross all write paths. - Driver-error text no longer reaches the client — generic messages replace
pg_last_error()/getMessage()in CSV import, demo seed, setup API, automation actions and the FK options loader; automation failures log details server-side instead of persisting them intospw_automation_runs.error_msg(which the admin API serves back). Pinned byAutomationErrorLeakTestandAdminPublicErrorLeakTest. - CSV COPY direct-stream column-order corruption — values taken in CSV-header order are now written to columns derived from the same ordering; previously a reordered mapping silently inserted rows into shifted columns with a success response.
- JS sinks escape API data —
health.js,setup.js,user-menu.js,files-page.jsandviews.jsescape interpolated values (escHtml()/textContent), closing the XSS path behind the backend leak above. - The Ask AI on/off switch works —
chat_enabledwas saved by the admin for years but read by nothing; it now gates the query API with403(before rate-limit and semaphore), hides the menu entry and the FAB, and defaults to enabled when unset. - Newly created records open on the first tab — the stale
#tabfragment was dropped from the create redirect.
Upgrade notes
- No database migration is required and no configuration keys are removed by this release. The 2FA toggle lives in the existing
settingsconfig key (two_factor_enabled) and the code reuses thespw_users.emailcolumn added by the3.3_user_contactmigration — apply that migration first if it is still pending, otherwise the Login Security tab note applies and 2FA simply cannot engage. - Two-factor login is opt-in: enable it in System → Settings → Login Security; make sure the email delivery settings (System → Cron → Email Delivery) are working first, otherwise logins for accounts with an email will fail closed at the code step.
- Custom admin integrations must send the CSRF token in the
X-CSRF-Tokenheader — a token posted in the request body is no longer accepted bypublic/admin/api.php. The bundled admin panel already does this (apiFetch). - External API definitions that point at a view must have that view registered and visible in Admin → Views; a stored filter that no longer matches the live column type fails closed with
404instead of being skipped. - External API responses for view-backed keys are ordered by the view's first selected column ascending; table-backed keys keep
id DESC. - The
mocks/directory is removed; nothing in the application references it.
Full changelog: https://github.com/wrobeltomasz/OpenSparrow/compare/4.0...4.1