diff --git a/Infra-Network/AUTH-USERS-SPEC.md b/Infra-Network/AUTH-USERS-SPEC.md new file mode 100644 index 0000000..d2ba6ee --- /dev/null +++ b/Infra-Network/AUTH-USERS-SPEC.md @@ -0,0 +1,95 @@ +# SPEC — Users, Access Matrix, registration, sessions and security + +**Date:** 2026-09-19 +**Status:** hardened Users + Access Matrix; **SMTP configurado e validado (2026-09-19)**. +**Module:** `backend/app/modules/users` · Admin UI `/users` · `/users/access` + +## Design references (not a framework swap) + +Patterns adapted from production FastAPI auth libraries (kept in-house RBAC matrix): + +- [fastapi-fullauth](https://github.com/mdfarhankc/fastapi-fullauth) — lockout, hashed reset tokens, email verification/reset, RBAC permissions, anti-enumeration +- [AuthWarden](https://github.com/timihack/authwarden) — single-use reset tokens, OTP attempt limits, brute-force lockout +- OWASP Authentication / Session / Password-reset cheat sheets + +RentCharge keeps its own SQLite models, cookie sessions and **permissions-first Access Matrix** (`module.view.action`). + +## Functional scope (both sides) + +### A — Self-service (`/login`, `/signup`, `/reset-password`, `/account`) + +1. Public registration with strong password + email OTP verification. +2. Forgot-password → single-use email token (SHA-256 stored, 1 hour). +3. Account profile, email verification, password change with strength meter. +4. Forced password change when `must_change_password` is set. + +### B — Admin Users + Access Matrix (`/users`, `/users/access`) + +1. Create staff logins with **generated strong passwords** (not shared starter defaults). +2. Edit login (username, nickname, display name, recovery email). +3. Set password (modal + strength meter) **or** email a reset token. +4. Require password change on first login / after admin reset. +5. Role assignment + full Role × Permission matrix with dirty-state and default-drift highlight. +6. Sessions revoke + security activity audit. + +## Security controls + +- **Password rules are chosen by Admin** under `/users/password-policy` (not hard-coded). + Presets: PIN 6+ digits · alphanumeric · strong · complex · custom toggles + (uppercase / lowercase / digit / special / min length / block nick-email / block common). +- Passwords: bcrypt cost 12; validation always follows the saved admin policy. +- Account lockout after 5 consecutive failed logins (15 minutes) + IP/identity rate limits. +- Reset tokens: 256-bit random, stored as SHA-256 only, single-use, invalidate previous tokens. +- Registration OTP: HMAC-SHA256, 10 min, max 5 attempts. +- Sessions: JWT + server-side session id; HttpOnly Secure SameSite=Strict cookie; idle + absolute timeouts. +- Logout / password change / reset / disable revoke sessions. +- Generic public responses to reduce account enumeration. +- Passwords and raw tokens never written to `auth_events`. + +## New / updated API + +| Method | Path | Purpose | +|--------|------|---------| +| GET | `/api/auth/password-policy` | Policy hint for UIs | +| POST | `/api/auth/password-strength` | Strength score helper | +| POST | `/api/users/{id}/send-password-reset` | Admin emails recovery token | +| GET | `/api/users/generate-password` | Cryptographically strong starter | +| PUT | `/api/users/{id}` | Now accepts `username` (edit login) | +| POST | `/api/users` | `must_change_password`, `send_invite_email` | +| POST | `/api/users/{id}/set-password` | `must_change_password` flag | + +## Database (additive) + +`users` columns: + +- `must_change_password` +- `failed_login_count` +- `locked_until` + + +## Mail transport (VM500) + +**Validated 2026-09-19** — OTP verification and password-reset emails delivered. + +| Setting | Value | +|---------|--------| +| Host | `mail.myvexx.com` | +| Port | `465` (SMTP SSL) | +| User | `admin@myvexx.com` | +| From | `Rent & Charge ` | +| Config | `/home/mini/rentcharge/config/rentcharge.env` (symlink `/etc/rentcharge.env`) | +| Also | Postfix on VM500 relays `[mail.myvexx.com]:465` with SASL | + +App flags: `SMTP_SSL=true`, `SMTP_STARTTLS=false`. Credentials stay only in env (never in Git). + +E2E checks: `POST /api/auth/forgot-password` → HTTP 200; inbox received **Rent & Charge password reset** and **Your Rent & Charge verification code**. + +## Verification checklist + +- [ ] Weak passwords rejected on register / change / admin set +- [ ] Generate strong password fills create form +- [ ] Edit login updates username + email +- [x] Email reset from Users works when SMTP configured +- [ ] Forced change redirects to `/account` +- [ ] Access matrix dirty badge + amber default-drift cells +- [ ] Backend tests: `python -m unittest backend.tests.test_auth_users` diff --git a/Infra-Network/SPEC-MONEY-PRIVACY.md b/Infra-Network/SPEC-MONEY-PRIVACY.md new file mode 100644 index 0000000..3947566 --- /dev/null +++ b/Infra-Network/SPEC-MONEY-PRIVACY.md @@ -0,0 +1,42 @@ +# SPEC — Money privacy (eye button) + +**Produto:** Rently / RentCharge +**Data:** 2026-09-19 +**Status:** implemented (prod + test) +**Versão:** 007.2026 +**Operador:** Roger + +**Relacionados:** `SPEC-SYSTEM-DASHBOARD.md` · `SPEC-SESSION-2026-09-19.md` + +--- + +## Purpose + +Operators often work in open offices. Euro amounts start **hidden** (`€ ••••`). One control (header + page toolbars) reveals or hides **all** sensitive amounts together. After **30 seconds** idle, amounts hide again. + +## UI + +| Control | Location | +|---------|----------| +| Show € / Hide € | AppShell header (all authenticated pages) | +| Same button | Page toolbars (Dashboard, Residents, Mapa, Receivables, …) | + +## Technical contract + +1. Module flag: `frontend/src/lib/moneyFormat.ts` — `euro()`, `privacyMoneyLabel()`, `setMoneyHiddenFlag()`. +2. Flag changes **notify** subscribers (`subscribeMoneyHidden`). +3. React: `useMoneyPrivacyRender()` / `useMoneyPrivacy()` use `useSyncExternalStore` so components re-render when the eye toggles. +4. **Why not only AppShell?** Next.js App Router does not re-render the page slot when a Client layout updates state. Every page that displays euros must subscribe (or use a child that does). + +## Rules + +- Print / statement handouts use `euroVisible()` (never masked). +- Counts and percentages are not masked. +- Default on load: **hidden**. + +## Acceptance + +1. Dashboard: eye toggles amounts. +2. `/residents`, `/mapa`, `/receivables`, resident detail: same toggle updates amounts without navigation. +3. Idle ~30s → amounts hide again. +4. Prod (`:3000`) and test (`:3010`) behave the same. diff --git a/Infra-Network/SPEC-RESIDENT-PROFILE-STATUS.md b/Infra-Network/SPEC-RESIDENT-PROFILE-STATUS.md new file mode 100644 index 0000000..75fd102 --- /dev/null +++ b/Infra-Network/SPEC-RESIDENT-PROFILE-STATUS.md @@ -0,0 +1,58 @@ +# SPEC — Resident profile status flags + audit + +**Produto:** Rently / RentCharge +**Data:** 2026-09-19 +**Status:** implemented (prod + test) +**Versão:** 007.2026 +**Operador:** Roger + +**UI:** `/residents/{id}` Setup · Settings → Edit resident + +--- + +## Flags (boolean on `residents`) + +| UI label | Column | +|----------|--------| +| Temporary | `is_temporary` | +| Deceased | `is_deceased` | +| In custody | `is_in_custody` | +| In treatment | `is_in_treatment` | + +(`is_active` remains a separate system flag; not part of this audit log.) + +## Audit log + +Table `resident_status_events` (append-only): + +| Column | Meaning | +|--------|---------| +| `resident_id` | FK | +| `flag` | e.g. `is_temporary` | +| `value` | `true` = checked / On · `false` = unchecked / Off | +| `changed_at` | UTC store | +| `changed_by_user_id` / `changed_by_username` | actor | + +**History is never deleted** when a box is unchecked — a new Off event is appended. + +## Display + +Beside each of the four checkboxes: + +- Latest event: `On|Off dd/mm/yyyy HH:mm · username` (formatted **Europe/Dublin**) +- Full recent history in the **tooltip** (`title`) + +Helper: `frontend/src/lib/statusFlags.ts`. + +## API + +- `ResidentOut.status_flags`: map flag → `{ last, history }` on get/create/update when `include_status_flags=True`. +- `GET /api/residents/{id}/status-history` — same map. +- Create/update log diffs for the four flags with the authenticated user. + +## Acceptance + +1. Check Temporary → stamp + user appear beside the box after save. +2. Uncheck → Off event logged; history still available on hover. +3. Settings edit shows the same stamps (loads full resident via `getResident`). +4. Prod + test. diff --git a/Infra-Network/SPEC-RESIDENTS-HUB.md b/Infra-Network/SPEC-RESIDENTS-HUB.md new file mode 100644 index 0000000..6681193 --- /dev/null +++ b/Infra-Network/SPEC-RESIDENTS-HUB.md @@ -0,0 +1,57 @@ +# SPEC — Residents hub (`/people`) + +**Produto:** Rently / RentCharge +**Data:** 2026-09-13 +**Status:** implementado +**Operador:** Roger + +**Relacionados:** `SPEC-SYSTEM-DASHBOARD.md` · Statement service · `SPEC-STATEMENT-PUBLIC-URL.md` + +--- + +## Problema + +A pesquisa do header enviava para o **Dashboard**, que não é uma vista de residente. Existiam listas em Control of Rent / Arrears / Maps, mas **não** um hub de consulta/seleção/edição rápida. + +## Solução + +| Item | Valor | +|------|--------| +| Rota | `/people` | +| Menu | **Residents** (antes de Control of Rent Payments) | +| Header search | → `/people?q=…` | +| Layout | Header estilo ROI Finance; lista à esquerda; painel à direita | + +## Painel do residente seleccionado + +- Rent arrears / debt **YTD** (mesmas regras do Statement) +- Semanas pagas / overdue no ano (semanas de calendário, incl. sem linha SAGE) +- Credit Book (débito/crédito) +- Acordos abertos +- Último pagamento +- Quick edit: room, notes, active +- Links: full record `/residents/{id}`, Statement, Add new resident + +## Regra de cálculo (obrigatória) + +**Não** somar só semanas com ficheiro SAGE. Usar `build_statement` / `fetchStatementJson` YTD (`01-01` → hoje), igual ao Statement impresso. + +Exemplo validado: **John J** D59 SAGE 411 → **£513** (não £62). + +Backend: `enrich_resident` sem `date_from/to` também usa Statement YTD para `total_debt` / `year_debit`. + +## Ficheiros + +- `frontend/src/app/people/page.tsx` +- `frontend/src/components/AppShell.tsx`, `frontend/src/lib/access.ts`, `frontend/src/lib/api.ts` (`fetchStatementJson`) +- `backend/app/main.py` (`enrich_resident`) + +## Critério de aceite + +1. Pesquisa header → Residents hub. +2. John J e amostra de 3 residentes: debt = Statement YTD. +3. Prod + test (mesmo frontend). + +## Profile status (007.2026) + +Flags Temporary / Deceased / In custody / In treatment on the full resident record (`/residents/{id}`) and Settings edit — with audit stamps. See `SPEC-RESIDENT-PROFILE-STATUS.md`. diff --git a/Infra-Network/SPEC-SESSION-2026-09-19.md b/Infra-Network/SPEC-SESSION-2026-09-19.md index 6c84e62..2852235 100644 --- a/Infra-Network/SPEC-SESSION-2026-09-19.md +++ b/Infra-Network/SPEC-SESSION-2026-09-19.md @@ -2,7 +2,7 @@ **Status:** implemented · **Operador:** Roger **Ambientes:** prod + test (VM500) -**Versão:** **006.2026** +**Versão:** **006.2026** → noite **007.2026** --- @@ -65,3 +65,57 @@ - `npm run build` prod + test (`NEXT_PUBLIC_APP_ENV=test` no test) - `systemctl restart` `rentcharge-{backend,frontend}` + `rentcharge-test-*` - Specs → Forgejo `myvexx/rently` + Obsidian CT130 via `scripts/sync_obsidian.py` + + +--- + +## Continuação (noite 2026-09-19) — versão **007.2026** + +### 7. Money privacy (olho €) — app-wide + +**Problema:** o botão Show € / Hide € só actualizava o Dashboard. Nas outras páginas os valores ficavam congelados (Next.js App Router: o layout/AppShell re-renderiza, mas o slot da página não). + +**Solução:** +- `moneyFormat.ts` — pub/sub (`subscribeMoneyHidden` / `setMoneyHiddenFlag` notifica listeners) +- `MoneyPrivacy.tsx` — `useMoneyPrivacyRender()` via `useSyncExternalStore`; `useMoneyPrivacy()` também subscreve +- Todas as páginas/componentes que mostram euros chamam o hook (Residents, Mapa, Receivables, ROI, Reports, ficha residente, KPIs, QuickPost, …) +- Idle 30s continua a esconder montantes +- Deploy: **prod + test** (VM500) + +### 8. Profile status — audit log (4 flags) + +Checkboxes na ficha do residente + Settings edit: + +| Flag | Campo | +|------|--------| +| Temporary | `is_temporary` | +| Deceased | `is_deceased` | +| In custody | `is_in_custody` | +| In treatment | `is_in_treatment` | + +- Tabela append-only `resident_status_events` (flag, value on/off, `changed_at`, `changed_by_user_id`, `changed_by_username`) +- Ao lado de cada checkbox: último evento `On|Off dd/mm/yyyy HH:mm · user` (Europe/Dublin) +- Histórico completo no tooltip; mantém eventos ao desmarcar +- API: `status_flags` em `ResidentOut` (get/update/create); `GET /api/residents/{id}/status-history` +- UI: `/residents/[id]` + `/settings` edit modal + +### 9. SMTP / auth mail (já em AUTH-USERS-SPEC) + +- `mail.myvexx.com:465` SSL · `admin@myvexx.com` · E2E forgot-password + OTP OK +- Credenciais só em `rentcharge.env` (não no Git) + +### 10. Cybersec hardening (F2–F6 + F1 scrub docs) + +- TLS verify no envio de email; cookie-only JWT web; path reset `/reset-password/{token}`; tips anti-phishing nos templates; docs sem passwords em claro + +## Ficheiros (007) + +- `frontend/src/lib/moneyFormat.ts`, `components/MoneyPrivacy.tsx`, páginas com euros +- `frontend/src/lib/statusFlags.ts`, `app/residents/[id]/page.tsx`, `app/settings/page.tsx` +- `backend/app/database.py` (`ResidentStatusEvent`), `schemas.py`, `main.py` +- `docs/SPEC-MONEY-PRIVACY.md`, `docs/SPEC-RESIDENT-PROFILE-STATUS.md`, `docs/releases/007.2026.md` + +## Deploy 007 + +- `npm run build` + restart `rentcharge-*` e `rentcharge-test-*` +- Specs → Forgejo `myvexx/rently` + Obsidian CT130 diff --git a/Infra-Network/SPEC-SYSTEM-DASHBOARD.md b/Infra-Network/SPEC-SYSTEM-DASHBOARD.md new file mode 100644 index 0000000..9698665 --- /dev/null +++ b/Infra-Network/SPEC-SYSTEM-DASHBOARD.md @@ -0,0 +1,51 @@ +# SPEC — System Dashboard + +**Date:** 2026-09-09 +**Status:** implemented on VM500 (`/`) +**Language:** English UI + +## Purpose + +The Dashboard is the system's presentation and navigation page. It summarizes the complete resident group and does not duplicate the Residents data grid. + +## Route separation + +- `/` — consolidated system Dashboard. Do not put the Cash (SA) grid here. +- `/residents` — **Rent Payments Control**: complete resident register and Cash (SA) account grid. +- `/residents/{id}` — individual resident account dashboard. +- The sidebar item **Rent Payments Control** links to `/residents`; **Dashboard** remains `/`. + +## Branding + +- The Salvation Army shield (`/salvation-army-shield.svg`) replaces the red `SA` badge in the **sidebar** (white circular background). It is not shown next to the Rent Payments Control page title. + +## Dashboard content + +- Current statement-week hero: collection rate, cash, due, paid, part-paid and unpaid resident counts. +- Residents: all records, currently housed, former/no-room records, occupied rooms and complete weekly due. +- Collections and arrears: expected, cash collected, payment count, collection gap and outstanding balance. +- Agreements and balances: active agreements, agreement balance, overdue installments and new rent arrears. +- Data quality: missing SAGE IDs, room conflicts, duplicate names and missing charge bands. +- System navigation cards: Residents, Account Receivables, Payments MAP WEEK, ROI Finance, Arrears and Reports. + +## Data contract + +- Totals use the complete API result and are not affected by UI pagination or search filters. +- Current-week collection figures use the same Payment Map report service as `/mapa`. +- Financial values are displayed in full EUR format; counts are displayed as complete integers. +- Every metric card links to its relevant operational page or report. +- The Dashboard contains no Cash (SA) transaction table. + +## Visual language + +- Hero band: donut gauge for the cash collection rate (green track `#dcefe5`, arc `#0f6b4f`, end dot), a vertical rule, then icon-chip statistics for total collected and total due. +- Hero right block: 2x2 tile grid separated by hairlines, each tile with an icon chip — Residents (blue), Paid (green filled check), Part paid (amber clock), Unpaid (red alert). +- Metric cards: horizontal layout with a rounded `9px` tinted icon chip on the left and label, value and note stacked on the right. No corner arrow. +- Section headers: bold 13px title on the left, muted 10px context note right-aligned. +- Colour is semantic: green for healthy/collected, amber for review, red for arrears and exceptions, blue for neutral volume. + +## Money privacy (007.2026) + +- Header and Dashboard toolbars include **Show € / Hide €**. +- Amounts use `privacyMoneyLabel` / `euro()` with app-wide subscription — see `SPEC-MONEY-PRIVACY.md`. +- Default: amounts hidden until the operator reveals them. diff --git a/Infra-Network/SPEC-VM500-RENTCHARGE.md b/Infra-Network/SPEC-VM500-RENTCHARGE.md index ab4ef88..0ede3fc 100644 --- a/Infra-Network/SPEC-VM500-RENTCHARGE.md +++ b/Infra-Network/SPEC-VM500-RENTCHARGE.md @@ -223,6 +223,14 @@ Templates HTML em `backend/app/templates/` (`letter_debt.html`, `letter_agreemen Porta 22 Tailscale do big = `SSH-2.0-Tailscale`, não OpenSSH. `ssh.exe` timeout no KEX; paramiko funciona. WAN 51.171.219.235:22/80/443 fechados neste contexto. Acesso público do **app** é pelo túnel Cloudflare, não por abrir WAN. + +## Atualização 2026-09-19 noite (007.2026) + +- Money privacy (olho €) app-wide — `SPEC-MONEY-PRIVACY.md` +- Profile status audit (Temporary / Deceased / Custody / Treatment) — `SPEC-RESIDENT-PROFILE-STATUS.md` +- SMTP auth mail validado — `AUTH-USERS-SPEC.md` +- Prod + test em VM500 alinhados para estas features + ## Checklist agente 1. Hop só no **big**, nunca big1 para esta VM. diff --git a/Infra-Network/VERSION-RENTCHARGE.md b/Infra-Network/VERSION-RENTCHARGE.md index e6754b2..6c26def 100644 --- a/Infra-Network/VERSION-RENTCHARGE.md +++ b/Infra-Network/VERSION-RENTCHARGE.md @@ -1,6 +1,9 @@ # RentCharge version -**Current: 006.2026** — 2026-09-19 +**Current: 007.2026** — 2026-09-19 + +## 007.2026 +Money privacy eye works app-wide (pub/sub + `useMoneyPrivacyRender` on every euro page — Next.js layout slot fix); resident Profile status audit log (4 flags, Dublin stamp + user beside checkbox, full history kept); prod+test deploy. Specs: `SPEC-MONEY-PRIVACY.md`, `SPEC-RESIDENT-PROFILE-STATUS.md`, session 2026-09-19 night section. ## 006.2026 Public Txn short form for ancillary cash (`000084-ME`, `000010-EL`, … — 9 chars `NNNNNN-AA`); rent `txn_hash` at post; Week Payments Maps — All cash payments (Hash/Ticket/Txn/Rent·Other·Total) + Residents in period (weekly due / cash done / 6 prior weeks); Invoice/Ticket **Required** flag on all +Add payment modals (+ edit); People search pulse on All Residents; table titles Payment history / Remittance by account; legacy CS-/long ME- rewrite + EOD refresh (prod + test). Specs: `SPEC-TRANSACTION-IDS.md`, `SPEC-SESSION-2026-09-18.md`, `SPEC-SESSION-2026-09-19.md`. diff --git a/Infra-Network/releases/RENTCHARGE-007.2026.md b/Infra-Network/releases/RENTCHARGE-007.2026.md new file mode 100644 index 0000000..5103a53 --- /dev/null +++ b/Infra-Network/releases/RENTCHARGE-007.2026.md @@ -0,0 +1,14 @@ +# Release 007.2026 — 2026-09-19 + +## Summary + +- Money privacy eye works on **all** pages (not only Dashboard). +- Resident profile status flags keep a full On/Off history with user + Dublin datetime beside each checkbox. +- Deployed to **production and test** on VM500. + +## Specs + +- `SPEC-MONEY-PRIVACY.md` +- `SPEC-RESIDENT-PROFILE-STATUS.md` +- `SPEC-SESSION-2026-09-19.md` (night section) +- `VERSION.md` → Current 007.2026 diff --git a/ligbox-ops-platform/docs/vms/RENTCHARGE-VERSION.md b/ligbox-ops-platform/docs/vms/RENTCHARGE-VERSION.md index e6754b2..6c26def 100644 --- a/ligbox-ops-platform/docs/vms/RENTCHARGE-VERSION.md +++ b/ligbox-ops-platform/docs/vms/RENTCHARGE-VERSION.md @@ -1,6 +1,9 @@ # RentCharge version -**Current: 006.2026** — 2026-09-19 +**Current: 007.2026** — 2026-09-19 + +## 007.2026 +Money privacy eye works app-wide (pub/sub + `useMoneyPrivacyRender` on every euro page — Next.js layout slot fix); resident Profile status audit log (4 flags, Dublin stamp + user beside checkbox, full history kept); prod+test deploy. Specs: `SPEC-MONEY-PRIVACY.md`, `SPEC-RESIDENT-PROFILE-STATUS.md`, session 2026-09-19 night section. ## 006.2026 Public Txn short form for ancillary cash (`000084-ME`, `000010-EL`, … — 9 chars `NNNNNN-AA`); rent `txn_hash` at post; Week Payments Maps — All cash payments (Hash/Ticket/Txn/Rent·Other·Total) + Residents in period (weekly due / cash done / 6 prior weeks); Invoice/Ticket **Required** flag on all +Add payment modals (+ edit); People search pulse on All Residents; table titles Payment history / Remittance by account; legacy CS-/long ME- rewrite + EOD refresh (prod + test). Specs: `SPEC-TRANSACTION-IDS.md`, `SPEC-SESSION-2026-09-18.md`, `SPEC-SESSION-2026-09-19.md`. diff --git a/ligbox-ops-platform/docs/vms/VM500.md b/ligbox-ops-platform/docs/vms/VM500.md index ab4ef88..0ede3fc 100644 --- a/ligbox-ops-platform/docs/vms/VM500.md +++ b/ligbox-ops-platform/docs/vms/VM500.md @@ -223,6 +223,14 @@ Templates HTML em `backend/app/templates/` (`letter_debt.html`, `letter_agreemen Porta 22 Tailscale do big = `SSH-2.0-Tailscale`, não OpenSSH. `ssh.exe` timeout no KEX; paramiko funciona. WAN 51.171.219.235:22/80/443 fechados neste contexto. Acesso público do **app** é pelo túnel Cloudflare, não por abrir WAN. + +## Atualização 2026-09-19 noite (007.2026) + +- Money privacy (olho €) app-wide — `SPEC-MONEY-PRIVACY.md` +- Profile status audit (Temporary / Deceased / Custody / Treatment) — `SPEC-RESIDENT-PROFILE-STATUS.md` +- SMTP auth mail validado — `AUTH-USERS-SPEC.md` +- Prod + test em VM500 alinhados para estas features + ## Checklist agente 1. Hop só no **big**, nunca big1 para esta VM.