Compare commits
No commits in common. "d802ab0bb5dacdfa8e8056366a503d3a9fb39537" and "1ffa75dcd41ddbc493e6b2b742ce4113f93a56a4" have entirely different histories.
d802ab0bb5
...
1ffa75dcd4
11 changed files with 176 additions and 208 deletions
|
|
@ -3,15 +3,9 @@ import { DomainConfig, IConfig, getCurrentConfig, saveCurrentConfig } from '../c
|
||||||
import { decryptNsec } from '../config/keys.js';
|
import { decryptNsec } from '../config/keys.js';
|
||||||
import { fork } from 'child_process';
|
import { fork } from 'child_process';
|
||||||
import { resolve } from 'path';
|
import { resolve } from 'path';
|
||||||
import { finalizeEvent, getPublicKey, nip05, nip19 } from 'nostr-tools';
|
import NDK, { NDKAppHandlerEvent, NDKKind, NDKPrivateKeySigner, NDKUser, NostrEvent } from '@nostr-dev-kit/ndk';
|
||||||
import { RelayPool } from '../daemon/lib/relay-pool.js';
|
|
||||||
import { secretKeyBytes } from '../daemon/nip46/transport.js';
|
|
||||||
import { debug } from 'console';
|
import { debug } from 'console';
|
||||||
|
|
||||||
// NIP-89 application-handler event kind, and the handled NIP-46 client kind.
|
|
||||||
const APP_HANDLER_KIND = 31990;
|
|
||||||
const NOSTR_CONNECT_KIND_STR = '24133';
|
|
||||||
|
|
||||||
interface IOpts {
|
interface IOpts {
|
||||||
keys: string[];
|
keys: string[];
|
||||||
verbose: boolean;
|
verbose: boolean;
|
||||||
|
|
@ -22,26 +16,29 @@ interface IOpts {
|
||||||
async function nip89announcement(configData: IConfig) {
|
async function nip89announcement(configData: IConfig) {
|
||||||
const domains = configData.domains as Record<string, DomainConfig>;
|
const domains = configData.domains as Record<string, DomainConfig>;
|
||||||
if (!domains) return;
|
if (!domains) return;
|
||||||
|
|
||||||
const sk = secretKeyBytes(configData.admin.key);
|
|
||||||
const signerPubkey = getPublicKey(sk);
|
|
||||||
|
|
||||||
for (const [ domain, config ] of Object.entries(domains)) {
|
for (const [ domain, config ] of Object.entries(domains)) {
|
||||||
if (!config.nip89) continue;
|
const hasNip89 = !!config.nip89;
|
||||||
|
if (!hasNip89) continue;
|
||||||
|
|
||||||
const profile = config.nip89.profile;
|
const signer = new NDKPrivateKeySigner(configData.admin.key);
|
||||||
const relays = config.nip89.relays;
|
const signerUser = await signer.user();
|
||||||
const nip05addr = `_@${domain}`;
|
|
||||||
|
const profile = config.nip89!.profile;
|
||||||
|
const relays = config.nip89!.relays;
|
||||||
|
const nip05 = `_@${domain}`;
|
||||||
|
|
||||||
|
const ndk = new NDK({explicitRelayUrls: relays});
|
||||||
|
|
||||||
// make sure the nip05 correctly points to this pubkey
|
// make sure the nip05 correctly points to this pubkey
|
||||||
const resolved = await nip05.queryProfile(nip05addr).catch(() => null);
|
const uservianip05 = await NDKUser.fromNip05(nip05, ndk);
|
||||||
if (!resolved || resolved.pubkey !== signerPubkey) {
|
if (!uservianip05 || uservianip05.pubkey !== signerUser.pubkey) {
|
||||||
console.log(`❌ ${nip05addr} does not point to this nsecbunker's key`);
|
console.log(`❌ ${nip05} does not point to this nsecbunker's key`);
|
||||||
if (resolved) {
|
if (uservianip05) {
|
||||||
console.log(`${nip05addr} points to ${resolved.pubkey} instead of ${signerPubkey}`)
|
console.log(`${nip05} points to ${uservianip05.pubkey} instead of ${signerUser.pubkey}`)
|
||||||
} else {
|
} else {
|
||||||
console.log(`${nip05addr} needs to point to ${signerPubkey}`)
|
console.log(`${nip05} needs to point to ${signerUser.pubkey}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -58,47 +55,52 @@ async function nip89announcement(configData: IConfig) {
|
||||||
const hasWallet = !!config.wallet;
|
const hasWallet = !!config.wallet;
|
||||||
const hasNostrdress = !!config.wallet?.lnbits?.nostdressUrl;
|
const hasNostrdress = !!config.wallet?.lnbits?.nostdressUrl;
|
||||||
|
|
||||||
// kind:31990 (NIP-89) is addressable by (kind, author, d-tag); this code
|
ndk.signer = signer;
|
||||||
// always uses the default d-tag "24133", so publishing replaces the prior
|
ndk.connect(5000).then(async () => {
|
||||||
// announcement — the same effect the old fetch-existing-d-tag dance had.
|
const event = new NDKAppHandlerEvent(ndk, {
|
||||||
const tags: string[][] = [
|
tags: [
|
||||||
["alt", "This is an nsecBunker announcement"],
|
[ "alt", "This is an nsecBunker announcement" ]
|
||||||
["d", NOSTR_CONNECT_KIND_STR],
|
]
|
||||||
["k", NOSTR_CONNECT_KIND_STR],
|
} as NostrEvent);
|
||||||
];
|
|
||||||
const operator = config.nip89.operator;
|
const operator = config.nip89!.operator;
|
||||||
if (operator) {
|
if (operator) {
|
||||||
|
try {
|
||||||
|
const opUser = new NDKUser({npub: operator});
|
||||||
|
event.tags.push(["p", opUser.pubkey]);
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
tags.push(["p", nip19.decode(operator).data as string]);
|
const user = await ndk.signer!.user();
|
||||||
} catch { /* ignore a bad operator npub */ }
|
const existingEvent = await ndk.fetchEvent({
|
||||||
}
|
authors: [user.pubkey],
|
||||||
if (hasWallet && hasNostrdress) {
|
kinds: [NDKKind.AppHandler],
|
||||||
tags.push(["f", "wallet"]);
|
"#k": [NDKKind.NostrConnect.toString()]
|
||||||
tags.push(["f", "zaps"]);
|
});
|
||||||
}
|
|
||||||
|
|
||||||
profile.nip05 = nip05addr;
|
if (existingEvent) {
|
||||||
const event = finalizeEvent(
|
debug(`🔍 Found existing NIP-89 announcement for ${domain}:`, existingEvent.encode());
|
||||||
{
|
// update existing event
|
||||||
kind: APP_HANDLER_KIND,
|
const dTag = existingEvent.tagValue("d");
|
||||||
created_at: Math.floor(Date.now() / 1000),
|
event.tags.push(["d", dTag!])
|
||||||
tags,
|
} else {
|
||||||
content: JSON.stringify(profile),
|
debug(`🔍 No existing NIP-89 announcement for ${domain} found.`);
|
||||||
},
|
event.tags.push(["d", NDKKind.NostrConnect.toString()]);
|
||||||
sk,
|
}
|
||||||
);
|
|
||||||
|
|
||||||
const pool = new RelayPool(relays, {});
|
profile.nip05 = nip05;
|
||||||
pool.start();
|
event.content = JSON.stringify(profile);
|
||||||
await new Promise((r) => setTimeout(r, 3000));
|
event.tags.push(["k", NDKKind.NostrConnect.toString()])
|
||||||
try {
|
if (hasWallet && hasNostrdress) {
|
||||||
await pool.publish(event);
|
// add wallet and zaps feature tags
|
||||||
debug(`✅ Published NIP-89 announcement for ${domain}`);
|
event.tags.push(["f", "wallet"]);
|
||||||
} catch (e: any) {
|
event.tags.push(["f", "zaps"]);
|
||||||
console.log(`❌ Failed to publish NIP-89 announcement for ${domain}!`, e.message);
|
}
|
||||||
} finally {
|
await event.publish();
|
||||||
pool.stop();
|
debug(`✅ Published NIP-89 announcement for ${domain}:`, event.encode());
|
||||||
}
|
} catch(e: any) { console.log(`❌ Failed to publish NIP-89 announcement for ${domain}!`, e.message); }
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { readFileSync, writeFileSync } from 'fs';
|
import { readFileSync, writeFileSync } from 'fs';
|
||||||
import { generateSecretKey } from 'nostr-tools';
|
import { NDKPrivateKeySigner, NDKUserProfile } from '@nostr-dev-kit/ndk';
|
||||||
import { IAdminOpts } from '../daemon/admin';
|
import { IAdminOpts } from '../daemon/admin';
|
||||||
|
|
||||||
import { version } from '../../package.json';
|
import { version } from '../../package.json';
|
||||||
|
|
||||||
const generatedKeyHex = Buffer.from(generateSecretKey()).toString('hex');
|
const generatedKey = NDKPrivateKeySigner.generate();
|
||||||
|
|
||||||
export type LNBitsWalletConfig = {
|
export type LNBitsWalletConfig = {
|
||||||
url: string,
|
url: string,
|
||||||
|
|
@ -54,7 +54,7 @@ const defaultConfig: IConfig = {
|
||||||
adminRelays: [
|
adminRelays: [
|
||||||
"wss://relay.nsecbunker.com"
|
"wss://relay.nsecbunker.com"
|
||||||
],
|
],
|
||||||
key: generatedKeyHex,
|
key: generatedKey.privateKey!,
|
||||||
notifyAdminsOnBoot: true,
|
notifyAdminsOnBoot: true,
|
||||||
},
|
},
|
||||||
database: 'sqlite://nsecbunker.db',
|
database: 'sqlite://nsecbunker.db',
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
|
import { Hexpubkey, NDKPrivateKeySigner, NDKUserProfile } from "@nostr-dev-kit/ndk";
|
||||||
import { AdminRpcRequest } from "../types.js";
|
import { AdminRpcRequest } from "../types.js";
|
||||||
import AdminInterface from "..";
|
import AdminInterface from "..";
|
||||||
import { nip19, generateSecretKey, getPublicKey } from 'nostr-tools';
|
import { nip19 } from 'nostr-tools';
|
||||||
import type { SkeletonProfile } from "../../lib/profile.js";
|
|
||||||
type Hexpubkey = string;
|
|
||||||
import { setupSkeletonProfile } from "../../lib/profile";
|
import { setupSkeletonProfile } from "../../lib/profile";
|
||||||
import { IConfig, getCurrentConfig, saveCurrentConfig } from "../../../config";
|
import { IConfig, getCurrentConfig, saveCurrentConfig } from "../../../config";
|
||||||
import { readFileSync, writeFileSync } from "fs";
|
import { readFileSync, writeFileSync } from "fs";
|
||||||
|
|
@ -162,20 +161,20 @@ export async function createAccountReal(
|
||||||
await validate(currentConfig, username, domain, email);
|
await validate(currentConfig, username, domain, email);
|
||||||
|
|
||||||
const nip05 = `${username}@${domain}`;
|
const nip05 = `${username}@${domain}`;
|
||||||
const sk = generateSecretKey();
|
const key = NDKPrivateKeySigner.generate();
|
||||||
const pubkey = getPublicKey(sk);
|
const profile: NDKUserProfile = {
|
||||||
const npub = nip19.npubEncode(pubkey);
|
|
||||||
const profile: SkeletonProfile = {
|
|
||||||
display_name: username,
|
display_name: username,
|
||||||
name: username,
|
name: username,
|
||||||
nip05,
|
nip05,
|
||||||
...(domainConfig.defaultProfile || {})
|
...(domainConfig.defaultProfile || {})
|
||||||
};
|
};
|
||||||
|
|
||||||
debug(`Created user ${npub} for ${nip05}`);
|
const generatedUser = await key.user();
|
||||||
|
|
||||||
|
debug(`Created user ${generatedUser.npub} for ${nip05}`);
|
||||||
|
|
||||||
// Add NIP-05
|
// Add NIP-05
|
||||||
await addNip05(currentConfig, username, domain, pubkey);
|
await addNip05(currentConfig, username, domain, generatedUser.pubkey);
|
||||||
|
|
||||||
debug(`Added NIP-05 for ${nip05}`);
|
debug(`Added NIP-05 for ${nip05}`);
|
||||||
|
|
||||||
|
|
@ -183,7 +182,7 @@ export async function createAccountReal(
|
||||||
if (domainConfig.wallet) {
|
if (domainConfig.wallet) {
|
||||||
generateWallet(
|
generateWallet(
|
||||||
domainConfig.wallet,
|
domainConfig.wallet,
|
||||||
username, domain, npub
|
username, domain, generatedUser.npub
|
||||||
).then((lnaddress) => {
|
).then((lnaddress) => {
|
||||||
debug(`wallet for ${nip05}`, {lnaddress});
|
debug(`wallet for ${nip05}`, {lnaddress});
|
||||||
if (lnaddress) profile.lud16 = lnaddress;
|
if (lnaddress) profile.lud16 = lnaddress;
|
||||||
|
|
@ -191,23 +190,23 @@ export async function createAccountReal(
|
||||||
debug(`error generating wallet for ${nip05}`, e);
|
debug(`error generating wallet for ${nip05}`, e);
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
debug(`saving profile for ${nip05}`, profile);
|
debug(`saving profile for ${nip05}`, profile);
|
||||||
setupSkeletonProfile(sk, profile, email);
|
setupSkeletonProfile(key, profile, email);
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
debug(`no wallet configuration for ${domain}`);
|
debug(`no wallet configuration for ${domain}`);
|
||||||
// Create user profile
|
// Create user profile
|
||||||
setupSkeletonProfile(sk, profile, email);
|
setupSkeletonProfile(key, profile, email);
|
||||||
}
|
}
|
||||||
|
|
||||||
const keyName = nip05;
|
const keyName = nip05;
|
||||||
const nsec = nip19.nsecEncode(sk);
|
const nsec = key.nsec;
|
||||||
currentConfig.keys[keyName] = { key: Buffer.from(sk).toString('hex') };
|
currentConfig.keys[keyName] = { key: key.privateKey };
|
||||||
|
|
||||||
saveCurrentConfig(admin.configFile, currentConfig);
|
saveCurrentConfig(admin.configFile, currentConfig);
|
||||||
|
|
||||||
await admin.loadNsec!(keyName, nsec);
|
await admin.loadNsec!(keyName, nsec);
|
||||||
|
|
||||||
await prisma.key.create({ data: { keyName, pubkey } });
|
await prisma.key.create({ data: { keyName, pubkey: generatedUser.pubkey } });
|
||||||
|
|
||||||
// Immediately grant access to the creator key
|
// Immediately grant access to the creator key
|
||||||
// This means that the client creating this account can immediately
|
// This means that the client creating this account can immediately
|
||||||
|
|
@ -220,8 +219,8 @@ export async function createAccountReal(
|
||||||
// request's kind so the response goes back on the same channel the
|
// request's kind so the response goes back on the same channel the
|
||||||
// client subscribed for. Filed as part of aiolabs/nsecbunkerd#7
|
// client subscribed for. Filed as part of aiolabs/nsecbunkerd#7
|
||||||
// diagnosis 2026-05-27.
|
// diagnosis 2026-05-27.
|
||||||
const originalKind = req.event.kind;
|
const originalKind = req.event.kind!;
|
||||||
return admin.rpc.sendResponse(req.id, req.pubkey, pubkey, originalKind);
|
return admin.rpc.sendResponse(req.id, req.pubkey, generatedUser.pubkey, originalKind);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.trace('error', e);
|
console.trace('error', e);
|
||||||
const originalKind = req.event.kind!;
|
const originalKind = req.event.kind!;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { generateSecretKey, getPublicKey, nip19 } from "nostr-tools";
|
import NDK, { NDKEvent, NDKPrivateKeySigner, type NostrEvent } from "@nostr-dev-kit/ndk";
|
||||||
import { AdminRpcRequest } from "../types.js";
|
import { AdminRpcRequest } from "../types.js";
|
||||||
import AdminInterface from "../index.js";
|
import AdminInterface from "../index.js";
|
||||||
import { NIP46_ADMIN_RESPONSE_KIND } from "../kinds.js";
|
import { NIP46_ADMIN_RESPONSE_KIND } from "../kinds.js";
|
||||||
|
|
@ -6,7 +6,6 @@ import { saveEncrypted } from "../../../commands/add.js";
|
||||||
import { getCurrentConfig } from "../../../config/index.js";
|
import { getCurrentConfig } from "../../../config/index.js";
|
||||||
import { decryptNsec } from "../../../config/keys.js";
|
import { decryptNsec } from "../../../config/keys.js";
|
||||||
import { setupSkeletonProfile } from "../../lib/profile.js";
|
import { setupSkeletonProfile } from "../../lib/profile.js";
|
||||||
import { secretKeyBytes } from "../../nip46/transport.js";
|
|
||||||
|
|
||||||
export default async function createNewKey(admin: AdminInterface, req: AdminRpcRequest) {
|
export default async function createNewKey(admin: AdminInterface, req: AdminRpcRequest) {
|
||||||
const [ keyName, passphrase, _nsec ] = req.params as [ string, string, string? ];
|
const [ keyName, passphrase, _nsec ] = req.params as [ string, string, string? ];
|
||||||
|
|
@ -45,26 +44,27 @@ export default async function createNewKey(admin: AdminInterface, req: AdminRpcR
|
||||||
`decrypt it; refusing to overwrite (${e.message})`,
|
`decrypt it; refusing to overwrite (${e.message})`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const existingNpub = nip19.npubEncode(getPublicKey(secretKeyBytes(existingNsec)));
|
const existingUser = await new NDKPrivateKeySigner(existingNsec).user();
|
||||||
const result = JSON.stringify({ npub: existingNpub });
|
const result = JSON.stringify({ npub: existingUser.npub });
|
||||||
return admin.rpc.sendResponse(req.id, req.pubkey, result, NIP46_ADMIN_RESPONSE_KIND);
|
return admin.rpc.sendResponse(req.id, req.pubkey, result, NIP46_ADMIN_RESPONSE_KIND);
|
||||||
}
|
}
|
||||||
|
|
||||||
let sk: Uint8Array;
|
let key;
|
||||||
|
|
||||||
if (_nsec) {
|
if (_nsec) {
|
||||||
// secretKeyBytes accepts nsec1 or hex directly.
|
// NDK 3.x's `NDKPrivateKeySigner` accepts nsec1 or hex directly
|
||||||
sk = secretKeyBytes(_nsec);
|
// (see core/src/signers/private-key/index.ts `@ai-guardrail`).
|
||||||
|
key = new NDKPrivateKeySigner(_nsec);
|
||||||
} else {
|
} else {
|
||||||
sk = generateSecretKey();
|
key = NDKPrivateKeySigner.generate();
|
||||||
|
|
||||||
setupSkeletonProfile(sk);
|
setupSkeletonProfile(key);
|
||||||
|
|
||||||
console.log(`setting up skeleton profile for ${keyName}`);
|
console.log(`setting up skeleton profile for ${keyName}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const npub = nip19.npubEncode(getPublicKey(sk));
|
const user = await key.user();
|
||||||
const nsec = nip19.nsecEncode(sk);
|
const nsec = key.nsec;
|
||||||
|
|
||||||
await saveEncrypted(
|
await saveEncrypted(
|
||||||
admin.configFile,
|
admin.configFile,
|
||||||
|
|
@ -76,7 +76,7 @@ export default async function createNewKey(admin: AdminInterface, req: AdminRpcR
|
||||||
await admin.loadNsec(keyName, nsec);
|
await admin.loadNsec(keyName, nsec);
|
||||||
|
|
||||||
const result = JSON.stringify({
|
const result = JSON.stringify({
|
||||||
npub,
|
npub: user.npub,
|
||||||
});
|
});
|
||||||
|
|
||||||
return admin.rpc.sendResponse(req.id, req.pubkey, result, NIP46_ADMIN_RESPONSE_KIND);
|
return admin.rpc.sendResponse(req.id, req.pubkey, result, NIP46_ADMIN_RESPONSE_KIND);
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import NDK, { NDKPrivateKeySigner } from '@nostr-dev-kit/ndk';
|
||||||
import { getPublicKey, nip19 } from 'nostr-tools';
|
import { getPublicKey, nip19 } from 'nostr-tools';
|
||||||
import createDebug from 'debug';
|
import createDebug from 'debug';
|
||||||
import { Key, KeyUser } from '../run';
|
import { Key, KeyUser } from '../run';
|
||||||
|
|
@ -121,19 +122,19 @@ class AdminInterface {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Boot-time DM to the admin npubs. One-shot, best-effort notification over
|
* Boot-time DM to the admin npubs. One-shot, best-effort notification over
|
||||||
* public relays via a throwaway RelayPool (aiolabs/nsecbunkerd#44).
|
* public relays — not part of the reconnect-sensitive RPC path, so it still
|
||||||
|
* uses a throwaway NDK + the existing dmUser helper. (#42 leaves this on NDK.)
|
||||||
*/
|
*/
|
||||||
private async notifyAdminsOfNewConnection(connectionString: string) {
|
private async notifyAdminsOfNewConnection(connectionString: string) {
|
||||||
const sk = secretKeyBytes(this.adminNsec);
|
const blastrNdk = new NDK({
|
||||||
const pool = new RelayPool(['wss://blastr.f7z.xyz', 'wss://nostr.mutinywallet.com'], {});
|
explicitRelayUrls: ['wss://blastr.f7z.xyz', 'wss://nostr.mutinywallet.com'],
|
||||||
pool.start();
|
signer: new NDKPrivateKeySigner(this.adminNsec),
|
||||||
// Give the connections a moment to come up before publishing.
|
});
|
||||||
await new Promise((r) => setTimeout(r, 2500));
|
await blastrNdk.connect(2500);
|
||||||
|
|
||||||
for (const npub of this.npubs || []) {
|
for (const npub of this.npubs || []) {
|
||||||
await dmUser(sk, npub, `nsecBunker has started; use ${connectionString} to connect to it and unlock your key(s)`, pool);
|
dmUser(blastrNdk, npub, `nsecBunker has started; use ${connectionString} to connect to it and unlock your key(s)`);
|
||||||
}
|
}
|
||||||
pool.stop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import type { Event as NDKEvent } from "nostr-tools";
|
import { Hexpubkey, NDKEvent, NostrEvent } from "@nostr-dev-kit/ndk";
|
||||||
type Hexpubkey = string;
|
|
||||||
import type { Backend } from "./backend";
|
import type { Backend } from "./backend";
|
||||||
import prisma from "../db";
|
import prisma from "../db";
|
||||||
import type { Request } from "@prisma/client";
|
import type { Request } from "@prisma/client";
|
||||||
|
|
@ -60,11 +59,8 @@ async function createRecord(
|
||||||
) {
|
) {
|
||||||
let params: string | undefined;
|
let params: string | undefined;
|
||||||
|
|
||||||
// `param` for sign_event is the parsed event object the signer passed to the
|
if (typeof param === 'object' && param !== null && 'rawEvent' in param) {
|
||||||
// ACL (a plain event since #43, no longer an NDKEvent with .rawEvent());
|
params = JSON.stringify(param.rawEvent());
|
||||||
// for the encrypt/decrypt methods it's the payload string.
|
|
||||||
if (typeof param === 'object' && param !== null) {
|
|
||||||
params = JSON.stringify(param);
|
|
||||||
} else if (param) {
|
} else if (param) {
|
||||||
params = param.toString();
|
params = param.toString();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import type { Event as NostrEvent } from 'nostr-tools';
|
import type { NostrEvent, NIP46Method } from '@nostr-dev-kit/ndk';
|
||||||
import type { NIP46Method } from '../../nip46/types.js';
|
|
||||||
import prisma from '../../../db.js';
|
import prisma from '../../../db.js';
|
||||||
import { liveWhere } from './lifecycle.js';
|
import { liveWhere } from './lifecycle.js';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { finalizeEvent, getPublicKey } from "nostr-tools";
|
import NDK, { NDKEvent, NDKPrivateKeySigner, NostrEvent, type NDKUserProfile } from "@nostr-dev-kit/ndk";
|
||||||
import * as CryptoJS from 'crypto-js';
|
import * as CryptoJS from 'crypto-js';
|
||||||
import createDebug from "debug";
|
import createDebug from "debug";
|
||||||
import { RelayPool } from "./relay-pool.js";
|
|
||||||
|
|
||||||
const debug = createDebug("nsecbunker:profile");
|
const debug = createDebug("nsecbunker:profile");
|
||||||
|
|
||||||
|
|
@ -13,27 +12,14 @@ const explicitRelayUrls = [
|
||||||
"wss://nostr.mutinywallet.com"
|
"wss://nostr.mutinywallet.com"
|
||||||
];
|
];
|
||||||
|
|
||||||
/** The handful of profile (kind:0) fields the skeleton sets. */
|
|
||||||
export interface SkeletonProfile {
|
|
||||||
display_name?: string;
|
|
||||||
about?: string;
|
|
||||||
website?: string;
|
|
||||||
image?: string;
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
[k: string]: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setup a skeleton profile for a new key since the experience of a completely
|
* Setup a skeleton profile for a new key since
|
||||||
* empty profile is pretty bad when logging in with Coracle.
|
* the experience of a completely empty profile
|
||||||
|
* is pretty bad when logging in with Coracle.
|
||||||
*
|
*
|
||||||
* Ported off NDK onto nostr-tools + a throwaway RelayPool (aiolabs/nsecbunkerd#44).
|
|
||||||
* Fire-and-forget, like before: the caller doesn't await it.
|
|
||||||
*
|
|
||||||
* @param sk - the new key's secret bytes
|
|
||||||
* @param email - if provided, will fetch the gravatar
|
* @param email - if provided, will fetch the gravatar
|
||||||
*/
|
*/
|
||||||
export async function setupSkeletonProfile(sk: Uint8Array, profile?: SkeletonProfile, email?: string) {
|
export async function setupSkeletonProfile(key: NDKPrivateKeySigner, profile?: NDKUserProfile, email?: string) {
|
||||||
const rand = Math.random().toString(36).substring(7);
|
const rand = Math.random().toString(36).substring(7);
|
||||||
profile ??= {};
|
profile ??= {};
|
||||||
profile.display_name ??= 'New User via nsecBunker';
|
profile.display_name ??= 'New User via nsecBunker';
|
||||||
|
|
@ -53,54 +39,49 @@ export async function setupSkeletonProfile(sk: Uint8Array, profile?: SkeletonPro
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const pubkey = getPublicKey(sk);
|
const user = await key.user();
|
||||||
const now = () => Math.floor(Date.now() / 1000);
|
const ndk = new NDK({
|
||||||
|
explicitRelayUrls,
|
||||||
|
signer: key
|
||||||
|
});
|
||||||
|
|
||||||
const pool = new RelayPool(explicitRelayUrls, {});
|
await ndk.connect(2500);
|
||||||
pool.start();
|
user.ndk = ndk;
|
||||||
// Give the connections a moment to come up (NDK used connect(2500)).
|
|
||||||
await new Promise((r) => setTimeout(r, 2500));
|
|
||||||
|
|
||||||
try {
|
let event = new NDKEvent(ndk, {
|
||||||
await pool.publish(
|
kind: 0,
|
||||||
finalizeEvent({ kind: 0, created_at: now(), tags: [], content: JSON.stringify(profile) }, sk),
|
content: JSON.stringify(profile),
|
||||||
);
|
pubkey: user.pubkey,
|
||||||
await pool.publish(
|
} as NostrEvent);
|
||||||
finalizeEvent(
|
await event.sign(key);
|
||||||
{
|
|
||||||
kind: 3,
|
const t = await event.publish();
|
||||||
created_at: now(),
|
debug(`Published to ${t.size} relays`);
|
||||||
tags: [
|
|
||||||
['p', 'fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52'],
|
event = new NDKEvent(ndk, {
|
||||||
['p', pubkey],
|
kind: 3,
|
||||||
],
|
tags: [
|
||||||
content: '',
|
['p', 'fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52'],
|
||||||
},
|
['p', user.pubkey],
|
||||||
sk,
|
],
|
||||||
),
|
pubkey: user.pubkey,
|
||||||
);
|
} as NostrEvent);
|
||||||
await pool.publish(
|
await event.sign(key);
|
||||||
finalizeEvent(
|
debug(`follow list event`, event.rawEvent());
|
||||||
{
|
await event.publish();
|
||||||
kind: 10002,
|
|
||||||
created_at: now(),
|
const relays = new NDKEvent(ndk, {
|
||||||
tags: [
|
kind: 10002,
|
||||||
['r', 'wss://purplepag.es'],
|
tags: [
|
||||||
['r', 'wss://relay.f7z.io'],
|
['r', 'wss://purplepag.es'],
|
||||||
['r', 'wss://relay.damus.io'],
|
['r', 'wss://relay.f7z.io'],
|
||||||
['r', 'wss://relayable.org'],
|
['r', 'wss://relay.damus.io'],
|
||||||
['r', 'wss://relay.nostr.band'],
|
['r', 'wss://relayable.org'],
|
||||||
['r', 'wss://relay.primal.net'],
|
['r', 'wss://relay.nostr.band'],
|
||||||
],
|
['r', 'wss://relay.primal.net'],
|
||||||
content: '',
|
],
|
||||||
},
|
pubkey: user.pubkey,
|
||||||
sk,
|
} as NostrEvent);
|
||||||
),
|
await relays.sign(key);
|
||||||
);
|
await relays.publish();
|
||||||
debug('published skeleton profile (kind 0/3/10002)');
|
}
|
||||||
} catch (e) {
|
|
||||||
debug('error publishing skeleton profile', e);
|
|
||||||
} finally {
|
|
||||||
pool.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
* Local NIP-46 types (aiolabs/nsecbunkerd#42).
|
* Local NIP-46 types (aiolabs/nsecbunkerd#42).
|
||||||
*
|
*
|
||||||
* These replace the identically-named types we used to import from
|
* These replace the identically-named types we used to import from
|
||||||
* NDK, so the daemon's signing path no longer depends on NDK.
|
* `@nostr-dev-kit/ndk`, so the daemon's signing path no longer depends on NDK.
|
||||||
* Kept structurally identical to NDK's so the ACL callback
|
* Kept structurally identical to NDK's so the ACL callback
|
||||||
* (`signingAuthorizationCallback`) and the admin layer don't have to change.
|
* (`signingAuthorizationCallback`) and the admin layer don't have to change.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { nip19, getPublicKey, utils as nostrUtils } from 'nostr-tools';
|
import { NDKPrivateKeySigner } from '@nostr-dev-kit/ndk';
|
||||||
|
import { nip19, utils as nostrUtils } from 'nostr-tools';
|
||||||
import { Backend } from './backend/index.js';
|
import { Backend } from './backend/index.js';
|
||||||
import { applyToken } from './backend/token-store.js';
|
import { applyToken } from './backend/token-store.js';
|
||||||
import { RelayPool } from './lib/relay-pool.js';
|
import { RelayPool } from './lib/relay-pool.js';
|
||||||
import { secretKeyBytes } from './nip46/transport.js';
|
|
||||||
import type { Nip46PermitCallback, Nip46PermitCallbackParams } from './nip46/types.js';
|
import type { Nip46PermitCallback, Nip46PermitCallbackParams } from './nip46/types.js';
|
||||||
import { checkIfPubkeyAllowed, recordSigning } from './lib/acl/index.js';
|
import { checkIfPubkeyAllowed, recordSigning } from './lib/acl/index.js';
|
||||||
import AdminInterface from './admin/index.js';
|
import AdminInterface from './admin/index.js';
|
||||||
|
|
@ -38,10 +38,10 @@ function getKeys(config: DaemonConfig) {
|
||||||
const keys: Key[] = [];
|
const keys: Key[] = [];
|
||||||
|
|
||||||
for (const [name, nsec] of Object.entries(config.keys)) {
|
for (const [name, nsec] of Object.entries(config.keys)) {
|
||||||
const npub = nip19.npubEncode(getPublicKey(secretKeyBytes(nsec)));
|
const user = await new NDKPrivateKeySigner(nsec).user();
|
||||||
const key = {
|
const key = {
|
||||||
name,
|
name,
|
||||||
npub,
|
npub: user.npub,
|
||||||
userCount: await prisma.keyUser.count({ where: { keyName: name } }),
|
userCount: await prisma.keyUser.count({ where: { keyName: name } }),
|
||||||
tokenCount: await prisma.token.count({ where: { keyName: name } })
|
tokenCount: await prisma.token.count({ where: { keyName: name } })
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,23 @@
|
||||||
import { finalizeEvent, nip04, nip19 } from "nostr-tools";
|
import NDK, { NDKUser, NDKEvent, NostrEvent } from "@nostr-dev-kit/ndk";
|
||||||
import type { RelayPool } from "../daemon/lib/relay-pool.js";
|
|
||||||
|
|
||||||
/**
|
export async function dmUser(ndk: NDK, recipient: NDKUser | string, content: string): Promise<NDKEvent> {
|
||||||
* Send a kind-4 NIP-04 DM to `recipient` (npub or hex) as `sk`, published via an
|
let targetUser;
|
||||||
* already-started `pool`. Ported off NDK (aiolabs/nsecbunkerd#44) — used only for
|
|
||||||
* the one-shot boot notification to admins.
|
if (typeof recipient === 'string') {
|
||||||
*/
|
targetUser = new NDKUser({ npub: recipient });
|
||||||
export async function dmUser(
|
} else if (recipient instanceof NDKUser) {
|
||||||
sk: Uint8Array,
|
targetUser = recipient;
|
||||||
recipient: string,
|
}
|
||||||
content: string,
|
|
||||||
pool: RelayPool,
|
const event = new NDKEvent(ndk, { kind: 4, content } as NostrEvent);
|
||||||
): Promise<void> {
|
event.tag(targetUser);
|
||||||
const recipientHex = recipient.startsWith("npub1")
|
await event.encrypt(targetUser);
|
||||||
? (nip19.decode(recipient).data as string)
|
await event.sign();
|
||||||
: recipient;
|
|
||||||
const ciphertext = nip04.encrypt(sk, recipientHex, content);
|
|
||||||
const event = finalizeEvent(
|
|
||||||
{
|
|
||||||
kind: 4,
|
|
||||||
created_at: Math.floor(Date.now() / 1000),
|
|
||||||
tags: [["p", recipientHex]],
|
|
||||||
content: ciphertext,
|
|
||||||
},
|
|
||||||
sk,
|
|
||||||
);
|
|
||||||
try {
|
try {
|
||||||
await pool.publish(event);
|
await event.publish();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return event;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue