diff --git a/cli/encryptionUtils.ts b/cli/encryptionUtils.ts index cb57fed..46d215f 100644 --- a/cli/encryptionUtils.ts +++ b/cli/encryptionUtils.ts @@ -44,9 +44,10 @@ type EncryptedData = EncryptedDataV1 | EncryptedDataV2 | EncryptedDataV3; // Encrypt content for a specific recipient export async function encryptContent( - content: string, - recipientName: string | null = null, - usePgp: boolean = false + content: string, + recipientName: string | null = null, + usePgp: boolean = false, + refreshGithubKeys: boolean = false ): Promise { try { let publicKey: string; @@ -60,16 +61,38 @@ export async function encryptContent( // 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); + await ensureGitHubKey(username, false, refreshGithubKeys); // 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}`); + console.error(`\nError: Failed to fetch GitHub key for user "${username}"`); + console.error(`Details: ${error.message}`); + console.error(`\nPlease verify:`); + console.error(` 1. The GitHub username is correct`); + console.error(` 2. The user has a GPG key uploaded to GitHub`); + console.error(` 3. You have internet connectivity`); + console.error(`\nTo manually add a GitHub user's key, run:`); + console.error(` dedpaste keys --github ${username}`); + throw new Error(`Failed to fetch GitHub key for ${username}`); + } + } + + // Also check if we should refresh an existing GitHub key + if (friendKey && recipientName.startsWith('github:') && refreshGithubKeys) { + const username = recipientName.replace('github:', ''); + + try { + const { ensureGitHubKey } = await import('./githubUtils.js'); + await ensureGitHubKey(username, false, true); + + // Reload the key after refreshing + friendKey = await getKey('any', recipientName); + } catch (error: any) { + console.warn(`Warning: Failed to refresh GitHub key, using cached version`); } } diff --git a/cli/githubUtils.ts b/cli/githubUtils.ts index 8429c82..70459be 100644 --- a/cli/githubUtils.ts +++ b/cli/githubUtils.ts @@ -155,15 +155,9 @@ export async function addGitHubKey( } // Fetch GPG key - if (!silent) { - console.log(`Fetching GPG key for GitHub user: ${sanitizedUsername}...`); - } const gpgKeyData = await fetchGitHubGpgKey(sanitizedUsername); // Import and validate PGP key - if (!silent) { - console.log(`Importing PGP key...`); - } const keyInfo = await importPgpKey(gpgKeyData); // Generate key name @@ -196,14 +190,16 @@ export async function addGitHubKey( /** * Fetch GitHub key just-in-time (used during encryption) - * Checks if key exists in database, fetches if not + * Checks if key exists in database, fetches if not or if stale * @param username GitHub username (without github: prefix) * @param silent Whether to suppress console output + * @param forceRefresh Whether to force refresh even if cached * @returns Key name and metadata */ export async function ensureGitHubKey( username: string, - silent: boolean = false + silent: boolean = false, + forceRefresh: boolean = false ): Promise<{ name: string; fingerprint: string; email?: string }> { const keyName = `github:${username}`; @@ -211,20 +207,21 @@ export async function ensureGitHubKey( const { getKey } = await import('./keyManager.js'); const existingKey = await getKey('github', keyName); - if (existingKey) { - if (!silent) { - console.log(`Using cached GitHub key: ${keyName}`); - } - return { - name: keyName, - fingerprint: existingKey.fingerprint, - email: existingKey.email - }; - } + if (existingKey && !forceRefresh) { + // Check if key is stale (older than 24 hours) + const lastFetched = existingKey.lastFetched ? new Date(existingKey.lastFetched) : null; + const now = new Date(); + const maxAge = 24 * 60 * 60 * 1000; // 24 hours in milliseconds - // Key doesn't exist, fetch it - if (!silent) { - console.log(`GitHub key not found locally, fetching from GitHub...`); + const isStale = lastFetched ? (now.getTime() - lastFetched.getTime() > maxAge) : true; + + if (!isStale) { + return { + name: keyName, + fingerprint: existingKey.fingerprint, + email: existingKey.email + }; + } } return await addGitHubKey(username, undefined, false, silent); diff --git a/cli/index.js b/cli/index.js index 16d0214..ae4d6bc 100755 --- a/cli/index.js +++ b/cli/index.js @@ -158,6 +158,7 @@ program // GitHub options .option('--github ', 'Fetch and add a GitHub user\'s GPG public key') .option('--github-name ', 'Custom name for the GitHub user\'s key (optional)') + .option('--refresh-github-keys', 'Force refresh of cached GitHub keys (re-fetch from GitHub)') // Debugging and logging options .option('--verbose', 'Enable verbose logging (same as --log-level debug)') .option('--debug', 'Enable debug mode with extensive logging (same as --log-level trace)') @@ -676,6 +677,7 @@ Key Storage: - Email: ${result.email || 'Not specified'} - Fingerprint: ${result.fingerprint} `); + process.exit(0); } catch (error) { logger.error('Failed to fetch GitHub key', { error: error.message }); @@ -749,6 +751,7 @@ program .option('--pgp', 'Use PGP encryption instead of hybrid RSA/AES') .option('--pgp-key-file ', 'Use a specific PGP public key file for encryption') .option('--pgp-armor', 'Output ASCII-armored PGP instead of binary format') + .option('--refresh-github-keys', 'Force refresh of cached GitHub keys when encrypting') .addHelpText('after', ` Examples: $ echo "Secret message" | dedpaste send --encrypt # Encrypt for yourself (RSA/AES) @@ -903,7 +906,7 @@ Encryption: } else { // Use the standard encryption flow with PGP option - const encryptResult = await encryptContent(content.toString('utf8'), recipientName, usePgp); + const encryptResult = await encryptContent(content.toString('utf8'), recipientName, usePgp, options.refreshGithubKeys); content = typeof encryptResult === 'string' ? Buffer.from(encryptResult) : encryptResult; } // Log PGP mode if used diff --git a/cli/index.ts b/cli/index.ts index b22d3a5..cfc6564 100644 --- a/cli/index.ts +++ b/cli/index.ts @@ -151,6 +151,7 @@ interface SendOptions { pgp?: boolean; pgpKeyFile?: string; pgpArmor?: boolean; + refreshGithubKeys?: boolean; } interface GetOptions { @@ -302,6 +303,7 @@ program // GitHub options .option('--github ', 'Fetch and add a GitHub user\'s GPG public key') .option('--github-name ', 'Custom name for the GitHub user\'s key (optional)') + .option('--refresh-github-keys', 'Force refresh of cached GitHub keys (re-fetch from GitHub)') // Debugging and logging options .option('--verbose', 'Enable verbose logging (same as --log-level debug)') .option('--debug', 'Enable debug mode with extensive logging (same as --log-level trace)') @@ -906,6 +908,7 @@ Key Storage: - Email: ${result.email || 'Not specified'} - Fingerprint: ${result.fingerprint} `); + process.exit(0); } catch (error: any) { logger.error('Failed to fetch GitHub key', { error: error.message }); console.error(`Error fetching GitHub key: ${error.message}`); @@ -983,6 +986,7 @@ program .option('--pgp', 'Use PGP encryption instead of hybrid RSA/AES') .option('--pgp-key-file ', 'Use a specific PGP public key file for encryption') .option('--pgp-armor', 'Output ASCII-armored PGP instead of binary format') + .option('--refresh-github-keys', 'Force refresh of cached GitHub keys when encrypting') .addHelpText('after', ` Examples: $ echo "Secret message" | dedpaste send --encrypt # Encrypt for yourself (RSA/AES) @@ -1154,7 +1158,12 @@ Encryption: content = typeof pgpResult === 'string' ? Buffer.from(pgpResult) : pgpResult; } else { // Use the standard encryption flow with PGP option - const encryptResult = await encryptContent(content.toString('utf8'), recipientName, usePgp); + const encryptResult = await encryptContent( + content.toString('utf8'), + recipientName, + usePgp, + options.refreshGithubKeys + ); content = typeof encryptResult === 'string' ? Buffer.from(encryptResult) : encryptResult; } diff --git a/cli/keyManager.ts b/cli/keyManager.ts index 4d11dbd..f554057 100644 --- a/cli/keyManager.ts +++ b/cli/keyManager.ts @@ -355,6 +355,7 @@ interface GitHubKeyAddInfo { fingerprint: string; email?: string; created?: Date; + lastFetched?: string; } /** @@ -388,7 +389,8 @@ export async function addGitHubKey( email: keyInfo.email, created: keyInfo.created, addedDate: new Date().toISOString(), - lastUsed: new Date().toISOString() + lastUsed: new Date().toISOString(), + lastFetched: keyInfo.lastFetched || new Date().toISOString() }; await saveKeyDatabase(db); diff --git a/package-lock.json b/package-lock.json index 5cbb027..cf8eff6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "dedpaste", - "version": "1.20.0", + "version": "1.20.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "dedpaste", - "version": "1.20.0", + "version": "1.20.1", "hasInstallScript": true, "license": "ISC", "dependencies": { diff --git a/src/types/index.ts b/src/types/index.ts index b3c66b6..64789de 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -13,6 +13,7 @@ export interface KeyInfo { created?: string | Date; lastUsed?: string | Date | null; addedDate?: string; + lastFetched?: string; id?: string; source?: "self" | "friend" | "pgp" | "keybase" | "github" | "gpg"; expires?: string | Date;