64 lines
6.4 KiB
Markdown
64 lines
6.4 KiB
Markdown
# 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 `_id` in the add-table form (`client/src/pages/add_table/state.rs:449`);
|
||
- treats every `*_id` name 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 `*_id` columns 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 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 server advertises the Link spelling, but the client cannot create it.
|
||
|
||
**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. Column presentation is managed by the table-definition UI.**
|
||
`SetColumnPresentation` atomically updates aliases and presentation order from the admin table workspace.
|
||
|
||
**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_name` for child references
|
||
- `row_revision` optimistic concurrency; `journal_id` / `recomputed_rows` removal left no client residue
|
||
- per-column currency, `accounting_currency`, `quantity_ledger` in add-table
|
||
- `ListColumnTypes` is fetched and drives the column-type picker
|