<commit_before>
cdb.ui.common.ShareDialog = cdb.ui.common.Dialog.extend({

  tagName: 'div',
  className: 'cartodb-share-dialog',

  events: {
    'click .ok': '_ok',
    'click .cancel': '_cancel',
    'click .close': '_cancel',
    "click":                      '_stopPropagation',
    "dblclick":                   '_stopPropagation',
    "mousedown":                  '_stopPropagation'
  },

  default_options: {
    title: '',
    description: '',
    ok_title: 'Ok',
    cancel_title: 'Cancel',
    width: 300,
    height: 200,
    clean_on_hide: false,
    enter_to_confirm: false,
    template_name: 'common/views/dialog_base',
    ok_button_classes: 'button green',
    cancel_button_classes: '',
    modal_type: '',
    modal_class: '',
    include_footer: true,
    additionalButtons: []
  },

  initialize: function() {

    _.defaults(this.options, this.default_options);

    _.bindAll(this, 'render', '_keydown');

    this.isOpen = false;

    var self = this;

    if (this.options.target) {
      this.options.target.on("click", function() {
        self.open();
      })
    }

    // Keydown bindings for the dialog
    $(document).bind('keydown', this._keydown);

    // After removing the dialog, cleaning other bindings
    this.bind("clean", this._reClean);

  },

  _stopPropagation: function(ev) {

    ev.stopPropagation();

  },

  open: function() {

    var self = this;

    this.$el.show(0, function(){
      self.isOpen = true;
    });

  },

  hide: function() {

    var self = this;

    this.$el.hide(0, function(){
      self.isOpen = false;
    });

    if (this.options.clean_on_hide) {
      this.clean();
    }

  },

  toggle: function() {

    if (this.isOpen) {
      this.hide();
    } else {
      this.open();
    }

  },

  _truncateTitle: function(s, length) {

    return s.substr(0, length-1) + (s.length > length ? '…' : '');

  },

  render: function() {

    var $el = this.$el;

    var title       = this.options.title;
    var description = this.options.description;
    var share_url   = this.options.share_url;

    var facebook_url, twitter_url;

    this.$el.addClass(this.options.size);

    var full_title    = title + ": " + description;
    var twitter_title;

    if (title && description) {
      twitter_title = this._truncateTitle(title + ": " + description, 112) + " %23map "
    } else if (title) {
      twitter_title = this._truncateTitle(title, 112) + " %23map"
    } else if (description){
      twitter_title = this._truncateTitle(description, 112) + " %23map"
    } else {
      twitter_title = "%23map"
    }

    if (this.options.facebook_url) {
      facebook_url = this.options.facebook_url;
    } else {
      facebook_url = "http://www.facebook.com/sharer.php?u=" + share_url + "&text=" + full_title;
    }

    if (this.options.twitter_url) {
      twitter_url = this.options.twitter_url;
    } else {
      twitter_url = "https://twitter.com/share?url=" + share_url + "&text=" + twitter_title;
    }

    var options = _.extend(this.options, { facebook_url: facebook_url, twitter_url: twitter_url });

    $el.html(this.options.template(options));

    $el.find(".modal").css({
      width: this.options.width
    });

    if (this.render_content) {
      this.$('.content').append(this.render_content());
    }

    if(this.options.modal_class) {
      this.$el.addClass(this.options.modal_class);
    }

    if (this.options.disableLinks) {
      this.$el.find("a").attr("target", "");
    }

    return this;
  }

});
<commit_msg>Stops propagation of the target link<commit_after>
cdb.ui.common.ShareDialog = cdb.ui.common.Dialog.extend({

  tagName: 'div',
  className: 'cartodb-share-dialog',

  events: {
    'click .ok':       '_ok',
    'click .cancel':   '_cancel',
    'click .close':    '_cancel',
    "click":           '_stopPropagation',
    "dblclick":        '_stopPropagation',
    "mousedown":       '_stopPropagation'
  },

  default_options: {
    title: '',
    description: '',
    ok_title: 'Ok',
    cancel_title: 'Cancel',
    width: 300,
    height: 200,
    clean_on_hide: false,
    enter_to_confirm: false,
    template_name: 'common/views/dialog_base',
    ok_button_classes: 'button green',
    cancel_button_classes: '',
    modal_type: '',
    modal_class: '',
    include_footer: true,
    additionalButtons: []
  },

  initialize: function() {

    _.defaults(this.options, this.default_options);

    _.bindAll(this, 'render', '_keydown');

    this.isOpen = false;

    var self = this;

    if (this.options.target) {
      this.options.target.on("click", function(e) {
        e.stopPropagation();

        self.open();

      })
    }

    // Keydown bindings for the dialog
    $(document).bind('keydown', this._keydown);

    // After removing the dialog, cleaning other bindings
    this.bind("clean", this._reClean);

  },

  _stopPropagation: function(ev) {

    ev.stopPropagation();

  },

  open: function() {

    var self = this;

    this.$el.show(0, function(){
      self.isOpen = true;
    });

  },

  hide: function() {

    var self = this;

    this.$el.hide(0, function(){
      self.isOpen = false;
    });

    if (this.options.clean_on_hide) {
      this.clean();
    }

  },

  toggle: function() {

    if (this.isOpen) {
      this.hide();
    } else {
      this.open();
    }

  },

  _truncateTitle: function(s, length) {

    return s.substr(0, length-1) + (s.length > length ? '…' : '');

  },

  render: function() {

    var $el = this.$el;

    var title       = this.options.title;
    var description = this.options.description;
    var share_url   = this.options.share_url;

    var facebook_url, twitter_url;

    this.$el.addClass(this.options.size);

    var full_title    = title + ": " + description;
    var twitter_title;

    if (title && description) {
      twitter_title = this._truncateTitle(title + ": " + description, 112) + " %23map "
    } else if (title) {
      twitter_title = this._truncateTitle(title, 112) + " %23map"
    } else if (description){
      twitter_title = this._truncateTitle(description, 112) + " %23map"
    } else {
      twitter_title = "%23map"
    }

    if (this.options.facebook_url) {
      facebook_url = this.options.facebook_url;
    } else {
      facebook_url = "http://www.facebook.com/sharer.php?u=" + share_url + "&text=" + full_title;
    }

    if (this.options.twitter_url) {
      twitter_url = this.options.twitter_url;
    } else {
      twitter_url = "https://twitter.com/share?url=" + share_url + "&text=" + twitter_title;
    }

    var options = _.extend(this.options, { facebook_url: facebook_url, twitter_url: twitter_url });

    $el.html(this.options.template(options));

    $el.find(".modal").css({
      width: this.options.width
    });

    if (this.render_content) {
      this.$('.content').append(this.render_content());
    }

    if(this.options.modal_class) {
      this.$el.addClass(this.options.modal_class);
    }

    if (this.options.disableLinks) {
      this.$el.find("a").attr("target", "");
    }

    return this;
  }

});
<|endoftext|>


<commit_before><commit_msg>some stuff commented.<commit_after><|endoftext|>


<commit_before>var prefix = function (name) {
  return '_' + name
}

var defined = function (name) {
  return name
}

exports.stringify = function (data) {
  if (typeof data === 'object' && data && data.name) return exports.stringify(data.name, data.protocol, data.subtypes)
  return Array.prototype.concat.apply([], arguments).filter(defined).map(prefix).join('.')
}

exports.parse = function (str) {
  var parts = str.split('.')

  for (var i = 0; i < parts.length; i++) {
    if (parts[i][0] !== '_') return null
    parts[i] = parts[i].slice(1)
  }

  return {
    name: parts.shift(),
    protocol: parts.shift() || null,
    subtypes: parts
  }
}

exports.tcp = function (name) {
  return exports.stringify(name, 'tcp', Array.prototype.concat.apply([], Array.prototype.slice.call(arguments, 1)))
}

exports.udp = function (name) {
  return exports.stringify(name, 'udp', Array.prototype.concat.apply([], Array.prototype.slice.call(arguments, 1)))
}
<commit_msg>underscores not mandatory<commit_after>var prefix = function (name) {
  return '_' + name
}

var defined = function (name) {
  return name
}

exports.stringify = function (data) {
  if (typeof data === 'object' && data && data.name) return exports.stringify(data.name, data.protocol, data.subtypes)
  return Array.prototype.concat.apply([], arguments).filter(defined).map(prefix).join('.')
}

exports.parse = function (str) {
  var parts = str.split('.')

  for (var i = 0; i < parts.length; i++) {
    if (parts[i][0] !== '_') continue
    parts[i] = parts[i].slice(1)
  }

  return {
    name: parts.shift(),
    protocol: parts.shift() || null,
    subtypes: parts
  }
}

exports.tcp = function (name) {
  return exports.stringify(name, 'tcp', Array.prototype.concat.apply([], Array.prototype.slice.call(arguments, 1)))
}

exports.udp = function (name) {
  return exports.stringify(name, 'udp', Array.prototype.concat.apply([], Array.prototype.slice.call(arguments, 1)))
}
<|endoftext|>


<commit_before>var path = require('path');
var spawn = require('child_process').spawn;

function findFreeServernum(servernum, callback) {
  path.exists('/tmp/.X' + servernum + '-lock', function(exists) {
    if(exists) {
      servernum++;
      findFreeServernum(servernum, callback);
      return;
    }
    callback(servernum);
  });
}

module.exports = function (startnum, callback) {
  if (!callback) {
    callback = startnum;
    startnum = 99;
  }

  findFreeServernum(startnum, function(servernum) {
    var childProcess = spawn('Xvfb', [':' + servernum]);
    // assume starting Xvfb takes less than 500 ms and continue if it hasn't
    // exited during that time
    var timeout = setTimeout(function() {
      callback(null, childProcess, servernum);
    }, 500);

    // if Xvfb exits prematurely the servernum wasn't valid.
    // Happens if there's already an X-server running on @servernum but no file was created in /tmp
    childProcess.on('exit', function() {
      clearTimeout(timeout);
      servernum++;
      headless.startXvfb(servernum, callback);
    });
  });
}
<commit_msg>fix stupid bug<commit_after>var path = require('path');
var spawn = require('child_process').spawn;

