Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Impossible O1 — Evidence Tracker

A Google Sheets–style web app for tracking O-1A visa evidence. Each user creates a packet (a tiny per-user SQLite file on the server), protects it with a password, tracks evidence against the ten O-1A criteria, and shares the link + password with lawyers or friends.

Hosted at app.impossibleo1.com.

How it works

  • Create packet → server generates an unguessable 128-bit ID and creates data/packets/<id>.db (a real SQLite file per packet), seeded with the ten O-1A criteria.
  • Password → hashed with scrypt (salted) and stored in the packet's own SQLite file. The server never stores plaintext passwords and there is no recovery — keep it safe.
  • Unlock → correct password returns a stateless 30-day HMAC token (AUTH_SECRET signs it). The browser keeps it in localStorage.
  • Share → send someone the packet link (/p/<id>) + the password. They get full view/edit access.
  • Autosave → edits debounce-save the whole sheet to the server.

Stack

  • Next.js 15 (App Router, standalone output) + React 19
  • better-sqlite3 — one .db file per packet under data/packets/
  • No external database, no accounts, no email

Develop

npm install
npm run dev        # http://localhost:3000

Production

# Required in production — signs access tokens. Without it a random secret is
# generated at data/.secret (tokens invalidate if that file is lost).
export AUTH_SECRET="$(openssl rand -hex 32)"

npm ci
npm run build
npm run start      # serves on :3000

Deploy on any Node 20+ host and point your reverse proxy (nginx/Caddy) for app.impossibleo1.com at the Node process. SQLite writes to local disk, so use a real VPS/container with persistent storage — not ephemeral serverless.

Persistence: the entire app state lives in data/ (packet .db files + .secret). Mount it as a persistent volume and back it up. Optionally override its location with DATA_DIR.

API

Method Route Auth Purpose
POST /api/packets Create packet {name, password}{id, token}
POST /api/packets/:id/unlock rate-limited {password}{token, name}
GET /api/packets/:id Bearer token Read {name, tabs} (401 {locked, name} without token)
PUT /api/packets/:id Bearer token Replace all tabs {tabs: {<tabKey>: rows[]}}

Evidence tabs

Each packet is a workbook of 14 tabs matching the O-1A regulatory context: one Major Award path (a one-time internationally recognized award), the 9 criterion tabs (document at least 3 — prizes/awards, press, original contributions, scholarly articles, exhibitions, leading/critical roles, high salary, judging/panels, memberships), plus logistics tabs — Expert Reviewers (recommendation letters), Final Documents (signature checklist), Costs (budget) — and an Other/Supporting tab.

Columns are typed per field: text, date (native date picker), number (strength, min 1 / max 10), currency (USD, summed in the status bar), select (editable combobox with suggestions), url / email (clickable links), and checkbox (click or Space to toggle). Each tab defines its own column schema in lib/sheet-model.ts.

Older v1 packet files (single sheet with a category column) are migrated automatically on first open: each row is routed to the tab matching its category and the category cell is dropped.

Security notes

  • Packet IDs are 128-bit random hex; the regex validation doubles as path-traversal protection (the id is a filename).
  • Passwords: scrypt + per-packet salt, compared with timingSafeEqual.
  • Unlock endpoint is rate-limited (10 failures / 5 min per IP+packet).
  • Tokens are HMAC-signed, packet-scoped, 30-day expiry.
  • Security headers set in next.config.ts.
  • SQLite files contain the sheet rows in plaintext — the password gates API access; it is not at-rest encryption. Treat data/ as sensitive.

About

O1A Evidence Tracker - Open Source

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages