@snapback/cli 1.0.2 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/SkippedTestDetector-B3JZUE5G.js +5 -0
- package/dist/{SkippedTestDetector-JY4EF5BN.js.map → SkippedTestDetector-B3JZUE5G.js.map} +1 -1
- package/dist/{analysis-B4NVULM4.js → analysis-Z53F5FT2.js} +6 -5
- package/dist/{analysis-B4NVULM4.js.map → analysis-Z53F5FT2.js.map} +1 -1
- package/dist/{chunk-BCIXMIPW.js → chunk-6MR2TINI.js} +4 -3
- package/dist/chunk-6MR2TINI.js.map +1 -0
- package/dist/{chunk-WCQVDF3K.js → chunk-BW7RALUZ.js} +3 -2
- package/dist/{chunk-WCQVDF3K.js.map → chunk-BW7RALUZ.js.map} +1 -1
- package/dist/{chunk-VSJ33PLA.js → chunk-G7QXHNGB.js} +5 -4
- package/dist/chunk-G7QXHNGB.js.map +1 -0
- package/dist/{chunk-MTQ6ESQR.js → chunk-ISVRGBWT.js} +6 -5
- package/dist/chunk-ISVRGBWT.js.map +1 -0
- package/dist/{chunk-KSPLKCVF.js → chunk-NKBZIXCN.js} +7 -6
- package/dist/chunk-NKBZIXCN.js.map +1 -0
- package/dist/{chunk-RU7BOXR3.js → chunk-P2F6HU3P.js} +4 -3
- package/dist/chunk-P2F6HU3P.js.map +1 -0
- package/dist/{chunk-BJS6XH2V.js → chunk-QAKFE3NE.js} +4 -3
- package/dist/chunk-QAKFE3NE.js.map +1 -0
- package/dist/{chunk-WALLF2AH.js → chunk-YOVA65PS.js} +4 -3
- package/dist/chunk-YOVA65PS.js.map +1 -0
- package/dist/{dist-FBRR6YHP.js → dist-7UKXVKH3.js} +5 -4
- package/dist/{dist-7GPVXUEA.js.map → dist-7UKXVKH3.js.map} +1 -1
- package/dist/{dist-7GPVXUEA.js → dist-JX77JABV.js} +5 -4
- package/dist/{dist-DVM64QIS.js.map → dist-JX77JABV.js.map} +1 -1
- package/dist/{dist-DVM64QIS.js → dist-WKLJSPJT.js} +8 -7
- package/dist/{dist-FBRR6YHP.js.map → dist-WKLJSPJT.js.map} +1 -1
- package/dist/index.js +19 -18
- package/dist/index.js.map +1 -1
- package/dist/{secure-credentials-YKZHAZNB.js → secure-credentials-6UMEU22H.js} +4 -3
- package/dist/secure-credentials-6UMEU22H.js.map +1 -0
- package/dist/{snapback-dir-4QRR2IPV.js → snapback-dir-T3CRQRY6.js} +6 -5
- package/dist/{snapback-dir-4QRR2IPV.js.map → snapback-dir-T3CRQRY6.js.map} +1 -1
- package/package.json +5 -5
- package/dist/SkippedTestDetector-JY4EF5BN.js +0 -4
- package/dist/chunk-BCIXMIPW.js.map +0 -1
- package/dist/chunk-BJS6XH2V.js.map +0 -1
- package/dist/chunk-KSPLKCVF.js.map +0 -1
- package/dist/chunk-MTQ6ESQR.js.map +0 -1
- package/dist/chunk-RU7BOXR3.js.map +0 -1
- package/dist/chunk-VSJ33PLA.js.map +0 -1
- package/dist/chunk-WALLF2AH.js.map +0 -1
- package/dist/secure-credentials-YKZHAZNB.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/services/secure-credentials.ts"],"names":["SERVICE_NAME","ACCOUNT_NAME","ENCRYPTION_ALGORITHM","KEY_LENGTH","IV_LENGTH","AUTH_TAG_LENGTH","SALT_LENGTH","GLOBAL_DIR","join","homedir","CREDENTIALS_FILE","ENCRYPTED_CREDENTIALS_FILE","createKeytarProvider","keytar","name","isAvailable","getPassword","service","account","setPassword","password","deletePassword","deriveMachineKey","salt","machineData","hostname","platform","userInfo","username","process","arch","scryptSync","encryptCredentials","credentials","key","iv","randomBytes","cipher","createCipheriv","plaintext","JSON","stringify","encrypted","Buffer","concat","update","final","authTag","getAuthTag","decryptCredentials","data","subarray","decipher","createDecipheriv","setAuthTag","decrypted","parse","toString","createEncryptedFileProvider","_service","_account","readFile","mkdir","dirname","recursive","writeFile","unlink","SecureCredentialsManager","provider","initialized","initialize","keytarProvider","getProviderName","getCredentials","stored","legacy","getLegacyCredentials","setCredentials","content","Error","clearCredentials","isLoggedIn","accessToken","expiresAt","Date","secureCredentialsManager","getSecureCredentials","getCredentialsSecure","saveCredentialsSecure","clearCredentialsSecure","isLoggedInSecure"],"mappings":"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","file":"secure-credentials-YKZHAZNB.js","sourcesContent":["/**\n * Secure Credentials Storage for SnapBack CLI\n *\n * FIX 4: Implements OS keychain storage with file fallback\n *\n * Security Hierarchy:\n * 1. OS Keychain (macOS Keychain, Windows Credential Manager, Linux Secret Service)\n * 2. Encrypted file fallback (AES-256-GCM with machine-derived key)\n * 3. Plain text fallback (development only, with warning)\n *\n * @module services/secure-credentials\n */\n\nimport { createCipheriv, createDecipheriv, randomBytes, scryptSync } from \"node:crypto\";\nimport { mkdir, readFile, unlink, writeFile } from \"node:fs/promises\";\nimport { homedir, hostname, platform, userInfo } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport type { GlobalCredentials } from \"./snapback-dir\";\n\n// =============================================================================\n// CONSTANTS\n// =============================================================================\n\nconst SERVICE_NAME = \"snapback-cli\";\nconst ACCOUNT_NAME = \"default\";\nconst ENCRYPTION_ALGORITHM = \"aes-256-gcm\";\nconst KEY_LENGTH = 32;\nconst IV_LENGTH = 12;\nconst AUTH_TAG_LENGTH = 16;\nconst SALT_LENGTH = 32;\n\n// File paths\nconst GLOBAL_DIR = join(homedir(), \".snapback\");\nconst CREDENTIALS_FILE = join(GLOBAL_DIR, \"credentials.json\");\nconst ENCRYPTED_CREDENTIALS_FILE = join(GLOBAL_DIR, \"credentials.enc\");\n\n// =============================================================================\n// KEYCHAIN INTERFACE\n// =============================================================================\n\n/**\n * Keychain abstraction interface\n * Allows for different implementations based on availability\n */\ninterface KeychainProvider {\n\tname: string;\n\tisAvailable(): Promise<boolean>;\n\tgetPassword(service: string, account: string): Promise<string | null>;\n\tsetPassword(service: string, account: string, password: string): Promise<void>;\n\tdeletePassword(service: string, account: string): Promise<boolean>;\n}\n\n// =============================================================================\n// KEYTAR PROVIDER (OS KEYCHAIN)\n// =============================================================================\n\n/**\n * Keytar-based keychain provider\n * Uses OS-level secure credential storage\n */\nasync function createKeytarProvider(): Promise<KeychainProvider | null> {\n\ttry {\n\t\t// Dynamic import to handle missing keytar gracefully\n\t\t// @ts-expect-error - keytar is optional dependency\n\t\tconst keytar = await import(\"keytar\");\n\n\t\treturn {\n\t\t\tname: \"keytar\",\n\t\t\tasync isAvailable(): Promise<boolean> {\n\t\t\t\ttry {\n\t\t\t\t\t// Test availability by trying a no-op operation\n\t\t\t\t\tawait keytar.getPassword(\"__snapback_test__\", \"__test__\");\n\t\t\t\t\treturn true;\n\t\t\t\t} catch {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t},\n\t\t\tasync getPassword(service: string, account: string): Promise<string | null> {\n\t\t\t\treturn keytar.getPassword(service, account);\n\t\t\t},\n\t\t\tasync setPassword(service: string, account: string, password: string): Promise<void> {\n\t\t\t\tawait keytar.setPassword(service, account, password);\n\t\t\t},\n\t\t\tasync deletePassword(service: string, account: string): Promise<boolean> {\n\t\t\t\treturn keytar.deletePassword(service, account);\n\t\t\t},\n\t\t};\n\t} catch {\n\t\t// keytar not available (not installed or native module issues)\n\t\treturn null;\n\t}\n}\n\n// =============================================================================\n// ENCRYPTED FILE PROVIDER\n// =============================================================================\n\n/**\n * Derive an encryption key from machine-specific data\n * This provides defense-in-depth even if the file is copied to another machine\n */\nfunction deriveMachineKey(salt: Buffer): Buffer {\n\t// Combine machine-specific values for key derivation\n\tconst machineData = [\n\t\thostname(),\n\t\tplatform(),\n\t\tuserInfo().username,\n\t\thomedir(),\n\t\t// Add some entropy from process info\n\t\tprocess.arch,\n\t\tprocess.platform,\n\t].join(\"|\");\n\n\treturn scryptSync(machineData, salt, KEY_LENGTH);\n}\n\n/**\n * Encrypt credentials with machine-derived key\n */\nfunction encryptCredentials(credentials: GlobalCredentials, salt: Buffer): Buffer {\n\tconst key = deriveMachineKey(salt);\n\tconst iv = randomBytes(IV_LENGTH);\n\tconst cipher = createCipheriv(ENCRYPTION_ALGORITHM, key, iv);\n\n\tconst plaintext = JSON.stringify(credentials);\n\tconst encrypted = Buffer.concat([cipher.update(plaintext, \"utf8\"), cipher.final()]);\n\tconst authTag = cipher.getAuthTag();\n\n\t// Format: salt (32) + iv (12) + authTag (16) + encrypted data\n\treturn Buffer.concat([salt, iv, authTag, encrypted]);\n}\n\n/**\n * Decrypt credentials with machine-derived key\n */\nfunction decryptCredentials(data: Buffer): GlobalCredentials {\n\t// Extract components\n\tconst salt = data.subarray(0, SALT_LENGTH);\n\tconst iv = data.subarray(SALT_LENGTH, SALT_LENGTH + IV_LENGTH);\n\tconst authTag = data.subarray(SALT_LENGTH + IV_LENGTH, SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH);\n\tconst encrypted = data.subarray(SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH);\n\n\tconst key = deriveMachineKey(salt);\n\tconst decipher = createDecipheriv(ENCRYPTION_ALGORITHM, key, iv);\n\tdecipher.setAuthTag(authTag);\n\n\tconst decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);\n\treturn JSON.parse(decrypted.toString(\"utf8\")) as GlobalCredentials;\n}\n\n/**\n * Encrypted file provider\n * Uses AES-256-GCM with machine-derived key\n */\nfunction createEncryptedFileProvider(): KeychainProvider {\n\treturn {\n\t\tname: \"encrypted-file\",\n\t\tasync isAvailable(): Promise<boolean> {\n\t\t\treturn true; // Always available as fallback\n\t\t},\n\t\tasync getPassword(_service: string, _account: string): Promise<string | null> {\n\t\t\ttry {\n\t\t\t\tconst data = await readFile(ENCRYPTED_CREDENTIALS_FILE);\n\t\t\t\tconst credentials = decryptCredentials(data);\n\t\t\t\treturn JSON.stringify(credentials);\n\t\t\t} catch {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t},\n\t\tasync setPassword(_service: string, _account: string, password: string): Promise<void> {\n\t\t\tconst credentials = JSON.parse(password) as GlobalCredentials;\n\t\t\tconst salt = randomBytes(SALT_LENGTH);\n\t\t\tconst encrypted = encryptCredentials(credentials, salt);\n\n\t\t\tawait mkdir(dirname(ENCRYPTED_CREDENTIALS_FILE), { recursive: true });\n\t\t\tawait writeFile(ENCRYPTED_CREDENTIALS_FILE, encrypted);\n\t\t},\n\t\tasync deletePassword(_service: string, _account: string): Promise<boolean> {\n\t\t\ttry {\n\t\t\t\tawait unlink(ENCRYPTED_CREDENTIALS_FILE);\n\t\t\t\treturn true;\n\t\t\t} catch {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t},\n\t};\n}\n\n// =============================================================================\n// PLAIN FILE PROVIDER (DEVELOPMENT FALLBACK)\n// =============================================================================\n\n/**\n * Plain file provider (legacy, development only)\n * Shows warning when used in production\n * @deprecated Use encrypted file provider instead\n */\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nfunction _createPlainFileProvider(): KeychainProvider {\n\tlet warningShown = false;\n\n\treturn {\n\t\tname: \"plain-file\",\n\t\tasync isAvailable(): Promise<boolean> {\n\t\t\treturn true;\n\t\t},\n\t\tasync getPassword(_service: string, _account: string): Promise<string | null> {\n\t\t\ttry {\n\t\t\t\tconst content = await readFile(CREDENTIALS_FILE, \"utf8\");\n\t\t\t\treturn content;\n\t\t\t} catch {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t},\n\t\tasync setPassword(_service: string, _account: string, password: string): Promise<void> {\n\t\t\tif (process.env.NODE_ENV === \"production\" && !warningShown) {\n\t\t\t\tconsole.warn(\n\t\t\t\t\t\"\\n⚠️ Warning: Storing credentials in plain text. \" +\n\t\t\t\t\t\t\"Install 'keytar' for OS keychain support: pnpm add keytar\\n\",\n\t\t\t\t);\n\t\t\t\twarningShown = true;\n\t\t\t}\n\n\t\t\tawait mkdir(dirname(CREDENTIALS_FILE), { recursive: true });\n\t\t\tawait writeFile(CREDENTIALS_FILE, password, { mode: 0o600 }); // Restrict file permissions\n\t\t},\n\t\tasync deletePassword(_service: string, _account: string): Promise<boolean> {\n\t\t\ttry {\n\t\t\t\tawait unlink(CREDENTIALS_FILE);\n\t\t\t\treturn true;\n\t\t\t} catch {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t},\n\t};\n}\n\n// =============================================================================\n// SECURE CREDENTIALS MANAGER\n// =============================================================================\n\n/**\n * Secure Credentials Manager\n *\n * Automatically selects the most secure available storage:\n * 1. OS Keychain (via keytar)\n * 2. Encrypted file\n * 3. Plain file (with warning)\n */\nclass SecureCredentialsManager {\n\tprivate provider: KeychainProvider | null = null;\n\tprivate initialized = false;\n\n\t/**\n\t * Initialize the credentials manager\n\t * Selects the best available provider\n\t */\n\tasync initialize(): Promise<void> {\n\t\tif (this.initialized) return;\n\n\t\t// Try keytar first (OS keychain)\n\t\tconst keytarProvider = await createKeytarProvider();\n\t\tif (keytarProvider && (await keytarProvider.isAvailable())) {\n\t\t\tthis.provider = keytarProvider;\n\t\t\tthis.initialized = true;\n\t\t\treturn;\n\t\t}\n\n\t\t// Fall back to encrypted file\n\t\tthis.provider = createEncryptedFileProvider();\n\t\tthis.initialized = true;\n\t}\n\n\t/**\n\t * Get the name of the active provider\n\t */\n\tgetProviderName(): string {\n\t\treturn this.provider?.name ?? \"none\";\n\t}\n\n\t/**\n\t * Get stored credentials\n\t */\n\tasync getCredentials(): Promise<GlobalCredentials | null> {\n\t\tawait this.initialize();\n\t\tif (!this.provider) return null;\n\n\t\tconst stored = await this.provider.getPassword(SERVICE_NAME, ACCOUNT_NAME);\n\t\tif (!stored) {\n\t\t\t// Check legacy plain file as migration fallback\n\t\t\tconst legacy = await this.getLegacyCredentials();\n\t\t\tif (legacy) {\n\t\t\t\t// Migrate to secure storage\n\t\t\t\tawait this.setCredentials(legacy);\n\t\t\t\t// Delete legacy file after successful migration\n\t\t\t\ttry {\n\t\t\t\t\tawait unlink(CREDENTIALS_FILE);\n\t\t\t\t} catch {\n\t\t\t\t\t// Ignore if file doesn't exist\n\t\t\t\t}\n\t\t\t\treturn legacy;\n\t\t\t}\n\t\t\treturn null;\n\t\t}\n\n\t\ttry {\n\t\t\treturn JSON.parse(stored) as GlobalCredentials;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * Get legacy plain-text credentials for migration\n\t */\n\tprivate async getLegacyCredentials(): Promise<GlobalCredentials | null> {\n\t\ttry {\n\t\t\tconst content = await readFile(CREDENTIALS_FILE, \"utf8\");\n\t\t\treturn JSON.parse(content) as GlobalCredentials;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * Save credentials securely\n\t */\n\tasync setCredentials(credentials: GlobalCredentials): Promise<void> {\n\t\tawait this.initialize();\n\t\tif (!this.provider) {\n\t\t\tthrow new Error(\"No credentials provider available\");\n\t\t}\n\n\t\tawait this.provider.setPassword(SERVICE_NAME, ACCOUNT_NAME, JSON.stringify(credentials));\n\t}\n\n\t/**\n\t * Clear stored credentials\n\t */\n\tasync clearCredentials(): Promise<void> {\n\t\tawait this.initialize();\n\t\tif (!this.provider) return;\n\n\t\tawait this.provider.deletePassword(SERVICE_NAME, ACCOUNT_NAME);\n\n\t\t// Also clean up any legacy files\n\t\ttry {\n\t\t\tawait unlink(CREDENTIALS_FILE);\n\t\t} catch {\n\t\t\t// Ignore\n\t\t}\n\t\ttry {\n\t\t\tawait unlink(ENCRYPTED_CREDENTIALS_FILE);\n\t\t} catch {\n\t\t\t// Ignore\n\t\t}\n\t}\n\n\t/**\n\t * Check if user is logged in\n\t */\n\tasync isLoggedIn(): Promise<boolean> {\n\t\tconst credentials = await this.getCredentials();\n\t\tif (!credentials?.accessToken) return false;\n\n\t\t// Check if token is expired\n\t\tif (credentials.expiresAt) {\n\t\t\tconst expiresAt = new Date(credentials.expiresAt);\n\t\t\tif (expiresAt < new Date()) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\treturn true;\n\t}\n}\n\n// =============================================================================\n// EXPORTS\n// =============================================================================\n\n// Singleton instance\nlet secureCredentialsManager: SecureCredentialsManager | null = null;\n\n/**\n * Get the secure credentials manager singleton\n */\nexport function getSecureCredentials(): SecureCredentialsManager {\n\tif (!secureCredentialsManager) {\n\t\tsecureCredentialsManager = new SecureCredentialsManager();\n\t}\n\treturn secureCredentialsManager;\n}\n\n/**\n * Secure versions of credential functions (drop-in replacements)\n */\nexport async function getCredentialsSecure(): Promise<GlobalCredentials | null> {\n\treturn getSecureCredentials().getCredentials();\n}\n\nexport async function saveCredentialsSecure(credentials: GlobalCredentials): Promise<void> {\n\treturn getSecureCredentials().setCredentials(credentials);\n}\n\nexport async function clearCredentialsSecure(): Promise<void> {\n\treturn getSecureCredentials().clearCredentials();\n}\n\nexport async function isLoggedInSecure(): Promise<boolean> {\n\treturn getSecureCredentials().isLoggedIn();\n}\n\nexport { SecureCredentialsManager };\n"]}
|