mirror of
https://github.com/kind-0/nsecbunkerd.git
synced 2025-03-17 13:22:54 +01:00
Policies and single-use tokens
This commit is contained in:
parent
28f4788aec
commit
c43f1cc95e
@ -1,31 +1,25 @@
|
||||
# Security Model
|
||||
|
||||
The premise of nsecBunker is that you can store Nostr private keys (nsecs), use them remotely
|
||||
under certain policies, but these keys can never be exfiltrated from nsecBunker.
|
||||
The premise of nsecBunker is that you can store Nostr private keys (nsecs), use them remotely under certain policies, but these keys can never be exfiltrated from nsecBunker.
|
||||
|
||||
All communication with nsecBunker happens through encrypted, ephemeral nostr events.
|
||||
|
||||
## Keys
|
||||
Within nsecBunker there are two distinct sets of keys:
|
||||
### User keys
|
||||
The keys that users want to sign with (e.g. your personal or company's key).
|
||||
|
||||
These keys are stored encrypted with a passphrase; the same way Lightning Network's LND
|
||||
stores keys locally: every time you start nsecBunker, you must enter the passphrase to decrypt it.
|
||||
### User keys (aka target keys)
|
||||
The keys that users want to sign with (e.g. your personal or company's keys).
|
||||
|
||||
These keys are stored encrypted with a passphrase; the same way Lightning Network's LND stores keys locally: every time you start nsecBunker, you must enter the passphrase to decrypt it.
|
||||
|
||||
Without this passphrase, keys cannot be used.
|
||||
|
||||
### nsecBunker's key
|
||||
nsecBunker generates it's own private key, which is used solely to communicate
|
||||
with the nsecBunker administration UI. If these keys are compromised, no key material is at risk.
|
||||
nsecBunker generates it's own private key, which is used solely to communicate with the nsecBunker administration UI. If these keys are compromised, no key material is at risk.
|
||||
|
||||
To interact with nsecBunker's administration UI, the administrator(s)' keys must be whitelisted
|
||||
within nsecBunker. All communication between the administrator and the nsecBunker is end-to-end
|
||||
encrypted with these two set of keys.
|
||||
To interact with nsecBunker's administration UI, the administrator(s)' keys must be whitelisted within nsecBunker. All communication between the administrator and the nsecBunker is end-to-end encrypted with these two set of keys.
|
||||
|
||||
Non-whitelisted keys simply cannot talk to nsecBunker's Administration UI, which is why even if
|
||||
the nsecBunker connection string that is created when you setup your nsecBunker is leaked, nothing
|
||||
happens.
|
||||
Non-whitelisted keys simply cannot talk to nsecBunker's Administration UI.
|
||||
|
||||
## Nostr Connect
|
||||
nsecBunker listens on certain relays (specified in the config file) for keys that are attempting to
|
||||
sign with the
|
||||
nsecBunker listens on certain relays (specified in the config file) for keys that are attempting to sign with the target keys.
|
78
package-lock.json
generated
78
package-lock.json
generated
@ -1,18 +1,18 @@
|
||||
{
|
||||
"name": "nsecbunkerd",
|
||||
"version": "0.5.7",
|
||||
"version": "0.5.9",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "nsecbunkerd",
|
||||
"version": "0.5.7",
|
||||
"version": "0.5.9",
|
||||
"license": "CC BY-NC-ND 4.0",
|
||||
"dependencies": {
|
||||
"@inquirer/password": "^1.0.0",
|
||||
"@inquirer/prompts": "^1.0.0",
|
||||
"@nostr-dev-kit/ndk": "^0.3.26",
|
||||
"@prisma/client": "^4.14.1",
|
||||
"@nostr-dev-kit/ndk": "^0.3.32",
|
||||
"@prisma/client": "^4.15.0",
|
||||
"@scure/base": "^1.1.1",
|
||||
"@types/yargs": "^17.0.24",
|
||||
"@typescript-eslint/eslint-plugin": "^5.57.0",
|
||||
@ -32,7 +32,7 @@
|
||||
"devDependencies": {
|
||||
"@types/debug": "^4.1.7",
|
||||
"@types/node": "^18.15.11",
|
||||
"prisma": "^4.14.1",
|
||||
"prisma": "^4.15.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.0.3"
|
||||
}
|
||||
@ -1117,9 +1117,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@nostr-dev-kit/ndk": {
|
||||
"version": "0.3.26",
|
||||
"resolved": "https://registry.npmjs.org/@nostr-dev-kit/ndk/-/ndk-0.3.26.tgz",
|
||||
"integrity": "sha512-Blr8T2G2CuhCK4JRqZxLE2ZsvXHrBTtJEfNN3Iw8uzMw5uhYM+oaNNVOZ3Gwf0Hth4nxTjsZSqYiUvqf5t7DRw==",
|
||||
"version": "0.3.32",
|
||||
"resolved": "https://registry.npmjs.org/@nostr-dev-kit/ndk/-/ndk-0.3.32.tgz",
|
||||
"integrity": "sha512-f6fttPt6rRFNM0JV+atNZudz3MUpS4cZtPHZv8DQOXgfEOzjJCG5yOl0n9AUTsPYQirsGq3glW/gZs/LkxrP8g==",
|
||||
"dependencies": {
|
||||
"@noble/secp256k1": "^2.0.0",
|
||||
"@scure/base": "^1.1.1",
|
||||
@ -1143,12 +1143,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/client": {
|
||||
"version": "4.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.14.1.tgz",
|
||||
"integrity": "sha512-TZIswkeX1ccsHG/eN2kICzg/csXll0osK3EHu1QKd8VJ3XLcXozbNELKkCNfsCUvKJAwPdDtFCzF+O+raIVldw==",
|
||||
"version": "4.15.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.15.0.tgz",
|
||||
"integrity": "sha512-xnROvyABcGiwqRNdrObHVZkD9EjkJYHOmVdlKy1yGgI+XOzvMzJ4tRg3dz1pUlsyhKxXGCnjIQjWW+2ur+YXuw==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@prisma/engines-version": "4.14.0-67.d9a4c5988f480fa576d43970d5a23641aa77bc9c"
|
||||
"@prisma/engines-version": "4.15.0-28.8fbc245156db7124f997f4cecdd8d1219e360944"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
@ -1163,16 +1163,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/engines": {
|
||||
"version": "4.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-4.14.1.tgz",
|
||||
"integrity": "sha512-APqFddPVHYmWNKqc+5J5SqrLFfOghKOLZxobmguDUacxOwdEutLsbXPVhNnpFDmuQWQFbXmrTTPoRrrF6B1MWA==",
|
||||
"version": "4.15.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-4.15.0.tgz",
|
||||
"integrity": "sha512-FTaOCGs0LL0OW68juZlGxFtYviZa4xdQj/rQEdat2txw0s3Vu/saAPKjNVXfIgUsGXmQ72HPgNr6935/P8FNAA==",
|
||||
"devOptional": true,
|
||||
"hasInstallScript": true
|
||||
},
|
||||
"node_modules/@prisma/engines-version": {
|
||||
"version": "4.14.0-67.d9a4c5988f480fa576d43970d5a23641aa77bc9c",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.14.0-67.d9a4c5988f480fa576d43970d5a23641aa77bc9c.tgz",
|
||||
"integrity": "sha512-3jum8/YSudeSN0zGW5qkpz+wAN2V/NYCQ+BPjvHYDfWatLWlQkqy99toX0GysDeaUoBIJg1vaz2yKqiA3CFcQw=="
|
||||
"version": "4.15.0-28.8fbc245156db7124f997f4cecdd8d1219e360944",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.15.0-28.8fbc245156db7124f997f4cecdd8d1219e360944.tgz",
|
||||
"integrity": "sha512-sVOig4tjGxxlYaFcXgE71f/rtFhzyYrfyfNFUsxCIEJyVKU9rdOWIlIwQ2NQ7PntvGnn+x0XuFo4OC1jvPJKzg=="
|
||||
},
|
||||
"node_modules/@scure/base": {
|
||||
"version": "1.1.1",
|
||||
@ -4199,13 +4199,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/prisma": {
|
||||
"version": "4.14.1",
|
||||
"resolved": "https://registry.npmjs.org/prisma/-/prisma-4.14.1.tgz",
|
||||
"integrity": "sha512-z6hxzTMYqT9SIKlzD08dhzsLUpxjFKKsLpp5/kBDnSqiOjtUyyl/dC5tzxLcOa3jkEHQ8+RpB/fE3w8bgNP51g==",
|
||||
"version": "4.15.0",
|
||||
"resolved": "https://registry.npmjs.org/prisma/-/prisma-4.15.0.tgz",
|
||||
"integrity": "sha512-iKZZpobPl48gTcSZVawLMQ3lEy6BnXwtoMj7hluoGFYu2kQ6F9LBuBrUyF95zRVnNo8/3KzLXJXJ5TEnLSJFiA==",
|
||||
"devOptional": true,
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@prisma/engines": "4.14.1"
|
||||
"@prisma/engines": "4.15.0"
|
||||
},
|
||||
"bin": {
|
||||
"prisma": "build/index.js",
|
||||
@ -5872,9 +5872,9 @@
|
||||
}
|
||||
},
|
||||
"@nostr-dev-kit/ndk": {
|
||||
"version": "0.3.26",
|
||||
"resolved": "https://registry.npmjs.org/@nostr-dev-kit/ndk/-/ndk-0.3.26.tgz",
|
||||
"integrity": "sha512-Blr8T2G2CuhCK4JRqZxLE2ZsvXHrBTtJEfNN3Iw8uzMw5uhYM+oaNNVOZ3Gwf0Hth4nxTjsZSqYiUvqf5t7DRw==",
|
||||
"version": "0.3.32",
|
||||
"resolved": "https://registry.npmjs.org/@nostr-dev-kit/ndk/-/ndk-0.3.32.tgz",
|
||||
"integrity": "sha512-f6fttPt6rRFNM0JV+atNZudz3MUpS4cZtPHZv8DQOXgfEOzjJCG5yOl0n9AUTsPYQirsGq3glW/gZs/LkxrP8g==",
|
||||
"requires": {
|
||||
"@noble/secp256k1": "^2.0.0",
|
||||
"@scure/base": "^1.1.1",
|
||||
@ -5898,23 +5898,23 @@
|
||||
}
|
||||
},
|
||||
"@prisma/client": {
|
||||
"version": "4.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.14.1.tgz",
|
||||
"integrity": "sha512-TZIswkeX1ccsHG/eN2kICzg/csXll0osK3EHu1QKd8VJ3XLcXozbNELKkCNfsCUvKJAwPdDtFCzF+O+raIVldw==",
|
||||
"version": "4.15.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.15.0.tgz",
|
||||
"integrity": "sha512-xnROvyABcGiwqRNdrObHVZkD9EjkJYHOmVdlKy1yGgI+XOzvMzJ4tRg3dz1pUlsyhKxXGCnjIQjWW+2ur+YXuw==",
|
||||
"requires": {
|
||||
"@prisma/engines-version": "4.14.0-67.d9a4c5988f480fa576d43970d5a23641aa77bc9c"
|
||||
"@prisma/engines-version": "4.15.0-28.8fbc245156db7124f997f4cecdd8d1219e360944"
|
||||
}
|
||||
},
|
||||
"@prisma/engines": {
|
||||
"version": "4.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-4.14.1.tgz",
|
||||
"integrity": "sha512-APqFddPVHYmWNKqc+5J5SqrLFfOghKOLZxobmguDUacxOwdEutLsbXPVhNnpFDmuQWQFbXmrTTPoRrrF6B1MWA==",
|
||||
"version": "4.15.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-4.15.0.tgz",
|
||||
"integrity": "sha512-FTaOCGs0LL0OW68juZlGxFtYviZa4xdQj/rQEdat2txw0s3Vu/saAPKjNVXfIgUsGXmQ72HPgNr6935/P8FNAA==",
|
||||
"devOptional": true
|
||||
},
|
||||
"@prisma/engines-version": {
|
||||
"version": "4.14.0-67.d9a4c5988f480fa576d43970d5a23641aa77bc9c",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.14.0-67.d9a4c5988f480fa576d43970d5a23641aa77bc9c.tgz",
|
||||
"integrity": "sha512-3jum8/YSudeSN0zGW5qkpz+wAN2V/NYCQ+BPjvHYDfWatLWlQkqy99toX0GysDeaUoBIJg1vaz2yKqiA3CFcQw=="
|
||||
"version": "4.15.0-28.8fbc245156db7124f997f4cecdd8d1219e360944",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.15.0-28.8fbc245156db7124f997f4cecdd8d1219e360944.tgz",
|
||||
"integrity": "sha512-sVOig4tjGxxlYaFcXgE71f/rtFhzyYrfyfNFUsxCIEJyVKU9rdOWIlIwQ2NQ7PntvGnn+x0XuFo4OC1jvPJKzg=="
|
||||
},
|
||||
"@scure/base": {
|
||||
"version": "1.1.1",
|
||||
@ -8073,12 +8073,12 @@
|
||||
}
|
||||
},
|
||||
"prisma": {
|
||||
"version": "4.14.1",
|
||||
"resolved": "https://registry.npmjs.org/prisma/-/prisma-4.14.1.tgz",
|
||||
"integrity": "sha512-z6hxzTMYqT9SIKlzD08dhzsLUpxjFKKsLpp5/kBDnSqiOjtUyyl/dC5tzxLcOa3jkEHQ8+RpB/fE3w8bgNP51g==",
|
||||
"version": "4.15.0",
|
||||
"resolved": "https://registry.npmjs.org/prisma/-/prisma-4.15.0.tgz",
|
||||
"integrity": "sha512-iKZZpobPl48gTcSZVawLMQ3lEy6BnXwtoMj7hluoGFYu2kQ6F9LBuBrUyF95zRVnNo8/3KzLXJXJ5TEnLSJFiA==",
|
||||
"devOptional": true,
|
||||
"requires": {
|
||||
"@prisma/engines": "4.14.1"
|
||||
"@prisma/engines": "4.15.0"
|
||||
}
|
||||
},
|
||||
"punycode": {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "nsecbunkerd",
|
||||
"version": "0.5.8",
|
||||
"version": "0.6.0",
|
||||
"description": "nsecbunker daemon",
|
||||
"main": "dist/index.js",
|
||||
"bin": {
|
||||
@ -35,8 +35,8 @@
|
||||
"dependencies": {
|
||||
"@inquirer/password": "^1.0.0",
|
||||
"@inquirer/prompts": "^1.0.0",
|
||||
"@nostr-dev-kit/ndk": "^0.3.26",
|
||||
"@prisma/client": "^4.14.1",
|
||||
"@nostr-dev-kit/ndk": "^0.3.32",
|
||||
"@prisma/client": "^4.15.0",
|
||||
"@scure/base": "^1.1.1",
|
||||
"@types/yargs": "^17.0.24",
|
||||
"@typescript-eslint/eslint-plugin": "^5.57.0",
|
||||
@ -52,7 +52,7 @@
|
||||
"devDependencies": {
|
||||
"@types/debug": "^4.1.7",
|
||||
"@types/node": "^18.15.11",
|
||||
"prisma": "^4.14.1",
|
||||
"prisma": "^4.15.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.0.3"
|
||||
}
|
||||
|
19
prisma/migrations/20230603115339_policies/migration.sql
Normal file
19
prisma/migrations/20230603115339_policies/migration.sql
Normal file
@ -0,0 +1,19 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "Policy" (
|
||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"name" TEXT NOT NULL,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"expiresAt" DATETIME
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "PolicyRule" (
|
||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"method" TEXT NOT NULL,
|
||||
"kind" TEXT,
|
||||
"maxUsageCount" INTEGER,
|
||||
"currentUsageCount" INTEGER,
|
||||
"policyId" INTEGER,
|
||||
CONSTRAINT "PolicyRule_policyId_fkey" FOREIGN KEY ("policyId") REFERENCES "Policy" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Policy" ADD COLUMN "deletedAt" DATETIME;
|
2
prisma/migrations/20230603122649_desc/migration.sql
Normal file
2
prisma/migrations/20230603122649_desc/migration.sql
Normal file
@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Policy" ADD COLUMN "description" TEXT;
|
19
prisma/migrations/20230603134135_tokens/migration.sql
Normal file
19
prisma/migrations/20230603134135_tokens/migration.sql
Normal file
@ -0,0 +1,19 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "Token" (
|
||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"token" TEXT NOT NULL,
|
||||
"clientName" TEXT NOT NULL,
|
||||
"createdBy" TEXT NOT NULL,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"deletedAt" DATETIME,
|
||||
"expiresAt" DATETIME,
|
||||
"redeemedAt" DATETIME,
|
||||
"keyUserId" INTEGER,
|
||||
"policyId" INTEGER,
|
||||
CONSTRAINT "Token_keyUserId_fkey" FOREIGN KEY ("keyUserId") REFERENCES "KeyUser" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
|
||||
CONSTRAINT "Token_policyId_fkey" FOREIGN KEY ("policyId") REFERENCES "Policy" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Token_token_key" ON "Token"("token");
|
30
prisma/migrations/20230603145715_add_keyname/migration.sql
Normal file
30
prisma/migrations/20230603145715_add_keyname/migration.sql
Normal file
@ -0,0 +1,30 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- Added the required column `keyName` to the `Token` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- RedefineTables
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_Token" (
|
||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"keyName" TEXT NOT NULL,
|
||||
"token" TEXT NOT NULL,
|
||||
"clientName" TEXT NOT NULL,
|
||||
"createdBy" TEXT NOT NULL,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"deletedAt" DATETIME,
|
||||
"expiresAt" DATETIME,
|
||||
"redeemedAt" DATETIME,
|
||||
"keyUserId" INTEGER,
|
||||
"policyId" INTEGER,
|
||||
CONSTRAINT "Token_keyUserId_fkey" FOREIGN KEY ("keyUserId") REFERENCES "KeyUser" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
|
||||
CONSTRAINT "Token_policyId_fkey" FOREIGN KEY ("policyId") REFERENCES "Policy" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_Token" ("clientName", "createdAt", "createdBy", "deletedAt", "expiresAt", "id", "keyUserId", "policyId", "redeemedAt", "token", "updatedAt") SELECT "clientName", "createdAt", "createdBy", "deletedAt", "expiresAt", "id", "keyUserId", "policyId", "redeemedAt", "token", "updatedAt" FROM "Token";
|
||||
DROP TABLE "Token";
|
||||
ALTER TABLE "new_Token" RENAME TO "Token";
|
||||
CREATE UNIQUE INDEX "Token_token_key" ON "Token"("token");
|
||||
PRAGMA foreign_key_check;
|
||||
PRAGMA foreign_keys=ON;
|
@ -20,6 +20,7 @@ model KeyUser {
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
lastUsedAt DateTime?
|
||||
Token Token[]
|
||||
|
||||
@@unique([keyName, userPubkey], name: "unique_key_user")
|
||||
}
|
||||
@ -45,3 +46,43 @@ model Log {
|
||||
KeyUser KeyUser? @relation(fields: [keyUserId], references: [id])
|
||||
keyUserId Int?
|
||||
}
|
||||
|
||||
model Policy {
|
||||
id Int @id @default(autoincrement())
|
||||
name String
|
||||
description String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
deletedAt DateTime?
|
||||
expiresAt DateTime?
|
||||
rules PolicyRule[]
|
||||
Token Token[]
|
||||
}
|
||||
|
||||
model PolicyRule {
|
||||
id Int @id @default(autoincrement())
|
||||
method String
|
||||
kind String?
|
||||
maxUsageCount Int?
|
||||
currentUsageCount Int?
|
||||
|
||||
Policy Policy? @relation(fields: [policyId], references: [id])
|
||||
policyId Int?
|
||||
}
|
||||
|
||||
model Token {
|
||||
id Int @id @default(autoincrement())
|
||||
keyName String
|
||||
token String @unique
|
||||
clientName String
|
||||
createdBy String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
deletedAt DateTime?
|
||||
expiresAt DateTime?
|
||||
redeemedAt DateTime?
|
||||
KeyUser KeyUser? @relation(fields: [keyUserId], references: [id])
|
||||
keyUserId Int?
|
||||
policy Policy? @relation(fields: [policyId], references: [id])
|
||||
policyId Int?
|
||||
}
|
||||
|
@ -18,8 +18,11 @@ export interface IConfig {
|
||||
const defaultConfig: IConfig = {
|
||||
nostr: {
|
||||
relays: [
|
||||
'wss://relay.damus.io',
|
||||
'wss://nos.lol',
|
||||
// 'wss://relay.damus.io'
|
||||
'wss://relay.snort.social',
|
||||
"wss://relay.nsecbunker.com",
|
||||
"wss://nostr.vulpem.com",
|
||||
]
|
||||
},
|
||||
admin: {
|
||||
|
33
src/daemon/admin/commands/create_new_policy.ts
Normal file
33
src/daemon/admin/commands/create_new_policy.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { NDKRpcRequest } from "@nostr-dev-kit/ndk";
|
||||
import AdminInterface from "../index.js";
|
||||
import prisma from "../../../db.js";
|
||||
|
||||
export default async function createNewPolicy(admin: AdminInterface, req: NDKRpcRequest) {
|
||||
const [ _policy ] = req.params as [ string ];
|
||||
|
||||
if (!_policy) throw new Error("Invalid params");
|
||||
|
||||
const policy = JSON.parse(_policy);
|
||||
|
||||
const policyRecord = await prisma.policy.create({
|
||||
data: {
|
||||
name: policy.name,
|
||||
expiresAt: policy.expires_at,
|
||||
}
|
||||
});
|
||||
|
||||
for (const rule of policy.rules) {
|
||||
await prisma.policyRule.create({
|
||||
data: {
|
||||
policyId: policyRecord.id,
|
||||
kind: rule.kind.toString(),
|
||||
method: rule.method,
|
||||
maxUsageCount: rule.use_count,
|
||||
currentUsageCount: 0,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const result = JSON.stringify(["ok"]);
|
||||
return admin.rpc.sendResponse(req.id, req.pubkey, result, 24134);
|
||||
}
|
30
src/daemon/admin/commands/create_new_token.ts
Normal file
30
src/daemon/admin/commands/create_new_token.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { NDKRpcRequest } from "@nostr-dev-kit/ndk";
|
||||
import AdminInterface from "../index.js";
|
||||
import prisma from "../../../db.js";
|
||||
|
||||
export default async function createNewToken(admin: AdminInterface, req: NDKRpcRequest) {
|
||||
const [ keyName, clientName, policyId, durationInHours ] = req.params as [ string, string, string, string? ];
|
||||
|
||||
if (!clientName || !policyId) throw new Error("Invalid params");
|
||||
|
||||
const policy = await prisma.policy.findUnique({ where: { id: parseInt(policyId) }, include: { rules: true } });
|
||||
|
||||
if (!policy) throw new Error("Policy not found");
|
||||
|
||||
console.log({clientName, policy, durationInHours});
|
||||
|
||||
const token = [...Array(64)].map(() => Math.floor(Math.random() * 16).toString(16)).join('');
|
||||
const data: any = {
|
||||
keyName, clientName, policyId,
|
||||
createdBy: req.pubkey,
|
||||
token
|
||||
};
|
||||
if (durationInHours) data.expiresAt = new Date(Date.now() + (parseInt(durationInHours) * 60 * 60 * 1000));
|
||||
|
||||
const tokenRecord = await prisma.token.create({data});
|
||||
|
||||
if (!tokenRecord) throw new Error("Token not created");
|
||||
|
||||
const result = JSON.stringify(["ok"]);
|
||||
return admin.rpc.sendResponse(req.id, req.pubkey, result, 24134);
|
||||
}
|
@ -1,14 +1,12 @@
|
||||
import NDK, { NDKEvent, NDKPrivateKeySigner, NDKRpcRequest, NDKRpcResponse, NDKUser } from '@nostr-dev-kit/ndk';
|
||||
import NDK, { NDKPrivateKeySigner, NDKRpcRequest, NDKRpcResponse, NDKUser } from '@nostr-dev-kit/ndk';
|
||||
import { NDKNostrRpc } from '@nostr-dev-kit/ndk';
|
||||
import { debug } from 'debug';
|
||||
import { Key, KeyUser } from '../run';
|
||||
import {
|
||||
checkIfPubkeyAllowed,
|
||||
allowAllRequestsFromKey,
|
||||
rejectAllRequestsFromKey
|
||||
} from '../lib/acl/index.js';
|
||||
import { allowAllRequestsFromKey } from '../lib/acl/index.js';
|
||||
import prisma from '../../db';
|
||||
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';
|
||||
|
||||
export type IAdminOpts = {
|
||||
@ -89,20 +87,108 @@ class AdminInterface {
|
||||
private async handleRequest(req: NDKRpcRequest) {
|
||||
// await this.validateRequest(req);
|
||||
|
||||
try {
|
||||
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;
|
||||
|
||||
default:
|
||||
console.log(`Unknown method ${req.method}`);
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error(`Error handling request ${req.method}: ${err.message}`, req.params);
|
||||
}
|
||||
}
|
||||
|
||||
private async validateRequest(req: NDKRpcRequest) {
|
||||
// TODO validate pubkey, validate signature
|
||||
}
|
||||
|
||||
/**
|
||||
* Command to list tokens
|
||||
*/
|
||||
private async reqGetKeyTokens(req: NDKRpcRequest) {
|
||||
const keyName = req.params[0];
|
||||
const tokens = await prisma.token.findMany({
|
||||
where: { keyName },
|
||||
include: {
|
||||
policy: {
|
||||
include: {
|
||||
rules: true,
|
||||
},
|
||||
},
|
||||
KeyUser: true,
|
||||
},
|
||||
});
|
||||
|
||||
const keys = await this.getKeys!();
|
||||
const key = keys.find((k) => k.name === keyName);
|
||||
|
||||
if (!key || !key.npub) {
|
||||
return this.rpc.sendResponse(req.id, req.pubkey, JSON.stringify([]), 24134);
|
||||
}
|
||||
|
||||
const npub = key.npub;
|
||||
|
||||
const result = JSON.stringify(tokens.map((t) => {
|
||||
return {
|
||||
id: t.id,
|
||||
key_name: t.keyName,
|
||||
client_name: t.clientName,
|
||||
token: [ npub, t.token ].join('#'),
|
||||
policy_id: t.policyId,
|
||||
policy_name: t.policy?.name,
|
||||
created_at: t.createdAt,
|
||||
updated_at: t.updatedAt,
|
||||
expires_at: t.expiresAt,
|
||||
redeemed_at: t.redeemedAt,
|
||||
redeemed_by: t.KeyUser?.description,
|
||||
time_until_expiration: t.expiresAt ? (t.expiresAt.getTime() - Date.now()) / 1000 : null,
|
||||
};
|
||||
}));
|
||||
|
||||
return this.rpc.sendResponse(req.id, req.pubkey, result, 24134);
|
||||
}
|
||||
|
||||
/**
|
||||
* Command to list policies
|
||||
*/
|
||||
private async reqListPolicies(req: NDKRpcRequest) {
|
||||
const policies = await prisma.policy.findMany({
|
||||
include: {
|
||||
rules: true,
|
||||
},
|
||||
});
|
||||
|
||||
const result = JSON.stringify(policies.map((p) => {
|
||||
return {
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
description: p.description,
|
||||
created_at: p.createdAt,
|
||||
updated_at: p.updatedAt,
|
||||
expires_at: p.expiresAt,
|
||||
rules: p.rules.map((r) => {
|
||||
return {
|
||||
method: r.method,
|
||||
kind: r.kind,
|
||||
max_usage_count: r.maxUsageCount,
|
||||
current_usage_count: r.currentUsageCount,
|
||||
};
|
||||
})
|
||||
};
|
||||
}));
|
||||
|
||||
return this.rpc.sendResponse(req.id, req.pubkey, result, 24134);
|
||||
}
|
||||
|
||||
/**
|
||||
* Command to fetch keys and their current state
|
||||
*/
|
||||
@ -127,8 +213,6 @@ class AdminInterface {
|
||||
return this.rpc.sendResponse(req.id, pubkey, result, 24134); // 24134
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* This function is called when a request is received from a remote user that needs
|
||||
* to be approved by the admin interface.
|
||||
|
@ -1,9 +1,73 @@
|
||||
import NDK, { NDKNip46Backend, Nip46PermitCallback } from '@nostr-dev-kit/ndk';
|
||||
import PublishEventHandlingStrategy from './publish-event.js';
|
||||
import prisma from '../../db.js';
|
||||
|
||||
export class Backend extends NDKNip46Backend {
|
||||
constructor(ndk: NDK, key: string, cb: Nip46PermitCallback) {
|
||||
super(ndk, key, cb);
|
||||
|
||||
this.setStrategy('publish_event', new PublishEventHandlingStrategy());
|
||||
}
|
||||
|
||||
private async validateToken(token: string) {
|
||||
if (!token) throw new Error("Invalid token");
|
||||
|
||||
const tokenRecord = await prisma.token.findUnique({ where: {
|
||||
token
|
||||
}, include: { policy: { include: { rules: true } } } });
|
||||
|
||||
if (!tokenRecord) throw new Error("Token not found");
|
||||
if (tokenRecord.redeemedAt) throw new Error("Token already redeemed");
|
||||
if (!tokenRecord.policy) throw new Error("Policy not found");
|
||||
if (tokenRecord.expiresAt && tokenRecord.expiresAt < new Date()) throw new Error("Token expired");
|
||||
|
||||
return tokenRecord;
|
||||
}
|
||||
|
||||
async applyToken(userPubkey: string, token: string): Promise<void> {
|
||||
const tokenRecord = await this.validateToken(token);
|
||||
const keyName = tokenRecord.keyName;
|
||||
|
||||
// Upsert the KeyUser with the given remotePubkey
|
||||
const upsertedUser = await prisma.keyUser.upsert({
|
||||
where: { unique_key_user: { keyName, userPubkey } },
|
||||
update: { },
|
||||
create: { keyName, userPubkey, description: tokenRecord.clientName },
|
||||
});
|
||||
|
||||
await prisma.signingCondition.create({
|
||||
data: {
|
||||
keyUserId: upsertedUser.id,
|
||||
method: 'connect',
|
||||
allowed: true,
|
||||
}
|
||||
});
|
||||
|
||||
// Go through the rules of this policy and apply them to the user
|
||||
for (const rule of tokenRecord!.policy!.rules) {
|
||||
const signingConditionQuery: any = { method: rule.method };
|
||||
|
||||
if (rule && rule.kind) {
|
||||
signingConditionQuery.kind = rule.kind.toString();
|
||||
}
|
||||
|
||||
await prisma.signingCondition.create({
|
||||
data: {
|
||||
keyUserId: upsertedUser.id,
|
||||
method: rule.method,
|
||||
allowed: true,
|
||||
...signingConditionQuery,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await prisma.token.update({
|
||||
where: { id: tokenRecord.id },
|
||||
data: {
|
||||
redeemedAt: new Date(),
|
||||
keyUserId: upsertedUser.id,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -63,7 +63,7 @@ export function requestToSigningConditionQuery(method: string, event?: NDKEvent)
|
||||
|
||||
switch (method) {
|
||||
case 'sign_event':
|
||||
signingConditionQuery.kind = event?.kind?.toString();
|
||||
signingConditionQuery.kind = { in: [ event?.kind?.toString(), 'all' ] };
|
||||
break;
|
||||
}
|
||||
|
||||
@ -97,24 +97,16 @@ export async function allowAllRequestsFromKey(
|
||||
create: { keyName, userPubkey: remotePubkey, description },
|
||||
});
|
||||
|
||||
console.log({ upsertedUser });
|
||||
|
||||
// Create a new SigningCondition for the given KeyUser and set allowed to true
|
||||
const signingConditionQuery = allowScopeToSigningConditionQuery(method, allowScope);
|
||||
await prisma.signingCondition.create({
|
||||
data: {
|
||||
allowed: true,
|
||||
keyUserId: upsertedUser.id,
|
||||
...signingConditionQuery
|
||||
...signingConditionQuery,
|
||||
kind: 'all'
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`create`, {
|
||||
allowed: true,
|
||||
keyUserId: upsertedUser.id,
|
||||
...signingConditionQuery
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
console.log('allowAllRequestsFromKey', e);
|
||||
}
|
||||
@ -128,8 +120,6 @@ export async function rejectAllRequestsFromKey(remotePubkey: string, keyName: st
|
||||
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: {
|
||||
|
Loading…
x
Reference in New Issue
Block a user