feat: add NIP-44 E2E encryption for push notifications#27
Conversation
Encrypt notification payloads to client device pubkeys using NIP-44 v2,
so Apple only sees ciphertext. Clients decrypt locally with their device
private key.
## Changes
### Infrastructure
- Enable `nip44` feature on nostr crate (uses rust-nostr implementation)
- Add `server_keys.rs` for server keypair management
- Add `NIP44_ENABLED` and `SERVER_PRIVKEY` environment variables
- Add `device_pubkey` column to `user_info` table
### API Endpoints
- `GET /server-pubkey` (public) - Returns server's pubkey for decryption
- `PUT /user-info/:pubkey/:deviceToken/encryption-key` (NIP-98 auth)
Registers device pubkey for encrypted notifications
### Notification Encryption
- When device has registered pubkey, encrypt event JSON with NIP-44 v2
- Payload format: `{"encrypted": true, "ciphertext": "<base64>"}`
- Fallback to plaintext for devices without registered pubkey
- Structured logging for metrics (nip44_notification, nip44_encryption_error)
## Environment Variables
- `NIP44_ENABLED=true` - Enable E2E encryption
- `SERVER_PRIVKEY=<64-char-hex>` - Server's secp256k1 secret key
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix INSERT OR REPLACE dropping device_pubkey on re-registration by using UPSERT (ON CONFLICT DO UPDATE) pattern - Require SERVER_PRIVKEY when NIP44_ENABLED=true (add NIP44_ALLOW_EPHEMERAL env var for explicit dev mode opt-in) - Return 404 instead of 500 when setting encryption-key before device registration - Downgrade sensitive logs (device tokens, full pubkeys) from info to debug - Add NIP-44 encrypt/decrypt roundtrip tests 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Residual risks addressed: - Replace string-matching error detection in /encryption-key endpoint with typed NotificationManagerError::DeviceNotRegistered for robustness - Add 5 tests for env gating logic (SERVER_PRIVKEY + allow_generate) - Refactor from_env_or_generate to extract testable from_env_value helper 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Test 1: Full encrypted notification flow (register → encrypt → decrypt) - Test 2: Plaintext fallback when device has no pubkey - Test 3: UPSERT preserves device_pubkey on re-registration - Test 4: Typed error when setting encryption key before registration Uses TestHarness with in-memory SQLite to test DB operations and NIP-44 encryption without requiring APNs client. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Mock APNs Integration TestsSince the APNs client is tightly coupled and requires real credentials, I created a What's TestedTest 1: Test 2: Test 3: Test 4: Test Harness Designstruct TestHarness {
db: Arc<Mutex<r2d2::Pool<SqliteConnectionManager>>>, // In-memory SQLite
server_keys: ServerKeys, // Generated test keys
}The harness mirrors the |
|
cc @tyiu @danieldaquino @jb55 ready for review sers |
Add platform-based routing to send notifications via APNs (iOS) or
ntfy (Android). This enables push notifications for Android devices
without requiring Google Play Services.
Changes:
- Add Platform enum (ios/android) with routing logic
- Add ntfy HTTP client using reqwest
- Add platform column to user_info database table
- Accept optional "platform" field in device registration API
- Add NTFY_SERVER_URL environment variable (default: ntfy.damus.io)
API usage for Android:
PUT /user-info/:pubkey/:deviceToken
{"platform": "android"}
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
Closes #13 |
|
convert to nip-59 - happens automatically in nostrdb |
|
@coderabbitai review please |
|
✅ Actions performedReview triggered.
|
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@coderabbitai review please |
|
✅ Actions performedReview triggered.
|
|
@coderabbitai review please |
|
✅ Actions performedReview triggered.
|
Summary
Changes
New Features
GET /server-pubkey- Public endpoint for clients to discover server's encryption pubkeyPUT /user-info/:pubkey/:deviceToken/encryption-key- Register device pubkey for encrypted notificationsConfiguration
NIP44_ENABLED=true- Enable encryption (default: false)SERVER_PRIVKEY=<hex>- Required server private key for stable identityNIP44_ALLOW_EPHEMERAL=true- Dev mode: allow ephemeral keys (not for production)Code Quality
NotificationManagerErrorfor robust error handlingTest Results
Commits
feat: add NIP-44 E2E encryption for push notifications- Core implementationfix: address NIP-44 code review feedback- UPSERT fix, env gating, log cleanupfix: use typed error for device-not-found, add env gating tests- Robust error handlingtest: add NIP-44 integration tests with in-memory DB- Full flow tests🤖 Generated with Claude Code