lots of things that I forgot to commit 😂
This commit is contained in:
parent
59556e673b
commit
6f16cafd41
13 changed files with 395 additions and 130 deletions
|
|
@ -86,7 +86,11 @@ $ npm run nsecbunkerd start
|
|||
|
||||
## 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
|
||||
|
||||
|
|
|
|||
122
package-lock.json
generated
122
package-lock.json
generated
|
|
@ -1,27 +1,27 @@
|
|||
{
|
||||
"name": "nsecbunkerd",
|
||||
"version": "0.6.1",
|
||||
"version": "0.6.5",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "nsecbunkerd",
|
||||
"version": "0.6.1",
|
||||
"version": "0.6.5",
|
||||
"license": "CC BY-NC-ND 4.0",
|
||||
"dependencies": {
|
||||
"@inquirer/password": "^1.1.2",
|
||||
"@inquirer/prompts": "^1.2.3",
|
||||
"@nostr-dev-kit/ndk": "^0.6.0",
|
||||
"@prisma/client": "^4.16.1",
|
||||
"@nostr-dev-kit/ndk": "^0.6.5",
|
||||
"@prisma/client": "^4.16.2",
|
||||
"@scure/base": "^1.1.1",
|
||||
"@types/yargs": "^17.0.24",
|
||||
"@typescript-eslint/eslint-plugin": "^5.60.0",
|
||||
"@typescript-eslint/parser": "^5.60.0",
|
||||
"dotenv": "^16.3.1",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-plugin-import": "^2.27.5",
|
||||
"eventemitter3": "^5.0.1",
|
||||
"isomorphic-ws": "^5.0.0",
|
||||
"websocket-polyfill": "^0.0.3",
|
||||
"ws": "^8.13.0",
|
||||
"yargs": "^17.7.2"
|
||||
},
|
||||
"bin": {
|
||||
|
|
@ -31,7 +31,7 @@
|
|||
"devDependencies": {
|
||||
"@types/debug": "^4.1.8",
|
||||
"@types/node": "^18.16.18",
|
||||
"prisma": "^4.16.1",
|
||||
"prisma": "^4.16.2",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.1.3"
|
||||
}
|
||||
|
|
@ -961,9 +961,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@nostr-dev-kit/ndk": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@nostr-dev-kit/ndk/-/ndk-0.6.0.tgz",
|
||||
"integrity": "sha512-0ptE6OIZhFW+aRRIXAI8PvUIoVU6iQLpiwFtJj48XAUO2EC3WiSuqKLshjg6wj1bbo9qGs1PyFS9AUhUlWWJtg==",
|
||||
"version": "0.6.5",
|
||||
"resolved": "https://registry.npmjs.org/@nostr-dev-kit/ndk/-/ndk-0.6.5.tgz",
|
||||
"integrity": "sha512-D95sDEonyFJhdTM1YswPOAYKaScnOlvZcxoyt9SlXnibT+gC9AGLtwAmpziK9zPC2h5Hh2GX44BnYXUGolLjZw==",
|
||||
"dependencies": {
|
||||
"@noble/hashes": "^1.3.1",
|
||||
"@noble/secp256k1": "^2.0.0",
|
||||
|
|
@ -987,12 +987,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@prisma/client": {
|
||||
"version": "4.16.1",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.16.1.tgz",
|
||||
"integrity": "sha512-CoDHu7Bt+NuDo40ijoeHP79EHtECsPBTy3yte5Yo3op8TqXt/kV0OT5OrsWewKvQGKFMHhYQ+ePed3zzjYdGAw==",
|
||||
"version": "4.16.2",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.16.2.tgz",
|
||||
"integrity": "sha512-qCoEyxv1ZrQ4bKy39GnylE8Zq31IRmm8bNhNbZx7bF2cU5aiCCnSa93J2imF88MBjn7J9eUQneNxUQVJdl/rPQ==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@prisma/engines-version": "4.16.0-66.b20ead4d3ab9e78ac112966e242ded703f4a052c"
|
||||
"@prisma/engines-version": "4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
|
|
@ -1007,16 +1007,16 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@prisma/engines": {
|
||||
"version": "4.16.1",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-4.16.1.tgz",
|
||||
"integrity": "sha512-gpZG0kGGxfemgvK/LghHdBIz+crHkZjzszja94xp4oytpsXrgt/Ice82MvPsWMleVIniKuARrowtsIsim0PFJQ==",
|
||||
"version": "4.16.2",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-4.16.2.tgz",
|
||||
"integrity": "sha512-vx1nxVvN4QeT/cepQce68deh/Turxy5Mr+4L4zClFuK1GlxN3+ivxfuv+ej/gvidWn1cE1uAhW7ALLNlYbRUAw==",
|
||||
"devOptional": true,
|
||||
"hasInstallScript": true
|
||||
},
|
||||
"node_modules/@prisma/engines-version": {
|
||||
"version": "4.16.0-66.b20ead4d3ab9e78ac112966e242ded703f4a052c",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.16.0-66.b20ead4d3ab9e78ac112966e242ded703f4a052c.tgz",
|
||||
"integrity": "sha512-tMWAF/qF00fbUH1HB4Yjmz6bjh7fzkb7Y3NRoUfMlHu6V+O45MGvqwYxqwBjn1BIUXkl3r04W351D4qdJjrgvA=="
|
||||
"version": "4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81.tgz",
|
||||
"integrity": "sha512-q617EUWfRIDTriWADZ4YiWRZXCa/WuhNgLTVd+HqWLffjMSPzyM5uOWoauX91wvQClSKZU4pzI4JJLQ9Kl62Qg=="
|
||||
},
|
||||
"node_modules/@scure/base": {
|
||||
"version": "1.1.1",
|
||||
|
|
@ -3159,6 +3159,14 @@
|
|||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"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": {
|
||||
"version": "29.5.0",
|
||||
"resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz",
|
||||
|
|
@ -4006,13 +4014,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/prisma": {
|
||||
"version": "4.16.1",
|
||||
"resolved": "https://registry.npmjs.org/prisma/-/prisma-4.16.1.tgz",
|
||||
"integrity": "sha512-C2Xm7yxHxjFjjscBEW4tmoraPHH/Vyu/A0XABdbaFtoiOZARsxvOM7rwc2iZ0qVxbh0bGBGBWZUSXO/52/nHBQ==",
|
||||
"version": "4.16.2",
|
||||
"resolved": "https://registry.npmjs.org/prisma/-/prisma-4.16.2.tgz",
|
||||
"integrity": "sha512-SYCsBvDf0/7XSJyf2cHTLjLeTLVXYfqp7pG5eEVafFLeT0u/hLFz/9W196nDRGUOo1JfPatAEb+uEnTQImQC1g==",
|
||||
"devOptional": true,
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@prisma/engines": "4.16.1"
|
||||
"@prisma/engines": "4.16.2"
|
||||
},
|
||||
"bin": {
|
||||
"prisma": "build/index.js",
|
||||
|
|
@ -4937,6 +4945,26 @@
|
|||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"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": {
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||
|
|
@ -5550,9 +5578,9 @@
|
|||
}
|
||||
},
|
||||
"@nostr-dev-kit/ndk": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@nostr-dev-kit/ndk/-/ndk-0.6.0.tgz",
|
||||
"integrity": "sha512-0ptE6OIZhFW+aRRIXAI8PvUIoVU6iQLpiwFtJj48XAUO2EC3WiSuqKLshjg6wj1bbo9qGs1PyFS9AUhUlWWJtg==",
|
||||
"version": "0.6.5",
|
||||
"resolved": "https://registry.npmjs.org/@nostr-dev-kit/ndk/-/ndk-0.6.5.tgz",
|
||||
"integrity": "sha512-D95sDEonyFJhdTM1YswPOAYKaScnOlvZcxoyt9SlXnibT+gC9AGLtwAmpziK9zPC2h5Hh2GX44BnYXUGolLjZw==",
|
||||
"requires": {
|
||||
"@noble/hashes": "^1.3.1",
|
||||
"@noble/secp256k1": "^2.0.0",
|
||||
|
|
@ -5576,23 +5604,23 @@
|
|||
}
|
||||
},
|
||||
"@prisma/client": {
|
||||
"version": "4.16.1",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.16.1.tgz",
|
||||
"integrity": "sha512-CoDHu7Bt+NuDo40ijoeHP79EHtECsPBTy3yte5Yo3op8TqXt/kV0OT5OrsWewKvQGKFMHhYQ+ePed3zzjYdGAw==",
|
||||
"version": "4.16.2",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.16.2.tgz",
|
||||
"integrity": "sha512-qCoEyxv1ZrQ4bKy39GnylE8Zq31IRmm8bNhNbZx7bF2cU5aiCCnSa93J2imF88MBjn7J9eUQneNxUQVJdl/rPQ==",
|
||||
"requires": {
|
||||
"@prisma/engines-version": "4.16.0-66.b20ead4d3ab9e78ac112966e242ded703f4a052c"
|
||||
"@prisma/engines-version": "4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81"
|
||||
}
|
||||
},
|
||||
"@prisma/engines": {
|
||||
"version": "4.16.1",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-4.16.1.tgz",
|
||||
"integrity": "sha512-gpZG0kGGxfemgvK/LghHdBIz+crHkZjzszja94xp4oytpsXrgt/Ice82MvPsWMleVIniKuARrowtsIsim0PFJQ==",
|
||||
"version": "4.16.2",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-4.16.2.tgz",
|
||||
"integrity": "sha512-vx1nxVvN4QeT/cepQce68deh/Turxy5Mr+4L4zClFuK1GlxN3+ivxfuv+ej/gvidWn1cE1uAhW7ALLNlYbRUAw==",
|
||||
"devOptional": true
|
||||
},
|
||||
"@prisma/engines-version": {
|
||||
"version": "4.16.0-66.b20ead4d3ab9e78ac112966e242ded703f4a052c",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.16.0-66.b20ead4d3ab9e78ac112966e242ded703f4a052c.tgz",
|
||||
"integrity": "sha512-tMWAF/qF00fbUH1HB4Yjmz6bjh7fzkb7Y3NRoUfMlHu6V+O45MGvqwYxqwBjn1BIUXkl3r04W351D4qdJjrgvA=="
|
||||
"version": "4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81.tgz",
|
||||
"integrity": "sha512-q617EUWfRIDTriWADZ4YiWRZXCa/WuhNgLTVd+HqWLffjMSPzyM5uOWoauX91wvQClSKZU4pzI4JJLQ9Kl62Qg=="
|
||||
},
|
||||
"@scure/base": {
|
||||
"version": "1.1.1",
|
||||
|
|
@ -7121,6 +7149,12 @@
|
|||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"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": {
|
||||
"version": "29.5.0",
|
||||
"resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz",
|
||||
|
|
@ -7722,12 +7756,12 @@
|
|||
}
|
||||
},
|
||||
"prisma": {
|
||||
"version": "4.16.1",
|
||||
"resolved": "https://registry.npmjs.org/prisma/-/prisma-4.16.1.tgz",
|
||||
"integrity": "sha512-C2Xm7yxHxjFjjscBEW4tmoraPHH/Vyu/A0XABdbaFtoiOZARsxvOM7rwc2iZ0qVxbh0bGBGBWZUSXO/52/nHBQ==",
|
||||
"version": "4.16.2",
|
||||
"resolved": "https://registry.npmjs.org/prisma/-/prisma-4.16.2.tgz",
|
||||
"integrity": "sha512-SYCsBvDf0/7XSJyf2cHTLjLeTLVXYfqp7pG5eEVafFLeT0u/hLFz/9W196nDRGUOo1JfPatAEb+uEnTQImQC1g==",
|
||||
"devOptional": true,
|
||||
"requires": {
|
||||
"@prisma/engines": "4.16.1"
|
||||
"@prisma/engines": "4.16.2"
|
||||
}
|
||||
},
|
||||
"punycode": {
|
||||
|
|
@ -8369,6 +8403,12 @@
|
|||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"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": {
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||
|
|
|
|||
10
package.json
10
package.json
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "nsecbunkerd",
|
||||
"version": "0.6.4",
|
||||
"version": "0.7.1",
|
||||
"description": "nsecbunker daemon",
|
||||
"main": "dist/index.js",
|
||||
"bin": {
|
||||
|
|
@ -35,21 +35,23 @@
|
|||
"dependencies": {
|
||||
"@inquirer/password": "^1.1.2",
|
||||
"@inquirer/prompts": "^1.2.3",
|
||||
"@nostr-dev-kit/ndk": "^0.6.5",
|
||||
"@prisma/client": "^4.16.1",
|
||||
"@nostr-dev-kit/ndk": "^0.7.4",
|
||||
"@prisma/client": "^4.16.2",
|
||||
"@scure/base": "^1.1.1",
|
||||
"@types/yargs": "^17.0.24",
|
||||
"dotenv": "^16.3.1",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-plugin-import": "^2.27.5",
|
||||
"eventemitter3": "^5.0.1",
|
||||
"isomorphic-ws": "^5.0.0",
|
||||
"websocket-polyfill": "^0.0.3",
|
||||
"ws": "^8.13.0",
|
||||
"yargs": "^17.7.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/debug": "^4.1.8",
|
||||
"@types/node": "^18.16.18",
|
||||
"prisma": "^4.16.1",
|
||||
"prisma": "^4.16.2",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.1.3"
|
||||
}
|
||||
|
|
|
|||
2
prisma/migrations/20230703092203_revokedat/migration.sql
Normal file
2
prisma/migrations/20230703092203_revokedat/migration.sql
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
-- AlterTable
|
||||
ALTER TABLE "KeyUser" ADD COLUMN "revokedAt" DATETIME;
|
||||
|
|
@ -13,6 +13,7 @@ model KeyUser {
|
|||
userPubkey String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
revokedAt DateTime?
|
||||
lastUsedAt DateTime?
|
||||
description String?
|
||||
logs Log[]
|
||||
|
|
|
|||
127
src/client.ts
127
src/client.ts
|
|
@ -1,52 +1,129 @@
|
|||
import NDK, { NDKUser, NDKEvent, NDKPrivateKeySigner, NDKNip46Signer, NostrEvent } from '@nostr-dev-kit/ndk';
|
||||
import fs from 'fs';
|
||||
|
||||
const remotePubkey = process.argv[2];
|
||||
const content = process.argv[3];
|
||||
const command = process.argv[2];
|
||||
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) {
|
||||
console.log('Usage: node src/client.js <remote-npub> <content>');
|
||||
if (!command) {
|
||||
console.log('Usage: node src/client.js <command> <remote-npub> <content> [--dont-publish] [--debug] [--pk <key>]');
|
||||
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<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);
|
||||
}
|
||||
|
||||
async function createNDK(): Promise<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'));
|
||||
ndk.pool.on('relay:disconnect', () => console.log('❌ disconnected'));
|
||||
if (debug) {
|
||||
ndk.pool.on('connect', () => console.log('✅ connected'));
|
||||
ndk.pool.on('disconnect', () => console.log('❌ disconnected'));
|
||||
}
|
||||
await ndk.connect(5000);
|
||||
|
||||
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 () => {
|
||||
const remoteUser = new NDKUser({npub: remotePubkey});
|
||||
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);
|
||||
console.log(`local pubkey`, (await signer.user()).npub);
|
||||
console.log(`remote pubkey`, remotePubkey);
|
||||
if (debug) console.log(`local pubkey`, (await localSigner.user()).npub);
|
||||
if (debug) console.log(`remote pubkey`, remotePubkey);
|
||||
ndk.signer = signer;
|
||||
|
||||
setTimeout(async () => {
|
||||
await signer.blockUntilReady();
|
||||
console.log(`authorized to sign as`, remotePubkey);
|
||||
try {
|
||||
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, {
|
||||
pubkey: remoteUser.hexpubkey(),
|
||||
kind: 1,
|
||||
content,
|
||||
tags: [
|
||||
['client', 'nsecbunker-client']
|
||||
],
|
||||
} as NostrEvent);
|
||||
let event;
|
||||
|
||||
await event.sign();
|
||||
console.log('resulting event', JSON.stringify(await event.toNostrEvent()));
|
||||
await event.publish();
|
||||
try {
|
||||
const json = JSON.parse(content);
|
||||
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);
|
||||
})();
|
||||
|
|
@ -18,7 +18,7 @@ interface IOpts {
|
|||
export async function start(opts: IOpts) {
|
||||
const configData = await getCurrentConfig(opts.config);
|
||||
|
||||
if (opts.adminNpubs) {
|
||||
if (opts.adminNpubs && opts.adminNpubs.length > 0) {
|
||||
configData.admin.npubs = opts.adminNpubs;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -93,7 +93,6 @@ async function setupSkeletonProfile(key: NDKPrivateKeySigner) {
|
|||
['r', 'wss://relay.f7z.io'],
|
||||
['r', 'wss://relay.snort.social'],
|
||||
['r', 'wss://relay.damus.io'],
|
||||
['r', 'wss://relay.damus.io'],
|
||||
],
|
||||
pubkey: user.hexpubkey(),
|
||||
} as NostrEvent);
|
||||
|
|
|
|||
24
src/daemon/admin/commands/revoke_user.ts
Normal file
24
src/daemon/admin/commands/revoke_user.ts
Normal 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);
|
||||
}
|
||||
|
|
@ -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 { debug } from 'debug';
|
||||
import { Key, KeyUser } from '../run';
|
||||
|
|
@ -8,7 +8,10 @@ import createNewKey from './commands/create_new_key';
|
|||
import createNewPolicy from './commands/create_new_policy';
|
||||
import createNewToken from './commands/create_new_token';
|
||||
import unlockKey from './commands/unlock_key';
|
||||
import revokeUser from './commands/revoke_user';
|
||||
import fs from 'fs';
|
||||
import { validateRequestFromAdmin } from './validations/request-from-admin';
|
||||
import { dmUser } from '../../utils/dm-user';
|
||||
|
||||
export type IAdminOpts = {
|
||||
npubs: string[];
|
||||
|
|
@ -43,7 +46,7 @@ class AdminInterface {
|
|||
let connectionString = `bunker://${user.npub}`;
|
||||
|
||||
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`);
|
||||
|
|
@ -54,11 +57,25 @@ class AdminInterface {
|
|||
this.signerUser = user;
|
||||
|
||||
this.connect();
|
||||
|
||||
this.notifyAdminsOfNewConnection(connectionString);
|
||||
});
|
||||
|
||||
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.
|
||||
*/
|
||||
|
|
@ -75,10 +92,10 @@ class AdminInterface {
|
|||
this.ndk.pool.on('relay:connect', () => console.log('✅ nsecBunker Admin Interface ready'));
|
||||
this.ndk.pool.on('relay:disconnect', () => console.log('❌ admin disconnected'));
|
||||
this.ndk.connect(2500).then(() => {
|
||||
// connect for whitelisted admins
|
||||
this.rpc.subscribe({
|
||||
"kinds": [24134 as number], // 24134
|
||||
"#p": [this.signerUser!.hexpubkey()],
|
||||
"authors": this.npubs.map((npub) => (new NDKUser({npub}).hexpubkey())),
|
||||
});
|
||||
|
||||
this.rpc.on('request', (req) => this.handleRequest(req));
|
||||
|
|
@ -89,30 +106,38 @@ class AdminInterface {
|
|||
}
|
||||
|
||||
private async handleRequest(req: NDKRpcRequest) {
|
||||
// await this.validateRequest(req);
|
||||
|
||||
try {
|
||||
await this.validateRequest(req);
|
||||
|
||||
switch (req.method) {
|
||||
case 'get_keys': this.reqGetKeys(req); break;
|
||||
case 'get_key_users': this.reqGetKeyUsers(req); break;
|
||||
case 'get_key_tokens': this.reqGetKeyTokens(req); break;
|
||||
case 'create_new_key': createNewKey(this, req); break;
|
||||
case 'unlock_key': unlockKey(this, req); break;
|
||||
case 'create_new_policy': createNewPolicy(this, req); break;
|
||||
case 'get_policies': this.reqListPolicies(req); break;
|
||||
|
||||
case 'create_new_token': createNewToken(this, req); break;
|
||||
|
||||
case 'get_keys': await this.reqGetKeys(req); break;
|
||||
case 'get_key_users': await this.reqGetKeyUsers(req); break;
|
||||
case 'get_key_tokens': await this.reqGetKeyTokens(req); break;
|
||||
case 'revoke_user': await revokeUser(this, req); break;
|
||||
case 'create_new_key': await createNewKey(this, req); break;
|
||||
case 'unlock_key': await unlockKey(this, req); break;
|
||||
case 'create_new_policy': await createNewPolicy(this, req); break;
|
||||
case 'get_policies': await this.reqListPolicies(req); break;
|
||||
case 'create_new_token': await createNewToken(this, req); break;
|
||||
default:
|
||||
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) {
|
||||
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) {
|
||||
// TODO validate pubkey, validate signature
|
||||
private async validateRequest(req: NDKRpcRequest): Promise<void> {
|
||||
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
|
||||
* 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(
|
||||
keyName: string,
|
||||
remotePubkey: string,
|
||||
method: string,
|
||||
param: any
|
||||
): Promise<boolean> {
|
||||
): Promise<boolean | undefined> {
|
||||
const keyUser = await prisma.keyUser.findUnique({
|
||||
where: {
|
||||
unique_key_user: {
|
||||
|
|
@ -254,53 +281,81 @@ class AdminInterface {
|
|||
console.log(`param`, param);
|
||||
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) {
|
||||
const remoteUser = new NDKUser({npub});
|
||||
console.log(`sending request to ${npub}`, remoteUser.hexpubkey());
|
||||
const params = JSON.stringify({
|
||||
keyName,
|
||||
remotePubkey,
|
||||
method,
|
||||
param,
|
||||
description: keyUser?.description,
|
||||
});
|
||||
|
||||
this.rpc.sendRequest(
|
||||
remoteUser.hexpubkey(),
|
||||
'acl',
|
||||
[JSON.stringify({
|
||||
keyName,
|
||||
remotePubkey,
|
||||
method,
|
||||
param,
|
||||
description: keyUser?.description,
|
||||
})],
|
||||
24134, // 24134
|
||||
[params],
|
||||
24134,
|
||||
(res: NDKRpcResponse) => {
|
||||
let resObj;
|
||||
try {
|
||||
resObj = JSON.parse(res.result);
|
||||
} catch (e) {
|
||||
console.log('error parsing result', e);
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
this.requestPermissionResponse(
|
||||
remotePubkey,
|
||||
keyName,
|
||||
method,
|
||||
param,
|
||||
resolve,
|
||||
res
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
18
src/daemon/admin/validations/request-from-admin.ts
Normal file
18
src/daemon/admin/validations/request-from-admin.ts
Normal 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);
|
||||
}
|
||||
|
|
@ -70,11 +70,13 @@ function getKeyUsers(config: IConfig) {
|
|||
|
||||
for (const user of users) {
|
||||
const keyUser = {
|
||||
id: user.id,
|
||||
name: user.keyName,
|
||||
pubkey: user.userPubkey,
|
||||
description: user.description || undefined,
|
||||
createdAt: user.createdAt,
|
||||
lastUsedAt: user.lastUsedAt || undefined,
|
||||
revokedAt: user.revokedAt || undefined,
|
||||
signingConditions: user.signingConditions, // Include signing conditions
|
||||
};
|
||||
|
||||
|
|
@ -152,7 +154,13 @@ function callbackForKeyAdminInterface(keyName: string, adminInterface: AdminInte
|
|||
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) {
|
||||
console.log('callbackForKey error:', e);
|
||||
}
|
||||
|
|
@ -205,8 +213,24 @@ class Daemon {
|
|||
this.ndk = new NDK({
|
||||
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('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() {
|
||||
|
|
|
|||
19
src/utils/dm-user.ts
Normal file
19
src/utils/dm-user.ts
Normal 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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue