Sep 16 - Optimize code, part 1
This commit is contained in:
@@ -92,7 +92,8 @@ QR_Code_Management/
|
||||
│ ├── __init__.py
|
||||
│ ├── helpers.py # Role decorators, QR generation, role/permission helpers
|
||||
│ ├── geocoding.py # Haversine distance, Google Maps client, reverse geocode
|
||||
│ └── template_helpers.py # Context processors: get_employee_name, format_hours, etc.
|
||||
│ ├── template_helpers.py # Context processors: get_employee_name, format_hours, etc.
|
||||
│ └── excel_safety.py # Formula-injection guard for every Excel/CSV export (Set 23)
|
||||
│
|
||||
├── static/
|
||||
│ ├── css/
|
||||
@@ -121,6 +122,7 @@ QR_Code_Management/
|
||||
├── migration_photo_verification_toggle.py # Adds photo_verification_enabled to qr_codes
|
||||
├── migration_PM_permissions.py # One-time: user_project/location_permissions tables
|
||||
├── migration_dynamic_qr_locations.py # One-time: qr_type column + qr_code_locations table
|
||||
├── migration_attendance_indexes.py # attendance_data secondary indexes (online DDL, re-runnable)
|
||||
└── optimize_time_attendance_db.py # Standalone DB index optimization script
|
||||
```
|
||||
|
||||
@@ -193,6 +195,21 @@ STAFF_LEVEL_ROLES = ['staff', 'payroll', 'project_manager', 'accounting']
|
||||
|
||||
**`/register` is restricted to `@admin_required`** — public self-registration is disabled.
|
||||
|
||||
**Server-side role enforcement (Sept 2026, Set 23)** — the sidebar in `base_authenticated.html`
|
||||
is the reference for who may use what; routes enforce the same rules:
|
||||
- `roles_required(*roles)` decorator and `restrict_blueprint_to_roles(bp, roles)` (a blueprint
|
||||
`before_request`) in `utils/helpers.py`. Anonymous → login redirect; wrong role → flash +
|
||||
dashboard redirect, or JSON 401/403 for `/api/`, `X-Requested-With` or JSON callers.
|
||||
- `PAYROLL_AREA_ROLES = ('admin','payroll','accounting')` → whole blueprints: `time_attendance`,
|
||||
`legacy_attendance`, `employees`, `statistics`.
|
||||
- `QR_MANAGEMENT_ROLES = ('admin','staff','payroll','accounting')` → QR create / edit / bulk import
|
||||
(not project managers). QR toggle / activate / deactivate → `admin` only (the dashboard shows
|
||||
those buttons to admins only).
|
||||
- User management state changes (delete, reactivate, promote, demote, activate, deactivate,
|
||||
permanently-delete) are **POST-only** — never re-add GET: CSRF validation only runs on POST.
|
||||
- New routes in a gated blueprint are covered automatically; new routes elsewhere need an
|
||||
explicit decorator.
|
||||
|
||||
---
|
||||
|
||||
## 5. Application Factory & Initialization Order
|
||||
@@ -227,7 +244,10 @@ STAFF_LEVEL_ROLES = ['staff', 'payroll', 'project_manager', 'accounting']
|
||||
**Critical notes:**
|
||||
- `create_tables()` and `update_existing_qr_codes()` run inside `with app.app_context()` inside `create_app()` — they execute under gunicorn, not only under `__main__`.
|
||||
- `PerformanceMonitor` is imported lazily inside `__main__` only — gunicorn workers never load it.
|
||||
- `update_existing_qr_codes()` prefers `QR_BASE_URL` from `.env`. Falls back to `FLASK_HOST`/`FLASK_PORT`. Never uses `request.url_root`.
|
||||
- `update_existing_qr_codes()` builds startup QR images from `QR_BASE_URL` only. When it is not set,
|
||||
missing images are **not** generated at startup (the old `FLASK_HOST`/`FLASK_PORT` fallback wrote
|
||||
`http://localhost:5000/...` into QR codes). Note: the QR routes in `routes/qr_codes.py` build URLs
|
||||
from `request.url_root`, not `QR_BASE_URL` — there is no `_get_qr_base_url()` helper despite older notes.
|
||||
|
||||
---
|
||||
|
||||
@@ -503,7 +523,14 @@ Exception: `/qr/<url>/checkin` is CSRF-exempt (public, unauthenticated).
|
||||
- **CSRF-exempt:** `auth.login`, `auth.register`, `qr_codes.qr_checkin`, `static`
|
||||
|
||||
### Rate Limiting
|
||||
- 5 failed login attempts within 15 minutes → IP blocked 15 minutes (in-memory per worker)
|
||||
- Failed logins are counted over 15 minutes per **IP + username** (blocked after 5) and per
|
||||
**username from any IP** (blocked after 20) — `SecurityManager.is_auth_rate_limited(username)`.
|
||||
Not a plain per-IP counter, so users behind one shared IP cannot lock each other out.
|
||||
In-memory per worker. The check that runs is in `auth.login`; the middleware's
|
||||
`request.endpoint in ['login', ...]` check never matches (endpoint is `auth.login`)
|
||||
- Client IP = `request.remote_addr` after `ProxyFix(x_for=TRUSTED_PROXY_COUNT)` in `create_app()`.
|
||||
**Never read the first `X-Forwarded-For` entry** — the client controls it (that bypassed the limiter).
|
||||
Behind Cloudflare + Nginx set `TRUSTED_PROXY_COUNT=2`
|
||||
- `SecurityManager.create_secure_session()` called on login success (clears failed-attempt counter)
|
||||
|
||||
### Session Security
|
||||
@@ -870,7 +897,11 @@ PHOTO_VERIFICATION_DISTANCE_THRESHOLD # Miles (default 0.3)
|
||||
VERIFICATION_PHOTO_MAX_SIZE # Bytes (default 5MB)
|
||||
UPLOAD_FOLDER # Temp path (default /tmp)
|
||||
DEFAULT_ADMIN_PASSWORD # CHANGE IN PRODUCTION
|
||||
QR_BASE_URL # Public-facing domain for QR links — required behind reverse proxy
|
||||
QR_BASE_URL # Public-facing domain — used for QR images generated at startup (none generated if unset)
|
||||
TRUSTED_PROXY_COUNT # Reverse proxies in front of the app (default 1 = Nginx; 2 with Cloudflare; 0 = none)
|
||||
MAX_UPLOAD_SIZE_MB # Whole-request cap, default 50 (Nginx client_max_body_size must be >= this)
|
||||
# MAX_FORM_MEMORY_SIZE = VERIFICATION_PHOTO_MAX_SIZE + 1 MB (Flask 3.1 default 500 KB
|
||||
# would 413 the base64 photo field) — set in config.py, not .env
|
||||
THEME_NAME # Activates static/css/theme-{THEME_NAME}.css
|
||||
SYNC_INTERVAL_MINUTES
|
||||
SQLALCHEMY_ENGINE_OPTIONS_POOL_SIZE / POOL_TIMEOUT / POOL_RECYCLE / MAX_OVERFLOW
|
||||
@@ -936,6 +967,7 @@ def run():
|
||||
| `migration_photo_verification_toggle.py` | `qr_codes.photo_verification_enabled` TINYINT(1) DEFAULT 1 |
|
||||
| `migration_dynamic_qr_locations.py` | `qr_type` column + `qr_code_locations` table |
|
||||
| `migration_PM_permissions.py` | `user_project_permissions` + `user_location_permissions` tables |
|
||||
| `migration_attendance_indexes.py` | `attendance_data` indexes: `idx_ad_date_time`, `idx_ad_emp_date_time`, `idx_ad_qr_emp_date`, `idx_ad_location_name`, `idx_ad_verif_status` — skips names/leading columns that already exist; online DDL with plain ALTER fallback (Set 23) |
|
||||
|
||||
---
|
||||
|
||||
@@ -1271,6 +1303,22 @@ it (the workers share no pub/sub).
|
||||
| `static/js/qr_destination.js` | `disableFormImmediately()` replaced the Employee ID placeholder with "Checking location services..." and nothing restored it after location services were confirmed. The override is removed — the submit button already shows the checking state |
|
||||
| `templates/qr_destination.html` | Placeholder is now "Enter your employee ID / Ingrese su ID de empleado" |
|
||||
|
||||
### Set 23 — Review Step 1: Security, Limits, Indexes (Sept 15, 2026)
|
||||
| File | Fix |
|
||||
|---|---|
|
||||
| `routes/users.py`, `templates/users.html`, `static/js/users.js` | 7 user-management actions POST-only (were GET+POST, so a forged link could promote/delete — CSRF only checks POST). Promote/demote links → CSRF forms with a `data-confirm` listener. `users.js` (not loaded by any template) switched to POST |
|
||||
| `utils/helpers.py`, `routes/time_attendance.py`, `employees.py`, `legacy_attendance.py`, `statistics.py`, `qr_codes.py` | `roles_required` / `restrict_blueprint_to_roles`; role rules in §4. Previously any logged-in user (incl. project managers) could import/export time attendance, delete employees, create/deactivate QR codes |
|
||||
| `advanced_security_middleware.py`, `routes/auth.py`, `app.py`, `config.py` | Login limiter: real IP via `ProxyFix`, counters per IP+username and per username (§10). The first `X-Forwarded-For` entry was trusted, so rotating it gave unlimited attempts |
|
||||
| `utils/excel_safety.py` + `routes/attendance_export.py`, `routes/time_attendance_export.py`, `legacy_attendance_service.py`, `routes/statistics.py` | Formula injection: `excel_hyperlink()` escapes `"` in HYPERLINK arguments; `neutralize_unexpected_formulas(wb)` before every export `wb.save` stores any formula not matching the app's own shapes (Google Maps HYPERLINK, SUM, SUMIF) as text; `csv_safe()` in the statistics CSV. **New formula types in exports must be added to `_ALLOWED_FORMULA_PATTERNS`, or they are written as text** |
|
||||
| `routes/qr_codes.py` | Check-in refuses an employee ID whose base is not 1–4 ASCII digits (was: only counted digits, so `=HYPERLINK(...)` passed into attendance_data) |
|
||||
| `location_logging.py` | `/api/location-debug-info` admin-only; no longer echoes request headers (incl. the session Cookie) |
|
||||
| `app.py`, `config.py` | Startup QR images use `QR_BASE_URL` or are skipped (no more `localhost` QR codes); `MAX_FORM_MEMORY_SIZE` / `MAX_CONTENT_LENGTH`; `@app.errorhandler(413)` (bilingual JSON for `/qr/` + `/api/`, flash + same-host redirect otherwise) |
|
||||
| `static/js/qr_destination.js` | `updateSubmitButton()` looked for `#submitCheckin`, but the button is `#submitButton`, so the post-clone submit path never showed "Processing" — now finds either, bilingual label, restores via `renderSubmitButton()` |
|
||||
| `routes/attendance.py` | Report query selects `NULL AS verification_photo` (the page never used the base64 photos it loaded for up to 1,000 rows) |
|
||||
| `tools/migration_attendance_indexes.py` | New migration — see §17 |
|
||||
| — | Verified offline (67 checks): formula guard incl. openpyxl save/reload, role gates per role and for JSON callers, limiter behind ProxyFix (spoofed XFF, shared IP, per-username cap, reset on login), debug endpoint, check-in ID rule, config limits, POST-only routes, migration against a fake cursor (create / covered / re-run / fallback / missing column). Not run against MySQL, Nginx or a browser |
|
||||
| — | Known, not changed: `templates/confirm_delete_qr.html` links (GET) to the POST-only `deactivate_qr_code`, so that button returns 405 |
|
||||
|
||||
---
|
||||
|
||||
## 21. Infrastructure & Deployment
|
||||
|
||||
Reference in New Issue
Block a user