04/27 Updated documents

This commit is contained in:
2026-04-27 15:26:24 -04:00
parent f941762e34
commit 412c101b5f
2 changed files with 19 additions and 12 deletions
+11 -5
View File
@@ -2,7 +2,7 @@
> **Audience:** AI assistants and developers working on this codebase.
> **Purpose:** Authoritative reference for architecture, conventions, gotchas, and decisions.
> **Last reviewed:** April 2026 (Phase 12 complete + post-review hardening patch)
> **Last reviewed:** April 2026 (Phase 12 complete + post-review hardening + UX improvements)
---
@@ -195,7 +195,9 @@ users: id, username (unique, indexed), full_name, email (unique, indexed),
**Expired invitations:** Customer accounts with `password_set=False` and `set_password_token_expires < now_eastern()` are flagged on the `/customers` index with an inline Resend button.
**Token verification:** `User.verify_set_password_token()` uses `hmac.compare_digest()` as a constant-time comparison guard after the DB lookup and expiry check, preventing timing oracle attacks on the stored token value.
**Customer self-service setup:** When a customer clicks their invitation link, `set_password.html` prompts them to choose their own **username** (replacing the auto-generated placeholder) and set a **password**. The `set_password` route saves `form.username.data` to `user.username` before committing. The `SetPasswordForm` validates username uniqueness inline via `validate_username()`.
**Token verification:** `User.verify_set_password_token()` guards in order: token present → DB lookup → `password_set=False` check → expiry check → `hmac.compare_digest()` constant-time comparison. The `password_set=False` guard ensures accounts already activated cannot be re-used via a stale token. `compare_digest` is wrapped in `try/except` to safely handle type mismatches.
### Facility / Area
@@ -326,10 +328,10 @@ api_device_tokens: id, user_id, device_id, apns_token, device_name, app_version
|---|---|---|
| `auth` | `/auth` | `/login` (rate-limited 20/min), `/logout`, `/profile`, `/users/*`, `/notification-matrix` |
| `dashboard` | `/` | `GET /`, `/facility-trend` (AJAX) |
| `facilities` | `/facilities` | CRUD + area management |
| `facilities` | `/facilities` | CRUD + area management; list grouped by Contract with collapsible sections; detail page has Back button |
| `projects` | `/projects` | CRUD + customer assignment management |
| `customers` | `/customers` | list (expired invitation banner), invite, set-password, manage, import CSV |
| `inspections` | `/inspections` | list (stale badge, `now` passed from route), start, execute, view, PDF export, flag-issue, save-draft (AJAX), flag-followup, reinspect |
| `customers` | `/customers` | list (expired invitation banner), invite (name + email only), set-password (customer chooses username + password), manage, import CSV |
| `inspections` | `/inspections` | list (stale badge, `now` passed from route), start (template + facility only — area and notes removed), execute, view, PDF export, flag-issue, save-draft (AJAX), flag-followup, reinspect |
| `templates` | `/templates` | list, create, edit, delete, form editor, preview |
| `issues` | `/issues` | list (SLA column, facility filter, quick-assign dropdown), view, create, update, verify, comment, follow/unfollow, verification queue, delete |
| `issues` | `/issues` | `POST /<id>/quick-assign` — JSON AJAX, admin/director only |
@@ -625,6 +627,10 @@ Logs: `/home/jqc/logs/gunicorn-error.log`, `/home/jqc/logs/gunicorn-access.log`
| 23 | **`hmac.compare_digest()` for token comparison** | Prevents timing oracle attacks on `verify_set_password_token` |
| 24 | **`get_customer_scope()` uses bulk project query** | Single `Facility.project_id.in_(project_ids)` replaces per-assignment loop |
| 25 | **CSV exports always call `log_action(ACTION_EXPORT, ...)`** | Data exports are compliance-relevant audit events |
| 26 | **`StartInspectionForm` has no `area_id` or `notes` fields** | Both removed from the start page; `Inspection` constructor receives `area_id=None, notes=None` explicitly |
| 27 | **Customer set-password page collects username + password** | `SetPasswordForm` includes `username` field; route saves it to `user.username`, replacing the auto-generated placeholder |
| 28 | **`verify_set_password_token` requires `password_set=False`** | Prevents reuse of a stale token on an already-activated account |
| 29 | **Facilities list grouped by Contract in route, not template** | `list_facilities()` builds a `grouped` OrderedDict before rendering; template iterates `grouped`, not the flat `facilities` list |
---
+8 -7
View File
@@ -6,9 +6,9 @@ A production-grade web application for managing janitorial service contracts, fa
## Features
- **Inspection Management** — Execute structured inspections against configurable templates with a drag-and-drop form builder supporting ratings, pass/fail, photos, signatures, and free-form fields
- **Inspection Management** — Execute structured inspections against configurable templates with a drag-and-drop form builder supporting ratings, pass/fail, photos, signatures, and free-form fields. Starting an inspection requires only a Template and Facility selection.
- **Issue Tracking** — Full lifecycle management (open → in-progress → pending verification → resolved) with SLA enforcement, follower subscriptions, resolution photo uploads, and inline quick-assign from the issues list
- **Customer Portal** — Scoped facility visibility for client accounts with invitation-based onboarding (email link, 72-hour token); expired invitation warnings surface on the admin dashboard
- **Customer Portal** — Scoped facility visibility for client accounts with invitation-based onboarding; admin enters name and email only, customer chooses their own username and password via a 72-hour emailed link; expired invitation warnings surface on the admin dashboard
- **Notification System** — In-app + email notifications driven by an admin-controlled routing matrix; per-user preferences including digest mode and a one-click "Pause All Emails" toggle
- **Reports** — On-demand PDF/CSV scorecards and scheduled recurring email reports (daily/weekly/monthly)
- **Audit Trail** — Immutable log of every create, update, and delete action with actor and IP capture
@@ -218,12 +218,13 @@ flask shell
## Customer Onboarding
1. Admin navigates to **Customers → New Customer**
2. Enter the customer's name and email — username is auto-generated
3. The system sends an invitation email with a 72-hour setup link
4. Customer sets their password via the link and gains access to their scoped portal
5. Admin assigns the customer to Contracts/Facilities via **Customers → Manage**
2. Enter the customer's **Full Name** and **Email** only — no username or password required from the admin
3. The system auto-generates a temporary username and sends an invitation email with a secure **72-hour setup link**
4. Customer clicks the link and arrives at the account setup page where they **choose their own username and set their password**
5. The account activates immediately and the customer can log in
6. Admin assigns the customer to Contracts/Facilities via **Customers → Manage**
> Expired invitations (token past 72 hours, password never set) are surfaced as a warning banner on the Customers page with inline **Resend** buttons.
> Expired invitations (token past 72 hours, password never set) are surfaced as a warning banner on the Customers page with inline **Resend** buttons. Resending generates a fresh 72-hour token.
---