function findFreeServernum(servernum, callback) {
  path.exists('/tmp/.X' + servernum + '-lock', function(exists) {
    if(exists) {
      servernum++;
      findFreeServernum(servernum, callback);
      return;
    }
    callback(servernum);
  });
}

module.exports = function headless(startnum, callback) {
  if (!callback) {
    callback = startnum;
    startnum = 99;
  }

  findFreeServernum(startnum, function(servernum) {
    var childProcess = spawn('Xvfb', [':' + servernum]);
    // assume starting Xvfb takes less than 500 ms and continue if it hasn't
    // exited during that time
    var timeout = setTimeout(function() {
      callback(null, childProcess, servernum);
    }, 500);

    // if Xvfb exits prematurely the servernum wasn't valid.
    // Happens if there's already an X-server running on @servernum but no file was created in /tmp
    childProcess.on('exit', function() {
      clearTimeout(timeout);
      servernum++;
      headless(servernum, callback);
    });
  });
}
<|endoftext|>


<commit_before>'use strict'

const fs = require('fs')
const path = require('path')
const globby = require('globby')
const marked = require('marked')
const yaml = require('js-yaml')
const mkdirp = require('mkdirp')
const defaultOptions = require('./defaultOptions')

const EXTENSIONS = {
  JSON: '.json',
  MD: '.md',
  YML: '.yml',
}
const NEWLINE = process.platform === 'win32' ? '\r\n' : '\n'
const FRONTMATTER_SEPERATOR = '---' + NEWLINE

const SOURCE_MODE = 'source'

// Main function
function processmd(options, callback) {
  options = Object.assign({}, defaultOptions, options)

  // Init marked.
  let markedOptions = {}
  if (options.highlightCode) {
    try {
      markedOptions = {
        highlight: function(code) {
          return require('highlight.js').highlightAuto(code).value
        },
      }
    } catch (e) {
      console.log(e)
    }
  }
  marked.setOptions(markedOptions)

  options.markdownRenderer = options.markdownRenderer || marked

  const globs = (options.files || []).concat(options._)
  if (globs.length === 0) {
    throw new Error('You must pass file patterns in to be processed.')
  }

  const p = new Promise(function(resolve, reject) {
    globby(globs).then(function(result) {
      const commonDir = findCommonDir(result)
      options._commonDir = commonDir

      if (options.watch) {
        const d = debounce(
          function() {
            processOutput()
          },
          200,
          true
        )

        // fs.watch isn't supported on linux.
        try {
          fs.watch(commonDir, function(event, filename) {
            d()
          })
        } catch (e) {
          console.log(e)
        }
      }

      let processingFunc = processYamlAndMarkdown
      if (typeof options._customProcessingFunc === 'function') {
        processingFunc = options._customProcessingFunc // used for testing.
      } else if (options.convertMode === SOURCE_MODE) {
        processingFunc = processJson
      }

      function processOutput() {
        const summaryObj = {}
        summaryObj.fileMap = {}
        summaryObj.sourceFileArray = result
        let finishCount = 0
        result.forEach(function(file, i) {
          processingFunc(file, options, function(newFile, content) {
            finishCount++

            // Replace backslashes with forward slashes to keep windows consistent.
            const filename = replaceBackslashes(newFile)

            // Remove body props from summary.
            if (options.removeBodyProps) {
              content = removeBodyProps(content)
            }

            summaryObj.fileMap[filename] = options.convertMode === SOURCE_MODE
              ? content
              : JSON.parse(content)

            if (finishCount === result.length) {
              if (options.summaryOutput) {
                writeFileContent(options.summaryOutput, JSON.stringify(summaryObj, null, 2), function(e, d) {
                  resolve(summaryObj)
                })
              } else {
                resolve(summaryObj)
              }
            }
          })
        })
      }

      processOutput()
    })
  })

  // Enable callback support too.
  if (callback) {
    p.then(result => {
      callback(null, result)
    })
  }

  return p
}

