Ankit Sinha All projects

Selected work / 01

GateKeep

Encrypted, expiring notes with passphrase gates, atomic read limits, and access analytics.

TypeScript Node.js Express PostgreSQL Drizzle Supabase Storage Docker Render

The problem

Sharing a sensitive note usually means choosing between a permanent link and a screenshot. A link is convenient, but it tends to outlive the reason it was sent. GateKeep explores a stricter middle ground: a note should be readable by the right person, for a limited number of reads, and then disappear.

The hard part is not the text editor or the browser UI. It is making the promise hold when a link is opened twice at once, when a client retries, when a file is requested after the note expires, and when someone tries to guess a passphrase.

How it fits together

Browser
  │  JWT session
  ▼
Express API ─────────────── PostgreSQL
  │                         ├─ note metadata, expiry, read counts
  │                         ├─ access analytics
  │                         └─ HMAC webhook events
  │
  ├── AES-256-GCM encrypted note content
  │
  ├── Supabase Storage
  │     └─ private files → short-lived signed URLs
  │
  └── rate limits

The API owns expiry, passphrase verification, read counters, file authorization, and analytics. The browser only receives what it needs for the current request.

Design decisions

Expiry and read limits live in the database

A read is an atomic operation in Postgres, not a client-side counter. Two simultaneous viewers cannot both consume the last allowed read, and an expired note cannot be revived by refreshing the page.

Passphrases are checked before content is returned

The note link is not the secret. The passphrase gate is verified server-side before the note body or file URL is released, so the passphrase never has to live in the URL or in browser state.

Encryption at rest, private files in storage

Note content is encrypted with AES-256-GCM before storage. Uploads stay in a private Supabase bucket and are exposed only through short-lived signed URLs after authorization.

Webhooks are signed and retried

Analytics and notification events are sent with an HMAC signature so receivers can verify the payload. Retries make delivery more resilient without turning every failed request into a manual cleanup task.

At a glance

28unit + E2E tests
plus integration coverage
AES-256GCM note encryption
3access gates: passphrase, expiry, read limit
HMACsigned, retried webhooks

The part I care about most: the read limit is enforced by the database, not by UI state.

What I'd do next