lots of things that I forgot to commit 😂

This commit is contained in:
pablof7z 2023-07-06 22:29:31 +02:00
commit 6f16cafd41
13 changed files with 395 additions and 130 deletions

View file

@ -86,7 +86,11 @@ $ npm run nsecbunkerd start
## Testing with `nsecbunker-client` ## Testing with `nsecbunker-client`
nsecbunker ships with a simple client that can request signatures from an nsecbunkerd:
```
nsecbunker-client sign <target-npub> "hi, I'm signing from the command line with my nsecbunkerd!"
```
# Authors # Authors

122
package-lock.json generated
View file

@ -1,27 +1,27 @@
{ {
"name": "nsecbunkerd", "name": "nsecbunkerd",
"version": "0.6.1", "version": "0.6.5",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "nsecbunkerd", "name": "nsecbunkerd",
"version": "0.6.1", "version": "0.6.5",
"license": "CC BY-NC-ND 4.0", "license": "CC BY-NC-ND 4.0",
"dependencies": { "dependencies": {
"@inquirer/password": "^1.1.2", "@inquirer/password": "^1.1.2",
"@inquirer/prompts": "^1.2.3", "@inquirer/prompts": "^1.2.3",
"@nostr-dev-kit/ndk": "^0.6.0", "@nostr-dev-kit/ndk": "^0.6.5",
"@prisma/client": "^4.16.1", "@prisma/client": "^4.16.2",
"@scure/base": "^1.1.1", "@scure/base": "^1.1.1",
"@types/yargs": "^17.0.24", "@types/yargs": "^17.0.24",
"@typescript-eslint/eslint-plugin": "^5.60.0",
"@typescript-eslint/parser": "^5.60.0",
"dotenv": "^16.3.1", "dotenv": "^16.3.1",
"eslint-config-prettier": "^8.8.0", "eslint-config-prettier": "^8.8.0",
"eslint-plugin-import": "^2.27.5", "eslint-plugin-import": "^2.27.5",
"eventemitter3": "^5.0.1", "eventemitter3": "^5.0.1",
"isomorphic-ws": "^5.0.0",
"websocket-polyfill": "^0.0.3", "websocket-polyfill": "^0.0.3",
"ws": "^8.13.0",
"yargs": "^17.7.2" "yargs": "^17.7.2"
}, },
"bin": { "bin": {
@ -31,7 +31,7 @@
"devDependencies": { "devDependencies": {
"@types/debug": "^4.1.8", "@types/debug": "^4.1.8",
"@types/node": "^18.16.18", "@types/node": "^18.16.18",
"prisma": "^4.16.1", "prisma": "^4.16.2",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"typescript": "^5.1.3" "typescript": "^5.1.3"
} }
@ -961,9 +961,9 @@
} }
}, },
"node_modules/@nostr-dev-kit/ndk": { "node_modules/@nostr-dev-kit/ndk": {
"version": "0.6.0", "version": "0.6.5",
"resolved": "https://registry.npmjs.org/@nostr-dev-kit/ndk/-/ndk-0.6.0.tgz", "resolved": "https://registry.npmjs.org/@nostr-dev-kit/ndk/-/ndk-0.6.5.tgz",
"integrity": "sha512-0ptE6OIZhFW+aRRIXAI8PvUIoVU6iQLpiwFtJj48XAUO2EC3WiSuqKLshjg6wj1bbo9qGs1PyFS9AUhUlWWJtg==", "integrity": "sha512-D95sDEonyFJhdTM1YswPOAYKaScnOlvZcxoyt9SlXnibT+gC9AGLtwAmpziK9zPC2h5Hh2GX44BnYXUGolLjZw==",
"dependencies": { "dependencies": {
"@noble/hashes": "^1.3.1", "@noble/hashes": "^1.3.1",
"@noble/secp256k1": "^2.0.0", "@noble/secp256k1": "^2.0.0",
@ -987,12 +987,12 @@
} }
}, },
"node_modules/@prisma/client": { "node_modules/@prisma/client": {
"version": "4.16.1", "version": "4.16.2",
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.16.1.tgz", "resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.16.2.tgz",
"integrity": "sha512-CoDHu7Bt+NuDo40ijoeHP79EHtECsPBTy3yte5Yo3op8TqXt/kV0OT5OrsWewKvQGKFMHhYQ+ePed3zzjYdGAw==", "integrity": "sha512-qCoEyxv1ZrQ4bKy39GnylE8Zq31IRmm8bNhNbZx7bF2cU5aiCCnSa93J2imF88MBjn7J9eUQneNxUQVJdl/rPQ==",
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"@prisma/engines-version": "4.16.0-66.b20ead4d3ab9e78ac112966e242ded703f4a052c" "@prisma/engines-version": "4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81"
}, },
"engines": { "engines": {
"node": ">=14.17" "node": ">=14.17"
@ -1007,16 +1007,16 @@
} }
}, },
"node_modules/@prisma/engines": { "node_modules/@prisma/engines": {
"version": "4.16.1", "version": "4.16.2",
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-4.16.1.tgz", "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-4.16.2.tgz",
"integrity": "sha512-gpZG0kGGxfemgvK/LghHdBIz+crHkZjzszja94xp4oytpsXrgt/Ice82MvPsWMleVIniKuARrowtsIsim0PFJQ==", "integrity": "sha512-vx1nxVvN4QeT/cepQce68deh/Turxy5Mr+4L4zClFuK1GlxN3+ivxfuv+ej/gvidWn1cE1uAhW7ALLNlYbRUAw==",
"devOptional": true, "devOptional": true,
"hasInstallScript": true "hasInstallScript": true
}, },
"node_modules/@prisma/engines-version": { "node_modules/@prisma/engines-version": {
"version": "4.16.0-66.b20ead4d3ab9e78ac112966e242ded703f4a052c", "version": "4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81",
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.16.0-66.b20ead4d3ab9e78ac112966e242ded703f4a052c.tgz", "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81.tgz",
"integrity": "sha512-tMWAF/qF00fbUH1HB4Yjmz6bjh7fzkb7Y3NRoUfMlHu6V+O45MGvqwYxqwBjn1BIUXkl3r04W351D4qdJjrgvA==" "integrity": "sha512-q617EUWfRIDTriWADZ4YiWRZXCa/WuhNgLTVd+HqWLffjMSPzyM5uOWoauX91wvQClSKZU4pzI4JJLQ9Kl62Qg=="
}, },
"node_modules/@scure/base": { "node_modules/@scure/base": {
"version": "1.1.1", "version": "1.1.1",
@ -3159,6 +3159,14 @@
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
}, },
"node_modules/isomorphic-ws": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz",
"integrity": "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==",
"peerDependencies": {
"ws": "*"
}
},
"node_modules/jest-diff": { "node_modules/jest-diff": {
"version": "29.5.0", "version": "29.5.0",
"resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz",
@ -4006,13 +4014,13 @@
} }
}, },
"node_modules/prisma": { "node_modules/prisma": {
"version": "4.16.1", "version": "4.16.2",
"resolved": "https://registry.npmjs.org/prisma/-/prisma-4.16.1.tgz", "resolved": "https://registry.npmjs.org/prisma/-/prisma-4.16.2.tgz",
"integrity": "sha512-C2Xm7yxHxjFjjscBEW4tmoraPHH/Vyu/A0XABdbaFtoiOZARsxvOM7rwc2iZ0qVxbh0bGBGBWZUSXO/52/nHBQ==", "integrity": "sha512-SYCsBvDf0/7XSJyf2cHTLjLeTLVXYfqp7pG5eEVafFLeT0u/hLFz/9W196nDRGUOo1JfPatAEb+uEnTQImQC1g==",
"devOptional": true, "devOptional": true,
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"@prisma/engines": "4.16.1" "@prisma/engines": "4.16.2"
}, },
"bin": { "bin": {
"prisma": "build/index.js", "prisma": "build/index.js",
@ -4937,6 +4945,26 @@
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
}, },
"node_modules/ws": {
"version": "8.13.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
"integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/y18n": { "node_modules/y18n": {
"version": "5.0.8", "version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
@ -5550,9 +5578,9 @@
} }
}, },
"@nostr-dev-kit/ndk": { "@nostr-dev-kit/ndk": {
"version": "0.6.0", "version": "0.6.5",
"resolved": "https://registry.npmjs.org/@nostr-dev-kit/ndk/-/ndk-0.6.0.tgz", "resolved": "https://registry.npmjs.org/@nostr-dev-kit/ndk/-/ndk-0.6.5.tgz",
"integrity": "sha512-0ptE6OIZhFW+aRRIXAI8PvUIoVU6iQLpiwFtJj48XAUO2EC3WiSuqKLshjg6wj1bbo9qGs1PyFS9AUhUlWWJtg==", "integrity": "sha512-D95sDEonyFJhdTM1YswPOAYKaScnOlvZcxoyt9SlXnibT+gC9AGLtwAmpziK9zPC2h5Hh2GX44BnYXUGolLjZw==",
"requires": { "requires": {
"@noble/hashes": "^1.3.1", "@noble/hashes": "^1.3.1",
"@noble/secp256k1": "^2.0.0", "@noble/secp256k1": "^2.0.0",
@ -5576,23 +5604,23 @@
} }
}, },
"@prisma/client": { "@prisma/client": {
"version": "4.16.1", "version": "4.16.2",
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.16.1.tgz", "resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.16.2.tgz",
"integrity": "sha512-CoDHu7Bt+NuDo40ijoeHP79EHtECsPBTy3yte5Yo3op8TqXt/kV0OT5OrsWewKvQGKFMHhYQ+ePed3zzjYdGAw==", "integrity": "sha512-qCoEyxv1ZrQ4bKy39GnylE8Zq31IRmm8bNhNbZx7bF2cU5aiCCnSa93J2imF88MBjn7J9eUQneNxUQVJdl/rPQ==",
"requires": { "requires": {
"@prisma/engines-version": "4.16.0-66.b20ead4d3ab9e78ac112966e242ded703f4a052c" "@prisma/engines-version": "4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81"
} }
}, },
"@prisma/engines": { "@prisma/engines": {
"version": "4.16.1", "version": "4.16.2",
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-4.16.1.tgz", "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-4.16.2.tgz",
"integrity": "sha512-gpZG0kGGxfemgvK/LghHdBIz+crHkZjzszja94xp4oytpsXrgt/Ice82MvPsWMleVIniKuARrowtsIsim0PFJQ==", "integrity": "sha512-vx1nxVvN4QeT/cepQce68deh/Turxy5Mr+4L4zClFuK1GlxN3+ivxfuv+ej/gvidWn1cE1uAhW7ALLNlYbRUAw==",
"devOptional": true "devOptional": true
}, },
"@prisma/engines-version": { "@prisma/engines-version": {
"version": "4.16.0-66.b20ead4d3ab9e78ac112966e242ded703f4a052c", "version": "4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81",
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.16.0-66.b20ead4d3ab9e78ac112966e242ded703f4a052c.tgz", "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81.tgz",
"integrity": "sha512-tMWAF/qF00fbUH1HB4Yjmz6bjh7fzkb7Y3NRoUfMlHu6V+O45MGvqwYxqwBjn1BIUXkl3r04W351D4qdJjrgvA==" "integrity": "sha512-q617EUWfRIDTriWADZ4YiWRZXCa/WuhNgLTVd+HqWLffjMSPzyM5uOWoauX91wvQClSKZU4pzI4JJLQ9Kl62Qg=="
}, },
"@scure/base": { "@scure/base": {
"version": "1.1.1", "version": "1.1.1",
@ -7121,6 +7149,12 @@
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
}, },
"isomorphic-ws": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz",
"integrity": "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==",
"requires": {}
},
"jest-diff": { "jest-diff": {
"version": "29.5.0", "version": "29.5.0",
"resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz",
@ -7722,12 +7756,12 @@
} }
}, },
"prisma": { "prisma": {
"version": "4.16.1", "version": "4.16.2",
"resolved": "https://registry.npmjs.org/prisma/-/prisma-4.16.1.tgz", "resolved": "https://registry.npmjs.org/prisma/-/prisma-4.16.2.tgz",
"integrity": "sha512-C2Xm7yxHxjFjjscBEW4tmoraPHH/Vyu/A0XABdbaFtoiOZARsxvOM7rwc2iZ0qVxbh0bGBGBWZUSXO/52/nHBQ==", "integrity": "sha512-SYCsBvDf0/7XSJyf2cHTLjLeTLVXYfqp7pG5eEVafFLeT0u/hLFz/9W196nDRGUOo1JfPatAEb+uEnTQImQC1g==",
"devOptional": true, "devOptional": true,
"requires": { "requires": {
"@prisma/engines": "4.16.1" "@prisma/engines": "4.16.2"
} }
}, },
"punycode": { "punycode": {
@ -8369,6 +8403,12 @@
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
}, },
"ws": {
"version": "8.13.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
"integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
"requires": {}
},
"y18n": { "y18n": {
"version": "5.0.8", "version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",

View file

@ -1,6 +1,6 @@
{ {
"name": "nsecbunkerd", "name": "nsecbunkerd",
"version": "0.6.4", "version": "0.7.1",
"description": "nsecbunker daemon", "description": "nsecbunker daemon",
"main": "dist/index.js", "main": "dist/index.js",
"bin": { "bin": {
@ -35,21 +35,23 @@
"dependencies": { "dependencies": {
"@inquirer/password": "^1.1.2", "@inquirer/password": "^1.1.2",
"@inquirer/prompts": "^1.2.3", "@inquirer/prompts": "^1.2.3",
"@nostr-dev-kit/ndk": "^0.6.5", "@nostr-dev-kit/ndk": "^0.7.4",
"@prisma/client": "^4.16.1", "@prisma/client": "^4.16.2",
"@scure/base": "^1.1.1", "@scure/base": "^1.1.1",
"@types/yargs": "^17.0.24", "@types/yargs": "^17.0.24",
"dotenv": "^16.3.1", "dotenv": "^16.3.1",
"eslint-config-prettier": "^8.8.0", "eslint-config-prettier": "^8.8.0",
"eslint-plugin-import": "^2.27.5", "eslint-plugin-import": "^2.27.5",
"eventemitter3": "^5.0.1", "eventemitter3": "^5.0.1",
"isomorphic-ws": "^5.0.0",
"websocket-polyfill": "^0.0.3", "websocket-polyfill": "^0.0.3",
"ws": "^8.13.0",
"yargs": "^17.7.2" "yargs": "^17.7.2"
}, },
"devDependencies": { "devDependencies": {
"@types/debug": "^4.1.8", "@types/debug": "^4.1.8",
"@types/node": "^18.16.18", "@types/node": "^18.16.18",
"prisma": "^4.16.1", "prisma": "^4.16.2",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"typescript": "^5.1.3" "typescript": "^5.1.3"
} }

View file

@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "KeyUser" ADD COLUMN "revokedAt" DATETIME;

View file

@ -13,6 +13,7 @@ model KeyUser {
userPubkey String userPubkey String
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt updatedAt DateTime @default(now()) @updatedAt
revokedAt DateTime?
lastUsedAt DateTime? lastUsedAt DateTime?
description String? description String?
logs Log[] logs Log[]

View file

@ -1,52 +1,129 @@
import NDK, { NDKUser, NDKEvent, NDKPrivateKeySigner, NDKNip46Signer, NostrEvent } from '@nostr-dev-kit/ndk'; import NDK, { NDKUser, NDKEvent, NDKPrivateKeySigner, NDKNip46Signer, NostrEvent } from '@nostr-dev-kit/ndk';
import fs from 'fs';
const remotePubkey = process.argv[2]; const command = process.argv[2];
const content = process.argv[3]; const remotePubkey = process.argv[3];
const content = process.argv[4];
const dontPublish = process.argv.includes('--dont-publish');
const debug = process.argv.includes('--debug');
if (!content) { if (!command) {
console.log('Usage: node src/client.js <remote-npub> <content>'); console.log('Usage: node src/client.js <command> <remote-npub> <content> [--dont-publish] [--debug] [--pk <key>]');
console.log(''); console.log('');
console.log(`\t<command>: command to run (ping, sign)`);
console.log(`\t<remote-npub>: npub that should be published as`); console.log(`\t<remote-npub>: npub that should be published as`);
console.log(`\t<content>: event JSON to sign | or kind:1 content string to sign`); console.log(`\t<content>: event JSON to sign (no need for pubkey or id fields) | or kind:1 content string to sign`);
console.log('\t--dont-publish: do not publish the event to the relay');
console.log('\t--debug: enable debug mode');
process.exit(1); process.exit(1);
} }
async function createNDK(): Promise<NDK> { async function createNDK(): Promise<NDK> {
const ndk = new NDK({ const ndk = new NDK({
explicitRelayUrls: ['wss://nostr.vulpem.com', "wss://67aee52897df.ngrok.app"], explicitRelayUrls: ['wss://nostr.vulpem.com', "wss://relay.nsecbunker.com"],
}); });
ndk.pool.on('relay:connect', () => console.log('✅ connected')); if (debug) {
ndk.pool.on('relay:disconnect', () => console.log('❌ disconnected')); ndk.pool.on('connect', () => console.log('✅ connected'));
ndk.pool.on('disconnect', () => console.log('❌ disconnected'));
}
await ndk.connect(5000); await ndk.connect(5000);
return ndk; return ndk;
} }
// switch (command) {
// case 'ping':
// ping(remotePubkey);
function getPrivateKeyPath() {
const home = process.env.HOME || process.env.USERPROFILE;
return `${home}/.nsecbunker-client-private.key`;
}
function savePrivateKey(pk: string) {
const path = getPrivateKeyPath();
if (!fs.existsSync(path)) {
fs.mkdirSync(path);
}
fs.writeFileSync(`${path}/private.key`, pk);
}
function loadPrivateKey(): string | undefined {
const path = getPrivateKeyPath();
if (!fs.existsSync(path)) {
return undefined;
}
return fs.readFileSync(`${path}/private.key`).toString();
}
(async () => { (async () => {
const remoteUser = new NDKUser({npub: remotePubkey}); const remoteUser = new NDKUser({npub: remotePubkey});
const ndk = await createNDK(); const ndk = await createNDK();
const localSigner = NDKPrivateKeySigner.generate();
// const localSigner = new NDKPrivateKeySigner('b8baad35c387d7cf84d52e0958d9a02aff214393a85b0703de4146c7a3697bb3'); const pk = loadPrivateKey();
let localSigner: NDKPrivateKeySigner;
if (pk) {
localSigner = new NDKPrivateKeySigner(pk);
} else {
localSigner = NDKPrivateKeySigner.generate();
savePrivateKey(localSigner.privateKey!);
}
const signer = new NDKNip46Signer(ndk, remoteUser.hexpubkey(), localSigner); const signer = new NDKNip46Signer(ndk, remoteUser.hexpubkey(), localSigner);
console.log(`local pubkey`, (await signer.user()).npub); if (debug) console.log(`local pubkey`, (await localSigner.user()).npub);
console.log(`remote pubkey`, remotePubkey); if (debug) console.log(`remote pubkey`, remotePubkey);
ndk.signer = signer; ndk.signer = signer;
setTimeout(async () => { setTimeout(async () => {
await signer.blockUntilReady(); try {
console.log(`authorized to sign as`, remotePubkey); if (debug) console.log(`waiting for authorization (check your nsecBunker)...`);
await signer.blockUntilReady();
} catch(e) {
console.log('error:', e);
process.exit(1);
}
if (debug) console.log(`authorized to sign as`, remotePubkey);
const event = new NDKEvent(ndk, { let event;
pubkey: remoteUser.hexpubkey(),
kind: 1,
content,
tags: [
['client', 'nsecbunker-client']
],
} as NostrEvent);
await event.sign(); try {
console.log('resulting event', JSON.stringify(await event.toNostrEvent())); const json = JSON.parse(content);
await event.publish(); event = new NDKEvent(ndk, json);
if (!event.tags) { event.tags = []; }
if (!event.content) { event.content = ""; }
if (!event.kind) { throw "No kind on the event to sign!"; }
} catch (e) {
event = new NDKEvent(ndk, {
kind: 1,
content,
tags: [
['client', 'nsecbunker-client']
],
} as NostrEvent);
}
event.pubkey = remoteUser.hexpubkey();
try {
await event.sign();
if (debug) {
console.log({
event: event.rawEvent(),
signature: event.sig,
});
} else {
console.log(event.sig);
}
if (!dontPublish) {
await event.publish();
}
process.exit(0);
} catch(e) {
console.log('sign error', e);
}
}, 2000); }, 2000);
})(); })();

View file

@ -18,7 +18,7 @@ interface IOpts {
export async function start(opts: IOpts) { export async function start(opts: IOpts) {
const configData = await getCurrentConfig(opts.config); const configData = await getCurrentConfig(opts.config);
if (opts.adminNpubs) { if (opts.adminNpubs && opts.adminNpubs.length > 0) {
configData.admin.npubs = opts.adminNpubs; configData.admin.npubs = opts.adminNpubs;
} }

View file

@ -93,7 +93,6 @@ async function setupSkeletonProfile(key: NDKPrivateKeySigner) {
['r', 'wss://relay.f7z.io'], ['r', 'wss://relay.f7z.io'],
['r', 'wss://relay.snort.social'], ['r', 'wss://relay.snort.social'],
['r', 'wss://relay.damus.io'], ['r', 'wss://relay.damus.io'],
['r', 'wss://relay.damus.io'],
], ],
pubkey: user.hexpubkey(), pubkey: user.hexpubkey(),
} as NostrEvent); } as NostrEvent);

View file

@ -0,0 +1,24 @@
import { NDKRpcRequest } from "@nostr-dev-kit/ndk";
import AdminInterface from "../index.js";
import prisma from "../../../db.js";
export default async function revokeUser(admin: AdminInterface, req: NDKRpcRequest) {
const [ keyUserId ] = req.params as [ string ];
if (!keyUserId) throw new Error("Invalid params");
const keyUserIdInt = parseInt(keyUserId);
if (isNaN(keyUserIdInt)) throw new Error("Invalid params");
await prisma.keyUser.update({
where: {
id: keyUserIdInt,
},
data: {
revokedAt: new Date(),
}
});
const result = JSON.stringify(["ok"]);
return admin.rpc.sendResponse(req.id, req.pubkey, result, 24134);
}

View file

@ -1,4 +1,4 @@
import NDK, { NDKPrivateKeySigner, NDKRpcRequest, NDKRpcResponse, NDKUser } from '@nostr-dev-kit/ndk'; import NDK, { NDKPrivateKeySigner, NDKRpcRequest, NDKRpcResponse, NDKUser, NostrEvent } from '@nostr-dev-kit/ndk';
import { NDKNostrRpc } from '@nostr-dev-kit/ndk'; import { NDKNostrRpc } from '@nostr-dev-kit/ndk';
import { debug } from 'debug'; import { debug } from 'debug';
import { Key, KeyUser } from '../run'; import { Key, KeyUser } from '../run';
@ -8,7 +8,10 @@ import createNewKey from './commands/create_new_key';
import createNewPolicy from './commands/create_new_policy'; import createNewPolicy from './commands/create_new_policy';
import createNewToken from './commands/create_new_token'; import createNewToken from './commands/create_new_token';
import unlockKey from './commands/unlock_key'; import unlockKey from './commands/unlock_key';
import revokeUser from './commands/revoke_user';
import fs from 'fs'; import fs from 'fs';
import { validateRequestFromAdmin } from './validations/request-from-admin';
import { dmUser } from '../../utils/dm-user';
export type IAdminOpts = { export type IAdminOpts = {
npubs: string[]; npubs: string[];
@ -43,7 +46,7 @@ class AdminInterface {
let connectionString = `bunker://${user.npub}`; let connectionString = `bunker://${user.npub}`;
if (opts.adminRelays.length > 0) { if (opts.adminRelays.length > 0) {
connectionString += `@${opts.adminRelays.join(',').replace(/wss:\/\//g, '')}`; connectionString += '@' + encodeURIComponent(`${opts.adminRelays.join(',').replace(/wss:\/\//g, '')}`);
} }
console.log(`\n\nnsecBunker connection string:\n\n${connectionString}\n\n`); console.log(`\n\nnsecBunker connection string:\n\n${connectionString}\n\n`);
@ -54,11 +57,25 @@ class AdminInterface {
this.signerUser = user; this.signerUser = user;
this.connect(); this.connect();
this.notifyAdminsOfNewConnection(connectionString);
}); });
this.rpc = new NDKNostrRpc(this.ndk, this.ndk.signer!, debug("ndk:rpc")); this.rpc = new NDKNostrRpc(this.ndk, this.ndk.signer!, debug("ndk:rpc"));
} }
private async notifyAdminsOfNewConnection(connectionString: string) {
const blastrNdk = new NDK({
explicitRelayUrls: ['wss://blastr.f7z.xyz', 'wss://nostr.mutinywallet.com'],
signer: this.ndk.signer
});
await blastrNdk.connect(2500);
for (const npub of this.npubs) {
dmUser(blastrNdk, npub, `nsecBunker has started; use ${connectionString} to connect to it and unlock your key(s)`);
}
}
/** /**
* Get the npub of the admin interface. * Get the npub of the admin interface.
*/ */
@ -75,10 +92,10 @@ class AdminInterface {
this.ndk.pool.on('relay:connect', () => console.log('✅ nsecBunker Admin Interface ready')); this.ndk.pool.on('relay:connect', () => console.log('✅ nsecBunker Admin Interface ready'));
this.ndk.pool.on('relay:disconnect', () => console.log('❌ admin disconnected')); this.ndk.pool.on('relay:disconnect', () => console.log('❌ admin disconnected'));
this.ndk.connect(2500).then(() => { this.ndk.connect(2500).then(() => {
// connect for whitelisted admins
this.rpc.subscribe({ this.rpc.subscribe({
"kinds": [24134 as number], // 24134 "kinds": [24134 as number], // 24134
"#p": [this.signerUser!.hexpubkey()], "#p": [this.signerUser!.hexpubkey()],
"authors": this.npubs.map((npub) => (new NDKUser({npub}).hexpubkey())),
}); });
this.rpc.on('request', (req) => this.handleRequest(req)); this.rpc.on('request', (req) => this.handleRequest(req));
@ -89,30 +106,38 @@ class AdminInterface {
} }
private async handleRequest(req: NDKRpcRequest) { private async handleRequest(req: NDKRpcRequest) {
// await this.validateRequest(req);
try { try {
await this.validateRequest(req);
switch (req.method) { switch (req.method) {
case 'get_keys': this.reqGetKeys(req); break; case 'get_keys': await this.reqGetKeys(req); break;
case 'get_key_users': this.reqGetKeyUsers(req); break; case 'get_key_users': await this.reqGetKeyUsers(req); break;
case 'get_key_tokens': this.reqGetKeyTokens(req); break; case 'get_key_tokens': await this.reqGetKeyTokens(req); break;
case 'create_new_key': createNewKey(this, req); break; case 'revoke_user': await revokeUser(this, req); break;
case 'unlock_key': unlockKey(this, req); break; case 'create_new_key': await createNewKey(this, req); break;
case 'create_new_policy': createNewPolicy(this, req); break; case 'unlock_key': await unlockKey(this, req); break;
case 'get_policies': this.reqListPolicies(req); break; case 'create_new_policy': await createNewPolicy(this, req); break;
case 'get_policies': await this.reqListPolicies(req); break;
case 'create_new_token': createNewToken(this, req); break; case 'create_new_token': await createNewToken(this, req); break;
default: default:
console.log(`Unknown method ${req.method}`); console.log(`Unknown method ${req.method}`);
return this.rpc.sendResponse(
req.id,
req.pubkey,
JSON.stringify(['error', `Unknown method ${req.method}`]),
24134
);
} }
} catch (err: any) { } catch (err: any) {
console.error(`Error handling request ${req.method}: ${err.message}`, req.params); console.error(`Error handling request ${req.method}: ${err.message}`, req.params);
return this.rpc.sendResponse(req.id, req.pubkey, JSON.stringify(['error', err?.message]), 24134);
} }
} }
private async validateRequest(req: NDKRpcRequest) { private async validateRequest(req: NDKRpcRequest): Promise<void> {
// TODO validate pubkey, validate signature if (!await validateRequestFromAdmin(req, this.npubs)) {
throw new Error('You are not designated to administrate this bunker');
}
} }
/** /**
@ -220,13 +245,15 @@ class AdminInterface {
/** /**
* This function is called when a request is received from a remote user that needs * This function is called when a request is received from a remote user that needs
* to be approved by the admin interface. * to be approved by the admin interface.
*
* @returns true if the request is approved, false if it is denied, undefined if it timedout
*/ */
public async requestPermission( public async requestPermission(
keyName: string, keyName: string,
remotePubkey: string, remotePubkey: string,
method: string, method: string,
param: any param: any
): Promise<boolean> { ): Promise<boolean | undefined> {
const keyUser = await prisma.keyUser.findUnique({ const keyUser = await prisma.keyUser.findUnique({
where: { where: {
unique_key_user: { unique_key_user: {
@ -254,53 +281,81 @@ class AdminInterface {
console.log(`param`, param); console.log(`param`, param);
console.log(`keyUser`, keyUser); console.log(`keyUser`, keyUser);
/**
* If an admin doesn't respond within 10 seconds, report back to the user that the request timed out
*/
setTimeout(() => {
resolve(undefined);
}, 10000);
for (const npub of this.npubs) { for (const npub of this.npubs) {
const remoteUser = new NDKUser({npub}); const remoteUser = new NDKUser({npub});
console.log(`sending request to ${npub}`, remoteUser.hexpubkey()); console.log(`sending request to ${npub}`, remoteUser.hexpubkey());
const params = JSON.stringify({
keyName,
remotePubkey,
method,
param,
description: keyUser?.description,
});
this.rpc.sendRequest( this.rpc.sendRequest(
remoteUser.hexpubkey(), remoteUser.hexpubkey(),
'acl', 'acl',
[JSON.stringify({ [params],
keyName, 24134,
remotePubkey,
method,
param,
description: keyUser?.description,
})],
24134, // 24134
(res: NDKRpcResponse) => { (res: NDKRpcResponse) => {
let resObj; this.requestPermissionResponse(
try { remotePubkey,
resObj = JSON.parse(res.result); keyName,
} catch (e) { method,
console.log('error parsing result', e); param,
return; resolve,
} res
);
console.log('request result', resObj);
switch (resObj[0]) {
case 'always': {
allowAllRequestsFromKey(
remotePubkey,
keyName,
method,
param,
resObj[1],
resObj[2]
).then(() => {
resolve(true);
});
break;
}
default:
console.log('request result', res.result);
}
} }
); );
} }
}); });
} }
public async requestPermissionResponse(
remotePubkey: string,
keyName: string,
method: string,
param: string,
resolve: (value: boolean) => void,
res: NDKRpcResponse
) {
let resObj;
try {
resObj = JSON.parse(res.result);
} catch (e) {
console.log('error parsing result', e);
return;
}
switch (resObj[0]) {
case 'always': {
allowAllRequestsFromKey(
remotePubkey,
keyName,
method,
param,
resObj[1],
resObj[2]
);
resolve(true);
break;
}
case 'never': {
console.log('not implemented');
break;
}
default:
console.log('request result', res.result);
}
}
} }
export default AdminInterface; export default AdminInterface;

View file

@ -0,0 +1,18 @@
import { NDKRpcRequest } from "@nostr-dev-kit/ndk";
import { nip19 } from "nostr-tools";
export async function validateRequestFromAdmin(
req: NDKRpcRequest,
npubs: string[],
): Promise<boolean> {
const hexpubkey = req.pubkey;
if (!hexpubkey) {
console.log('missing pubkey');
return false;
}
const hexpubkeys = npubs.map((npub) => nip19.decode(npub).data as string);
return hexpubkeys.includes(hexpubkey);
}

View file

@ -70,11 +70,13 @@ function getKeyUsers(config: IConfig) {
for (const user of users) { for (const user of users) {
const keyUser = { const keyUser = {
id: user.id,
name: user.keyName, name: user.keyName,
pubkey: user.userPubkey, pubkey: user.userPubkey,
description: user.description || undefined, description: user.description || undefined,
createdAt: user.createdAt, createdAt: user.createdAt,
lastUsedAt: user.lastUsedAt || undefined, lastUsedAt: user.lastUsedAt || undefined,
revokedAt: user.revokedAt || undefined,
signingConditions: user.signingConditions, // Include signing conditions signingConditions: user.signingConditions, // Include signing conditions
}; };
@ -152,7 +154,13 @@ function callbackForKeyAdminInterface(keyName: string, adminInterface: AdminInte
return keyAllowed; return keyAllowed;
} }
return adminInterface.requestPermission(keyName, remotePubkey, method, param); const requestedPerm = await adminInterface.requestPermission(keyName, remotePubkey, method, param);
if (requestedPerm === undefined) {
throw new Error('adminInterface.requestPermission returned undefined');
}
return requestedPerm;
} catch(e) { } catch(e) {
console.log('callbackForKey error:', e); console.log('callbackForKey error:', e);
} }
@ -205,8 +213,24 @@ class Daemon {
this.ndk = new NDK({ this.ndk = new NDK({
explicitRelayUrls: config.nostr.relays, explicitRelayUrls: config.nostr.relays,
}); });
this.ndk.pool.on('connect', (r) => { console.log(`✅ Connected to ${r.url}`); }); this.ndk.pool.on('relay:connect', (r) => {
if (r) {
console.log(`✅ Connected to ${r.url}`);
} else {
console.log('✅ Connected to relays', this.ndk.pool.urls);
}
});
this.ndk.pool.on('notice', (n, r) => { console.log(`👀 Notice from ${r.url}`, n); }); this.ndk.pool.on('notice', (n, r) => { console.log(`👀 Notice from ${r.url}`, n); });
this.ndk.pool.on('relay:disconnect', (r) => {
console.log(`🚫 Disconnected from ${r.url}`);
});
setInterval(() => {
const stats = this.ndk.pool.stats();
console.log(`📡 ${stats.connected} connected, ${stats.disconnected} disconnected, ${stats.connecting} connecting`);
}, 10000);
} }
async start() { async start() {

19
src/utils/dm-user.ts Normal file
View file

@ -0,0 +1,19 @@
import NDK, { NDKUser, NDKEvent, NostrEvent } from "@nostr-dev-kit/ndk";
export async function dmUser(ndk: NDK, recipient: NDKUser | string, content: string): Promise<NDKEvent> {
let targetUser;
if (typeof recipient === 'string') {
targetUser = new NDKUser({ npub: recipient });
} else if (recipient instanceof NDKUser) {
targetUser = recipient;
}
const event = new NDKEvent(ndk, { kind: 4, content } as NostrEvent);
event.tag(targetUser);
await event.encrypt(targetUser);
await event.sign();
await event.publish();
return event;
}