Skip to main content

External portal flow (the arguido's view)

The accused employee (or external counsel) accesses the case through an unauthenticated, token-based portal — no Supabase login. Backed by the portal-access edge function (service-role for everything) and the route /portal/:token.

Tokens

process_portal_links: token (default encode(gen_random_bytes(32),'hex'), UNIQUE), invited_email, invited_name, invited_role (EMPLOYEE / EXTERNAL_COUNSEL), expires_at, revoked_at, otp_required (default false, unused), allow_submission. validateToken requires revoked_at IS NULL and checks expires_at > now in code.

What's exposed

portal_items control visibility per link, with a visibility window (visible_from / visible_until) enforced in two places (list query + item re-check). item_type maps to a backing table: CASE_ATTACHMENT, EVIDENCE_ITEM, DELIVERABLE, CHECKLIST_ATTACHMENT. Files are served via 1-hour signed URLs from case-attachments; download requires allow_download. Deliverables with no file fall back to inline_content (the text description).

Submission

If allow_submission, the employee can POST a message (max 5000 chars) and/or files (→ case_messages + case_attachments, both is_from_employee: true). Handlers are then notified via notifications rows (type: 'PORTAL_SUBMISSION'); the email notification is a console.log stub.

Access logging

Every action → portal_access_log (LOGIN / VIEW_ITEM / DOWNLOAD_ITEM / SUBMIT_RESPONSE, with ip_address, user_agent).

Gotchas / dangers

  • No rate limiting or brute-force protection on token guessing (portal no rate-limit).
  • The route the UI actually links to for the "public consultation" is inconsistent — buttons point at /consulta/:slug, which isn't routed (broken links).
  • Legacy cases.access_password (plaintext) survives from the whistleblower era but the portal is now token-based (access password).

Rebuild implication

Keep opaque-token access + visibility windows + access log; add rate limiting, decide the URL scheme once, wire the notification email, and drop the legacy password.