Skip to content

andrei-trukhin/one-time-secret

Repository files navigation

One Time Secret

one-time-secret is a self-hosted web app for sharing secrets via a link that can be read once (optional) and/or expires automatically.

The server stores only encrypted ciphertext in MongoDB. Encryption and decryption happen on the client.

How it works

  1. You enter a secret (plain text or a JSON object) and optional settings.

  2. The browser encrypts it using AES-256-GCM.

  3. The app saves the encrypted payload (ciphertext) in MongoDB under a random key.

  4. You get a share URL of the form:

    /secret/{uniqueKey}:{decryptionKey}
    

    The uniqueKey is used to fetch the ciphertext from the server, and the decryptionKey is used by the browser to decrypt it.

Features

  • Client-side encryption (AES-256-GCM)
  • Optional passphrase (used to derive the encryption key via PBKDF2)
  • Burn-after-read (deletes the stored ciphertext after a successful read)
  • Expiration date for secrets
  • Localized routes via /{locale} (i18n)

Tech stack

  • Next.js (App Router)
  • React
  • MongoDB
  • Jest (unit tests)
  • Tailwind CSS

Prerequisites

  • Node.js (see package.json engines/scripts if you pin versions in your environment)
  • A MongoDB instance (local or remote)

Configuration

The app uses MONGODB_URI for database access.

  • In development, if MONGODB_URI is not set, it defaults to:

    mongodb://root:password@localhost:27017/one-time-secret?authSource=admin
    
  • In production, MONGODB_URI must be set.

Local development

1) Install dependencies

npm ci

2) Start MongoDB (Docker)

This repository includes compose.yaml to run MongoDB locally:

docker compose up -d

Optionally initialize indexes/collections (uses mongosh):

npm run db:init

3) Run the dev server

npm run dev

Open http://localhost:3000.

Tests

npm test

API (high level)

  • POST /api/v1/secret — store an encrypted secret with metadata (expirationDate, burnAfterRead, ...)
  • GET /api/v1/secret/key/{key} — fetch the encrypted secret; if burnAfterRead is enabled it is deleted after being read

Security notes / limitations

  • The share URL contains the decryption key as part of the path ({uniqueKey}:{decryptionKey}). Treat the full URL as sensitive: it can end up in browser history, logs, analytics, referer headers, screenshots, etc.
  • The server never sees the plaintext secret, but it can see/store the ciphertext and metadata.
  • If you set a passphrase, it is required to decrypt in the browser.

License

See LICENSE.