initial commit

This commit is contained in:
pablof7z 2023-05-15 20:05:55 +02:00
commit 54de9cfa8e
26 changed files with 6950 additions and 0 deletions

7
.env Normal file
View File

@ -0,0 +1,7 @@
# Environment variables declared in this file are automatically made available to Prisma.
# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema
# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings
DATABASE_URL="file:./dev.db"

27
.eslintrc.json Normal file
View File

@ -0,0 +1,27 @@
{
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"prettier"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"sourceType": "module",
"ecmaVersion": 2021
},
"plugins": ["@typescript-eslint"],
"env": {
"jest": true
},
"rules": {
"@typescript-eslint/no-unused-vars": ["warn", { "args": "none" }], // No warnings for unused function arguments, which might be used in the future.
"no-constant-binary-expression": "error",
"semi": ["error", "always"]
},
"globals": {
"Buffer": true,
"expect": true,
"process": true,
"test": true
}
}

13
.gitignore vendored Normal file
View File

@ -0,0 +1,13 @@
node_modules
**/*.js
dist
**/*.d.ts
**/*.d.ts.map
nsecbunker.json
# Environment variables declared in this file are automatically made available to Prisma.
# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema
# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings
DATABASE_URL="file:./dev.db"

7
.prettierrc.json Normal file
View File

@ -0,0 +1,7 @@
{
"importOrder": ["^[./]"],
"importOrderSeparation": true,
"tabWidth": 4,
"useTabs": false,
"semi": true
}

22
README.md Normal file
View File

@ -0,0 +1,22 @@
# nsecbunker
Daemon to remotely sign nostr events using keys.
## Installation
```
npm i -g nsecbunker
```
## Usage
nsecbunker -
# Authors
* [pablof7z](nostr:npub1l2vyh47mk2p0qlsku7hg0vn29faehy9hy34ygaclpn66ukqp3afqutajft)
* npub1l2vyh47mk2p0qlsku7hg0vn29faehy9hy34ygaclpn66ukqp3afqutajft
# License
CC BY-NC-ND 3.0
Contact @pablof7z for licensing.

5950
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

52
package.json Normal file
View File

@ -0,0 +1,52 @@
{
"name": "nsecbunker",
"version": "0.1.0",
"description": "nsecbunker",
"main": "dist/index.js",
"bin": {
"nsecbunkerd": "dist/index.js",
"nsecbunker-client": "dist/client.js"
},
"files": [
"dist"
],
"repository": {
"type": "git",
"url": "https://github.com/sanity-island/nsecbunker"
},
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"nsecbunkerd": "node dist/index.js",
"nsecbunker-client": "node dist/client.js"
},
"keywords": [
"nostr"
],
"author": "pablof7z",
"license": "MIT",
"dependencies": {
"@inquirer/password": "^1.0.0",
"@inquirer/prompts": "^1.0.0",
"@prisma/client": "4.13.0",
"@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",
"eslint-plugin-import": "^2.27.5",
"eventemitter3": "^5.0.0",
"websocket-polyfill": "^0.0.3",
"yargs": "^17.7.1"
},
"devDependencies": {
"@types/debug": "^4.1.7",
"@types/node": "^18.15.11",
"prisma": "^4.13.0",
"ts-node": "^10.9.1",
"typescript": "^5.0.3"
}
}

View File

@ -0,0 +1,15 @@
-- CreateTable
CREATE TABLE "KeyUser" (
"keyName" TEXT NOT NULL PRIMARY KEY,
"userNpub" TEXT NOT NULL
);
-- CreateTable
CREATE TABLE "SigningCondition" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"kind" INTEGER,
"content" TEXT,
"keyUserKeyName" TEXT,
"allowed" BOOLEAN,
CONSTRAINT "SigningCondition_keyUserKeyName_fkey" FOREIGN KEY ("keyUserKeyName") REFERENCES "KeyUser" ("keyName") ON DELETE SET NULL ON UPDATE CASCADE
);

View File

@ -0,0 +1,8 @@
/*
Warnings:
- A unique constraint covering the columns `[keyName,userNpub]` on the table `KeyUser` will be added. If there are existing duplicate values, this will fail.
*/
-- CreateIndex
CREATE UNIQUE INDEX "KeyUser_keyName_userNpub_key" ON "KeyUser"("keyName", "userNpub");

