Merge pull request #13046 from mozilla/joi_fix

chore(deps): Upgrade hapi/joi dependency - fix import
This commit is contained in:
Lisa Chan 2022-05-25 17:45:34 -04:00 коммит произвёл GitHub
Родитель 025a91a983 d970f293d3
Коммит 843f098973
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
92 изменённых файлов: 542 добавлений и 764 удалений

Просмотреть файл

@ -71,7 +71,7 @@
'use strict';
const Joi = require('@hapi/joi');
const Joi = require('joi');
const Pool = require('./pool');
const error = require('./error');
@ -135,24 +135,23 @@ module.exports = function createBackendServiceAPI(
// to the client.
function validate(location, value, schema, options) {
const { value: result, error: err } = schema.validate(value, options);
return new Promise((resolve, reject) => {
Joi.validate(value, schema, options, (err, value) => {
if (!err) {
return resolve(value);
}
log.error(fullMethodName, {
error: `${location} schema validation failed`,
message: err.message,
value,
});
reject(
error.internalValidationError(
fullMethodName,
{ location, value },
err
)
);
if (!err) {
return resolve(result);
}
log.error(fullMethodName, {
error: `${location} schema validation failed`,
message: err.message,
value,
});
reject(
error.internalValidationError(
fullMethodName,
{ location, value },
err
)
);
});
}

Просмотреть файл

@ -4,7 +4,7 @@
'use strict';
const Joi = require('@hapi/joi');
const Joi = require('joi');
const createBackendServiceAPI = require('./backendService');
const config = require('../config');
const localizeTimestamp = require('fxa-shared').l10n.localizeTimestamp({

Просмотреть файл

@ -4,7 +4,7 @@
'use strict';
const isA = require('@hapi/joi');
const isA = require('joi');
const DESCRIPTION = require('../docs/swagger/shared/descriptions').default;
const validators = require('./routes/validators');
const error = require('./error');

Просмотреть файл

@ -5,7 +5,6 @@
'use strict';
const inherits = require('util').inherits;
const messages = require('@hapi/joi/lib/language').errors;
const OauthError = require('./oauth/error');
const verror = require('verror');
@ -270,7 +269,7 @@ AppError.translate = function (request, response) {
} else if (payload.validation) {
if (
payload.message &&
payload.message.indexOf(messages.any.required) >= 0
payload.message.includes('is required')
) {
error = AppError.missingRequestParameter(payload.validation.keys[0]);
} else {

Просмотреть файл

@ -5,7 +5,7 @@
'use strict';
const crypto = require('crypto');
const isA = require('@hapi/joi');
const isA = require('joi');
const SCHEMA = isA.array().items(isA.string()).optional();

Просмотреть файл

@ -7,7 +7,7 @@
const bufferEqualConstantTime = require('buffer-equal-constant-time');
const crypto = require('crypto');
const HEX_STRING = require('../routes/validators').HEX_STRING;
const isA = require('@hapi/joi');
const isA = require('joi');
const FLOW_ID_LENGTH = 64;

Просмотреть файл

@ -21,7 +21,7 @@
*
*/
const Joi = require('@hapi/joi');
const Joi = require('joi');
const validators = require('./validators');
const OauthError = require('./error');
@ -122,7 +122,7 @@ module.exports = async function verifyAssertion(assertion) {
}
}
try {
return await CLAIMS_SCHEMA.validate(claims);
return await CLAIMS_SCHEMA.validateAsync(claims);
} catch (err) {
return error(assertion, err, claims);
}

Просмотреть файл

@ -4,7 +4,7 @@
const crypto = require('crypto');
const buf = require('buf').hex;
const Joi = require('@hapi/joi');
const Joi = require('joi');
const OauthError = require('./error');
const validators = require('./validators');

Просмотреть файл

@ -6,28 +6,28 @@ const assert = require('assert');
const config = require('../../config');
const { jwk2pem, pem2jwk } = require('pem-jwk');
const crypto = require('crypto');
const Joi = require('@hapi/joi');
const Joi = require('joi');
const BASE64URL = /^[A-Za-z0-9-_]+$/;
const PUBLIC_KEY_SCHEMA = (exports.PUBLIC_KEY_SCHEMA = Joi.object({
kty: Joi.string().only('RSA').required(),
kty: Joi.string().valid('RSA').required(),
kid: Joi.string().required(),
n: Joi.string().regex(BASE64URL).required(),
e: Joi.string().regex(BASE64URL).required(),
alg: Joi.string().only('RS256').optional(),
use: Joi.string().only('sig').optional(),
alg: Joi.string().valid('RS256').optional(),
use: Joi.string().valid('sig').optional(),
'fxa-createdAt': Joi.number().integer().min(0).optional(),
}));
const PRIVATE_KEY_SCHEMA = (exports.PRIVATE_KEY_SCHEMA = Joi.object({
kty: Joi.string().only('RSA').required(),
kty: Joi.string().valid('RSA').required(),
kid: Joi.string().required(),
n: Joi.string().regex(BASE64URL).required(),
e: Joi.string().regex(BASE64URL).required(),
d: Joi.string().regex(BASE64URL).required(),
alg: Joi.string().only('RS256').optional(),
use: Joi.string().only('sig').optional(),
alg: Joi.string().valid('RS256').optional(),
use: Joi.string().valid('sig').optional(),
p: Joi.string().regex(BASE64URL).required(),
q: Joi.string().regex(BASE64URL).required(),
dp: Joi.string().regex(BASE64URL).required(),
@ -46,7 +46,7 @@ const currentPrivJWK = config.get('oauthServer.openid.key');
if (currentPrivJWK) {
assert.strictEqual(
PRIVATE_KEY_SCHEMA.validate(currentPrivJWK).error,
null,
undefined,
'openid.key must be a valid private key'
);
PRIVATE_JWKS_MAP.set(currentPrivJWK.kid, currentPrivJWK);
@ -62,7 +62,7 @@ const newPrivJWK = config.get('oauthServer.openid.newKey');
if (newPrivJWK) {
assert.strictEqual(
PRIVATE_KEY_SCHEMA.validate(newPrivJWK).error,
null,
undefined,
'openid.newKey must be a valid private key'
);
assert.notEqual(
@ -79,7 +79,7 @@ const oldPubJWK = config.get('oauthServer.openid.oldKey');
if (oldPubJWK) {
assert.strictEqual(
PUBLIC_KEY_SCHEMA.validate(oldPubJWK).error,
null,
undefined,
'openid.oldKey must be a valid public key'
);
assert.notEqual(

Просмотреть файл

@ -3,7 +3,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const Joi = require('@hapi/joi');
const Joi = require('joi');
const ScopeSet = require('fxa-shared').oauth.scopes;
const authServerValidators = require('../routes/validators');
@ -37,20 +37,22 @@ exports.sessionToken = authServerValidators.sessionToken;
const scopeString = Joi.string().max(256);
exports.scope = Joi.extend({
name: 'scope',
type: 'scope',
base: Joi.any(), // We're not returning a string, so don't base this on Joi.string().
language: {
messages: {
base: 'needs to be a valid scope string',
},
pre(value, state, options) {
prepare(value, state, options) {
const err = scopeString.validate(value).err;
if (err) {
return err;
}
try {
return ScopeSet.fromString(value || '');
return { value: ScopeSet.fromString(value || '') };
} catch (err) {
return this.createError('scope.base', { v: value }, state, options);
return {
errors: this.$_createError('scope.base', { v: value }, state, options),
};
}
},
}).scope();
@ -77,7 +79,7 @@ exports.jwt = Joi.string()
// JWT format: 'header.payload.signature'
.regex(/^([a-zA-Z0-9\-_]+)\.([a-zA-Z0-9\-_]+)\.([a-zA-Z0-9\-_]+)$/);
exports.accessToken = Joi.alternatives().try([exports.token, exports.jwt]);
exports.accessToken = Joi.alternatives().try(exports.token, exports.jwt);
exports.ppidSeed = authServerValidators.ppidSeed.default(0);

Просмотреть файл

@ -4,7 +4,7 @@
'use strict';
const isA = require('@hapi/joi');
const isA = require('joi');
const createBackendServiceAPI = require('../backendService');
const PATH_PREFIX = '/v1';

Просмотреть файл

@ -16,7 +16,7 @@
'use strict';
const isA = require('@hapi/joi');
const isA = require('joi');
const error = require('./error');
const createBackendServiceAPI = require('./backendService');
const validators = require('./routes/validators');

Просмотреть файл

@ -1,7 +1,7 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import isA from '@hapi/joi';
import isA from 'joi';
import {
deleteAllPayPalBAs,
getAllPayPalBAByUid,
@ -1590,7 +1590,7 @@ export const accountRoutes = (
.optional()
.description(DESCRIPTION.resume),
metricsContext: METRICS_CONTEXT_SCHEMA,
style: isA.string().allow(['trailhead']).optional(),
style: isA.string().allow('trailhead').optional(),
verificationMethod: validators.verificationMethod.optional(),
// preVerified is not available in production mode.
...(!(config as any).isProduction && {

Просмотреть файл

@ -4,7 +4,7 @@
'use strict';
const isA = require('@hapi/joi');
const isA = require('joi');
const validators = require('./validators');
const authorizedClients = require('../oauth/authorized_clients');
const error = require('../error');
@ -137,7 +137,7 @@ module.exports = (log, db, devices, clientUtils) => {
.with('refreshTokenId', ['clientId']),
},
response: {
schema: {},
schema: isA.object({}),
},
},
handler: async function (request) {

Просмотреть файл

@ -5,7 +5,7 @@
'use strict';
const AppError = require('../../error');
const joi = require('@hapi/joi');
const joi = require('joi');
const hex = require('buf').to.hex;
const validators = require('../validators');
const { BEARER_AUTH_REGEX } = validators;

Просмотреть файл

@ -10,7 +10,7 @@ const ajv = new Ajv();
const hex = require('buf').to.hex;
const error = require('../error');
const fs = require('fs');
const isA = require('@hapi/joi');
const isA = require('joi');
const path = require('path');
const validators = require('./validators');
@ -440,7 +440,7 @@ module.exports = (
),
},
response: {
schema: {},
schema: isA.object({}),
},
},
handler: async function (request) {
@ -760,7 +760,7 @@ module.exports = (
}),
},
response: {
schema: {},
schema: isA.object({}),
},
},
handler: async function (request) {

Просмотреть файл

@ -7,7 +7,7 @@
const butil = require('../crypto/butil');
const emailUtils = require('./utils/email');
const error = require('../error');
const isA = require('@hapi/joi');
const isA = require('joi');
const random = require('../crypto/random');
const Sentry = require('@sentry/node');
const validators = require('./validators');
@ -213,7 +213,7 @@ module.exports = (
.string()
.max(32)
.alphanum()
.allow(['upgradeSession'])
.allow('upgradeSession')
.optional(),
}),
payload: isA.object({
@ -227,12 +227,12 @@ module.exports = (
.max(2048)
.optional()
.description(DESCRIPTION.resume),
style: isA.string().allow(['trailhead']).optional(),
style: isA.string().allow('trailhead').optional(),
type: isA
.string()
.max(32)
.alphanum()
.allow(['upgradeSession'])
.allow('upgradeSession')
.optional(),
}),
},
@ -385,7 +385,7 @@ module.exports = (
.alphanum()
.optional()
.description(DESCRIPTION.type),
style: isA.string().allow(['trailhead']).optional(),
style: isA.string().allow('trailhead').optional(),
// The `marketingOptIn` is safe to remove after train-167+
marketingOptIn: isA.boolean().optional(),
newsletters: validators.newsletters,

Просмотреть файл

@ -3,7 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const hex = require('buf').to.hex;
const Joi = require('@hapi/joi');
const Joi = require('joi');
const OauthError = require('../../oauth/error');
const AuthError = require('../../error');

Просмотреть файл

@ -2,8 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const Joi = require('@hapi/joi');
const Joi = require('joi');
const validators = require('../../../oauth/validators');
const verifyAssertion = require('../../../oauth/assertion');
const authorizedClients = require('../../../oauth/authorized_clients');

Просмотреть файл

@ -3,7 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const hex = require('buf').to.hex;
const Joi = require('@hapi/joi');
const Joi = require('joi');
const AppError = require('../../../oauth/error');
const validators = require('../../../oauth/validators');

Просмотреть файл

@ -3,7 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const crypto = require('crypto');
const Joi = require('@hapi/joi');
const Joi = require('joi');
const hex = require('buf').to.hex;
const OauthError = require('../../oauth/error');

Просмотреть файл

@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const Joi = require('@hapi/joi');
const Joi = require('joi');
const JWTIdToken = require('../../oauth/jwt_id_token');
const MISC_DOCS = require('../../../docs/swagger/misc-api').default;

Просмотреть файл

@ -3,7 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*jshint camelcase: false*/
const Joi = require('@hapi/joi');
const Joi = require('joi');
const validators = require('../../oauth/validators');
const hex = require('buf').to.hex;
const AppError = require('../../oauth/error');
@ -12,7 +12,7 @@ const MISC_DOCS = require('../../../docs/swagger/misc-api').default;
const PAYLOAD_SCHEMA = Joi.object({
token: Joi.string().required(),
token_type_hint: Joi.string().equal(['access_token', 'refresh_token']),
token_type_hint: Joi.string().equal('access_token', 'refresh_token'),
});
// The "token introspection" endpoint, per https://tools.ietf.org/html/rfc7662
@ -32,7 +32,7 @@ module.exports = ({ oauthDB }) => ({
active: Joi.boolean().required(),
scope: validators.scope.optional(),
client_id: validators.clientId.optional(),
token_type: Joi.string().equal(['access_token', 'refresh_token']),
token_type: Joi.string().equal('access_token', 'refresh_token'),
exp: Joi.number().optional(),
iat: Joi.number().optional(),
sub: Joi.string().optional(),

Просмотреть файл

@ -2,8 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const Joi = require('@hapi/joi');
const Joi = require('joi');
const OauthError = require('../../oauth/error');
const AuthError = require('../../error');
const config = require('../../../config').getProperties();

Просмотреть файл

@ -30,7 +30,7 @@ const OauthError = require('../../oauth/error');
const AuthError = require('../../error');
const buf = require('buf').hex;
const hex = require('buf').to.hex;
const Joi = require('@hapi/joi');
const Joi = require('joi');
const {
OAUTH_SCOPE_OLD_SYNC,
@ -110,11 +110,11 @@ const PAYLOAD_SCHEMA = Joi.object({
ttl: Joi.number().positive().default(MAX_TTL_S).optional(),
scope: Joi.alternatives()
.when('grant_type', {
.conditional('grant_type', {
is: GRANT_REFRESH_TOKEN,
then: validators.scope.optional(),
})
.when('grant_type', {
.conditional('grant_type', {
is: GRANT_FXA_ASSERTION,
then: validators.scope.required(),
otherwise: Joi.forbidden(),

Просмотреть файл

@ -2,8 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const Joi = require('@hapi/joi');
const Joi = require('joi');
const token = require('../../oauth/token');
const validators = require('../../oauth/validators');
const MISC_DOCS = require('../../../docs/swagger/misc-api').default;

Просмотреть файл

@ -9,7 +9,7 @@ const HEX_STRING = validators.HEX_STRING;
const butil = require('../crypto/butil');
const error = require('../error');
const isA = require('@hapi/joi');
const isA = require('joi');
const random = require('../crypto/random');
const requestHelper = require('../routes/utils/request_helper');
const { emailsMatch } = require('fxa-shared').email.helpers;

Просмотреть файл

@ -5,7 +5,7 @@
'use strict';
const errors = require('../error');
const isA = require('@hapi/joi');
const isA = require('joi');
const validators = require('./validators');
const RECOVERY_CODES_DOCS =
require('../../docs/swagger/recovery-codes-api').default;

Просмотреть файл

@ -10,7 +10,7 @@ const DESCRIPTION = require('../../docs/swagger/shared/descriptions').default;
const errors = require('../error');
const validators = require('./validators');
const isA = require('@hapi/joi');
const isA = require('joi');
module.exports = (log, db, Password, verifierVersion, customs, mailer) => {
return [

Просмотреть файл

@ -5,7 +5,7 @@
'use strict';
const error = require('../error');
const isA = require('@hapi/joi');
const isA = require('joi');
const requestHelper = require('../routes/utils/request_helper');
const METRICS_CONTEXT_SCHEMA = require('../metrics/context').schema;
const validators = require('./validators');

Просмотреть файл

@ -5,7 +5,7 @@
'use strict';
const error = require('../error');
const isA = require('@hapi/joi');
const isA = require('joi');
const validators = require('./validators');
const SIGN_DOCS = require('../../docs/swagger/sign-api').default;
const DESCRIPTION = require('../../docs/swagger/shared/descriptions').default;

Просмотреть файл

@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import { ServerRoute } from '@hapi/hapi';
import isA from '@hapi/joi';
import isA from 'joi';
import { OAUTH_SCOPE_SUBSCRIPTIONS_IAP } from 'fxa-shared/oauth/constants';
import { Container } from 'typedi';

Просмотреть файл

@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import { ServerRoute } from '@hapi/hapi';
import isA from '@hapi/joi';
import isA from 'joi';
import {
createPayPalBA,
getAccountCustomerByUid,

Просмотреть файл

@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import { ServerRoute } from '@hapi/hapi';
import isA from '@hapi/joi';
import isA from 'joi';
import { Container } from 'typedi';
import SUBSCRIPTIONS_DOCS from '../../../docs/swagger/subscriptions-api';

Просмотреть файл

@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import { ServerRoute } from '@hapi/hapi';
import isA from '@hapi/joi';
import isA from 'joi';
import * as Sentry from '@sentry/node';
import { Account } from 'fxa-shared/db/models/auth';
import { ACTIVE_SUBSCRIPTION_STATUSES } from 'fxa-shared/subscriptions/stripe';

Просмотреть файл

@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import { ServerRoute } from '@hapi/hapi';
import isA from '@hapi/joi';
import isA from 'joi';
import * as Sentry from '@sentry/node';
import { getAccountCustomerByUid } from 'fxa-shared/db/models/auth';
import { AbbrevPlan } from 'fxa-shared/dist/subscriptions/types';

Просмотреть файл

@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import { ServerRoute } from '@hapi/hapi';
import isA from '@hapi/joi';
import isA from 'joi';
import zendesk from 'node-zendesk';
import pRetry from 'p-retry';

Просмотреть файл

@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import { ServerRoute } from '@hapi/hapi';
import isA from '@hapi/joi';
import isA from 'joi';
import { MozillaSubscriptionTypes } from 'fxa-shared/subscriptions/types';
import { Container } from 'typedi';

Просмотреть файл

@ -6,7 +6,7 @@
const errors = require('../error');
const validators = require('./validators');
const isA = require('@hapi/joi');
const isA = require('joi');
const otplib = require('otplib');
const qrcode = require('qrcode');
const { promisify } = require('util');

Просмотреть файл

@ -8,7 +8,7 @@ const UNBLOCK_CODES_DOCS =
require('../../docs/swagger/unblock-codes-api').default;
const DESCRIPTION = require('../../docs/swagger/shared/descriptions').default;
const isA = require('@hapi/joi');
const isA = require('joi');
const METRICS_CONTEXT_SCHEMA = require('../metrics/context').schema;
const validators = require('./validators');

Просмотреть файл

@ -4,7 +4,7 @@
'use strict';
const isA = require('@hapi/joi');
const isA = require('joi');
const random = require('../crypto/random');
const validators = require('./validators');
const UTIL_DOCS = require('../../docs/swagger/util-api').default;

Просмотреть файл

@ -5,7 +5,7 @@
'use strict';
const emailUtils = require('./email');
const isA = require('@hapi/joi');
const isA = require('joi');
const validators = require('../validators');
const butil = require('../../crypto/butil');
const error = require('../../error');

Просмотреть файл

@ -7,7 +7,7 @@
const { URL } = require('url');
const punycode = require('punycode.js');
const isA = require('@hapi/joi');
const isA = require('joi');
const { MozillaSubscriptionTypes } = require('fxa-shared/subscriptions/types');
const {
minimalConfigSchema,
@ -81,25 +81,20 @@ module.exports.BEARER_AUTH_REGEX = BEARER_AUTH_REGEX;
// This is different to Joi's builtin email validator, and
// requires a custom validation function.
// The custom validators below need to either return the value
// or create an error object using `createError`.
// see examples here: https://github.com/hapijs/joi/blob/master/lib/string.js
module.exports.email = function () {
const email = isA.string().max(255).regex(DISPLAY_SAFE_UNICODE);
// Imma add a custom test to this Joi object using internal
// properties because I can't find a nice API to do that.
email._tests.push({
func: function (value, state, options) {
if (value !== undefined && value !== null) {
if (module.exports.isValidEmailAddress(value)) {
return value;
}
}
const email = isA
.string()
.max(255)
.regex(DISPLAY_SAFE_UNICODE)
.custom((value) => {
// Do custom validation
const isValid = module.exports.isValidEmailAddress(value);
return email.createError('string.base', { value }, state, options);
},
});
if (!isValid) {
throw new Error('Not a valid email address');
}
return value;
});
return email;
};
@ -154,7 +149,7 @@ module.exports.jwt = isA
module.exports.accessToken = isA
.alternatives()
.try([module.exports.hexString.length(64), module.exports.jwt]);
.try(module.exports.hexString.length(64), module.exports.jwt);
// Function to validate an email address.
//
@ -192,38 +187,36 @@ module.exports.isValidEmailAddress = function (value) {
};
module.exports.redirectTo = function redirectTo(base) {
const validator = isA.string().max(512);
let hostnameRegex = null;
if (base) {
hostnameRegex = new RegExp(`(?:\\.|^)${base.replace('.', '\\.')}$`);
}
validator._tests.push({
func: (value, state, options) => {
if (value !== undefined && value !== null) {
if (isValidUrl(value, hostnameRegex)) {
return value;
}
const validator = isA
.string()
.max(512)
.custom((value) => {
let hostnameRegex = '';
if (base) {
hostnameRegex = new RegExp(`(?:\\.|^)${base.replace('.', '\\.')}$`);
}
// Do your validation
const isValid = isValidUrl(value, hostnameRegex);
return validator.createError('string.base', { value }, state, options);
},
});
if (!isValid) {
throw new Error('Not a valid URL');
}
return value;
});
return validator;
};
module.exports.url = function url(options) {
const validator = isA.string().uri(options);
validator._tests.push({
func: (value, state, options) => {
if (value !== undefined && value !== null) {
if (isValidUrl(value)) {
return value;
}
const validator = isA
.string()
.uri(options)
.custom((value) => {
const isValid = isValidUrl(value);
if (!isValid) {
throw new Error('Not a valid URL');
}
return validator.createError('string.base', { value }, state, options);
},
});
return value;
});
return validator;
};
@ -232,25 +225,22 @@ module.exports.url = function url(options) {
module.exports.resourceUrl = module.exports.url().regex(/#/, { invert: true });
module.exports.pushCallbackUrl = function pushUrl(options) {
const validator = isA.string().uri(options);
validator._tests.push({
func: (value, state, options) => {
if (value !== undefined && value !== null) {
let normalizedValue = value;
// Fx Desktop registers https push urls with a :443 which causes `isValidUrl`
// to fail because the :443 is expected to have been normalized away.
if (/^https:\/\/[a-zA-Z0-9._-]+(:443)($|\/)/.test(value)) {
normalizedValue = value.replace(':443', '');
}
if (isValidUrl(normalizedValue)) {
return value;
}
const validator = isA
.string()
.uri(options)
.custom((value) => {
let normalizedValue = value;
// Fx Desktop registers https push urls with a :443 which causes `isValidUrl`
// to fail because the :443 is expected to have been normalized away.
if (/^https:\/\/[a-zA-Z0-9._-]+(:443)($|\/)/.test(value)) {
normalizedValue = value.replace(':443', '');
}
return validator.createError('string.base', { value }, state, options);
},
});
const isValid = isValidUrl(normalizedValue);
if (!isValid) {
throw new Error('Not a valid URL');
}
return value;
});
return validator;
};
@ -279,13 +269,13 @@ function isValidUrl(url, hostnameRegex) {
return parsed.href;
}
module.exports.verificationMethod = isA.string().valid([
module.exports.verificationMethod = isA.string().valid(
'email', // Verification by email link
'email-otp', // Verification by email otp code using account long code (`emailCode`) as secret
'email-2fa', // Verification by email code using randomly generated code (used in login flow)
'email-captcha', // Verification by email code using randomly generated code (used in unblock flow)
'totp-2fa', // Verification by TOTP authenticator device code, secret is randomly generated
]);
'totp-2fa' // Verification by TOTP authenticator device code, secret is randomly generated
);
module.exports.authPW = isA.string().length(64).regex(HEX_STRING).required();
module.exports.wrapKb = isA.string().length(64).regex(HEX_STRING);
@ -486,12 +476,20 @@ module.exports.subscriptionProductMetadataValidator = {
error: 'Capability missing from metadata',
};
}
return module.exports.subscriptionProductMetadataBaseValidator.validate(
metadata,
{
abortEarly: false,
}
);
const { value: result, error } =
module.exports.subscriptionProductMetadataBaseValidator.validate(
metadata,
{
abortEarly: false,
}
);
if (error) {
return { error };
}
return { result };
},
async validateAsync(metadata) {
const hasCapability = Object.keys(metadata).some((k) =>
@ -505,13 +503,11 @@ module.exports.subscriptionProductMetadataValidator = {
}
try {
const value = await isA.validate(
metadata,
module.exports.subscriptionProductMetadataBaseValidator,
{
abortEarly: false,
}
);
const validationSchema =
module.exports.subscriptionProductMetadataBaseValidator;
const value = await validationSchema.validateAsync(metadata, {
abortEarly: false,
});
return { value };
} catch (error) {
return { error };
@ -567,7 +563,7 @@ module.exports.subscriptionsStripeIntentValidator = isA
.alternatives(isA.string(), isA.object({}).unknown(true))
.optional()
.allow(null),
source: isA.alternatives().when('payment_method', {
source: isA.alternatives().conditional('payment_method', {
// cannot be that strict here since this validator is used in two routes
is: null,
then: isA.string().optional(),
@ -719,7 +715,7 @@ module.exports.newsletters = isA
module.exports.thirdPartyProvider = isA
.string()
.max(256)
.allow(['google', 'apple'])
.allow('google', 'apple')
.required();
module.exports.thirdPartyIdToken = module.exports.jwt.optional();

Просмотреть файл

@ -6,10 +6,9 @@
const fs = require('fs');
const Hapi = require('@hapi/hapi');
const joi = require('@hapi/joi');
const HapiSwagger = require('hapi-swagger');
const Inert = require('inert');
const Vision = require('vision');
const Inert = require('@hapi/inert');
const Vision = require('@hapi/vision');
const path = require('path');
const url = require('url');
const userAgent = require('./userAgent');
@ -171,7 +170,7 @@ async function create(log, error, config, routes, db, statsd) {
}
const server = new Hapi.Server(serverOptions);
server.validator(require('@hapi/joi'));
server.validator(require('joi'));
server.ext('onRequest', (request, h) => {
log.begin('server.onRequest', request);
@ -187,9 +186,7 @@ async function create(log, error, config, routes, db, statsd) {
return xff
.filter(Boolean)
.map((address) => address.trim())
.filter(
(address) => !joi.validate(address, IP_ADDRESS.required()).error
);
.filter((address) => !IP_ADDRESS.required().validate(address).error);
});
defineLazyGetter(request.app, 'clientAddress', () => {

Просмотреть файл

@ -61,7 +61,8 @@
"@hapi/hapi": "^20.2.1",
"@hapi/hawk": "^8.0.0",
"@hapi/hoek": "^10.0.0",
"@hapi/joi": "^15.1.1",
"@hapi/inert": "^6.0.5",
"@hapi/vision": "^6.1.0",
"@sentry/integrations": "^6.19.1",
"@sentry/node": "^6.19.1",
"@type-cacheable/core": "^10.1.0",
@ -91,12 +92,12 @@
"googleapis": "^100.0.0",
"hapi-auth-jwt2": "^10.2.0",
"hapi-error": "^2.3.0",
"hapi-swagger": "10.3.0",
"hapi-swagger": "^14.4.0",
"hkdf": "0.0.2",
"hot-shots": "^9.0.0",
"i18n-iso-countries": "^7.4.0",
"inert": "^5.1.3",
"ioredis": "^4.28.2",
"joi": "17.4.0",
"jose": "^4.8.1",
"jsdom": "^19.0.0",
"jsonwebtoken": "^8.5.1",
@ -129,7 +130,6 @@
"typedi": "^0.8.0",
"uuid": "^8.3.2",
"verror": "^1.10.1",
"vision": "^5.4.4",
"web-push": "3.4.4"
},
"devDependencies": {
@ -144,8 +144,6 @@
"@types/chai-as-promised": "^7",
"@types/dedent": "^0",
"@types/hapi__hapi": "^20.0.10",
"@types/hapi__joi": "^15.0.4",
"@types/inert": "^5",
"@types/ioredis": "^4.26.4",
"@types/jsdom": "^16",
"@types/jsonwebtoken": "^8.5.1",
@ -163,7 +161,6 @@
"@types/sass": "^1",
"@types/uuid": "^8.3.0",
"@types/verror": "^1.10.4",
"@types/vision": "^5",
"@types/webpack": "5.28.0",
"acorn": "^8.0.1",
"async-retry": "^1.3.3",

Просмотреть файл

@ -5,7 +5,7 @@
'use strict';
const nock = require('nock');
const Joi = require('@hapi/joi');
const Joi = require('joi');
const { assert } = require('chai');
const { mockLog } = require('../mocks');
const sinon = require('sinon');

Просмотреть файл

@ -6,16 +6,10 @@
const { assert } = require('chai');
const verror = require('verror');
const messages = require('@hapi/joi/lib/language');
const AppError = require('../../lib/error');
const OauthError = require('../../lib/oauth/error');
describe('AppErrors', () => {
it('tightly-coupled joi message hack is okay', () => {
assert.equal(typeof messages.errors.any.required, 'string');
assert.notEqual(messages.errors.any.required, '');
});
it('exported functions exist', () => {
assert.equal(typeof AppError, 'function');
assert.equal(AppError.length, 4);
@ -53,7 +47,7 @@ describe('AppErrors', () => {
const result = AppError.translate(null, {
output: {
payload: {
message: `foo${messages.errors.any.required}`,
message: `foo${'is required'}`,
validation: {
keys: ['bar', 'baz'],
},

Просмотреть файл

@ -288,10 +288,7 @@ describe('PaymentConfigManager', () => {
await paymentConfigManager.storePlanConfig(newPlan, randomUUID());
assert.fail('should have thrown');
} catch (err) {
assert.equal(
err.jse_cause.message,
'child "active" fails because ["active" is required]'
);
assert.equal(err.jse_cause.message, '"active" is required');
assert.equal(err.errno, errors.ERRNO.INTERNAL_VALIDATION_ERROR);
}
});

Просмотреть файл

@ -7,10 +7,9 @@
const sinon = require('sinon');
const assert = { ...sinon.assert, ...require('chai').assert };
const crypto = require('crypto');
const Joi = require('@hapi/joi');
const Joi = require('joi');
const error = require('../../../lib/error');
const getRoute = require('../../routes_helpers').getRoute;
const isA = require('@hapi/joi');
const mocks = require('../../mocks');
const moment = require('moment'); // Ensure consistency with production code
const proxyquire = require('proxyquire');
@ -77,7 +76,8 @@ async function runTest(route, request, onSuccess, onError) {
try {
const response = await route.handler(request);
if (route.options.response.schema) {
await Joi.validate(response, route.options.response.schema);
const validationSchema = route.options.response.schema;
await validationSchema.validateAsync(response);
}
if (onSuccess) {
onSuccess(response);
@ -808,10 +808,8 @@ describe('/account/device/commands', () => {
'/account/device/commands'
);
mockRequest.query = isA.validate(
mockRequest.query,
route.options.validate.query
).value;
const validationSchema = route.options.validate.query;
mockRequest.query = validationSchema.validate(mockRequest.query).value;
assert.ok(mockRequest.query);
return runTest(route, mockRequest).then((response) => {
assert.equal(mockPushbox.retrieve.callCount, 1, 'pushbox was called');
@ -911,10 +909,8 @@ describe('/account/device/commands', () => {
'/account/device/commands'
);
mockRequest.query = isA.validate(
mockRequest.query,
route.options.validate.query
).value;
const validationSchema = route.options.validate.query;
mockRequest.query = validationSchema.validate(mockRequest.query).value;
assert.ok(mockRequest.query);
return runTest(route, mockRequest).then((response) => {
assert.callCount(mockLog.info, 2);
@ -1628,12 +1624,12 @@ describe('/account/devices', () => {
pushEndpointExpired: false,
},
];
isA.assert(res, route.options.response.schema);
Joi.assert(res, route.options.response.schema);
});
it('should allow returning approximateLastAccessTime', () => {
const route = getRoute(makeRoutes({}), '/account/devices');
isA.assert(
Joi.assert(
[
{
id: crypto.randomBytes(16).toString('hex'),
@ -1653,7 +1649,7 @@ describe('/account/devices', () => {
it('should not allow returning approximateLastAccessTime < EARLIEST_SANE_TIMESTAMP', () => {
const route = getRoute(makeRoutes({}), '/account/devices');
assert.throws(() =>
isA.assert(
Joi.assert(
[
{
id: crypto.randomBytes(16).toString('hex'),

Просмотреть файл

@ -7,7 +7,6 @@
const ROOT_DIR = '../../..';
const sinon = require('sinon');
const Joi = require('@hapi/joi');
const assert = { ...sinon.assert, ...require('chai').assert };
const getRoute = require('../../routes_helpers').getRoute;
const mocks = require('../../mocks');
@ -37,10 +36,10 @@ describe('/oauth/ routes', () => {
);
const route = await getRoute(routes, path);
if (route.config.validate.payload) {
const validationSchema = route.config.validate.payload;
// eslint-disable-next-line require-atomic-updates
request.payload = await Joi.validate(
request.payload = await validationSchema.validateAsync(
request.payload,
route.config.validate.payload,
{
context: {
headers: request.headers || {},

Просмотреть файл

@ -889,7 +889,9 @@ describe('/v1', function () {
assert.equal(res.statusCode, 200);
assertSecurityHeaders(res);
assert(res.result.access_token);
assert.isNull(validators.jwt.validate(res.result.access_token).error);
assert.isUndefined(
validators.jwt.validate(res.result.access_token).error
);
const jwt = decodeJWT(res.result.access_token);
assert.strictEqual(jwt.claims.sub, USERID);
assert.deepEqual(jwt.claims.aud, [
@ -2133,6 +2135,7 @@ describe('/v1', function () {
payload: {
client_id: clientId,
grant_type: 'fxa-credentials',
scope: 'profile testme',
},
});
assertInvalidRequestParam(res.result, 'assertion');
@ -2186,6 +2189,7 @@ describe('/v1', function () {
grant_type: 'fxa-credentials',
ttl: 42,
assertion: AN_ASSERTION,
scope: 'profile testme',
},
});
assertSecurityHeaders(res);
@ -2202,6 +2206,7 @@ describe('/v1', function () {
grant_type: 'fxa-credentials',
ttl: MAX_TTL_S * 100,
assertion: AN_ASSERTION,
scope: 'profile testme',
},
});
assertSecurityHeaders(res);
@ -2234,6 +2239,7 @@ describe('/v1', function () {
client_id: clientId,
grant_type: 'fxa-credentials',
assertion: AN_ASSERTION,
scope: 'profile testme',
},
});
assertSecurityHeaders(res);
@ -3721,7 +3727,7 @@ describe('/v1', function () {
assert.equal(tokenResult.statusCode, 200);
assertSecurityHeaders(tokenResult);
assert.ok(tokenResult.result.access_token);
assert.isNull(
assert.isUndefined(
validators.jwt.validate(tokenResult.result.access_token).error
);
assert.strictEqual(tokenResult.result.token_type, 'bearer');
@ -3808,7 +3814,7 @@ describe('/v1', function () {
assert.equal(refreshTokenResult.statusCode, 200);
assertSecurityHeaders(refreshTokenResult);
assert.ok(refreshTokenResult.result.access_token);
assert.isNull(
assert.isUndefined(
validators.jwt.validate(refreshTokenResult.result.access_token).error
);
assert.strictEqual(refreshTokenResult.result.token_type, 'bearer');
@ -3878,7 +3884,7 @@ describe('/v1', function () {
);
assert.equal(ppidTokenResult.statusCode, 200);
assert.isNull(
assert.isUndefined(
validators.jwt.validate(ppidTokenResult.result.access_token).error
);
@ -3916,7 +3922,7 @@ describe('/v1', function () {
});
assert.equal(refreshTokenResult.statusCode, 200);
assert.isNull(
assert.isUndefined(
validators.jwt.validate(refreshTokenResult.result.access_token).error
);
@ -3957,7 +3963,7 @@ describe('/v1', function () {
});
assert.equal(refreshTokenResult.statusCode, 200);
assert.isNull(
assert.isUndefined(
validators.jwt.validate(refreshTokenResult.result.access_token).error
);
@ -3988,7 +3994,7 @@ describe('/v1', function () {
}
);
assert.equal(tokenResult.statusCode, 200);
assert.isNull(
assert.isUndefined(
validators.jwt.validate(tokenResult.result.access_token).error
);
const tokenJWT = decodeJWT(tokenResult.result.access_token);
@ -4009,7 +4015,7 @@ describe('/v1', function () {
});
assert.equal(serverRotatedResult.statusCode, 200);
assert.isNull(
assert.isUndefined(
validators.jwt.validate(serverRotatedResult.result.access_token).error
);
@ -4034,7 +4040,7 @@ describe('/v1', function () {
}
);
assert.equal(accessTokenResult.statusCode, 200);
assert.isNull(
assert.isUndefined(
validators.jwt.validate(accessTokenResult.result.access_token).error
);
const accessTokenJWT = decodeJWT(accessTokenResult.result.access_token);
@ -4051,7 +4057,7 @@ describe('/v1', function () {
},
});
assert.equal(refreshTokenResult.statusCode, 200);
assert.isNull(
assert.isUndefined(
validators.jwt.validate(refreshTokenResult.result.access_token).error
);
const refreshTokenJWT = decodeJWT(refreshTokenResult.result.access_token);
@ -4074,7 +4080,7 @@ describe('/v1', function () {
}
);
assert.equal(tokenResult.statusCode, 200);
assert.isNull(
assert.isUndefined(
validators.jwt.validate(tokenResult.result.access_token).error
);
const tokenJWT = decodeJWT(tokenResult.result.access_token);
@ -4090,7 +4096,7 @@ describe('/v1', function () {
},
});
assert.equal(clientRotatedResult.statusCode, 200);
assert.isNull(
assert.isUndefined(
validators.jwt.validate(clientRotatedResult.result.access_token).error
);
const clientRotatedJWT = decodeJWT(

Просмотреть файл

@ -252,14 +252,14 @@ describe('lib/keys', () => {
it('can generate new private keys', () => {
const key = keys.generatePrivateKey();
assert.strictEqual(keys.PRIVATE_KEY_SCHEMA.validate(key).error, null);
assert.strictEqual(keys.PRIVATE_KEY_SCHEMA.validate(key).error, undefined);
assert.ok(key['fxa-createdAt'] <= Date.now() / 1000);
assert.ok(key['fxa-createdAt'] >= Date.now() / 1000 - 3600);
});
it('can extract public keys', () => {
const key = keys.extractPublicKey(keys.generatePrivateKey());
assert.strictEqual(keys.PUBLIC_KEY_SCHEMA.validate(key).error, null);
assert.notEqual(keys.PRIVATE_KEY_SCHEMA.validate(key).error, null);
assert.strictEqual(keys.PUBLIC_KEY_SCHEMA.validate(key).error, undefined);
assert.notEqual(keys.PRIVATE_KEY_SCHEMA.validate(key).error, undefined);
});
});

Просмотреть файл

@ -5,7 +5,7 @@
'use strict';
const { assert } = require('chai');
const Joi = require('@hapi/joi');
const Joi = require('joi');
const CLIENT_ID = '98e6508e88680e1b';
// jscs:disable
@ -38,7 +38,6 @@ describe('/authorization POST', function () {
Joi.assert(req, validation);
} catch (err) {
fail = true;
assert.ok(err.isJoi);
assert.ok(err.name, 'ValidationError');
assert.equal(err.details[0].message, `"${param}" ${messagePostfix}`);
}
@ -140,7 +139,7 @@ describe('/authorization POST', function () {
code_challenge_method: 'bad_method',
},
'code_challenge_method',
'must be one of [S256]'
'must be [S256]'
);
});

Просмотреть файл

@ -3,7 +3,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const { assert } = require('chai');
const Joi = require('@hapi/joi');
const CLIENT_SECRET =
'b93ef8a8f3e553a430d7e5b904c6132b2722633af9f03128029201d24a97f2a8';
@ -30,14 +29,14 @@ const route = require('../../../lib/routes/oauth/token')({
})[0];
function joiRequired(err, param) {
assert.ok(err.isJoi);
assert.ok(err.name, 'ValidationError');
assert.isTrue(err.isJoi);
assert.equal(err.name, 'ValidationError');
assert.equal(err.details[0].message, `"${param}" is required`);
}
function joiNotAllowed(err, param) {
assert.ok(err.isJoi);
assert.ok(err.name, 'ValidationError');
assert.isTrue(err.isJoi);
assert.equal(err.name, 'ValidationError');
assert.equal(err.details[0].message, `"${param}" is not allowed`);
}
@ -49,51 +48,39 @@ describe('/token POST', function () {
cb = ctx;
ctx = undefined;
}
Joi.validate(req, route.config.validate.payload, { context: ctx }, cb);
const validationSchema = route.config.validate.payload;
return validationSchema.validate(req, { context: ctx }, cb);
}
it('fails with no client_id', (done) => {
v(
{
client_secret: CLIENT_SECRET,
code: CODE,
},
(err) => {
joiRequired(err, 'client_id');
done();
}
);
it('fails with no client_id', () => {
const res = v({
client_secret: CLIENT_SECRET,
code: CODE,
});
joiRequired(res.error, 'client_id');
});
it('valid client_secret scheme', (done) => {
v(
{
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
code: CODE,
},
(err) => {
assert.equal(err, null);
done();
}
);
it('valid client_secret scheme', () => {
const res = v({
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
code: CODE,
});
assert.equal(res.error, undefined);
});
it('requires client_secret', (done) => {
v(
{
client_id: CLIENT_ID,
code: CODE,
},
(err) => {
joiRequired(err, 'client_secret');
done();
}
);
it('requires client_secret', () => {
const res = v({
client_id: CLIENT_ID,
code: CODE,
});
joiRequired(res.error, 'client_secret');
});
it('forbids client_id when authz header provided', (done) => {
v(
it('forbids client_id when authz header provided', () => {
const res = v(
{
client_id: CLIENT_ID,
},
@ -101,16 +88,14 @@ describe('/token POST', function () {
headers: {
authorization: 'Basic ABCDEF',
},
},
(err) => {
joiNotAllowed(err, 'client_id');
done();
}
);
joiNotAllowed(res.error, 'client_id');
});
it('forbids client_secret when authz header provided', (done) => {
v(
it('forbids client_secret when authz header provided', () => {
const res = v(
{
client_secret: CLIENT_SECRET,
code: CODE, // If we don't send `code`, then the missing `code` will fail validation first.
@ -119,92 +104,75 @@ describe('/token POST', function () {
headers: {
authorization: 'Basic ABCDEF',
},
},
(err) => {
joiNotAllowed(err, 'client_secret');
done();
}
);
joiNotAllowed(res.error, 'client_secret');
});
describe('pkce', () => {
it('accepts pkce code_verifier instead of client_secret', (done) => {
v(
{
client_id: CLIENT_ID,
code_verifier: PKCE_CODE_VERIFIER,
code: CODE,
},
(err) => {
assert.equal(err, null);
done();
}
);
it('accepts pkce code_verifier instead of client_secret', () => {
const res = v({
client_id: CLIENT_ID,
code_verifier: PKCE_CODE_VERIFIER,
code: CODE,
});
assert.equal(res.error, undefined);
});
it('rejects pkce code_verifier that is too small', (done) => {
it('rejects pkce code_verifier that is too small', () => {
const bad_code_verifier = PKCE_CODE_VERIFIER.substring(0, 32);
v(
{
client_id: CLIENT_ID,
code_verifier: bad_code_verifier,
code: CODE,
},
(err) => {
assert.ok(err.isJoi);
assert.ok(err.name, 'ValidationError');
assert.equal(
err.details[0].message,
// eslint-disable-next-line quotes
`"code_verifier" length must be at least 43 characters long`
); // eslint-disable-line quotes
done();
}
);
const res = v({
client_id: CLIENT_ID,
code_verifier: bad_code_verifier,
code: CODE,
});
assert.isTrue(res.error.isJoi);
assert.equal(res.error.name, 'ValidationError');
assert.equal(
res.error.details[0].message,
// eslint-disable-next-line quotes
`"code_verifier" length must be at least 43 characters long`
); // eslint-disable-line quotes
});
it('rejects pkce code_verifier that is too big', (done) => {
it('rejects pkce code_verifier that is too big', () => {
const bad_code_verifier =
PKCE_CODE_VERIFIER +
PKCE_CODE_VERIFIER +
PKCE_CODE_VERIFIER +
PKCE_CODE_VERIFIER;
v(
{
client_id: CLIENT_ID,
code_verifier: bad_code_verifier,
code: CODE,
},
(err) => {
assert.ok(err.isJoi);
assert.ok(err.name, 'ValidationError');
assert.equal(
err.details[0].message,
// eslint-disable-next-line quotes
`"code_verifier" length must be less than or equal to 128 characters long`
); // eslint-disable-line quotes
done();
}
);
const res = v({
client_id: CLIENT_ID,
code_verifier: bad_code_verifier,
code: CODE,
});
assert.isTrue(res.error.isJoi);
assert.equal(res.error.name, 'ValidationError');
assert.equal(
res.error.details[0].message,
// eslint-disable-next-line quotes
`"code_verifier" length must be less than or equal to 128 characters long`
); // eslint-disable-line quotes
});
it('rejects pkce code_verifier that contains invalid characters', (done) => {
it('rejects pkce code_verifier that contains invalid characters', () => {
const bad_code_verifier = PKCE_CODE_VERIFIER + ' :.';
v(
{
client_id: CLIENT_ID,
code_verifier: bad_code_verifier,
code: CODE,
},
(err) => {
assert.ok(err.isJoi);
assert.ok(err.name, 'ValidationError');
assert.equal(
err.details[0].message,
`"code_verifier" with value "${bad_code_verifier}" fails to match the required pattern: /^[A-Za-z0-9-_]+$/`
);
done();
}
const res = v({
client_id: CLIENT_ID,
code_verifier: bad_code_verifier,
code: CODE,
});
assert.isTrue(res.error.isJoi);
assert.equal(res.error.name, 'ValidationError');
assert.equal(
res.error.details[0].message,
`"code_verifier" with value "${bad_code_verifier}" fails to match the required pattern: /^[A-Za-z0-9-_]+$/`
);
});
});

Просмотреть файл

@ -3,7 +3,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const { assert } = require('chai');
const Joi = require('@hapi/joi');
const proxyquire = require('proxyquire');
const sinon = require('sinon');
const ScopeSet = require('fxa-shared').oauth.scopes;
@ -12,8 +11,8 @@ const TOKEN =
'df6dcfe7bf6b54a65db5742cbcdce5c0a84a5da81a0bb6bdf5fc793eef041fc6';
function joiRequired(err, param) {
assert.ok(err.isJoi);
assert.ok(err.name, 'ValidationError');
assert.isTrue(err.isJoi);
assert.equal(err.name, 'ValidationError');
assert.strictEqual(err.details[0].message, `"${param}" is required`);
}
@ -59,7 +58,8 @@ describe('/verify POST', () => {
describe('validation', () => {
function validate(req, context = {}) {
const result = Joi.validate(req, route.config.validate.payload, {
const validationSchema = route.config.validate.payload;
const result = validationSchema.validate(req, {
context,
});
return result.error;
@ -76,7 +76,7 @@ describe('/verify POST', () => {
const err = validate({
token: TOKEN,
});
assert.strictEqual(err, null);
assert.strictEqual(err, undefined);
});
});

Просмотреть файл

@ -532,6 +532,7 @@ describe('StripeProductsAndPlansConverter', () => {
Container.reset();
sandbox.reset();
});
it('processes new products and plans', async () => {
await converter.convert(args);
products = await paymentConfigManager.allProducts();
@ -567,6 +568,7 @@ describe('StripeProductsAndPlansConverter', () => {
productConfigId: products[1].id,
});
});
it('updates existing products and plans', async () => {
// Put some configs into Firestore
const productConfigDocId1 = await paymentConfigManager.storeProductConfig(
@ -638,6 +640,7 @@ describe('StripeProductsAndPlansConverter', () => {
productConfigId: products[0].id,
});
});
it('processes only the product with productId when passed', async () => {
await converter.convert({ ...args, productId: product1.id });
sinon.assert.calledOnceWithExactly(
@ -645,6 +648,7 @@ describe('StripeProductsAndPlansConverter', () => {
{ ids: [product1.id] }
);
});
it('does not update Firestore if dryRun = true', async () => {
paymentConfigManager.storeProductConfig = sandbox.stub();
paymentConfigManager.storePlanConfig = sandbox.stub();
@ -653,6 +657,7 @@ describe('StripeProductsAndPlansConverter', () => {
sinon.assert.notCalled(paymentConfigManager.storeProductConfig);
sinon.assert.notCalled(paymentConfigManager.storePlanConfig);
});
it('moves localized data from plans into the productConfig', async () => {
const productWithRequiredKeys = {
...deepCopy(product1),
@ -711,6 +716,7 @@ describe('StripeProductsAndPlansConverter', () => {
};
assert.deepEqual(products[0].locales, expected);
});
it('logs an error and keeps processing if a product fails', async () => {
const productConfigId = 'test-product-id';
const planConfigId = 'test-plan-id';
@ -760,6 +766,7 @@ describe('StripeProductsAndPlansConverter', () => {
}
);
});
it('logs an error and keeps processing if a plan fails', async () => {
const productConfigId = 'test-product-id';
const planConfigId = 'test-plan-id';

Просмотреть файл

@ -98,7 +98,7 @@
"hot-shots": "^9.0.0",
"http-proxy-middleware": "^2.0.0",
"i18n-abide": "0.0.26",
"joi": "^14.3.1",
"joi": "17.4.0",
"jquery": "3.6.0",
"jquery-modal": "https://github.com/mozilla-fxa/jquery-modal.git#0576775d1b4590314b114386019f4c7421c77503",
"jquery-simulate": "1.0.2",

Просмотреть файл

@ -29,6 +29,7 @@ const PATTERNS = {
SERVICE: /^([a-zA-Z0-9\-]{1,16})$/,
SYNC_ENGINE: /^[a-z]+$/,
UNIQUE_USER_ID: /^[0-9a-z-]{36}$/,
UTM: /^[\w\/.%-]+$/,
};
const TYPES = {
@ -37,7 +38,7 @@ const TYPES = {
BOOLEAN: joi.boolean(),
DIMENSION: joi.number().integer().min(0),
DOMAIN: joi.string().max(32).regex(PATTERNS.DOMAIN),
EXPERIMENT: joi.string().valid(EXPERIMENT_NAMES),
EXPERIMENT: joi.string().valid(...EXPERIMENT_NAMES),
FLOW_ID: joi.string().hex().length(64),
HEX32: joi.string().regex(/^[0-9a-f]{32}$/),
INTEGER: joi.number().integer(),
@ -66,7 +67,7 @@ const TYPES = {
.string()
.max(128)
// eslint-disable-next-line no-useless-escape
.regex(/^[\w\/.%-]+$/), // values here can be 'firefox/sync'
.regex(PATTERNS.UTM), // values here can be 'firefox/sync'
};
// the crazy long allow comes from the firstrun page.

Просмотреть файл

@ -564,6 +564,9 @@ async function testInvalidFlowQueryParam(paramName, paramValue) {
assert.fail('request should have failed');
} catch (err) {
assert.equal(err.response.statusCode, 400);
assert.include(JSON.parse(err.response.body).validation.keys, paramName);
assert.include(
JSON.parse(err.response.body).validation.query.keys,
paramName
);
}
}

Просмотреть файл

@ -11,16 +11,16 @@ const validation = require('../../server/lib/validation');
const METRICS_DOCS_URL =
'https://raw.githubusercontent.com/mozilla/ecosystem-platform/master/docs/relying-parties/reference/metrics-for-relying-parties.md';
const UTM_REGEX = validation.TYPES.UTM._tests[1].arg.pattern;
const REGEXES = new Map([
['entrypoint', validation.PATTERNS.ENTRYPOINT],
['entrypoint_experiment', validation.PATTERNS.ENTRYPOINT],
['entrypoint_variation', validation.PATTERNS.ENTRYPOINT],
['utm_campaign', UTM_REGEX],
['utm_content', UTM_REGEX],
['utm_medium', UTM_REGEX],
['utm_source', UTM_REGEX],
['utm_term', UTM_REGEX],
['utm_campaign', validation.PATTERNS.UTM],
['utm_content', validation.PATTERNS.UTM],
['utm_medium', validation.PATTERNS.UTM],
['utm_source', validation.PATTERNS.UTM],
['utm_term', validation.PATTERNS.UTM],
]);
let docs;

Просмотреть файл

@ -60,6 +60,7 @@
"google-auth-library": "^8.0.2",
"graphql": "^15.6.1",
"hot-shots": "^9.0.0",
"joi": "17.4.0",
"jwks-rsa": "^2.1.1",
"mozlog": "^3.0.2",
"passport": "^0.5.2",

Просмотреть файл

@ -124,7 +124,7 @@
"@stripe/react-stripe-js": "^1.7.1",
"@stripe/stripe-js": "^1.29.0",
"accept-language-parser": "^1.5.0",
"celebrate": "^10.1.0",
"celebrate": "^15.0.1",
"classnames": "^2.3.1",
"convict": "^6.2.2",
"convict-format-with-moment": "^6.2.0",
@ -140,7 +140,7 @@
"helmet": "^5.0.0",
"hot-shots": "^9.0.0",
"intl-pluralrules": "^1.3.1",
"joi": "^14.3.1",
"joi": "17.4.0",
"jquery-modal": "https://github.com/mozilla-fxa/jquery-modal.git#0576775d1b4590314b114386019f4c7421c77503",
"morgan": "^1.10.0",
"mozlog": "^3.0.2",

Просмотреть файл

@ -81,7 +81,7 @@ module.exports = {
method: 'get',
path: '/legal-docs',
validate: {
query: { url: joi.string().uri().required() },
query: joi.object({ url: joi.string().uri().required() }),
},
async process(req, res) {
const docUrl = new URL(req.query.url);

Просмотреть файл

@ -35,11 +35,11 @@ describe('navigation-timing route', () => {
describe('request body validation', () => {
test('should pass when given a validate request body', () => {
const result = route.validate.body.validate(validRequestBody);
expect(result.error).toBeNull();
expect(result.error).toBeUndefined();
});
test('should fail when given an invalidate request body', () => {
const result = route.validate.body.validate(invalidRequestBody);
expect(result.error).not.toBeNull();
expect(result.error).not.toBeUndefined();
});
});
describe('handler', () => {

Просмотреть файл

@ -18,10 +18,9 @@ const UTM = joi
// eslint-disable-next-line no-useless-escape
.regex(/^[\w\/.%-]+$/); // values here can be 'firefox/sync'
const UTM_CAMPAIGN = UTM.allow('page+referral+-+not+part+of+a+campaign');
const BODY_SCHEMA = {
const BODY_SCHEMA = joi.object({
data: joi
.object()
.keys({
.object({
flowBeginTime: OFFSET_TYPE.optional(),
flowId: STRING_TYPE.hex().length(64).optional(),
utm_campaign: UTM_CAMPAIGN.optional(),
@ -41,7 +40,7 @@ const BODY_SCHEMA = {
})
)
.required(),
};
});
module.exports = {
method: 'post',

Просмотреть файл

@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const Joi = require('@hapi/joi');
const Joi = require('joi');
const AppError = require('../error');
const config = require('../config');
@ -29,7 +29,7 @@ module.exports = {
],
},
response: {
schema: {
schema: Joi.object({
email: Joi.string().optional(),
locale: Joi.string().optional(),
amrValues: Joi.array().items(Joi.string().required()).optional(),
@ -38,7 +38,7 @@ module.exports = {
subscriptionsByClientId: Joi.object().unknown(true).optional(),
profileChangedAt: Joi.number().optional(),
metricsEnabled: Joi.boolean().optional(),
},
}),
},
handler: async function _core_profile(req) {
function makeReq() {

Просмотреть файл

@ -3,7 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const hex = require('buf').to.hex;
const Joi = require('@hapi/joi');
const Joi = require('joi');
const P = require('../../promise');
const AppError = require('../../error');
@ -24,9 +24,9 @@ module.exports = {
scope: ['profile:avatar:write'],
},
validate: {
params: {
params: Joi.object({
id: Joi.string().length(32).regex(validate.hex).optional(),
},
}),
},
handler: async function deleteAvatar(req) {
if (req.params.id === DEFAULT_AVATAR_ID) {

Просмотреть файл

@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const Joi = require('@hapi/joi');
const Joi = require('joi');
const db = require('../../db');
const hex = require('buf').to.hex;
@ -28,11 +28,11 @@ module.exports = {
scope: ['profile:avatar'],
},
response: {
schema: {
schema: Joi.object({
id: Joi.string().regex(validate.hex).length(32),
avatarDefault: Joi.boolean(),
avatar: Joi.string().max(256),
},
}),
},
handler: async function avatar(req, h) {
var uid = req.auth.credentials.user;

Просмотреть файл

@ -4,7 +4,7 @@
const assert = require('assert');
const Joi = require('@hapi/joi');
const Joi = require('joi');
const config = require('../../config');
const db = require('../../db');
@ -41,10 +41,10 @@ module.exports = {
maxBytes: config.get('img.uploads.maxSize'),
},
response: {
schema: {
schema: Joi.object({
id: Joi.string().regex(validate.hex).length(32),
url: Joi.string().required(),
},
}),
},
handler: async function upload(req, h) {
const uid = req.auth.credentials.user;

Просмотреть файл

@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const Joi = require('@hapi/joi');
const Joi = require('joi');
const checksum = require('checksum');
const db = require('../../db');
@ -13,9 +13,9 @@ module.exports = {
scope: ['profile:display_name'],
},
response: {
schema: {
schema: Joi.object({
displayName: Joi.string().max(256),
},
}),
},
handler: async function displayNameGet(req, h) {
return db.getDisplayName(req.auth.credentials.user).then(function (result) {

Просмотреть файл

@ -2,55 +2,55 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const Joi = require('@hapi/joi');
const db = require('../../db');
const notifyProfileUpdated = require('../../updates-queue');
const Joi = require('joi');
const db = require('../../db');
const notifyProfileUpdated = require('../../updates-queue');
const EMPTY = Object.create(null);
const EMPTY = Object.create(null);
// We're pretty liberal with what's allowed in a display-name,
// but we exclude the following classes of characters:
//
// \u0000-\u001F - C0 (ascii) control characters
// \u007F - ascii DEL character
// \u0080-\u009F - C1 (ansi escape) control characters
// \u2028-\u2029 - unicode line/paragraph separator
// \uE000-\uF8FF - BMP private use area
// \uFFF9-\uFFFC - unicode specials prior to the replacement character
// \uFFFE-\uFFFF - unicode this-is-not-a-character specials
//
// Note that the unicode replacement character \uFFFD is explicitly allowed,
// and clients may use it to replace other disallowed characters.
//
// We might tweak this list in future.
// We're pretty liberal with what's allowed in a display-name,
// but we exclude the following classes of characters:
//
// \u0000-\u001F - C0 (ascii) control characters
// \u007F - ascii DEL character
// \u0080-\u009F - C1 (ansi escape) control characters
// \u2028-\u2029 - unicode line/paragraph separator
// \uE000-\uF8FF - BMP private use area
// \uFFF9-\uFFFC - unicode specials prior to the replacement character
// \uFFFE-\uFFFF - unicode this-is-not-a-character specials
//
// Note that the unicode replacement character \uFFFD is explicitly allowed,
// and clients may use it to replace other disallowed characters.
//
// We might tweak this list in future.
// eslint-disable-next-line no-control-regex
const ALLOWED_DISPLAY_NAME_CHARS = /^(?:[^\u0000-\u001F\u007F\u0080-\u009F\u2028-\u2029\uE000-\uF8FF\uFFF9-\uFFFC\uFFFE-\uFFFF])*$/;
// eslint-disable-next-line no-control-regex
const ALLOWED_DISPLAY_NAME_CHARS = /^(?:[^\u0000-\u001F\u007F\u0080-\u009F\u2028-\u2029\uE000-\uF8FF\uFFF9-\uFFFC\uFFFE-\uFFFF])*$/;
module.exports = {
auth: {
strategy: 'secretBearerToken',
},
validate: {
payload: {
name: Joi.string()
.max(256)
.required()
.allow('')
.regex(ALLOWED_DISPLAY_NAME_CHARS),
},
params: {
uid: Joi.string(),
}
},
handler: async function displayNamePost(req) {
const uid = req.params.uid;
return req.server.methods.profileCache.drop(uid).then(() => {
const payload = req.payload;
return db.setDisplayName(uid, payload.name).then(() => {
notifyProfileUpdated(uid); // Don't wait on promise
return EMPTY;
});
});
},
};
module.exports = {
auth: {
strategy: 'secretBearerToken',
},
validate: {
payload: {
name: Joi.string()
.max(256)
.required()
.allow('')
.regex(ALLOWED_DISPLAY_NAME_CHARS),
},
params: {
uid: Joi.string(),
}
},
handler: async function displayNamePost(req) {
const uid = req.params.uid;
return req.server.methods.profileCache.drop(uid).then(() => {
const payload = req.payload;
return db.setDisplayName(uid, payload.name).then(() => {
notifyProfileUpdated(uid); // Don't wait on promise
return EMPTY;
});
});
},
};

Просмотреть файл

@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const Joi = require('@hapi/joi');
const Joi = require('joi');
const db = require('../../db');
const notifyProfileUpdated = require('../../updates-queue');
@ -33,13 +33,13 @@ module.exports = {
scope: ['profile:display_name:write'],
},
validate: {
payload: {
payload: Joi.object({
displayName: Joi.string()
.max(256)
.required()
.allow('')
.regex(ALLOWED_DISPLAY_NAME_CHARS),
},
}),
},
handler: async function displayNamePost(req) {
const uid = req.auth.credentials.user;

Просмотреть файл

@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const Joi = require('@hapi/joi');
const Joi = require('joi');
const AppError = require('../error');
const logger = require('../logging')('routes.email');
@ -13,9 +13,9 @@ module.exports = {
scope: ['profile:email', /* openid-connect scope */ 'email'],
},
response: {
schema: {
schema: Joi.object({
email: Joi.string().required(),
},
}),
},
handler: async function email(req) {
return req.server

Просмотреть файл

@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const Joi = require('@hapi/joi');
const Joi = require('joi');
const crypto = require('crypto');
const checksum = require('checksum');
const {
@ -60,7 +60,7 @@ module.exports = {
strategy: 'oauth',
},
response: {
schema: {
schema: Joi.object({
email: Joi.string().allow(null),
uid: Joi.string().allow(null),
avatar: Joi.string().allow(null),
@ -74,7 +74,7 @@ module.exports = {
//openid-connect
sub: Joi.string().allow(null),
},
}),
},
handler: async function profile(req, h) {
const server = req.server;

Просмотреть файл

@ -5,7 +5,7 @@
const exec = require('child_process').exec;
const path = require('path');
const Joi = require('@hapi/joi');
const Joi = require('joi');
const version = require('../../package.json').version;
let commitHash, source;
@ -21,11 +21,11 @@ try {
module.exports = {
response: {
schema: {
schema: Joi.object({
version: Joi.string().required(),
commit: Joi.string().required(),
source: Joi.string().required(),
},
}),
},
handler: async function index(req, h) {
function sendReply() {

Просмотреть файл

@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const Joi = require('@hapi/joi');
const Joi = require('joi');
const {
determineClientVisibleSubscriptionCapabilities,
} = require('../subscriptions');
@ -13,9 +13,9 @@ module.exports = {
scope: ['profile:subscriptions'],
},
response: {
schema: {
schema: Joi.object({
subscriptions: Joi.array().items(Joi.string()).required(),
},
}),
},
handler: async function subscriptions(req) {
const res = await req.server.inject({

Просмотреть файл

@ -6,6 +6,7 @@ const Hapi = require('@hapi/hapi');
const Boom = require('@hapi/boom');
const path = require('path');
const Inert = require('@hapi/inert');
const Joi = require('joi');
const config = require('../config').getProperties();
const logger = require('../logging')('server.static');
@ -37,7 +38,7 @@ exports.create = async function () {
},
},
});
server.validator(require('@hapi/joi'));
server.validator(Joi);
await server.register(Inert);

Просмотреть файл

@ -93,7 +93,7 @@ exports.create = async function createServer() {
},
},
});
server.validator(require('@hapi/joi'));
server.validator(require('joi'));
// configure Sentry
if (config.sentry && config.sentry.dsn) {

Просмотреть файл

@ -3,7 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const Hapi = require('@hapi/hapi');
const Joi = require('@hapi/joi');
const Joi = require('joi');
const P = require('../promise');
const AppError = require('../error');
@ -29,7 +29,7 @@ exports.create = async function () {
},
},
});
server.validator(require('@hapi/joi'));
server.validator(Joi);
server.route({
method: 'GET',

Просмотреть файл

@ -20,7 +20,6 @@
"@hapi/catbox-redis": "~6.0.2",
"@hapi/hapi": "^20.2.1",
"@hapi/inert": "6.0.5",
"@hapi/joi": "^17.1.1",
"@sentry/node": "^6.19.1",
"aws-sdk": "^2.1135.0",
"bluebird": "^3.7.2",
@ -31,6 +30,7 @@
"convict-format-with-moment": "^6.2.0",
"convict-format-with-validator": "^6.2.0",
"fxa-shared": "workspace:*",
"joi": "17.4.0",
"lodash": "^4.17.21",
"mozlog": "^3.0.2",
"mysql": "^2.18.1",

Просмотреть файл

@ -1,4 +1,4 @@
import joi from 'typesafe-joi';
import joi from 'joi';
export interface CouponDetails {
promotionCode: string;
@ -20,4 +20,12 @@ export const couponDetailsSchema = joi.object({
maximallyRedeemed: joi.boolean().required(),
});
export type couponDetailsSchema = joi.Literal<typeof couponDetailsSchema>;
export type couponDetailsSchema = {
promotionCode: string;
type: string;
durationInMonths: number | null;
valid: boolean;
discountAmount?: number;
expired: boolean;
maximallyRedeemed: boolean;
};

Просмотреть файл

@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import Stripe from 'stripe';
import joi from 'typesafe-joi';
import joi from 'joi';
import {
MozillaSubscriptionTypes,
@ -21,9 +21,11 @@ export const iapExtraStripeInfoSchema = joi.object({
product_name: joi.string().required(),
});
export type iapExtraStripeInfoSchema = joi.Literal<
typeof iapExtraStripeInfoSchema
>;
export type iapExtraStripeInfoSchema = {
price_id: string;
product_id: string;
product_name: string;
};
export type PlayStoreSubscription = {
auto_renewing: boolean;
@ -45,10 +47,6 @@ export const playStoreSubscriptionSchema = joi
})
.concat(iapExtraStripeInfoSchema);
export type playStoreSubscriptionSchema = joi.Literal<
typeof playStoreSubscriptionSchema
>;
export type AppStoreSubscription = {
app_store_product_id: string;
auto_renewing: boolean;
@ -68,7 +66,3 @@ export const appStoreSubscriptionSchema = joi
_subscription_type: MozillaSubscriptionTypes.IAP_APPLE,
})
.concat(iapExtraStripeInfoSchema);
export type appStoreSubscriptionSchema = joi.Literal<
typeof appStoreSubscriptionSchema
>;

Просмотреть файл

@ -1,7 +1,7 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import joi from 'typesafe-joi';
import joi from 'joi';
export interface InvoiceLineItem {
amount: number;
@ -64,9 +64,29 @@ export const firstInvoicePreviewSchema = joi.object({
}),
});
export type firstInvoicePreviewSchema = joi.Literal<
typeof firstInvoicePreviewSchema
>;
type line_item = {
amount: number;
currency: string;
id: string;
name: string;
};
export type firstInvoicePreviewSchema = {
line_items: Array<line_item>;
subtotal: number;
total: number;
tax?: {
amount: number;
inclusive: boolean;
name: string;
percentage: number;
};
discount?: {
amount: number;
amount_off: number | null;
percent_off: number | null;
};
};
/**
* Defines an interface for the subsequent invoice preview response
@ -86,6 +106,10 @@ export const subsequentInvoicePreviewsSchema = joi.array().items(
})
);
export type subsequentInvoicePreviewsSchema = joi.Literal<
typeof subsequentInvoicePreviewsSchema
>;
type subsequentInvoicePreview = {
subscriptionId: string;
period_start: number;
total: number;
};
export type subsequentInvoicePreviewsSchema = Array<subsequentInvoicePreview>;

Просмотреть файл

@ -16,7 +16,7 @@ module.exports = (clientIpAddressDepth) => (request) => {
.map((address) => address.trim());
ipAddresses.push(request.ip || request.connection.remoteAddress);
ipAddresses = ipAddresses.filter(
(ipAddress) => !joi.validate(ipAddress, IP_ADDRESS).error
(ipAddress) => !IP_ADDRESS.validate(ipAddress).error
);
let clientAddressIndex = ipAddresses.length - clientIpAddressDepth;

Просмотреть файл

@ -6,7 +6,7 @@ module.exports = (app, logger) => {
const cors = require('./cors');
const {
celebrate,
isCelebrate: isValidationError,
isCelebrateError: isValidationError,
errors: validationErrorHandlerFactory,
} = require('celebrate');
const validationErrorHandler = validationErrorHandlerFactory();

Просмотреть файл

@ -47,7 +47,6 @@
"@types/ioredis": "^4.26.4",
"@types/ip": "^1",
"@types/jest": "^26.0.23",
"@types/joi": "^14.3.4",
"@types/lodash.omitby": "^4",
"@types/lodash.pick": "^4",
"@types/mocha": "^8.2.2",
@ -98,7 +97,7 @@
"app-store-server-api": "^0.3.0",
"aws-sdk": "^2.1135.0",
"buf": "^0.1.1",
"celebrate": "^10.0.1",
"celebrate": "^15.0.1",
"class-transformer": "^0.5.1",
"class-validator": "^0.13.2",
"cldr-localenames-full": "41.0.0",
@ -110,7 +109,7 @@
"hot-shots": "^9.0.0",
"ioredis": "^4.28.2",
"ip": "^1.1.5",
"joi": "^14.3.1",
"joi": "17.4.0",
"js-md5": "^0.7.3",
"knex": "^2.0.0",
"lodash.omitby": "^4.6.0",
@ -124,7 +123,6 @@
"rxjs": "^7.2.0",
"stripe": "^8.218.0",
"superagent": "^7.1.2",
"typesafe-joi": "^2.1.0",
"typesafe-node-firestore": "^1.4.0"
},
"jest": {

Просмотреть файл

@ -30,12 +30,8 @@ export const planConfigJoiKeys = {
.optional(),
};
export const planConfigSchema = baseConfigSchema
.keys(planConfigJoiKeys)
.requiredKeys('active');
const buildPlanConfigSchema = (baseSchema: joi.ObjectSchema) =>
baseSchema.keys(planConfigJoiKeys).requiredKeys('active');
const buildPlanConfigSchema = (schema: joi.ObjectSchema) =>
schema.fork(['active'], (schema) => schema.required());
export class PlanConfig implements BaseConfig {
// Firestore document id
@ -70,14 +66,14 @@ export class PlanConfig implements BaseConfig {
schemaValidation: ProductConfigSchemaValidation
) {
const extendedBaseSchema = extendBaseConfigSchema(
baseConfigSchema,
baseConfigSchema.keys(planConfigJoiKeys),
schemaValidation.cdnUrlRegex
);
const planConfigSchema = buildPlanConfigSchema(extendedBaseSchema);
try {
const value = await joi.validate(planConfig, planConfigSchema, {
const value = await planConfigSchema.validateAsync(planConfig, {
abortEarly: false,
});
return { value };

Просмотреть файл

@ -22,9 +22,8 @@ export const productConfigJoiKeys = {
};
const buildProductConfigSchema = (baseSchema: joi.ObjectSchema) =>
baseSchema
.keys(productConfigJoiKeys)
.requiredKeys(
baseSchema.fork(
[
'capabilities',
'locales',
'styles',
@ -35,8 +34,10 @@ const buildProductConfigSchema = (baseSchema: joi.ObjectSchema) =>
'urls.termsOfService',
'urls.termsOfServiceDownload',
'urls.webIcon',
'urls'
);
'urls',
],
(schema) => schema.required()
);
// This type defines the required fields of urls, set by function buildProductConfigSchema.
// Any change to required fields in urls, should be updated here as well.
@ -83,14 +84,14 @@ export class ProductConfig implements BaseConfig {
schemaValidation: ProductConfigSchemaValidation
) {
const extendedBaseSchema = extendBaseConfigSchema(
baseConfigSchema,
baseConfigSchema.keys(productConfigJoiKeys),
schemaValidation.cdnUrlRegex
);
const productConfigSchema = buildProductConfigSchema(extendedBaseSchema);
try {
const value = await joi.validate(productConfig, productConfigSchema, {
const value = await productConfigSchema.validateAsync(productConfig, {
abortEarly: false,
});
return { value };

Просмотреть файл

@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import Joi from '@hapi/joi';
import Joi from 'joi';
export const capabilitiesClientIdPattern = /^capabilities/;

Просмотреть файл

@ -14,19 +14,19 @@ describe('express/routing:', () => {
let celebrateMock;
let corsHandler;
let errorHandler;
let isCelebrate;
let isCelebrateError;
let loggerMock;
let routing;
let routingFactory;
beforeEach(() => {
celebrateHandler = () => {};
isCelebrate = false;
isCelebrateError = false;
errorHandler = sinon.spy();
celebrateMock = {
celebrate: () => celebrateHandler,
isCelebrate: () => isCelebrate,
isCelebrateError: () => isCelebrateError,
errors: () => errorHandler,
};
@ -202,7 +202,7 @@ describe('express/routing:', () => {
it('logs and delegates validation errors to celebrate', () => {
const error = new Error('uh oh');
const next = sinon.spy();
isCelebrate = true;
isCelebrateError = true;
routing.validationErrorHandler(error, {}, {}, next);
@ -213,7 +213,7 @@ describe('express/routing:', () => {
it('passes on other errors to the next error handler', () => {
const error = new Error('uh oh');
const next = sinon.spy();
isCelebrate = false;
isCelebrateError = false;
routing.validationErrorHandler(error, {}, {}, next);

Просмотреть файл

@ -56,8 +56,7 @@
"rxjs": "^7.2.0",
"semver": "^7.3.5",
"superagent": "^7.1.2",
"tslib": "^2.3.1",
"typesafe-joi": "^2.1.0"
"tslib": "^2.3.1"
},
"devDependencies": {
"@nestjs/cli": "^8.2.5",

320
yarn.lock
Просмотреть файл

@ -3841,15 +3841,6 @@ __metadata:
languageName: node
linkType: hard
"@hapi/address@npm:^4.0.1":
version: 4.0.1
resolution: "@hapi/address@npm:4.0.1"
dependencies:
"@hapi/hoek": ^9.0.0
checksum: 365547dbed1dcc8404a2cf5212fbe4c279f069e2ed817cb2a1b53bcc85a5e8ea7ce0e5377dc111cfff9d31e46275dbf83b316b0e4aba5abd05416c2a38914ea5
languageName: node
linkType: hard
"@hapi/ammo@npm:5.x.x, @hapi/ammo@npm:^5.0.1":
version: 5.0.1
resolution: "@hapi/ammo@npm:5.0.1"
@ -3868,7 +3859,7 @@ __metadata:
languageName: node
linkType: hard
"@hapi/boom@npm:9.x.x, @hapi/boom@npm:^9.0.0, @hapi/boom@npm:^9.1.0":
"@hapi/boom@npm:9.x.x, @hapi/boom@npm:^9.0.0, @hapi/boom@npm:^9.1.0, @hapi/boom@npm:^9.1.4":
version: 9.1.4
resolution: "@hapi/boom@npm:9.1.4"
dependencies:
@ -3877,15 +3868,6 @@ __metadata:
languageName: node
linkType: hard
"@hapi/boom@npm:^7.1.1":
version: 7.4.11
resolution: "@hapi/boom@npm:7.4.11"
dependencies:
"@hapi/hoek": 8.x.x
checksum: ef166e1a764a9574b084d258a315d83fd61070b3ba0f5c16a2a5d716fe3f3fc068ce3c600bcb8bf62c0bad0ab01ba505b81ff93e806091d17e17571bcb76c6d3
languageName: node
linkType: hard
"@hapi/boom@npm:~10.0.0":
version: 10.0.0
resolution: "@hapi/boom@npm:10.0.0"
@ -3987,13 +3969,6 @@ __metadata:
languageName: node
linkType: hard
"@hapi/formula@npm:^2.0.0":
version: 2.0.0
resolution: "@hapi/formula@npm:2.0.0"
checksum: 902da057c53027d9356de15e53a8af9ea4795d3fb78c066668b36cfca54abd2d707860469618448e7de02eb8364ead86e53b839dbd363e1aeb65ee9a195e5fad
languageName: node
linkType: hard
"@hapi/hapi@npm:^20.2.1":
version: 20.2.1
resolution: "@hapi/hapi@npm:20.2.1"
@ -4065,14 +4040,14 @@ __metadata:
languageName: node
linkType: hard
"@hapi/hoek@npm:^6.1.2":
version: 6.2.4
resolution: "@hapi/hoek@npm:6.2.4"
checksum: 17e6e687509c20d3730dfb14b05536024d50253e96fcd0c7a4df8247e3a30568a8028b59208173f3e63dbab3eb6b71f07eff06b234dd5c2bd773d254af769e37
"@hapi/hoek@npm:^9.3.0":
version: 9.3.0
resolution: "@hapi/hoek@npm:9.3.0"
checksum: 4771c7a776242c3c022b168046af4e324d116a9d2e1d60631ee64f474c6e38d1bb07092d898bf95c7bc5d334c5582798a1456321b2e53ca817d4e7c88bc25b43
languageName: node
linkType: hard
"@hapi/inert@npm:6.0.5":
"@hapi/inert@npm:6.0.5, @hapi/inert@npm:^6.0.5":
version: 6.0.5
resolution: "@hapi/inert@npm:6.0.5"
dependencies:
@ -4099,7 +4074,7 @@ __metadata:
languageName: node
linkType: hard
"@hapi/joi@npm:15.x.x, @hapi/joi@npm:^15.0.1, @hapi/joi@npm:^15.1.0, @hapi/joi@npm:^15.1.1":
"@hapi/joi@npm:^15.1.0, @hapi/joi@npm:^15.1.1":
version: 15.1.1
resolution: "@hapi/joi@npm:15.1.1"
dependencies:
@ -4111,19 +4086,6 @@ __metadata:
languageName: node
linkType: hard
"@hapi/joi@npm:^17.1.1":
version: 17.1.1
resolution: "@hapi/joi@npm:17.1.1"
dependencies:
"@hapi/address": ^4.0.1
"@hapi/formula": ^2.0.0
"@hapi/hoek": ^9.0.0
"@hapi/pinpoint": ^2.0.0
"@hapi/topo": ^5.0.0
checksum: 803d77e19e26802860de22b8d1ae3435679844202703d20bfd6246a8c6aa00143ce48d0e8beecf55812334e8e89ac04b79301264d4a3f87c980eb0c0646b33ab
languageName: node
linkType: hard
"@hapi/mimos@npm:^6.0.0":
version: 6.0.0
resolution: "@hapi/mimos@npm:6.0.0"
@ -4157,13 +4119,6 @@ __metadata:
languageName: node
linkType: hard
"@hapi/pinpoint@npm:^2.0.0":
version: 2.0.0
resolution: "@hapi/pinpoint@npm:2.0.0"
checksum: ed011c0af4eeab75f7ea2b4657f16a93e31d449a43b40a4f398e2f1a0752357af2badd7b3f3e2da9554cb9d13adb52bde123b250acef1709260954d0301eed16
languageName: node
linkType: hard
"@hapi/podium@npm:4.x.x, @hapi/podium@npm:^4.1.1":
version: 4.1.1
resolution: "@hapi/podium@npm:4.1.1"
@ -4299,6 +4254,18 @@ __metadata:
languageName: node
linkType: hard
"@hapi/vision@npm:^6.1.0":
version: 6.1.0
resolution: "@hapi/vision@npm:6.1.0"
dependencies:
"@hapi/boom": 9.x.x
"@hapi/bounce": 2.x.x
"@hapi/hoek": 9.x.x
"@hapi/validate": 1.x.x
checksum: 3e38ea9a0b29849c98ce9ba92b200c6912f60d47768140cb371f05593b4bf34ad4544d3e48e38b57831b22e47f8778cf5877f8a5e7d895566b1d2b8cc82a1535
languageName: node
linkType: hard
"@hapi/wreck@npm:17.x.x":
version: 17.0.0
resolution: "@hapi/wreck@npm:17.0.0"
@ -9254,13 +9221,6 @@ __metadata:
languageName: node
linkType: hard
"@types/boom@npm:*":
version: 7.3.1
resolution: "@types/boom@npm:7.3.1"
checksum: fc483602fb667d8858cef0a7685b9f25ce805d2e15ba033abf6e33da4ca1f103d69ec77e2d28d116d88a17a18f57c909358d99f9fbe3a9dd00c781190e877b7f
languageName: node
linkType: hard
"@types/braces@npm:*":
version: 3.0.0
resolution: "@types/braces@npm:3.0.0"
@ -9296,13 +9256,6 @@ __metadata:
languageName: node
linkType: hard
"@types/catbox@npm:*":
version: 10.0.7
resolution: "@types/catbox@npm:10.0.7"
checksum: d44d7e529757d46be3a3c755659b1ab68104749ada8537ce72959588560ae87db8d9c546bd2b179f91e5d5edae569dc6b58ea91f7edcffb6130509f98059b613
languageName: node
linkType: hard
"@types/chai-as-promised@npm:^7":
version: 7.1.4
resolution: "@types/chai-as-promised@npm:7.1.4"
@ -9613,22 +9566,6 @@ __metadata:
languageName: node
linkType: hard
"@types/hapi@npm:*":
version: 18.0.7
resolution: "@types/hapi@npm:18.0.7"
dependencies:
"@types/boom": "*"
"@types/catbox": "*"
"@types/iron": "*"
"@types/mimos": "*"
"@types/node": "*"
"@types/podium": "*"
"@types/shot": "*"
joi: ^17.3.0
checksum: a10ed6407ad54382bd9e05a67ce5eff2ec70f74e1f40f0a13bf5687c4458245e96d93b1704d51e870ca2d12f6e4f957281d711c80884f69e8702ff3c08533720
languageName: node
linkType: hard
"@types/hapi__catbox@npm:*":
version: 10.2.2
resolution: "@types/hapi__catbox@npm:10.2.2"
@ -9659,7 +9596,7 @@ __metadata:
languageName: node
linkType: hard
"@types/hapi__joi@npm:^15.0.1, @types/hapi__joi@npm:^15.0.4":
"@types/hapi__joi@npm:^15.0.1":
version: 15.0.4
resolution: "@types/hapi__joi@npm:15.0.4"
dependencies:
@ -9762,15 +9699,6 @@ __metadata:
languageName: node
linkType: hard
"@types/inert@npm:^5":
version: 5.1.3
resolution: "@types/inert@npm:5.1.3"
dependencies:
"@types/hapi": "*"
checksum: 223811d00ba88f043e0ec9ff7e3e2bdbc9c61552bcfadd65bbe5ddaa36da9f501bf342a5c74d981a8c2fc748bce8ea41f017ba86853c1f27275908ef21a7f84d
languageName: node
linkType: hard
"@types/ioredis@npm:^4.26.4":
version: 4.26.4
resolution: "@types/ioredis@npm:4.26.4"
@ -9789,15 +9717,6 @@ __metadata:
languageName: node
linkType: hard
"@types/iron@npm:*":
version: 5.0.2
resolution: "@types/iron@npm:5.0.2"
dependencies:
"@types/node": "*"
checksum: a3e9f914b3e880632ca3f423c08249c66a98277b1a4cb16bca42e910c216eaf8bd333d24b550b9deb685b6851f36784a25066806078ccf4bdcacc5997eb44e10
languageName: node
linkType: hard
"@types/is-function@npm:^1.0.0":
version: 1.0.0
resolution: "@types/is-function@npm:1.0.0"
@ -9861,13 +9780,6 @@ __metadata:
languageName: node
linkType: hard
"@types/joi@npm:^14.3.4":
version: 14.3.4
resolution: "@types/joi@npm:14.3.4"
checksum: 084c0fe211b43a84d25317d772bb8f6426382f1975cb4065fbfa1da7a64c600e9068a9b13ec2a32ddbe71d14f25c90da14a196651acb27e35d02856dbf6d4798
languageName: node
linkType: hard
"@types/jquery@npm:*":
version: 3.3.38
resolution: "@types/jquery@npm:3.3.38"
@ -10082,15 +9994,6 @@ __metadata:
languageName: node
linkType: hard
"@types/mimos@npm:*":
version: 3.0.3
resolution: "@types/mimos@npm:3.0.3"
dependencies:
"@types/mime-db": "*"
checksum: 84d9ff1fdb9e67366aade363e898ad00a843c10dfab9790865268848a2665aa2e87cd2a9374a1e3def4cd7d7ea0c2441885e67df9c8b8b2b5a567f7c2618def4
languageName: node
linkType: hard
"@types/minimatch@npm:*, @types/minimatch@npm:^3.0.3":
version: 3.0.3
resolution: "@types/minimatch@npm:3.0.3"
@ -10280,13 +10183,6 @@ __metadata:
languageName: node
linkType: hard
"@types/podium@npm:*":
version: 1.0.1
resolution: "@types/podium@npm:1.0.1"
checksum: 7e9a54922f0c534fbc48db886a6a8c1fc65fa9361139a60e86d4d18600f0b9d60908bfdcee084492c5a19d7d2dfd012203a796622c2b52e1e37fc778d8701c35
languageName: node
linkType: hard
"@types/postcss-import@npm:^12":
version: 12.0.1
resolution: "@types/postcss-import@npm:12.0.1"
@ -10598,15 +10494,6 @@ __metadata:
languageName: node
linkType: hard
"@types/shot@npm:*":
version: 4.0.1
resolution: "@types/shot@npm:4.0.1"
dependencies:
"@types/node": "*"
checksum: 31eae160a82a1bfdf53f10f8cd18bc7add5a8a860d9da1b183fa4e681eb12318d8ee4099d1f3d29623684291caed59d235a14eec089114d81738e138b245d658
languageName: node
linkType: hard
"@types/sinon-chai@npm:3.2.5":
version: 3.2.5
resolution: "@types/sinon-chai@npm:3.2.5"
@ -10800,16 +10687,6 @@ __metadata:
languageName: node
linkType: hard
"@types/vision@npm:^5":
version: 5.3.8
resolution: "@types/vision@npm:5.3.8"
dependencies:
"@types/hapi": "*"
handlebars: ^4.1.0
checksum: bafe17c0e881304a1e0dc5d4ce819938bd53968609c3ec6fecf9fa64eff0ce1575e905bca87511ffc78448b3a907444cdab9883e88148cedf1599330a3be6ee9
languageName: node
linkType: hard
"@types/webpack-env@npm:^1.16.0":
version: 1.16.0
resolution: "@types/webpack-env@npm:1.16.0"
@ -12007,15 +11884,6 @@ __metadata:
languageName: node
linkType: hard
"ammo@npm:3.x.x":
version: 3.0.3
resolution: "ammo@npm:3.0.3"
dependencies:
hoek: 6.x.x
checksum: 6f8c7c5864317645f7397607edbd09213cc54a7868b36523842ef23b72e1e0e0f93946795725a353a4c67ad3a0a71bfbd0f23ddb0434ac119008a0234c4a4401
languageName: node
linkType: hard
"amp-message@npm:~0.1.1":
version: 0.1.2
resolution: "amp-message@npm:0.1.2"
@ -14747,25 +14615,6 @@ __metadata:
languageName: node
linkType: hard
"boom@npm:7.x.x":
version: 7.3.0
resolution: "boom@npm:7.3.0"
dependencies:
hoek: 6.x.x
checksum: 86d22bef321d3907a5a8f9188e3004efd1bf9e4aff21e358e3a2a8061a8ff5e442843cf5c43041ae5324496fc881562070c65040a59b7265ee84130fb1a8eae3
languageName: node
linkType: hard
"bounce@npm:1.x.x":
version: 1.2.3
resolution: "bounce@npm:1.2.3"
dependencies:
boom: 7.x.x
hoek: 6.x.x
checksum: 4f6c177433e4e2f58f357589a18492f81db1d3085835d36b233db50e978d493d4e1b9b3e26a89f03ac6e0e019f688f30e860f4aa11e7a486858d7ba249ba0fe5
languageName: node
linkType: hard
"boxen@npm:^4.2.0":
version: 4.2.0
resolution: "boxen@npm:4.2.0"
@ -15753,14 +15602,14 @@ __metadata:
languageName: node
linkType: hard
"celebrate@npm:^10.0.1, celebrate@npm:^10.1.0":
version: 10.1.0
resolution: "celebrate@npm:10.1.0"
"celebrate@npm:^15.0.1":
version: 15.0.1
resolution: "celebrate@npm:15.0.1"
dependencies:
"@hapi/joi": 15.x.x
escape-html: 1.0.3
lodash.get: 4.4.x
checksum: b229c9fccca47152bf9ea70f98dfe7a3679d6cc565d2ff5d2d7c808f37ef3889898c85ea04714714cc06158cea16a38c945acba7073d1ba749f26b6e6a75ddf2
joi: 17.x.x
lodash: 4.17.x
checksum: 8f20e47f1285fbb914f80f1f6ebb60eb224805f737a7652f1423cbcd2307f5b6e4e9681859a84e59154f7625214abe086c24a516c5483fe798db54d3066c790a
languageName: node
linkType: hard
@ -22370,7 +22219,8 @@ fsevents@~2.1.1:
"@hapi/hapi": ^20.2.1
"@hapi/hawk": ^8.0.0
"@hapi/hoek": ^10.0.0
"@hapi/joi": ^15.1.1
"@hapi/inert": ^6.0.5
"@hapi/vision": ^6.1.0
"@sentry/integrations": ^6.19.1
"@sentry/node": ^6.19.1
"@storybook/addon-controls": ^6.4.19
@ -22386,8 +22236,6 @@ fsevents@~2.1.1:
"@types/dedent": ^0
"@types/ejs": ^3.0.6
"@types/hapi__hapi": ^20.0.10
"@types/hapi__joi": ^15.0.4
"@types/inert": ^5
"@types/ioredis": ^4.26.4
"@types/jsdom": ^16
"@types/jsonwebtoken": ^8.5.1
@ -22406,7 +22254,6 @@ fsevents@~2.1.1:
"@types/sass": ^1
"@types/uuid": ^8.3.0
"@types/verror": ^1.10.4
"@types/vision": ^5
"@types/webpack": 5.28.0
acorn: ^8.0.1
ajv: ^6.12.2
@ -22450,12 +22297,12 @@ fsevents@~2.1.1:
grunt-newer: 1.3.0
hapi-auth-jwt2: ^10.2.0
hapi-error: ^2.3.0
hapi-swagger: 10.3.0
hapi-swagger: ^14.4.0
hkdf: 0.0.2
hot-shots: ^9.0.0
i18n-iso-countries: ^7.4.0
inert: ^5.1.3
ioredis: ^4.28.2
joi: 17.4.0
jose: ^4.8.1
jsdom: ^19.0.0
jsonwebtoken: ^8.5.1
@ -22517,7 +22364,6 @@ fsevents@~2.1.1:
typescript: ^4.5.2
uuid: ^8.3.2
verror: ^1.10.1
vision: ^5.4.4
web-push: 3.4.4
webpack: ^4.43.0
webpack-watch-files-plugin: ^1.2.1
@ -22632,7 +22478,7 @@ fsevents@~2.1.1:
i18n-abide: 0.0.26
install: 0.13.0
intern: ^4.10.1
joi: ^14.3.1
joi: 17.4.0
jquery: 3.6.0
jquery-modal: "https://github.com/mozilla-fxa/jquery-modal.git#0576775d1b4590314b114386019f4c7421c77503"
jquery-simulate: 1.0.2
@ -22811,6 +22657,7 @@ fsevents@~2.1.1:
graphql: ^15.6.1
hot-shots: ^9.0.0
jest: 27.5.1
joi: 17.4.0
jwks-rsa: ^2.1.1
mozlog: ^3.0.2
nock: ^13.2.2
@ -23017,7 +22864,7 @@ fsevents@~2.1.1:
babel-loader: ^8.2.2
browserslist: ^4.7.2
caniuse-lite: ^1.0.30001294
celebrate: ^10.1.0
celebrate: ^15.0.1
classnames: ^2.3.1
convict: ^6.2.2
convict-format-with-moment: ^6.2.0
@ -23044,7 +22891,7 @@ fsevents@~2.1.1:
intl: 1.2.5
intl-pluralrules: ^1.3.1
jest: 27.5.1
joi: ^14.3.1
joi: 17.4.0
jquery-modal: "https://github.com/mozilla-fxa/jquery-modal.git#0576775d1b4590314b114386019f4c7421c77503"
morgan: ^1.10.0
mozlog: ^3.0.2
@ -23099,7 +22946,6 @@ fsevents@~2.1.1:
"@hapi/catbox-redis": ~6.0.2
"@hapi/hapi": ^20.2.1
"@hapi/inert": 6.0.5
"@hapi/joi": ^17.1.1
"@sentry/node": ^6.19.1
"@types/sharp": ^0
audit-filter: ^0.5.0
@ -23116,6 +22962,7 @@ fsevents@~2.1.1:
eslint-plugin-fxa: ^2.0.2
fxa-shared: "workspace:*"
insist: 1.0.1
joi: 17.4.0
lodash: ^4.17.21
mkdirp: 0.5.1
mocha: ^9.1.2
@ -23305,7 +23152,6 @@ fsevents@~2.1.1:
"@types/ioredis": ^4.26.4
"@types/ip": ^1
"@types/jest": ^26.0.23
"@types/joi": ^14.3.4
"@types/js-md5": ^0.4.2
"@types/lodash.omitby": ^4
"@types/lodash.pick": ^4
@ -23323,7 +23169,7 @@ fsevents@~2.1.1:
audit-filter: ^0.5.0
aws-sdk: ^2.1135.0
buf: ^0.1.1
celebrate: ^10.0.1
celebrate: ^15.0.1
chai: ^4.3.6
chance: ^1.1.8
class-transformer: ^0.5.1
@ -23342,7 +23188,7 @@ fsevents@~2.1.1:
ioredis: ^4.28.2
ip: ^1.1.5
jest: 27.5.1
joi: ^14.3.1
joi: 17.4.0
js-md5: ^0.7.3
jsdom: 19.0.0
jsdom-global: 3.0.2
@ -23367,7 +23213,6 @@ fsevents@~2.1.1:
ts-jest: ^27.1.4
ts-loader: ^8.3.0
tsconfig-paths: ^4.0.0
typesafe-joi: ^2.1.0
typesafe-node-firestore: ^1.4.0
typescript: ^4.5.2
underscore: ^1.13.1
@ -23420,7 +23265,6 @@ fsevents@~2.1.1:
supertest: ^6.2.3
ts-jest: ^27.1.4
tslib: ^2.3.1
typesafe-joi: ^2.1.0
typescript: ^4.5.2
languageName: unknown
linkType: soft
@ -24857,7 +24701,7 @@ fsevents@~2.1.1:
languageName: node
linkType: hard
"handlebars@npm:4.7.7, handlebars@npm:^4.1.0, handlebars@npm:^4.3.3, handlebars@npm:^4.7.7":
"handlebars@npm:4.7.7, handlebars@npm:^4.1.0, handlebars@npm:^4.7.7":
version: 4.7.7
resolution: "handlebars@npm:4.7.7"
dependencies:
@ -24895,21 +24739,21 @@ fsevents@~2.1.1:
languageName: node
linkType: hard
"hapi-swagger@npm:10.3.0":
version: 10.3.0
resolution: "hapi-swagger@npm:10.3.0"
"hapi-swagger@npm:^14.4.0":
version: 14.5.1
resolution: "hapi-swagger@npm:14.5.1"
dependencies:
"@hapi/boom": ^7.1.1
"@hapi/hoek": ^6.1.2
"@hapi/joi": ^15.0.1
handlebars: ^4.3.3
http-status: ^1.0.1
"@hapi/boom": ^9.1.4
"@hapi/hoek": ^9.3.0
handlebars: ^4.7.7
http-status: ^1.5.2
json-schema-ref-parser: ^6.1.0
swagger-parser: 4.0.2
swagger-ui-dist: ^3.22.1
swagger-ui-dist: ^4.11.1
peerDependencies:
"@hapi/hapi": ^18.0.0
checksum: b49d22c3640ccb4e86d2397d889212776a68cf4dc6e21c5eefee09e87fd50d47cd31827ba6e7adfe793f59546b97f6dce78918261f764d1e0aafec646e8c0216
"@hapi/hapi": ^20.2.2
joi: 17.x
checksum: 5a9968885d64f50cff138c5fdb718680a330f730e397a9919a01f489d36c86ed443bffb0d025bafd3b6a8af262dd094ad57384e07b3e134e633ea041a0b56f0e
languageName: node
linkType: hard
@ -25785,10 +25629,10 @@ fsevents@~2.1.1:
languageName: node
linkType: hard
"http-status@npm:^1.0.1":
version: 1.5.0
resolution: "http-status@npm:1.5.0"
checksum: d311aacdfb5156fb2c073a00e8e69d5b7adf9995e5f420bc2fb2b57639ea1796343956584607718ba6b1975d38cb6aee829f32d12e26980c36dd7da6046c1aa8
"http-status@npm:^1.5.2":
version: 1.5.2
resolution: "http-status@npm:1.5.2"
checksum: f975c98184d232afb438af86c77e4011a5d161fd490f048992ad19dfa0133ddce169f54d18e226d55b80f39def7008822d993cfbb7f318bab5a49836ec5097e1
languageName: node
linkType: hard
@ -26184,20 +26028,6 @@ fsevents@~2.1.1:
languageName: node
linkType: hard
"inert@npm:^5.1.3":
version: 5.1.3
resolution: "inert@npm:5.1.3"
dependencies:
ammo: 3.x.x
boom: 7.x.x
bounce: 1.x.x
hoek: 6.x.x
joi: 14.x.x
lru-cache: 4.1.x
checksum: 8859c41b3f30b1cc17ab3215810819910dac405508e6202c6d9c436e6c14d3fb4aa1c0b1c0309494b172ba6b9c10140910dbeef28ba1ba09dbbecead9e1a3fc4
languageName: node
linkType: hard
"infer-owner@npm:^1.0.3, infer-owner@npm:^1.0.4":
version: 1.0.4
resolution: "infer-owner@npm:1.0.4"
@ -27576,13 +27406,6 @@ fsevents@~2.1.1:
languageName: node
linkType: hard
"items@npm:2.x.x":
version: 2.1.2
resolution: "items@npm:2.1.2"
checksum: 29730f0085ec4585a51f25655ce9ecff0d444d3fe75bb7963fc6cbe741176d47cb6a4eb18d67019030e67ac8c9b0267579e8cebd5ed4ba50cf5d95b01650d7a0
languageName: node
linkType: hard
"iterall@npm:1.3.0, iterall@npm:^1.1.3, iterall@npm:^1.2.1":
version: 1.3.0
resolution: "iterall@npm:1.3.0"
@ -28809,18 +28632,7 @@ fsevents@~2.1.1:
languageName: node
linkType: hard
"joi@npm:14.x.x, joi@npm:^14.3.1":
version: 14.3.1
resolution: "joi@npm:14.3.1"
dependencies:
hoek: 6.x.x
isemail: 3.x.x
topo: 3.x.x
checksum: 2ae5b54c4f425ed26e1ac937940fb21e8d71a4c3cbaf99d895319d3f99b119fddabc663a4b32926c3911a5c017c1dc5953adc7b43db4d0410d522d717bd80c8d
languageName: node
linkType: hard
"joi@npm:17.x.x":
"joi@npm:17.4.0, joi@npm:17.x.x":
version: 17.4.0
resolution: "joi@npm:17.4.0"
dependencies:
@ -30252,7 +30064,7 @@ fsevents@~2.1.1:
languageName: node
linkType: hard
"lodash.get@npm:4.4.x, lodash.get@npm:^4.0.0, lodash.get@npm:^4.4.2":
"lodash.get@npm:^4.0.0, lodash.get@npm:^4.4.2":
version: 4.4.2
resolution: "lodash.get@npm:4.4.2"
checksum: e403047ddb03181c9d0e92df9556570e2b67e0f0a930fcbbbd779370972368f5568e914f913e93f3b08f6d492abc71e14d4e9b7a18916c31fa04bd2306efe545
@ -30604,7 +30416,7 @@ fsevents@~2.1.1:
languageName: node
linkType: hard
"lru-cache@npm:4.1.x, lru-cache@npm:^4.0.0, lru-cache@npm:^4.1.5":
"lru-cache@npm:^4.0.0, lru-cache@npm:^4.1.5":
version: 4.1.5
resolution: "lru-cache@npm:4.1.5"
dependencies:
@ -41284,10 +41096,10 @@ resolve@^2.0.0-next.3:
languageName: node
linkType: hard
"swagger-ui-dist@npm:^3.22.1":
version: 3.52.5
resolution: "swagger-ui-dist@npm:3.52.5"
checksum: a8bd28bf475c67ad8445a2bb864c4130415d001caeb5db54f0a561785ac9f7df77230a7634520e9c10f25b568035a8f924ecc059945d4caed00a3f2a39ca243c
"swagger-ui-dist@npm:^4.11.1":
version: 4.11.1
resolution: "swagger-ui-dist@npm:4.11.1"
checksum: 678f677f2620f58d868d3f97832d9a9a9402e88f3d513da5749273d78c527cd8825db5d275f6046badf981ddb977b6c8737a384c575bdc8de2a67605ff11822b
languageName: node
linkType: hard
@ -43732,18 +43544,6 @@ resolve@^2.0.0-next.3:
languageName: node
linkType: hard
"vision@npm:^5.4.4":
version: 5.4.4
resolution: "vision@npm:5.4.4"
dependencies:
boom: 7.x.x
hoek: 6.x.x
items: 2.x.x
joi: 14.x.x
checksum: a48c6a75b2426d66e3f3b68d4586a9291fb24f28640033209780a4929144eee4abfb3e40acaaa3cc35760337bca22896d640cda699ceda5d31fc8f7fe30915e0
languageName: node
linkType: hard
"vizion@npm:~2.2.1":
version: 2.2.1
resolution: "vizion@npm:2.2.1"