# 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.