v.addValidator = function(validator, key) { if (v[key] !== undefined) { throw new Error('[Validator] key: ' + key + '" has existed.'); } v[key] = util.constant(validator); }; v.addValidators({ templateProp: v.object({ message: v.string().required() .description('describe the meaning of key'), required: v.boolean().default(true), default: v.any().when('required', { is: false, then: v.required(), otherwise: v.optional(), }).description('the default value for error instance when `definition.required` is `OPTIONAL`'), }), }); exports.check = function(value, schema) { var rst = v.validate(value, schema, { allowUnknown: true, }); if (rst.error) { var message = 'wrong parameters of function\nInput Value: ' + rst.error.annotate(); throw new Error(message); } return rst.value; };
/* global __dirname */ 'use strict'; const Hapi = require('hapi'); const Joi = require('joi'); const server = new Hapi.Server(); server.connection({ host: 'localhost', port: Number(process.argv[2] || 8080) }); server.route({ path: '/chickens/{breed}', method: 'GET', handler: function(request, response) { response('You asked for the chickens ' + request.params.breed); }, config: { validate: { params: { breed: Joi.required() } } } }); server.start(function() { console.log('Server running at: ', server.info.uri); });
exports.register = function(server, options, next) { var Users = server.plugins.db.users function validateFunc(token, callback) { jwtVerifyAsync(token, APP_SECRET).then(function(data) { return Users.findById(data.userId).then(function(user) { if (user === null) return callback(null, false) callback(undefined, true, { user: user, scope: data.scope || [] }) }).catch(function(e) { callback(e) }) }).catch(function() { callback(null, false) }) } server.route({ path: "/oauth/access_token", method: "POST", config: { tags: ["oauth"], validate: { payload: { grant_type: Joi.string().valid( "password", "refresh_token").required(), username: 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()}), refresh_token: Joi.string().when("grant_type", {is: "refresh_token", then: Joi.required(), otherwise: Joi.forbidden()}), scope: Joi.string() } }, response: { schema: { access_token: Joi.string().required(), token_type: Joi.string().valid("bearer").required(), expires_in: Joi.number().integer().required(), refresh_token: Joi.string().required(), scope: Joi.array().required() } }, handler: function(req, rep) { function generateTokens(id, scope) { var access_token = jwt.sign( {userId: id, scope: scope, type: "access_token"}, APP_SECRET, {expiresInSeconds: EXP_TIME} ) var refresh_token = jwt.sign( {userId: id, scope: scope, type: "refresh_token"}, APP_SECRET, {expiresInSeconds: EXP_TIME_REFRESH} ) rep({ access_token: access_token, token_type: "bearer", expires_in: EXP_TIME, refresh_token: refresh_token, scope: scope }) } function doError(error) { var err = new Boom.badRequest() err.output.payload.error = error rep(err) } var scope = (function(s) { if (!_.isString(s)) return [] return s.split(",") })(req.payload.scope) if (req.payload.grant_type == "password") { Users.findOne({where: {email: req.payload.username}}).then(function(user) { if (user === null) return doError("invalid_grant") return user.checkPassword(req.payload.password).then(function(ok) { if (ok) return generateTokens(user.id, scope) doError("invalid_grant") }) }).catch(rep) } else if (req.payload.grant_type == "refresh_token"){ jwtVerifyAsync(req.payload.refresh_token, APP_SECRET).then(function(data) { if (_.every(scope, function(s) {return _.includes(data.scope, s)})) { return generateTokens(data.userId, scope) } doError("invalid_scope") }).catch(jwt.TokenExpiredError, function() { doError("invalid_grant") }) } else { doError("unsupported_grant_type") } } } }) server.register(require("hapi-auth-bearer-token"), function(err) { if (err) return next(err) server.auth.strategy("oauth", "bearer-access-token", {validateFunc: validateFunc}) next() }) }
'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(),
joi$: function (schema, actmeta) { return schema.keys({b: Joi.required()}) }
/** * Created by mattiden on 06.06.15. */ var db = require('../../config/db'); var Joi = require('joi'); var bcrypt = require('bcrypt'); var signUpSchema = Joi.object().keys({ email: Joi.string().email().required(), password: Joi.required(), firstname: Joi.string().required(), lastname: Joi.string().required() }); module.exports = function(request, response){ if(request.body.email && request.body.password){ db.select() .from("Trainer") .where({email: request.body.email}) .then(function(arrayWithExistingUser){ if(arrayWithExistingUser.length){ response.send({message: "User already exists."}); throw 0 } else { var password = bcrypt.hashSync(request.body.password, 10); return db.insert({ firstname: request.body.firstname, lastname: request.body.lastname, password: password, email: request.body.email
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; };
module.exports = (log, db, config, customs, push, pushbox, devices, oauthdb) => { // Loads and compiles a json validator for the payloads received // in /account/devices/notify const validatePushSchema = JSON.parse(fs.readFileSync(PUSH_PAYLOADS_SCHEMA_PATH)); const validatePushPayloadAjv = ajv.compile(validatePushSchema); const { supportedLanguages, defaultLanguage } = config.i18n; const localizeTimestamp = require('fxa-shared').l10n.localizeTimestamp({ supportedLanguages, defaultLanguage }); const earliestSaneTimestamp = config.lastAccessTimeUpdates.earliestSaneTimestamp; function validatePushPayload(payload, endpoint) { if (endpoint === 'accountVerify') { if (isEmpty(payload)) { return true; } return false; } return validatePushPayloadAjv(payload); } function isEmpty(payload) { return payload && Object.keys(payload).length === 0; } function marshallLastAccessTime (lastAccessTime, request) { const languages = request.app.acceptLanguage; const result = { lastAccessTime, lastAccessTimeFormatted: localizeTimestamp.format(lastAccessTime, languages), }; if (lastAccessTime < earliestSaneTimestamp) { // Values older than earliestSaneTimestamp are probably wrong. // Signal that to the front end so that it can fall back to // an approximate string like "last sync over 2 months ago". // And do it using additional properties so we don't affect // older content servers that are unfamiliar with the change. result.approximateLastAccessTime = earliestSaneTimestamp; result.approximateLastAccessTimeFormatted = localizeTimestamp.format(earliestSaneTimestamp, languages); } return result; } function marshallLocation (location, request) { let language; if (! location) { // Shortcut the error logging if location isn't set return {}; } try { const languages = i18n.parseAcceptLanguage(request.app.acceptLanguage); language = i18n.bestLanguage(languages, supportedLanguages, defaultLanguage); if (language[0] === 'e' && language[1] === 'n') { // For English, return all of the location components return { city: location.city, country: location.country, state: location.state, stateCode: location.stateCode }; } // For other languages, only return what we can translate const territories = require(`cldr-localenames-full/main/${language}/territories.json`); return { country: territories.main[language].localeDisplayNames.territories[location.countryCode] }; } catch (err) { log.warn('devices.marshallLocation.warning', { err: err.message, languages: request.app.acceptLanguage, language, location }); } // If something failed, don't return location return {}; } // Creates a "full" device response, provided a credentials object and an optional // updated DB device record. function buildDeviceResponse(credentials, device = null) { // We must respond with the full device record, // including any default values for missing fields. return { // These properties can be picked from sessionToken or device as appropriate. pushCallback: credentials.deviceCallbackURL, pushPublicKey: credentials.deviceCallbackPublicKey, pushAuthKey: credentials.deviceCallbackAuthKey, pushEndpointExpired: !! credentials.deviceCallbackIsExpired, ...device, // But these need to be non-falsey, using default fallbacks if necessary id: (device && device.id) || credentials.deviceId, name: (device && device.name) || credentials.deviceName || devices.synthesizeName(credentials), type: (device && device.type) || credentials.deviceType || 'desktop', availableCommands: (device && device.availableCommands) || credentials.deviceAvailableCommands || {}, }; } return [ { method: 'POST', path: '/account/device', options: { auth: { strategies: [ 'sessionToken', 'refreshToken' ] }, validate: { payload: isA.object({ id: DEVICES_SCHEMA.id.optional(), name: DEVICES_SCHEMA.name.optional(), type: DEVICES_SCHEMA.type.optional(), pushCallback: DEVICES_SCHEMA.pushCallback.optional(), pushPublicKey: DEVICES_SCHEMA.pushPublicKey.optional(), pushAuthKey: DEVICES_SCHEMA.pushAuthKey.optional(), availableCommands: DEVICES_SCHEMA.availableCommands.optional(), // Some versions of desktop firefox send a zero-length // "capabilities" array, for historical reasons. // We accept but ignore it. capabilities: isA.array().length(0).optional() }) .and('pushCallback', 'pushPublicKey', 'pushAuthKey') }, response: { schema: isA.object({ id: DEVICES_SCHEMA.id.required(), createdAt: isA.number().positive().optional(), name: DEVICES_SCHEMA.nameResponse.optional(), type: DEVICES_SCHEMA.type.optional(), pushCallback: DEVICES_SCHEMA.pushCallback.optional(), pushPublicKey: DEVICES_SCHEMA.pushPublicKey.optional(), pushAuthKey: DEVICES_SCHEMA.pushAuthKey.optional(), pushEndpointExpired: DEVICES_SCHEMA.pushEndpointExpired.optional(), availableCommands: DEVICES_SCHEMA.availableCommands.optional(), }).and('pushCallback', 'pushPublicKey', 'pushAuthKey') } }, handler: async function (request) { log.begin('Account.device', request); const payload = request.payload; const credentials = request.auth.credentials; // Remove obsolete field, so we don't try to echo it back to the client. delete payload.capabilities; // Some additional, slightly tricky validation to detect bad public keys. if (payload.pushPublicKey && ! push.isValidPublicKey(payload.pushPublicKey)) { throw error.invalidRequestParameter('invalid pushPublicKey'); } if (payload.id) { // Don't write out the update if nothing has actually changed. if (devices.isSpuriousUpdate(payload, credentials)) { return buildDeviceResponse(credentials); } // We also reserve the right to disable updates until // we're confident clients are behaving correctly. if (config.deviceUpdatesEnabled === false) { throw error.featureNotEnabled(); } } else if (credentials.deviceId) { // Keep the old id, which is probably from a synthesized device record payload.id = credentials.deviceId; } const pushEndpointOk = ! payload.id || // New device. (payload.id && payload.pushCallback && payload.pushCallback !== credentials.deviceCallbackURL); // Updating the pushCallback if (pushEndpointOk) { payload.pushEndpointExpired = false; } // We're doing a gradual rollout of the 'device commands' feature // in support of pushbox, so accept an 'availableCommands' field // if pushbox is enabled. if (payload.availableCommands && ! config.pushbox.enabled) { payload.availableCommands = {}; } const device = await devices.upsert(request, credentials, payload); return buildDeviceResponse(credentials, device); } }, { method: 'GET', path: '/account/device/commands', options: { validate: { query: { index: isA.number().optional(), limit: isA.number().optional().min(0).max(100).default(100), } }, auth: { strategies: [ 'sessionToken', 'refreshToken' ] }, response: { schema: isA.object({ index: isA.number().required(), last: isA.boolean().optional(), messages: isA.array().items(isA.object({ index: isA.number().required(), data: isA.object({ command: isA.string().max(255).required(), payload: isA.object().required(), sender: DEVICES_SCHEMA.id.optional() }).required() })).optional() }).and('last', 'messages') } }, handler: async function (request) { log.begin('Account.deviceCommands', request); const sessionToken = request.auth.credentials; const uid = sessionToken.uid; const deviceId = sessionToken.deviceId; const query = request.query || {}; const {index, limit} = query; return pushbox.retrieve(uid, deviceId, limit, index) .then(resp => { log.info('commands.fetch', { resp: resp }); return resp; }); } }, { method: 'POST', path: '/account/devices/invoke_command', options: { auth: { strategies: [ 'sessionToken', 'refreshToken' ] }, validate: { payload: { target: DEVICES_SCHEMA.id.required(), command: isA.string().required(), payload: isA.object().required(), ttl: isA.number().integer().min(0).max(10000000).optional() } }, response: { schema: {} } }, handler: async function (request) { log.begin('Account.invokeDeviceCommand', request); const {target, command, payload} = request.payload; let {ttl} = request.payload; const sessionToken = request.auth.credentials; const uid = sessionToken.uid; const sender = sessionToken.deviceId; return customs.checkAuthenticated(request, uid, 'invokeDeviceCommand') .then(() => db.device(uid, target)) .then(device => { if (! device.availableCommands.hasOwnProperty(command)) { throw error.unavailableDeviceCommand(); } // 0 is perfectly acceptable TTL, hence the strict equality check. if (ttl === undefined && DEFAULT_COMMAND_TTL.has(command)) { ttl = DEFAULT_COMMAND_TTL.get(command); } const data = { command, payload, sender, }; return pushbox.store(uid, device.id, data, ttl) .then(({index}) => { const url = new URL('v1/account/device/commands', config.publicUrl); url.searchParams.set('index', index); url.searchParams.set('limit', 1); return push.notifyCommandReceived(uid, device, command, sender, index, url.href, ttl); }); }) .then(() => { return {}; }); } }, { method: 'POST', path: '/account/devices/notify', options: { auth: { strategies: [ 'sessionToken', 'refreshToken' ] }, validate: { payload: isA.alternatives().try( isA.object({ to: isA.string().valid('all').required(), _endpointAction: isA.string().valid('accountVerify').optional(), excluded: isA.array().items(isA.string().length(32).regex(HEX_STRING)).optional(), payload: isA.object().when('_endpointAction', { is: 'accountVerify', then: isA.required(), otherwise: isA.required() }), TTL: isA.number().integer().min(0).optional() }), isA.object({ to: isA.array().items(isA.string().length(32).regex(HEX_STRING)).required(), _endpointAction: isA.string().valid('accountVerify').optional(), payload: isA.object().when('_endpointAction', { is: 'accountVerify', then: isA.required(), otherwise: isA.required() }), TTL: isA.number().integer().min(0).optional() }) ) }, response: { schema: {} } }, handler: async function (request) { log.begin('Account.devicesNotify', request); // We reserve the right to disable notifications until // we're confident clients are behaving correctly. if (config.deviceNotificationsEnabled === false) { throw error.featureNotEnabled(); } const body = request.payload; const sessionToken = request.auth.credentials; const uid = sessionToken.uid; const payload = body.payload; const endpointAction = body._endpointAction || 'devicesNotify'; if (! validatePushPayload(payload, endpointAction)) { throw error.invalidRequestParameter('invalid payload'); } const pushOptions = { data: payload }; if (body.TTL) { pushOptions.TTL = body.TTL; } return customs.checkAuthenticated(request, uid, endpointAction) .then(() => request.app.devices) .then(devices => { if (body.to !== 'all') { const include = new Set(body.to); devices = devices.filter(device => include.has(device.id)); if (devices.length === 0) { log.error('Account.devicesNotify', { uid: uid, error: 'devices empty' }); return; } } else if (body.excluded) { const exclude = new Set(body.excluded); devices = devices.filter(device => ! exclude.has(device.id)); } return push.sendPush(uid, devices, endpointAction, pushOptions) .catch(catchPushError); }) .then(() => { // Emit a metrics event for when a user sends tabs between devices. // In the future we will aim to get this event directly from sync telemetry, // but we're doing it here for now as a quick way to get metrics on the feature. if ( payload && payload.command === 'sync:collection_changed' && // Note that payload schema validation ensures that these properties exist. payload.data.collections.length === 1 && payload.data.collections[0] === 'clients' ) { let deviceId = undefined; if (sessionToken.deviceId) { deviceId = sessionToken.deviceId; } return request.emitMetricsEvent('sync.sentTabToDevice', { device_id: deviceId, service: 'sync', uid: uid }); } }) .then(() => { return {}; }); function catchPushError (err) { // push may fail due to not found devices or a bad push action // log the error but still respond with a 200. log.error('Account.devicesNotify', { uid: uid, error: err }); } } }, { method: 'GET', path: '/account/devices', options: { auth: { strategies: [ 'sessionToken', 'refreshToken' ] }, response: { schema: isA.array().items(isA.object({ id: DEVICES_SCHEMA.id.required(), isCurrentDevice: isA.boolean().required(), lastAccessTime: isA.number().min(0).required().allow(null), lastAccessTimeFormatted: isA.string().optional().allow(''), approximateLastAccessTime: isA.number().min(earliestSaneTimestamp).optional(), approximateLastAccessTimeFormatted: isA.string().optional().allow(''), location: DEVICES_SCHEMA.location, name: DEVICES_SCHEMA.nameResponse.allow('').required(), type: DEVICES_SCHEMA.type.required(), pushCallback: DEVICES_SCHEMA.pushCallback.allow(null).optional(), pushPublicKey: DEVICES_SCHEMA.pushPublicKey.allow(null).optional(), pushAuthKey: DEVICES_SCHEMA.pushAuthKey.allow(null).optional(), pushEndpointExpired: DEVICES_SCHEMA.pushEndpointExpired.optional(), availableCommands: DEVICES_SCHEMA.availableCommands.optional(), }).and('pushPublicKey', 'pushAuthKey')) } }, handler: async function (request) { log.begin('Account.devices', request); const credentials = request.auth.credentials; return request.app.devices .then(deviceArray => { return deviceArray.map(device => { return Object.assign({ id: device.id, isCurrentDevice: !! ((credentials.id && credentials.id === device.sessionToken) || (credentials.refreshTokenId && credentials.refreshTokenId === device.refreshTokenId)), location: marshallLocation(device.location, request), name: device.name || devices.synthesizeName(device), type: device.type || device.uaDeviceType || 'desktop', pushCallback: device.pushCallback, pushPublicKey: device.pushPublicKey, pushAuthKey: device.pushAuthKey, pushEndpointExpired: device.pushEndpointExpired, availableCommands: device.availableCommands }, marshallLastAccessTime(device.lastAccessTime, request)); }); } ); } }, { method: 'GET', path: '/account/sessions', options: { auth: { strategies: [ 'sessionToken', // this endpoint is only used by the content server // no refreshToken access here ] }, response: { schema: isA.array().items(isA.object({ id: isA.string().regex(HEX_STRING).required(), lastAccessTime: isA.number().min(0).required().allow(null), lastAccessTimeFormatted: isA.string().optional().allow(''), approximateLastAccessTime: isA.number().min(earliestSaneTimestamp).optional(), approximateLastAccessTimeFormatted: isA.string().optional().allow(''), createdTime: isA.number().min(0).required().allow(null), createdTimeFormatted: isA.string().optional().allow(''), location: DEVICES_SCHEMA.location, userAgent: isA.string().max(255).required().allow(''), os: isA.string().max(255).allow('').allow(null), deviceId: DEVICES_SCHEMA.id.allow(null).required(), deviceName: DEVICES_SCHEMA.nameResponse.allow('').allow(null).required(), deviceAvailableCommands: DEVICES_SCHEMA.availableCommands.allow(null).required(), deviceType: DEVICES_SCHEMA.type.allow(null).required(), deviceCallbackURL: DEVICES_SCHEMA.pushCallback.allow(null).required(), deviceCallbackPublicKey: DEVICES_SCHEMA.pushPublicKey.allow(null).required(), deviceCallbackAuthKey: DEVICES_SCHEMA.pushAuthKey.allow(null).required(), deviceCallbackIsExpired: DEVICES_SCHEMA.pushEndpointExpired.allow(null).required(), isDevice: isA.boolean().required(), isCurrentDevice: isA.boolean().required() })) } }, handler: async function (request) { log.begin('Account.sessions', request); const sessionToken = request.auth.credentials; const uid = sessionToken.uid; return db.sessions(uid) .then(sessions => { return sessions.map(session => { const deviceId = session.deviceId; const isDevice = !! deviceId; let deviceName = session.deviceName; if (! deviceName) { deviceName = devices.synthesizeName(session); } let userAgent; if (! session.uaBrowser) { userAgent = ''; } else if (! session.uaBrowserVersion) { userAgent = session.uaBrowser; } else { const { uaBrowser: browser, uaBrowserVersion: version } = session; userAgent = `${browser} ${version.split('.')[0]}`; } return Object.assign({ deviceId, deviceName, deviceType: session.uaDeviceType || 'desktop', deviceAvailableCommands: session.deviceAvailableCommands || null, deviceCallbackURL: session.deviceCallbackURL, deviceCallbackPublicKey: session.deviceCallbackPublicKey, deviceCallbackAuthKey: session.deviceCallbackAuthKey, deviceCallbackIsExpired: !! session.deviceCallbackIsExpired, id: session.id, isCurrentDevice: session.id === sessionToken.id, isDevice, location: marshallLocation(session.location, request), createdTime: session.createdAt, createdTimeFormatted: localizeTimestamp.format( session.createdAt, request.headers['accept-language'] ), os: session.uaOS, userAgent }, marshallLastAccessTime(session.lastAccessTime, request)); }); } ); } }, { method: 'POST', path: '/account/device/destroy', options: { auth: { strategies: [ 'sessionToken', 'refreshToken' ] }, validate: { payload: { id: DEVICES_SCHEMA.id.required() } }, response: { schema: {} } }, handler: async function (request) { log.begin('Account.deviceDestroy', request); const credentials = request.auth.credentials; const uid = credentials.uid; const id = request.payload.id; let devices; // We want to include the disconnected device in the list // of devices to notify, so list them before disconnecting. return request.app.devices .then(res => { devices = res; return db.deleteDevice(uid, id); }) .then(() => { const deviceToDelete = devices.find(d => d.id === id); if (deviceToDelete && deviceToDelete.refreshTokenId) { // attempt to clean up the refreshToken in the OAuth DB return oauthdb.revokeRefreshTokenById(deviceToDelete.refreshTokenId).catch((err) => { log.error('deviceDestroy.revokeRefreshTokenById.error', {err: err.message}); }); } }) .then(() => { push.notifyDeviceDisconnected(uid, devices, id) .catch(() => {}); return P.all([ request.emitMetricsEvent('device.deleted', { uid: uid, device_id: id }), log.notifyAttachedServices('device:delete', request, { uid: uid, id: id, timestamp: Date.now() }) ]); }) .then(() => { return {}; }); } } ]; };
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const Joi = require("joi"); const posts = require("./post"); const comments = require("./comment"); const reactions = require("./reaction"); const user = require("./user"); const friends = require("./friend"); const commons = require("../utils/commons"); const postUrlSchema = Joi.object({ user: commons.user.required().description('The post\'s author, identified as `username@instance-domain.tld`'), timestamp: Joi.number().integer().min(1).required().description('The post\'s creation timestamp') }).label('PostParams'); const tokens = Joi.object({ idToken: Joi.string().description('Identification token bound to a friend'), signature: Joi.string().when('idToken', { is: Joi.string().required(), then: Joi.required(), otherwise: Joi.optional() }).description('Request signature. Must be provided if an idToken is provided') }).label('Tokens'); module.exports = { v1: { '/client/me': { get: { description: 'Retrieve data on the current user', notes: 'Retrieve data on the current user. Full documentation is available [here](https://github.com/JosephCaillet/vinimay/wiki/Client-to-server-API#retrieval).', handler: user.get, plugins: { 'hapi-swagger': { responses: { '200': { description: 'Data on the current user', schema: user.schema }, '401': {
/*global require, module, applicationContext */ 'use strict'; var _ = require('underscore'); var joi = require('joi'); var actions = require('./actions'); module.exports = { mount: applicationContext.mount, name: 'segment', maxFailures: applicationContext.configuration.maxFailures, schema: joi.object().keys({ 0: _.reduce(actions, function (schema, actionSchema, actionName) { return schema.valid(actionName); }, joi.required()), 1: _.reduce(actions, function (schema, actionSchema, actionName) { return schema.when('0', {is: actionName, then: actionSchema}); }, joi.alternatives().required()) }).unknown(true).required().meta({isTuple: true}) };
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(), csp: Joi.object({ rules: Joi.array().items(Joi.string()).default(DEFAULT_CSP_RULES), strict: Joi.boolean().default(false), warnLegacyBrowsers: Joi.boolean().default(true), }).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`), rewriteBasePath: Joi.boolean().when('basePath', { is: '', then: Joi.default(false).valid(false), otherwise: Joi.default(false), }), 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()).default([]), supportedProtocols: Joi.array().items(Joi.string().valid('TLSv1', 'TLSv1.1', 'TLSv1.2')).default(['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), whitelist: Joi.array().items( Joi.string().regex(/^\//, 'start with a slash') ).default([]), token: Joi.string().optional().notes('Deprecated') }).default(), }).default(), uiSettings: Joi.object().keys({ overrides: Joi.object().unknown(true).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) }), timezone: Joi.string() }).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(), migrations: Joi.object({ batchSize: Joi.number().default(100), scrollDuration: Joi.string().default('15m'), pollInterval: Joi.number().default(1500), }).default(), stats: Joi.object({ maximumWaitTimeForAllCollectorsInS: Joi.number().default(60) }).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')), sourceMaps: Joi.when('$prod', { is: true, then: Joi.boolean().valid(false), otherwise: Joi .alternatives() .try( Joi.string().required(), Joi.boolean() ) .default('#cheap-source-map'), }), workers: Joi.number().min(1), profile: Joi.boolean().default(false) }).default(), status: Joi.object({ allowAnonymous: Joi.boolean().default(false) }).default(), map: Joi.object({ includeElasticMapsService: Joi.boolean().default(true), proxyElasticMapsServiceInMaps: Joi.boolean().default(false), 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: Joi.boolean() }).default({ default: true }) }).default(), regionmap: Joi.object({ includeElasticMapsService: Joi.boolean().default(true), layers: Joi.array().items(Joi.object({ url: Joi.string(), format: Joi.object({ type: Joi.string().default('geojson') }).default({ type: 'geojson' }), meta: Joi.object({ feature_collection_path: Joi.string().default('data') }).default({ feature_collection_path: 'data' }), attribution: Joi.string(), name: Joi.string(), fields: Joi.array().items(Joi.object({ name: Joi.string(), description: Joi.string() })) })).default([]) }).default(), manifestServiceUrl: Joi.string().default('https://catalogue-staging.maps.elastic.co/v7.2/manifest'), emsLandingPageUrl: Joi.string().default('https://maps.elastic.co/v7.0'), emsTileLayerId: Joi.object({ bright: Joi.string().default('road_map'), desaturated: Joi.string().default('road_map_desaturated'), dark: Joi.string().default('dark_map'), }).default({ bright: 'road_map', desaturated: 'road_map_desaturated', dark: 'dark_map', }), }).default(), i18n: Joi.object({ locale: Joi.string().default('en'), }).default(), savedObjects: Joi.object({ maxImportPayloadBytes: Joi.number().default(10485760), maxImportExportSize: Joi.number().default(10000), }).default(), }).default();
var _ = require('lodash'); var Joi = require('joi'); var common = { team: Joi.number().integer().min(0), userUuid: Joi.string().regex(/^[A-Za-z0-9_-]+$/).min(1, 'utf8').max(128, 'utf8'), primaryKeyId: Joi.number().integer().min(0) }; const schemas = { common: common, action: { user: common.userUuid.required(), type: Joi.string().uppercase().required(), imageData: Joi.string().when('type', { is: 'IMAGE', then: Joi.required() }), text: Joi.string().when('type', { is: 'TEXT', then: Joi.required() }), location: Joi.object({ latitude: Joi.number(), longitude: Joi.number() }) }, user: { uuid: common.userUuid.required(), name: Joi.string().min(1, 'utf8').max(50, 'utf8').required(), team: common.team.required() }, feedParams: { beforeId: Joi.number().integer().min(0).optional(),
const HABITAT_PACKAGE = Joi .string() .description('Package of the Habitat command') .example('core/git/2.14.1'); const HABITAT_COMMAND = Joi .string() .description('Executable of the Habitat command') .example('git'); const SCHEMA_HABITAT = Joi.object() .keys({ mode: HABITAT_MODE, file: HABITAT_FILE .when('mode', { is: 'local', then: Joi.required() }), package: HABITAT_PACKAGE, command: HABITAT_COMMAND }) .requiredKeys('mode', 'package', 'command'); const DOCKER_IMAGE = Joi .string() .description('Image of the Docker command') .example('chefdk:1.2.3'); const DOCKER_COMMAND = Joi .string() .description('Executable of the Docker command') .default('') .example('knife');
'use strict'; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var Call = require('call'); var Hoek = require('hoek'); var Joi = require('joi'); var Handler = Joi.alternatives([Joi.func(), Joi.object({ statusCode: Joi.number().positive().integer().min(200).default(200), headers: Joi.object(), payload: Joi.required() }), Joi.object({ isBoom: true }).unknown()]); var _route = Joi.alternatives([Joi.object({ method: Joi.string().regex(/^[a-zA-Z0-9!#\$%&'\*\+\-\.^_`\|~]+$/).lowercase().default('get'), path: Joi.string().required(), handler: Handler, vhost: Joi.array().items(Joi.string().hostname()).min(1).single().default(['*']), filter: Joi.func().optional() }), Joi.object({ method: Joi.string().regex(/^[a-zA-Z0-9!#\$%&'\*\+\-\.^_`\|~]+$/).lowercase().default('get'), path: Joi.string().required(), config: Joi.object({ handler: Handler }).unknown(), vhost: Joi.array().items(Joi.string().hostname()).min(1).single().default(['*']),
http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ var Joi = require('joi'); var blueprintValidator = module.exports = {}; blueprintValidator.launch = { params: { blueprintId: Joi.required() }, body: { version: Joi.required(), envId: Joi.required() } }; blueprintValidator.create = { body: { "name": Joi.string().required(), "organizationId": Joi.string().required(), "businessGroupId": Joi.string().required(), "projectId": Joi.string().required(), "networkProfileId": Joi.string().required(), "vmImageId": Joi.string().required(),
exports.register = function (server, options, next) { server.auth.scheme('cookie', internals.implementation); next(); }; exports.register.attributes = { pkg: require('../package.json') }; internals.schema = Joi.object({ cookie: Joi.string().default('sid'), password: Joi.alternatives(Joi.string(), Joi.object().type(Buffer)).required(), ttl: Joi.number().integer().min(0).when('keepAlive', { is: true, then: Joi.required() }), domain: Joi.string().allow(null), path: Joi.string().default('/'), clearInvalid: Joi.boolean().default(false), keepAlive: Joi.boolean().default(false), isSecure: Joi.boolean().default(true), isHttpOnly: Joi.boolean().default(true), redirectTo: Joi.string().allow(false), appendNext: Joi.alternatives(Joi.string(), Joi.boolean()).default(false), redirectOnTry: Joi.boolean().default(true), validateFunc: Joi.func(), requestDecoratorName: Joi.string().default('cookieAuth') }).required(); internals.implementation = function (server, options) {
var Joi = require('joi'); import CONST from '../constants'; var common = { team: Joi.number().integer().min(0), userUuid: Joi.string().regex(/^[A-Za-z0-9_-]+$/).min(1, 'utf8').max(128, 'utf8'), primaryKeyId: Joi.number().integer().min(0) }; const schemas = { common: common, action: { user: common.userUuid.required(), type: Joi.string().uppercase().required(), imageData: Joi.string().when('type', { is: 'IMAGE', then: Joi.required() }), imageText: Joi.string().max(50, 'utf8').optional(), imageTextPosition: Joi.number().min(0).max(1).optional(), text: Joi.string().when('type', { is: 'TEXT', then: Joi.required() }), eventId: common.primaryKeyId.when('type', { is: 'CHECK_IN_EVENT', then: Joi.required()}), city: common.primaryKeyId, location: Joi.object({ latitude: Joi.number(), longitude: Joi.number() }).when('type', {is: 'CHECK_IN_EVENT', then: Joi.required()}), }, user: { uuid: common.userUuid.required(), name: Joi.string().min(1, 'utf8').max(50, 'utf8').required(), team: common.team.required()
server.auth.scheme('bell', internals.implementation); server.expose('oauth', OAuth); next(); }; exports.register.attributes = { pkg: require('../package.json') }; internals.schema = Joi.object({ provider: Joi.object({ name: Joi.string().optional().default('custom'), protocol: Joi.string().valid('oauth', 'oauth2'), temporary: Joi.string().when('protocol', { is: 'oauth', then: Joi.required(), otherwise: Joi.forbidden() }), signatureMethod: Joi.string().valid('HMAC-SHA1', 'RSA-SHA1').when('protocol', { is: 'oauth', then: Joi.default('HMAC-SHA1'), otherwise: Joi.forbidden() }), auth: Joi.string().required(), useParamsAuth: Joi.boolean().default(false).when('protocol', { is: 'oauth2', then: Joi.optional(), otherwise: Joi.forbidden() }), token: Joi.string().required(), headers: Joi.object(), profile: Joi.func(), scope: Joi.alternatives().try( Joi.array().items(Joi.string()), Joi.func() ).when('protocol', { is: 'oauth2', otherwise: Joi.forbidden() }), scopeSeparator: Joi.string().when('protocol', { is: 'oauth2', otherwise: Joi.forbidden() }), version: Joi.string() }).required(), password: Joi.string().required(), clientId: Joi.string().required(),
if (err.statusCode === 404) { return reply.view('errors/not-found', err).code(404); } else if (err.statusCode < 500) { return handleUserError(request, reply, '/org/' + orgName, err.message); } else { return reply.view('errors/internal', err); } }); }; var validPayloadSchema = { updateType: Joi.string().required(), name: Joi.string().when('updateType', { is: 'updateWritePermissions', then: Joi.required() }).when('updateType', { is: 'removePackage', then: Joi.required() }).when('updateType', { is: 'removeUser', then: Joi.required() }), names: Joi.any().when('updateType', { is: 'addPackagesToTeam', then: Joi.required() }), writePermission: Joi.string().when('updateType', { is: 'updateWritePermissions', then: Joi.allow('on') }),
it('ignores validation on * route when request is GET', async () => { const server = Hapi.server(); server.route({ method: '*', path: '/', handler: () => null, options: { validate: { payload: { a: Joi.required() } } } }); const res = await server.inject('/'); expect(res.statusCode).to.equal(200); });
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();
file:///usr/local/lib/node_modules/makemehapi/node_modules/joi/README.md */ var server = new (require('hapi').Server)() var vision = require('vision') var joi = require('joi') server.connection({ host: 'localhost' , port: Number(process.argv[2] || 8080) }) server.route({ path: '/chickens/{breed}' , method:'GET' , handler: function(request, reply) { // no-op } , config: { validate: { params: { breed: joi.required() } } } }) server.start(function() { console.log('Server running at:', server.info.uri) })
it('ignores validation on * route when request is GET', function (done) { var server = new Hapi.Server(); server.connection(); server.route({ method: '*', path: '/', handler: function (request, reply) { return reply(); }, config: { validate: { payload: { a: Joi.required() } } } }); server.inject('/', function (res) { expect(res.statusCode).to.equal(200); done(); }); });
'use strict'; var Joi = require('joi'), nodeUUID = require('node-uuid'), _ = require('lodash'); var internals = {}; internals.secondaryIndexSchema = Joi.object().keys({ hashKey : Joi.string().when('type', { is: 'local', then: Joi.ref('$hashKey'), otherwise : Joi.required()}), rangeKey: Joi.string().when('type', { is: 'local', then: Joi.required(), otherwise: Joi.optional() }), type : Joi.string().valid('local', 'global').required(), name : Joi.string().required(), projection : Joi.object(), readCapacity : Joi.number().when('type', { is: 'global', then: Joi.optional(), otherwise : Joi.forbidden()}), writeCapacity : Joi.number().when('type', { is: 'global', then: Joi.optional(), otherwise : Joi.forbidden()}) }); internals.configSchema = Joi.object().keys({ hashKey : Joi.string().required(), rangeKey : Joi.string(), tableName : Joi.alternatives().try(Joi.string(), Joi.func()), indexes : Joi.array().includes(internals.secondaryIndexSchema), schema : Joi.object(), timestamps : Joi.boolean().default(false) }).required(); internals.wireType = function (key) { switch (key) { case 'string': return 'S';
}, { method: 'GET', path: '/json', handler: function (request, reply) { reply({ hello: 'world' }); } }, { method: 'POST', path: '/login', config: { handler: function (request, reply) { reply('login successful'); }, validate: { payload: Joi.object({ isGuest: Joi.boolean().required(), username: Joi.string().when('isGuest', { is: false, then: Joi.required() }), password: Joi.string().alphanum(), accessToken: Joi.string().alphanum() }).options({ allowUnknown: true }).without('password', 'accessToken') } } } ]);
}); internals.plugin = internals.register.keys({ register: Joi.func().keys({ attributes: Joi.object({ pkg: Joi.object({ name: Joi.string(), version: Joi.string().default('0.0.0') }) .unknown() .default({ version: '0.0.0' }), name: Joi.string() .when('pkg.name', { is: Joi.exist(), otherwise: Joi.required() }), version: Joi.string(), multiple: Joi.boolean().default(false), dependencies: Joi.array().items(Joi.string()).single(), connections: Joi.boolean().allow('conditional').default(true), once: Joi.boolean().valid(true) }) .required() .unknown() }) .required(), options: Joi.any() }) .without('once', 'options') .unknown();
(async () => { try { const server = Hapi.Server({ host: 'localhost', port: Number(process.argv[2] || 8080) }); server.route({ method: 'POST', path: '/login', config: { handler: (request, h) => { return 'login successful'; }, validate: { payload: Joi.object({ isGuest: Joi.boolean().required(), username: Joi.string().when('isGuest', { is: false, then: Joi.required() }), password: Joi.string().alphanum(), accessToken: Joi.string().alphanum() }).options({ allowUnknown: true }).without('password', 'accessToken') } } }); await server.start(); } catch (error) { console.log(error); } })();
import Joi from 'joi'; import jwt from 'jsonwebtoken'; import { decorators } from 'octobus.js'; const { withSchema } = decorators; const schema = Joi.object().keys({ id: Joi.required(), username: Joi.string().required(), options: Joi.object().default({}), }).unknown(true).required(); export default ({ key, options: defaultOptions }) => withSchema(schema)( async ({ params }) => { const { options, ...user } = params; return jwt.sign(user, key, { ...defaultOptions, ...options }); }, );