function processYamlAndMarkdown(file, options, cb) {
  readFileContent(file, (err, file, fileContent) => {
    const hasFrontmatter = fileContent.indexOf(FRONTMATTER_SEPERATOR) === 0
    const isYaml = file.endsWith('.yaml') || file.endsWith('.yml')
    let content = fileContent.trim()
    let frontmatter = {}
    let jsonData = {}

    // Markdown.
    if (hasFrontmatter) {
      let splitContent = fileContent.split(FRONTMATTER_SEPERATOR)
      // Remove first string in split content which is empty.
      if (splitContent[0] === '') {
        splitContent.shift()
      }
      frontmatter = yaml.safeLoad(splitContent[0])
      content = splitContent[1].trim()
    }

    if (isYaml) {
      jsonData = yaml.safeLoad(content)
    } else {
      jsonData = Object.assign({}, frontmatter, {
        bodyContent: content,
        bodyHtml: options.markdownRenderer(content),
      })
    }

    // Rename to the new file.
    const baseFilename = file.replace(options._commonDir, '')
    const parsedPath = path.parse(path.join(options.outputDir, baseFilename))
    const sourceExt = parsedPath.ext
    const sourceBase = parsedPath.base
    const newPathObj = Object.assign({}, parsedPath, {
      ext: EXTENSIONS.JSON,
      base: options.filenamePrefix +
        parsedPath.base.replace(sourceExt, EXTENSIONS.JSON),
    })
    const newPath = path.format(newPathObj)

    if (options.preview > 0 && jsonData.bodyContent) {
      // TODO: These regular expressions could probably be better.
      jsonData.preview = jsonData.bodyHtml.match(/<p>(.*?)<\/p>/)[1]
      jsonData.preview = jsonData.preview.split(/<[^>]*>/).join('')
      jsonData.preview = jsonData.preview.substring(0, options.preview) + '…'
    }
    if (options.includeTitle && jsonData.bodyContent) {
      jsonData.title = jsonData.title || jsonData.bodyHtml.match(/>(.*?)<\//)[1]
    }
    if (options.includeDir) {
      jsonData.dir = replaceBackslashes(path.dirname(newPath))
    }
    if (options.includeBase) {
      jsonData.base = path.basename(newPath)
    }
    if (options.includeExt) {
      jsonData.ext = EXTENSIONS.JSON
    }
    if (options.includeSourceBase) {
      jsonData.sourceBase = sourceBase
    }
    if (options.includeSourceExt) {
      jsonData.sourceExt = sourceExt
    }

    // TODO: make this a default callback
    // 2 spaces indent for stringify.
    writeFileContent(newPath, JSON.stringify(jsonData, null, 2), function(e, d) {
      cb(newPath, JSON.stringify(jsonData))
    })
  })
}

function processJson(file, options, cb) {
  readFileContent(file, (err, file, fileContent) => {
    const fileData = JSON.parse(fileContent)

    // Process content.
    let newContent = ''
    const cleanProps = cleanFileProps(
      cleanMarkdownProps(Object.assign({}, fileData))
    )
    const cleanYaml = yaml.safeDump(cleanProps)
    let extension = '.yml'
    if (isMarkdown(fileData)) {
      newContent += fileData.bodyContent + NEWLINE
      if (Object.keys(cleanProps).length > 0) {
        newContent =
          FRONTMATTER_SEPERATOR +
          cleanYaml +
          FRONTMATTER_SEPERATOR +
          NEWLINE +
          newContent
      }
      extension = '.md'
    } else {
      newContent = cleanYaml
    }

    // Rename to the new file.
    const baseFilename = file.replace(options._commonDir, '')
    const parsedPath = path.parse(path.join(options.outputDir, baseFilename))
    const sourceExt = parsedPath.ext
    // const sourceBase = parsedPath.base
    const newPathObj = Object.assign({}, parsedPath, {
      ext: extension,
      base: options.filenamePrefix +
        parsedPath.base.replace(sourceExt, extension),
    })
    const newPath = path.format(newPathObj)

    writeFileContent(newPath, newContent, function(e, d) {
      cb(newPath, newContent)
    })
  })
}

function cleanFileProps(obj) {
  delete obj.dir
  delete obj.base
  delete obj.ext
  delete obj.sourceBase
  delete obj.sourceExt
  return obj
}

function cleanMarkdownProps(obj) {
  delete obj.bodyContent
  delete obj.bodyHtml
  delete obj.preview
  delete obj.title
  return obj
}

// Read a file making sure that it is not a directory first.
function readFileContent(file, cb) {
  if (!file || fs.lstatSync(file).isDirectory()) {
    return null
  }
  fs.readFile(file, (err, data) => {
    cb(err, file, data && data.toString())
  })
}

// Write a file making sure the directory exists first.
function writeFileContent(file, content, cb) {
  mkdirp(path.dirname(file), function(err) {
    fs.writeFile(file, content, (e, data) => {
      cb(e, data)
    })
  })
}

// Replace backslashes for windows paths.
function replaceBackslashes(str) {
  return str.split('\\').join('/')
}

// Determine if its data for a markdown file.
function isMarkdown(data) {
  return Boolean(data.bodyContent && data.bodyHtml)
}

// Find the common parent directory given an array of files.
function findCommonDir(files) {
  return files.reduce(function(p, c) {
    // If it's a file not in any directory then just skip it by assigning the previous value.
    if (c.indexOf('/') === -1) {
      return p
    }
    return !p ? c : p.split('').filter((letter, i) => letter === c[i]).join('')
  }, '')
}

// Remove body props from summary.
function removeBodyProps(content) {
  try {
    const json = JSON.parse(content)
    delete json.bodyContent
    delete json.bodyHtml
    return JSON.stringify(json)
  } catch(e) { }
}

// Debounce from: https://davidwalsh.name/function-debounce
function debounce(func, wait, immediate) {
  var timeout
  return function() {
    var context = this, args = arguments
    var later = function() {
      timeout = null
      if (!immediate) func.apply(context, args)
    }
    var callNow = immediate && !timeout
    clearTimeout(timeout)
    timeout = setTimeout(later, wait)
    if (callNow) func.apply(context, args)
  }
}

module.exports = {
  default: processmd,
  _readFileContent: readFileContent, // for testing.
  _writeFileContent: writeFileContent, // for testing.
  _isMarkdown: isMarkdown, // for testing.
  _findCommonDir: findCommonDir, // for testing.
}
<commit_msg>preview from bodyContent<commit_after>'use strict'

const fs = require('fs')
const path = require('path')
const globby = require('globby')
const marked = require('marked')
const yaml = require('js-yaml')
const mkdirp = require('mkdirp')
const defaultOptions = require('./defaultOptions')

const EXTENSIONS = {
  JSON: '.json',
  MD: '.md',
  YML: '.yml',
}
const NEWLINE = process.platform === 'win32' ? '\r\n' : '\n'
const FRONTMATTER_SEPERATOR = '---' + NEWLINE

const SOURCE_MODE = 'source'

// Main function
function processmd(options, callback) {
  options = Object.assign({}, defaultOptions, options)

  // Init marked.
  let markedOptions = {}
  if (options.highlightCode) {
    try {
      markedOptions = {
        highlight: function(code) {
          return require('highlight.js').highlightAuto(code).value
        },
      }
    } catch (e) {
      console.log(e)
    }
  }
  marked.setOptions(markedOptions)

  options.markdownRenderer = options.markdownRenderer || marked

  const globs = (options.files || []).concat(options._)
  if (globs.length === 0) {
    throw new Error('You must pass file patterns in to be processed.')
  }

  const p = new Promise(function(resolve, reject) {
    globby(globs).then(function(result) {
      const commonDir = findCommonDir(result)
      options._commonDir = commonDir

      if (options.watch) {
        const d = debounce(
          function() {
            processOutput()
          },
          200,
          true
        )

        // fs.watch isn't supported on linux.
        try {
          fs.watch(commonDir, function(event, filename) {
            d()
          })
        } catch (e) {
          console.log(e)
        }
      }

      let processingFunc = processYamlAndMarkdown
      if (typeof options._customProcessingFunc === 'function') {
        processingFunc = options._customProcessingFunc // used for testing.
      } else if (options.convertMode === SOURCE_MODE) {
        processingFunc = processJson
      }

      function processOutput() {
        const summaryObj = {}
        summaryObj.fileMap = {}
        summaryObj.sourceFileArray = result
        let finishCount = 0
        result.forEach(function(file, i) {
          processingFunc(file, options, function(newFile, content) {
            finishCount++

            // Replace backslashes with forward slashes to keep windows consistent.
            const filename = replaceBackslashes(newFile)

            // Remove body props from summary.
            if (options.removeBodyProps) {
              content = removeBodyProps(content)
            }

            summaryObj.fileMap[filename] = options.convertMode === SOURCE_MODE
              ? content
              : JSON.parse(content)

            if (finishCount === result.length) {
              if (options.summaryOutput) {
                writeFileContent(options.summaryOutput, JSON.stringify(summaryObj, null, 2), function(e, d) {
                  resolve(summaryObj)
                })
              } else {
                resolve(summaryObj)
              }
            }
          })
        })
      }

      processOutput()
    })
  })

  // Enable callback support too.
  if (callback) {
    p.then(result => {
      callback(null, result)
    })
  }

  return p
}

function processYamlAndMarkdown(file, options, cb) {
  readFileContent(file, (err, file, fileContent) => {
    const hasFrontmatter = fileContent.indexOf(FRONTMATTER_SEPERATOR) === 0
    const isYaml = file.endsWith('.yaml') || file.endsWith('.yml')
    let content = fileContent.trim()
    let frontmatter = {}
    let jsonData = {}

    // Markdown.
    if (hasFrontmatter) {
      let splitContent = fileContent.split(FRONTMATTER_SEPERATOR)
      // Remove first string in split content which is empty.
      if (splitContent[0] === '') {
        splitContent.shift()
      }
      frontmatter = yaml.safeLoad(splitContent[0])
      content = splitContent[1].trim()
    }

    if (isYaml) {
      jsonData = yaml.safeLoad(content)
    } else {
      jsonData = Object.assign({}, frontmatter, {
        bodyContent: content,
        bodyHtml: options.markdownRenderer(content),
      })
    }

    // Rename to the new file.
    const baseFilename = file.replace(options._commonDir, '')
    const parsedPath = path.parse(path.join(options.outputDir, baseFilename))
    const sourceExt = parsedPath.ext
    const sourceBase = parsedPath.base
    const newPathObj = Object.assign({}, parsedPath, {
      ext: EXTENSIONS.JSON,
      base: options.filenamePrefix +
        parsedPath.base.replace(sourceExt, EXTENSIONS.JSON),
    })
    const newPath = path.format(newPathObj)

    if (options.preview > 0 && jsonData.bodyContent) {
      // TODO: These regular expressions could probably be better.
      // jsonData.preview = jsonData.bodyHtml.match(/<p>(.*?)<\/p>/)[1]
      // jsonData.preview = jsonData.preview.split(/<[^>]*>/).join('')
      jsonData.preview = jsonData.bodyContent.substring(0, options.preview) + '…'
    }
    if (options.includeTitle && jsonData.bodyContent) {
      jsonData.title = jsonData.title || jsonData.bodyHtml.match(/>(.*?)<\//)[1]
    }
    if (options.includeDir) {
      jsonData.dir = replaceBackslashes(path.dirname(newPath))
    }
    if (options.includeBase) {
      jsonData.base = path.basename(newPath)
    }
    if (options.includeExt) {
      jsonData.ext = EXTENSIONS.JSON
    }
    if (options.includeSourceBase) {
      jsonData.sourceBase = sourceBase
    }
    if (options.includeSourceExt) {
      jsonData.sourceExt = sourceExt
    }

    // TODO: make this a default callback
    // 2 spaces indent for stringify.
    writeFileContent(newPath, JSON.stringify(jsonData, null, 2), function(e, d) {
      cb(newPath, JSON.stringify(jsonData))
    })
  })
}

function processJson(file, options, cb) {
  readFileContent(file, (err, file, fileContent) => {
    const fileData = JSON.parse(fileContent)

    // Process content.
    let newContent = ''
    const cleanProps = cleanFileProps(
      cleanMarkdownProps(Object.assign({}, fileData))
    )
    const cleanYaml = yaml.safeDump(cleanProps)
    let extension = '.yml'
    if (isMarkdown(fileData)) {
      newContent += fileData.bodyContent + NEWLINE
      if (Object.keys(cleanProps).length > 0) {
        newContent =
          FRONTMATTER_SEPERATOR +
          cleanYaml +
          FRONTMATTER_SEPERATOR +
          NEWLINE +
          newContent
      }
      extension = '.md'
    } else {
      newContent = cleanYaml
    }

    // Rename to the new file.
    const baseFilename = file.replace(options._commonDir, '')
    const parsedPath = path.parse(path.join(options.outputDir, baseFilename))
    const sourceExt = parsedPath.ext
    // const sourceBase = parsedPath.base
    const newPathObj = Object.assign({}, parsedPath, {
      ext: extension,
      base: options.filenamePrefix +
        parsedPath.base.replace(sourceExt, extension),
    })
    const newPath = path.format(newPathObj)

    writeFileContent(newPath, newContent, function(e, d) {
      cb(newPath, newContent)
    })
  })
}

function cleanFileProps(obj) {
  delete obj.dir
  delete obj.base
  delete obj.ext
  delete obj.sourceBase
  delete obj.sourceExt
  return obj
}

function cleanMarkdownProps(obj) {
  delete obj.bodyContent
  delete obj.bodyHtml
  delete obj.preview
  delete obj.title
  return obj
}

// Read a file making sure that it is not a directory first.
function readFileContent(file, cb) {
  if (!file || fs.lstatSync(file).isDirectory()) {
    return null
  }
  fs.readFile(file, (err, data) => {
    cb(err, file, data && data.toString())
  })
}

// Write a file making sure the directory exists first.
function writeFileContent(file, content, cb) {
  mkdirp(path.dirname(file), function(err) {
    fs.writeFile(file, content, (e, data) => {
      cb(e, data)
    })
  })
}

// Replace backslashes for windows paths.
function replaceBackslashes(str) {
  return str.split('\\').join('/')
}

// Determine if its data for a markdown file.
function isMarkdown(data) {
  return Boolean(data.bodyContent && data.bodyHtml)
}

// Find the common parent directory given an array of files.
function findCommonDir(files) {
  return files.reduce(function(p, c) {
    // If it's a file not in any directory then just skip it by assigning the previous value.
    if (c.indexOf('/') === -1) {
      return p
    }
    return !p ? c : p.split('').filter((letter, i) => letter === c[i]).join('')
  }, '')
}

// Remove body props from summary.
function removeBodyProps(content) {
  try {
    const json = JSON.parse(content)
    delete json.bodyContent
    delete json.bodyHtml
    return JSON.stringify(json)
  } catch(e) { }
}

// Debounce from: https://davidwalsh.name/function-debounce
function debounce(func, wait, immediate) {
  var timeout
  return function() {
    var context = this, args = arguments
    var later = function() {
      timeout = null
      if (!immediate) func.apply(context, args)
    }
    var callNow = immediate && !timeout
    clearTimeout(timeout)
    timeout = setTimeout(later, wait)
    if (callNow) func.apply(context, args)
  }
}

module.exports = {
  default: processmd,
  _readFileContent: readFileContent, // for testing.
  _writeFileContent: writeFileContent, // for testing.
  _isMarkdown: isMarkdown, // for testing.
  _findCommonDir: findCommonDir, // for testing.
}
<|endoftext|>


<commit_before>#! /usr/bin/env node
/* eslint import/no-dynamic-require: 0 */

const log = require('winston');
const inquirer = require('inquirer');
const search = require('./lib/search');
const exec = require('./lib/exec');

const packageJSON = require(`${process.cwd()}/package.json`);
const commands = Object.keys(packageJSON.scripts).map(command => ({ command, humanized: command.split(':').join(' ') }));
const searchTerm = process.argv.slice(2).join(' ');

log.remove(log.transports.Console);
log.add(log.transports.Console, { colorize: true });

const prompt = () => {
  inquirer.prompt({
    message: 'Choose a command to run:',
    type: 'list',
    name: 'command',
    pageSize: 100,
    choices: commands.map(c => c.command),
  })
  .then((answer) => {
    exec(answer.command);
  });
};

const fuseSearch = () => {
  const result = search(commands, searchTerm);

  if (result.length > 0) {
    const command = result[0].item.command;
    exec(command);
  } else {
    log.error(`There are no results that match "${searchTerm}"`);
    process.exit(1);
  }
};

if (!searchTerm) {
  prompt();
} else {
  fuseSearch();
}
<commit_msg>Change list type for a faster script selection<commit_after>#! /usr/bin/env node
/* eslint import/no-dynamic-require: 0 */

const log = require('winston');
const inquirer = require('inquirer');
const search = require('./lib/search');
const exec = require('./lib/exec');

const packageJSON = require(`${process.cwd()}/package.json`);
const commands = Object.keys(packageJSON.scripts).map(command => ({ command, humanized: command.split(':').join(' ') }));
const searchTerm = process.argv.slice(2).join(' ');

log.remove(log.transports.Console);
log.add(log.transports.Console, { colorize: true });

const prompt = () => {
  inquirer.prompt({
    message: 'Choose a command to run:',
    type: 'rawlist',
    name: 'command',
    pageSize: 100,
    choices: commands.map(c => c.command),
  })
  .then((answer) => {
    exec(answer.command);
  });
};

const fuseSearch = () => {
  const result = search(commands, searchTerm);

  if (result.length > 0) {
    const command = result[0].item.command;
    exec(command);
  } else {
    log.error(`There are no results that match "${searchTerm}"`);
    process.exit(1);
  }
};

if (!searchTerm) {
  prompt();
} else {
  fuseSearch();
}
<|endoftext|>


<commit_before>// Module dependencies
var path     = require('path');
var fs       = require('fs');
var express  = require('express');
var mongoose = require('mongoose');
var passport = require('passport');
var config   = require(__dirname + '/app/config/config');
var serveStatic = require('serve-static');
var Auth = require('./app/middleware/authorization');
var User = require('./app/models/user');
var Conversation = require('./app/models/conversation');
var Message = require('./app/models/message');
var PushNotificationController = require('./app/controllers/PushNotificationController');
var async = require('async');

var app = require('express')();

app.use(function(req, res, next) {
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader('Access-Control-Allow-Headers', '*');
  next();
});

var server = require('http').Server(app);
var io = require('socket.io')(server);

if(process.env.NODE_ENV === 'production') {
  app.enable('trust proxy');
  app.use(require('express-enforces-ssl')());
}

app.config = config;

// Database
require('./app/config/database')(app, mongoose);

var models_path = __dirname + '/app/models'

fs.readdirSync(models_path).forEach(function (file) {
  if (~file.indexOf('.js'))
    require(models_path + '/' + file)
});

require('./app/config/passport')(app, passport);

// express settings
require('./app/config/express')(app, express, passport);

// create a server instance
// passing in express app as a request event handler

server.listen(app.get('port'), function() {
  console.log("\n✔ Express server listening on port %d in %s mode", app.get('port'), app.get('env'));
});

io.use(function(socket, next) {
  if(!socket.request._query) {
      return next(new Error("not authorized"));
  }

  var token = socket.request._query.token;
  Auth.checkAuthToken(app, token, function(err, user) {
    if (err || !user) {
      next(new Error("not authorized"));
    }

    socket.user = user;
    next();
  })
});

io.on('connection', function (socket) {

  console.log("New connection", socket.id);
  // make user online
  var user = socket.user;

  User.update({_id: user._id},
              {
                $set: {
                  online: true
                }
              }).exec();

  socket.on('disconnect', function(){
    console.log(socket.request._query, "is disconnected");

    // make user offline
    User.update({_id: user._id},
                {
                  $set: {
                    online: false
                  }
                }).exec();
  });

  // join room
  socket.on('joinRoom', function(roomID){
    socket.room = roomID;

    Conversation.findOne(socket.room)
    .populate("createUser")
    .populate("targetUser")
    .exec(function (err, conversation) {

      if(err || !conversation) {
        socket.emit('log', 'SERVER', 'room doenst exsit');
      }

      // if user is not createUser or targetUser
      // we deny it
      if(conversation.createUser._id.equals(socket.user._id) ||
         conversation.targetUser._id.equals(socket.user._id)
        )
      {
        socket.join(roomID);
        socket.emit('log', 'SERVER', 'you have connected to room');

        Message.find({conversation: roomID})
        .populate("user")
        .exec(function (err, messages) {
          socket.emit('joinRoomSuccessfully', {_id: conversation._id,
                      messages: messages});
        })

      } else {
        socket.emit('log', 'SERVER', 'you dont have permissions to join this room');
      }

    });
  });

  socket.on('sendChat', function(data) {
    var message = new Message();
    message.content = data;
    message.user = user;

    async.parallel({
      user: function(callback){
        User.findOne(user._id)
        .exec(callback)
      },
      conversation: function(callback){
        Conversation.findOne(socket.room)
        .populate("createUser")
        .populate("targetUser")
        .exec(callback);
      }
    },
    function(err, result) {
      if(err) {
        // TODO: handle error;
        return;
      }

      var conversation = result.conversation;
      var user = result.user;

      message.conversation = conversation;
      message.user = user;
      message.save(function(err, m) {
        if (err) {
          return;
        }

        Message.findOne(message._id)
        .populate("user")
        .exec(function(err, mes) {
          io.sockets.in(socket.room).emit('updateChat', mes);
        })

      });

      if(!conversation.createUser.online) {
        //TODO: push notificaiton
        PushNotificationController.sendNotificationToUserByUserID(conversation.createUser._id.toString(), user.name + ": " + message.content, {
          conversation_id: conversation._id,
          user_id: user._id,
        });
        console.log("push notification to user " + conversation.createUser._id);
      }

      if(!conversation.targetUser.online) {
        //TODO: push notificaiton
        PushNotificationController.sendNotificationToUserByUserID(conversation.targetUser._id.toString(), user.name + ": " + message.content, {
          conversation_id: conversation._id,
          user_id: user._id,
        });
        console.log("push notification to user " + conversation.targerUser._id);
      }
    });

  });

});
<commit_msg>[FIX] typo<commit_after>// Module dependencies
var path     = require('path');
var fs       = require('fs');
var express  = require('express');
var mongoose = require('mongoose');
var passport = require('passport');
var config   = require(__dirname + '/app/config/config');
var serveStatic = require('serve-static');
var Auth = require('./app/middleware/authorization');
var User = require('./app/models/user');
var Conversation = require('./app/models/conversation');
var Message = require('./app/models/message');
var PushNotificationController = require('./app/controllers/PushNotificationController');
var async = require('async');

var app = require('express')();

app.use(function(req, res, next) {
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader('Access-Control-Allow-Headers', '*');
  next();
});

var server = require('http').Server(app);
var io = require('socket.io')(server);

if(process.env.NODE_ENV === 'production') {
  app.enable('trust proxy');
  app.use(require('express-enforces-ssl')());
}

app.config = config;

// Database
require('./app/config/database')(app, mongoose);

var models_path = __dirname + '/app/models'

fs.readdirSync(models_path).forEach(function (file) {
  if (~file.indexOf('.js'))
    require(models_path + '/' + file)
});

require('./app/config/passport')(app, passport);

// express settings
require('./app/config/express')(app, express, passport);

// create a server instance
// passing in express app as a request event handler

server.listen(app.get('port'), function() {
  console.log("\n✔ Express server listening on port %d in %s mode", app.get('port'), app.get('env'));
});

io.use(function(socket, next) {
  if(!socket.request._query) {
      return next(new Error("not authorized"));
  }

  var token = socket.request._query.token;
  Auth.checkAuthToken(app, token, function(err, user) {
    if (err || !user) {
      next(new Error("not authorized"));
    }

    socket.user = user;
    next();
  })
});

io.on('connection', function (socket) {

  console.log("New connection", socket.id);
  // make user online
  var user = socket.user;

  User.update({_id: user._id},
              {
                $set: {
                  online: true
                }
              }).exec();

  socket.on('disconnect', function(){
    console.log(socket.request._query, "is disconnected");

    // make user offline
    User.update({_id: user._id},
                {
                  $set: {
                    online: false
                  }
                }).exec();
  });

  // join room
  socket.on('joinRoom', function(roomID){
    socket.room = roomID;

    Conversation.findOne(socket.room)
    .populate("createUser")
    .populate("targetUser")
    .exec(function (err, conversation) {

      if(err || !conversation) {
        socket.emit('log', 'SERVER', 'room doenst exsit');
      }

      // if user is not createUser or targetUser
      // we deny it
      if(conversation.createUser._id.equals(socket.user._id) ||
         conversation.targetUser._id.equals(socket.user._id)
        )
      {
        socket.join(roomID);
        socket.emit('log', 'SERVER', 'you have connected to room');

        Message.find({conversation: roomID})
        .populate("user")
        .exec(function (err, messages) {
          socket.emit('joinRoomSuccessfully', {_id: conversation._id,
                      messages: messages});
        })

      } else {
        socket.emit('log', 'SERVER', 'you dont have permissions to join this room');
      }

    });
  });

  socket.on('sendChat', function(data) {
    var message = new Message();
    message.content = data;
    message.user = user;

    async.parallel({
      user: function(callback){
        User.findOne(user._id)
        .exec(callback)
      },
      conversation: function(callback){
        Conversation.findOne(socket.room)
        .populate("createUser")
        .populate("targetUser")
        .exec(callback);
      }
    },
    function(err, result) {
      if(err) {
        // TODO: handle error;
        return;
      }

      var conversation = result.conversation;
      var user = result.user;

      message.conversation = conversation;
      message.user = user;
      message.save(function(err, m) {
        if (err) {
          return;
        }

        Message.findOne(message._id)
        .populate("user")
        .exec(function(err, mes) {
          io.sockets.in(socket.room).emit('updateChat', mes);
        })

      });

      console.log(conversation);

      if(!conversation.createUser.online) {
        //TODO: push notificaiton
        PushNotificationController.sendNotificationToUserByUserID(conversation.createUser._id.toString(), user.name + ": " + message.content, {
          conversation_id: conversation._id,
          user_id: user._id,
        });
        console.log("push notification to user " + conversation.createUser._id);
      }

      if(!conversation.targetUser.online) {
        //TODO: push notificaiton
        PushNotificationController.sendNotificationToUserByUserID(conversation.targetUser._id.toString(), user.name + ": " + message.content, {
          conversation_id: conversation._id,
          user_id: user._id,
        });
        console.log("push notification to user " + conversation.targetUser._id);
      }
    });

  });

});
<|endoftext|>


<commit_before>//     react-native-loading-spinner-overlay
//     Copyright (c) 2016- Nick Baugh <niftylettuce@gmail.com>
//     MIT Licensed

// * Author: [@niftylettuce](https://twitter.com/#!/niftylettuce)
// * Source:
// <https://github.com/niftylettuce/react-native-loading-spinner-overlay>

// # react-native-loading-spinner-overlay
//
// <https://github.com/facebook/react-native/issues/2501>
// <https://rnplay.org/apps/1YkBCQ>
// <https://github.com/facebook/react-native/issues/2501>
// <https://github.com/brentvatne/react-native-overlay>
//

import React from 'react';
import PropTypes from 'prop-types';
import { StyleSheet, View, Text, Modal, ActivityIndicator } from 'react-native';

const transparent = 'transparent';
const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: transparent,
    position: 'absolute',
    top: 0,
    bottom: 0,
    left: 0,
    right: 0
  },
  background: {
    position: 'absolute',
    top: 0,
    bottom: 0,
    left: 0,
    right: 0,
    justifyContent: 'center',
    alignItems: 'center'
  },
  textContainer: {
    flex: 1,
    top: 0,
    bottom: 0,
    left: 0,
    right: 0,
    justifyContent: 'center',
    alignItems: 'center',
    position: 'absolute'
  },
  textContent: {
    top: 80,
    height: 50,
    fontSize: 20,
    fontWeight: 'bold'
  },
  activityIndicator: {
    flex: 1
  }
});

