Source: composer-common/lib/filewallet.js

/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

'use strict';

const fs = require('fs');
const homedir = require('homedir');
const Logger = require('./log/logger');
const mkdirp = require('mkdirp');
const path = require('path');
const thenify = require('thenify');
const thenifyAll = require('thenify-all');
const Wallet = require('./wallet');

const LOG = Logger.getLog('FileWallet');

/**
 * Class implementing a wallet (a container of credentials) that
 * stores the credentials on the file system.
 * @protected
 */
class FileWallet extends Wallet {

    /**
     * Get the current home directory.
     * @return {string} The current home directory.
     */
    static getHomeDirectory() {
        return homedir();
    }

    /**
     * Constructor.
     * @param {Object} [options] The options to use.
     * @param {string} [options.directory] The directory to store
     * credentials in.
     * @param {Object} [fs] The file system implementation to use.
     */
    constructor(options) {
        super();
        const method = 'constructor';
        LOG.entry(method, options);

        // Generate the directory if not specified in the options.
        options = options || {};
        this.directory = options.directory;
        if (!this.directory) {
            let h = FileWallet.getHomeDirectory();
            if (h) {
                this.directory = path.resolve(h, '.composer-credentials');
            } else {
                this.directory = path.resolve('/', '.composer-credentials');
            }
            LOG.debug(method, 'Generated directory', this.directory);
        }

        // Use the default fs implementation if one is not specified.
        let theFS = options.fs;
        if (!theFS) {
            theFS = fs;
        }

        // Promisify all of the APIs that we want to use.
        this.fs = thenifyAll(theFS, {});
        this.mkdirp = thenify((dir, cb) => {
            return mkdirp(dir, { fs: theFS }, cb);
        });

        LOG.exit(method);
    }

    /**
     * List all of the credentials in the wallet.
     * @return {Promise} A promise that is resolved with
     * an array of credential names, or rejected with an
     * error.
     */
    list() {
        const method = 'list';
        LOG.entry(method);
        const result = [];
        return this.fs.readdir(this.directory)
            .then((files) => {
                files.forEach((file) => {
                    LOG.debug(method, 'Found file', file);
                    result.push(file);
                });
                result.sort();
            })
            .catch((error) => {
                // Ignore any errors.
                LOG.debug(method, 'Ignoring error', error);
            })
            .then(() => {
                LOG.exit(method, result);
                return result;
            });
    }

    /**
     * Check to see if the named credentials are in
     * the wallet.
     * @param {string} name The name of the credentials.
     * @return {Promise} A promise that is resolved with
     * a boolean; true if the named credentials are in the
     * wallet, false otherwise.
     */
    contains(name) {
        const method = 'contains';
        LOG.entry(method, name);
        let result = false;
        const file = path.resolve(this.directory, name);
        return this.fs.readFile(file, 'utf8')
            .then((value) => {
                LOG.debug(method, 'Read file successfully');
                result = true;
            })
            .catch((error) => {
                // Ignore any errors.
                LOG.debug(method, 'Ignoring error', error);
            })
            .then(() => {
                LOG.exit(method, result);
                return result;
            });
    }

    /**
     * Get the named credentials from the wallet.
     * @abstract
     * @param {string} name The name of the credentials.
     * @return {Promise} A promise that is resolved with
     * the named credentials, or rejected with an error.
     */
    get(name) {
        const method = 'get';
        LOG.entry(method, name);
        const file = path.resolve(this.directory, name);
        return this.fs.readFile(file, 'utf8')
            .then((value) => {
                LOG.debug(method, 'Read file successfully');
                LOG.exit(method, value);
                return value;
            })
            .catch((error) => {
                LOG.error(method, error);
                throw error;
            });
    }

    /**
     * Add a new credential to the wallet.
     * @abstract
     * @param {string} name The name of the credentials.
     * @param {string} value The credentials.
     * @return {Promise} A promise that is resolved when
     * complete, or rejected with an error.
     */
    add(name, value) {
        const method = 'add';
        LOG.entry(method, name, value);
        const file = path.resolve(this.directory, name);
        return this.mkdirp(this.directory)
            .then(() => {
                return this.fs.writeFile(file, value, { flag: 'wx', mode: 0o600 });
            })
            .then((value) => {
                LOG.debug(method, 'Wrote file successfully');
                LOG.exit(method);
            })
            .catch((error) => {
                LOG.error(method, error);
                throw error;
            });
    }

    /**
     * Update existing credentials in the wallet.
     * @abstract
     * @param {string} name The name of the credentials.
     * @param {string} value The credentials.
     * @return {Promise} A promise that is resolved when
     * complete, or rejected with an error.
     */
    update(name, value) {
        const method = 'update';
        LOG.entry(method, name, value);
        const file = path.resolve(this.directory, name);
        return this.fs.readFile(file, 'utf8')
            .then(() => {
                LOG.debug(method, 'Read file successfully');
                return this.fs.writeFile(file, value, { mode: 0o600 });
            })
            .then((value) => {
                LOG.debug(method, 'Wrote file successfully');
                LOG.exit(method);
            })
            .catch((error) => {
                LOG.error(method, error);
                throw error;
            });
    }

    /**
     * Remove existing credentials from the wallet.
     * @abstract
     * @param {string} name The name of the credentials.
     * @return {Promise} A promise that is resolved when
     * complete, or rejected with an error.
     */
    remove(name) {
        const method = 'remove';
        LOG.entry(method, name);
        const file = path.resolve(this.directory, name);
        return this.fs.unlink(file)
            .then((value) => {
                LOG.debug(method, 'Removed file successfully');
                LOG.exit(method);
            })
            .catch((error) => {
                LOG.error(method, error);
                throw error;
            });
    }

}

module.exports = FileWallet;