96 lines
5.2 KiB
Markdown
96 lines
5.2 KiB
Markdown
# Digital Consent (Sprint 3)
|
|
|
|
This document describes the digital consent and student transfer approval implementation (Sprint 3).
|
|
|
|
## Architecture overview
|
|
|
|
- Server: Node 20 + Express, SQLite via better-sqlite3.
|
|
- Frontend: React + Vite, SignatureModal component captures drawings to a PNG data URL.
|
|
- USSD: state machine + ussd service. PIN verification is bcrypt-based and persisted on the `users` row (`ussd_pin`, `ussd_failed_attempts`, `ussd_locked_until`).
|
|
- Audit: AuditService writes to `audit_logs` table. Consent actions write audit entries.
|
|
|
|
## Database schema
|
|
|
|
- New table: `transfer_consents` (created by migration `server/src/database/migrations/knex/2026073000000000_consent_columns.js`). Key columns:
|
|
- id (PK integer)
|
|
- uid (TEXT UNIQUE)
|
|
- student_id (FK -> users.id)
|
|
- parent_id (FK -> users.id)
|
|
- requested_by (TEXT)
|
|
- from_school_id (TEXT)
|
|
- target_school (TEXT)
|
|
- transfer_request_id (self FK)
|
|
- consent_status (TEXT) — values: 'pending','approved','rejected','expired'
|
|
- consent_method (TEXT) — 'WEB_SIGNATURE' | 'USSD_PIN'
|
|
- signed_at (DATETIME)
|
|
- signature_hash (TEXT)
|
|
- expires_at (DATETIME)
|
|
- created_at, updated_at
|
|
|
|
Indexes: student_id, parent_id, consent_status, plus a partial index for pending rows.
|
|
|
|
Notes: field names were chosen to align with existing code patterns (`target_school` used for the destination school, `signed_at` used for approval timestamp).
|
|
|
|
## Web signature flow
|
|
|
|
1. Admin initiates a transfer via POST /api/transfers/initiate — this creates a `transfer_consents` pending row and sends instructions to the parent.
|
|
2. Parent (authenticated) opens `TransferConsents` page (GET /api/consents/pending) and opens the `SignatureModal` component.
|
|
3. SignatureModal captures drawing to a PNG data URL and sends it to POST /api/transfers/:id/consent.
|
|
4. Server builds a canonical payload and computes SHA-256 over the canonical string (see signature.service.canonicalize). The server stores only the hex hash in `signature_hash` on the consent row along with `signed_at`.
|
|
|
|
Security properties:
|
|
- The payload includes consent_id (uid), student_id, parent_id, both schools, and approved_at timestamp — so replaying an old PNG alone won't pass verification because the approved_at will differ.
|
|
- The signature hash is verified with constant-time comparison when needed (signature.service.verifyConsentHash).
|
|
- Parent/student relationship is validated against `parent_students` before approval.
|
|
- Atomic UPDATE ensures duplicate approvals are prevented (0 rows affected → conflict).
|
|
|
|
## USSD approval flow
|
|
|
|
1. Parent uses USSD and authenticates with student ID and 4-digit PIN.
|
|
2. In the Main Menu choose "Transfer Approval"; the USSD state machine picks the next pending consent for the student.
|
|
3. Parent enters PIN to approve. The PIN is verified using `ussd.service.verifyPin` (bcrypt). On success the server computes the same SHA-256 payload and marks the consent `approved` with `consent_method = 'USSD_PIN'`.
|
|
4. The USSD flow returns `END Transfer approved successfully.` on successful approval.
|
|
|
|
## API endpoints
|
|
|
|
- POST /api/transfers/initiate — admin-only; creates pending consent.
|
|
- POST /api/transfers/:id/consent — authenticated user (parent or admin override); approves with web signature image in `signature_data_url`.
|
|
- GET /api/consents/pending — parent-only; lists pending consents for the authenticated parent.
|
|
- POST /api/consents/:id/reject — parent or admin; rejects a pending consent.
|
|
- Additional routes: GET /api/transfers/:id/consent and DELETE /api/transfers/:id/consent (for compatibility).
|
|
|
|
## Signature hashing
|
|
|
|
- Canonical format: pipe-separated key=value pairs in this exact order:
|
|
`consent_id, student_id, parent_id, from_school_id, to_school_id, approved_at`.
|
|
- Hash algorithm: SHA-256 hex digest.
|
|
- Generation and verification functions live in `server/src/services/signature.service.js`.
|
|
|
|
## Audit logging
|
|
|
|
- All consent actions write audit entries using `AuditService` or the local `writeAudit` helper.
|
|
- Actions include: `consent_requested`, `consent_approved`, `consent_ussd_approved`, `consent_rejected`, `consent_expired_sweep`.
|
|
- Audit rows include user, entity_type, entity_id (consent uid), timestamp, and new_data metadata.
|
|
|
|
## Testing instructions
|
|
|
|
- Unit tests for signature hashing were added (Vitest) at `server/test/signature.spec.js`.
|
|
- Run the server tests from the `server/` folder:
|
|
|
|
```bash
|
|
cd server
|
|
npm install # if dependencies are not yet installed
|
|
npm run test
|
|
```
|
|
|
|
- Integration tests for the full consent workflow are recommended (DB-backed); these require a test DB and fixtures. Create tests that run `node src/database/init.js` against a temporary DB path or use in-memory SQLite.
|
|
|
|
## Open items / caveats
|
|
|
|
- A small USSD-to-service return-shape mismatch and an audit user reference bug were fixed in `server/src/services/consent.service.js` (USSD approval now returns `{ ok: true, consent }` and audit user uses `consent.parent_id`).
|
|
- Full integration tests covering the transfer lifecycle, parent linkage negative cases, and PIN lockout flows are not yet present — recommended next work.
|
|
|
|
## Contact
|
|
|
|
For changes to consent formats (canonicalization) or migration overwrite, coordinate with the ops team — existing signed consents rely on the exact canonicalization string.
|