@@ -35,12 +35,7 @@ const pkg = require('../package.json');
3535const inquirer = require ( './inquirer' ) ;
3636const { stringifyStream } = require ( '@discoveryjs/json-ext' ) ;
3737const progress = require ( 'cli-progress' ) ;
38- let keytar = null ;
39- try {
40- keytar = require ( 'keytar' ) ;
41- } catch {
42- // keytar cannot be imported because the library is not provided for this operating system / architecture
43- }
38+ const secureStorage = require ( './secure-storage' ) ;
4439
4540const DEBUG = require ( './debug' ) ;
4641const stream = require ( 'node:stream' ) ;
@@ -96,9 +91,29 @@ const ENVIRONMENTS_FILE_PATH = path.join(
9691 CONFIG_FOLDER_PATH ,
9792 'box_environments.json'
9893) ;
94+ const ENVIRONMENTS_KEYCHAIN_SERVICE = 'boxcli' ;
95+ const ENVIRONMENTS_KEYCHAIN_ACCOUNT = 'Box' ;
9996
10097const DEFAULT_ANALYTICS_CLIENT_NAME = 'box-cli' ;
10198
99+ /**
100+ * Convert error objects to a stable debug-safe shape.
101+ *
102+ * @param {unknown } error A caught error object
103+ * @returns {Object } A reduced object for DEBUG logging
104+ */
105+ function getDebugErrorDetails ( error ) {
106+ if ( ! error || typeof error !== 'object' ) {
107+ return { message : String ( error ) } ;
108+ }
109+ return {
110+ name : error . name || 'Error' ,
111+ code : error . code ,
112+ message : error . message || String ( error ) ,
113+ stack : error . stack ,
114+ } ;
115+ }
116+
102117/**
103118 * Parse a string value from CSV into the correct boolean value
104119 * @param {string|boolean } value The value to parse
@@ -300,8 +315,7 @@ class BoxCommand extends Command {
300315 this . disableRequiredArgsAndFlags ( ) ;
301316 }
302317
303- this . supportsSecureStorage =
304- keytar && [ 'darwin' , 'win32' , 'linux' ] . includes ( process . platform ) ;
318+ this . supportsSecureStorage = secureStorage . available ;
305319
306320 let { flags, args } = await this . parse ( this . constructor ) ;
307321
@@ -1809,34 +1823,62 @@ class BoxCommand extends Command {
18091823 * @returns {Object } The parsed environment information
18101824 */
18111825 async getEnvironments ( ) {
1812- // Try secure storage first on supported platforms
18131826 if ( this . supportsSecureStorage ) {
1827+ DEBUG . init (
1828+ 'Attempting secure storage read via %s service="%s" account="%s"' ,
1829+ secureStorage . backend ,
1830+ ENVIRONMENTS_KEYCHAIN_SERVICE ,
1831+ ENVIRONMENTS_KEYCHAIN_ACCOUNT
1832+ ) ;
18141833 try {
1815- const password = await keytar . getPassword (
1816- 'boxcli' /* service */ ,
1817- 'Box' /* account */
1834+ const password = await secureStorage . getPassword (
1835+ ENVIRONMENTS_KEYCHAIN_SERVICE ,
1836+ ENVIRONMENTS_KEYCHAIN_ACCOUNT
18181837 ) ;
18191838 if ( password ) {
1839+ DEBUG . init (
1840+ 'Successfully loaded environments from secure storage (%s)' ,
1841+ secureStorage . backend
1842+ ) ;
18201843 return JSON . parse ( password ) ;
18211844 }
1845+ DEBUG . init (
1846+ 'Secure storage returned empty result for service="%s" account="%s"' ,
1847+ ENVIRONMENTS_KEYCHAIN_SERVICE ,
1848+ ENVIRONMENTS_KEYCHAIN_ACCOUNT
1849+ ) ;
18221850 } catch ( error ) {
18231851 DEBUG . init (
1824- 'Failed to read from secure storage, falling back to file: %s' ,
1825- error . message
1852+ 'Failed to read from secure storage (%s), falling back to file: %O' ,
1853+ secureStorage . backend ,
1854+ getDebugErrorDetails ( error )
18261855 ) ;
1827- // fallback to env file
18281856 }
1857+ } else {
1858+ DEBUG . init (
1859+ 'Skipping secure storage read: platform=%s available=%s' ,
1860+ process . platform ,
1861+ secureStorage . available
1862+ ) ;
18291863 }
18301864
18311865 // Try to read from file (fallback or no secure storage)
18321866 try {
18331867 if ( fs . existsSync ( ENVIRONMENTS_FILE_PATH ) ) {
1868+ DEBUG . init (
1869+ 'Attempting environments fallback file read at %s' ,
1870+ ENVIRONMENTS_FILE_PATH
1871+ ) ;
18341872 return JSON . parse ( fs . readFileSync ( ENVIRONMENTS_FILE_PATH ) ) ;
18351873 }
1874+ DEBUG . init (
1875+ 'Environments fallback file does not exist at %s' ,
1876+ ENVIRONMENTS_FILE_PATH
1877+ ) ;
18361878 } catch ( error ) {
18371879 DEBUG . init (
1838- 'Failed to read environments from file: %s ' ,
1839- error . message
1880+ 'Failed to read environments from file: %O ' ,
1881+ getDebugErrorDetails ( error )
18401882 ) ;
18411883 }
18421884
@@ -1861,32 +1903,43 @@ class BoxCommand extends Command {
18611903
18621904 let storedInSecureStorage = false ;
18631905
1864- // Try secure storage first on supported platforms
18651906 if ( this . supportsSecureStorage ) {
1907+ DEBUG . init (
1908+ 'Attempting secure storage write via %s service="%s" account="%s"' ,
1909+ secureStorage . backend ,
1910+ ENVIRONMENTS_KEYCHAIN_SERVICE ,
1911+ ENVIRONMENTS_KEYCHAIN_ACCOUNT
1912+ ) ;
18661913 try {
1867- await keytar . setPassword (
1868- 'boxcli' /* service */ ,
1869- 'Box' /* account */ ,
1870- JSON . stringify ( environments ) /* password */
1914+ await secureStorage . setPassword (
1915+ ENVIRONMENTS_KEYCHAIN_SERVICE ,
1916+ ENVIRONMENTS_KEYCHAIN_ACCOUNT ,
1917+ JSON . stringify ( environments )
18711918 ) ;
18721919 storedInSecureStorage = true ;
18731920 DEBUG . init (
1874- 'Stored environment configuration in secure storage'
1921+ 'Stored environment configuration in secure storage (%s)' ,
1922+ secureStorage . backend
18751923 ) ;
1876- // Successfully stored in secure storage, remove the file
18771924 if ( fs . existsSync ( ENVIRONMENTS_FILE_PATH ) ) {
18781925 fs . unlinkSync ( ENVIRONMENTS_FILE_PATH ) ;
18791926 DEBUG . init (
18801927 'Removed environment configuration file after migrating to secure storage'
18811928 ) ;
18821929 }
1883- } catch ( keytarError ) {
1884- // fallback to file storage if secure storage fails
1930+ } catch ( error ) {
18851931 DEBUG . init (
1886- 'Could not store credentials in secure storage, falling back to file: %s' ,
1887- keytarError . message
1932+ 'Could not store credentials in secure storage (%s), falling back to file: %O' ,
1933+ secureStorage . backend ,
1934+ getDebugErrorDetails ( error )
18881935 ) ;
18891936 }
1937+ } else {
1938+ DEBUG . init (
1939+ 'Skipping secure storage write: platform=%s available=%s' ,
1940+ process . platform ,
1941+ secureStorage . available
1942+ ) ;
18901943 }
18911944
18921945 // Write to file if secure storage failed or not available
@@ -1895,13 +1948,10 @@ class BoxCommand extends Command {
18951948 let fileContents = JSON . stringify ( environments , null , 4 ) ;
18961949 fs . writeFileSync ( ENVIRONMENTS_FILE_PATH , fileContents , 'utf8' ) ;
18971950
1898- // Show warning to user if secure storage was attempted but failed
1899- if ( this . supportsSecureStorage ) {
1951+ if ( process . platform === 'linux' && this . supportsSecureStorage ) {
19001952 this . info (
1901- `Could not store credentials in secure storage, falling back to file.` +
1902- ( process . platform === 'linux'
1903- ? ' To enable secure storage on Linux, install libsecret-1-dev package.'
1904- : '' )
1953+ 'Could not store credentials in secure storage, falling back to file.' +
1954+ ' To enable secure storage on Linux, install libsecret-1-dev package.'
19051955 ) ;
19061956 }
19071957 } catch ( error ) {
0 commit comments