mirror of
https://github.com/aljazceru/nsecbunkerd.git
synced 2025-12-17 06:04:22 +01:00
small refactors
This commit is contained in:
@@ -1,12 +1,10 @@
|
||||
# OAuth-like flow
|
||||
|
||||
The OAuth-like flow is a way to create new users in the bunker.
|
||||
The OAuth-like flow is a way to create new users in the bunker remotely. This is an interesting flow since it allows users to create accounts without having to install any extension or having to deal with key management, while retaining the function of interoperability with other NIP-46-supporting clients.
|
||||
|
||||
The goal of this flow is to provide a flow that is familiar for new users that are not familiar with key management and that doesn't requrie installing extensions.
|
||||
The way it works is, a new user goes to a client that implements this flow, when they click 'register':
|
||||
|
||||
The way it works is, a new user without a nostr account goes to an client that implements this flow, when they click register the following happen:
|
||||
|
||||
* the client should ask for the user desired NIP-05.
|
||||
* the client asks for the user desired NIP-05.
|
||||
* to accomplish this, the client can hardcode using their own backend or they can use NIP-89 to find nsecbunker providers.
|
||||
* if using non-trusted (i.e. from NIP-89) the client should validate that the bunker's pubkey `kind:0` has a valid NIP-05 with the `_@domain` identifier.
|
||||
* the client generates a local key and stores it in the user's device. **This is the local key the client will use to sign on behalf of the user**
|
||||
@@ -15,13 +13,7 @@ The way it works is, a new user without a nostr account goes to an client that i
|
||||
{
|
||||
"content": nip04_encrypt("{
|
||||
method: "create_account",
|
||||
params: [
|
||||
{
|
||||
email: "<optional-email-to-identify-the-user>",
|
||||
username: "<desired-username>",
|
||||
domain: "<desired-nip05-domain>" // it should be available in this bunker
|
||||
}
|
||||
]
|
||||
params: [ "username", "domain", "email" ]
|
||||
}")
|
||||
}
|
||||
```
|
||||
|
||||
@@ -37,7 +37,8 @@ const defaultConfig: IConfig = {
|
||||
adminRelays: [
|
||||
"wss://relay.nsecbunker.com"
|
||||
],
|
||||
key: generatedKey.privateKey!
|
||||
key: generatedKey.privateKey!,
|
||||
notifyAdminsOnBoot: true,
|
||||
},
|
||||
baseUrl: "https://nostr.me",
|
||||
database: 'sqlite://nsecbunker.db',
|
||||
@@ -49,7 +50,13 @@ const defaultConfig: IConfig = {
|
||||
async function getCurrentConfig(config: string): Promise<IConfig> {
|
||||
try {
|
||||
const configFileContents = readFileSync(config, 'utf8');
|
||||
return JSON.parse(configFileContents);
|
||||
|
||||
// add new config options to the config file
|
||||
const currentConfig = JSON.parse(configFileContents);
|
||||
currentConfig.version = version;
|
||||
currentConfig.admin.notifyAdminsOnBoot ??= true;
|
||||
|
||||
return currentConfig;
|
||||
} catch (err: any) {
|
||||
if (err.code === 'ENOENT') {
|
||||
await saveCurrentConfig(config, defaultConfig);
|
||||
|
||||
@@ -39,8 +39,8 @@ async function addNip05(currentConfig: IConfig, username: string, domain: string
|
||||
const currentNip05s = await getCurrentNip05File(currentConfig, domain);
|
||||
currentNip05s.names[username] = pubkey;
|
||||
currentNip05s.relays ??= {};
|
||||
currentNip05s.nip46Relays ??= {};
|
||||
currentNip05s.nip46Relays[username] = currentConfig.nostr.relays;
|
||||
currentNip05s.nip46 ??= {};
|
||||
currentNip05s.nip46[username] = currentConfig.nostr.relays;
|
||||
|
||||
// save file
|
||||
const nip05File = currentConfig.domains![domain].nip05;
|
||||
|
||||
@@ -22,6 +22,7 @@ export type IAdminOpts = {
|
||||
npubs: string[];
|
||||
adminRelays: string[];
|
||||
key: string;
|
||||
notifyAdminsOnBoot?: boolean;
|
||||
}
|
||||
|
||||
// TODO: Move to configuration
|
||||
@@ -66,7 +67,11 @@ class AdminInterface {
|
||||
|
||||
this.connect();
|
||||
|
||||
this.notifyAdminsOfNewConnection(connectionString);
|
||||
this.config().then((config) => {
|
||||
if (config.admin?.notifyAdminsOnBoot) {
|
||||
this.notifyAdminsOfNewConnection(connectionString);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this.rpc = new NDKNostrRpc(this.ndk, this.ndk.signer!, debug("ndk:rpc"));
|
||||
@@ -118,7 +123,6 @@ class AdminInterface {
|
||||
}
|
||||
|
||||
private async handleRequest(req: NDKRpcRequest) {
|
||||
console.log(`request coming in`, req);
|
||||
try {
|
||||
await this.validateRequest(req);
|
||||
|
||||
@@ -145,7 +149,7 @@ class AdminInterface {
|
||||
);
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error(`Error handling request ${req.method}: ${err.message}`, req.params);
|
||||
console.error(`Error handling request ${req.method}: ${err?.message??err}`, req.params);
|
||||
return this.rpc.sendResponse(req.id, req.pubkey, "error", NDKKind.NostrConnectAdmin, err?.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ import { decryptNsec } from '../config/keys.js';
|
||||
import { requestAuthorization } from './authorize.js';
|
||||
import Fastify, { type FastifyInstance } from 'fastify';
|
||||
import FastifyFormBody from "@fastify/formbody";
|
||||
// import FastifyNextjs from '@fastify/nextjs';
|
||||
import FastifyView from '@fastify/view';
|
||||
import Handlebars from "handlebars";
|
||||
import {authorizeRequestWebHandler, processRequestWebHandler} from "./web/authorize.js";
|
||||
@@ -93,57 +92,6 @@ function getKeyUsers(config: IConfig) {
|
||||
};
|
||||
}
|
||||
|
||||
// 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) => {
|
||||
// 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);
|
||||
// },
|
||||
// response: () => {
|
||||
// requestPermissionMutex = false;
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
|
||||
// return promise;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Called by the NDKNip46Backend when an action requires authorization
|
||||
* @param keyName -- Key attempting to be used
|
||||
@@ -153,7 +101,6 @@ function getKeyUsers(config: IConfig) {
|
||||
function signingAuthorizationCallback(keyName: string, adminInterface: AdminInterface): Nip46PermitCallback {
|
||||
return async (p: Nip46PermitCallbackParams): Promise<boolean> => {
|
||||
const { id, method, pubkey: remotePubkey, params: payload } = p;
|
||||
console.trace(`received call with`, {id, remotePubkey, method, payload, p});
|
||||
console.log(`🔑 ${keyName} is being requested to ${method} by ${nip19.npubEncode(remotePubkey)}, request ${id}`);
|
||||
|
||||
if (!adminInterface.requestPermission) {
|
||||
@@ -168,14 +115,18 @@ function signingAuthorizationCallback(keyName: string, adminInterface: AdminInte
|
||||
return keyAllowed;
|
||||
}
|
||||
|
||||
return await requestAuthorization(
|
||||
adminInterface,
|
||||
keyName,
|
||||
remotePubkey,
|
||||
id,
|
||||
method,
|
||||
payload
|
||||
);
|
||||
return new Promise((resolve) => {
|
||||
requestAuthorization(
|
||||
adminInterface,
|
||||
keyName,
|
||||
remotePubkey,
|
||||
id,
|
||||
method,
|
||||
payload
|
||||
)
|
||||
.then(() => resolve(true))
|
||||
.catch(() => resolve(false));
|
||||
});
|
||||
} catch(e) {
|
||||
console.log('callbackForKey error:', e);
|
||||
}
|
||||
@@ -212,22 +163,16 @@ class Daemon {
|
||||
this.ndk = new NDK({
|
||||
explicitRelayUrls: config.nostr.relays,
|
||||
});
|
||||
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:connect', (r) => console.log(`✅ Connected to ${r.url}`) );
|
||||
this.ndk.pool.on('relay:notice', (n, r) => { console.log(`👀 Notice from ${r.url}`, n); });
|
||||
|
||||
this.ndk.pool.on('relay:disconnect', (r) => {
|
||||
console.log(`🚫 Disconnected from ${r.url}`);
|
||||
});
|
||||
}
|
||||
|
||||
async start() {
|
||||
await this.ndk.connect(5000);
|
||||
async startWebAuth() {
|
||||
if (!this.config.authPort) return;
|
||||
|
||||
this.fastify.register(FastifyView, {
|
||||
engine: {
|
||||
@@ -235,37 +180,37 @@ class Daemon {
|
||||
}
|
||||
});
|
||||
|
||||
// this.fastify
|
||||
// .register(FastifyNextjs)
|
||||
// .after(() => {
|
||||
// this.fastify.next('/hello');
|
||||
// });
|
||||
|
||||
this.fastify.listen({ port: this.config.authPort });
|
||||
|
||||
this.fastify.get('/requests/:id', authorizeRequestWebHandler);
|
||||
this.fastify.post('/requests/:id', processRequestWebHandler);
|
||||
this.fastify.post('/register/:id', processRegistrationWebHandler);
|
||||
}
|
||||
|
||||
setTimeout(async () => {
|
||||
console.log('🔑 Starting keys', Object.keys(this.config.keys));
|
||||
for (const [name, nsec] of Object.entries(this.config.keys)) {
|
||||
await this.startKey(name, nsec);
|
||||
async startKeys() {
|
||||
console.log('🔑 Starting keys', Object.keys(this.config.keys));
|
||||
for (const [name, nsec] of Object.entries(this.config.keys)) {
|
||||
await this.startKey(name, nsec);
|
||||
}
|
||||
|
||||
// Load unencrypted keys
|
||||
const config = await this.adminInterface.config();
|
||||
for (const [keyName, settings ] of Object.entries(config.keys)) {
|
||||
if (!settings.key) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Load unencrypted keys
|
||||
const config = await this.adminInterface.config();
|
||||
for (const [keyName, settings ] of Object.entries(config.keys)) {
|
||||
if (!settings.key) {
|
||||
continue;
|
||||
}
|
||||
const nsec = nip19.nsecEncode(settings.key);
|
||||
await this.loadNsec(keyName, nsec);
|
||||
}
|
||||
}
|
||||
|
||||
const nsec = nip19.nsecEncode(settings.key);
|
||||
await this.loadNsec(keyName, nsec);
|
||||
}
|
||||
async start() {
|
||||
await this.ndk.connect(5000);
|
||||
await this.startWebAuth();
|
||||
await this.startKeys();
|
||||
|
||||
console.log('✅ nsecBunker ready to serve requests.');
|
||||
}, 1000);
|
||||
console.log('✅ nsecBunker ready to serve requests.');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -274,7 +219,6 @@ class Daemon {
|
||||
* @param nsec NSec of the key
|
||||
*/
|
||||
async startKey(name: string, nsec: string) {
|
||||
console.log(`starting key ${name}`);
|
||||
const cb = signingAuthorizationCallback(name, this.adminInterface);
|
||||
const hexpk = nip19.decode(nsec).data as string;
|
||||
const backend = new Backend(this.ndk, this.fastify, hexpk, cb, this.config.baseUrl);
|
||||
@@ -294,7 +238,6 @@ class Daemon {
|
||||
}
|
||||
|
||||
loadNsec(keyName: string, nsec: string) {
|
||||
console.log(`activating key ${keyName}`);
|
||||
this.activeKeys[keyName] = nsec;
|
||||
|
||||
this.startKey(keyName, nsec);
|
||||
|
||||
Reference in New Issue
Block a user