const ANIMATION = ['none', 'slide', 'fade'];
const SIZES = ['small', 'normal', 'large'];

export default class Spinner extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      visible: this.props.visible,
      textContent: this.props.textContent
    };
  }

  static propTypes = {
    cancelable: PropTypes.bool,
    color: PropTypes.string,
    animation: PropTypes.oneOf(ANIMATION),
    overlayColor: PropTypes.string,
    size: PropTypes.oneOf(SIZES),
    textContent: PropTypes.string,
    textStyle: PropTypes.object,
    visible: PropTypes.bool,
    indicatorStyle: PropTypes.object,
    customIndicator: PropTypes.element,
    children: PropTypes.element,
    spinnerKey: PropTypes.string
  };

  static defaultProps = {
    visible: false,
    cancelable: false,
    textContent: '',
    animation: 'none',
    color: 'white',
    size: 'large', // 'normal',
    overlayColor: 'rgba(0, 0, 0, 0.25)'
  };

  close() {
    this.setState({ visible: false });
  }

  static getDerivedStateFromProps(props, state) {
    const newState = {};
    if (state.visible !== props.visible) newState.visible = props.visible;
    if (state.textContent !== props.textContent)
      newState.textContent = props.textContent;
    return newState;
  }

  _handleOnRequestClose() {
    if (this.props.cancelable) {
      this.close();
    }
  }

  _renderDefaultContent() {
    return (
      <View style={styles.background}>
        {this.props.customIndicator ? (
          this.props.customIndicator
        ) : (
          <ActivityIndicator
            color={this.props.color}
            size={this.props.size}
            style={[styles.activityIndicator, { ...this.props.indicatorStyle }]}
          />
        )}
        <View style={[styles.textContainer, { ...this.props.indicatorStyle }]}>
          <Text style={[styles.textContent, this.props.textStyle]}>
            {this.state.textContent}
          </Text>
        </View>
      </View>
    );
  }

  _renderSpinner() {
    const spinner = (
      <View
        style={[styles.container, { backgroundColor: this.props.overlayColor }]}
        key={this.props.spinnerKey ? this.props.spinnerKey : `spinner_${Date.now()}`}
      >
        {this.props.children
          ? this.props.children
          : this._renderDefaultContent()}
      </View>
    );

    return (
      <Modal
        animationType={this.props.animation}
        onRequestClose={() => this._handleOnRequestClose()}
        supportedOrientations={['landscape', 'portrait']}
        transparent
        visible={this.state.visible}
      >
        {spinner}
      </Modal>
    );
  }

  render() {
    return this._renderSpinner();
  }
}
<commit_msg>fix: fixed lint warnings<commit_after>//     react-native-loading-spinner-overlay
//     Copyright (c) 2016- Nick Baugh <niftylettuce@gmail.com>
//     MIT Licensed

