feat(profile): self-service name/email/password + desktop installers in CI
Self-service profile: any signed-in user edits their OWN fullName/email and changes their OWN password (proving the current one), without any user:* permission. New routes PUT /api/auth/profile + /api/auth/password act only on req.user.sub (cannot touch username/role), CSRF-guarded; SPA screen at /profile reachable from the header username chip. email added to the session view + SessionUser. 7 tests (routes/profile.test.ts); 148 server tests green. Desktop in CI: new .gitea/workflows/build-desktop.yml builds .deb + .AppImage on every push to dev/main and uploads them as unsigned workflow artifacts (per-commit test build). Signed/versioned release stays on release.yml (tag v*). Wiki: local-jwt-auth (self-service routes), desktop-shell-tauri (two-workflow CI split), log entry. Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
This commit is contained in:
@@ -167,3 +167,19 @@ Per the user's choices — the operator **keeps OS access** (no fullscreen lockd
|
||||
Windows/macOS "unknown publisher", and from the [[atecc608]]/[[tpm]] **event** signing.)*
|
||||
- **Still deferred:** the actual update-hosting URL, OS-level installer signing
|
||||
(Windows/macOS publisher trust), and the Windows kiosk-browser fallback path.
|
||||
|
||||
### Desktop in CI — two workflows, two purposes (added 2026-06-24)
|
||||
|
||||
The desktop bundle now runs in CI under **two distinct workflows** — keep the split clear:
|
||||
|
||||
- **`.gitea/workflows/release.yml`** (tag `v*`) — the **signed, versioned release**: builds
|
||||
`.deb`/`.rpm`/`.AppImage` **+ their `.sig`** (updater key from secrets), assembles `latest.json`,
|
||||
and publishes a Gitea Release. This is what the auto-updater consumes. Unchanged.
|
||||
- **`.gitea/workflows/build-desktop.yml`** (push to `dev`/`main`) — a **per-commit test build**:
|
||||
compiles `.deb` + `.AppImage` only (`pnpm --filter @parking/desktop bundle --bundles deb,appimage`)
|
||||
and uploads them as **workflow artifacts** (14-day retention). **Unsigned** — no `TAURI_SIGNING_*`,
|
||||
no Release, no `latest.json` — so it must NEVER be wired to the updater (an unsigned artifact would
|
||||
be rejected anyway). It exists so each branch push yields a downloadable installer for manual
|
||||
testing of the native shell, and catches a broken Tauri/Rust build early. Same system-deps + cargo
|
||||
cache as `release.yml`. The container images (`build-images.yml`) and the desktop installers are
|
||||
deliberately separate pipelines — the desktop app is **not** containerized ([[container-deployment]]).
|
||||
|
||||
@@ -69,6 +69,21 @@ The SPA never sees the JWT. Login (`POST /api/auth/login`) verifies bcrypt and s
|
||||
requires header == cookie == the signed claim (**double-submit CSRF**). Safe reads are exempt.
|
||||
|
||||
Routes: `login`, `logout` (clears cookies), `me` (bootstraps SPA session on load). The dev
|
||||
|
||||
**Self-service profile (added 2026-06-24).** Alongside the admin user-manager (`routes/users.ts`,
|
||||
gated on `user:*`), any signed-in user has two **self-only** routes (no permission needed — they
|
||||
act solely on `req.user.sub`):
|
||||
- `PUT /api/auth/profile` — edit own `fullName` / `email` (`""` clears → null). Returns the
|
||||
refreshed session (so the SPA header updates). **Cannot** touch `username` or `role` — those stay
|
||||
admin-only, so this is not a privilege-escalation surface.
|
||||
- `PUT /api/auth/password` — change own password, but **must prove the current one** first
|
||||
(`bcrypt.compare`) → defends a walked-up, already-logged-in booth from a silent re-key. New
|
||||
password ≥ 8 chars. Distinct from the admin reset (`PUT /api/users/:id/password`), which needs no
|
||||
current password but DOES need `user:update` + the no-escalation guard.
|
||||
Both are still CSRF-guarded (mutations). The SPA surfaces them at `/profile` (`apps/web/src/Profile.tsx`),
|
||||
reachable from the header username chip. Covered by `apps/server/src/routes/profile.test.ts`.
|
||||
|
||||
The dev
|
||||
[[react-vite-spa|Vite]] proxy and the prod **nginx** reverse proxy keep the SPA and API
|
||||
**same-origin**, so the cookies work without CORS. (This replaced an earlier dev-only
|
||||
`SETUP_AUTH_BYPASS` shim, now removed.)
|
||||
|
||||
+12
@@ -1540,3 +1540,15 @@ Verified the relative-/api + Caddy fix end-to-end locally (Host: parksystems.msa
|
||||
SPA + /api/auth/login reach the server, no CORS). See [[container-deployment]] "Web access",
|
||||
[[appliance-provisioning]]. REMAINING on the box: push dev so CI rebuilds parking-server:dev with the
|
||||
relative-/api fix, then pull on the booth; kiosk autostart; operator user lxd/lpadmin cleanup.
|
||||
|
||||
## [2026-06-24] build | Self-service user profile + desktop installers in CI
|
||||
Two app-side additions. (1) **Self-service profile** — any signed-in user can now edit their OWN
|
||||
`fullName`/`email` and change their OWN password (proving the current one), without any `user:*`
|
||||
permission. New routes `PUT /api/auth/profile` + `PUT /api/auth/password` (act only on `req.user.sub`;
|
||||
cannot touch username/role; CSRF-guarded), SPA screen `apps/web/src/Profile.tsx` at `/profile` (header
|
||||
username chip links to it), `email` added to the session view + `SessionUser`. 7 new tests
|
||||
(`routes/profile.test.ts`); server 148/148 green. Distinct from the admin user-manager (`routes/users.ts`,
|
||||
`user:*`-gated). See [[local-jwt-auth]]. (2) **Desktop in CI** — new `.gitea/workflows/build-desktop.yml`
|
||||
builds `.deb` + `.AppImage` on every push to dev/main and uploads them as UNSIGNED workflow artifacts
|
||||
(per-commit test build); the signed/versioned release stays on `release.yml` (tag `v*`). See
|
||||
[[desktop-shell-tauri]] "Desktop in CI".
|
||||
|
||||
Reference in New Issue
Block a user