'use strict';
const Joi = require('joi');
const MongoModels = require('mongo-models');


class Dummy extends MongoModels {}

Dummy.collectionName = 'dummies';

Dummy.schema = Joi.object().keys({
    name: Joi.string().required(),
    hasHat: Joi.boolean()
});

Dummy.indexes = [
    { key: { name: 1 } },
    { key: { hasHat: -1 } }
];


module.exports = Dummy;
Example #2
0
export default () => Joi.object({
  pkg: Joi.object({
    version: Joi.string().default(Joi.ref('$version')),
    branch: Joi.string().default(Joi.ref('$branch')),
    buildNum: Joi.number().default(Joi.ref('$buildNum')),
    buildSha: Joi.string().default(Joi.ref('$buildSha')),
  }).default(),

  env: Joi.object({
    name: Joi.string().default(Joi.ref('$env')),
    dev: Joi.boolean().default(Joi.ref('$dev')),
    prod: Joi.boolean().default(Joi.ref('$prod'))
  }).default(),

  dev: Joi.object({
    basePathProxyTarget: Joi.number().default(5603),
  }).default(),

  pid: Joi.object({
    file: Joi.string(),
    exclusive: Joi.boolean().default(false)
  }).default(),

  cpu: Joi.object({
    cgroup: Joi.object({
      path: Joi.object({
        override: Joi.string().default()
      })
    })
  }),

  cpuacct: Joi.object({
    cgroup: Joi.object({
      path: Joi.object({
        override: Joi.string().default()
      })
    })
  }),

  server: Joi.object({
    uuid: Joi.string().guid().default(),
    name: Joi.string().default(os.hostname()),
    host: Joi.string().hostname().default('localhost'),
    port: Joi.number().default(5601),
    maxPayloadBytes: Joi.number().default(1048576),
    autoListen: Joi.boolean().default(true),
    defaultRoute: Joi.string().default('/app/kibana').regex(/^\//, `start with a slash`),
    basePath: Joi.string().default('').allow('').regex(/(^$|^\/.*[^\/]$)/, `start with a slash, don't end with one`),
    customResponseHeaders: Joi.object().unknown(true).default({}),
    ssl: Joi.object({
      enabled: Joi.boolean().default(false),
      redirectHttpFromPort: Joi.number(),
      certificate: Joi.string().when('enabled', {
        is: true,
        then: Joi.required(),
      }),
      key: Joi.string().when('enabled', {
        is: true,
        then: Joi.required()
      }),
      keyPassphrase: Joi.string(),
      certificateAuthorities: Joi.array().single().items(Joi.string()),
      supportedProtocols: Joi.array().items(Joi.string().valid('TLSv1', 'TLSv1.1', 'TLSv1.2')),
      cipherSuites: Joi.array().items(Joi.string()).default(cryptoConstants.defaultCoreCipherList.split(':'))
    }).default(),
    cors: Joi.when('$dev', {
      is: true,
      then: Joi.object().default({
        origin: ['*://localhost:9876'] // karma test server
      }),
      otherwise: Joi.boolean().default(false)
    }),
    xsrf: Joi.object({
      disableProtection: Joi.boolean().default(false),
      token: Joi.string().optional().notes('Deprecated')
    }).default(),
  }).default(),

  logging: Joi.object().keys({
    silent: Joi.boolean().default(false),

    quiet: Joi.boolean()
      .when('silent', {
        is: true,
        then: Joi.default(true).valid(true),
        otherwise: Joi.default(false)
      }),

    verbose: Joi.boolean()
      .when('quiet', {
        is: true,
        then: Joi.valid(false).default(false),
        otherwise: Joi.default(false)
      }),

    events: Joi.any().default({}),
    dest: Joi.string().default('stdout'),
    filter: Joi.any().default({}),
    json: Joi.boolean()
      .when('dest', {
        is: 'stdout',
        then: Joi.default(!process.stdout.isTTY),
        otherwise: Joi.default(true)
      }),

    useUTC: Joi.boolean().default(true),
  })
    .default(),

  ops: Joi.object({
    interval: Joi.number().default(5000),
  }).default(),

  plugins: Joi.object({
    paths: Joi.array().items(Joi.string()).default([]),
    scanDirs: Joi.array().items(Joi.string()).default([]),
    initialize: Joi.boolean().default(true)
  }).default(),

  path: Joi.object({
    data: Joi.string().default(getData())
  }).default(),

  optimize: Joi.object({
    enabled: Joi.boolean().default(true),
    bundleFilter: Joi.string().default('!tests'),
    bundleDir: Joi.string().default(fromRoot('optimize/bundles')),
    viewCaching: Joi.boolean().default(Joi.ref('$prod')),
    watch: Joi.boolean().default(false),
    watchPort: Joi.number().default(5602),
    watchHost: Joi.string().hostname().default('localhost'),
    watchPrebuild: Joi.boolean().default(false),
    watchProxyTimeout: Joi.number().default(5 * 60000),
    useBundleCache: Joi.boolean().default(Joi.ref('$prod')),
    unsafeCache: Joi.when('$prod', {
      is: true,
      then: Joi.boolean().valid(false),
      otherwise: Joi
        .alternatives()
        .try(
          Joi.boolean(),
          Joi.string().regex(/^\/.+\/$/)
        )
        .default(true),
    }),
    sourceMaps: Joi.when('$prod', {
      is: true,
      then: Joi.boolean().valid(false),
      otherwise: Joi
        .alternatives()
        .try(
          Joi.string().required(),
          Joi.boolean()
        )
        .default('#cheap-source-map'),
    }),
    profile: Joi.boolean().default(false)
  }).default(),
  status: Joi.object({
    allowAnonymous: Joi.boolean().default(false)
  }).default(),
  map: Joi.object({
    manifestServiceUrl: Joi.when('$dev', {
      is: true,
      then: Joi.string().default('https://staging-dot-catalogue-dot-elastic-layer.appspot.com/v1/manifest'),
      otherwise: Joi.string().default('https://catalogue.maps.elastic.co/v1/manifest')
    })
  }).default(),
  tilemap: Joi.object({
    url: Joi.string(),
    options: Joi.object({
      attribution: Joi.string(),
      minZoom: Joi.number().min(0, 'Must be 0 or higher').default(0),
      maxZoom: Joi.number().default(10),
      tileSize: Joi.number(),
      subdomains: Joi.array().items(Joi.string()).single(),
      errorTileUrl: Joi.string().uri(),
      tms: Joi.boolean(),
      reuseTiles: Joi.boolean(),
      bounds: Joi.array().items(Joi.array().items(Joi.number()).min(2).required()).min(2)
    }).default()
  }).default(),
  regionmap: Joi.object({
    includeElasticMapsService: Joi.boolean().default(true),
    layers: Joi.array().items(Joi.object({
      url: Joi.string(),
      type: Joi.string(),
      attribution: Joi.string(),
      name: Joi.string(),
      fields: Joi.array().items(Joi.object({
        name: Joi.string(),
        description: Joi.string()
      }))
    }))
  }).default(),

  i18n: Joi.object({
    defaultLocale: Joi.string().default('en'),
  }).default(),

}).default();
     'wheelchair'
   ])
   .default('driving-car')
   .description(
     'Specifies the mode of transport to use when calculating directions.'
   ),
 restrictions: Joi.object()
   .when('profile', {
     is: Joi.string().regex(/^driving-hgv$/),
     then: Joi.object().keys({
       length: Joi.number().description('Length of the HGV vehicle'),
       width: Joi.number().description('Width of the HGV vehicle'),
       weight: Joi.number().description('Weight of the HGV vehicle'),
       height: Joi.number().description('Height of the HGV vehicle'),
       axleload: Joi.number().description('Axleload of the HGV vehicle'),
       hazmat: Joi.boolean().description(
         'Whether the HGV carries hazardous materials'
       )
     })
   })
   .when('profile', {
     is: Joi.string().regex(/^wheelchair$/),
     then: Joi.object().keys({
       surface_type: Joi.any().description('Surface type'),
       track_type: Joi.any().description('Track type'),
       smoothness_type: Joi.any().description('Smoothness type'),
       maximum_sloped_curb: Joi.any().description('Maximum sloped curb'),
       maximum_incline: Joi.any().description('Maximum incline')
     })
   })
   .when('profile', {

internals.Policy.prototype.ttl = function (created) {

    return internals.Policy.ttl(this.rule, created);
};


internals.schema = Joi.object({
    expiresIn: Joi.number().integer().min(1),
    expiresAt: Joi.string().regex(/^\d\d?\:\d\d$/),
    staleIn: [Joi.number().integer().min(1).max(86400000 - 1), Joi.func()],               // One day - 1 (max is inclusive)
    staleTimeout: Joi.number().integer().min(1),
    generateFunc: Joi.func(),
    generateTimeout: Joi.number().integer().min(1).allow(false),
    generateOnReadError: Joi.boolean(),
    generateIgnoreWriteError: Joi.boolean(),
    dropOnError: Joi.boolean(),
    pendingGenerateTimeout: Joi.number().integer().min(1),

    // Ignored external keys (hapi)

    privacy: Joi.any(),
    cache: Joi.any(),
    segment: Joi.any(),
    shared: Joi.any()
})
    .without('expiresIn', 'expiresAt')
    .with('staleIn', 'generateFunc')
    .with('generateOnReadError', 'generateFunc')
    .with('generateIgnoreWriteError', 'generateFunc')
Example #5
0
  page: { allow: true },
  create: { allow: true },
  delete: { allow: true }
};

var noCreatePermissions = {
  page: { allow: true },
  delete: { allow: true }
};

var noPermissions = {};

var mentions = {
  validation: Joi.object().keys({
    page: Joi.object().keys({
      allow: Joi.boolean()
    }),
    create: Joi.object().keys({
      allow: Joi.boolean()
    }),
    delete: Joi.object().keys({
      allow: Joi.boolean()
    })
  }),

  layout: {
    page: { title: 'Allow User to View Mentions' },
    create: { title: 'Allow User to Create Mentions' },
    delete: { title: 'Allow User to Delete Mentions' }
  },
const joi = require('joi')

const envVarsSchema = joi.object({  
  NODE_ENV: joi.string()
    .allow(['development', 'production', 'test', 'provision'])
    .required(),
  PORT: joi.number()
    .required(),
  LOGGER_LEVEL: joi.string()
    .allow(['error', 'warn', 'info', 'verbose', 'debug', 'silly'])
    .default('info'),
  LOGGER_ENABLED: joi.boolean()
    .truthy('TRUE')
    .truthy('true')
    .falsy('FALSE')
    .falsy('false')
    .default(true)
}).unknown()
  .required()

const { error, value: envVars } = joi.validate(process.env, envVarsSchema)  
if (error) {  
  throw new Error(`Config validation error: ${error.message}`)
}

const config = {  
  env: envVars.NODE_ENV,
  isTest: envVars.NODE_ENV === 'test',
  isDevelopment: envVars.NODE_ENV === 'development',
  logger: {
    level: envVars.LOGGER_LEVEL,
Example #7
0
var lodash = require('lodash');
var joi = require('joi');

var types = {
    'number'       :  {validate: joi.number()},
    'alpha'        :  {validate: joi.string().regex(/^[a-z]+$/i)},
    'alphanumeric' :  {validate: joi.string().alphanum()},
    'date'         :  {
        validate:  joi.date(),
        default: function () {
            return Date.now();
        }
    },
    'email'        :  {validate: joi.string().email()},
    'boolean'      :  {
        validate: joi.boolean(),
        default:    false,
        onSet:      Boolean,
        processIn:  Boolean,
    },
    'integer': {
        validate: joi.number().integer(),
        processIn: function (v) {
            return parseInt(v, 10);
        },
        setTo: function (v) {
            return parseInt(v, 10);
        },
    },
    'enum': {
        validate: function (value) {
Example #8
0
var Joi = require('joi');

var ConfigSchema = Joi.object().keys({
    tz: Joi.string().required(),
    port: Joi.number().required(),
    recordspath: Joi.string().required(),
    db: Joi.object().keys({
      client: Joi.string().required(),
      connection: Joi.object().keys({
        host: Joi.string().required(),
        port: Joi.number().integer().min(1).max(65535).default(3306),
        user: Joi.string().required(),
        database: Joi.string().required(),
        password: Joi.string().allow('').required(),
        charset: Joi.string().required()
      }).required()
    }),
    cdr: Joi.object().keys({
      table: Joi.string().required()
    }).required(),
    auth: Joi.boolean().valid(true, false).default(false),
    username: Joi.string()
      .when('auth', {is: true, then: Joi.required().default('admin')}),
    password: Joi.string()
      .when('auth', {is: true, then: Joi.required().default('password')}),
    realm: Joi.string()
      .when('auth', {is: true, then: Joi.default('Ryo CDR')}),
});

module.exports = ConfigSchema;
Example #9
0
const Hoek = require('hoek');
const Joi = require('joi');
const Etag = require('./etag');


// Declare internals

const internals = {};


internals.schema = Joi.alternatives([
    Joi.string(),
    Joi.func(),
    Joi.object({
        path: Joi.alternatives(Joi.string(), Joi.func()).required(),
        confine: Joi.alternatives(Joi.string(), Joi.boolean()).default(true),
        filename: Joi.string(),
        mode: Joi.string().valid('attachment', 'inline').allow(false),
        lookupCompressed: Joi.boolean(),
        etagMethod: Joi.string().valid('hash', 'simple').allow(false)
    })
        .with('filename', 'mode')
]);


exports.handler = function (route, options) {

    let settings = Joi.attempt(options, internals.schema, 'Invalid file handler options (' + route.path + ')');
    settings = (typeof options !== 'object' ? { path: options, confine: '.' } : settings);
    settings.confine = settings.confine === true ? '.' : settings.confine;
    Hoek.assert(typeof settings.path !== 'string' || settings.path[settings.path.length - 1] !== '/', 'File path cannot end with a \'/\':', route.path);
Example #10
0
module.exports = function(options) {
  var serverConfig = {
    debug: options.debug,
    connections: {
      routes: {
        security: true
      }
    }
  };

  if ( options.redisUrl ) {
    var redisUrl = require('redis-url').parse(options.redisUrl);
    serverConfig.cache = {
      engine: require('catbox-redis'),
      host: redisUrl.hostname,
      port: redisUrl.port,
      password: redisUrl.password
    };
  }

  var server = new Hapi.Server(serverConfig);

  server.connection({
    host: options.host,
    port: options.port
  });

  if ( options.logging ) {
    server.register({
      register: require('hapi-bunyan'),
      options: {
        logger: require('bunyan').createLogger({
          name: 'id-webmaker-org',
          level: options.logLevel
        })
      }
    }, function(err) {
      Hoek.assert(!err, err);
    });
  }

  server.register([
    require('hapi-auth-cookie'),
    require('inert'),
    require('scooter'),
    {
      register: require('blankie'),
      options: {
        defaultSrc: [
          '\'none\''
        ],
        styleSrc: [
          '\'self\'',
          'https://fonts.googleapis.com'
        ],
        imgSrc: [
          '\'self\'',
          'data:',
          'https://www.google-analytics.com',
          'http://www.google-analytics.com'
        ],
        scriptSrc: [
          '\'self\'',
          '\'unsafe-eval\'',
          'https://www.google-analytics.com',
          'http://www.google-analytics.com'
        ],
        fontSrc: [
          '\'self\'',
          'https://fonts.gstatic.com'
        ]
      }
    }
  ], function(err) {
    // MAYDAY, MAYDAY, MAYDAY!
    Hoek.assert(!err, err);

    server.auth.strategy('session', 'cookie', {
      password: options.cookieSecret,
      cookie: 'webmaker',
      ttl: 1000 * 60 * 60 * 24,
      isSecure: options.secureCookies,
      isHttpOnly: true
    });

    server.auth.default({
      strategy: 'session',
      mode: 'try'
    });
  });

  function skipCSRF(request, reply) {
    return true;
  }

  function isUniqueError(fieldName, err) {
    // SQLite and MariaDB/MySQL have conflicting error messages, and we don't know which DB the login server is using
    if (
      err &&
      // SQLite
      err.indexOf('Users.' + fieldName) !== -1 ||
      (
        // MariaDB/MySQL
        err.indexOf('ER_DUP_ENTRY') !== -1 &&
        err.indexOf(fieldName) !== -1
      )
    ) {
      return true;
    }
    return false;
  }

  server.register({
    register: require('crumb'),
    options: {
      restful: true,
      skip: !options.enableCSRF ? skipCSRF : undefined,
      cookieOptions: {
        isSecure: options.secureCookies
      }
    }
  }, function(err) {
    Hoek.assert(!err, err);
  });

  server.register({
    register: require('../lib/account'),
    options: {
      loginAPI: options.loginAPI,
      uri: options.uri
    }
  }, function(err) {
    Hoek.assert(!err, err);
  });

  var oauthDb = new OAuthDB(options.oauth_clients, options.authCodes, options.accessTokens);

  server.route([
    {
      method: 'GET',
      path: '/',
      handler: function(request, reply) {
        reply.redirect('/signup');
      }
    },
    {
      method: 'GET',
      path: '/{params*}',
      handler: {
        file: {
          path: Path.join(__dirname, '../public/index.html')
        }
      }
    },
    {
      method: 'GET',
      path: '/assets/{param*}',
      handler: {
        directory: {
          path: Path.join(__dirname, '../public')
        }
      }
    },
    {
      method: 'GET',
      path: '/login/oauth/authorize',
      config: {
        validate: {
          query: {
            client_id: Joi.string().required(),
            response_type: Joi.string().valid('code', 'token'),
            scopes: Joi.string().required(),
            state: Joi.string().required(),
            action: Joi.string().optional().valid('signup', 'signin').default('signin')
          }
        },
        pre: [
          {
            assign: 'user',
            method: function(request, reply) {
              if (request.auth.isAuthenticated) {
                return reply(request.auth.credentials);
              }

              var redirectUrl = '/login';
              if (request.query.action === 'signup') {
                redirectUrl = '/signup';
              }

              var redirect = url.parse(redirectUrl, true);
              redirect.query.client_id = request.query.client_id;
              redirect.query.response_type = request.query.response_type;
              redirect.query.state = request.query.state;
              redirect.query.scopes = request.query.scopes;

              reply().takeover().redirect(url.format(redirect));
            }
          },
          {
            assign: 'client',
            method: function(request, reply) {
              oauthDb.getClient(request.query.client_id, reply);
            }
          },
          {
            method: function(request, reply) {
              if (
                request.pre.client.allowed_responses.indexOf(request.query.response_type) === -1
              ) {
                return reply(Boom.forbidden('Response type forbidden: ' + request.query.response_type));
              }
              reply();
            }
          },
          {
            assign: 'scopes',
            method: function(request, reply) {
              reply(request.query.scopes.split(' '));
            }
          },
          {
            assign: 'auth_code',
            method: function(request, reply) {
              if (request.query.response_type !== 'code') {
                return reply(null);
              }

              oauthDb.generateAuthCode(
                request.pre.client.client_id,
                request.pre.user.id,
                request.pre.scopes,
                new Date(Date.now() + 60 * 1000).toISOString(),
                function(err, authCode) {
                  if (err) {
                    return reply(Boom.badRequest('An error occurred processing your request', err));
                  }

                  reply(authCode);
                }
              );
            }
          },
          {
            assign: 'access_token',
            method: function(request, reply) {
              if (request.query.response_type !== 'token') {
                return reply(null);
              }

              oauthDb.generateAccessToken(
                request.pre.client.client_id,
                request.pre.user.id,
                request.pre.scopes,
                reply
              );
            }
          }
        ]
      },
      handler: function(request, reply) {
        var redirectObj = url.parse(request.pre.client.redirect_uri, true);
        redirectObj.search = null;

        if (request.query.response_type === 'token') {
          redirectObj.hash = 'token=' + request.pre.access_token;
        } else {
          redirectObj.query.code = request.pre.auth_code;
          redirectObj.query.client_id = request.query.client_id;
        }
        redirectObj.query.state = request.query.state;

        reply.redirect(url.format(redirectObj));
      }
    },
    {
      method: 'POST',
      path: '/login/oauth/access_token',
      config: {
        validate: {
          payload: {
            grant_type: Joi.any().valid('authorization_code', 'password').required(),
            code: Joi.string().when('grant_type', {
              is: 'authorization_code',
              then: Joi.required(),
              otherwise: Joi.forbidden()
            }),
            client_secret: Joi.string().when('grant_type', {
              is: 'authorization_code',
              then: Joi.required(),
              otherwise: Joi.forbidden()
            }),
            client_id: Joi.string().required(),
            uid: Joi.string().when('grant_type', {
              is: 'password',
              then: Joi.required(),
              otherwise: Joi.forbidden()
            }),
            password: Joi.string().when('grant_type', {
              is: 'password',
              then: Joi.required(),
              otherwise: Joi.forbidden()
            }),
            scopes: Joi.string().when('grant_type', {
              is: 'password',
              then: Joi.required(),
              otherwise: Joi.forbidden()
            })
          },
          failAction: function(request, reply, source, error) {
            reply(Boom.badRequest('invalid ' + source + ': ' + error.data.details[0].path));
          }
        },
        auth: false,
        plugins: {
          crumb: false
        },
        pre: [
          {
            assign: 'grant_type',
            method: function (request, reply) {
              reply(request.payload.grant_type);
            }
          },
          {
            assign: 'client',
            method: function(request, reply) {
              oauthDb.getClient(request.payload.client_id, function(err, client) {
                if ( err ) {
                  return reply(err);
                }
                if (
                  client.allowed_grants.indexOf(request.pre.grant_type) === -1 ||
                  (
                    request.pre.grant_type === 'authorization_code' &&
                    client.client_secret !== request.payload.client_secret
                  )
                ) {
                  return reply(Boom.forbidden('Invalid Client Credentials'));
                }

                reply(client);
              });
            }
          },
          {
            assign: 'authCode',
            method: function(request, reply) {
              if ( request.pre.grant_type === 'password' ) {
                return server.methods.account.verifyPassword(request, function(err, json) {
                  if ( err ) {
                    return reply(err);
                  }

                  reply({
                    user_id: json.user.id,
                    scopes: request.payload.scopes.split(' ')
                  });
                });
              }
              oauthDb.verifyAuthCode(request.payload.code, request.pre.client.client_id, reply);
            }
          },
          {
            assign: 'accessToken',
            method: function(request, reply) {
              oauthDb.generateAccessToken(
                request.pre.client.client_id,
                request.pre.authCode.user_id,
                request.pre.authCode.scopes,
                reply
              );
            }
          }
        ]
      },
      handler: function(request, reply) {
        var responseObj = {
          access_token: request.pre.accessToken,
          scopes: request.pre.authCode.scopes,
          token_type: 'token'
        };

        reply(responseObj);
      }
    },
    {
      method: 'POST',
      path: '/login',
      config: {
        pre: [
          {
            assign: 'user',
            method: function(request, reply) {
              server.methods.account.verifyPassword(request, function(err, json) {
                if ( err ) {
                  return reply(err);
                }

                reply(json.user);
              });
            }
          }
        ]
      },
      handler: function(request, reply) {
        request.auth.session.set(request.pre.user);
        reply({ status: 'Logged In' });
      }
    },
    {
      method: 'POST',
      path: '/request-reset',
      config:{
        auth: false
      },
      handler: function(request, reply) {
        server.methods.account.requestReset(request, function(err, json) {
          if ( err ) {
            return reply(err);
          }

          reply(json);
        });
      }
    },
    {
      method: 'POST',
      path: '/reset-password',
      config:{
        auth: false
      },
      handler: function(request, reply) {
        server.methods.account.resetPassword(request, function(err, json) {
          if ( err ) {
            return reply(err);
          }

          reply(json);
        });
      }
    },
    {
      method: 'POST',
      path: '/create-user',
      config: {
        auth: false,
        plugins: {
          crumb: false
        },
        cors: true,
        validate: {
          payload: {
            username: Joi.string().regex(/^[a-zA-Z0-9\-]{1,20}$/).required(),
            email: Joi.string().email().required(),
            password: Joi.string().regex(/^\S{8,128}$/).required(),
            feedback: Joi.boolean().required(),
            client_id: Joi.string().required(),
            lang: Joi.string().default('en-US')
          },
          failAction: function(request, reply, source, error) {
            reply(Boom.badRequest('invalid ' + source + ': ' + error.data.details[0].path));
          }
        },
        pre: [
          {
            assign: 'username',
            method: function(request, reply) {
              reply(request.payload.username);
            }
          },
          {
            assign: 'password',
            method: function(request, reply) {
              var password = request.payload.password;
              var result = passTest.test(password);

              if ( !result.strong ) {
                var err = Boom.badRequest('Password not strong enough.', result);
                err.output.payload.details = err.data;
                return reply(err);
              }

              reply(password);
            }
          },
          {
            assign: 'client',
            method: function(request, reply) {
              oauthDb.getClient(request.payload.client_id, reply);
            }
          }
        ]
      },
      handler: function(request, reply) {
        server.methods.account.createUser(request, function(err, json) {
          if ( err ) {
            err.output.payload.data = err.data;
            return reply(err);
          }
          if ( json.login_error ) {
            if ( isUniqueError('username', json.login_error) ) {
              return reply(Boom.badRequest('That username is taken'));
            } else if ( isUniqueError('email', json.login_error) ) {
              return reply(Boom.badRequest('An account exists for that email address'));
            }
            return reply(Boom.badRequest(json.login_error));
          }
          request.auth.session.set(json.user);
          reply(json.user);
        });
      }
    },
    {
      method: 'GET',
      path: '/logout',
      config: {
        auth: false,
        pre: [
          {
            assign: 'redirectUri',
            method: function(request, reply) {
              if ( !request.query.client_id ) {
                return reply('https://webmaker.org');
              }
              oauthDb.getClient(request.query.client_id, function(err, client) {
                if ( err ) {
                  return reply(err);
                }
                reply(client.redirect_uri);
              });
            }
          }
        ]
      },
      handler: function(request, reply) {
        request.auth.session.clear();

        var redirectObj = url.parse(request.pre.redirectUri, true);
        redirectObj.query.logout = true;
        reply.redirect(url.format(redirectObj))
          .header('cache-control', 'no-cache');
      }
    },
    {
      method: 'GET',
      path: '/user',
      config: {
        auth: false,
        cors: true,
        pre: [
          {
            assign: 'requestToken',
            method: function(request, reply) {
              var tokenHeader = request.headers.authorization || '';
              tokenHeader = tokenHeader.split(' ');

              if ( tokenHeader[0] !== 'token' || !tokenHeader[1] ) {
                return reply(Boom.unauthorized('Missing or invalid authorization header'));
              }

              reply(tokenHeader[1]);
            }
          },
          {
            assign: 'token',
            method: function(request, reply) {
              oauthDb.lookupAccessToken(request.pre.requestToken, function(err, token) {
                if ( err ) {
                  return reply(err);
                }

                if ( token.expires_at <= Date.now() ) {
                  return reply(Boom.unauthorized('Expired token'));
                }

                var tokenScopes = token.scopes;

                if ( tokenScopes.indexOf('user') === -1 && tokenScopes.indexOf('email') === -1 ) {
                  return reply(Boom.unauthorized('The token does not have the required scopes'));
                }

                reply(token);
              });
            }
          },
          {
            assign: 'user',
            method: function(request, reply) {
              server.methods.account.getUser(request.pre.token.user_id, function(err, json) {
                if ( err ) {
                  return reply(Boom.badImplementation(err));
                }
                reply(json.user);
              });
            }
          }
        ]
      },
      handler: function(request, reply) {
        var responseObj = Scopes.filterUserForScopes(
          request.pre.user,
          request.pre.token.scopes
        );

        reply(responseObj);
      }
    },
    {
      method: 'POST',
      path: '/request-migration-email',
      config: {
        auth: false
      },
      handler: function(request, reply) {
        server.methods.account.requestMigrateEmail(request, function(err, json) {
          if ( err ) {
            return reply(Boom.badImplementation(err));
          }
          reply({ status: 'migration email sent' });
        });
      }
    },
    {
      method: 'POST',
      path: '/migrate-user',
      config: {
        auth: false,
        pre: [
          {
            assign: 'uid',
            method: function(request, reply) {
              reply(request.payload.uid);
            }
          },
          {
            assign: 'password',
            method: function(request, reply) {
              var password = request.payload.password;
              if ( !password ) {
                return reply(Boom.badRequest('No password provided'));
              }

              var result = passTest.test(password);

              if ( !result.strong ) {
                return reply(Boom.badRequest('Password not strong enough'), result);
              }

              reply(password);
            }
          },
          {
            assign: 'isValidToken',
            method: function(request, reply) {
              server.methods.account.verifyToken(request, function(err, json) {
                if ( err ) {
                  return reply(err);
                }

                reply(true);
              });
            }
          },
          {
            assign: 'user',
            method: function(request, reply) {
              server.methods.account.setPassword(
                request,
                request.pre.uid,
                request.pre.password,
                function(err, json) {
                  if ( err ) {
                    return reply(err);
                  }

                  reply(json.user);
                }
              );
            }
          }
        ]
      },
      handler: function(request, reply) {
        request.auth.session.set(request.pre.user);
        reply({ status: 'Logged in' });
      }
    },
    {
      method: 'POST',
      path: '/check-username',
      config: {
        auth: false
      },
      handler: function(request, reply) {
        server.methods.account.checkUsername(request, function(err, json) {
          if ( err ) {
            return reply(err);
          }

          reply(json);
        });
      }
    }
  ]);

  return server;
};
Example #11
0
};


internals.schema = {};


internals.schema.viewOverride = Joi.object({
    path: [Joi.array().items(Joi.string()), Joi.string()],
    relativeTo: Joi.string(),
    compileOptions: Joi.object(),
    runtimeOptions: Joi.object(),
    layout: Joi.string().allow(false, true),
    layoutKeyword: Joi.string(),
    layoutPath: [Joi.array().items(Joi.string()), Joi.string()],
    encoding: Joi.string(),
    allowAbsolutePaths: Joi.boolean(),
    allowInsecureAccess: Joi.boolean(),
    contentType: Joi.string()
});


internals.schema.viewBase = internals.schema.viewOverride.keys({
    partialsPath: [Joi.array().items(Joi.string()), Joi.string()],
    helpersPath: [Joi.array().items(Joi.string()), Joi.string()],
    isCached: Joi.boolean(),
    compileMode: Joi.string().valid('sync', 'async'),
    defaultExtension: Joi.string()
});


internals.schema.manager = internals.schema.viewBase.keys({
Example #12
0
var Joi = require('joi');

var generateUsername = function (context) {
  return context.firstname.toLowerCase() + '-' + context.lastname.toLowerCase();
};

generateUsername.description = 'generated username';
module.exports = {
  body: {
    username: Joi.string().default(generateUsername),
    firstname: Joi.string(),
    lastname: Joi.string(),
    created: Joi.date().default(Date.now, 'time of creation'),
    status: Joi.string().default('registered'),   
    registered: Joi.boolean().default(true),
    type: Joi.string().when('registered', { is: true, then: Joi.default('registered'), otherwise: Joi.default('unregistered') }),
    values: Joi.array().default(['1'])
  }
};
Example #13
0
exports.register = (server, options, next) => {
    const scm = server.root.app.pipelineFactory.scm;
    const pluginOptions = joi.attempt(options, joi.object().keys({
        username: joi.string().required(),
        ignoreCommitsBy: joi.array().items(joi.string()).optional(),
        restrictPR: joi.string().valid('all', 'none', 'branch', 'fork').optional(),
        chainPR: joi.boolean().optional(),
        maxBytes: joi.number().integer().optional()
    }), 'Invalid config for plugin-webhooks');

    server.route({
        method: 'POST',
        path: '/webhooks',
        config: {
            description: 'Handle webhook events',
            notes: 'Acts on pull request, pushes, comments, etc.',
            tags: ['api', 'webhook'],
            payload: {
                maxBytes: parseInt(pluginOptions.maxBytes, 10) || DEFAULT_MAX_BYTES
            },
            handler: async (request, reply) => {
                const userFactory = request.server.app.userFactory;
                const ignoreUser = pluginOptions.ignoreCommitsBy;
                let message = 'Unable to process this kind of event';
                let skipMessage;

                try {
                    const parsed = await scm.parseHook(request.headers, request.payload);

                    if (!parsed) { // for all non-matching events or actions
                        return reply({ message }).code(204);
                    }

                    const { type, hookId, username, scmContext, ref, checkoutUrl, action } = parsed;

                    request.log(['webhook', hookId], `Received event type ${type}`);

                    // skipping checks
                    if (/\[(skip ci|ci skip)\]/.test(parsed.lastCommitMessage)) {
                        skipMessage = 'Skipping due to the commit message: [skip ci]';
                    }

                    // if skip ci then don't return
                    if (ignoreUser && ignoreUser.includes(username) && !skipMessage) {
                        message = `Skipping because user ${username} is ignored`;
                        request.log(['webhook', hookId], message);

                        return reply({ message }).code(204);
                    }

                    const token = await obtainScmToken(
                        pluginOptions, userFactory, username, scmContext);

                    if (!parsed.sha) {
                        try {
                            parsed.sha = await getCommitRefSha({
                                scm,
                                token,
                                ref,
                                checkoutUrl,
                                scmContext
                            });
                        } catch (err) {
                            request.log(['webhook', hookId, 'getCommitRefSha'], err);

                            // there is a possibility of scm.getCommitRefSha() is not implemented yet
                            return reply({ message }).code(204);
                        }
                    }

                    if (action !== 'release' && action !== 'tag') {
                        await promiseToWait(WAIT_FOR_CHANGEDFILES);

                        parsed.changedFiles = await scm.getChangedFiles({
                            payload: request.payload,
                            type,
                            token,
                            scmContext
                        });
                        request.log(['webhook', hookId],
                            `Changed files are ${parsed.changedFiles}`);
                    }

                    if (type === 'pr') {
                        // disregard skip ci for pull request events
                        return pullRequestEvent(pluginOptions, request, reply, parsed, token);
                    }

                    return pushEvent(pluginOptions, request, reply, parsed, skipMessage, token);
                } catch (err) {
                    return reply(boom.boomify(err));
                }
            }
        }
    });

    next();
};
Example #14
0
  getMoodParams: {
    user: common.primaryKeyId,
    team: common.primaryKeyId,
    city: common.primaryKeyId,
  },

  radioParams: {
    city: common.primaryKeyId,
    id: common.primaryKeyId,
  },

  eventsParams: {
    city: common.primaryKeyId,
    id: common.primaryKeyId,
    showPast: Joi.boolean().default(false),
  },

  citiesParams: {
    city: common.primaryKeyId,
  },

  teamsParams: {
    city: common.primaryKeyId,
  },
};

const conversions = {};

function assert(obj, schemaName) {
  var joiObj = _getSchema(schemaName);
Example #15
0
}

module.exports = {
  auth: {
    strategy: auth.AUTH_STRATEGY,
    scope: auth.SCOPE_CLIENT_MANAGEMENT.getImplicantValues()
  },
  response: {
    schema: {
      clients: Joi.array().items(
        Joi.object().keys({
          id: validators.clientId,
          name: Joi.string().required(),
          image_uri: Joi.string().allow(''),
          redirect_uri: Joi.string().allow('').required(),
          can_grant: Joi.boolean().required(),
          trusted: Joi.boolean().required()
        })
      )
    }
  },
  handler: async function listEndpoint(req) {
    const developerEmail = req.auth.credentials.email;

    return db.getClients(developerEmail).then(function(clients) {
      return {
        clients: clients.map(serialize)
      };
    });
  }
};
Example #16
0
var Joi = require('joi');

var strOrArray = Joi.alternatives().try(Joi.string(), Joi.array().includes(Joi.string()));
var sandboxString = Joi.string().valid('allow-forms', 'allow-same-origin', 'allow-scripts', 'allow-top-navigation');

module.exports = Joi.object().keys({
    defaultSrc: strOrArray,
    scriptSrc: strOrArray,
    styleSrc: strOrArray,
    imgSrc: strOrArray,
    connectSrc: strOrArray,
    fontSrc: strOrArray,
    objectSrc: strOrArray,
    mediaSrc: strOrArray,
    frameSrc: strOrArray,
    sandbox: [
        sandboxString,
        Joi.array().includes(sandboxString),
        Joi.boolean()
    ],
    reportUri: Joi.string(),
    reportOnly: Joi.boolean(),
    oldSafari: Joi.boolean()
}).with('reportOnly', 'reportUri');
Example #17
0
    // TODO 2.0 - remove notFound
    response = { context: context, values: valuesToReturn, isNotFound: isNotFound, notFound: isNotFound };

    this._callback(null, response);
    return true;
};

var schema = Joi.object().keys({
    // namespace
    bucket: Joi.string().required(),
    // bucket type is required since default probably shouldn't have a
    // datatype associated with it
    bucketType: Joi.string().required(),
    key: Joi.binary().required(),
    returnRaw: Joi.boolean().default(false).optional(),

    // quorum
    r: Joi.number().default(null).optional(),
    pr: Joi.number().default(null).optional(),
    notFoundOk: Joi.boolean().default(null).optional(),
    useBasicQuorum: Joi.boolean().default(null).optional(),

    setsAsBuffers: Joi.boolean().default(false).optional(),
    timeout: Joi.number().default(null).optional()

});

/**
 * A builder for constructing FetchSet instances.
 *
Example #18
0
// schema for paths
paths.schema = Joi.object({
    tags: Joi.array().items(Joi.string()),
    summary: Joi.string(),
    description: Joi.string(),
    externalDocs: Joi.object({
        description: Joi.string(),
        url: Joi.string().uri()
    }),
    operationId: Joi.string(),
    consumes: Joi.array().items(Joi.string()),
    produces: Joi.array().items(Joi.string()),
    parameters: Joi.array().items(Joi.object()),
    responses: Joi.object().required(),
    schemes: Joi.array().items(Joi.string().valid(['http', 'https', 'ws', 'wss'])),
    deprecated: Joi.boolean(),
    security: Joi.array().items(Joi.object())
});


/**
 * build the swagger path section
 *
 * @param  {Object} setting
 * @param  {Object} routes
 * @return {Object}
 */
paths.build = function (settings, routes) {

    Responses.build({}, {}, {}, {});
Example #19
0
 cache: joi.object().keys({
   privacy: joi.string(),
   expiresIn: joi.any(),
   expiresAt: joi.any(),
   statuses: joi.array()
 }),
 config: joi.object().keys({
   description: joi.string(),
   notes: joi.string(),
   tags: joi.string(),
   handler: joi.alternatives().try(
     joi.func(),
     joi.string()
   ),
   cors: joi.alternatives().try(
     joi.boolean(),
     //Hapi definition
     joi.object().keys({
       origin: joi.array(),
       maxAge: joi.number(),
       headers: joi.array(),
       additionalHeaders: joi.array(),
       exposedHeaders: joi.array(),
       additionalExposedHeaders: joi.array(),
       credentials: joi.boolean()
     }),
     //Express definition
     joi.object().keys({
       origin: joi.any(),
       methods: [joi.string(), joi.array()],
       allowedHeaders: [joi.string(), joi.array()],
Example #20
0
  * @apiSuccess {timestamp} data.created_at Timestamp of when the report note was created
  * @apiSuccess {timestamp} data.updated_at Timestamp of when the report note was last updated
  *
  * @apiError (Error 500) InternalServerError There was an error retrieving the user report notes
  */
module.exports = {
  method: 'GET',
  path: '/api/reports/usernotes/{report_id}',
  config: {
    auth: { strategy: 'jwt' },
    validate: {
      params: { report_id: Joi.string().required() },
      query: {
        page: Joi.number().integer().min(1).default(1),
        limit: Joi.number().integer().min(1).max(100).default(10),
        desc: Joi.boolean().default(true)
      }
    },
    pre: [ { method: 'auth.reports.users.notes.page(server, auth)' } ]
  },
  handler: function(request, reply) {
    var reportId = request.params.report_id;
    var opts = {
      limit: request.query.limit,
      page: request.query.page,
      desc: request.query.desc
    };

    var reportNotes = request.db.reports.pageUserReportsNotes(reportId, opts);
    var reportNotesCount = request.db.reports.userReportsNotesCount(reportId);
Example #21
0
    }
});

Weiner._collection = 'weiners';

Weiner.schema = Joi.object().keys({
    _id: Joi.object(),
    weinerFrom: Joi.object().keys({
        userId: Joi.number().required(),
        username: Joi.string().required()
    }).required(),
    weinerTo: Joi.array().items(Joi.object().keys({
      userId: Joi.number().required(),
      username: Joi.string().required(),
      avatar: Joi.string().required(),
      userChecked: Joi.boolean().required()
    })).required(),
    content: Joi.string(),
    created: Joi.date(),
    status: Joi.number()
});

Weiner.indexes = [
    { key: { 'weinerFrom.userId': 1, unique: true } },
    { key: { 'weinerTo.userId': 1, unique: true } }
];

// Create and save weiner to database.
Weiner.create = function(weiner, callback) {

    const self = this;
Example #22
0
var joi = require('joi');

var schema = {
  name: joi.string().min(3),
  description: joi.string(),
  dockerRepository: joi.string(),
  publicFacing: joi.boolean(),
  ports: joi.array().includes(
    joi.object().keys({
      container: joi.number().integer().required(),
      host: joi.number().integer()
    })),
  env: joi.object(),
  volume: joi.object(),
  numInstances: joi.number().integer().min(1)
};

module.exports = {
  write: {
    name: schema.name.required(),
    description: schema.description,
    dockerRepository: schema.dockerRepository.required(),
    publicFacing: schema.publicFacing,
    ports: schema.ports,
    env: schema.env,
    volume: schema.volume,
    numInstances: schema.numInstances
  },
  patch: {
    name: schema.name,
    description: schema.description,
Example #23
0
const Path = require('path');
const Boom = require('boom');
const Hoek = require('hoek');
const Items = require('items');
const Joi = require('joi');
const File = require('./file');


// Declare internals

const internals = {};


internals.schema = Joi.object({
    path: Joi.alternatives(Joi.array().items(Joi.string()).single(), Joi.func()).required(),
    index: Joi.alternatives(Joi.boolean(), Joi.array().items(Joi.string()).single()).default(true),
    listing: Joi.boolean(),
    showHidden: Joi.boolean(),
    redirectToSlash: Joi.boolean(),
    lookupCompressed: Joi.boolean(),
    etagMethod: Joi.string().valid('hash', 'simple').allow(false),
    defaultExtension: Joi.string().alphanum()
});


exports.handler = function (route, options) {

    const settings = Joi.attempt(options, internals.schema, 'Invalid directory handler options (' + route.path + ')');
    Hoek.assert(route.path[route.path.length - 1] === '}', 'The route path for a directory handler must end with a parameter:', route.path);

    const normalize = (paths) => {
Example #24
0
var utils = require('../../../../utils.js');
var is = require('joi');

module.exports = is.object({
  name: 'Airlines',
  type: 'object',
  file: is.string(),
  root: is.string(),
  is_dependency: is.boolean(),
  dependants: is.array(),
  key: '_id',
  seed: 0,
  data: {
    min: 0,
    max: 0,
    count: 1,
    dependencies: is.array().items(is.string()).length(1),
    inputs: is.array().items(is.string()).length(1),
    pre_run: is.func(),
    post_build: is.func(),
  },
  properties: {
    _id: utils.check('string', 'The document id', { post_build: is.func(), }),
    doc_type: utils.check('string', 'The document type', { value: is.string(), }),
    airline_id: utils.check('integer', 'The airlines id', { pre_build: is.func(), }),
    airline_name: utils.check('string', 'The name of the airline', { build: is.func(), }),
    airline_iata: utils.check('string', 'The airlines iata code if availabe, otherwise null', { build: is.func(), }),
    airline_icao: utils.check('string', 'The airlines icao code if available, otherwise null', { build: is.func(), }),
    callsign: utils.check('string', 'The airlines callsign if available', { build: is.func(), }),
    iso_country: utils.check('string', 'The ISO country code the airline is based in', { build: is.func(), }),
    active: utils.check('boolean', 'Whether or not the airline is active', { pre_build: is.func(), post_build: is.func(), }),
Example #25
0
import chalk from 'chalk'
import moduleSchema from './properties/module'
import entrySchema from './properties/entry'
import contextSchema from './properties/context'
import devtoolSchema from './properties/devtool'
import externalsSchema from './properties/externals'
import nodeSchema from './properties/node'
import pluginsSchema from './properties/plugins'
import resolveSchema from './properties/resolve'
import outputSchema from './properties/output'
import watchOptionsSchema from './properties/watchOptions'
import { absolutePath } from './types'

const schema = Joi.object({
  amd: Joi.object(),
  bail: Joi.boolean(),
  cache: Joi.boolean(),
  context: contextSchema,
  debug: Joi.boolean(),
  devServer: Joi.object(),
  devtool: devtoolSchema,
  entry: entrySchema,
  externals: externalsSchema,
  loader: Joi.any(), // ?
  module: moduleSchema,
  node: nodeSchema,
  output: outputSchema,
  plugins: pluginsSchema,
  profile: Joi.boolean(),
  recordsInputPath: absolutePath,
  recordsOutputPath: absolutePath,
  method: 'GET',
  path: '/centroid/v1/{table}',
  config: {
    description: 'feature centroid',
    notes: 'Get the centroid of a feature(s).',
    tags: ['api'],
    validate: {
      params: {
        table: Joi.string()
          .required()
          .description('name of the table')
      },
      query: {
        geom_column: Joi.string().default('geom')
          .description('The geometry column of the table. The default is <em>geom</em>.'),
        srid: Joi.number().integer().default(4326)
          .description('The SRID for the returned centroid. The default is <em>4326</em> Lat/Lng.'),
        filter: Joi.string()
          .description('Filtering parameters for a SQL WHERE statement.'),
        force_on_surface: Joi.boolean().default(false)
          .description('Set true to force point on surface. The default is the <em>false</em>.')
      }
    },
    jsonp: 'callback',
    cache: config.cache,
    handler: function(request, reply) {
      config.fetch.postgis(config.db.postgis, formatSQL(request), reply);
    }
  }
}];
Example #27
0
'use strict';

var Joi = require('joi');

var ConfigSchema = Joi.object().keys({
    agi: Joi.object().keys({
        port: Joi.number().integer().min(1).max(65535).required()
    }).required(),
    web: Joi.object().keys({
        port: Joi.number().integer().min(1).max(65535).required(),
        auth: Joi.boolean().valid(true, false).default(false),
        username: Joi.string()
            .when('auth', {is: true, then: Joi.required().default('admin')}),
        password: Joi.string()
            .when('auth', {is: true, then: Joi.required().default('password')}),
        realm: Joi.string()
            .when('auth', {is: true, then: Joi.default('voicer web')}),
    }).required(),    
    processing: Joi.object().keys({
        totalAttempts: Joi.number().min(1).max(20).default(2),
        playGreeting: Joi.boolean().default(true),
        playBeepBeforeRecording: Joi.boolean().default(true)
    }).required(),
    asterisk: Joi.object().keys({
        sounds: Joi.object().keys({
            onErrorBeforeFinish: Joi.string(),
            onErrorBeforeRepeat: Joi.string(),
            greeting: Joi.string()
        }).required(),
        recognitionDialplanVars: Joi.object().keys({
            status: Joi.string().required(),
'use strict'

const path = require('path')
const joi = require('joi')
const { parse } = require('pg-connection-string')

const envVarsSchema = joi.object({
  PG_URI: joi.string().uri({ scheme: 'postgres' }).required(),
  PG_SSL_CA: joi.string(),
  PG_SSL_KEY: joi.string(),
  PG_SSL_CERT: joi.string(),
  PG_SSL_ALLOW_UNAUTHORIZED: joi.boolean().truthy('true').falsy('false').default(true),
  PG_POOL_MIN: joi.number().integer().default(1),
  PG_POOL_MAX: joi.number().integer().default(20),
  PG_HEALTH_CHECK_TIMEOUT: joi.number().integer().default(2000)
}).unknown()
  .required()

const envVars = joi.attempt(process.env, envVarsSchema)

const config = {
  client: 'pg',
  connection: Object.assign(
    parse(envVars.PG_URI),
    envVars.PG_SSL_CA || envVars.PG_SSL_KEY || envVars.PG_SSL_CERT
      ? {
        ssl: {
          ca: envVars.PG_SSL_CA,
          key: envVars.PG_SSL_KEY,
          cert: envVars.PG_SSL_CERT,
          rejectUnauthorized: !envVars.PG_SSL_ALLOW_UNAUTHORIZED
Example #29
0
  if (profile) {
    return checksum(JSON.stringify(profile));
  }
  return false;
}

module.exports = {
  auth: {
    strategy: 'oauth'
  },
  response: {
    schema: {
      email: Joi.string().allow(null),
      uid: Joi.string().allow(null),
      avatar: Joi.string().allow(null),
      avatarDefault: Joi.boolean().allow(null),
      displayName: Joi.string().allow(null),
      locale: Joi.string().allow(null),
      amrValues: Joi.array().items(Joi.string().required()).allow(null),
      twoFactorAuthentication: Joi.boolean().allow(null),
      subscriptions: Joi.array().items(Joi.string().required()).optional(),

      //openid-connect
      sub: Joi.string().allow(null)
    }
  },
  handler: function profile(req, reply) {
    const server = req.server;
    const creds = req.auth.credentials;

    function createResponse (err, result, cached, report) {
Example #30
0
internals.url = Joi.alternatives(Joi.string().required(), Joi.object().keys({
    protocol: Joi.string(),
    hostname: Joi.string(),
    port: Joi.any(),
    pathname: Joi.string().required(),
    query: Joi.any()
}).required());


internals.options = Joi.object().keys({
    url: internals.url.required(),
    headers: Joi.object(),
    payload: Joi.any(),
    simulate: Joi.object().keys({
        end: Joi.boolean(),
        split: Joi.boolean(),
        error: Joi.boolean(),
        close: Joi.boolean()
    }),
    authority: Joi.string(),
    remoteAddress: Joi.string(),
    method: Joi.string().regex(/^[a-zA-Z0-9!#\$%&'\*\+\-\.^_`\|~]+$/)
}).min(1);


module.exports = Joi.object().keys({
    dispatchFunc: Joi.func().required(),
    options: Joi.alternatives(internals.options, internals.url).required(),
    callback: Joi.func()
});