// * Author: [@niftylettuce](https://twitter.com/#!/niftylettuce)
// * Source:
// <https://github.com/niftylettuce/react-native-loading-spinner-overlay>

// # react-native-loading-spinner-overlay
//
// <https://github.com/facebook/react-native/issues/2501>
// <https://rnplay.org/apps/1YkBCQ>
// <https://github.com/facebook/react-native/issues/2501>
// <https://github.com/brentvatne/react-native-overlay>
//

import React from 'react';
import PropTypes from 'prop-types';
import { StyleSheet, View, Text, Modal, ActivityIndicator } from 'react-native';

const transparent = 'transparent';
const styles = StyleSheet.create({
  activityIndicator: {
    flex: 1
  },
  background: {
    alignItems: 'center',
    bottom: 0,
    justifyContent: 'center',
    left: 0,
    position: 'absolute',
    right: 0,
    top: 0
  },
  container: {
    backgroundColor: transparent,
    bottom: 0,
    flex: 1,
    left: 0,
    position: 'absolute',
    right: 0,
    top: 0
  },
  textContainer: {
    alignItems: 'center',
    bottom: 0,
    flex: 1,
    justifyContent: 'center',
    left: 0,
    position: 'absolute',
    right: 0,
    top: 0
  },
  textContent: {
    fontSize: 20,
    fontWeight: 'bold',
    height: 50,
    top: 80
  }
});

const ANIMATION = ['none', 'slide', 'fade'];
const SIZES = ['small', 'normal', 'large'];

export default class Spinner extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      visible: this.props.visible,
      textContent: this.props.textContent
    };
  }

  static propTypes = {
    cancelable: PropTypes.bool,
    color: PropTypes.string,
    animation: PropTypes.oneOf(ANIMATION),
    overlayColor: PropTypes.string,
    size: PropTypes.oneOf(SIZES),
    textContent: PropTypes.string,
    textStyle: PropTypes.object,
    visible: PropTypes.bool,
    indicatorStyle: PropTypes.object,
    customIndicator: PropTypes.element,
    children: PropTypes.element,
    spinnerKey: PropTypes.string
  };

  static defaultProps = {
    visible: false,
    cancelable: false,
    textContent: '',
    animation: 'none',
    color: 'white',
    size: 'large', // 'normal',
    overlayColor: 'rgba(0, 0, 0, 0.25)'
  };

  close() {
    this.setState({ visible: false });
  }

  static getDerivedStateFromProps(props, state) {
    const newState = {};
    if (state.visible !== props.visible) newState.visible = props.visible;
    if (state.textContent !== props.textContent)
      newState.textContent = props.textContent;
    return newState;
  }

  _handleOnRequestClose() {
    if (this.props.cancelable) {
      this.close();
    }
  }

  _renderDefaultContent() {
    return (
      <View style={styles.background}>
        {this.props.customIndicator ? (
          this.props.customIndicator
        ) : (
          <ActivityIndicator
            color={this.props.color}
            size={this.props.size}
            style={[styles.activityIndicator, { ...this.props.indicatorStyle }]}
          />
        )}
        <View style={[styles.textContainer, { ...this.props.indicatorStyle }]}>
          <Text style={[styles.textContent, this.props.textStyle]}>
            {this.state.textContent}
          </Text>
        </View>
      </View>
    );
  }

  _renderSpinner() {
    const spinner = (
      <View
        style={[styles.container, { backgroundColor: this.props.overlayColor }]}
        key={
          this.props.spinnerKey
            ? this.props.spinnerKey
            : `spinner_${Date.now()}`
        }
      >
        {this.props.children
          ? this.props.children
          : this._renderDefaultContent()}
      </View>
    );

    return (
      <Modal
        animationType={this.props.animation}
        onRequestClose={() => this._handleOnRequestClose()}
        supportedOrientations={['landscape', 'portrait']}
        transparent
        visible={this.state.visible}
      >
        {spinner}
      </Modal>
    );
  }

  render() {
    return this._renderSpinner();
  }
}
<|endoftext|>


<commit_before>"use strict";

const assert = require("assert");
const constants = require("./constants");
const InternalAuthTokenProvider = require("./internalAuthTokenProvider");
const {SuperUserAuthenticator} = require("./superUserAuthenticator");
const axios = require("axios");
const audiences = require("./audiences.js")

