YC
PRODUCTION APPLICATION · LITIGANT-FACING DISPUTE PLATFORM

Resolution Desk · Litigant Dispute Platform & Stateless Gateway

A self-service dispute resolution web application designed for loan borrowers facing arbitration proceedings. Engineered as a stateless Backend-for-Frontend (BFF) over upstream Frappe ODR with a Rust/Leptos WebAssembly PWA frontend, zero-password stateless HMAC-SHA256 session authentication, offline borrower cross-verification, and strict case-scoped authorization.

ROLE & SCOPESoftware Engineer · IO2iApplication, UI & BFF Gateway
FRONTEND ARCHITECTURERust + Leptos (WASM PWA)Service Worker Offline Shell
AUTH MECHANISMStateless HMAC-SHA256Ephemeral OTP + LAN Token
UPSTREAM INTEGRATIONFrappe ODR APIStateless BFF Architecture
Resolution Desk Borrower Dashboard Overview

Figure 1: Resolution Desk borrower dashboard showing active case summary, dispute claim amount, arbitrator allocation, hearing timeline, and document submission.

02. THE OPERATIONAL PROBLEM

Designing Frictionless Access for Anxious, Non-Technical Litigants

When financial institutions initiate formal dispute arbitration for delinquent loan accounts, recipients receive legal notice communications via registered postal dispatch or electronic messaging. Litigants require clear access to case information, claim details, and scheduled proceedings to review notices and submit responses.

Recipients often experience anxiety when facing legal notices and may be unfamiliar with complex digital portals, frequently seeking basic clarification on claim amounts, hearing schedules, and response procedures.

Standard SaaS authentication patterns present significant usability barriers in this context:

  • Password Friction: Requiring one-time litigants to register traditional accounts with passwords introduces high friction, as most users need rapid, single-purpose access rather than persistent account management.
  • Missing Digital Contact Records: Some borrowers receive physical notices delivered to their address without active phone numbers or email addresses recorded in bank systems, requiring alternative identity verification methods.
  • Procedural Timelines: Formal dispute proceedings advance according to scheduled hearing dates. If litigants cannot easily inspect case files or submit defense documentation ahead of hearings, proceedings risk proceeding without their input.
03. WHAT I BUILT & OWNERSHIP BOUNDARIES

Litigant Web Client, Stateless Auth Engine & Case Scoping

I designed and built the Resolution Desk web application and backend gateway from scratch:

WHAT I PERSONALLY BUILT
  • Rust/Leptos WebAssembly PWA frontend client with service worker caching.
  • FastAPI Backend-for-Frontend (BFF) connecting to upstream Frappe ODR.
  • Dual-identifier input auto-detection for ARB case codes and LAN numbers.
  • Stateless HMAC-SHA256 session token system with in-memory revocation list.
  • Offline borrower cross-verification fallback (LAN ↔ ARB matching).
  • Strict single-LAN session authorization (_enforce_case_lan).
  • Document upload pipeline streaming files directly to upstream storage.
EXPLICIT OWNERSHIP BOUNDARIES
  • Notice QR/Magic-Link Pipeline: The physical notice generation and QR-code encoding pipeline was built by my team lead (engineering director). My application consumed and validated these tokens via exchange_case_access_token_via_frappe.
  • Upstream Frappe ODR: Case records, arbitrator calendars, and formal case status mutations reside in upstream Frappe systems.
  • No Local Database: Resolution Desk maintains zero local SQL database tables; all dispute state is retrieved via authenticated Frappe API proxies.

Key Borrower Workflows Supported

DUAL-IDENTIFIER CASE LOOKUP

Borrowers input either their Loan Account Number (LAN) or Arbitration Case Number (ARB); the system auto-detects the format and queries upstream Frappe.

STATELESS OTP CHALLENGE

Masked contact confirmation with ephemeral OTP challenge via Frappe, issuing a cryptographically signed HMAC bearer session token.

OFFLINE CROSS-VERIFICATION FALLBACK

For borrowers with no contact details on file, matches both LAN and ARB from their physical letter before prompting them to securely submit contact info.

CASE SUMMARY & EVIDENCE UPLOAD

Review dispute notices, scheduled hearing times, settlement options, and upload legal response documents directly to Frappe storage.

04. ARCHITECTURE & VERIFICATION FLOW

The Litigant Journey: Zero-Password Access Flow

Resolution Desk operates without a persistent user table. Authentication state is embedded in cryptographically signed tokens, and every subsequent request is strictly locked to the verified borrower's Loan Account Number:

BORROWER ACCESS & AUTHORIZATION STATE MACHINE:
[ Borrower Receives Physical Speed Post Notice or SMS ]
                      │
        ┌─────────────┴─────────────┐
        ▼                           ▼
[ Scans Notice QR Code ]   [ Opens Portal Manually ]
        │                           │
        ▼ (Token in URL)            ▼ (Enters LAN or ARB Number)
POST /case-access-login     POST /api/method/auth/initiate
        │                           │
        │                           ├───────────────────────────────────┐
        │                           ▼ (Contact info exists)             ▼ (No phone/email on file)
        │                   [ Frappe Dispatches OTP ]           [ Requires Cross-Verification ]
        │                           │                                   │
        │                   POST /auth/verify-otp               POST /auth/verify-cross
        │                   (Submits 6-digit code)              (Submits matching LAN + ARB)
        │                           │                                   │
        └───────────────────────────┼───────────────────────────────────┘
                                    │
                                    ▼
┌────────────────────────────────────────────────────────────────────────┐
│ FastAPI Gateway Issues Stateless Session Token                         │
│ • Payload: {"lan": "LAN-84920", "iat": 1720000000, "exp": +TTL, "jti"} │
│ • Signature: HMAC-SHA256(payload, SESSION_SECRET)                      │
│ • Returns: token string & session TTL to Leptos WASM Client            │
└────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼ (Subsequent Requests with Bearer Token)
┌────────────────────────────────────────────────────────────────────────┐
│ Case Invariant Enforcement in cases.py                                 │
│ • decode_session_token() verifies HMAC signature & checks revocations  │
│ • _enforce_query_lan(): requested LAN == session.lan                   │
│ • _enforce_case_lan(): fetched Frappe case.lan == session.lan           │
│ • Prevents horizontal privilege escalation: HTTP 403 on mismatch      │
└────────────────────────────────────────────────────────────────────────┘
05. ENGINEERING DEEP DIVES

Implementation Details Backed by Source Code

1. Stateless HMAC-SHA256 Session Tokens with Revocation Tracking

Rather than maintaining a server-side session table in a database, I implemented a lightweight, stateless token generation and verification mechanism in session_auth.py.

The token combines a URL-safe Base64-encoded JSON payload (lan, iat, exp, and unique jti) with an HMAC-SHA256 signature verified via constant-time comparison (hmac.compare_digest). An in-memory revocation map allows immediate logout invalidation with automatic expiration cleanup:

Sanitized Source Excerpt: session_auth.py
import base64, hashlib, hmac, json, secrets, time
from fastapi import HTTPException

_revoked_jti: dict[str, int] = {}

def create_session_token(lan: str) -> tuple[str, int]:
    now = int(time.time())
    exp = now + int(settings.SESSION_TTL_SECONDS)
    payload = {"lan": lan, "iat": now, "exp": exp, "jti": secrets.token_urlsafe(12)}
    payload_b64 = _b64url_encode(json.dumps(payload, separators=(",", ":")).encode())
    sig_b64 = _sign(payload_b64)
    return f"{payload_b64}.{sig_b64}", int(settings.SESSION_TTL_SECONDS)

def decode_session_token(token: str) -> dict:
    try:
        payload_b64, sig_b64 = token.split(".", 1)
    except ValueError:
        raise HTTPException(status_code=401, detail="Invalid session token")

    expected = _sign(payload_b64)
    # Constant-time comparison defeats timing attacks
    if not hmac.compare_digest(sig_b64, expected):
        raise HTTPException(status_code=401, detail="Invalid session token")

    payload = json.loads(_b64url_decode(payload_b64).decode())
    now = int(time.time())
    
    # Check in-memory revocations and expiration
    if payload.get("jti") in _revoked_jti or now >= int(payload.get("exp", 0)):
        raise HTTPException(status_code=401, detail="Session expired. Please login again.")
    return payload

2. Strict Case-Scoped Authorization Invariants

