Michilis fe2b95258d feat: admin endpoints to reset username sync flags
Add POST /v1/admin/users/{pubkey}/reset-username and
POST /v1/admin/users/reset-usernames to clear manual_username
and last_synced_at so nostr profile sync re-evaluates users.
Includes OpenAPI docs, audit actions, and tests.
2026-05-06 19:31:13 +00:00
2026-04-29 02:35:00 +00:00
2026-04-29 02:35:00 +00:00
2026-04-29 02:35:00 +00:00
2026-04-29 02:35:00 +00:00
2026-04-29 02:35:00 +00:00
2026-04-29 02:35:00 +00:00
2026-04-29 02:35:00 +00:00
2026-04-29 02:35:00 +00:00
2026-04-29 02:35:00 +00:00
2026-04-29 02:35:00 +00:00
2026-04-29 02:35:00 +00:00
2026-04-29 02:35:00 +00:00

nip05api

A single-domain NIP-05 identity service. Lightning-paid registration through LNbits. Lifecycle DMs over Nostr. Optional outbound webhook on payment events. Swagger docs. One binary, one SQLite file, one systemd unit.

See SPEC.md for the full design specification.

Features

  • NIP-05 lookup at /.well-known/nostr.json for active subscribers
  • Yearly or lifetime subscriptions, BOLT11 invoices via LNbits
  • Renewal detection by pubkey: same user, same username, extended expiry
  • Configurable expiry-reminder DMs and grace-period username reservation
  • Profile sync: pulls kind:0 from configured relays and updates non-pinned usernames
  • HMAC-signed webhook outbox with exponential retry
  • NIP-04 or NIP-17 (gift-wrapped) DM delivery
  • Pure-Go SQLite, no CGO; static binary under 20 MB
  • Embedded migrations applied on boot
  • Embedded OpenAPI spec served at /openapi.json, Swagger UI at /docs

Quick start

git clone https://github.com/noderunners/nip05api.git
cd nip05api
cp .env.example .env
cp messages.example.yaml messages.yaml
# Edit .env: DOMAIN, ADMIN_API_KEY, LNBITS_*, DM_NSEC, RELAYS
make build
./bin/nip05api

The binary creates .data/nip05.db, applies migrations, and starts listening on PORT (default 8080). Hit http://localhost:8080/healthz to confirm.

Configuration

All config is environment-driven. See .env.example for the full list. Required keys:

Key Notes
DOMAIN the apex domain users will be name@domain on
ADMIN_API_KEY 24+ chars; sent as X-API-Key on /v1/admin/*
LNBITS_URL, LNBITS_INVOICE_KEY required when LIGHTNING_ENABLED=true
RELAYS comma-separated wss://..., required when DM or sync enabled
DM_NSEC required when DM_ENABLED=true

Subsystems toggle independently: LIGHTNING_ENABLED, DM_ENABLED, USERNAME_SYNC_ENABLED, plus webhook is enabled by setting WEBHOOK_URL.

API surface

Eleven endpoints, full schema at /openapi.json and rendered at /docs:

Method Path Auth Purpose
GET /.well-known/nostr.json NIP-05 lookup
GET /healthz DB ping + version
GET /v1/pricing yearly/lifetime sat pricing
GET /v1/users/{pubkey} lookup by hex or npub
GET /v1/usernames/{name}/available availability check
POST /v1/invoices create payment invoice
GET /v1/invoices/{hash} invoice status
POST /v1/admin/users api key manual add
GET /v1/admin/users api key list / search
PUT /v1/admin/users/{pubkey} api key pin username
DELETE /v1/admin/users/{pubkey} api key hard delete
POST /v1/admin/users/{pubkey}/extend api key extend or upgrade

Deployment

make build
sudo make install-systemd
sudo cp .env /opt/nip05api/.env
sudo systemctl enable --now nip05api
journalctl -u nip05api -f

The unit is hardened (no-new-privileges, ProtectSystem=strict, capability-bound to nothing) and runs as a dedicated nip05 user. See deploy/nip05api.service.

Docker

docker build -t nip05api:latest .
docker compose up -d

The image is multi-stage and ships from gcr.io/distroless/static-debian12:nonroot. Compose binds the data directory and messages.yaml from the host so updates to copy don't require a rebuild.

Reverse proxy

A reverse proxy terminates TLS and forwards /.well-known/nostr.json, /v1/*, /healthz, /openapi.json, and /docs to 127.0.0.1:8080.

Sample configs:

The proxy must forward X-Forwarded-For or X-Real-IP; the API uses these for rate limiting and access logs.

Operations

Database

SQLite WAL-mode at DATABASE_PATH (default .data/nip05.db). Backups while the service is running:

sqlite3 .data/nip05.db ".backup .data/backup-$(date +%F).db"

WAL files (-wal, -shm) are checkpointed on graceful shutdown. To rotate: stop the service, copy nip05.db, nip05.db-wal, nip05.db-shm together, restart.

Migrations

Embedded as .sql files under internal/db/migrations/. They run inside a transaction on every boot and record their version in schema_migrations. Applying twice is a no-op. To add a migration: drop a new NNNN_description.sql next to the others; rebuild.

Audit log

Admin actions, payment confirmations, expiries, and grace-period purges all write to audit_log. Cleanup worker prunes entries older than 180 days hourly.

Outbox cleanup

Delivered webhook and DM rows are pruned after 7 days; dead (gave up after all retries) after 30 days. Tunable in cmd/nip05api/cleanup.go.

Crash safety

  • Migrations: transactional, idempotent.
  • Payment confirmation: idempotent. The first confirmation captures target_expires_at on the invoice row; subsequent retries apply the same absolute value, so a crash mid-confirm cannot double-extend a renewal.
  • Webhook delivery: outbox + retry schedule (30s, 2m, 10m, 1h, 6h), HMAC-signed if WEBHOOK_SECRET is set.
  • DM delivery: outbox + retry schedule (1m, 5m, 30m, 2h, 12h).
  • Expiry pass: each phase (reminders → expirations → grace cleanup) updates idempotent flags, so partial completion is safe to resume.
  • Workers all watch a single root context; SIGINT/SIGTERM closes them in order with a 30s HTTP grace window.

Observability

  • Logs: structured JSON to stdout via slog; ship to journald or your logging stack of choice. request_id is propagated through middleware.
  • Secrets that never appear in logs: ADMIN_API_KEY, LNBITS_INVOICE_KEY, WEBHOOK_SECRET, DM_NSEC, BOLT11 strings, DM plaintext.
  • Health: /healthz returns 200 with version + db status, 503 if DB unreachable.

Development

make tidy         # go mod tidy
make build        # static binary in bin/
make test         # short tests
make test-race    # all tests with -race
make lint         # golangci-lint, see .golangci.yml
make docker       # build container image

Module layout:

cmd/nip05api/        entry point + cleanup worker
internal/audit/      append-only audit log
internal/config/     env loader + validation
internal/db/         SQLite + embedded migrations
internal/dm/         outbox + NIP-04/NIP-17 builder + worker
internal/expiry/     daily reminders / expirations / grace cleanup
internal/http/       chi router, middleware, handlers, OpenAPI docs
internal/invoice/    LNbits client + service + repo
internal/log/        slog setup
internal/messages/   YAML template loader + renderer
internal/nostr/      keys, relay pool, profile fetch, publish
internal/payments/   poll LNbits → confirm → dispatch DM/webhook/audit
internal/sync/       periodic kind:0 username sync
internal/user/       repo + service
internal/webhook/    outbox + HMAC signer + worker

Each Go file is kept under ~200 lines. The dependency graph points downward: handlers depend on services, services on repos, repos on db. Nothing in internal/ depends on internal/http/.

License

See LICENSE (MIT).

Description
No description provided
Readme MIT 705 KiB
Languages
JavaScript 89.6%
Go 10.3%