dedpaste
Version:
CLI pastebin application using Cloudflare Workers and R2
357 lines (314 loc) • 10.1 kB
JavaScript
// Interactive mode for key management and encryption
import inquirer from 'inquirer';
import { listKeys, addFriendKey, removeKey, getKey } from './keyManager.js';
import { encryptContent } from './encryptionUtils.js';
import fs from 'fs';
import { promises as fsPromises } from 'fs';
import path from 'path';
import { homedir } from 'os';
// Interactive key management
async function interactiveKeyManagement() {
const { action } = await inquirer.prompt([{
type: 'list',
name: 'action',
message: 'What would you like to do?',
choices: [
{ name: 'List all keys', value: 'list' },
{ name: 'Add a friend\'s key', value: 'add' },
{ name: 'Remove a key', value: 'remove' },
{ name: 'Export your public key', value: 'export' },
{ name: 'Cancel', value: 'cancel' }
]
}]);
switch (action) {
case 'list':
return interactiveListKeys();
case 'add':
return interactiveAddFriend();
case 'remove':
return interactiveRemoveKey();
case 'export':
return interactiveExportKey();
case 'cancel':
return { success: true, message: 'Operation cancelled' };
}
}
// Interactive list keys
async function interactiveListKeys() {
const db = await listKeys();
console.log('\nAvailable keys:');
// Self key
if (db.keys.self) {
console.log(` - You (self)`);
console.log(` Fingerprint: ${db.keys.self.fingerprint}`);
console.log(` Created: ${new Date(db.keys.self.created).toLocaleString()}`);
} else {
console.log(' - No personal key found. Generate one with --gen-key');
}
// Friend keys
const friendNames = Object.keys(db.keys.friends);
if (friendNames.length > 0) {
console.log('\n Friends:');
for (const name of friendNames) {
const friend = db.keys.friends[name];
const lastUsed = new Date(friend.last_used).toLocaleString();
console.log(` - ${name} (last used: ${lastUsed})`);
console.log(` Fingerprint: ${friend.fingerprint}`);
}
} else {
console.log('\n No friend keys found. Add one with "dedpaste keys add-friend"');
}
// PGP keys
const pgpNames = Object.keys(db.keys.pgp || {});
if (pgpNames.length > 0) {
console.log('\n PGP Keys:');
for (const name of pgpNames) {
const pgp = db.keys.pgp[name];
const lastUsed = new Date(pgp.last_used).toLocaleString();
console.log(` - ${name} (PGP, last used: ${lastUsed})`);
console.log(` Fingerprint: ${pgp.fingerprint}`);
if (pgp.email) {
console.log(` Email: ${pgp.email}`);
}
}
}
// Keybase keys
const keybaseNames = Object.keys(db.keys.keybase || {});
if (keybaseNames.length > 0) {
console.log('\n Keybase Keys:');
for (const name of keybaseNames) {
const kb = db.keys.keybase[name];
const lastUsed = new Date(kb.last_used).toLocaleString();
console.log(` - ${name} (Keybase, last used: ${lastUsed})`);
console.log(` Username: ${kb.username}`);
console.log(` Fingerprint: ${kb.fingerprint}`);
if (kb.email) {
console.log(` Email: ${kb.email}`);
}
}
}
if (friendNames.length === 0 && pgpNames.length === 0 && keybaseNames.length === 0) {
console.log('\n No recipient keys found. Add keys using:');
console.log(' - dedpaste keys --add-friend <name> --key-file <path>');
console.log(' - dedpaste keys --pgp-key <email-or-id>');
console.log(' - dedpaste keys --keybase <username>');
}
return { success: true };
}
// Interactive add friend
async function interactiveAddFriend() {
const { name } = await inquirer.prompt([{
type: 'input',
name: 'name',
message: 'Enter friend\'s name:',
validate: input => input.trim() !== '' ? true : 'Name cannot be empty'
}]);
const { method } = await inquirer.prompt([{
type: 'list',
name: 'method',
message: 'How would you like to add the key?',
choices: [
{ name: 'From a file', value: 'file' },
{ name: 'Paste the key content', value: 'paste' },
{ name: 'Cancel', value: 'cancel' }
]
}]);
if (method === 'cancel') {
return { success: true, message: 'Operation cancelled' };
}
let keyContent;
if (method === 'file') {
const { filePath } = await inquirer.prompt([{
type: 'input',
name: 'filePath',
message: 'Enter path to the public key file:',
validate: input => {
if (input.trim() === '') return 'Path cannot be empty';
if (!fs.existsSync(input)) return 'File does not exist';
return true;
}
}]);
keyContent = await fsPromises.readFile(filePath, 'utf8');
} else {
const { content } = await inquirer.prompt([{
type: 'editor',
name: 'content',
message: 'Paste the public key content:',
validate: input => {
if (input.trim() === '') return 'Key content cannot be empty';
if (!input.includes('-----BEGIN PUBLIC KEY-----')) {
return 'Invalid key format. Must be a PEM format public key';
}
return true;
}
}]);
keyContent = content;
}
try {
const keyPath = await addFriendKey(name, keyContent);
return {
success: true,
message: `Added ${name}'s public key at ${keyPath}`
};
} catch (error) {
return {
success: false,
message: `Error adding friend's key: ${error.message}`
};
}
}
// Interactive remove key
async function interactiveRemoveKey() {
const db = await listKeys();
const friendNames = Object.keys(db.keys.friends);
if (friendNames.length === 0) {
return {
success: false,
message: 'No friend keys found to remove'
};
}
const choices = friendNames.map(name => ({ name, value: name }));
choices.push({ name: 'Cancel', value: 'cancel' });
const { friend } = await inquirer.prompt([{
type: 'list',
name: 'friend',
message: 'Select friend to remove:',
choices: choices
}]);
if (friend === 'cancel') {
return { success: true, message: 'Operation cancelled' };
}
const { confirm } = await inquirer.prompt([{
type: 'confirm',
name: 'confirm',
message: `Are you sure you want to remove ${friend}'s key?`,
default: false
}]);
if (!confirm) {
return { success: true, message: 'Operation cancelled' };
}
try {
await removeKey('friend', friend);
return {
success: true,
message: `Removed ${friend}'s key successfully`
};
} catch (error) {
return {
success: false,
message: `Error removing key: ${error.message}`
};
}
}
// Interactive export key
async function interactiveExportKey() {
const selfKey = await getKey('self');
if (!selfKey) {
return {
success: false,
message: 'No personal key found. Generate one with --gen-key'
};
}
const publicKeyContent = await fsPromises.readFile(selfKey.public, 'utf8');
const { method } = await inquirer.prompt([{
type: 'list',
name: 'method',
message: 'How would you like to export your public key?',
choices: [
{ name: 'Display on screen', value: 'display' },
{ name: 'Save to file', value: 'file' },
{ name: 'Cancel', value: 'cancel' }
]
}]);
if (method === 'cancel') {
return { success: true, message: 'Operation cancelled' };
}
if (method === 'display') {
console.log('\nYour public key:');
console.log('----------------');
console.log(publicKeyContent);
console.log('----------------');
console.log('\nShare this key with your friends so they can send you encrypted pastes.');
return { success: true };
} else {
const { filePath } = await inquirer.prompt([{
type: 'input',
name: 'filePath',
message: 'Enter path to save the public key:',
default: path.join(homedir(), 'my_dedpaste_public_key.pem'),
validate: input => input.trim() !== '' ? true : 'Path cannot be empty'
}]);
try {
await fsPromises.writeFile(filePath, publicKeyContent);
return {
success: true,
message: `Public key saved to ${filePath}`
};
} catch (error) {
return {
success: false,
message: `Error saving key: ${error.message}`
};
}
}
}
// Interactive send
async function interactiveSend() {
// Get message
const { message } = await inquirer.prompt([{
type: 'editor',
name: 'message',
message: 'Enter your message:',
validate: input => input.trim() !== '' ? true : 'Message cannot be empty'
}]);
// Get recipient
const db = await listKeys();
const choices = [{ name: 'Yourself (self)', value: null }];
// Add regular friends
const friendNames = Object.keys(db.keys.friends);
for (const name of friendNames) {
choices.push({ name: `Friend: ${name}`, value: name });
}
// Add PGP keys if available
if (db.keys.pgp) {
const pgpNames = Object.keys(db.keys.pgp);
for (const name of pgpNames) {
const email = db.keys.pgp[name].email ? ` (${db.keys.pgp[name].email})` : '';
choices.push({ name: `PGP: ${name}${email}`, value: name });
}
}
// Add Keybase keys if available
if (db.keys.keybase) {
const keybaseNames = Object.keys(db.keys.keybase);
for (const name of keybaseNames) {
choices.push({ name: `Keybase: ${name} (${db.keys.keybase[name].username})`, value: name });
}
}
const { recipient } = await inquirer.prompt([{
type: 'list',
name: 'recipient',
message: 'Select recipient:',
choices: choices
}]);
// Get paste options
const { isTemp } = await inquirer.prompt([{
type: 'confirm',
name: 'isTemp',
message: 'Create a one-time paste (deleted after first view)?',
default: false
}]);
return {
content: Buffer.from(message),
recipient: recipient,
temp: isTemp
};
}
// Export functions
export {
interactiveKeyManagement,
interactiveListKeys,
interactiveAddFriend,
interactiveRemoveKey,
interactiveExportKey,
interactiveSend
};