6.8 KiB
Client ↔ server drift — auth, rbac, forms
Window: 2026-07-21 → 2026-08-11 (last 3 weeks).
Scope: TUI client (client/) vs server (server/) against the shared API (common/proto). Admin/backup/superadmin tooling is excluded.
Auth
1. Change password is in the client; bootstrap now flows through it (verified, no drift).
Commit 71106a0 "pws reset" replaced SetInitialPassword with two RPCs (common/proto/auth.proto): ChangePassword — the authenticated user changes their own password after the current one is verified, and an empty current password is accepted for freshly seeded admin/superadmin accounts — and ResetUserPassword, which lets a higher-ranked user reset a lower-ranked user's password (server/src/auth/handlers/password.rs). The TUI client now implements ChangePassword (page client/src/pages/change_password, RPC client/src/services/auth.rs): log in with an empty password on a fresh install, then change it. ResetUserPassword stays server-side administration tooling, consistent with item 5.
2. Self-registration is aligned (verified, no drift).
The server ignores any requested role and always creates guest (server/src/auth/handlers/register.rs); the client sends no role (client/src/services/auth.rs) and shows the returned role.
3. JWT slimming is a non-issue for the client (verified).
The server stripped authorization claims from the token (server 70a2062); the client never decodes the JWT, it reads LoginResponse fields (username, role, timezone, phone_country, authorization) which still exist.
RBAC
4. RBAC is intentionally server-only (verified, no drift).
The server owns the full grant model and enforces it on every data-plane call: struct:<area> manage, data:<profile>/<table>, data:<profile>/*, data:*, journal:<profile>, journal:* with read/insert/update/delete (server/src/auth/rbac/objects.rs, guard.rs; commits a5c8e08, e66b025). The client does not download permission snapshots or hide individual controls based on grants. It uses the structural admin/superadmin distinction only to select the administration workspace versus the data-entry workspace; this is presentation routing, not authorization. Server rejections are reported in an error dialog.
5. Role / grant / user administration is intentionally not a client feature (verified, no drift).
The server implements ListRoles, AddRole, RemoveRole, GrantPermission, RevokePermission, ListRolePermissions, ListGrantableObjects, AssignUserRole, ListUsers (common/proto/auth.proto, server/src/auth/grpc.rs). These server-side administration operations are outside the TUI client's scope.
Forms page (table definition + data entry)
6. _id is free on the server, still reserved in the client.
Commits 5be1a5c "_id is free" and 8881041/58b7d0b (alias is surface, physical name internal — user columns are stored under ordinals, the API exposes aliases). The server's system columns are exactly id, deleted, row_revision, created_at (+ account on accounting tables) — common/src/system_column.rs. The client still:
- rejects column names ending
_idin the add-table form (client/src/pages/add_table/state.rs:449); - treats every
*_idname as a system column (client/src/utils/columns.rs:5) — used to hide columns in pickers (picker/object.rs:543,656) and UI service (ui_service.rs:377,441); - filters
*_idcolumns out of the form and converts them into link fields by name-suffix matching (ui_service.rs:104,127–129).
So legal columns are blocked at creation, hidden in forms, or misrendered as links.
7. Link detection is by name, not by type.
Links are now a declarable LINK(table) column type with arbitrary names and multiple links to the same target allowed (751bfd9, 0d78556). The client decides is_link purely from the _id suffix: a LINK column named customer renders as a plain BIGINT text field with no picker, and a non-link column named e.g. invoice_id is hidden or misdetected as a link. (Child-reference resolution does use the new Dependency.column_name — ui_service.rs:204–222 — that part is synced.)
8. The add-table form cannot create LINK or parameterized DECIMAL columns.
The Relations pane lists available tables but selection is display-only (add_table/data.rs:376, ui.rs:311) — nothing is added to the request. The column-type input only accepts bare spellings (supports_column_type requires ColumnTypeSpelling::Bare), and a test asserts decimal(10,2) is rejected (add_table/state.rs:589). The server's ListColumnTypes advertises the Decimal and Link spellings. The two argument-taking types the server supports are uncreatable from the client.
9. required flag is not exposed in the add-table UI.
Commit 29ccd8d added ColumnDefinition.required end-to-end (definition → validation → insert/update enforcement: post_table_definition.rs:372, table_validation/runtime.rs:75). The client always sends required: false (add_table/logic.rs:33) with no UI to mark a column required. The data-entry form does honor required returned by GetTableValidation — that path is synced.
10. account field is bare.
The server exposes physical account_id as API column account — TEXT, slash-delimited, required on ACCOUNTING tables, and sending account_id directly is rejected (server/src/tables_data/account_binding.rs, table_structure/query.rs:258). The client removed its old account_id → accounts special case (client 827e6a4) and added no account handling: the form shows it as a plain TEXT field with no account picker or format validation. The ledger display part was fixed (ledger.rs renders line.account).
11. No alias rename UI.
rename_column_alias is defined in the client's gRPC client (grpc_client.rs:441) but never called; the server's renameable-alias feature (8881041) is unreachable from the client.
12. CreateInvoiceTemplateTable not used.
The server added table bundles generated from Typst invoice templates (a5c8e08); the client ships the typst-template feature but has no call or screen for it.
Verified synced (no drift)
- register (no role, always
guest), login response fields - server-only RBAC enforcement; structural roles select the client workspace, while grants are not interpreted by the client
- mask +
storage_mode(raw/formatted) in validation (forms/validation.rs:261–272) row_display_values/row_display_columns(repeated) in forms, pickers, link display (forms/logic.rs:46,link_display.rs:32)Dependency.column_namefor child referencesrow_revisionoptimistic concurrency;journal_id/recomputed_rowsremoval left no client residue- per-column currency,
accounting_currency,quantity_ledgerin add-table ListColumnTypesis fetched and drives the column-type picker