v1.7.21
Back to indexPrevious published version: 1.7.20 (1 October 2026)
Backend: .NET 10 + IIS / Linux nginx
Frontend: Angular 21
A release focused on security and data access. It closes the findings of a complete security review of the framework and brings OData to the same permission level as the CRUD. In short:
enableCookieAuthenticationaccepts a third value,"signed": cookie signed by the server, several sessions per user, logout per single session;- OData applies route and column permissions and the user's row constraints, and writes through the framework CRUD;
- personal tokens let Power BI, Excel and scripts connect to the OData endpoints;
- the "secure upload" flag of upload columns protects the column's files again;
- edit and delete honour the per-user restriction in the UI CRUD as well;
- errors show the user a translated message with a tracking code, technical details only to the superadmin.
Metadata schema changes are applied automatically at startup. The features were verified with end-to-end tests on SQL Server, MySQL, PostgreSQL and Oracle, in the three enableCookieAuthentication modes.
🔐 Authentication and sessions
- Three modes.
enableCookieAuthenticationisfalse,trueor"signed". Withfalsethe user is whoever the browser cookie declares: development only. A[security]message reports it at startup, the AppSettings editor describes it and asks for confirmation on save. An unrecognised value counts as"signed", with a warning in the log. "signed". The server signs thek-usercookie (HMAC-SHA256, expiry inside the signature): a tampered or expired cookie counts as missing. The same user can have several open sessions, each with its own identifier.- Logout and revocation. Logout closes only the session it comes from, copies of the cookie included. An administrator logging out a user, a password reset and a password change close all of that user's sessions in their company.
- Maximum lifetime. With
signedSessionMaxLifetimeHours(default 24,0= no limit) a signed session expires at that age from login, even when used continuously. - Signing key.
cookie-signing-keyis generated on first start and saved inappsettings.json. At startup the log shows the key fingerprint (never the key) and warns if the key was not saved. - Calls verified on the server. With
falsethe cookie no longer carries forged privileges (administrator, role, company): the user is reloaded from the database.
🔗 OData
- Permissions. Every entity set requires read permission on the route. A column denied to the user and named in
$select,$filter,$orderbyor$expandreturns 403; otherwise it comes back empty. - Row constraints. The query starts from the same SELECT as the CRUD, with the per-user, per-role or per-company restriction, the default filter and logical delete.
$filter,$orderby,$skip,$topand$countare applied outside and can only narrow:or 1 eq 1does not widen the result. $expand. Allowed towards readable routes without row constraints or logical delete; 403 otherwise.- Writes.
POST,PATCHandDELETEgo throughinsertRecord,updateRecordanddeleteRecord: permissions, non-editable columns, triggers, logging fields, change log, workflows and logical delete apply as from the UI. A row hidden from the user returns 404. - Multi-company. The data connection is the user's company one, as in the CRUD.
- Discovery.
/odata,/odata/$metadataand/odata/openapi.jsonrequire a session. WithodataPublicMetadata=truethey are public again; the data stays protected. - Personal tokens. With
apiTokensEnabled=trueevery user creates from the user menu ("API tokens")wuic_pat_…tokens with a name, an expiry (default 90 days, maximumapiTokenMaxLifetimeDays) and read-only or read-write permission. They work only on/odata, with the owner's permissions. Power BI and Excel use them as the password with "Basic" credentials; scripts withAuthorization: Bearer. The token is shown only once; the superadmin sees and revokes everyone's.
🛡️ Security
Best-effort hardening across the whole surface: checks on the values that end up in queries (sorting, operators, aggregates, keys, numeric filters) aligned across the four providers; upload and report paths confined to their folders; administration functions (report designer, report removal and scaffolding, column reordering, restart, OData scaffold) reserved to the superadmin verified on the database; workflow emails only from users who can run the workflow; sample data of the AI-assistant tools only to the superadmin and never with credential columns; guards on users and roles bound to the table, not to the route name; licence public key embedded in the package.
📎 Upload
- "Secure upload" per column. The files of a column with
upload_securecan be read only with a valid session and with the column visible to the user, both from/uploadand from/api/UploadImage. The files of the other columns stay public. Columns that store the file in the database are protected by/api/UploadImage. - File names. Names with inner dots (
invoice-acme.com.pdf) are accepted; names with server-executable extensions stay rejected even in the middle (photo.aspx.png).
🚦 Errors
The user gets a translated message with a tracking code ("Copy code" button in the error dialog). The same code is stored with the full stack in _error__logs. SQL, stacks and internal messages reach only the superadmin. Translations of the new error messages are added automatically at startup.
🐛 Notable bug fixes
- Per-user restriction on edit and delete. The CRUD UPDATE and DELETE contained only the key: knowing the id, a user could edit another user's row. Now the row must be one the user can see, otherwise 403
errors.auth.route_read_forbidden. - Default filter with OR filters. With the OR operator the route's default filter disappeared and the grid showed the rows it was meant to hide. Now it stays in AND.
- Oracle. Booleans written as 1/0 also on columns with a boolean UI type, in filters and in stored-procedure parameters; functions told apart from procedures in the right schema; OData on the data schema even with the
SYSTEMuser (before, intermittent ORA-00942 errors). - PostgreSQL and Oracle. Inserting with an identity key not sent by the client no longer fails; reports apply the user's permissions and filters as on SQL Server and MySQL.
- First start under IIS. The initial configuration no longer returns a 500 after a successful installation: migrations complete when the worker restarts.
- Spreadsheet and synchronisation. With an active filter, edit and delete hit the wrong row; a selection beyond the page no longer reaches the records of later pages; the kanban batch save and offline synchronisation retry only what failed.
- Menu. The bar appears with labels already translated, without briefly showing the keys.
📦 Updated packages
| Package | From | To |
|---|---|---|
WuicCore |
1.7.20 | 1.7.21 |
Wuic.Webcore |
1.7.20 | 1.7.21 |
WuicOData |
1.7.20 | 1.7.21 |
RuntimeEfCore |
1.7.20 | 1.7.21 |
Wuic.MySqlProvider |
1.7.20 | 1.7.21 |
Wuic.PostgresProvider |
1.7.20 | 1.7.21 |
Wuic.OracleProvider |
1.7.20 | 1.7.21 |
wuic-framework-lib (npm) |
1.7.20 | 1.7.21 |
🔧 Recommended operational updates when upgrading
- Cookie mode: in production set
enableCookieAuthenticationto"signed"(ortrue);falseis for development only. - Several instances behind a load balancer: copy the same
cookie-signing-keyto every instance; with different keys a session is valid only on the instance that created it. Check in the log that the fingerprint matches. - OData clients: clients that read
$metadatawithout a session must authenticate, or setodataPublicMetadata=true. OData writes now run the CRUD triggers and logging and, on routes with logical delete, set the flag instead of deleting the row. md_service_apply_default_filter: deprecated. The default filter always applies, OData included.- Licence: the machine fingerprint override is set only with the
WUIC_LICENSE_MACHINE_FINGERPRINTenvironment variable; thelicense-machine-fingerprint-overrideandlicense-public-key-pemkeys inappsettings.jsonare ignored. - API tokens: to connect Power BI, Excel or scripts set
apiTokensEnabled=true(and optionallyapiTokenMaxLifetimeDays) from the AppSettings editor.