diff --git a/packages/phone-number-privacy/combiner/src/common/error.ts b/packages/phone-number-privacy/combiner/src/common/error.ts new file mode 100644 index 00000000000..91060f23691 --- /dev/null +++ b/packages/phone-number-privacy/combiner/src/common/error.ts @@ -0,0 +1,19 @@ +import { ErrorType } from '@celo/phone-number-privacy-common' + +export class OdisError extends Error { + constructor(readonly code: ErrorType, readonly parent?: Error, readonly status: number = 500) { + // This is necessary when extending Error Classes + super(code) // 'Error' breaks prototype chain here + Object.setPrototypeOf(this, new.target.prototype) // restore prototype chain + } +} + +export function wrapError( + valueOrError: Promise, + code: ErrorType, + status: number = 500 +): Promise { + return valueOrError.catch((parentErr) => { + throw new OdisError(code, parentErr, status) + }) +} diff --git a/packages/phone-number-privacy/combiner/src/common/handlers.ts b/packages/phone-number-privacy/combiner/src/common/handlers.ts index ac0872993bf..a8323540fcd 100644 --- a/packages/phone-number-privacy/combiner/src/common/handlers.ts +++ b/packages/phone-number-privacy/combiner/src/common/handlers.ts @@ -1,13 +1,23 @@ import { ErrorMessage, + ErrorType, OdisRequest, OdisResponse, + PnpQuotaStatus, + send, + // tslint:disable-next-line: ordered-imports + SequentialDelayDomainState, WarningMessage, } from '@celo/phone-number-privacy-common' +import opentelemetry, { SpanStatusCode } from '@opentelemetry/api' +import { SemanticAttributes } from '@opentelemetry/semantic-conventions' import Logger from 'bunyan' import { Request, Response } from 'express' import { performance, PerformanceObserver } from 'perf_hooks' -import { sendFailure } from './io' +import { getCombinerVersion } from '../config' +import { OdisError } from './error' + +const tracer = opentelemetry.trace.getTracer('combiner-tracer') export interface Locals { logger: Logger @@ -18,24 +28,22 @@ export type PromiseHandler = ( res: Response, Locals> ) => Promise -type ParentHandler = (req: Request<{}, {}, any>, res: Response) => Promise - export function catchErrorHandler( handler: PromiseHandler -): ParentHandler { +): PromiseHandler { return async (req, res) => { - const logger: Logger = res.locals.logger try { await handler(req, res) } catch (err) { + const logger: Logger = res.locals.logger logger.error(ErrorMessage.CAUGHT_ERROR_IN_ENDPOINT_HANDLER) logger.error(err) if (!res.headersSent) { - logger.info('Responding with error in outer endpoint handler') - res.status(500).json({ - success: false, - error: ErrorMessage.UNKNOWN_ERROR, - }) + if (err instanceof OdisError) { + sendFailure(err.code, err.status, res, req.url) + } else { + sendFailure(ErrorMessage.UNKNOWN_ERROR, 500, res, req.url) + } } else { logger.error(ErrorMessage.ERROR_AFTER_RESPONSE_SENT) } @@ -43,6 +51,39 @@ export function catchErrorHandler( } } +export function tracingHandler( + handler: PromiseHandler +): PromiseHandler { + return async (req, res) => { + return tracer.startActiveSpan( + req.url, + { + attributes: { + [SemanticAttributes.HTTP_ROUTE]: req.path, + [SemanticAttributes.HTTP_METHOD]: req.method, + [SemanticAttributes.HTTP_CLIENT_IP]: req.ip, + }, + }, + async (span) => { + try { + await handler(req, res) + span.setStatus({ + code: SpanStatusCode.OK, + }) + } catch (err: any) { + span.setStatus({ + code: SpanStatusCode.ERROR, + message: err instanceof Error ? err.message : 'Fail', + }) + throw err + } finally { + span.end() + } + } + ) + } +} + export function meteringHandler( handler: PromiseHandler ): PromiseHandler { @@ -86,9 +127,85 @@ export function meteringHandler( } } +export function timeoutHandler( + timeoutMs: number, + handler: PromiseHandler +): PromiseHandler { + return async (req, res) => { + const timeoutSignal = (AbortSignal as any).timeout(timeoutMs) + timeoutSignal.addEventListener( + 'abort', + () => { + if (!res.headersSent) { + sendFailure(ErrorMessage.TIMEOUT_FROM_SIGNER, 500, res, req.url) + } + }, + { once: true } + ) + + await handler(req, res) + } +} + export async function disabledHandler( - _: Request<{}, {}, R>, + req: Request<{}, {}, R>, response: Response, Locals> ): Promise { - sendFailure(WarningMessage.API_UNAVAILABLE, 503, response) + sendFailure(WarningMessage.API_UNAVAILABLE, 503, response, req.url) +} + +export function sendFailure( + error: ErrorType, + status: number, + response: Response, + _endpoint: string, + body?: Record // TODO remove any +) { + send( + response, + { + success: false, + version: getCombinerVersion(), + error, + ...body, + }, + status, + response.locals.logger + ) +} + +export interface Result { + status: number + body: OdisResponse +} + +export type ResultHandler = ( + request: Request<{}, {}, R>, + res: Response, Locals> +) => Promise> + +export function resultHandler( + resHandler: ResultHandler +): PromiseHandler { + return async (req, res) => { + const result = await resHandler(req, res) + send(res, result.body, result.status, res.locals.logger) + } +} + +export function errorResult( + status: number, + error: string, + quotaStatus?: PnpQuotaStatus | { status: SequentialDelayDomainState } +): Result { + // TODO remove any + return { + status, + body: { + success: false, + version: getCombinerVersion(), + error, + ...quotaStatus, + }, + } } diff --git a/packages/phone-number-privacy/combiner/src/common/io.ts b/packages/phone-number-privacy/combiner/src/common/io.ts index c43b81f7066..22dfd6a8089 100644 --- a/packages/phone-number-privacy/combiner/src/common/io.ts +++ b/packages/phone-number-privacy/combiner/src/common/io.ts @@ -1,21 +1,19 @@ import { - ErrorType, getRequestKeyVersion, KEY_VERSION_HEADER, KeyVersionInfo, OdisRequest, OdisResponse, requestHasValidKeyVersion, - send, SignerEndpoint, } from '@celo/phone-number-privacy-common' import Logger from 'bunyan' -import { Request, Response } from 'express' +import { Request } from 'express' import * as http from 'http' import * as https from 'https' import fetch, { Response as FetchResponse } from 'node-fetch' import { performance } from 'perf_hooks' -import { getCombinerVersion, OdisConfig } from '../config' +import { OdisConfig } from '../config' import { isAbortError, Signer } from './combine' const httpAgent = new http.Agent({ keepAlive: true }) @@ -116,16 +114,3 @@ async function measureTime(name: string, fn: () => Promise): Promise { performance.measure(name, start, end) } } - -export function sendFailure(error: ErrorType, status: number, response: Response) { - send( - response, - { - success: false, - version: getCombinerVersion(), - error, - }, - status, - response.locals.logger - ) -} diff --git a/packages/phone-number-privacy/combiner/src/domain/endpoints/disable/action.ts b/packages/phone-number-privacy/combiner/src/domain/endpoints/disable/action.ts index 8427bdee30f..2de518eba55 100644 --- a/packages/phone-number-privacy/combiner/src/domain/endpoints/disable/action.ts +++ b/packages/phone-number-privacy/combiner/src/domain/endpoints/disable/action.ts @@ -6,31 +6,28 @@ import { DomainSchema, ErrorMessage, getSignerEndpoint, - send, SequentialDelayDomainStateSchema, verifyDisableDomainRequestAuthenticity, WarningMessage, } from '@celo/phone-number-privacy-common' import { Signer, thresholdCallToSigners } from '../../../common/combine' -import { PromiseHandler } from '../../../common/handlers' -import { getKeyVersionInfo, sendFailure } from '../../../common/io' +import { errorResult, ResultHandler } from '../../../common/handlers' +import { getKeyVersionInfo } from '../../../common/io' import { getCombinerVersion, OdisConfig } from '../../../config' import { logDomainResponseDiscrepancies } from '../../services/log-responses' import { findThresholdDomainState } from '../../services/threshold-state' -export function createDisableDomainHandler( +export function disableDomain( signers: Signer[], config: OdisConfig -): PromiseHandler { +): ResultHandler { return async (request, response) => { if (!disableDomainRequestSchema(DomainSchema).is(request.body)) { - sendFailure(WarningMessage.INVALID_INPUT, 400, response) - return + return errorResult(400, WarningMessage.INVALID_INPUT) } if (!verifyDisableDomainRequestAuthenticity(request.body)) { - sendFailure(WarningMessage.UNAUTHENTICATED_USER, 401, response) - return + return errorResult(401, WarningMessage.UNAUTHENTICATED_USER) } // TODO remove? @@ -57,18 +54,14 @@ export function createDisableDomainHandler( signers.length ) if (disableDomainStatus.disabled) { - send( - response, - { + return { + status: 200, + body: { success: true, version: getCombinerVersion(), status: disableDomainStatus, }, - 200, - response.locals.logger - ) - - return + } } } catch (err) { response.locals.logger.error( @@ -77,6 +70,6 @@ export function createDisableDomainHandler( ) } - sendFailure(ErrorMessage.THRESHOLD_DISABLE_DOMAIN_FAILURE, maxErrorCode ?? 500, response) + return errorResult(maxErrorCode ?? 500, ErrorMessage.THRESHOLD_DISABLE_DOMAIN_FAILURE) } } diff --git a/packages/phone-number-privacy/combiner/src/domain/endpoints/quota/action.ts b/packages/phone-number-privacy/combiner/src/domain/endpoints/quota/action.ts index 8d80ee871a5..8112f598a25 100644 --- a/packages/phone-number-privacy/combiner/src/domain/endpoints/quota/action.ts +++ b/packages/phone-number-privacy/combiner/src/domain/endpoints/quota/action.ts @@ -6,31 +6,28 @@ import { DomainSchema, ErrorMessage, getSignerEndpoint, - send, SequentialDelayDomainStateSchema, verifyDomainQuotaStatusRequestAuthenticity, WarningMessage, } from '@celo/phone-number-privacy-common' import { Signer, thresholdCallToSigners } from '../../../common/combine' -import { PromiseHandler } from '../../../common/handlers' -import { getKeyVersionInfo, sendFailure } from '../../../common/io' +import { errorResult, ResultHandler } from '../../../common/handlers' +import { getKeyVersionInfo } from '../../../common/io' import { getCombinerVersion, OdisConfig } from '../../../config' import { logDomainResponseDiscrepancies } from '../../services/log-responses' import { findThresholdDomainState } from '../../services/threshold-state' -export function createDomainQuotaHandler( +export function domainQuota( signers: Signer[], config: OdisConfig -): PromiseHandler { +): ResultHandler { return async (request, response) => { if (!domainQuotaStatusRequestSchema(DomainSchema).is(request.body)) { - sendFailure(WarningMessage.INVALID_INPUT, 400, response) - return + return errorResult(400, WarningMessage.INVALID_INPUT) } if (!verifyDomainQuotaStatusRequestAuthenticity(request.body)) { - sendFailure(WarningMessage.UNAUTHENTICATED_USER, 401, response) - return + return errorResult(401, WarningMessage.UNAUTHENTICATED_USER) } // TODO remove? @@ -49,21 +46,18 @@ export function createDomainQuotaHandler( logDomainResponseDiscrepancies(response.locals.logger, signerResponses) if (signerResponses.length >= keyVersionInfo.threshold) { try { - send( - response, - { + return { + status: 200, + body: { success: true, version: getCombinerVersion(), status: findThresholdDomainState(keyVersionInfo, signerResponses, signers.length), }, - 200, - response.locals.logger - ) - return + } } catch (err) { response.locals.logger.error(err, 'Error combining signer quota status responses') } } - sendFailure(ErrorMessage.THRESHOLD_DOMAIN_QUOTA_STATUS_FAILURE, maxErrorCode ?? 500, response) + return errorResult(maxErrorCode ?? 500, ErrorMessage.THRESHOLD_DOMAIN_QUOTA_STATUS_FAILURE) } } diff --git a/packages/phone-number-privacy/combiner/src/domain/endpoints/sign/action.ts b/packages/phone-number-privacy/combiner/src/domain/endpoints/sign/action.ts index fe4ba2c59dc..d696c2f4a06 100644 --- a/packages/phone-number-privacy/combiner/src/domain/endpoints/sign/action.ts +++ b/packages/phone-number-privacy/combiner/src/domain/endpoints/sign/action.ts @@ -8,7 +8,6 @@ import { ErrorType, getSignerEndpoint, OdisResponse, - send, SequentialDelayDomainStateSchema, verifyDomainRestrictedSignatureRequestAuthenticity, WarningMessage, @@ -16,34 +15,31 @@ import { import assert from 'node:assert' import { Signer, thresholdCallToSigners } from '../../../common/combine' import { DomainCryptoClient } from '../../../common/crypto-clients/domain-crypto-client' -import { PromiseHandler } from '../../../common/handlers' -import { getKeyVersionInfo, requestHasSupportedKeyVersion, sendFailure } from '../../../common/io' +import { errorResult, ResultHandler } from '../../../common/handlers' +import { getKeyVersionInfo, requestHasSupportedKeyVersion } from '../../../common/io' import { getCombinerVersion, OdisConfig } from '../../../config' import { logDomainResponseDiscrepancies } from '../../services/log-responses' import { findThresholdDomainState } from '../../services/threshold-state' -export function createDomainSignHandler( +export function domainSign( signers: Signer[], config: OdisConfig -): PromiseHandler { +): ResultHandler { return async (request, response) => { const { logger } = response.locals if (!domainRestrictedSignatureRequestSchema(DomainSchema).is(request.body)) { - sendFailure(WarningMessage.INVALID_INPUT, 400, response) - return + return errorResult(400, WarningMessage.INVALID_INPUT) } if (!requestHasSupportedKeyVersion(request, config, logger)) { - sendFailure(WarningMessage.INVALID_KEY_VERSION_REQUEST, 400, response) - return + return errorResult(400, WarningMessage.INVALID_KEY_VERSION_REQUEST) } // Note that signing requests may include a nonce for replay protection that will be checked by // the signer, but is not checked here. As a result, requests that pass the authentication check // here may still fail when sent to the signer. if (!verifyDomainRestrictedSignatureRequestAuthenticity(request.body)) { - sendFailure(WarningMessage.UNAUTHENTICATED_USER, 401, response) - return + return errorResult(401, WarningMessage.UNAUTHENTICATED_USER) } const keyVersionInfo = getKeyVersionInfo(request, config, logger) @@ -96,17 +92,15 @@ export function createDomainSignHandler( logger ) - return send( - response, - { + return { + status: 200, + body: { success: true, version: getCombinerVersion(), signature: combinedSignature, status: findThresholdDomainState(keyVersionInfo, signerResponses, signers.length), }, - 200, - response.locals.logger - ) + } } catch (err) { // May fail upon combining signatures if too many sigs are invalid logger.error('Combining signatures failed in combine') @@ -117,7 +111,7 @@ export function createDomainSignHandler( const errorCode = maxErrorCode ?? 500 const error = errorCodeToError(errorCode) - sendFailure(error, errorCode, response) + return errorResult(errorCode, error) } } diff --git a/packages/phone-number-privacy/combiner/src/index.ts b/packages/phone-number-privacy/combiner/src/index.ts index 42336c696d9..58d5869bf6b 100644 --- a/packages/phone-number-privacy/combiner/src/index.ts +++ b/packages/phone-number-privacy/combiner/src/index.ts @@ -1,4 +1,3 @@ -import { getContractKitWithAgent } from '@celo/phone-number-privacy-common' import * as functions from 'firebase-functions' import config from './config' import { startCombiner } from './server' @@ -12,5 +11,6 @@ export const combiner = functions // Defined check required for running tests vs. deployment minInstances: functions.config().service ? Number(functions.config().service.min_instances) : 0, }) - .https.onRequest(startCombiner(config, getContractKitWithAgent(config.blockchain))) + .https.onRequest(startCombiner(config)) + export * from './config' diff --git a/packages/phone-number-privacy/combiner/src/pnp/endpoints/quota/action.ts b/packages/phone-number-privacy/combiner/src/pnp/endpoints/quota/action.ts index 8abf37cc48e..64f31e66ba2 100644 --- a/packages/phone-number-privacy/combiner/src/pnp/endpoints/quota/action.ts +++ b/packages/phone-number-privacy/combiner/src/pnp/endpoints/quota/action.ts @@ -9,33 +9,30 @@ import { PnpQuotaRequest, PnpQuotaRequestSchema, PnpQuotaResponseSchema, - send, WarningMessage, } from '@celo/phone-number-privacy-common' import { Request } from 'express' import { Signer, thresholdCallToSigners } from '../../../common/combine' -import { PromiseHandler } from '../../../common/handlers' -import { getKeyVersionInfo, sendFailure } from '../../../common/io' +import { errorResult, ResultHandler } from '../../../common/handlers' +import { getKeyVersionInfo } from '../../../common/io' import { getCombinerVersion, OdisConfig } from '../../../config' import { logPnpSignerResponseDiscrepancies } from '../../services/log-responses' import { findCombinerQuotaState } from '../../services/threshold-state' -export function createPnpQuotaHandler( +export function pnpQuota( signers: Signer[], config: OdisConfig, dekFetcher: DataEncryptionKeyFetcher -): PromiseHandler { +): ResultHandler { return async (request, response) => { const logger = response.locals.logger - if (!validateRequest(request)) { - sendFailure(WarningMessage.INVALID_INPUT, 400, response) - return + if (!isValidRequest(request)) { + return errorResult(400, WarningMessage.INVALID_INPUT) } if (!(await authenticateUser(request, logger, dekFetcher))) { - sendFailure(WarningMessage.UNAUTHENTICATED_USER, 401, response) - return + return errorResult(401, WarningMessage.UNAUTHENTICATED_USER) } // TODO remove this, we shouldn't need keyVersionInfo for non-signing endpoints @@ -57,28 +54,24 @@ export function createPnpQuotaHandler( if (signerResponses.length >= threshold) { try { const quotaStatus = findCombinerQuotaState(keyVersionInfo, signerResponses, warnings) - send( - response, - { + return { + status: 200, + body: { success: true, version: getCombinerVersion(), ...quotaStatus, warnings, }, - 200, - logger - ) - - return + } } catch (err) { logger.error(err, 'Error combining signer quota status responses') } } - sendFailure(ErrorMessage.THRESHOLD_PNP_QUOTA_STATUS_FAILURE, maxErrorCode ?? 500, response) + return errorResult(maxErrorCode ?? 500, ErrorMessage.THRESHOLD_PNP_QUOTA_STATUS_FAILURE) } } -function validateRequest( +function isValidRequest( request: Request<{}, {}, unknown> ): request is Request<{}, {}, PnpQuotaRequest> { return ( diff --git a/packages/phone-number-privacy/combiner/src/pnp/endpoints/sign/action.ts b/packages/phone-number-privacy/combiner/src/pnp/endpoints/sign/action.ts index eb0f9b6f72d..72c97b3a3a1 100644 --- a/packages/phone-number-privacy/combiner/src/pnp/endpoints/sign/action.ts +++ b/packages/phone-number-privacy/combiner/src/pnp/endpoints/sign/action.ts @@ -9,7 +9,6 @@ import { hasValidBlindedPhoneNumberParam, isBodyReasonablySized, OdisResponse, - send, SignMessageRequest, SignMessageRequestSchema, SignMessageResponseSchema, @@ -19,32 +18,29 @@ import { Request } from 'express' import assert from 'node:assert' import { Signer, thresholdCallToSigners } from '../../../common/combine' import { BLSCryptographyClient } from '../../../common/crypto-clients/bls-crypto-client' -import { PromiseHandler } from '../../../common/handlers' -import { getKeyVersionInfo, requestHasSupportedKeyVersion, sendFailure } from '../../../common/io' +import { errorResult, ResultHandler } from '../../../common/handlers' +import { getKeyVersionInfo, requestHasSupportedKeyVersion } from '../../../common/io' import { getCombinerVersion, OdisConfig } from '../../../config' import { logPnpSignerResponseDiscrepancies } from '../../services/log-responses' import { findCombinerQuotaState } from '../../services/threshold-state' -export function createPnpSignHandler( +export function pnpSign( signers: Signer[], config: OdisConfig, dekFetcher: DataEncryptionKeyFetcher -): PromiseHandler { +): ResultHandler { return async (request, response) => { const logger = response.locals.logger - if (!validateRequest(request)) { - sendFailure(WarningMessage.INVALID_INPUT, 400, response) - return + if (!isValidRequest(request)) { + return errorResult(400, WarningMessage.INVALID_INPUT) } if (!requestHasSupportedKeyVersion(request, config, response.locals.logger)) { - sendFailure(WarningMessage.INVALID_KEY_VERSION_REQUEST, 400, response) - return + return errorResult(400, WarningMessage.INVALID_KEY_VERSION_REQUEST) } if (!(await authenticateUser(request, logger, dekFetcher))) { - sendFailure(WarningMessage.UNAUTHENTICATED_USER, 401, response) - return + return errorResult(401, WarningMessage.UNAUTHENTICATED_USER) } const keyVersionInfo = getKeyVersionInfo(request, config, logger) const crypto = new BLSCryptographyClient(keyVersionInfo) @@ -93,18 +89,16 @@ export function createPnpSignHandler( logger ) - return send( - response, - { + return { + status: 200, + body: { success: true, version: getCombinerVersion(), signature: combinedSignature, ...findCombinerQuotaState(keyVersionInfo, signerResponses, warnings), warnings, }, - 200, - logger - ) + } } catch (error) { // May fail upon combining signatures if too many sigs are invalid // Fallback to handleMissingSignatures @@ -114,11 +108,11 @@ export function createPnpSignHandler( const errorCode = maxErrorCode ?? 500 const error = errorCodeToError(errorCode) - sendFailure(error, errorCode, response) + return errorResult(errorCode, error) } } -function validateRequest( +function isValidRequest( request: Request<{}, {}, unknown> ): request is Request<{}, {}, SignMessageRequest> { return ( diff --git a/packages/phone-number-privacy/combiner/src/server.ts b/packages/phone-number-privacy/combiner/src/server.ts index cfb5a24eeed..94ca03ffcf1 100644 --- a/packages/phone-number-privacy/combiner/src/server.ts +++ b/packages/phone-number-privacy/combiner/src/server.ts @@ -1,6 +1,7 @@ import { ContractKit } from '@celo/contractkit' import { CombinerEndpoint, + getContractKitWithAgent, KEY_VERSION_HEADER, loggerMiddleware, newContractKitFetcher, @@ -12,21 +13,26 @@ import { Signer } from './common/combine' import { catchErrorHandler, disabledHandler, + Locals, meteringHandler, - PromiseHandler, + resultHandler, + ResultHandler, + tracingHandler, } from './common/handlers' import { CombinerConfig, getCombinerVersion } from './config' -import { createDisableDomainHandler } from './domain/endpoints/disable/action' -import { createDomainQuotaHandler } from './domain/endpoints/quota/action' -import { createDomainSignHandler } from './domain/endpoints/sign/action' -import { createPnpQuotaHandler } from './pnp/endpoints/quota/action' -import { createPnpSignHandler } from './pnp/endpoints/sign/action' +import { disableDomain } from './domain/endpoints/disable/action' +import { domainQuota } from './domain/endpoints/quota/action' +import { domainSign } from './domain/endpoints/sign/action' +import { pnpQuota } from './pnp/endpoints/quota/action' +import { pnpSign } from './pnp/endpoints/sign/action' require('events').EventEmitter.defaultMaxListeners = 15 -export function startCombiner(config: CombinerConfig, kit: ContractKit) { +export function startCombiner(config: CombinerConfig, kit?: ContractKit) { const logger = rootLogger(config.serviceName) + kit = kit ?? getContractKitWithAgent(config.blockchain) + logger.info('Creating combiner express server') const app = express() @@ -64,26 +70,39 @@ export function startCombiner(config: CombinerConfig, kit: ContractKit) { ) const pnpSigners: Signer[] = JSON.parse(config.phoneNumberPrivacy.odisServices.signers) - const pnpQuota = createPnpQuotaHandler(pnpSigners, config.phoneNumberPrivacy, dekFetcher) - const pnpSign = createPnpSignHandler(pnpSigners, config.phoneNumberPrivacy, dekFetcher) - const domainSigners: Signer[] = JSON.parse(config.domains.odisServices.signers) - const domainQuota = createDomainQuotaHandler(domainSigners, config.domains) - const domainSign = createDomainSignHandler(domainSigners, config.domains) - const domainDisable = createDisableDomainHandler(domainSigners, config.domains) - app.post(CombinerEndpoint.PNP_QUOTA, createHandler(config.phoneNumberPrivacy.enabled, pnpQuota)) - app.post(CombinerEndpoint.PNP_SIGN, createHandler(config.phoneNumberPrivacy.enabled, pnpSign)) - app.post(CombinerEndpoint.DOMAIN_QUOTA_STATUS, createHandler(config.domains.enabled, domainQuota)) - app.post(CombinerEndpoint.DOMAIN_SIGN, createHandler(config.domains.enabled, domainSign)) - app.post(CombinerEndpoint.DISABLE_DOMAIN, createHandler(config.domains.enabled, domainDisable)) + const { domains, phoneNumberPrivacy } = config + + app.post( + CombinerEndpoint.PNP_QUOTA, + createHandler(phoneNumberPrivacy.enabled, pnpQuota(pnpSigners, phoneNumberPrivacy, dekFetcher)) + ) + app.post( + CombinerEndpoint.PNP_SIGN, + createHandler(phoneNumberPrivacy.enabled, pnpSign(pnpSigners, phoneNumberPrivacy, dekFetcher)) + ) + app.post( + CombinerEndpoint.DOMAIN_QUOTA_STATUS, + createHandler(domains.enabled, domainQuota(domainSigners, domains)) + ) + app.post( + CombinerEndpoint.DOMAIN_SIGN, + createHandler(domains.enabled, domainSign(domainSigners, domains)) + ) + app.post( + CombinerEndpoint.DISABLE_DOMAIN, + createHandler(domains.enabled, disableDomain(domainSigners, domains)) + ) return app } -export function createHandler( +function createHandler( enabled: boolean, - handler: PromiseHandler -): PromiseHandler { - return meteringHandler(catchErrorHandler(enabled ? handler : disabledHandler)) + action: ResultHandler +): RequestHandler<{}, {}, R, {}, Locals> { + return catchErrorHandler( + tracingHandler(meteringHandler(enabled ? resultHandler(action) : disabledHandler)) + ) }