View File

@ -0,0 +1,34 @@
/*
Warnings:
- The primary key for the `KeyUser` table will be changed. If it partially fails, the table could be left without primary key constraint.
- You are about to drop the column `userNpub` on the `KeyUser` table. All the data in the column will be lost.
- Added the required column `id` to the `KeyUser` table without a default value. This is not possible if the table is not empty.
- Added the required column `userPubkey` to the `KeyUser` table without a default value. This is not possible if the table is not empty.
*/
-- RedefineTables
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_KeyUser" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"keyName" TEXT NOT NULL,
"userPubkey" TEXT NOT NULL
);
INSERT INTO "new_KeyUser" ("keyName") SELECT "keyName" FROM "KeyUser";
DROP TABLE "KeyUser";
ALTER TABLE "new_KeyUser" RENAME TO "KeyUser";
CREATE UNIQUE INDEX "KeyUser_keyName_userPubkey_key" ON "KeyUser"("keyName", "userPubkey");
CREATE TABLE "new_SigningCondition" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"kind" INTEGER,
"content" TEXT,
"keyUserKeyName" TEXT,
"allowed" BOOLEAN,
"keyUserId" INTEGER,
CONSTRAINT "SigningCondition_keyUserId_fkey" FOREIGN KEY ("keyUserId") REFERENCES "KeyUser" ("id") ON DELETE SET NULL ON UPDATE CASCADE
);
INSERT INTO "new_SigningCondition" ("allowed", "content", "id", "keyUserKeyName", "kind") SELECT "allowed", "content", "id", "keyUserKeyName", "kind" FROM "SigningCondition";
DROP TABLE "SigningCondition";
ALTER TABLE "new_SigningCondition" RENAME TO "SigningCondition";
PRAGMA foreign_key_check;
PRAGMA foreign_keys=ON;

View File

@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "SigningCondition" ADD COLUMN "method" TEXT;

View File

@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
provider = "sqlite"

43
prisma/schema.prisma Normal file
View File

@ -0,0 +1,43 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
model KeyUser {
id Int @id @default(autoincrement())
keyName String
userPubkey String
signingConditions SigningCondition[]
logs Log[]
@@unique([keyName, userPubkey], name: "unique_key_user")
}
model SigningCondition {
id Int @id @default(autoincrement())
method String?
kind Int?
content String?
keyUserKeyName String?
allowed Boolean?
KeyUser KeyUser? @relation(fields: [keyUserId], references: [id])
keyUserId Int?
}
model Log {
id Int @id @default(autoincrement())
timestamp DateTime
type String
method String?
params String?
KeyUser KeyUser? @relation(fields: [keyUserId], references: [id])
keyUserId Int?
}

56
src/client.ts Normal file
View File

