small refactors

This commit is contained in:
Pablo Fernandez
2023-12-20 22:02:21 +00:00
parent 4dee186cf3
commit dc1e3d4aca
5 changed files with 59 additions and 113 deletions

View File

@@ -1,12 +1,10 @@
# OAuth-like flow # 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 asks for the user desired NIP-05.
* the client should ask 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. * 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. * 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** * 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("{ "content": nip04_encrypt("{
method: "create_account", method: "create_account",
params: [ params: [ "username", "domain", "email" ]
{
email: "<optional-email-to-identify-the-user>",
username: "<desired-username>",
domain: "<desired-nip05-domain>" // it should be available in this bunker
}
]
}") }")
} }
``` ```

View File

@@ -37,7 +37,8 @@ const defaultConfig: IConfig = {
adminRelays: [ adminRelays: [
"wss://relay.nsecbunker.com" "wss://relay.nsecbunker.com"
], ],
key: generatedKey.privateKey! key: generatedKey.privateKey!,
notifyAdminsOnBoot: true,
}, },
baseUrl: "https://nostr.me", baseUrl: "https://nostr.me",
database: 'sqlite://nsecbunker.db', database: 'sqlite://nsecbunker.db',
@@ -49,7 +50,13 @@ const defaultConfig: IConfig = {
async function getCurrentConfig(config: string): Promise<IConfig> { async function getCurrentConfig(config: string): Promise<IConfig> {
try { try {
const configFileContents = readFileSync(config, 'utf8'); 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) { } catch (err: any) {
if (err.code === 'ENOENT') { if (err.code === 'ENOENT') {
await saveCurrentConfig(config, defaultConfig); await saveCurrentConfig(config, defaultConfig);

View File

@@ -39,8 +39,8 @@ async function addNip05(currentConfig: IConfig, username: string, domain: string
const currentNip05s = await getCurrentNip05File(currentConfig, domain); const currentNip05s = await getCurrentNip05File(currentConfig, domain);
currentNip05s.names[username] = pubkey; currentNip05s.names[username] = pubkey;
currentNip05s.relays ??= {}; currentNip05s.relays ??= {};
currentNip05s.nip46Relays ??= {}; currentNip05s.nip46 ??= {};
currentNip05s.nip46Relays[username] = currentConfig.nostr.relays; currentNip05s.nip46[username] = currentConfig.nostr.relays;
// save file // save file
const nip05File = currentConfig.domains![domain].nip05; const nip05File = currentConfig.domains![domain].nip05;

View File

@@ -22,6 +22,7 @@ export type IAdminOpts = {
npubs: string[]; npubs: string[];
adminRelays: string[]; adminRelays: string[];
key: string; key: string;
notifyAdminsOnBoot?: boolean;
} }
// TODO: Move to configuration // TODO: Move to configuration
@@ -66,7 +67,11 @@ class AdminInterface {
this.connect(); 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")); this.rpc = new NDKNostrRpc(this.ndk, this.ndk.signer!, debug("ndk:rpc"));
@@ -118,7 +123,6 @@ class AdminInterface {
} }
private async handleRequest(req: NDKRpcRequest) { private async handleRequest(req: NDKRpcRequest) {
console.log(`request coming in`, req);
try { try {
await this.validateRequest(req); await this.validateRequest(req);
@@ -145,7 +149,7 @@ class AdminInterface {
); );
} }
} catch (err: any) { } 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); return this.rpc.sendResponse(req.id, req.pubkey, "error", NDKKind.NostrConnectAdmin, err?.message);
} }
} }

View File

@@ -14,7 +14,6 @@ import { decryptNsec } from '../config/keys.js';
import { requestAuthorization } from './authorize.js'; import { requestAuthorization } from './authorize.js';
import Fastify, { type FastifyInstance } from 'fastify'; import Fastify, { type FastifyInstance } from 'fastify';
import FastifyFormBody from "@fastify/formbody"; import FastifyFormBody from "@fastify/formbody";
// import FastifyNextjs from '@fastify/nextjs';
import FastifyView from '@fastify/view'; import FastifyView from '@fastify/view';
import Handlebars from "handlebars"; import Handlebars from "handlebars";
import {authorizeRequestWebHandler, processRequestWebHandler} from "./web/authorize.js"; 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 * Called by the NDKNip46Backend when an action requires authorization
* @param keyName -- Key attempting to be used * @param keyName -- Key attempting to be used
@@ -153,7 +101,6 @@ function getKeyUsers(config: IConfig) {
function signingAuthorizationCallback(keyName: string, adminInterface: AdminInterface): Nip46PermitCallback { function signingAuthorizationCallback(keyName: string, adminInterface: AdminInterface): Nip46PermitCallback {
return async (p: Nip46PermitCallbackParams): Promise<boolean> => { return async (p: Nip46PermitCallbackParams): Promise<boolean> => {
const { id, method, pubkey: remotePubkey, params: payload } = p; 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}`); console.log(`🔑 ${keyName} is being requested to ${method} by ${nip19.npubEncode(remotePubkey)}, request ${id}`);
if (!adminInterface.requestPermission) { if (!adminInterface.requestPermission) {
@@ -168,14 +115,18 @@ function signingAuthorizationCallback(keyName: string, adminInterface: AdminInte
return keyAllowed; return keyAllowed;
} }
return await requestAuthorization( return new Promise((resolve) => {
adminInterface, requestAuthorization(
keyName, adminInterface,
remotePubkey, keyName,
id, remotePubkey,
method, id,
payload method,
); payload
)
.then(() => resolve(true))
.catch(() => resolve(false));
});
} catch(e) { } catch(e) {
console.log('callbackForKey error:', e); console.log('callbackForKey error:', e);
} }
@@ -212,22 +163,16 @@ class Daemon {
this.ndk = new NDK({ this.ndk = new NDK({
explicitRelayUrls: config.nostr.relays, explicitRelayUrls: config.nostr.relays,
}); });
this.ndk.pool.on('relay:connect', (r) => { this.ndk.pool.on('relay:connect', (r) => console.log(`✅ Connected to ${r.url}`) );
if (r) { this.ndk.pool.on('relay:notice', (n, r) => { console.log(`👀 Notice from ${r.url}`, n); });
console.log(`✅ Connected to ${r.url}`);
} else {
console.log('✅ Connected to relays', this.ndk.pool.urls);
}
});
this.ndk.pool.on('notice', (n, r) => { console.log(`👀 Notice from ${r.url}`, n); });
this.ndk.pool.on('relay:disconnect', (r) => { this.ndk.pool.on('relay:disconnect', (r) => {
console.log(`🚫 Disconnected from ${r.url}`); console.log(`🚫 Disconnected from ${r.url}`);
}); });
} }
async start() { async startWebAuth() {
await this.ndk.connect(5000); if (!this.config.authPort) return;
this.fastify.register(FastifyView, { this.fastify.register(FastifyView, {
engine: { 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.listen({ port: this.config.authPort });
this.fastify.get('/requests/:id', authorizeRequestWebHandler); this.fastify.get('/requests/:id', authorizeRequestWebHandler);
this.fastify.post('/requests/:id', processRequestWebHandler); this.fastify.post('/requests/:id', processRequestWebHandler);
this.fastify.post('/register/:id', processRegistrationWebHandler); this.fastify.post('/register/:id', processRegistrationWebHandler);
}
setTimeout(async () => { async startKeys() {
console.log('🔑 Starting keys', Object.keys(this.config.keys)); console.log('🔑 Starting keys', Object.keys(this.config.keys));
for (const [name, nsec] of Object.entries(this.config.keys)) { for (const [name, nsec] of Object.entries(this.config.keys)) {
await this.startKey(name, nsec); 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 nsec = nip19.nsecEncode(settings.key);
const config = await this.adminInterface.config(); await this.loadNsec(keyName, nsec);
for (const [keyName, settings ] of Object.entries(config.keys)) { }
if (!settings.key) { }
continue;
}
const nsec = nip19.nsecEncode(settings.key); async start() {
await this.loadNsec(keyName, nsec); await this.ndk.connect(5000);
} await this.startWebAuth();
await this.startKeys();
console.log('✅ nsecBunker ready to serve requests.'); console.log('✅ nsecBunker ready to serve requests.');
}, 1000);
} }
/** /**
@@ -274,7 +219,6 @@ class Daemon {
* @param nsec NSec of the key * @param nsec NSec of the key
*/ */
async startKey(name: string, nsec: string) { async startKey(name: string, nsec: string) {
console.log(`starting key ${name}`);
const cb = signingAuthorizationCallback(name, this.adminInterface); const cb = signingAuthorizationCallback(name, this.adminInterface);
const hexpk = nip19.decode(nsec).data as string; const hexpk = nip19.decode(nsec).data as string;
const backend = new Backend(this.ndk, this.fastify, hexpk, cb, this.config.baseUrl); const backend = new Backend(this.ndk, this.fastify, hexpk, cb, this.config.baseUrl);
@@ -294,7 +238,6 @@ class Daemon {
} }
loadNsec(keyName: string, nsec: string) { loadNsec(keyName: string, nsec: string) {
console.log(`activating key ${keyName}`);
this.activeKeys[keyName] = nsec; this.activeKeys[keyName] = nsec;
this.startKey(keyName, nsec); this.startKey(keyName, nsec);