✨ Feat: Implement support for multiple CCNs
This commit is contained in:
parent
097f02938d
commit
a8ffce918e
7 changed files with 778 additions and 169 deletions
106
utils.ts
106
utils.ts
|
@ -1,13 +1,15 @@
|
|||
import { decodeBase64, encodeBase64 } from 'jsr:@std/encoding@0.224/base64';
|
||||
import { exists } from 'jsr:@std/fs';
|
||||
import * as nostrTools from '@nostr/tools';
|
||||
import * as nip06 from '@nostr/tools/nip06';
|
||||
import type { Database } from 'jsr:@db/sqlite';
|
||||
import { decodeBase64, encodeBase64 } from 'jsr:@std/encoding@0.224/base64';
|
||||
import { exists } from 'jsr:@std/fs';
|
||||
import {
|
||||
decryptUint8Array,
|
||||
encryptUint8Array,
|
||||
encryptionKey,
|
||||
} from './utils/encryption.ts';
|
||||
import { getEveFilePath } from './utils/files.ts';
|
||||
import { sql } from './utils/queries.ts';
|
||||
|
||||
export function isLocalhost(req: Request): boolean {
|
||||
const url = new URL(req.url);
|
||||
|
@ -38,35 +40,66 @@ export function randomTimeUpTo2DaysInThePast() {
|
|||
);
|
||||
}
|
||||
|
||||
export async function getCCNPubkey(): Promise<string> {
|
||||
const ccnPubPath = await getEveFilePath('ccn.pub');
|
||||
const seedPath = await getEveFilePath('ccn.seed');
|
||||
const doWeHaveKey = await exists(ccnPubPath);
|
||||
if (doWeHaveKey) return Deno.readTextFileSync(ccnPubPath);
|
||||
const ccnSeed =
|
||||
Deno.env.get('CCN_SEED') ||
|
||||
((await exists(seedPath))
|
||||
? Deno.readTextFileSync(seedPath)
|
||||
: nip06.generateSeedWords());
|
||||
const ccnPrivateKey = nip06.privateKeyFromSeedWords(ccnSeed);
|
||||
const ccnPublicKey = nostrTools.getPublicKey(ccnPrivateKey);
|
||||
const encryptedPrivateKey = encryptUint8Array(ccnPrivateKey, encryptionKey);
|
||||
|
||||
Deno.writeTextFileSync(ccnPubPath, ccnPublicKey);
|
||||
Deno.writeTextFileSync(
|
||||
await getEveFilePath('ccn.priv'),
|
||||
encodeBase64(encryptedPrivateKey),
|
||||
);
|
||||
Deno.writeTextFileSync(seedPath, ccnSeed);
|
||||
|
||||
return ccnPublicKey;
|
||||
/**
|
||||
* Get all CCNs from the database
|
||||
*/
|
||||
export function getAllCCNs(db: Database): { pubkey: string; name: string }[] {
|
||||
return sql`SELECT pubkey, name FROM ccns`(db) as {
|
||||
pubkey: string;
|
||||
name: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
export async function getCCNPrivateKey(): Promise<Uint8Array> {
|
||||
const encryptedPrivateKey = Deno.readTextFileSync(
|
||||
await getEveFilePath('ccn.priv'),
|
||||
);
|
||||
return decryptUint8Array(decodeBase64(encryptedPrivateKey), encryptionKey);
|
||||
/**
|
||||
* Create a new CCN and store it in the database
|
||||
*
|
||||
* @param db - The database instance
|
||||
* @param name - The name of the CCN
|
||||
* @param seed - The seed words for the CCN
|
||||
* @returns The public key and private key of the CCN
|
||||
*/
|
||||
export async function createNewCCN(
|
||||
db: Database,
|
||||
name: string,
|
||||
seed?: string,
|
||||
): Promise<{ pubkey: string; privkey: Uint8Array }> {
|
||||
const ccnSeed = seed || nip06.generateSeedWords();
|
||||
const ccnPrivateKey = nip06.privateKeyFromSeedWords(ccnSeed);
|
||||
const ccnPublicKey = nostrTools.getPublicKey(ccnPrivateKey);
|
||||
|
||||
const ccnSeedPath = await getEveFilePath(`ccn_seeds/${ccnPublicKey}`);
|
||||
const ccnPrivPath = await getEveFilePath(`ccn_keys/${ccnPublicKey}`);
|
||||
|
||||
await Deno.mkdir(await getEveFilePath('ccn_seeds'), { recursive: true });
|
||||
await Deno.mkdir(await getEveFilePath('ccn_keys'), { recursive: true });
|
||||
|
||||
const encryptedPrivateKey = encryptUint8Array(ccnPrivateKey, encryptionKey);
|
||||
|
||||
Deno.writeTextFileSync(ccnSeedPath, ccnSeed);
|
||||
Deno.writeTextFileSync(ccnPrivPath, encodeBase64(encryptedPrivateKey));
|
||||
|
||||
sql`INSERT INTO ccns (pubkey, name) VALUES (${ccnPublicKey}, ${name})`(db);
|
||||
|
||||
return {
|
||||
pubkey: ccnPublicKey,
|
||||
privkey: ccnPrivateKey,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the private key for a specific CCN
|
||||
*/
|
||||
export async function getCCNPrivateKeyByPubkey(
|
||||
pubkey: string,
|
||||
): Promise<Uint8Array> {
|
||||
const ccnPrivPath = await getEveFilePath(`ccn_keys/${pubkey}`);
|
||||
|
||||
if (await exists(ccnPrivPath)) {
|
||||
const encryptedPrivateKey = Deno.readTextFileSync(ccnPrivPath);
|
||||
return decryptUint8Array(decodeBase64(encryptedPrivateKey), encryptionKey);
|
||||
}
|
||||
|
||||
throw new Error(`CCN private key for ${pubkey} not found`);
|
||||
}
|
||||
|
||||
export function isReplaceableEvent(kind: number): boolean {
|
||||
|
@ -104,3 +137,18 @@ export function parseATagQuery(aTagValue: string): {
|
|||
dTag: parts.length > 2 ? parts[2] : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the single active CCN from the database
|
||||
* @returns The active CCN or null if none is active
|
||||
*/
|
||||
export function getActiveCCN(
|
||||
db: Database,
|
||||
): { pubkey: string; name: string } | null {
|
||||
const result = sql`SELECT pubkey, name FROM ccns WHERE is_active = 1 LIMIT 1`(
|
||||
db,
|
||||
);
|
||||
return result.length > 0
|
||||
? (result[0] as { pubkey: string; name: string })
|
||||
: null;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue