Skip to content

dsbaars/bunker46

Repository files navigation

Bunker46

Bunker46

CI TypeScript Vue Node pnpm License

A modern, secure NIP-46 Nsec Bunker management tool built with TypeScript.

Screenshots

Login Dashboard
Login Dashboard
Connections Keys
Connections Keys

Screenshots are captured with Playwright (WebP). The connections screenshot uses mocked API data. To regenerate: run pnpm dev in one terminal, then pnpm run e2e:screenshots.

Stack

Layer Technology
Frontend Vue 3, Vite 8, Tailwind CSS v4, shadcn-vue, nanostores, localforage
Backend NestJS 11, Fastify, Prisma ORM 7 (driver adapter), nostr-tools
Database PostgreSQL 17
Auth JWT + Argon2, TOTP (otplib), WebAuthn/Passkeys (@simplewebauthn)
Testing Vitest, Playwright, eslint-plugin-security
Infra Docker, Docker Compose, Node 24, pnpm 10, Redis (optional, for live updates)

Architecture

bunker46/
├── apps/
│   ├── server/          # NestJS + Fastify backend
│   │   ├── src/
│   │   │   ├── auth/    # JWT, TOTP, WebAuthn, session management
│   │   │   ├── bunker/  # NIP-46 RPC handler, relay pool, URI parsing
│   │   │   ├── connections/ # CRUD for bunker connections & permissions
│   │   │   ├── events/  # Redis pub/sub, SSE stream (live dashboard/connections)
│   │   │   ├── logging/ # Signing logs & dashboard stats
│   │   │   ├── users/   # User management
│   │   │   ├── prisma/  # Database service
│   │   │   └── common/  # Encryption, guards, interceptors
│   │   └── prisma/      # Schema & migrations
│   └── web/             # Vue 3 SPA
│       ├── src/
│       │   ├── components/ # UI components (shadcn-style)
│       │   ├── views/     # Route views
│       │   ├── stores/    # nanostores + localforage
│       │   ├── router/    # Vue Router
│       │   └── lib/       # API client, utilities
│       └── e2e/          # Playwright E2E tests
├── packages/
│   ├── shared-types/    # Zod schemas & TypeScript types
│   ├── config/          # Environment config & constants
│   ├── tsconfig/        # Shared TypeScript configs
│   └── eslint-config/   # Shared ESLint configs
├── docker-compose.yml   # Production Docker Compose
└── docker-compose.dev.yml # Dev services (DB, Redis)

Quick Start

Prerequisites

  • Node.js >= 24
  • pnpm >= 10
  • Docker & Docker Compose (for database)

Development

# Install dependencies
pnpm install

# Start database services
docker compose -f docker-compose.dev.yml up -d

# Copy environment config
cp .env.example .env
# Optional: set REDIS_URL=redis://localhost:6379 in .env for live dashboard/connections updates
# Optional: set ALLOW_REGISTRATION=false to disable new user sign-ups (backend returns 403; frontend hides register link)
# Optional: set TRUST_PROXY=true when the API is behind a reverse proxy (e.g. Caddy) so session IPs and rate limiting use the real client IP

# Generate Prisma client (required; runs automatically on pnpm install) & run migrations
pnpm db:generate
pnpm db:migrate
# Optional: seed the database (Prisma 7 does not auto-seed after migrate)
# pnpm --filter @bunker46/server run db:seed

# Start dev servers (backend + frontend)
pnpm dev

The frontend will be available at http://localhost:5173 and the API at http://localhost:3000. With Redis running and REDIS_URL set, the dashboard and connections list refresh automatically when activity occurs.

If ports 3000 or 5173 are already in use (e.g. a previous dev run didn’t exit cleanly), free them and restart:

pnpm kill-port
pnpm dev

Testing

# Run all unit/integration tests
pnpm test

# Run with coverage
pnpm test:coverage

# Run E2E tests (Playwright; starts full stack from repo root so API works)
pnpm e2e

# If you already have `pnpm dev` running, reuse it to avoid port conflicts:
# CI= pnpm e2e

# Run E2E with UI
pnpm e2e:ui

# When the server is started by Playwright (pnpm dev), rate limiting is skipped for localhost in non-production so E2E does not hit limits. For E2E_USE_PREVIEW, start the server yourself; localhost requests are also skipped when NODE_ENV is not production.
# Lint (includes security rules)
pnpm lint