function Authenticator(options, logger) {

  assert(logger && logger.info && logger.error, "you must provide a logger");
  assert(options.internalAuthTokenSigningSecrets, "you must provide 'internalAuthTokenSigningSecrets'");
  assert(options.internalAuthTokenSigningSecrets.main, "you must provide 'internalAuthTokenSigningSecrets.main'");
  assert(options.internalAuthTokenSigningSecrets.secondary, "you must provide 'internalAuthTokenSigningSecrets.secondary'");

  const internalAuthTokenSigningSecrets = options.internalAuthTokenSigningSecrets;
  const ignoredRoutes = options.ignoredRoutes && Array.isArray(options.ignoredRoutes) ? options.ignoredRoutes : [];
  const strategyOptions = {
    passReqToCallback: true,
    apiKeyHeader: options.authKeyFields && options.authKeyFields.header ? options.authKeyFields.header : "x-api-key",
    apiKeyField: options.authKeyFields && options.authKeyFields.request ? options.authKeyFields.request : "x-api-key"
  };
  let preLoadedUser = null;

  // username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]

  const passport = require("passport");
  const LocalStrategy = require("passport-localapikey-update").Strategy;
  const SimpleDao = require("btrz-simple-dao").SimpleDao;
  const simpleDao = new SimpleDao(options);
  const jwt = require("jsonwebtoken");

  function useTestKey() {
    return new Promise(function (resolve) {
      if (options.testUser) {
        resolve(options.testUser);
      } else {
        resolve(true);
      }
    });
  }

  function getTestUser(token) {
    if (isTestToken(token)) {
      if (options.testUser) {
        return options.testUser;
      } else {
        return true;
      }
    }
    return null;
  }

  function isTestToken(token) {
    return (token && token === options.testToken);
  }

  function isCorrectBackOfficeAudience(audience) {
    return Array.isArray(options.audiences) ? options.audiences.indexOf(audience) > -1 : audience === "betterez-app";
  }

  function useDb(apikey) {
    let query = {};
    query[options.collection.property] =  apikey;
    return simpleDao.connect()
      .then((db) => {
        return db.collection(options.collection.name).findOne(query)
        .then((result) => {
          if(!result) {
            logger.error("api-key not found");
          }
          return result;
        });
      })
      .catch((err) => {
        return Promise.reject(err);
      });
  }

  function getAuthInfo(apikey) {
    const url = `${options.apiUrl}/${apikey}`;
    const payload = {
      headers: {
        "Authorization": `Bearer ${options.internalAuthTokenProvider.getToken()}`
      },
      body: {},
      json: true
    };
    return axios.get(url, payload)
      .then((info) => {
        preLoadedUser = info.data.user;
        return info.data.application;
      })
      .catch((err) => {
        logger.error("ERROR getting auth info::getAuthInfo::", err);
        return null;
      });
  }

  function useApiAuth() {
    return options.apiAuth && options.apiUrl && options.internalAuthTokenProvider;
  }

  function findByApiKey(apikey) {
    if (apikey === options.testKey) {
      return useTestKey(apikey);
    }

    if (useApiAuth()) {
      return getAuthInfo(apikey);
    }

    return useDb(apikey);
  }

  function findUserById(userId) {
    if (preLoadedUser) {
      return Promise.resolve(preLoadedUser);
    }

    if(typeof userId !== "string") {
      return Promise.reject(new Error("userId must be a string"));
    }

    return simpleDao.connect()
      .then((db) => {
        return db.collection(constants.DB_USER_COLLECTION_NAME).findOne({_id: simpleDao.objectId(userId), deleted: false});
      });
  }

  function shouldIgnoreRoute(originalUrl, method) {
    return ignoredRoutes.some(function (ignoredRoute) {
      if (typeof ignoredRoute === "string") {
        return originalUrl.match(ignoredRoute) || null;
      } else if (typeof ignoredRoute === "object") {
        let methodMatches = (ignoredRoute.methods.indexOf(method) > -1);
        return (methodMatches && originalUrl.match(ignoredRoute.route));
      } else {
        return null;
      }
    });
  }

  function isIgnoredRouteWithoutAuthAttempt(req, strategyOptions) {
    const isXApiKey = req.headers[strategyOptions.apiKeyHeader] || req.query[strategyOptions.apiKeyField];

    return shouldIgnoreRoute(req.originalUrl, req.method) && !isXApiKey;
  }

  function innerAuthenticateMiddleware(req, res, next) {
    const jwtToken = getAuthToken(req);
    const decodedToken = decodeToken(jwtToken);
    const isXApiKey = req.headers[strategyOptions.apiKeyHeader] || req.query[strategyOptions.apiKeyField];
    const someAuthAttempt = decodedToken || isXApiKey;
    const isInternalToken = decodedToken && decodedToken.iss === constants.INTERNAL_AUTH_TOKEN_ISSUER;

    if(isInternalToken) {
      req.internalUser = true;
    }

    if (shouldIgnoreRoute(req.originalUrl, req.method) && (!someAuthAttempt || isInternalToken)) {
      next();
    } else {
      passport.authenticate("localapikey", {session: false})(req, res, next);
    }
  }

  passport.use(new LocalStrategy(strategyOptions,
    function (req, apikey, done) {
      let onSuccess = function (result) {
        const jwtToken = getAuthToken(req);
        req.application = result;
        req.tokens = { token: apikey, jwtToken };
        // if jwtToken is present obtain user from it
        if (result && result.privateKey && jwtToken) {
          const user = fetchUserFromJwtToken(jwtToken, result.privateKey);
          if (user && user.aud === 'customer') {
            req.customer = user;
          } else {
            req.user = user;
          }
        }
        // done executes Passport login and fills req.user (or an alias if userProperty is defined in root index.js)
        return done(null, result);
      };
      let onErr = function (err) { return done(err, null); };

      let result = findByApiKey(apikey, req).then(onSuccess, onErr);
      if (result.done) {
        result.done();
      }
    }
  ));

  function getAuthToken(req) {
    return req.headers.authorization ? req.headers.authorization.replace(/^Bearer /, "") : null;
  }

  function decodeToken(token) {
    try {
      return jwt.decode(token);
    } catch (err) {
      return null;
    }
  }

  function shouldValidateAccount(bypassAccount, req) {
    return !bypassAccount && !(req.account && req.account.privateKey);
  }

  function verifyInternalToken(token, secrets) {

    function verify(keyName, opts) {
      try {
        return jwt.verify(token, secrets[keyName], opts);
      } catch (err) {
        // failing to validate the token against one of the signing keys is expected behaviour when a key rotation is in progress
        logger.info(`authenticateTokenMiddleware: Failed to validate internal auth token using ${keyName} signing key`, err);
        return false;
      }
    }

    const opts = {
      algorithms: ["HS512"],
      issuer: constants.INTERNAL_AUTH_TOKEN_ISSUER,
    };

    return verify("main", opts) || verify("secondary", opts);
  }

  // if this setup is used then the endpoint will be opened, please sanitize the content if needed
  // if some auth attempt is done then authtentication will be performed
  function optionalTokenSecured(req, res, next) {
    if (isIgnoredRouteWithoutAuthAttempt(req, strategyOptions)) {
      return next();
    }

    return authenticateTokenMiddleware(req, res, next);
  }

  function authenticateTokenMiddleware(req, res, next, options = {}) {
    const {bypassAccount = false} = options;

    if (shouldValidateAccount(bypassAccount, req)) {
      logger.error("authenticateTokenMiddleware: No account or account has no private key");
      return res.status(401).send("Unauthorized");
    } else if (!req.headers.authorization) {
      logger.info("authenticateTokenMiddleware: Request is missing 'authorization' header");
      return res.status(401).send("Unauthorized");
    }

    // at this point headers.authorization was already checked for existency
    const jwtToken = getAuthToken(req);

    // moving a the token process to a unified function
    // which will also be used after the passport login happens
    // but omitting res, next and options
    processJwtToken(req, res, jwtToken, next, options);
  }

  function tokenSecured(req, res, next) {
    return authenticateTokenMiddleware(req, res, next);
  }

  //if channel 'backoffice' or 'agency-backoffice' is requested in the body or querystring,
  //checks request has a valid token for backoffice ('betterez-app' internal application)
  function tokenSecuredForBackoffice(req, res, next) {
    let channel = (req.body ? req.body.channel : "") || (req.query ? req.query.channel : "");
    let channels = (req.body ? req.body.channels : "") || (req.query ? req.query.channels : "");
    let mustAuth = false;

    if (channels) {
      if (!Array.isArray(channels)) {
        channels = channels.split(",");
      }

      channels.forEach(function (ch) {
        if (ch.trim().toLowerCase() === "backoffice" || ch.trim().toLowerCase() === "agency-backoffice") {
          mustAuth = true;
          return;
        }
      });
    }
    if (!mustAuth && channel && (channel.trim().toLowerCase() === "backoffice" || channel.trim().toLowerCase() === "agency-backoffice")) {
      mustAuth = true;
    }

    if (mustAuth) {
      authenticateTokenMiddleware(req, res, function (err) {
        if (err) {
          return next(err);
        }
        if (isTestToken(getAuthToken(req))) {
          return next();
        }
        if (!req.user || !isCorrectBackOfficeAudience(req.user.aud)) {
          return res.status(401).send("Unauthorized");
        } else {
          return next();
        }
      });
    } else {
      return next();
    }
  }

  function customerTokenSecured(req, res, next) {
    return authenticateTokenMiddleware(req, res, next, {audience: "customer"});
  }

  function tokenSecuredWithoutAccount(req, res, next) {
    return authenticateTokenMiddleware(req, res, next, {bypassAccount: true});
  }

  function tokenSecuredForAudiences(audiences) {
    return function (req, res, next) {
      return authenticateTokenMiddleware(req, res, function (err) {
        if (err) {
          return next(err);
        }
        if (isTestToken(getAuthToken(req))) {
          return next();
        }
        const notAuthenticated = !req.user,
          wrongAudience = audiences.indexOf(req.user.aud) === -1;
        if (notAuthenticated || wrongAudience) {
          return res.status(401).send("Unauthorized");
        } else {
          return next();
        }
      });
    };
  }

  function validateJwtIfGiven(req, res, next) {
    const jwtToken = getAuthToken(req);
    if (!jwtToken) {
      return next();
    }
    return authenticateTokenMiddleware(req, res, next);
  }

  function findOneAdministrator(accountId) {
    const query = {
      accountId,
      deleted: false,
      "roles.administrator": 1,
      "locked.status": false,
    };
    return simpleDao.connect()
      .then((db) => {
        return db.collection(constants.DB_USER_COLLECTION_NAME).findOne(query);
      });
  }

  function findUserForInternalToken(application) {
    // find the "original user", using the userId of the Application found by x-api-key
    return findUserById(application.userId).then((user) => {
      if (user) {
        return user;
      }
      // the "original user" is no longer enabled, fetch a valid administrator to impersonate
      return findOneAdministrator(application.accountId);
    });
  }

  function processJwtToken(req, res, jwtToken, next, options = {}) {
    // will only assign req.user if it's not present. Because it could've been assigned previously
    const {audience = null, bypassAccount = false} = options;
    const decodedToken = decodeToken(jwtToken);

    if (isTestToken(jwtToken)) {
      if (!req.user) {
        req.user = getTestUser(jwtToken);
      }
      return next();
    }

    if (!decodedToken) {
      logger.error("authenticateTokenMiddleware: Token is malformed");
      return res.status(401).send("Unauthorized");
    } else if (!decodedToken.iss) {
      logger.error("authenticateTokenMiddleware: Token does not specify its issuer");
      return res.status(401).send("Unauthorized");
    }

    const isInternalToken = decodedToken.iss === constants.INTERNAL_AUTH_TOKEN_ISSUER;

    if (isInternalToken) {
      const tokenPayload = verifyInternalToken(jwtToken, internalAuthTokenSigningSecrets);

      if (!tokenPayload) {
        logger.error("authenticateTokenMiddleware: Failed to validate internal auth token using any signing key");
        return res.status(401).send("Unauthorized");
      }
      if (bypassAccount) {
        return next();
      }

      return findUserForInternalToken(req.account)
        .then((user) => {
          assert(user, "unable to find user to impersonate");

          Reflect.deleteProperty(user, "password");

          if (!req.user) {
            req.user = Object.assign({}, user, tokenPayload);
            req.user._id = req.user._id.toString();
          }
          return next();
        })
        .catch((err) => {
          logger.error(`authenticateTokenMiddleware: Error occurred finding user to impersonate for internal token. Check user ${req.account.userId} exists, or the account has at least one enabled administrator.`, err);
          return res.status(401).send("Unauthorized");
        });
    } else {
      // Validate a user-provided token
      try {
        const userTokenVerifyOptions = {
          algorithms: ["HS512"],
          subject: "account_user_sign_in",
          issuer: constants.USER_AUTH_TOKEN_ISSUER
        };

        if (audience) {
          userTokenVerifyOptions.audience = audience;
        }
        // audience does not exist at api-key verification time, so if it's defined, token needs to be verified again
        if (!req.user || audience) {
          req.user = jwt.verify(jwtToken, req.account.privateKey, userTokenVerifyOptions);
        }
        return next();
      } catch (err) {
        if (err.name === "TokenExpiredError" || err.name === "JsonWebTokenError") {
          logger.info(`authenticateTokenMiddleware: Token expired or 'JsonWebTokenError' occurred`, err);
          return res.status(401).send("Unauthorized");
        }

        logger.error(`authenticateTokenMiddleware: Unexpected error occurred validating user token`, err);
        return res.status(401).send("Unauthorized");
      }
    }
  }

  function fetchUserFromJwtToken(jwtToken, privateKey) {
    let user = null;
    try {
      const decodedToken = decodeToken(jwtToken);
      const testToken = isTestToken(jwtToken);
      if (decodedToken) {
        const isInternalToken = decodedToken.iss === constants.INTERNAL_AUTH_TOKEN_ISSUER;
        if (!isInternalToken) {
          if (testToken) {
            user = getTestUser(jwtToken);
          } else {
            const userTokenVerifyOptions = {
              algorithms: ["HS512"],
              subject: "account_user_sign_in",
              issuer: constants.USER_AUTH_TOKEN_ISSUER,
            };
            user = jwt.verify(jwtToken, privateKey, userTokenVerifyOptions);
          }
        }
      }
    } catch (err) {
      logger.error(`Passport localapikey validation: ${err.name || 'Unexpected error'} occurred fetching the user from the jwt`, err);
    }
    return user;
  }

  return {
    initialize: function (passportInitOptions) {
      return passport.initialize(passportInitOptions);
    },
    authenticate: function () {
      return innerAuthenticateMiddleware;
    },
    // tokenSecured,
    tokenSecuredWithoutAccount,
    tokenSecuredForBackoffice,
    tokenSecuredForAudiences,
    customerTokenSecured,
    optionalTokenSecured,
    validateJwtIfGiven
  };
};