Because borrowers are authenticated via stateless tokens rather than enterprise RBAC roles, the gateway must strictly prevent horizontal privilege escalation (e.g. Borrower A altering request parameters to view Borrower B's case).

In routers/cases.py, every case detail fetch and document upload enforces double-layered LAN invariants: verifying the query parameter matches the session token, and confirming the fetched Frappe document matches the token LAN:

Sanitized Source Excerpt: routers/cases.py
def _session_lan(session_payload: dict) -> str:
    lan = (session_payload.get("lan") or "").strip()
    if not lan:
        raise HTTPException(status_code=401, detail="Invalid session")
    return lan

def _enforce_query_lan(session_lan: str, lan_query: str) -> None:
    if lan_query.strip().upper() != session_lan.strip().upper():
        raise HTTPException(status_code=403, detail="Access denied for requested LAN")

def _enforce_case_lan(session_lan: str, doc: dict) -> None:
    case_lan = (doc.get("lan") or doc.get("name") or "").strip()
    if not case_lan or case_lan.upper() != session_lan.strip().upper():
        raise HTTPException(status_code=403, detail="Access denied for this case")

3. Dual-Identifier Auto-Detection & Offline Cross-Verification

Physical notice letters feature both a Loan Account Number (issued by the lending bank) and an Arbitration Case Number (issued by the arbitral tribunal). In routers/auth.py, the system parses inbound identifiers using case-insensitive prefix matching:

If the identifier starts with ARB_PREFIX, it routes lookups by arb_case_num; otherwise, it queries by lan. For offline borrowers lacking phone/email records, /verify-cross requires the user to input both identifiers simultaneously, proving physical possession of the notice letter before permitting contact details to be recorded.

Resolution Desk OTP verification flow screenshot

Figure 2: Frictionless OTP verification view prompting borrowers with masked contact information and an ephemeral challenge countdown.

Resolution Desk secure identifier lookup screen

Figure 3: Secure identifier entry screen supporting dual Loan Account Number and Arbitration Case Number resolution.

06. ENGINEERING CHALLENGES & MITIGATIONS

Observed Constraints & How They Were Resolved

THE OFFLINE BORROWER AUTHENTICATION DILEMMA

Difficulty: Numerous delinquent loan files contained outdated telephone numbers or no email address whatsoever. Borrowers receiving physical speed post notices had no digital channel to receive an OTP.

Mitigation: Built the verify-cross endpoint. Litigants must match two independent pieces of data present solely on their physical paper notice: the Loan Account Number and the Tribunal Case Number. Once matched, they enter their current telephone/email, allowing firm case managers to verify identity before granting portal access.

CASE-SCOPED AUTHORIZATION INVARIANTS

Technical Constraint: Because litigants authenticate via case-specific tokens rather than enterprise user accounts, the gateway must strictly prevent horizontal privilege escalation or unauthorized document access across dispute records.

Implementation: Enforced two-tier LAN verification (_enforce_query_lan and _enforce_case_lan) on all dispute endpoints. Every operation verifies both the query parameter and the upstream Frappe case record against the Loan Account Number encoded in the signed bearer token, rejecting mismatches with HTTP 403 Forbidden.

07. DEPLOYMENT & OPERATIONS

Production Topology & PWA Packaging

Resolution Desk runs as a containerized FastAPI service deployed alongside Nginx on Linux production infrastructure:

LEPTOS PWA PACKAGING

Compiled to WebAssembly with service worker registration (sw.js) and manifest.json, enabling instant mobile loading over flaky 3G cellular connections.

EDGE NGINX PROXY

Terminates TLS, serves static WASM bundles with proper MIME headers, and forwards /api/* traffic to internal Uvicorn workers.

STRUCTURED LOGGING & HEALTH

Outputs structured JSON logs with request correlation IDs to stdout. Exposes /health probes for container monitoring.

08. LIMITATIONS & LESSONS LEARNED

Architectural Trade-Offs & Next Steps

  • In-Memory Revocation Scope: The token revocation map (_revoked_jti) is held in Python process memory. In a multi-worker or multi-container environment, revocations are process-local unless backed by an external distributed store like Redis.
  • Upstream Messaging Dependency: One-time passcode generation and dispatch rely on upstream enterprise messaging services. If external telecommunication delivery experiences latency, the client interface provides countdown-based retry triggers while the session token architecture remains fully stateless.
  • Future Evolution: Adding WebAuthn / Passkeys as a one-tap subsequent authentication upgrade for mobile borrowers after their initial verified OTP session.
09. VERIFIED EVIDENCE & PRIVACY BOUNDARY
CONFIDENTIALITY STATEMENT:

Resolution Desk handles sensitive dispute proceedings involving individual borrowers. To protect consumer privacy and comply with financial confidentiality standards, all screenshots display anonymized or synthetic dispute records. All architectural diagrams, session models, and code excerpts reflect the genuine implementation in the repository.