# Security-specific lint
pnpm lint:security

Production (Docker)

docker-compose.yml is the production example. It is secure by default: it has no fallback secrets and does not publish the database/Redis ports to the host, so it will refuse to start until you provide strong secrets.

# 1. Generate strong secrets and write them to a .env next to docker-compose.yml
cat > .env <<EOF
JWT_SECRET=$(openssl rand -base64 48)
JWT_REFRESH_SECRET=$(openssl rand -base64 48)
ENCRYPTION_KEY=$(openssl rand -base64 48)
# Set these to your real public origin:
CORS_ORIGINS=https://bunker.example.com
WEBAUTHN_RP_ID=bunker.example.com
WEBAUTHN_ORIGIN=https://bunker.example.com
EOF

# 2. Build and run all services
docker compose up --build

# 3. Access the application (front it with TLS via your reverse proxy)
open http://localhost:8080

⚠️ ENCRYPTION_KEY protects the stored Nostr private keys. If you lose or change it, existing encrypted keys can no longer be decrypted. Back it up and rotate deliberately.

Production deployment checklist:

  • Strong, unique JWT_SECRET, JWT_REFRESH_SECRET, ENCRYPTION_KEY (not the dev placeholders — the server refuses to start with them when NODE_ENV=production)
  • TLS terminated by a reverse proxy (Caddy/Nginx); only the web/proxy entrypoint is exposed publicly
  • TRUST_PROXY=true set only when the app is reachable solely through that trusted proxy
  • CORS_ORIGINS, WEBAUTHN_RP_ID, WEBAUTHN_ORIGIN set to your real public origin
  • ALLOW_REGISTRATION left disabled (default) unless you intend open sign-ups; the first account can still be created on first run
  • Postgres/Redis not published to the host (default) — reachable only over the internal Docker network
  • Database backups and a tested restore procedure in place

When running behind a reverse proxy (e.g. Caddy, Nginx), set TRUST_PROXY=true (or 1 / yes) in the server environment so that session IPs and throttling use the client IP from X-Forwarded-For / X-Real-IP. Only enable this when the app is only reachable through a trusted proxy.

NIP-46 Implementation

This tool implements the full NIP-46 Nostr Remote Signing specification:

  • URI Support: Parse and generate both bunker:// and nostrconnect:// URIs
  • All RPC Methods: connect, sign_event, ping, get_public_key, nip04_encrypt/decrypt, nip44_encrypt/decrypt, switch_relays
  • Fine-grained Permissions: Per-connection method and event-kind restrictions, enforced default-deny (a connection with no permissions can sign/decrypt nothing). New connections get a conservative default set (common signing kinds, no decryption). Following the NIP-46 model for a non-interactive signer, a connecting client may declare the scope it needs in its connect request and that becomes the connection's permission set, so only connect clients you trust with the bound key; operators can tighten any connection's permissions in the dashboard at any time.
  • NIP-44 Encryption: All communication encrypted using NIP-44
  • Auth Challenges: Support for out-of-band authentication
  • Relay Management: Configurable relays per connection with automatic reconnection

Security

  • Private keys (nsecs) are encrypted at rest using AES-256-GCM
  • Passwords hashed with Argon2
  • 2FA via TOTP and WebAuthn/Passkeys
  • JWT with short-lived access tokens and refresh token rotation
  • Session management: list active sessions (with IP and user-agent), revoke a session, or log out all other sessions
  • HTTP-only secure cookies option
  • Security ESLint rules enforced
  • Non-root Docker containers

CORS / CSRF: Keep CORS_ORIGINS strict: use explicit origins (e.g. http://localhost:5173 or your frontend URL), never * when credentials are used. Avoid overly broad origins to reduce cross-site request risk; consider SameSite cookie attributes and CSRF tokens for critical state-changing actions.

Dependencies

To check for outdated packages across the monorepo:

pnpm outdated -r

The monorepo tracks current major versions (TypeScript 6, Vite 8, Prisma 7, Vitest 4, Vue Router 5, Zod 4). Run pnpm outdated -r periodically and upgrade deliberately, running the full test suite after each bump.

License

MIT

About

A modern, secure NIP-46 Nsec Bunker management tool built with TypeScript.

Resources

License

Stars

12 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors