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
|
# 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
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}")
|
}")
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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.config().then((config) => {
|
||||||
|
if (config.admin?.notifyAdminsOnBoot) {
|
||||||
this.notifyAdminsOfNewConnection(connectionString);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
requestAuthorization(
|
||||||
adminInterface,
|
adminInterface,
|
||||||
keyName,
|
keyName,
|
||||||
remotePubkey,
|
remotePubkey,
|
||||||
id,
|
id,
|
||||||
method,
|
method,
|
||||||
payload
|
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,19 +180,14 @@ 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);
|
||||||
@@ -263,9 +203,14 @@ class Daemon {
|
|||||||
const nsec = nip19.nsecEncode(settings.key);
|
const nsec = nip19.nsecEncode(settings.key);
|
||||||
await this.loadNsec(keyName, nsec);
|
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.');
|
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);
|
||||||
|
|||||||
Reference in New Issue
Block a user