@ -0,0 +1,56 @@
import NDK, { 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 content = process.argv[3];
if (!content) {
console.log('Usage: node src/client.js <remote-pubkey> <content>');
console.log('');
console.log(`\t<remote-pubkey>: 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'],
});
await ndk.connect(2000);
ndk.pool.on('connect', () => console.log('✅ connected'));
return ndk;
}
(async () => {
const ndk = await createNDK();
const localSigner = new NDKPrivateKeySigner('9ec8a4b2e1fac9eae616736f718f92ed30c57fc2fff36ef8139e27c31889e327');
const signer = new NDKNip46Signer(ndk, remotePubkey, localSigner);
console.log(`local pubkey`, (await signer.user()).npub);
console.log(`remote pubkey`, remotePubkey);
ndk.signer = signer;
setTimeout(async () => {
await signer.blockUntilReady();
console.log(`authorized to sign as`, remotePubkey);
const notPabloEvent = new NDKEvent(ndk, {
pubkey: remotePubkey,
kind: 1,
content,
tags: [
['t', 'grownostr'],
],
} as NostrEvent);
await notPabloEvent.sign();
console.log('resulting event', JSON.stringify(await notPabloEvent.toNostrEvent()));
// await notPabloEvent.publish();
}, 2000);
})();

49
src/commands/add.ts Normal file
View File

@ -0,0 +1,49 @@
import {nip19, getPublicKey} from 'nostr-tools';
import readline from 'readline';
import { getCurrentConfig, saveCurrentConfig } from '../config/index.js';
import { encryptNsec } from '../config/keys.js';
interface IOpts {
config: string;
name: string;
}
function saveEncrypted(config: string, nsec: string, passphrase: string, name: string) {
const { iv, data } = encryptNsec(nsec, passphrase);
const currentConfig = getCurrentConfig(config);
currentConfig.keys[name] = { iv, data };
saveCurrentConfig(config, currentConfig);
}
export async function addNsec(opts: IOpts) {
const name = opts.name;
const config = opts.config;
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
console.log(`nsecBunker uses a passphrase to encrypt your nsec when stored on-disk.\n` +
`Every time you restart it, you will need to type in this password.` +
`\n`);
rl.question(`Enter a passphrase: `, (passphrase: string) => {
rl.question(`Enter the nsec for ${name}: `, (nsec: string) => {
let decoded;
try {
decoded = nip19.decode(nsec);
const hexpubkey = getPublicKey(decoded.data as string);
const npub = nip19.npubEncode(hexpubkey);
saveEncrypted(config, nsec, passphrase, name);
rl.close();
} catch (e: any) {
console.log(e.message);
process.exit(1);
}
});
});
}

7
src/commands/setup.ts Normal file
View File

@ -0,0 +1,7 @@
import readline from 'readline';
import fs from 'fs';
import crypto from 'crypto';
export function setup(config: string) {
}

84
src/commands/start.ts Normal file
View File

@ -0,0 +1,84 @@
import readline from 'readline';
import { getCurrentConfig } from '../config/index.js';
import { decryptNsec } from '../config/keys.js';
import { fork } from 'child_process';
import { resolve } from 'path';
interface IOpts {
keys: string[];
verbose: boolean;
config: string;
}
export async function start(opts: IOpts) {
const configData = getCurrentConfig(opts.config);
if (opts.verbose) {
configData.verbose = opts.verbose;
}
const keys: Record<string, string> = {};
let keysToStart = opts.keys;
if (!keysToStart) {
keysToStart = Object.keys(configData.keys);
}
for (const keyName of keysToStart) {
const nsec = await startKey(keyName, configData.keys[keyName], opts.verbose);
if (nsec) {
keys[keyName] = nsec;
}
}
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);
}
interface KeyData {
iv: string;
data: string;
}
/**
* Start a key
*/
async function startKey(key: string, keyData: KeyData, verbose: boolean): Promise<string | undefined> {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
return new Promise((resolve) => {
rl.question(`Enter passphrase for ${key}: `, (passphrase: string) => {
try {
const { iv, data } = keyData;
const nsec = decryptNsec(iv, data, passphrase);
if (verbose) {
console.log(`Starting ${key}...`);
}
rl.close();
resolve(nsec);
} catch (e: any) {
console.log(e.message);
process.exit(1);
}
});
});
}

53
src/config/index.ts Normal file
View File

@ -0,0 +1,53 @@
import { randomBytes } from 'crypto';
import { readFileSync, writeFileSync } from 'fs';
function getPassphrase(): string {
const passwordLength = 32;
const passwordBytes = randomBytes(passwordLength);
return passwordBytes.toString('base64').slice(0, passwordLength);
}
const defaultConfig = {
nostr: {
relays: [
'wss://nos.lol',
// 'wss://relay.damus.io'
]
},
remote: {
passphrase: getPassphrase(),
},
database: 'sqlite://nsecbunker.db',
logs: './nsecbunker.log',
keys: {},
verbose: false,
};
export function getCurrentConfig(config: string) {
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`);
return defaultConfig;
} else {
console.error(`Error reading config file: ${err.message}`);
process.exit(1); // Kill the process if there is an error parsing the JSON
}
}
}
export function saveCurrentConfig(config: string, currentConfig: any) {
try {
const configString = JSON.stringify(currentConfig, null, 2);
writeFileSync(config, configString);
} catch (err: any) {
console.error(`Error writing config file: ${err.message}`);
process.exit(1); // Kill the process if there is an error parsing the JSON
}
}

