[grade=A] P1-5, P1-6: replace 40 console.* calls in credential-manager.js + auth-manager.js
credential-manager.js: 20 console calls → log.info/warn/error tagged 'cred'. auth-manager.js: 20 console calls → log.info/error tagged 'auth'. Mixed-content strings extracted into meta payload (key, keyId, operation, etc). 1539/1539 Jest tests pass. ESLint: 4 pre-existing warnings unchanged.
This commit is contained in:
@@ -8,6 +8,7 @@ const jwt = require('jsonwebtoken');
|
|||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
const credentialManager = require('./credential-manager');
|
const credentialManager = require('./credential-manager');
|
||||||
const cryptoUtils = require('../security/crypto-utils');
|
const cryptoUtils = require('../security/crypto-utils');
|
||||||
|
const { log } = require('../utils/logging');
|
||||||
|
|
||||||
// JWT signing secret - derived from encryption key for consistency
|
// JWT signing secret - derived from encryption key for consistency
|
||||||
const JWT_SECRET = cryptoUtils.loadOrCreateKey();
|
const JWT_SECRET = cryptoUtils.loadOrCreateKey();
|
||||||
@@ -19,7 +20,7 @@ const API_KEY_METADATA_NAMESPACE = 'auth.metadata';
|
|||||||
class AuthManager {
|
class AuthManager {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.keyMetadataCache = new Map(); // Cache for API key metadata
|
this.keyMetadataCache = new Map(); // Cache for API key metadata
|
||||||
console.log('[AuthManager] Initialized');
|
log.info('auth', 'Initialized');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -44,10 +45,10 @@ class AuthManager {
|
|||||||
{ expiresIn }
|
{ expiresIn }
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(`[AuthManager] Generated JWT for user: ${payload.sub}, expires in: ${expiresIn}`);
|
log.info('auth', 'Generated JWT', { user: payload.sub, expiresIn });
|
||||||
return token;
|
return token;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[AuthManager] JWT generation failed:', error.message);
|
log.error('auth', error, { operation: 'jwtGenerate' });
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -68,11 +69,11 @@ class AuthManager {
|
|||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.name === 'TokenExpiredError') {
|
if (error.name === 'TokenExpiredError') {
|
||||||
console.log('[AuthManager] JWT token expired');
|
log.info('auth', 'JWT token expired');
|
||||||
} else if (error.name === 'JsonWebTokenError') {
|
} else if (error.name === 'JsonWebTokenError') {
|
||||||
console.log('[AuthManager] JWT token invalid:', error.message);
|
log.info('auth', 'JWT token invalid', { error: error.message });
|
||||||
} else {
|
} else {
|
||||||
console.error('[AuthManager] JWT verification failed:', error.message);
|
log.error('auth', error, { operation: 'jwtVerify' });
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -116,7 +117,7 @@ class AuthManager {
|
|||||||
// Cache metadata
|
// Cache metadata
|
||||||
this.keyMetadataCache.set(keyId, metadata);
|
this.keyMetadataCache.set(keyId, metadata);
|
||||||
|
|
||||||
console.log(`[AuthManager] Generated API key: ${name} (${keyId})`);
|
log.info('auth', 'Generated API key', { name, keyId });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
key: apiKey,
|
key: apiKey,
|
||||||
@@ -126,7 +127,7 @@ class AuthManager {
|
|||||||
createdAt: metadata.createdAt
|
createdAt: metadata.createdAt
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[AuthManager] API key generation failed:', error.message);
|
log.error('auth', error, { operation: 'apiKeyGenerate' });
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -154,30 +155,30 @@ class AuthManager {
|
|||||||
// Retrieve stored hash
|
// Retrieve stored hash
|
||||||
const storedHash = await credentialManager.retrieve(credentialKey);
|
const storedHash = await credentialManager.retrieve(credentialKey);
|
||||||
if (!storedHash) {
|
if (!storedHash) {
|
||||||
console.log(`[AuthManager] API key not found: ${keyId}`);
|
log.info('auth', 'API key not found', { keyId });
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify key matches stored hash
|
// Verify key matches stored hash
|
||||||
const providedHash = crypto.createHash('sha256').update(key).digest('hex');
|
const providedHash = crypto.createHash('sha256').update(key).digest('hex');
|
||||||
if (!crypto.timingSafeEqual(Buffer.from(storedHash), Buffer.from(providedHash))) {
|
if (!crypto.timingSafeEqual(Buffer.from(storedHash), Buffer.from(providedHash))) {
|
||||||
console.log(`[AuthManager] API key hash mismatch: ${keyId}`);
|
log.info('auth', 'API key hash mismatch', { keyId });
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get metadata
|
// Get metadata
|
||||||
const metadata = await this.getKeyMetadata(keyId);
|
const metadata = await this.getKeyMetadata(keyId);
|
||||||
if (!metadata) {
|
if (!metadata) {
|
||||||
console.log(`[AuthManager] API key metadata not found: ${keyId}`);
|
log.info('auth', 'API key metadata not found', { keyId });
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update last used timestamp (non-blocking)
|
// Update last used timestamp (non-blocking)
|
||||||
this.updateLastUsed(keyId, metadata).catch(err =>
|
this.updateLastUsed(keyId, metadata).catch(err =>
|
||||||
console.error(`[AuthManager] Failed to update lastUsed for ${keyId}:`, err.message)
|
log.error('auth', err, { keyId, operation: 'updateLastUsed' })
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(`[AuthManager] API key verified: ${metadata.name} (${keyId})`);
|
log.info('auth', 'API key verified', { name: metadata.name, keyId });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
keyId,
|
keyId,
|
||||||
@@ -185,7 +186,7 @@ class AuthManager {
|
|||||||
name: metadata.name
|
name: metadata.name
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[AuthManager] API key verification failed:', error.message);
|
log.error('auth', error, { operation: 'apiKeyVerify' });
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -205,10 +206,10 @@ class AuthManager {
|
|||||||
|
|
||||||
this.keyMetadataCache.delete(keyId);
|
this.keyMetadataCache.delete(keyId);
|
||||||
|
|
||||||
console.log(`[AuthManager] Revoked API key: ${keyId}`);
|
log.info('auth', 'Revoked API key', { keyId });
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[AuthManager] Failed to revoke API key ${keyId}:`, error.message);
|
log.error('auth', error, { keyId, operation: 'revoke' });
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -233,7 +234,7 @@ class AuthManager {
|
|||||||
|
|
||||||
return keys;
|
return keys;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[AuthManager] Failed to list API keys:', error.message);
|
log.error('auth', error, { operation: 'listApiKeys' });
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -262,7 +263,7 @@ class AuthManager {
|
|||||||
|
|
||||||
return metadata;
|
return metadata;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[AuthManager] Failed to get metadata for ${keyId}:`, error.message);
|
log.error('auth', error, { keyId, operation: 'getMetadata' });
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -285,7 +286,7 @@ class AuthManager {
|
|||||||
|
|
||||||
this.keyMetadataCache.set(keyId, updatedMetadata);
|
this.keyMetadataCache.set(keyId, updatedMetadata);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[AuthManager] Failed to update lastUsed for ${keyId}:`, error.message);
|
log.error('auth', error, { keyId, operation: 'updateLastUsed' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,7 +295,7 @@ class AuthManager {
|
|||||||
*/
|
*/
|
||||||
clearCache() {
|
clearCache() {
|
||||||
this.keyMetadataCache.clear();
|
this.keyMetadataCache.clear();
|
||||||
console.log('[AuthManager] Cache cleared');
|
log.info('auth', 'Cache cleared');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ const keychainManager = require('../security/keychain-manager');
|
|||||||
const cryptoUtils = require('../security/crypto-utils');
|
const cryptoUtils = require('../security/crypto-utils');
|
||||||
const lockfile = require('proper-lockfile');
|
const lockfile = require('proper-lockfile');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
const { log } = require('../utils/logging');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const platformPaths = require('../../platform-paths');
|
const platformPaths = require('../../platform-paths');
|
||||||
|
|
||||||
@@ -33,7 +34,7 @@ class CredentialManager {
|
|||||||
stale: 30000
|
stale: 30000
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log(`[CredentialManager] Initialized with ${this.useKeychain ? 'OS keychain' : 'encrypted file'} storage`);
|
log.info('cred', 'Initialized', { storage: this.useKeychain ? 'keychain' : 'file' });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -60,19 +61,19 @@ class CredentialManager {
|
|||||||
// Store metadata separately in file
|
// Store metadata separately in file
|
||||||
await this.storeMetadata(key, metadata);
|
await this.storeMetadata(key, metadata);
|
||||||
this.cache.set(key, { value, exp: Date.now() + this.CACHE_TTL_MS });
|
this.cache.set(key, { value, exp: Date.now() + this.CACHE_TTL_MS });
|
||||||
console.log(`[CredentialManager] Stored '${key}' in OS keychain`);
|
log.info('cred', 'Stored credential in keychain', { key });
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
console.warn(`[CredentialManager] Keychain storage failed for '${key}', falling back to encrypted file`);
|
log.warn('cred', 'Keychain storage failed, falling back to encrypted file', { key });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback to encrypted file storage
|
// Fallback to encrypted file storage
|
||||||
await this.storeInFile(key, value, metadata);
|
await this.storeInFile(key, value, metadata);
|
||||||
this.cache.set(key, { value, exp: Date.now() + this.CACHE_TTL_MS });
|
this.cache.set(key, { value, exp: Date.now() + this.CACHE_TTL_MS });
|
||||||
console.log(`[CredentialManager] Stored '${key}' in encrypted file`);
|
log.info('cred', 'Stored credential in encrypted file', { key });
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[CredentialManager] Failed to store '${key}':`, error.message);
|
log.error('cred', error, { key, operation: 'store' });
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -109,7 +110,7 @@ class CredentialManager {
|
|||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[CredentialManager] Failed to retrieve '${key}':`, error.message);
|
log.error('cred', error, { key, operation: 'retrieve' });
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -132,10 +133,10 @@ class CredentialManager {
|
|||||||
// Remove from file storage
|
// Remove from file storage
|
||||||
await this.deleteFromFile(key);
|
await this.deleteFromFile(key);
|
||||||
|
|
||||||
console.log(`[CredentialManager] Deleted '${key}'`);
|
log.info('cred', 'Deleted credential', { key });
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[CredentialManager] Failed to delete '${key}':`, error.message);
|
log.error('cred', error, { key, operation: 'delete' });
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -149,7 +150,7 @@ class CredentialManager {
|
|||||||
const credentials = await this.loadCredentialsFile();
|
const credentials = await this.loadCredentialsFile();
|
||||||
return Object.keys(credentials);
|
return Object.keys(credentials);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[CredentialManager] Failed to list credentials:', error.message);
|
log.error('cred', error, { operation: 'list' });
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -175,7 +176,7 @@ class CredentialManager {
|
|||||||
async rotateEncryptionKey() {
|
async rotateEncryptionKey() {
|
||||||
let release;
|
let release;
|
||||||
try {
|
try {
|
||||||
console.log('[CredentialManager] Starting encryption key rotation...');
|
log.info('cred', 'Starting encryption key rotation');
|
||||||
|
|
||||||
// Ensure file exists before locking
|
// Ensure file exists before locking
|
||||||
this._ensureFileExists();
|
this._ensureFileExists();
|
||||||
@@ -186,7 +187,7 @@ class CredentialManager {
|
|||||||
const keys = Object.keys(credentials);
|
const keys = Object.keys(credentials);
|
||||||
|
|
||||||
if (keys.length === 0) {
|
if (keys.length === 0) {
|
||||||
console.log('[CredentialManager] No credentials to rotate');
|
log.info('cred', 'No credentials to rotate');
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,10 +220,10 @@ class CredentialManager {
|
|||||||
// Clear cache to force reload
|
// Clear cache to force reload
|
||||||
this.cache.clear();
|
this.cache.clear();
|
||||||
|
|
||||||
console.log(`[CredentialManager] Successfully rotated ${keys.length} credentials`);
|
log.info('cred', 'Rotated credentials', { count: keys.length });
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[CredentialManager] Key rotation failed:', error.message);
|
log.error('cred', error, { operation: 'rotate' });
|
||||||
return false;
|
return false;
|
||||||
} finally {
|
} finally {
|
||||||
if (release) {
|
if (release) {
|
||||||
@@ -255,12 +256,12 @@ class CredentialManager {
|
|||||||
|
|
||||||
if (migrated > 0) {
|
if (migrated > 0) {
|
||||||
this.cache.clear();
|
this.cache.clear();
|
||||||
console.log(`[CredentialManager] Migrated ${migrated} plaintext credentials to encrypted format`);
|
log.info('cred', 'Migrated plaintext credentials', { count: migrated });
|
||||||
}
|
}
|
||||||
|
|
||||||
return { migrated, skipped, total: migrated + skipped };
|
return { migrated, skipped, total: migrated + skipped };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[CredentialManager] Migration failed:', error.message);
|
log.error('cred', error, { operation: 'migrate' });
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -365,14 +366,11 @@ class CredentialManager {
|
|||||||
// Most common cause: the encryption key on disk is different from
|
// Most common cause: the encryption key on disk is different from
|
||||||
// the key that originally encrypted this entry (rotated by a
|
// the key that originally encrypted this entry (rotated by a
|
||||||
// container recreate that didn't preserve CREDENTIALS_FILE env).
|
// container recreate that didn't preserve CREDENTIALS_FILE env).
|
||||||
console.warn(
|
log.warn('cred', 'Credential present but cannot be decrypted (likely encryption-key mismatch)', { key, error: decryptErr.message });
|
||||||
`[CredentialManager] '${key}' is present but cannot be decrypted ` +
|
|
||||||
`(likely encryption-key mismatch): ${decryptErr.message}`
|
|
||||||
);
|
|
||||||
return { status: 'unreadable', value: null, error: decryptErr.message };
|
return { status: 'unreadable', value: null, error: decryptErr.message };
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`[CredentialManager] diagnose('${key}') failed:`, err.message);
|
log.error('cred', err, { key, operation: 'diagnose' });
|
||||||
return { status: 'malformed', value: null, error: err.message };
|
return { status: 'malformed', value: null, error: err.message };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -404,7 +402,7 @@ class CredentialManager {
|
|||||||
const data = fs.readFileSync(CREDENTIALS_FILE, 'utf8');
|
const data = fs.readFileSync(CREDENTIALS_FILE, 'utf8');
|
||||||
return JSON.parse(data);
|
return JSON.parse(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[CredentialManager] Failed to load credentials file:', error.message);
|
log.error('cred', error, { operation: 'loadFile' });
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -440,10 +438,10 @@ class CredentialManager {
|
|||||||
await this._lockedUpdate(() => backup.credentials);
|
await this._lockedUpdate(() => backup.credentials);
|
||||||
this.cache.clear();
|
this.cache.clear();
|
||||||
|
|
||||||
console.log('[CredentialManager] Successfully imported backup');
|
log.info('cred', 'Successfully imported backup');
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[CredentialManager] Failed to import backup:', error.message);
|
log.error('cred', error, { operation: 'importBackup' });
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user