|
| 1 | +# Release v2.0.4: Password-Free Auth Artifacts, Picture API Cleanup, Stronger Wipe Validation, Higher Coverage |
| 2 | + |
| 3 | +## Summary |
| 4 | +v2.0.4 is a hardening + ergonomics release focused on secure password-free resume flows, clarified and modernized picture APIs, safer destructive actions, and significantly expanded test coverage (now ~98%). It remains fully backward compatible for consumers relying on deprecated method names; all new functionality is additive. |
| 5 | + |
| 6 | +## Highlights |
| 7 | + |
| 8 | +### 1. Authentication Artifacts (Password-Free Resume) |
| 9 | +- New methods: |
| 10 | + - `FmdClient.from_auth_artifacts(artifacts: dict)` – Restore a client without the raw password. |
| 11 | + - `FmdClient.resume(...)` – Lower-level variant accepting explicit fields. |
| 12 | + - `await client.export_auth_artifacts()` – Export `base_url`, `fmd_id`, `access_token`, `private_key` (PEM), optional `password_hash`, `session_duration`, `token_issued_at`. |
| 13 | + - `await client.drop_password()` / `drop_password=True` in `create()` – Immediately discard raw password after onboarding. |
| 14 | +- 401 handling logic hierarchy: |
| 15 | + 1. If raw password present → reauthenticate (existing flow) |
| 16 | + 2. Else if `password_hash` present → hash-based token refresh (`_reauth_with_hash()`) |
| 17 | + 3. Else → raise `FmdApiException` (caller must re-onboard) |
| 18 | +- Private key load now supports both PEM and DER (fallback path tested). |
| 19 | + |
| 20 | +### 2. Picture API Renaming & Deprecations |
| 21 | +- New canonical methods on `Device`: |
| 22 | + - `get_picture_blobs()` – fetch encrypted Base64 blobs. |
| 23 | + - `decode_picture()` – decrypt + decode into `PhotoResult`. |
| 24 | +- Deprecated (still functional, emit `DeprecationWarning`): |
| 25 | + - `take_front_photo()`, `take_rear_photo()` → use `take_front_picture()`, `take_rear_picture()` |
| 26 | + - `fetch_pictures()`, `get_pictures()` (Device) → use `get_picture_blobs()` |
| 27 | + - `download_photo()`, `get_picture()` (Device) → use `decode_picture()` |
| 28 | +- `download_photo()` now points directly to `decode_picture()` (avoids chained deprecated call). |
| 29 | + |
| 30 | +### 3. Wipe (Factory Reset) Hardening |
| 31 | +- `Device.wipe()` now strictly requires: |
| 32 | + - `confirm=True` |
| 33 | + - `pin` argument present |
| 34 | + - PIN must be alphanumeric ASCII (no spaces). Future enforcement of 16+ length is noted (fmd-android MR 379). |
| 35 | +- Removed redundant space validation branch. |
| 36 | + |
| 37 | +### 4. Lock Message Support |
| 38 | +- `Device.lock(message=...)` allows an optional user message; sanitized (quotes / backticks / semicolons removed, whitespace collapsed, 120 char cap). Falls back cleanly if server ignores payload. |
| 39 | + |
| 40 | +### 5. Expanded Export Functionality & Robustness |
| 41 | +- `export_data_zip()` improvements: |
| 42 | + - Picture file extension detection: `.png` via magic bytes, default `.jpg` else. |
| 43 | + - Resilient manifest entries capturing per-item decryption errors (non-fatal). |
| 44 | + - Additional validation of location/picture list types with graceful fallbacks. |
| 45 | + |
| 46 | +### 6. Coverage & Testing Improvements |
| 47 | +- Overall coverage ~98% (client.py ~98%, device.py mid/high 90s, models 100%). |
| 48 | +- New targeted tests exercise: |
| 49 | + - DER private key resume path |
| 50 | + - Missing artifact field errors |
| 51 | + - 401 reauth when neither password nor hash is available (expected exception) |
| 52 | + - Hash-based 401 reauth success path |
| 53 | + - PNG export branch + unknown image default `.jpg` |
| 54 | + - Deprecated wrapper warning emission |
| 55 | + - Non-dict JSON fallback handling & non-list picture responses |
| 56 | + - Error recording during export (location & picture failures) |
| 57 | + - Retry logic: 429 (Retry-After numeric/date/negative), 500/502 sequences, connection errors, backoff jitter/no-jitter paths |
| 58 | + - Masking helpers and retry-after parsing edge cases |
| 59 | + |
| 60 | +### 7. Documentation Updates |
| 61 | +- README: Added password-free artifact usage, wipe PIN notes, lock message mention, community listing. |
| 62 | +- `MIGRATE_FROM_V1.md`: Corrected camera method naming, clarified wipe requirements, updated picture usage. |
| 63 | +- `AUTH_ARTIFACTS_DESIGN.md`: Formal specification of artifact-based resume workflow. |
| 64 | + |
| 65 | +### 8. Internal / Quality Enhancements |
| 66 | +- Removed chained deprecation in `Device.download_photo()`. |
| 67 | +- Simplified `wipe()` validation logic (single branch covers spaces & non-alphanumeric). |
| 68 | +- Eliminated redundant PIN space check. |
| 69 | +- Minor consistency and defensive branches now covered or documented. |
| 70 | + |
| 71 | +## Deprecations (No Immediate Removal) |
| 72 | +| Deprecated | Replacement | |
| 73 | +|------------|-------------| |
| 74 | +| `Device.take_front_photo()` | `Device.take_front_picture()` | |
| 75 | +| `Device.take_rear_photo()` | `Device.take_rear_picture()` | |
| 76 | +| `Device.fetch_pictures()` | `Device.get_picture_blobs()` | |
| 77 | +| `Device.get_pictures()` (Device wrapper) | `Device.get_picture_blobs()` | |
| 78 | +| `Device.download_photo()` | `Device.decode_picture()` | |
| 79 | +| `Device.get_picture()` | `Device.decode_picture()` | |
| 80 | + |
| 81 | +Plan: Monitor usage; consider removal or formal EOL notice in a future minor/major once ecosystem migrates. |
| 82 | + |
| 83 | +## Security Considerations |
| 84 | +- Encourages immediate password discarding (`drop_password=True`), reducing exposure of raw credentials. |
| 85 | +- `password_hash` still sensitive—store using platform secret storage if possible. |
| 86 | +- Wipe PIN validation prevents accidental destructive actions with weak/empty inputs. |
| 87 | +- Lock message sanitization avoids command injection edge cases in future server parsing contexts. |
| 88 | + |
| 89 | +## Migration Notes (2.0.3 → 2.0.4) |
| 90 | +- Existing code continues to function; deprecation warnings guide picture API migration. |
| 91 | +- To adopt password-free resume: |
| 92 | + 1. Onboard normally with `create(..., drop_password=True)`. |
| 93 | + 2. Persist `await client.export_auth_artifacts()` securely. |
| 94 | + 3. Resume with `await FmdClient.from_auth_artifacts(artifacts)`. |
| 95 | + |
| 96 | +## Example: Password-Free Cycle |
| 97 | +```python |
| 98 | +client = await FmdClient.create(url, fmd_id, password, drop_password=True) |
| 99 | +artifacts = await client.export_auth_artifacts() |
| 100 | +# Persist artifacts securely... |
| 101 | +await client.close() |
| 102 | + |
| 103 | +client2 = await FmdClient.from_auth_artifacts(artifacts) |
| 104 | +locations = await client2.get_locations(1) |
| 105 | +``` |
| 106 | + |
| 107 | +## Potential Follow-Ups (Not Included) |
| 108 | +- Enforce future 16+ PIN length once upstream server mandates it. |
| 109 | +- Add a CHANGELOG.md consolidating releases (this file can seed that entry). |
| 110 | +- Provide optional encrypted artifact export (password-protected ZIP or keyring integration). |
| 111 | +- Add coverage for final remaining defensive lines (currently low-risk). |
| 112 | + |
| 113 | +## Version |
| 114 | +`fmd_api.__version__ == "2.0.4"` |
| 115 | + |
| 116 | +## Acknowledgements |
| 117 | +Thanks to contributors and early testers providing feedback on artifact-based auth and API naming clarity. |
| 118 | + |
| 119 | +--- |
| 120 | +Released: 2025-11-09 |
0 commit comments