26
src/config/keys.ts Normal file
View File

@ -0,0 +1,26 @@
import crypto from 'crypto';
export function encryptNsec(nsec: string, passphrase: string): { iv: string, data: string } {
const algorithm = 'aes-256-cbc';
const key = crypto.createHash('sha256').update(passphrase).digest();
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv(algorithm, key, iv);
let encrypted = cipher.update(nsec);
encrypted = Buffer.concat([encrypted, cipher.final()]);
return {
iv: iv.toString('hex'),
data: encrypted.toString('hex'),
};
}
export function decryptNsec(iv: string, data: string, passphrase: string): string {
const algorithm = 'aes-256-cbc';
const key = crypto.createHash('sha256').update(passphrase).digest();
const ivBuffer = Buffer.from(iv, 'hex');
const dataBuffer = Buffer.from(data, 'hex');
const decipher = crypto.createDecipheriv(algorithm, key, ivBuffer);
let decrypted = decipher.update(dataBuffer);
decrypted = Buffer.concat([decrypted, decipher.final()]);
return decrypted.toString();
}

View File

@ -0,0 +1,9 @@
import NDK, { NDKNip46Backend, Nip46PermitCallback } from '@nostr-dev-kit/ndk';
import PublishEventHandlingStrategy from './publish-event.js';
export class Backend extends NDKNip46Backend {
constructor(ndk: NDK, key: string, cb: Nip46PermitCallback) {
super(ndk, key, cb);
this.setStrategy('publish_event', new PublishEventHandlingStrategy());
}
}

View File

@ -0,0 +1,13 @@
import { NDKNip46Backend } from "@nostr-dev-kit/ndk";
import { IEventHandlingStrategy } from '@nostr-dev-kit/ndk/lib/src/signers/nip46/backend';
export default class PublishEventHandlingStrategy implements IEventHandlingStrategy {
async handle(backend: NDKNip46Backend, remotePubkey: string, params: string[]): Promise<string|undefined> {
const event = await backend.signEvent(remotePubkey, params);
if (!event) return undefined;
backend.ndk.publish(event);
return JSON.stringify(await event.toNostrEvent());
}
}

6
src/daemon/index.ts Normal file
View File

@ -0,0 +1,6 @@
import run from './run';
import type {IOpts} from './run';
process.on('message', (configData: IOpts) => {
run(configData);
});

272
src/daemon/run.ts Normal file
View File

