UNPKG

expresser

Version:

A ready to use Node.js web app wrapper, built on top of Express.

340 lines (279 loc) 14.8 kB
# EXPRESSER SETTINGS # ----------------------------------------------------------------------------- # All main settings for the Expresser platform are set and described on the # settings.default.json file. Do not edit it!!! To change settings please # create a settings.json file and put your values there. # # You can also create specific settings for different runtime environments. # For example to set settings on development, create `settings.development.json` # and for production a `settings.production.json` file. These will be parsed # AFTER the main `settings.json` file. # # Please note that the `settings.json` must ne located on the root of your app! # <!-- # @example Sample settings.json file # { # "general": { # "debug": true, # "appTitle": "A Super Cool App" # }, # "firewall" { # "enabled": false # } # } # --> class Settings crypto = require "crypto" fs = require "fs" lodash = require "lodash" path = require "path" utils = require "./utils.coffee" currentEnv: process.env.NODE_ENV # MAIN METHODS # -------------------------------------------------------------------------- # Load settings from settings.default.json, then settings.json, then environment specific settings. load: => @currentEnv = process.env.NODE_ENV or "development" @loadFromJson "settings.default.json" @loadFromJson "settings.json" @loadFromJson "settings.#{@currentEnv.toString().toLowerCase()}.json" # Helper to load values from the specified settings file. # @param [String] filename The filename or path to the settings file. # @param [Boolean] extend If true it won't update settings that are already existing. # @return [Object] Returns the JSON representation of the loaded file. loadFromJson: (filename, extend) => extend = false if not extend? filename = utils.getFilePath filename settingsJson = null # Has json? Load it. Try using UTF8 first, if failed, use ASCII. if filename? encUtf8 = {encoding: "utf8"} encAscii = {encoding: "ascii"} # Try parsing the file with UTF8 first, if fails, try ASCII. try settingsJson = fs.readFileSync filename, encUtf8 settingsJson = utils.minifyJson settingsJson catch ex settingsJson = fs.readFileSync filename, encAscii settingsJson = utils.minifyJson settingsJson # Helper function to overwrite properties. xtend = (source, target) -> for prop, value of source if value?.constructor is Object target[prop] = {} if not target[prop]? xtend source[prop], target[prop] else if not extend or not target[prop]? target[prop] = source[prop] xtend settingsJson, this if @general.debug and @logger.console console.log "Settings.loadFromJson", filename # Return the JSON representation of the file (or null if not found / empty). return settingsJson # Reset to default settings. reset: => @instance = new Settings() @instance.load() # ENCRYPTION # -------------------------------------------------------------------------- # Helper to encrypt or decrypt settings files. The default encryption password # defined on the `Settings.coffee` file is "ExpresserSettings", which ideally you # should change. The default cipher algorithm is AES 256. # @param [Boolean] encrypt Pass true to encrypt, false to decrypt. # @param [String] filename The file to be encrypted or decrypted. # @param [Object] options Options to be passed to the cipher. # @option options [String] cipher The cipher to be used, default is aes256. # @option options [String] password The default encryption password. cryptoHelper: (encrypt, filename, options) => options = {} if not options? options = lodash.defaults options, {cipher: "aes256", password: "ExpresserSettings"} settingsJson = @loadFromJson filename, false # Settings file not found or invalid? Stop here. if not settingsJson? and @logger.console console.warn "Settings.cryptoHelper", encrypt, filename, "File not found or invalid, abort!" return false # If trying to encrypt and settings property `encrypted` is true, # abort encryption and log to the console. if settingsJson.encrypted is true and encrypt if @logger.console console.warn "Settings.cryptoHelper", encrypt, filename, "Property 'encrypted' is true, abort!" return false # Helper to parse and encrypt / decrypt settings data. parser = (obj) => currentValue = null for prop, value of obj if value?.constructor is Object parser obj[prop] else try currentValue = obj[prop] if encrypt # Check the property data type and prefix the new value. if lodash.isBoolean currentValue newValue = "bool:" else if lodash.isNumber currentValue newValue = "number:" else newValue = "string:" # Create cipher amd encrypt data. c = crypto.createCipher options.cipher, options.password newValue += c.update currentValue.toString(), @general.encoding, "hex" newValue += c.final "hex" else # Split the data as "datatype:encryptedValue". arrValue = currentValue.split ":" newValue = "" # Create cipher and decrypt. c = crypto.createDecipher options.cipher, options.password newValue += c.update arrValue[1], "hex", @general.encoding newValue += c.final @general.encoding # Cast data type (boolean, number or string). if arrValue[0] is "bool" if newValue is "true" or newValue is "1" newValue = true else newValue = false else if arrValue[0] is "number" newValue = parseFloat newValue catch ex if @logger.console console.error "Settings.cryptoHelper", encrypt, filename, ex, currentValue # Update settings property value. obj[prop] = newValue # Remove `encrypted` property prior to decrypting. if not encrypt delete settingsJson["encrypted"] # Process settings data. parser settingsJson # Add `encrypted` property after file is encrypted. if encrypt settingsJson.encrypted = true # Stringify and save the new settings file. newSettingsJson = JSON.stringify settingsJson, null, 4 fs.writeFileSync filename, newSettingsJson, {encoding: @general.encoding} return true # Helper to encrypt the specified settings file. Please see `cryptoHelper` above. # @param [String] filename The file to be encrypted. # @param [Object] options Options to be passed to the cipher. # @return [Boolean] Returns true if encryption OK, false if something went wrong. encrypt: (filename, options) => @cryptoHelper true, filename, options # Helper to decrypt the specified settings file. Please see `cryptoHelper` above. # @param [String] filename The file to be decrypted. # @param [Object] options Options to be passed to the cipher. # @return [Boolean] Returns true if decryption OK, false if something went wrong. decrypt: (filename, options) => @cryptoHelper false, filename, options # FILE WATCHER # -------------------------------------------------------------------------- # Enable or disable the settings files watcher to auto reload settings when file changes. # The `callback` is optional in case you want to notify another module about settings updates. # @param [Boolean] enable If enabled is true activate the fs watcher, otherwise deactivate. # @param [Method] callback A function (event, filename) triggered when a settings file changes. watch: (enable, callback) => if callback? and not lodash.isFunction callback throw new TypeError "The callback must be a valid function, or null/undefined." logToConsole = @general.debug and @logger.console # Add / remove watcher for the @json file if it exists. filename = utils.getFilePath "settings.json" if filename? if enable fs.watchFile filename, {persistent: true}, (evt, filename) => @loadFromJson filename console.log "Settings.watch", filename, "Reloaded!" if logToConsole callback(evt, filename) if callback? else fs.unwatchFile filename, callback # Add / remove watcher for the settings.NODE_ENV.json file if it exists. filename = utils.getFilePath "settings.#{@currentEnv.toString().toLowerCase()}.json" if filename? if enable fs.watchFile filename, {persistent: true}, (evt, filename) => @loadFromJson filename console.log "Settings.watch", filename, "Reloaded!" if logToConsole callback(evt, filename) if callback? else fs.unwatchFile filename, callback if logToConsole console.log "Settings.watch", enable, (if callback? then "With callback" else "No callback") # PAAS # -------------------------------------------------------------------------- # Update settings based on Cloud Environmental variables. If a `filter` is specified, # update only settings that match it, otherwise update everything. # @param [String] filter Filter settings to be updated, for example "mailer" or "database". updateFromPaaS: (filter) => env = process.env filter = false if not filter? or filter is "" # Update app IP and port (OpenShift, AppFog). if not filter or filter.indexOf("app") >= 0 ip = env.OPENSHIFT_NODEJS_IP or env.IP port = env.OPENSHIFT_NODEJS_PORT or env.VCAP_APP_PORT or env.PORT @app.ip = ip if ip? and ip isnt "" @app.port = port if port? and port isnt "" # Update database settings (AppFog, MongoLab, MongoHQ). if not filter or filter.indexOf("database") >= 0 vcap = env.VCAP_SERVICES vcap = JSON.parse vcap if vcap? # Check for AppFog MongoDB variables. if vcap? and vcap isnt "" mongo = vcap["mongodb-1.8"] mongo = mongo[0]["credentials"] if mongo? if mongo? @database.connString = "mongodb://#{mongo.hostname}:#{mongo.port}/#{mongo.db}" # Check for MongoLab variables. mongoLab = env.MONGOLAB_URI @database.connString = mongoLab if mongoLab? and mongoLab isnt "" # Check for MongoHQ variables. mongoHq = env.MONGOHQ_URL @database.connString = mongoHq if mongoHq? and mongoHq isnt "" # Update logger settings (Logentries and Loggly). if not filter or filter.indexOf("logger") >= 0 logentriesToken = env.LOGENTRIES_TOKEN logglyToken = env.LOGGLY_TOKEN logglySubdomain = env.LOGGLY_SUBDOMAIN @logger.logentries.token = logentriesToken if logentriesToken? and logentriesToken isnt "" @logger.loggly.token = logglyToken if logglyToken? and logglyToken isnt "" @logger.loggly.subdomain = logglySubdomain if logglySubdomain? and logglySubdomain isnt "" # Update mailer settings (Mailgun, Mandrill, SendGrid). if not filter or filter.indexOf("mail") >= 0 currentSmtpHost = @mailer.smtp.host?.toLowerCase() currentSmtpHost = "" if not currentSmtpHost? # Get and set Mailgun. if currentSmtpHost.indexOf("mailgun") >= 0 or smtpHost?.indexOf("mailgun") >= 0 @mailer.smtp.service = "mailgun" smtpUser = env.MAILGUN_SMTP_LOGIN smtpPassword = env.MAILGUN_SMTP_PASSWORD if smtpUser? and smtpUser isnt "" and smtpPassword? and smtpPassword isnt "" @mailer.smtp.user = smtpUser @mailer.smtp.password = smtpPassword # Get and set Mandrill. if currentSmtpHost.indexOf("mandrill") >= 0 or smtpHost?.indexOf("mandrill") >= 0 @mailer.smtp.service = "mandrill" smtpUser = env.MANDRILL_USERNAME smtpPassword = env.MANDRILL_APIKEY if smtpUser? and smtpUser isnt "" and smtpPassword? and smtpPassword isnt "" @mailer.smtp.user = smtpUser @mailer.smtp.password = smtpPassword # Get and set SendGrid. if currentSmtpHost.indexOf("sendgrid") >= 0 or smtpHost?.indexOf("sendgrid") >= 0 @mailer.smtp.service = "sendgrid" smtpUser = env.SENDGRID_USERNAME smtpPassword = env.SENDGRID_PASSWORD if smtpUser? and smtpUser isnt "" and smtpPassword? and smtpPassword isnt "" @mailer.smtp.user = smtpUser @mailer.smtp.password = smtpPassword # Log to console. if @general.debug and @logger.console console.log "Settings.updateFromPaaS", "Updated!", filter # Singleton implementation # ----------------------------------------------------------------------------- Settings.getInstance = -> if process.env is "test" obj = new Settings() obj.load() obj.logger.console = false return obj if not @instance? @instance = new Settings() @instance.load() return @instance module.exports = exports = Settings.getInstance()