geocrop-platform./apps/nextgen/docs/DIGITAL_CONSENT.md

5.2 KiB

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:

    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.