From c00434bc405d62c3ea41c1fba7431599c6529f0d Mon Sep 17 00:00:00 2001 From: Paul Melnikow Date: Mon, 18 Feb 2019 22:52:20 -0500 Subject: [PATCH] Validate query params in BaseService [npm nodeping mozillaobservatory matrix gitlab f-droid endpoint dynamic bitbucket appveyor] Fix #2676 --- core/base-service/base-svg-scraping.spec.js | 4 ++ core/base-service/base-xml.spec.js | 4 ++ core/base-service/base.js | 63 +++++++++++------ core/base-service/base.spec.js | 70 ++++++++++++++----- core/base-service/route.js | 12 ++++ core/base-service/route.spec.js | 23 +++++- services/appveyor/appveyor-tests.service.js | 15 ++-- services/appveyor/appveyor-tests.tester.js | 9 +-- .../bitbucket-pull-request.service.js | 8 ++- services/dynamic/dynamic-helpers.js | 16 ++--- services/dynamic/dynamic-json.service.js | 11 +-- services/dynamic/dynamic-xml.service.js | 11 +-- services/dynamic/dynamic-yaml.service.js | 11 +-- services/endpoint/endpoint.service.js | 9 +-- services/f-droid/f-droid.service.js | 24 +++---- .../gitlab/gitlab-pipeline-status.service.js | 10 +-- services/matrix/matrix.service.js | 8 +-- .../mozilla-observatory.service.js | 10 ++- services/nodeping/nodeping-status.service.js | 29 ++++---- services/npm/npm-base.js | 9 ++- 20 files changed, 218 insertions(+), 138 deletions(-) diff --git a/core/base-service/base-svg-scraping.spec.js b/core/base-service/base-svg-scraping.spec.js index 2cc8af5c2f8ce..15548e0ba6c90 100644 --- a/core/base-service/base-svg-scraping.spec.js +++ b/core/base-service/base-svg-scraping.spec.js @@ -124,6 +124,10 @@ describe('BaseSvgScrapingService', function() { it('allows overriding the valueMatcher', async function() { class WithValueMatcher extends BaseSvgScrapingService { + static get route() { + return {} + } + async handle() { return this._requestSvg({ schema, diff --git a/core/base-service/base-xml.spec.js b/core/base-service/base-xml.spec.js index 05ac79b59c711..aeba56ee69b64 100644 --- a/core/base-service/base-xml.spec.js +++ b/core/base-service/base-xml.spec.js @@ -56,6 +56,10 @@ describe('BaseXmlService', function() { it('forwards options to _sendAndCacheRequest', async function() { class WithCustomOptions extends BaseXmlService { + static get route() { + return {} + } + async handle() { const { requiredString } = await this._requestXml({ schema: dummySchema, diff --git a/core/base-service/base.js b/core/base-service/base.js index b08b1a3bcb130..5398a1caafac9 100644 --- a/core/base-service/base.js +++ b/core/base-service/base.js @@ -18,6 +18,7 @@ const { assertValidRoute, prepareRoute, namedParamsForMatch, + getQueryParamNames, } = require('./route') const { assertValidServiceDefinition } = require('./service-definitions') const trace = require('./trace') @@ -174,7 +175,8 @@ module.exports = class BaseService { let base, format, pattern, queryParams try { - ;({ base, format, pattern, query: queryParams = [] } = this.route) + ;({ base, format, pattern } = this.route) + queryParams = getQueryParamNames(this.route) } catch (e) { // Legacy services do not have a route. } @@ -270,12 +272,44 @@ module.exports = class BaseService { const serviceInstance = new this(context, config) + let serviceError + const { queryParamSchema } = this.route + if (queryParamSchema) { + try { + queryParams = validate( + { + ErrorClass: InvalidParameter, + prettyErrorMessage: 'invalid query parameter', + includeKeys: true, + traceErrorMessage: 'Query params did not match schema', + traceSuccessMessage: 'Query params after validation', + }, + queryParams, + queryParamSchema + ) + trace.logTrace( + 'inbound', + emojic.crayon, + 'Query params after validation', + queryParams + ) + } catch (error) { + serviceError = error + } + } + let serviceData - try { - serviceData = await serviceInstance.handle(namedParams, queryParams) - Joi.assert(serviceData, serviceDataSchema) - } catch (error) { - serviceData = serviceInstance._handleError(error) + if (!serviceError) { + try { + serviceData = await serviceInstance.handle(namedParams, queryParams) + Joi.assert(serviceData, serviceDataSchema) + } catch (error) { + serviceError = error + } + } + + if (serviceError) { + serviceData = serviceInstance._handleError(serviceError) } trace.logTrace('outbound', emojic.shield, 'Service data', serviceData) @@ -286,11 +320,12 @@ module.exports = class BaseService { static register({ camp, handleRequest, githubApiProvider }, serviceConfig) { const { cacheHeaders: cacheHeaderConfig, fetchLimitBytes } = serviceConfig const { regex, captureNames } = prepareRoute(this.route) + const queryParams = getQueryParamNames(this.route) camp.route( regex, handleRequest(cacheHeaderConfig, { - queryParams: this.route.queryParams, + queryParams, handler: async (queryParams, match, sendBadge, request) => { const namedParams = namedParamsForMatch(captureNames, match, this) const serviceData = await this.invoke( @@ -343,20 +378,6 @@ module.exports = class BaseService { ) } - static _validateQueryParams(queryParams, queryParamSchema) { - return validate( - { - ErrorClass: InvalidParameter, - prettyErrorMessage: 'invalid query parameter', - includeKeys: true, - traceErrorMessage: 'Query params did not match schema', - traceSuccessMessage: 'Query params after validation', - }, - queryParams, - queryParamSchema - ) - } - async _request({ url, options = {}, errorMessages = {} }) { const logTrace = (...args) => trace.logTrace('fetch', ...args) logTrace(emojic.bowAndArrow, 'Request', url, '\n', options) diff --git a/core/base-service/base.spec.js b/core/base-service/base.spec.js index a3e4b7fe26201..8bbd754425f2e 100644 --- a/core/base-service/base.spec.js +++ b/core/base-service/base.spec.js @@ -16,6 +16,15 @@ const BaseService = require('./base') require('../register-chai-plugins.spec') +const queryParamSchema = Joi.object({ + queryParamA: Joi.string(), +}) + .rename('legacyQueryParamA', 'queryParamA', { + ignoreUndefined: true, + override: true, + }) + .required() + class DummyService extends BaseService { static render({ namedParamA, queryParamA }) { return { @@ -62,7 +71,7 @@ class DummyService extends BaseService { return { base: 'foo', pattern: ':namedParamA', - queryParams: ['queryParamA'], + queryParamSchema, } } } @@ -83,6 +92,21 @@ describe('BaseService', function() { }) }) + it('Validates query params', async function() { + expect( + await DummyService.invoke( + {}, + defaultConfig, + { namedParamA: 'bar.bar.bar' }, + { queryParamA: ['foo', 'bar'] } + ) + ).to.deep.equal({ + color: 'red', + isError: true, + message: 'invalid query parameter: queryParamA', + }) + }) + describe('Required overrides', function() { it('Should throw if render() is not overridden', function() { expect(() => BaseService.render()).to.throw( @@ -90,12 +114,26 @@ describe('BaseService', function() { ) }) - it('Should throw if handle() is not overridden', async function() { + it('Should throw if route is not overridden', async function() { try { await BaseService.invoke({}, {}, {}) expect.fail('Expected to throw') } catch (e) { - expect(e.message).to.equal('Handler not implemented for BaseService') + expect(e.message).to.equal('Route not defined for BaseService') + } + }) + + class WithRoute extends BaseService { + static get route() { + return {} + } + } + it('Should throw if handle() is not overridden', async function() { + try { + await WithRoute.invoke({}, {}, {}) + expect.fail('Expected to throw') + } catch (e) { + expect(e.message).to.equal('Handler not implemented for WithRoute') } }) @@ -139,7 +177,7 @@ describe('BaseService', function() { expect(trace.logTrace).to.be.calledWith( 'inbound', sinon.match.string, - 'Query params', + 'Query params after validation', { queryParamA: '!' } ) }) @@ -293,7 +331,15 @@ describe('BaseService', function() { it('handles the request', async function() { expect(mockHandleRequest).to.have.been.calledOnce - const { handler: requestHandler } = mockHandleRequest.getCall(0).args[1] + + const { + queryParams: serviceQueryParams, + handler: requestHandler, + } = mockHandleRequest.getCall(0).args[1] + expect(serviceQueryParams).to.deep.equal([ + 'queryParamA', + 'legacyQueryParamA', + ]) const mockSendBadge = sinon.spy() const mockRequest = { @@ -340,7 +386,7 @@ describe('BaseService', function() { isDeprecated: false, route: { pattern: '/foo/:namedParamA', - queryParams: [], + queryParams: ['queryParamA', 'legacyQueryParamA'], }, }) @@ -415,18 +461,6 @@ describe('BaseService', function() { expect(e).to.be.an.instanceof(InvalidResponse) } }) - - it('throws error for invalid query params', async function() { - try { - DummyService._validateQueryParams( - { requiredString: ['this', "shouldn't", 'work'] }, - dummySchema - ) - expect.fail('Expected to throw') - } catch (e) { - expect(e).to.be.an.instanceof(InvalidParameter) - } - }) }) describe('request', function() { diff --git a/core/base-service/route.js b/core/base-service/route.js index c154db4cdb645..711d7d5ca426b 100644 --- a/core/base-service/route.js +++ b/core/base-service/route.js @@ -15,9 +15,11 @@ const routeSchema = Joi.object({ is: Joi.string().required(), then: Joi.array().items(Joi.string().required()), }), + queryParamSchema: Joi.object().schema(), queryParams: Joi.array().items(Joi.string().required()), }) .xor('pattern', 'format') + .oxor('queryParamSchema', 'queryParams') .required() function assertValidRoute(route, message = undefined) { @@ -67,9 +69,19 @@ function namedParamsForMatch(captureNames = [], match, ServiceClass) { return result } +function getQueryParamNames({ queryParams = [], queryParamSchema }) { + if (queryParamSchema) { + const { children, renames = [] } = Joi.describe(queryParamSchema) + return Object.keys(children).concat(renames.map(({ from }) => from)) + } else { + return queryParams + } +} + module.exports = { makeFullUrl, assertValidRoute, prepareRoute, namedParamsForMatch, + getQueryParamNames, } diff --git a/core/base-service/route.spec.js b/core/base-service/route.spec.js index 9dadb0a196a30..86607976cf715 100644 --- a/core/base-service/route.spec.js +++ b/core/base-service/route.spec.js @@ -1,8 +1,13 @@ 'use strict' const { expect } = require('chai') +const Joi = require('joi') const { test, given, forCases } = require('sazerac') -const { prepareRoute, namedParamsForMatch } = require('./route') +const { + prepareRoute, + namedParamsForMatch, + getQueryParamNames, +} = require('./route') describe('Route helpers', function() { context('A `pattern` with a named param is declared', function() { @@ -101,4 +106,20 @@ describe('Route helpers', function() { 'Service MyService declares incorrect number of named params (expected 2, got 1)' ) }) + + it('getQueryParamNames', function() { + expect(getQueryParamNames({ queryParams: ['foo'] })).to.deep.equal(['foo']) + expect( + getQueryParamNames({ + queryParamSchema: Joi.object({ foo: Joi.string() }).required(), + }) + ).to.deep.equal(['foo']) + expect( + getQueryParamNames({ + queryParamSchema: Joi.object({ foo: Joi.string() }) + .rename('bar', 'foo', { ignoreUndefined: true, override: true }) + .required(), + }) + ).to.deep.equal(['foo', 'bar']) + }) }) diff --git a/services/appveyor/appveyor-tests.service.js b/services/appveyor/appveyor-tests.service.js index af16b606086f0..2b7f5fe740ed2 100644 --- a/services/appveyor/appveyor-tests.service.js +++ b/services/appveyor/appveyor-tests.service.js @@ -1,5 +1,6 @@ 'use strict' +const Joi = require('joi') const { renderTestResultBadge } = require('../../lib/text-formatters') const AppVeyorBase = require('./appveyor-base') @@ -19,16 +20,18 @@ const documentation = `

` +const queryParamSchema = Joi.object({ + compact_message: Joi.equal(''), + passed_label: Joi.string(), + failed_label: Joi.string(), + skipped_label: Joi.string(), +}).required() + module.exports = class AppVeyorTests extends AppVeyorBase { static get route() { return { ...this.buildRoute('appveyor/tests'), - queryParams: [ - 'compact_message', - 'passed_label', - 'failed_label', - 'skipped_label', - ], + queryParamSchema, } } diff --git a/services/appveyor/appveyor-tests.tester.js b/services/appveyor/appveyor-tests.tester.js index cfd2fbb6b111a..25acacd4a3ba4 100644 --- a/services/appveyor/appveyor-tests.tester.js +++ b/services/appveyor/appveyor-tests.tester.js @@ -1,6 +1,7 @@ 'use strict' const Joi = require('joi') +const queryString = require('querystring') const isAppveyorTestTotals = Joi.string().regex( /^[0-9]+ passed(, [0-9]+ failed)?(, [0-9]+ skipped)?$/ @@ -56,14 +57,14 @@ t.create('Test status with custom labels') t.create('Test status with compact message and custom labels') .timeout(10000) - .get('/NZSmartie/coap-net-iu0to.json', { - qs: { + .get( + `/NZSmartie/coap-net-iu0to.json?${queryString.stringify({ compact_message: null, passed_label: '💃', failed_label: '🤦‍♀️', skipped_label: '🤷', - }, - }) + })}` + ) .expectJSONTypes( Joi.object().keys({ name: 'tests', diff --git a/services/bitbucket/bitbucket-pull-request.service.js b/services/bitbucket/bitbucket-pull-request.service.js index 496983e5bf8b3..f7561ca85a74f 100644 --- a/services/bitbucket/bitbucket-pull-request.service.js +++ b/services/bitbucket/bitbucket-pull-request.service.js @@ -3,13 +3,17 @@ const Joi = require('joi') const serverSecrets = require('../../lib/server-secrets') const { metric } = require('../../lib/text-formatters') -const { nonNegativeInteger } = require('../validators') +const { nonNegativeInteger, optionalUrl } = require('../validators') const { BaseJsonService } = require('..') const bitbucketPullRequestSchema = Joi.object({ size: nonNegativeInteger, }).required() +const queryParamSchema = Joi.object({ + server: optionalUrl, +}).required() + function pullRequestClassGenerator(raw) { const routePrefix = raw ? 'pr-raw' : 'pr' const badgeSuffix = raw ? '' : ' open' @@ -98,7 +102,7 @@ function pullRequestClassGenerator(raw) { return { base: `bitbucket/${routePrefix}`, pattern: `:user/:repo`, - queryParams: ['server'], + queryParamSchema, } } diff --git a/services/dynamic/dynamic-helpers.js b/services/dynamic/dynamic-helpers.js index e0ab46a2b3df7..d68ccaf487e80 100644 --- a/services/dynamic/dynamic-helpers.js +++ b/services/dynamic/dynamic-helpers.js @@ -3,14 +3,6 @@ const Joi = require('joi') const { optionalUrl } = require('../validators') -function createRoute(which) { - return { - base: `badge/dynamic/${which}`, - pattern: '', - queryParams: ['uri', 'url', 'query', 'prefix', 'suffix'], - } -} - const queryParamSchema = Joi.object({ url: optionalUrl.required(), query: Joi.string().required(), @@ -20,6 +12,14 @@ const queryParamSchema = Joi.object({ .rename('uri', 'url', { ignoreUndefined: true, override: true }) .required() +function createRoute(which) { + return { + base: `badge/dynamic/${which}`, + pattern: '', + queryParamSchema, + } +} + module.exports = { createRoute, queryParamSchema, diff --git a/services/dynamic/dynamic-json.service.js b/services/dynamic/dynamic-json.service.js index 55fa9819ec852..abce5fc774d0b 100644 --- a/services/dynamic/dynamic-json.service.js +++ b/services/dynamic/dynamic-json.service.js @@ -4,7 +4,7 @@ const Joi = require('joi') const jp = require('jsonpath') const { BaseJsonService, InvalidResponse } = require('..') const { renderDynamicBadge, errorMessages } = require('../dynamic-common') -const { createRoute, queryParamSchema } = require('./dynamic-helpers') +const { createRoute } = require('./dynamic-helpers') module.exports = class DynamicJson extends BaseJsonService { static get category() { @@ -21,14 +21,7 @@ module.exports = class DynamicJson extends BaseJsonService { } } - async handle(namedParams, queryParams) { - const { - url, - query: pathExpression, - prefix, - suffix, - } = this.constructor._validateQueryParams(queryParams, queryParamSchema) - + async handle(namedParams, { url, query: pathExpression, prefix, suffix }) { const data = await this._requestJson({ schema: Joi.any(), url, diff --git a/services/dynamic/dynamic-xml.service.js b/services/dynamic/dynamic-xml.service.js index 1bb2edc65ffd4..189532e774f31 100644 --- a/services/dynamic/dynamic-xml.service.js +++ b/services/dynamic/dynamic-xml.service.js @@ -4,7 +4,7 @@ const { DOMParser } = require('xmldom') const xpath = require('xpath') const { BaseService, InvalidResponse } = require('..') const { renderDynamicBadge, errorMessages } = require('../dynamic-common') -const { createRoute, queryParamSchema } = require('./dynamic-helpers') +const { createRoute } = require('./dynamic-helpers') // This service extends BaseService because it uses a different XML parser // than BaseXmlService which can be used with xpath. @@ -27,14 +27,7 @@ module.exports = class DynamicXml extends BaseService { } } - async handle(namedParams, queryParams) { - const { - url, - query: pathExpression, - prefix, - suffix, - } = this.constructor._validateQueryParams(queryParams, queryParamSchema) - + async handle(namedParams, { url, query: pathExpression, prefix, suffix }) { const pathIsAttr = pathExpression.includes('@') const { buffer } = await this._request({ diff --git a/services/dynamic/dynamic-yaml.service.js b/services/dynamic/dynamic-yaml.service.js index 6ccc56c4edbdd..800467f29a010 100644 --- a/services/dynamic/dynamic-yaml.service.js +++ b/services/dynamic/dynamic-yaml.service.js @@ -4,7 +4,7 @@ const Joi = require('joi') const jp = require('jsonpath') const { BaseYamlService, InvalidResponse } = require('..') const { renderDynamicBadge, errorMessages } = require('../dynamic-common') -const { createRoute, queryParamSchema } = require('./dynamic-helpers') +const { createRoute } = require('./dynamic-helpers') module.exports = class DynamicYaml extends BaseYamlService { static get category() { @@ -21,14 +21,7 @@ module.exports = class DynamicYaml extends BaseYamlService { } } - async handle(namedParams, queryParams) { - const { - url, - query: pathExpression, - prefix, - suffix, - } = this.constructor._validateQueryParams(queryParams, queryParamSchema) - + async handle(namedParams, { url, query: pathExpression, prefix, suffix }) { const data = await this._requestYaml({ schema: Joi.any(), url, diff --git a/services/endpoint/endpoint.service.js b/services/endpoint/endpoint.service.js index 083bc7ef65cf4..79ff34ff11fca 100644 --- a/services/endpoint/endpoint.service.js +++ b/services/endpoint/endpoint.service.js @@ -22,7 +22,7 @@ module.exports = class Endpoint extends BaseJsonService { return { base: 'badge/endpoint', pattern: '', - queryParams: ['url'], + queryParamSchema, } } @@ -66,12 +66,7 @@ module.exports = class Endpoint extends BaseJsonService { } } - async handle(namedParams, queryParams) { - const { url } = this.constructor._validateQueryParams( - queryParams, - queryParamSchema - ) - + async handle(namedParams, { url }) { const { protocol, hostname } = new URL(url) if (protocol !== 'https:') { throw new InvalidParameter({ prettyMessage: 'please use https' }) diff --git a/services/f-droid/f-droid.service.js b/services/f-droid/f-droid.service.js index 046bf92df9eab..3f2b6279c06a2 100644 --- a/services/f-droid/f-droid.service.js +++ b/services/f-droid/f-droid.service.js @@ -11,6 +11,10 @@ const schema = Joi.object({ .required(), }).required() +const queryParamSchema = Joi.object({ + metadata_format: Joi.string().valid(['yml', 'txt']), +}).required() + module.exports = class FDroid extends BaseYamlService { static render({ version }) { return { @@ -19,9 +23,7 @@ module.exports = class FDroid extends BaseYamlService { } } - async handle({ appId }, queryParams) { - const constructor = this.constructor - const { metadata_format: format } = constructor.validateParams(queryParams) + async handle({ appId }, { metadata_format: metadataFormat }) { const url = `https://gitlab.com/fdroid/fdroiddata/raw/master/metadata/${appId}` const fetchOpts = { options: {}, @@ -29,7 +31,7 @@ module.exports = class FDroid extends BaseYamlService { 404: 'app not found', }, } - const fetch = format === 'yml' ? this.fetchYaml : this.fetchText + const fetch = metadataFormat === 'yml' ? this.fetchYaml : this.fetchText let result try { @@ -38,14 +40,14 @@ module.exports = class FDroid extends BaseYamlService { // on f-droid, so if txt is not found we look for yml as the fallback result = await fetch.call(this, url, fetchOpts) } catch (error) { - if (format) { + if (metadataFormat) { // if the format was specified it doesn't make the fallback request throw error } result = await this.fetchYaml(url, fetchOpts) } - return constructor.render(result) + return this.constructor.render(result) } async fetchYaml(url, options) { @@ -82,14 +84,6 @@ module.exports = class FDroid extends BaseYamlService { return { version: match[1] } } - static validateParams(queryParams) { - const queryParamsSchema = Joi.object({ - metadata_format: Joi.string().valid(['yml', 'txt']), - }).required() - - return this._validateQueryParams(queryParams, queryParamsSchema) - } - // Metadata static get defaultBadgeData() { return { label: 'f-droid' } @@ -103,7 +97,7 @@ module.exports = class FDroid extends BaseYamlService { return { base: 'f-droid/v', pattern: ':appId', - queryParams: ['metadata_format'], + queryParamSchema, } } diff --git a/services/gitlab/gitlab-pipeline-status.service.js b/services/gitlab/gitlab-pipeline-status.service.js index b1686e75cc2fd..91dedb22c0311 100644 --- a/services/gitlab/gitlab-pipeline-status.service.js +++ b/services/gitlab/gitlab-pipeline-status.service.js @@ -27,7 +27,7 @@ module.exports = class GitlabPipelineStatus extends BaseSvgScrapingService { return { base: 'gitlab/pipeline', pattern: ':user/:repo/:branch*', - queryParams: ['gitlab_url'], + queryParamSchema, } } @@ -63,10 +63,10 @@ module.exports = class GitlabPipelineStatus extends BaseSvgScrapingService { return renderBuildStatusBadge({ status }) } - async handle({ user, repo, branch = 'master' }, queryParams) { - const { - gitlab_url: baseUrl = 'https://gitlab.com', - } = this.constructor._validateQueryParams(queryParams, queryParamSchema) + async handle( + { user, repo, branch = 'master' }, + { gitlab_url: baseUrl = 'https://gitlab.com' } + ) { const { message: status } = await this._requestSvg({ schema: badgeSchema, url: `${baseUrl}/${user}/${repo}/badges/${branch}/pipeline.svg`, diff --git a/services/matrix/matrix.service.js b/services/matrix/matrix.service.js index 17b10bf75c60a..8e910710a73be 100644 --- a/services/matrix/matrix.service.js +++ b/services/matrix/matrix.service.js @@ -170,11 +170,7 @@ module.exports = class Matrix extends BaseJsonService { } } - async handle({ roomAlias }, queryParams) { - const { server_fqdn: serverFQDN } = this.constructor._validateQueryParams( - queryParams, - queryParamSchema - ) + async handle({ roomAlias }, { server_fqdn: serverFQDN }) { const members = await this.fetch({ roomAlias, serverFQDN }) return this.constructor.render({ members }) } @@ -191,7 +187,7 @@ module.exports = class Matrix extends BaseJsonService { return { base: 'matrix', pattern: ':roomAlias', - queryParams: ['server_fqdn'], + queryParamSchema, } } diff --git a/services/mozilla-observatory/mozilla-observatory.service.js b/services/mozilla-observatory/mozilla-observatory.service.js index f9e0d281157e0..88610e398491f 100644 --- a/services/mozilla-observatory/mozilla-observatory.service.js +++ b/services/mozilla-observatory/mozilla-observatory.service.js @@ -1,8 +1,8 @@ 'use strict' +const Joi = require('joi') const { BaseJsonService } = require('..') -const Joi = require('joi') const schema = Joi.object({ state: Joi.string() .valid('ABORTED', 'FAILED', 'FINISHED', 'PENDING', 'STARTING', 'RUNNING') @@ -26,6 +26,10 @@ const schema = Joi.object({ .required(), }).required() +const queryParamSchema = Joi.object({ + publish: Joi.equal(''), +}).required() + const documentation = `

