This commit is contained in:
pablof7z 2023-05-31 20:26:29 +02:00
commit 74cd9715ac
12 changed files with 3353 additions and 438 deletions

View file

@ -1,15 +1,78 @@
# nsecbunker
# nsecbunkerd
Daemon to remotely sign nostr events using keys.
## Installation
## Easy setup via docker
To quickly install `nsecbunkerd` via Docker just run:
```
npm i -g nsecbunker
docker run -d --name nsecbunkerd pablof7z/nsecbunkerd start --admin <your-npub>
```
## Usage
nsecBunker will give you a connection string like:
```
bunker://npub1tj2dmc4udvgafxxxxxxxrtgne8j8l6rgrnaykzc8sys9mzfcz@relay.nsecbunker.com
```
You can visit https://app.nsecbunker.com/ to administrate your nsecBunker remotely.
## Hard setup:
(If you installed via docker you don't need to do any of this, skip to the [Configure](#configure) section)
```
git clone <nsecbunkerd-repo>
npm i
npm run build
npx prisma migrate deploy
```
## Configure
### Easy: Remote configuration
Using the connection string you saw before, you can go to https://app.nsecbunker.com and paste your connection string.
Note that ONLY the npub that you designated as an administrator when launching nsecBunker is able to control your nsecBunker. Even if someone sees your connection string, without access to your administrator keys, there's nothing they can do.
### Hard: manual configuration
(If you are using remote configuration you don't need to do any of this)
### Add your nsec to nsecBunker
Here you'll give nsecBunker your nsec. It will ask you for a passphrase to encrypt it on-disk.
The name is an internal name you'll use to refer to this keypair. Choose anything that is useful to you.
```
npm run nsecbunkerd -- add --name <your-key-name>
```
#### Example
```
$ npm run nsecbunkerd -- add --name "Uncomfortable family"
nsecBunker uses a passphrase to encrypt your nsec when stored on-disk.
Every time you restart it, you will need to type in this password.
Enter a passphrase: <enter-your-passphrase-here>
Enter the nsec for Uncomfortable family: <copy-your-nsec-here>
nsecBunker generated an admin password for you:
***************************
You will need this to manage users of your keys.
````
## Start
```
$ npm run nsecbunkerd start
```
## Testing with `nsecbunker-client`
nsecbunker -
# Authors
@ -18,5 +81,5 @@ nsecbunker -
# License
CC BY-NC-ND 3.0
CC BY-NC-ND 4.0
Contact @pablof7z for licensing.

2977
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,14 +1,18 @@
{
"name": "nsecbunker",
"version": "0.1.0",
"description": "nsecbunker",
"name": "nsecbunkerd",
"version": "0.5.8",
"description": "nsecbunker daemon",
"main": "dist/index.js",
"bin": {
"nsecbunkerd": "dist/index.js",
"nsecbunker-client": "dist/client.js"
},
"files": [
"dist"
"dist",
"scripts/start.js",
"prisma/schema.prisma",
"LICENSE",
"README.md"
],
"repository": {
"type": "git",
@ -16,7 +20,10 @@
},
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"prisma:generate": "npx prisma generate",
"prisma:migrate": "npx prisma migrate deploy",
"prisma:create": "npx prisma db push --preview-feature",
"start": "node ./scripts/start.js",
"nsecbunkerd": "node dist/index.js",
"nsecbunker-client": "node dist/client.js"
},
@ -24,16 +31,16 @@
"nostr"
],
"author": "pablof7z",
"license": "MIT",
"license": "CC BY-NC-ND 4.0",
"dependencies": {
"@inquirer/password": "^1.0.0",
"@inquirer/prompts": "^1.0.0",
"@prisma/client": "4.13.0",
"@nostr-dev-kit/ndk": "^0.3.26",
"@prisma/client": "^4.14.1",
"@scure/base": "^1.1.1",
"@types/yargs": "^17.0.24",
"@typescript-eslint/eslint-plugin": "^5.57.0",
"@typescript-eslint/parser": "^5.57.0",
"crypto": "^1.0.1",
"dotenv": "^16.0.3",
"eslint": "^8.37.0",
"eslint-config-prettier": "^8.8.0",
@ -45,7 +52,7 @@
"devDependencies": {
"@types/debug": "^4.1.7",
"@types/node": "^18.15.11",
"prisma": "^4.13.0",
"prisma": "^4.14.1",
"ts-node": "^10.9.1",
"typescript": "^5.0.3"
}

View file

@ -7,15 +7,19 @@ generator client {
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
url = "file:./nsecbunker.db"
}
model KeyUser {
id Int @id @default(autoincrement())
keyName String
userPubkey String
description String?
signingConditions SigningCondition[]
logs Log[]
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
lastUsedAt DateTime?
@@unique([keyName, userPubkey], name: "unique_key_user")
}
@ -24,7 +28,7 @@ model SigningCondition {
id Int @id @default(autoincrement())
method String?
kind Int?
kind String?
content String?
keyUserKeyName String?
allowed Boolean?

View file

@ -1,37 +1,33 @@
import NDK, { NDKEvent, NDKPrivateKeySigner, NDKNip46Signer, NostrEvent } from '@nostr-dev-kit/ndk';
import NDK, { NDKUser, NDKEvent, NDKPrivateKeySigner, NDKNip46Signer, NostrEvent } from '@nostr-dev-kit/ndk';
const remotePubkey = process.env.PUBKEY;
if (!remotePubkey) {
console.log('Usage: PUBKEY=<pubkey> node src/client.js <content>');
process.exit(1);
}
const pubkey = process.argv[2];
const remotePubkey = process.argv[2];
const content = process.argv[3];
if (!content) {
console.log('Usage: node src/client.js <remote-pubkey> <content>');
console.log('Usage: node src/client.js <remote-npub> <content>');
console.log('');
console.log(`\t<remote-pubkey>: 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`);
process.exit(1);
}
async function createNDK(): Promise<NDK> {
const ndk = new NDK({
explicitRelayUrls: ['wss://nos.lol'],
explicitRelayUrls: ['wss://nostr.vulpem.com', "wss://67aee52897df.ngrok.app"],
});
await ndk.connect(2000);
ndk.pool.on('connect', () => console.log('✅ connected'));
ndk.pool.on('relay:connect', () => console.log('✅ connected'));
ndk.pool.on('relay:disconnect', () => console.log('❌ disconnected'));
await ndk.connect(5000);
return ndk;
}
(async () => {
const remoteUser = new NDKUser({npub: remotePubkey});
const ndk = await createNDK();
const localSigner = new NDKPrivateKeySigner('9ec8a4b2e1fac9eae616736f718f92ed30c57fc2fff36ef8139e27c31889e327');
const signer = new NDKNip46Signer(ndk, remotePubkey, localSigner);
const localSigner = NDKPrivateKeySigner.generate();
// const localSigner = new NDKPrivateKeySigner('b8baad35c387d7cf84d52e0958d9a02aff214393a85b0703de4146c7a3697bb3');
const signer = new NDKNip46Signer(ndk, remoteUser.hexpubkey(), localSigner);
console.log(`local pubkey`, (await signer.user()).npub);
console.log(`remote pubkey`, remotePubkey);
ndk.signer = signer;
@ -40,17 +36,17 @@ async function createNDK(): Promise<NDK> {
await signer.blockUntilReady();
console.log(`authorized to sign as`, remotePubkey);
const notPabloEvent = new NDKEvent(ndk, {
pubkey: remotePubkey,
const event = new NDKEvent(ndk, {
pubkey: remoteUser.hexpubkey(),
kind: 1,
content,
tags: [
['t', 'grownostr'],
['client', 'nsecbunker-client']
],
} as NostrEvent);
await notPabloEvent.sign();
console.log('resulting event', JSON.stringify(await notPabloEvent.toNostrEvent()));
// await notPabloEvent.publish();
await event.sign();
console.log('resulting event', JSON.stringify(await event.toNostrEvent()));
await event.publish();
}, 2000);
})();

View file

@ -1,4 +1,4 @@
import {nip19, getPublicKey} from 'nostr-tools';
import {nip19} from 'nostr-tools';
import readline from 'readline';
import { getCurrentConfig, saveCurrentConfig } from '../config/index.js';
import { encryptNsec } from '../config/keys.js';
@ -8,9 +8,9 @@ interface IOpts {
name: string;
}
function saveEncrypted(config: string, nsec: string, passphrase: string, name: string) {
export async function saveEncrypted(config: string, nsec: string, passphrase: string, name: string) {
const { iv, data } = encryptNsec(nsec, passphrase);
const currentConfig = getCurrentConfig(config);
const currentConfig = await getCurrentConfig(config);
currentConfig.keys[name] = { iv, data };
@ -35,8 +35,8 @@ export async function addNsec(opts: IOpts) {
let decoded;
try {
decoded = nip19.decode(nsec);
const hexpubkey = getPublicKey(decoded.data as string);
const npub = nip19.npubEncode(hexpubkey);
// const hexpubkey = getPublicKey(decoded.data as string);
// const npub = nip19.npubEncode(hexpubkey);
saveEncrypted(config, nsec, passphrase, name);
rl.close();

View file

@ -1,7 +1,21 @@
import readline from 'readline';
import fs from 'fs';
import crypto from 'crypto';
import { getCurrentConfig, saveCurrentConfig } from '../config/index.js';
export function setup(config: string) {
export async function setup(config: string) {
const currentConfig = await getCurrentConfig(config);
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
console.log(`You need at least one administrator to remotely control nsecBunker. This should probably be your own npub.\n`);
rl.question(`Enter an administrator npub: `, (npub: string) => {
currentConfig.admin.npubs.push(npub);
saveCurrentConfig(config, currentConfig);
rl.close();
console.log(`Administrator npub added!`);
});
}

View file

@ -1,5 +1,5 @@
import readline from 'readline';
import { getCurrentConfig } from '../config/index.js';
import { getCurrentConfig, saveCurrentConfig } from '../config/index.js';
import { decryptNsec } from '../config/keys.js';
import { fork } from 'child_process';
import { resolve } from 'path';
@ -8,10 +8,21 @@ interface IOpts {
keys: string[];
verbose: boolean;
config: string;
adminNpubs: string[];
}
/**
* This command starts the nsecbunkerd process with an (optional)
* admin interface over websockets or redis.
*/
export async function start(opts: IOpts) {
const configData = getCurrentConfig(opts.config);
const configData = await getCurrentConfig(opts.config);
if (opts.adminNpubs) {
configData.admin.npubs = opts.adminNpubs;
}
await saveCurrentConfig(opts.config, configData);
if (opts.verbose) {
configData.verbose = opts.verbose;
@ -19,11 +30,7 @@ export async function start(opts: IOpts) {
const keys: Record<string, string> = {};
let keysToStart = opts.keys;
if (!keysToStart) {
keysToStart = Object.keys(configData.keys);
}
const keysToStart = opts.keys || [];
for (const keyName of keysToStart) {
const nsec = await startKey(keyName, configData.keys[keyName], opts.verbose);
@ -33,19 +40,13 @@ export async function start(opts: IOpts) {
}
}
if (Object.keys(keys).length === 0) {
console.log(`No keys started.`);
process.exit(1);
}
console.log(`nsecBunker starting with keys:`, Object.keys(keys).join(', '));
configData.keys = keys;
const daemonProcess = fork(resolve(__dirname, '../daemon/index.js'));
daemonProcess.send(configData);
// process.exit(0);
daemonProcess.send({
configFile: opts.config,
allKeys: configData.keys,
...configData,
keys,
});
}
interface KeyData {

View file

@ -1,21 +1,34 @@
import { randomBytes } from 'crypto';
import { readFileSync, writeFileSync } from 'fs';
import { NDKPrivateKeySigner } from '@nostr-dev-kit/ndk';
import { IAdminOpts } from '../daemon/admin';
function getPassphrase(): string {
const passwordLength = 32;
const passwordBytes = randomBytes(passwordLength);
return passwordBytes.toString('base64').slice(0, passwordLength);
const generatedKey = NDKPrivateKeySigner.generate();
export interface IConfig {
nostr: {
relays: string[];
};
admin: IAdminOpts;
database: string;
logs: string;
keys: Record<string, any>;
verbose: boolean;
}
const defaultConfig = {
const defaultConfig: IConfig = {
nostr: {
relays: [
'wss://nos.lol',
// 'wss://relay.damus.io'
]
},
remote: {
passphrase: getPassphrase(),
admin: {
npubs: [],
adminRelays: [
"wss://nostr.vulpem.com",
"wss://relay.nsecbunker.com"
],
key: generatedKey.privateKey!
},
database: 'sqlite://nsecbunker.db',
logs: './nsecbunker.log',
@ -23,17 +36,13 @@ const defaultConfig = {
verbose: false,
};
export function getCurrentConfig(config: string) {
async function getCurrentConfig(config: string): Promise<IConfig> {
try {
const configFileContents = readFileSync(config, 'utf8');
return JSON.parse(configFileContents);
} catch (err: any) {
if (err.code === 'ENOENT') {
const d = defaultConfig;
console.log(`nsecBunker generated an admin password for you:\n\n${d.remote.passphrase}\n\n` +
`You will need this to manage users of your keys.\n\n`);
await saveCurrentConfig(config, defaultConfig);
return defaultConfig;
} else {
console.error(`Error reading config file: ${err.message}`);
@ -51,3 +60,5 @@ export function saveCurrentConfig(config: string, currentConfig: any) {
process.exit(1); // Kill the process if there is an error parsing the JSON
}
}
export {getCurrentConfig};

View file

@ -1,6 +1,11 @@
import run from './run';
import type {IOpts} from './run';
import type {IConfig} from '../config/index';
process.on('message', (configData: IOpts) => {
run(configData);
export type DaemonConfig = IConfig & {
configFile: string;
allKeys: Record<string, any>;
};
process.on('message', (config: DaemonConfig) => {
run(config);
});

View file

@ -1,222 +1,104 @@
import NDK, { Nip46PermitCallback } from '@nostr-dev-kit/ndk';
import NDK, { NDKPrivateKeySigner, Nip46PermitCallback } from '@nostr-dev-kit/ndk';
import { nip19 } from 'nostr-tools';
import { Backend } from './backend/index.js';
import readline from 'readline';
import {
checkIfPubkeyAllowed,
allowAllRequestsFromKey,
rejectAllRequestsFromKey
} from './lib/acl/index.js';
import AdminInterface from './admin/index.js';
import { askYNquestion } from '../utils/prompts/boolean.js';
import { IConfig } from '../config/index.js';
import { NDKRpcRequest } from '@nostr-dev-kit/ndk';
import prisma from '../db.js';
import { DaemonConfig } from './index.js';
import { decryptNsec } from '../config/keys.js';
export interface IOpts {
keys: Record<string, string>;
nostr: {
relays: string[],
}
verbose: boolean;
}
export type Key = {
name: string;
npub?: string;
};
export type KeyUser = {
name: string;
pubkey: string;
description?: string;
createdAt: Date;
lastUsedAt?: Date;
};
export default async function run(opts: IOpts) {
console.log(`nsecBunker daemon starting with PID ${process.pid}...`);
console.log(`Connecting to ${opts.nostr.relays.length} relays...`);
function getKeys(config: DaemonConfig) {
return async (): Promise<Key[]> => {
let lockedKeyNames = Object.keys(config.allKeys);
const keys: Key[] = [];
const ndk = new NDK({
explicitRelayUrls: opts.nostr.relays,
});
await ndk.pool.on('connect', (r) => { console.log(`✅ Connected to ${r.url}`); });
await ndk.pool.on('notice', (n, r) => { console.log(`👀 Notice from ${r.url}`, n); });
await ndk.connect(5000);
setTimeout(async () => {
const promise = [];
for (const [name, nsec] of Object.entries(opts.keys)) {
const cb = callbackForKey(name);
for (const [name, nsec] of Object.entries(config.keys)) {
const hexpk = nip19.decode(nsec).data as string;
const backend = new Backend(ndk, hexpk, cb);
promise.push(backend.start());
const user = await new NDKPrivateKeySigner(hexpk).user();
const key = {
name,
npub: user.npub,
};
lockedKeyNames = lockedKeyNames.filter((keyName) => keyName !== name);
keys.push(key);
}
await Promise.all(promise);
console.log({ lockedKeyNames });
console.log('✅ nsecBunker ready to serve requests.');
}, 1000);
for (const name of lockedKeyNames) {
keys.push({ name });
}
async function checkIfPubkeyAllowed(keyName: string, remotePubkey: string, method: string, param?: any): Promise<boolean | undefined> {
// find KeyUser
const keyUser = await prisma.keyUser.findUnique({
where: { unique_key_user: { keyName, userPubkey: remotePubkey } },
});
if (!keyUser) {
return undefined;
return keys;
};
}
// find SigningCondition
const signingConditionQuery = requestToSigningConditionQuery(method, param);
function getKeyUsers(config: IConfig) {
return async (req: NDKRpcRequest): Promise<KeyUser[]> => {
const keyUsers: KeyUser[] = [];
const keyName = req.params[0];
const explicitReject = await prisma.signingCondition.findFirst({
const users = await prisma.keyUser.findMany({
where: {
keyUserId: keyUser.id,
method: '*',
allowed: false,
}
});
if (explicitReject) {
console.log(`explicit reject`, explicitReject);
return false;
}
const signingCondition = await prisma.signingCondition.findFirst({
where: {
keyUserId: keyUser.id,
...signingConditionQuery,
}
});
// if no SigningCondition found, return undefined
if (!signingCondition) {
return undefined;
}
const allowed = signingCondition.allowed;
if (allowed === true || allowed === false) {
console.log(`found signing condition`, signingCondition);
return allowed;
}
return undefined;
}
function requestToSigningConditionQuery(method: string, param?: any) {
const signingConditionQuery: any = { method };
switch (method) {
case 'sign_event':
signingConditionQuery.kind = param.kind;
break;
}
return signingConditionQuery;
}
async function allowAllRequestsFromKey(remotePubkey: string, keyName: string, method: string, param?: any): Promise<void> {
try {
// Upsert the KeyUser with the given remotePubkey
const upsertedUser = await prisma.keyUser.upsert({
where: { unique_key_user: { keyName, userPubkey: remotePubkey } },
update: { },
create: { keyName, userPubkey: remotePubkey },
});
console.log({ upsertedUser });
// Create a new SigningCondition for the given KeyUser and set allowed to true
const signingConditionQuery = requestToSigningConditionQuery(method, param);
await prisma.signingCondition.create({
data: {
allowed: true,
keyUserId: upsertedUser.id,
...signingConditionQuery
keyName,
},
include: {
signingConditions: true,
},
});
} catch (e) {
console.log('allowAllRequestsFromKey', e);
}
for (const user of users) {
const keyUser = {
name: user.keyName,
pubkey: user.userPubkey,
description: user.description || undefined,
createdAt: user.createdAt,
lastUsedAt: user.lastUsedAt || undefined,
signingConditions: user.signingConditions, // Include signing conditions
};
keyUsers.push(keyUser);
}
async function rejectAllRequestsFromKey(remotePubkey: string, keyName: string): Promise<void> {
// Upsert the KeyUser with the given remotePubkey
const upsertedUser = await prisma.keyUser.upsert({
where: { unique_key_user: { keyName, userPubkey: remotePubkey } },
update: { },
create: { keyName, userPubkey: remotePubkey },
});
console.log({ upsertedUser });
// Create a new SigningCondition for the given KeyUser and set allowed to false
await prisma.signingCondition.create({
data: {
allowed: false,
keyUserId: upsertedUser.id,
},
});
return keyUsers;
};
}
interface IAskYNquestionOpts {
timeoutLength?: number;
yes: any;
no: any;
always?: any;
never?: any;
response?: any;
timeout?: any;
}
async function askYNquestion(
question: string,
opts: IAskYNquestionOpts
) {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
let timeout: NodeJS.Timeout | undefined;
if (opts.timeoutLength) {
timeout = setTimeout(() => {
rl.close();
opts.timeout && opts.timeout();
}, opts.timeoutLength);
}
const prompts = ['y', 'n'];
if (opts.always) prompts.push('always');
if (opts.never) prompts.push('never');
question += ` (${prompts.join('/')})`;
rl.question(question, (answer) => {
timeout && clearTimeout(timeout);
switch (answer) {
case 'y':
case 'Y':
opts.yes();
opts.response && opts.response(answer);
break;
case 'n':
case 'N':
opts.no();
opts.response && opts.response(answer);
break;
case 'always':
opts.yes();
opts.always();
opts.response && opts.response(answer);
break;
case 'never':
opts.no();
opts.never();
opts.response && opts.response(answer);
break;
default:
console.log('Invalid answer');
askYNquestion(question, opts);
break;
}
rl.close();
});
return rl;
}
let requestPermissionMutex = false;
async function requestPermission(keyName: string, remotePubkey: string, method: string, param?: any): Promise<boolean> {
if (requestPermissionMutex) {
console.log(`can't process request ${method} because signer is busy`);
return false;
// setTimeout(() => {
// requestPermission(keyName, remotePubkey, method, param);
// }, 1000);
// return;
}
requestPermissionMutex = true;
const npub = nip19.npubEncode(remotePubkey);
const promise = new Promise<boolean>((resolve, reject) => {
@ -245,12 +127,40 @@ async function requestPermission(keyName: string, remotePubkey: string, method:
console.log('🚫 Denying this request and all future requests from this key.');
await rejectAllRequestsFromKey(remotePubkey, keyName);
},
response: () => {
requestPermissionMutex = false;
}
});
});
return promise;
}
function callbackForKeyAdminInterface(keyName: string, adminInterface: AdminInterface): Nip46PermitCallback {
return async (remotePubkey: string, method: string, param?: any): Promise<boolean> => {
console.log(`🔑 ${keyName} is being requested to ${method} by ${nip19.npubEncode(remotePubkey)}`);
if (!adminInterface.requestPermission) {
throw new Error('adminInterface.requestPermission is not defined');
}
try {
const keyAllowed = await checkIfPubkeyAllowed(keyName, remotePubkey, method, param);
if (keyAllowed === true || keyAllowed === false) {
console.log(`🔎 ${nip19.npubEncode(remotePubkey)} is ${keyAllowed ? 'allowed' : 'denied'} to ${method} with key ${keyName}`);
return keyAllowed;
}
return adminInterface.requestPermission(keyName, remotePubkey, method, param);
} catch(e) {
console.log('callbackForKey error:', e);
}
return false;
};
}
function callbackForKey(keyName: string): Nip46PermitCallback {
return async (remotePubkey: string, method: string, param?: any): Promise<boolean> => {
try {
@ -270,3 +180,78 @@ function callbackForKey(keyName: string): Nip46PermitCallback {
return false;
};
}
export default async function run(config: DaemonConfig) {
const daemon = new Daemon(config);
await daemon.start();
}
class Daemon {
private config: DaemonConfig;
private activeKeys: Record<string, any>;
private adminInterface: AdminInterface;
private ndk: NDK;
constructor(config: DaemonConfig) {
this.config = config;
this.activeKeys = config.keys;
this.adminInterface = new AdminInterface(config.admin, config.configFile);
this.adminInterface.getKeys = getKeys(config);
this.adminInterface.getKeyUsers = getKeyUsers(config);
this.adminInterface.unlockKey = this.unlockKey.bind(this);
this.adminInterface.loadNsec = this.loadNsec.bind(this);
this.ndk = new NDK({
explicitRelayUrls: config.nostr.relays,
});
this.ndk.pool.on('connect', (r) => { console.log(`✅ Connected to ${r.url}`); });
this.ndk.pool.on('notice', (n, r) => { console.log(`👀 Notice from ${r.url}`, n); });
}
async start() {
await this.ndk.connect(5000);
setTimeout(async () => {
const promise = [];
for (const [name, nsec] of Object.entries(this.config.keys)) {
promise.push(this.startKey(name, nsec));
}
// await Promise.all(promise);
console.log('✅ nsecBunker ready to serve requests.');
}, 1000);
}
/**
* Method to start a key's backend
* @param name Name of the key
* @param nsec NSec of the key
*/
async startKey(name: string, nsec: string) {
const cb = callbackForKeyAdminInterface(name, this.adminInterface);
const hexpk = nip19.decode(nsec).data as string;
const backend = new Backend(this.ndk, hexpk, cb);
await backend.start();
}
async unlockKey(keyName: string, passphrase: string): Promise<boolean> {
const keyData = this.config.allKeys[keyName];
const { iv, data } = keyData;
const nsec = decryptNsec(iv, data, passphrase);
this.activeKeys[keyName] = nsec;
this.startKey(keyName, nsec);
return true;
}
loadNsec(keyName: string, nsec: string) {
this.activeKeys[keyName] = nsec;
this.startKey(keyName, nsec);
}
}

View file

@ -7,14 +7,14 @@ import { setup } from './commands/setup.js';
import { addNsec } from './commands/add.js';
import { start } from './commands/start.js';
console.log(`nsecBunker licensed under CC BY-NC-ND 3.0:`);
console.log(`nsecBunker licensed under CC BY-NC-ND 4.0:`);
console.log(`free to use for non-commercial use`);
console.log(`Copyright by pablof7z <npub1l2vyh47mk2p0qlsku7hg0vn29faehy9hy34ygaclpn66ukqp3afqutajft> 2023`);
console.log(`Copyright by pablof7z <pablo@f7z.io> 2023`);
console.log(`Contact for licensing`);
console.log(``);
yargs(hideBin(process.argv))
.command('setup', 'Setup nsecBunker', () => {}, (argv) => {
.command('setup', 'Setup nsecBunker', {}, (argv) => {
setup(argv.config as string);
})
@ -30,13 +30,19 @@ yargs(hideBin(process.argv))
.option('key <name>', {
type: 'string',
description: 'Name of key to enable',
})
.array('admin')
.option('admin <npub>', {
alias: 'a',
type: 'string',
description: 'Admin npub',
});
}, (argv) => {
start({
keys: argv.key as string[],
verbose: argv.verbose as boolean,
config: argv.config as string,
adminNpubs: argv.admin as string[],
});
})
@ -65,49 +71,3 @@ yargs(hideBin(process.argv))
})
.demandCommand(1)
.parse();
// async function cb(pubkey: string, method: string, param?: any): Promise<boolean> {
// // check if pubkey is in allowed list file
// // if not, return false
// // if yes, return true
// // read file allowed.json
// try {
// const data = fs.readFileSync('config.json', 'utf8');
// const config = JSON.parse(data);
// const allowedPubkeys = config.allowedPubkeys || {};
// console.log('allowedPubkeys', allowedPubkeys, allowedPubkeys[pubkey]);
// if (allowedPubkeys[pubkey] && allowedPubkeys[pubkey].methods[method]) {
// console.log(`✅ ${pubkey} is allowed to ${method}`);
// return true;
// }
// } catch(e) {
// console.log('Error:', e);
// }
// console.log(`🚫 ${pubkey} is not allowed to ${method}`);
// return false;
// }
// (async () => {
// const ndk = await createNDK();
// console.log(`NSECBUNKER BOOTING UP`);
// if (!process.env.PKEY) {
// console.error('PKEY not set');
// process.exit(1);
// }
// const backend = new Backend(ndk, process.env.PKEY, cb);
// await backend.start();
// const npub = backend.localUser?.npub;
// const hexpubkey = backend.localUser?.hexpubkey();
// console.log(`NPUB: ${npub}`);
// console.log(`PUBK: ${hexpubkey}`);
// })();