Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 32 additions & 3 deletions cli/encryptionUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,25 @@ export async function encryptContent(
let keyType: 'standard' | 'pgp' = 'standard';

if (recipientName) {
// Try to find the key in any key store (friend, PGP, or Keybase)
const friendKey = await getKey('any', recipientName);
// Try to find the key in any key store (friend, PGP, Keybase, or GitHub)
let friendKey = await getKey('any', recipientName);

// If not found and it starts with "github:", try to fetch it just-in-time
if (!friendKey && recipientName.startsWith('github:')) {
const username = recipientName.replace('github:', '');
console.log(`GitHub key not found locally, fetching from GitHub...`);

try {
const { ensureGitHubKey } = await import('./githubUtils.js');
await ensureGitHubKey(username, false);

// Try to get the key again after fetching
friendKey = await getKey('any', recipientName);
} catch (error: any) {
throw new Error(`Failed to fetch GitHub key for ${username}: ${error.message}`);
}
}

if (!friendKey) {
throw new Error(`Recipient "${recipientName}" not found in key database`);
}
Expand All @@ -75,14 +92,26 @@ export async function encryptContent(
keyType = 'pgp'; // Keybase keys are also PGP keys
const keyPath = typeof friendKey.path === 'string' ? friendKey.path : friendKey.path.public;
publicKey = await fsPromises.readFile(keyPath, 'utf8');

recipientInfo = {
type: 'keybase',
name: recipientName,
fingerprint: friendKey.fingerprint,
username: friendKey.username,
email: friendKey.email
};
} else if (friendKey.type === 'github') {
keyType = 'pgp'; // GitHub keys are also PGP keys
const keyPath = typeof friendKey.path === 'string' ? friendKey.path : friendKey.path.public;
publicKey = await fsPromises.readFile(keyPath, 'utf8');

recipientInfo = {
type: 'github',
name: recipientName,
fingerprint: friendKey.fingerprint,
username: friendKey.username,
email: friendKey.email
};
} else {
// Standard RSA key
publicKey = await fsPromises.readFile(friendKey.public!, 'utf8');
Expand Down
80 changes: 72 additions & 8 deletions cli/enhancedInteractiveMode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,8 @@ interface SearchOptions {
interface KeySearchResult {
id: string;
name: string;
type: 'self' | 'friend' | 'pgp' | 'keybase' | 'gpg';
source: 'self' | 'friend' | 'pgp' | 'keybase' | 'gpg';
type: 'self' | 'friend' | 'pgp' | 'keybase' | 'github' | 'gpg';
source: 'self' | 'friend' | 'pgp' | 'keybase' | 'github' | 'gpg';
fingerprint: string;
email?: string;
username?: string;
Expand All @@ -113,7 +113,7 @@ interface KeySearchResult {
}

interface ImportKeyOptions {
source: 'file' | 'pgp-server' | 'keybase' | 'gpg-keyring' | 'gpg-import' | 'paste';
source: 'file' | 'pgp-server' | 'keybase' | 'github' | 'gpg-keyring' | 'gpg-import' | 'paste';
file?: string;
content?: string;
name?: string;
Expand Down Expand Up @@ -350,9 +350,10 @@ export async function searchAndListKeys(): Promise<void> {
friend: [],
pgp: [],
keybase: [],
github: [],
gpg: []
};

for (const key of keys) {
if (key.source === 'self') {
groupedKeys.self.push(key);
Expand All @@ -362,6 +363,8 @@ export async function searchAndListKeys(): Promise<void> {
groupedKeys.pgp.push(key);
} else if (key.source === 'keybase') {
groupedKeys.keybase.push(key);
} else if (key.source === 'github') {
groupedKeys.github.push(key);
} else if (key.source === 'gpg') {
groupedKeys.gpg.push(key);
}
Expand Down Expand Up @@ -428,7 +431,24 @@ export async function searchAndListKeys(): Promise<void> {
}
}
}


// Print GitHub keys
if (groupedKeys.github.length > 0) {
console.log(chalk.bold.blue('\nGitHub Keys:'));
for (const key of groupedKeys.github) {
console.log(`- ${chalk.green('Name:')} ${key.name}`);
console.log(` ${chalk.green('Type:')} ${key.type.toUpperCase()}`);
console.log(` ${chalk.green('Username:')} ${key.username || 'unknown'}`);
console.log(` ${chalk.green('Fingerprint:')} ${key.fingerprint}`);
if (key.email) {
console.log(` ${chalk.green('Email:')} ${key.email}`);
}
if (key.lastUsed) {
console.log(` ${chalk.green('Last Used:')} ${key.lastUsed.toLocaleString()}`);
}
}
}

// Print GPG keys
if (groupedKeys.gpg.length > 0) {
console.log(chalk.bold.blue('\nGPG Keyring Keys:'));
Expand Down Expand Up @@ -480,6 +500,7 @@ export async function addOrImportKey(): Promise<void> {
{ name: 'Import from file', value: 'file' },
{ name: 'Fetch from PGP keyserver', value: 'pgp-server' },
{ name: 'Fetch from Keybase', value: 'keybase' },
{ name: 'Fetch from GitHub', value: 'github' },
{ name: 'Import from GPG keyring', value: 'gpg-keyring' },
{ name: 'Import to GPG keyring', value: 'gpg-import' },
{ name: 'Paste key content', value: 'paste' }
Expand Down Expand Up @@ -568,7 +589,35 @@ export async function addOrImportKey(): Promise<void> {
options.verify = keybaseAnswers.verify;
break;
}


case 'github': {
const githubAnswers = await inquirer.prompt<InquirerAnswers>([
{
type: 'input',
name: 'username',
message: 'Enter GitHub username:',
validate: (input: string) => input ? true : 'Username cannot be empty'
},
{
type: 'input',
name: 'name',
message: 'Enter a name for this key (leave empty to use default):',
default: ''
},
{
type: 'confirm',
name: 'verify',
message: 'Verify GitHub user before importing?',
default: false
}
]);

options.username = githubAnswers.username;
if (githubAnswers.name) options.name = githubAnswers.name;
options.verify = githubAnswers.verify;
break;
}

case 'gpg-keyring': {
// Check if GPG is available
const gpgInfo = await modules.unifiedKeyManager?.initialize();
Expand Down Expand Up @@ -796,6 +845,8 @@ export async function viewKeyDetails(preloadedKeys: KeySearchResult[] | null = n
label = `${key.name} (PGP Key${key.email ? ` - ${key.email}` : ''})`;
} else if (key.source === 'keybase') {
label = `${key.name} (Keybase - ${key.username || 'unknown'})`;
} else if (key.source === 'github') {
label = `${key.name} (GitHub - ${key.username || 'unknown'})`;
} else if (key.source === 'gpg') {
const uid = key.uids && key.uids.length > 0 ? key.uids[0].uid || 'Unknown' : 'Unknown';
label = `${key.id} (GPG - ${uid})`;
Expand Down Expand Up @@ -936,6 +987,8 @@ export async function exportKeys(): Promise<void> {
label = `${key.name} (PGP Key${key.email ? ` - ${key.email}` : ''})`;
} else if (key.source === 'keybase') {
label = `${key.name} (Keybase - ${key.username || 'unknown'})`;
} else if (key.source === 'github') {
label = `${key.name} (GitHub - ${key.username || 'unknown'})`;
} else if (key.source === 'gpg') {
const uid = key.uids && key.uids.length > 0 ? key.uids[0].uid || 'Unknown' : 'Unknown';
label = `${key.id} (GPG - ${uid})`;
Expand Down Expand Up @@ -992,7 +1045,7 @@ export async function exportKeys(): Promise<void> {
let filename: string;
if (keyInfo.source === 'self') {
filename = exportPrivate ? 'dedpaste_private_key.pem' : 'dedpaste_public_key.pem';
} else if (keyInfo.type === 'pgp' || keyInfo.type === 'keybase') {
} else if (keyInfo.type === 'pgp' || keyInfo.type === 'keybase' || keyInfo.type === 'github') {

Copilot AI Oct 22, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] This condition is checking multiple key types that all use .asc extension. Consider grouping these types into a constant array (e.g., PGP_BASED_KEY_TYPES) and using includes() to make the code more maintainable when adding future PGP-based key types.

Copilot uses AI. Check for mistakes.
filename = `${keyInfo.name.replace(/[^a-z0-9_-]/gi, '_')}.asc`;
} else if (keyInfo.type === 'gpg') {
filename = `gpg_${keyInfo.id.substring(keyInfo.id.length - 8)}.asc`;
Expand Down Expand Up @@ -1282,6 +1335,7 @@ export async function enhancedInteractiveSend(): Promise<OperationResult> {
const friendKeys = allKeys.filter(key => key.source === 'friend');
const pgpKeys = allKeys.filter(key => key.source === 'pgp');
const keybaseKeys = allKeys.filter(key => key.source === 'keybase');
const githubKeys = allKeys.filter(key => key.source === 'github');

// Create categorized choices
const choices: Array<{ name: string; value: string | null; short?: string; disabled?: boolean }> = [];
Expand Down Expand Up @@ -1325,7 +1379,17 @@ export async function enhancedInteractiveSend(): Promise<OperationResult> {
short: key.name
})));
}


// GitHub category
if (githubKeys.length > 0) {
choices.push({ name: chalk.bold.blue('--- GitHub ---'), value: null, disabled: true });
choices.push(...githubKeys.map(key => ({
name: `${key.name} (${key.username || 'unknown'})`,
value: key.id,
short: key.name
})));
}

// GPG option at the end
choices.push({ name: chalk.bold.blue('--- Other Options ---'), value: null, disabled: true });
choices.push({ name: 'Use GPG keyring directly (select a GPG key)', value: 'gpg', short: 'GPG' });
Expand Down
Loading