The Mozilla HTTP Observatory @@ -35,7 +39,7 @@ const documentation = `

By default the scan result is hidden from the public result list. You can activate the publication of the scan result - by setting publish parameter to true + by setting the publish parameter.

The badge returns a cached site result if the site has been scanned anytime in the previous 24 hours. @@ -55,7 +59,7 @@ module.exports = class MozillaObservatory extends BaseJsonService { return { base: 'mozilla-observatory', pattern: ':which(grade|grade-score)/:host', - queryParams: ['publish'], + queryParamSchema, } } diff --git a/services/nodeping/nodeping-status.service.js b/services/nodeping/nodeping-status.service.js index 987e3fb6dc55e..6c55ee3c7a553 100644 --- a/services/nodeping/nodeping-status.service.js +++ b/services/nodeping/nodeping-status.service.js @@ -3,18 +3,23 @@ const { BaseJsonService } = require('..') const Joi = require('joi') -const rowSchema = Joi.object().keys({ su: Joi.boolean() }) - const schema = Joi.array() - .items(rowSchema) + .items(Joi.object().keys({ su: Joi.boolean() })) .min(1) +const queryParamSchema = Joi.object({ + up_message: Joi.string(), + down_message: Joi.string(), + up_color: Joi.alternatives(Joi.string(), Joi.number()), + down_color: Joi.alternatives(Joi.string(), Joi.number()), +}).required() + /* * this is the checkUuid for the NodePing.com (as used on the [example page](https://nodeping.com/reporting.html#results)) */ -const sampleCheckUuid = 'jkiwn052-ntpp-4lbb-8d45-ihew6d9ucoei' +const exampleCheckUuid = 'jkiwn052-ntpp-4lbb-8d45-ihew6d9ucoei' -class NodePingStatus extends BaseJsonService { +module.exports = class NodePingStatus extends BaseJsonService { static get category() { return 'monitoring' } @@ -29,7 +34,7 @@ class NodePingStatus extends BaseJsonService { return { base: 'nodeping/status', pattern: ':checkUuid', - queryParams: ['up_message', 'down_message', 'up_color', 'down_color'], + queryParamSchema, } } @@ -38,24 +43,24 @@ class NodePingStatus extends BaseJsonService { { title: 'NodePing status', namedParams: { - checkUuid: sampleCheckUuid, + checkUuid: exampleCheckUuid, }, staticPreview: this.render({ status: true }), }, { title: 'NodePing status (customized)', namedParams: { - checkUuid: sampleCheckUuid, + checkUuid: exampleCheckUuid, }, queryParams: { - up_message: 'Online', + up_message: 'online', up_color: 'blue', - down_message: 'Offline', + down_message: 'offline', down_color: 'lightgrey', }, staticPreview: this.render({ status: true, - upMessage: 'Online', + upMessage: 'online', upColor: 'blue', }), }, @@ -101,5 +106,3 @@ class NodePingStatus extends BaseJsonService { : { message: downMessage || 'down', color: downColor || 'red' } } } - -module.exports = NodePingStatus diff --git a/services/npm/npm-base.js b/services/npm/npm-base.js index a6553a143fd67..326455f465f65 100644 --- a/services/npm/npm-base.js +++ b/services/npm/npm-base.js @@ -3,6 +3,7 @@ const Joi = require('joi') const serverSecrets = require('../../lib/server-secrets') const { BaseJsonService, InvalidResponse, NotFound } = require('..') +const { optionalUrl } = require('../validators') const { isDependencyMap } = require('../package-json-helpers') const deprecatedLicenseObjectSchema = Joi.object({ @@ -30,6 +31,10 @@ const packageDataSchema = Joi.object({ .default([]), }).required() +const queryParamSchema = Joi.object({ + registry_uri: optionalUrl, +}).required() + // Abstract class for NPM badges which display data about the latest version // of a package. module.exports = class NpmBase extends BaseJsonService { @@ -38,13 +43,13 @@ module.exports = class NpmBase extends BaseJsonService { return { base, pattern: ':scope(@[^/]+)?/:packageName/:tag?', - queryParams: ['registry_uri'], + queryParamSchema, } } else { return { base, pattern: ':scope(@[^/]+)?/:packageName', - queryParams: ['registry_uri'], + queryParamSchema, } } }