@ -0,0 +1,272 @@
import NDK, { Nip46PermitCallback } from '@nostr-dev-kit/ndk';
import { nip19 } from 'nostr-tools';
import { Backend } from './backend/index.js';
import readline from 'readline';
import prisma from '../db.js';
export interface IOpts {
keys: Record<string, string>;
nostr: {
relays: string[],
}
verbose: boolean;
}
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...`);
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);
const hexpk = nip19.decode(nsec).data as string;
const backend = new Backend(ndk, hexpk, cb);
promise.push(backend.start());
}
await Promise.all(promise);
console.log('✅ nsecBunker ready to serve requests.');
}, 1000);
}
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;
}
// find SigningCondition
const signingConditionQuery = requestToSigningConditionQuery(method, param);
const explicitReject = await prisma.signingCondition.findFirst({
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
},
});
} catch (e) {
console.log('allowAllRequestsFromKey', e);
}
}
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,
},
});
}
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;
}
async function requestPermission(keyName: string, remotePubkey: string, method: string, param?: any): Promise<boolean> {
const npub = nip19.npubEncode(remotePubkey);
const promise = new Promise<boolean>((resolve, reject) => {
const question = `👉 Do you want to allow ${npub} to ${method} with key ${keyName}?`;
if (method === 'sign_event') {
const e = param.rawEvent();
console.log(`👀 Event to be signed\n`, {
kind: e.kind,
content: e.content,
tags: e.tags,
});
}
askYNquestion(question, {
timeoutLength: 30000,
yes: () => { resolve(true); },
no: () => { resolve(false); },
timeout: () => { console.log('🚫 Timeout reached, denying request.'); resolve(false); },
always: async () => {
console.log('✅ Allowing this request and all future requests from this key.');
await allowAllRequestsFromKey(remotePubkey, keyName, method, param);
},
never: async () => {
console.log('🚫 Denying this request and all future requests from this key.');
await rejectAllRequestsFromKey(remotePubkey, keyName);
},
});
});
return promise;
}
function callbackForKey(keyName: string): Nip46PermitCallback {
return async (remotePubkey: string, method: string, param?: any): Promise<boolean> => {
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;
}
// No explicit allow or deny, ask the user
return requestPermission(keyName, remotePubkey, method, param);
} catch(e) {
console.log('callbackForKey error:', e);
}
return false;
};
}

5
src/db.ts Normal file
View File

@ -0,0 +1,5 @@
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export default prisma;

113
src/index.ts Normal file
View File

@ -0,0 +1,113 @@
#!/usr/bin/env node
import 'websocket-polyfill';
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
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(`free to use for non-commercial use`);
console.log(`Copyright by pablof7z <npub1l2vyh47mk2p0qlsku7hg0vn29faehy9hy34ygaclpn66ukqp3afqutajft> 2023`);
console.log(`Contact for licensing`);
console.log(``);
yargs(hideBin(process.argv))
.command('setup', 'Setup nsecBunker', () => {}, (argv) => {
setup(argv.config as string);
})
.command('start', 'Start nsecBunker', (yargs) => {
yargs
.option('verbose', {
alias: 'v',
type: 'boolean',
description: 'Run with verbose logging',
default: false,
})
.array('key')
.option('key <name>', {
type: 'string',
description: 'Name of key to enable',
});
}, (argv) => {
start({
keys: argv.key as string[],
verbose: argv.verbose as boolean,
config: argv.config as string,
});
})
.command('add', 'Add an nsec', (yargs) => {
yargs
.option('name', {
alias: 'n',
type: 'string',
description: 'Name of the nsec',
demandOption: true,
});
}, (argv) => {
addNsec({
config: argv.config as string,
name: argv.name as string
});
})
.options({
'config': {
alias: 'c',
type: 'string',
description: 'Path to config file',
default: 'nsecbunker.json',
},
})
.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}`);
// })();

74
tsconfig.json Normal file
View File

@ -0,0 +1,74 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "es2020" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
"module": "CommonJS", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
// "lib": [], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
"declaration": true, /* Generates corresponding '.d.ts' file. */
"sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "dist", /* Redirect output structure to the directory. */
"rootDir": "src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true /* Enable all strict type-checking options. */,
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
/* Module Resolution Options */
"moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */,
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
// "resolveJsonModule": true,
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
/* Advanced Options */
"skipLibCheck": true, /* Skip type checking of declaration files. */
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
},
"include": ["src/**/*"],
"exclude": [
"node_modules",
"**/*.spec.ts"
]
}