module.exports = {
  Authenticator,
  InternalAuthTokenProvider,
  SuperUserAuthenticator,
  audiences
};
<commit_msg>[HG] #32q9mp4<commit_after>"use strict";

const assert = require("assert");
const constants = require("./constants");
const InternalAuthTokenProvider = require("./internalAuthTokenProvider");
const {SuperUserAuthenticator} = require("./superUserAuthenticator");
const axios = require("axios");
const audiences = require("./audiences.js")

function Authenticator(options, logger) {

  assert(logger && logger.info && logger.error, "you must provide a logger");
  assert(options.internalAuthTokenSigningSecrets, "you must provide 'internalAuthTokenSigningSecrets'");
  assert(options.internalAuthTokenSigningSecrets.main, "you must provide 'internalAuthTokenSigningSecrets.main'");
  assert(options.internalAuthTokenSigningSecrets.secondary, "you must provide 'internalAuthTokenSigningSecrets.secondary'");

  const internalAuthTokenSigningSecrets = options.internalAuthTokenSigningSecrets;
  const ignoredRoutes = options.ignoredRoutes && Array.isArray(options.ignoredRoutes) ? options.ignoredRoutes : [];
  const strategyOptions = {
    passReqToCallback: true,
    apiKeyHeader: options.authKeyFields && options.authKeyFields.header ? options.authKeyFields.header : "x-api-key",
    apiKeyField: options.authKeyFields && options.authKeyFields.request ? options.authKeyFields.request : "x-api-key"
  };
  let preLoadedUser = null;

  // username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]

  const passport = require("passport");
  const LocalStrategy = require("passport-localapikey-update").Strategy;
  const SimpleDao = require("btrz-simple-dao").SimpleDao;
  const simpleDao = new SimpleDao(options);
  const jwt = require("jsonwebtoken");

  function useTestKey() {
    return new Promise(function (resolve) {
      if (options.testUser) {
        resolve(options.testUser);
      } else {
        resolve(true);
      }
    });
  }

  function getTestUser(token) {
    if (isTestToken(token)) {
      if (options.testUser) {
        return options.testUser;
      } else {
        return true;
      }
    }
    return null;
  }

  function isTestToken(token) {
    return (token && token === options.testToken);
  }

  function isCorrectBackOfficeAudience(audience) {
    return Array.isArray(options.audiences) ? options.audiences.indexOf(audience) > -1 : audience === "betterez-app";
  }

  function useDb(apikey) {
    let query = {};
    query[options.collection.property] =  apikey;
    return simpleDao.connect()
      .then((db) => {
        return db.collection(options.collection.name).findOne(query)
        .then((result) => {
          if(!result) {
            logger.error("api-key not found");
          }
          return result;
        });
      })
      .catch((err) => {
        return Promise.reject(err);
      });
  }

  function getAuthInfo(apikey) {
    const url = `${options.apiUrl}/${apikey}`;
    const payload = {
      headers: {
        "Authorization": `Bearer ${options.internalAuthTokenProvider.getToken()}`
      },
      body: {},
      json: true
    };
    return axios.get(url, payload)
      .then((info) => {
        preLoadedUser = info.data.user;
        return info.data.application;
      })
      .catch((err) => {
        logger.error("ERROR getting auth info::getAuthInfo::", err);
        return null;
      });
  }

  function useApiAuth() {
    return options.apiAuth && options.apiUrl && options.internalAuthTokenProvider;
  }

  function findByApiKey(apikey) {
    if (apikey === options.testKey) {
      return useTestKey(apikey);
    }

    if (useApiAuth()) {
      return getAuthInfo(apikey);
    }

    return useDb(apikey);
  }

  function findUserById(userId) {
    if (preLoadedUser) {
      return Promise.resolve(preLoadedUser);
    }

    if(typeof userId !== "string") {
      return Promise.reject(new Error("userId must be a string"));
    }

    return simpleDao.connect()
      .then((db) => {
        return db.collection(constants.DB_USER_COLLECTION_NAME).findOne({_id: simpleDao.objectId(userId), deleted: false});
      });
  }

  function shouldIgnoreRoute(originalUrl, method) {
    return ignoredRoutes.some(function (ignoredRoute) {
      if (typeof ignoredRoute === "string") {
        return originalUrl.match(ignoredRoute) || null;
      } else if (typeof ignoredRoute === "object") {
        let methodMatches = (ignoredRoute.methods.indexOf(method) > -1);
        return (methodMatches && originalUrl.match(ignoredRoute.route));
      } else {
        return null;
      }
    });
  }

  function isIgnoredRouteWithoutAuthAttempt(req, strategyOptions) {
    const isXApiKey = req.headers[strategyOptions.apiKeyHeader] || req.query[strategyOptions.apiKeyField];

    return shouldIgnoreRoute(req.originalUrl, req.method) && !isXApiKey;
  }

  function innerAuthenticateMiddleware(req, res, next) {
    const jwtToken = getAuthToken(req);
    const decodedToken = decodeToken(jwtToken);
    const isXApiKey = req.headers[strategyOptions.apiKeyHeader] || req.query[strategyOptions.apiKeyField];
    const someAuthAttempt = decodedToken || isXApiKey;
    const isInternalToken = decodedToken && decodedToken.iss === constants.INTERNAL_AUTH_TOKEN_ISSUER;

    if(isInternalToken) {
      req.internalUser = true;
    }

    if (shouldIgnoreRoute(req.originalUrl, req.method) && (!someAuthAttempt || isInternalToken)) {
      next();
    } else {
      passport.authenticate("localapikey", {session: false})(req, res, next);
    }
  }

  passport.use(new LocalStrategy(strategyOptions,
    function (req, apikey, done) {
      let onSuccess = function (result) {
        const jwtToken = getAuthToken(req);
        req.application = result;
        req.tokens = { token: apikey, jwtToken };
        // if jwtToken is present obtain user from it
        if (result && result.privateKey && jwtToken) {
          const user = fetchUserFromJwtToken(jwtToken, result.privateKey);
          if (user && user.aud === 'customer') {
            req.customer = user;
          } else {
            req.user = user;
          }
        }
        // done executes Passport login and fills req.user (or an alias if userProperty is defined in root index.js)
        return done(null, result);
      };
      let onErr = function (err) { return done(err, null); };

      let result = findByApiKey(apikey, req).then(onSuccess, onErr);
      if (result.done) {
        result.done();
      }
    }
  ));

  function getAuthToken(req) {
    return req.headers.authorization ? req.headers.authorization.replace(/^Bearer /, "") : null;
  }

  function decodeToken(token) {
    try {
      return jwt.decode(token);
    } catch (err) {
      return null;
    }
  }

  function shouldValidateAccount(bypassAccount, req) {
    return !bypassAccount && !(req.account && req.account.privateKey);
  }

  function verifyInternalToken(token, secrets) {

    function verify(keyName, opts) {
      try {
        return jwt.verify(token, secrets[keyName], opts);
      } catch (err) {
        // failing to validate the token against one of the signing keys is expected behaviour when a key rotation is in progress
        logger.info(`authenticateTokenMiddleware: Failed to validate internal auth token using ${keyName} signing key`, err);
        return false;
      }
    }

    const opts = {
      algorithms: ["HS512"],
      issuer: constants.INTERNAL_AUTH_TOKEN_ISSUER,
    };

    return verify("main", opts) || verify("secondary", opts);
  }

  // if this setup is used then the endpoint will be opened, please sanitize the content if needed
  // if some auth attempt is done then authtentication will be performed
  function optionalTokenSecured(req, res, next) {
    if (isIgnoredRouteWithoutAuthAttempt(req, strategyOptions)) {
      return next();
    }

    return authenticateTokenMiddleware(req, res, next);
  }

  function authenticateTokenMiddleware(req, res, next, options = {}) {
    const {bypassAccount = false} = options;

    if (shouldValidateAccount(bypassAccount, req)) {
      logger.error("authenticateTokenMiddleware: No account or account has no private key");
      return res.status(401).send("Unauthorized");
    } else if (!req.headers.authorization) {
      logger.info("authenticateTokenMiddleware: Request is missing 'authorization' header");
      return res.status(401).send("Unauthorized");
    }

    // at this point headers.authorization was already checked for existency
    const jwtToken = getAuthToken(req);

    // moving a the token process to a unified function
    // which will also be used after the passport login happens
    // but omitting res, next and options
    processJwtToken(req, res, jwtToken, next, options);
  }

  function tokenSecured(req, res, next) {
    return authenticateTokenMiddleware(req, res, next);
  }

  //if channel 'backoffice' or 'agency-backoffice' is requested in the body or querystring,
  //checks request has a valid token for backoffice ('betterez-app' internal application)
  function tokenSecuredForBackoffice(req, res, next) {
    let channel = (req.body ? req.body.channel : "") || (req.query ? req.query.channel : "");
    let channels = (req.body ? req.body.channels : "") || (req.query ? req.query.channels : "");
    let mustAuth = false;

    if (channels) {
      if (!Array.isArray(channels)) {
        channels = channels.split(",");
      }

      channels.forEach(function (ch) {
        if (ch.trim().toLowerCase() === "backoffice" || ch.trim().toLowerCase() === "agency-backoffice") {
          mustAuth = true;
          return;
        }
      });
    }
    if (!mustAuth && channel && (channel.trim().toLowerCase() === "backoffice" || channel.trim().toLowerCase() === "agency-backoffice")) {
      mustAuth = true;
    }

    if (mustAuth) {
      authenticateTokenMiddleware(req, res, function (err) {
        if (err) {
          return next(err);
        }
        if (isTestToken(getAuthToken(req))) {
          return next();
        }
        if (!req.user || !isCorrectBackOfficeAudience(req.user.aud)) {
          return res.status(401).send("Unauthorized");
        } else {
          return next();
        }
      });
    } else {
      return next();
    }
  }

  function customerTokenSecured(req, res, next) {
    return authenticateTokenMiddleware(req, res, next, {audience: "customer"});
  }

  function tokenSecuredWithoutAccount(req, res, next) {
    return authenticateTokenMiddleware(req, res, next, {bypassAccount: true});
  }

  function tokenSecuredForAudiences(audiences) {
    return function (req, res, next) {
      return authenticateTokenMiddleware(req, res, function (err) {
        if (err) {
          return next(err);
        }
        if (isTestToken(getAuthToken(req))) {
          return next();
        }
        const notAuthenticated = !req.user,
          wrongAudience = audiences.indexOf(req.user.aud) === -1;
        if (notAuthenticated || (wrongAudience && !req.internalUser)) {
          return res.status(401).send("Unauthorized");
        } else {
          return next();
        }
      });
    };
  }

  function validateJwtIfGiven(req, res, next) {
    const jwtToken = getAuthToken(req);
    if (!jwtToken) {
      return next();
    }
    return authenticateTokenMiddleware(req, res, next);
  }

  function findOneAdministrator(accountId) {
    const query = {
      accountId,
      deleted: false,
      "roles.administrator": 1,
      "locked.status": false,
    };
    return simpleDao.connect()
      .then((db) => {
        return db.collection(constants.DB_USER_COLLECTION_NAME).findOne(query);
      });
  }

  function findUserForInternalToken(application) {
    // find the "original user", using the userId of the Application found by x-api-key
    return findUserById(application.userId).then((user) => {
      if (user) {
        return user;
      }
      // the "original user" is no longer enabled, fetch a valid administrator to impersonate
      return findOneAdministrator(application.accountId);
    });
  }

  function processJwtToken(req, res, jwtToken, next, options = {}) {
    // will only assign req.user if it's not present. Because it could've been assigned previously
    const {audience = null, bypassAccount = false} = options;
    const decodedToken = decodeToken(jwtToken);

    if (isTestToken(jwtToken)) {
      if (!req.user) {
        req.user = getTestUser(jwtToken);
      }
      return next();
    }

    if (!decodedToken) {
      logger.error("authenticateTokenMiddleware: Token is malformed");
      return res.status(401).send("Unauthorized");
    } else if (!decodedToken.iss) {
      logger.error("authenticateTokenMiddleware: Token does not specify its issuer");
      return res.status(401).send("Unauthorized");
    }

    const isInternalToken = decodedToken.iss === constants.INTERNAL_AUTH_TOKEN_ISSUER;

    if (isInternalToken) {
      const tokenPayload = verifyInternalToken(jwtToken, internalAuthTokenSigningSecrets);

      if (!tokenPayload) {
        logger.error("authenticateTokenMiddleware: Failed to validate internal auth token using any signing key");
        return res.status(401).send("Unauthorized");
      }
      if (bypassAccount) {
        return next();
      }

      return findUserForInternalToken(req.account)
        .then((user) => {
          assert(user, "unable to find user to impersonate");

          Reflect.deleteProperty(user, "password");

          if (!req.user) {
            req.user = Object.assign({}, user, tokenPayload);
            req.user._id = req.user._id.toString();
          }
          return next();
        })
        .catch((err) => {
          logger.error(`authenticateTokenMiddleware: Error occurred finding user to impersonate for internal token. Check user ${req.account.userId} exists, or the account has at least one enabled administrator.`, err);
          return res.status(401).send("Unauthorized");
        });
    } else {
      // Validate a user-provided token
      try {
        const userTokenVerifyOptions = {
          algorithms: ["HS512"],
          subject: "account_user_sign_in",
          issuer: constants.USER_AUTH_TOKEN_ISSUER
        };

        if (audience) {
          userTokenVerifyOptions.audience = audience;
        }
        // audience does not exist at api-key verification time, so if it's defined, token needs to be verified again
        if (!req.user || audience) {
          req.user = jwt.verify(jwtToken, req.account.privateKey, userTokenVerifyOptions);
        }
        return next();
      } catch (err) {
        if (err.name === "TokenExpiredError" || err.name === "JsonWebTokenError") {
          logger.info(`authenticateTokenMiddleware: Token expired or 'JsonWebTokenError' occurred`, err);
          return res.status(401).send("Unauthorized");
        }

        logger.error(`authenticateTokenMiddleware: Unexpected error occurred validating user token`, err);
        return res.status(401).send("Unauthorized");
      }
    }
  }

  function fetchUserFromJwtToken(jwtToken, privateKey) {
    let user = null;
    try {
      const decodedToken = decodeToken(jwtToken);
      const testToken = isTestToken(jwtToken);
      if (decodedToken) {
        const isInternalToken = decodedToken.iss === constants.INTERNAL_AUTH_TOKEN_ISSUER;
        if (!isInternalToken) {
          if (testToken) {
            user = getTestUser(jwtToken);
          } else {
            const userTokenVerifyOptions = {
              algorithms: ["HS512"],
              subject: "account_user_sign_in",
              issuer: constants.USER_AUTH_TOKEN_ISSUER,
            };
            user = jwt.verify(jwtToken, privateKey, userTokenVerifyOptions);
          }
        }
      }
    } catch (err) {
      logger.error(`Passport localapikey validation: ${err.name || 'Unexpected error'} occurred fetching the user from the jwt`, err);
    }
    return user;
  }

  return {
    initialize: function (passportInitOptions) {
      return passport.initialize(passportInitOptions);
    },
    authenticate: function () {
      return innerAuthenticateMiddleware;
    },
    // tokenSecured,
    tokenSecuredWithoutAccount,
    tokenSecuredForBackoffice,
    tokenSecuredForAudiences,
    customerTokenSecured,
    optionalTokenSecured,
    validateJwtIfGiven
  };
};

module.exports = {
  Authenticator,
  InternalAuthTokenProvider,
  SuperUserAuthenticator,
  audiences
};
<|endoftext|>