{"version":3,"sources":["../../src/requests/index.ts","../../src/requests/exceptions.ts","../../src/requests/sign-request.ts","../../src/requests/typings/function.ts","../../src/requests/typings/request.ts","../../src/requests/typings/validators.ts","../../src/requests/utils.ts","../../src/requests/timing-safe-string-equal.ts","../../src/requests/verify-request.ts"],"sourcesContent":["export { ExpiredRequestException } from './exceptions'\nexport { signRequest } from './sign-request'\nexport { verifyRequest } from './verify-request'\nexport * from './typings'\n","export class ExpiredRequestException extends Error {\n  private readonly ttl: number\n\n  constructor(ttl: number) {\n    super()\n\n    this.ttl = ttl\n    this.message = `[${this.constructor.name}]: Requests are expected to be verified within ${this.ttl}s from their signature.`\n  }\n}\n","import * as crypto from 'crypto'\nimport {\n  CanonicalRequest,\n  NormalizedCanonicalRequest,\n  Secret,\n  Timestamp,\n  ContentfulHeader,\n  Context,\n  SignedRequestWithoutContextHeaders,\n  SubjectHeadersApp,\n  SubjectHeadersUser,\n  SignedRequestWithContextHeadersWithApp,\n  SignedRequestWithContextHeadersWithUser,\n} from './typings'\nimport { CanonicalRequestValidator, SecretValidator, TimestampValidator } from './typings'\nimport {\n  getNormalizedEncodedURI,\n  normalizeHeaders,\n  sortHeaderKeys,\n  normalizeContextHeaders,\n} from './utils'\n\nconst hash = (normalizedCanonicalRequest: NormalizedCanonicalRequest, secret: string) => {\n  const stringifiedHeaders = normalizedCanonicalRequest\n    .headers!.map(([key, value]) => `${key}:${value}`)\n    .join(';')\n\n  const stringifiedRequest = [\n    normalizedCanonicalRequest.method,\n    normalizedCanonicalRequest.path,\n    stringifiedHeaders,\n    normalizedCanonicalRequest.body,\n  ].join('\\n')\n\n  const hmac = crypto.createHmac('sha256', secret)\n\n  hmac.update(stringifiedRequest)\n\n  return hmac.digest('hex')\n}\n\nconst getSortedAndSignedHeaders = (headers: Record<string, string>, timestamp: number) => {\n  const rawSignedHeaders = Object.keys(headers)\n\n  if (!(ContentfulHeader.SignedHeaders in headers)) {\n    rawSignedHeaders.push(ContentfulHeader.SignedHeaders)\n  }\n\n  if (!(ContentfulHeader.Timestamp in headers)) {\n    rawSignedHeaders.push(ContentfulHeader.Timestamp)\n  }\n\n  const signedHeaders = rawSignedHeaders.sort(sortHeaderKeys).join(',')\n\n  headers[ContentfulHeader.Timestamp] = timestamp.toString()\n  headers[ContentfulHeader.SignedHeaders] = signedHeaders\n\n  const sortedHeaders = Object.entries(headers).sort(([keyA], [keyB]) => sortHeaderKeys(keyA, keyB))\n\n  return { sortedHeaders, signedHeaders }\n}\n\n/**\n * Given a secret, a canonical request, a timestamp and context headers, generates a signature.\n * ~~~\n * @category Requests\n */\n// Remove when this eslint rule covers all the cases\n// https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/ROADMAP.md\n/*eslint-disable no-unused-vars, no-redeclare*/\nexport function signRequest(\n  rawSecret: Secret,\n  rawCanonicalRequest: CanonicalRequest,\n  rawTimestamp?: Timestamp,\n): SignedRequestWithoutContextHeaders\nexport function signRequest(\n  rawSecret: Secret,\n  rawCanonicalRequest: CanonicalRequest,\n  rawTimestamp?: Timestamp,\n  rawContext?: Context<SubjectHeadersApp>,\n): SignedRequestWithContextHeadersWithApp\nexport function signRequest(\n  rawSecret: Secret,\n  rawCanonicalRequest: CanonicalRequest,\n  rawTimestamp?: Timestamp,\n  rawContext?: Context<SubjectHeadersUser>,\n): SignedRequestWithContextHeadersWithUser\nexport function signRequest(\n  rawSecret: Secret,\n  rawCanonicalRequest: CanonicalRequest,\n  rawTimestamp?: Timestamp,\n  rawContext?: any,\n) {\n  const maybeDefaultTimestamp = rawTimestamp ?? Date.now()\n  const canonicalRequest: CanonicalRequest = CanonicalRequestValidator.check(rawCanonicalRequest)\n  const timestamp: Timestamp = TimestampValidator.check(maybeDefaultTimestamp)\n  const secret: Secret = SecretValidator.check(rawSecret)\n\n  const path = getNormalizedEncodedURI(canonicalRequest.path)\n  const method = canonicalRequest.method\n  const headers = canonicalRequest.headers ? normalizeHeaders(canonicalRequest.headers) : {}\n  const body = canonicalRequest.body ?? ''\n\n  const contextHeaders = rawContext ? normalizeContextHeaders(rawContext) : {}\n\n  const { sortedHeaders, signedHeaders } = getSortedAndSignedHeaders(\n    { ...headers, ...contextHeaders },\n    timestamp,\n  )\n\n  return {\n    [ContentfulHeader.Signature]: hash({ method, headers: sortedHeaders, path, body }, secret),\n    [ContentfulHeader.SignedHeaders]: signedHeaders,\n    [ContentfulHeader.Timestamp]: timestamp.toString(),\n    ...contextHeaders,\n  }\n}\n/*eslint-enable no-unused-vars,no-redeclare */\n","// Remove when this eslint rule covers all the cases\n// https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/ROADMAP.md\n/*eslint-disable no-unused-vars*/\n\nimport { AppActionCategoryType, ClientOptions, PlainClientAPI } from 'contentful-management'\nimport { AppActionCategoryBodyMap, AppActionRequestBody } from './appAction'\nimport { AppEventPayloadMap } from './event-payloads'\nimport {\n  type ResourcesLookupRequest,\n  type ResourcesLookupResponse,\n  type ResourcesSearchRequest,\n  type ResourcesSearchResponse,\n} from './resources'\n\nexport enum FunctionTypeEnum {\n  GraphqlFieldMapping = 'graphql.field.mapping',\n  GraphqlResourceTypeMapping = 'graphql.resourcetype.mapping',\n  GraphqlQuery = 'graphql.query',\n  AppEventFilter = 'appevent.filter',\n  AppEventHandler = 'appevent.handler',\n  AppEventTransformation = 'appevent.transformation',\n  AppActionCall = 'appaction.call',\n  ResourcesSearch = 'resources.search',\n  ResourcesLookup = 'resources.lookup',\n}\n\ntype GraphQLFieldTypeMappingRequest = {\n  type: FunctionTypeEnum.GraphqlFieldMapping\n  fields: { contentTypeId: string; field: Field }[]\n}\n\ntype Field = {\n  id: string\n  type: string\n}\n\nexport type GraphQLFieldTypeMappingResponse = {\n  namespace: string\n  fields: GraphQLFieldTypeMapping[]\n}\n\nexport type GraphQLFieldTypeMapping = {\n  contentTypeId: string\n  fieldId: string\n  graphQLOutputType?: string\n  graphQLQueryField: string\n  graphQLQueryArguments: Record<string, string>\n}\n\ntype GraphQLResourceTypeMappingRequest = {\n  type: FunctionTypeEnum.GraphqlResourceTypeMapping\n  resourceTypes: {\n    resourceTypeId: string\n  }[]\n}\n\ntype GraphQLResourceTypeMappingResponse = {\n  resourceTypes: GraphQLResourceTypeMapping[]\n}\n\ntype GraphQLQueryArgumentMapping = {\n  [key: string]: GraphQLQueryArgumentMapping | string\n}\n\ntype GraphQLResourceTypeMapping = {\n  graphQLQueryField: string\n  graphQLQueryArguments: GraphQLQueryArgumentMapping\n  resourceTypeId: string\n  graphQLOutputType?: string\n}\n\ntype GraphQLQueryRequest = {\n  type: FunctionTypeEnum.GraphqlQuery\n  query: string\n  isIntrospectionQuery: boolean\n  variables: Record<string, unknown>\n  operationName?: string\n}\n\n/**\n * @see https://spec.graphql.org/October2021/#sec-Response\n */\nexport type GraphQLQueryResponse = {\n  data?: Record<string, any> | null\n  errors?: readonly Record<string, any>[]\n  extensions?: Record<string, unknown>\n}\n\ntype AppEventEntityName = keyof AppEventPayloadMap\ntype AppEventEntityActions<T extends AppEventEntityName> = keyof AppEventPayloadMap[T] & string\ntype AppEventEntityPayload<\n  T extends AppEventEntityName,\n  A extends AppEventEntityActions<T>,\n> = AppEventPayloadMap[T][A]\n\ntype AppEventBase<\n  EntityName extends AppEventEntityName,\n  EntityAction extends AppEventEntityActions<EntityName>,\n> = {\n  headers: Record<string, string | number> & {\n    'X-Contentful-Topic': `ContentManagement.${EntityName}.${EntityAction}`\n  }\n  body: AppEventEntityPayload<EntityName, EntityAction>\n  type:\n    | FunctionTypeEnum.AppEventHandler\n    | FunctionTypeEnum.AppEventTransformation\n    | FunctionTypeEnum.AppEventFilter\n}\n\nexport type AppEventContentType = {\n  [A in AppEventEntityActions<'ContentType'>]: AppEventBase<'ContentType', A>\n}[AppEventEntityActions<'ContentType'>]\n\nexport type AppEventEntry = {\n  [A in AppEventEntityActions<'Entry'>]: AppEventBase<'Entry', A>\n}[AppEventEntityActions<'Entry'>]\n\nexport type AppEventAsset = {\n  [A in AppEventEntityActions<'Asset'>]: AppEventBase<'Asset', A>\n}[AppEventEntityActions<'Asset'>]\n\nexport type AppEventAppInstallation = {\n  [A in AppEventEntityActions<'AppInstallation'>]: AppEventBase<'AppInstallation', A>\n}[AppEventEntityActions<'AppInstallation'>]\n\nexport type AppEventTask = {\n  [A in AppEventEntityActions<'Task'>]: AppEventBase<'Task', A>\n}[AppEventEntityActions<'Task'>]\n\nexport type AppEventComment = {\n  [A in AppEventEntityActions<'Comment'>]: AppEventBase<'Comment', A>\n}[AppEventEntityActions<'Comment'>]\n\nexport type AppEventRelease = {\n  [A in AppEventEntityActions<'Release'>]: AppEventBase<'Release', A>\n}[AppEventEntityActions<'Release'>]\n\nexport type AppEventReleaseAction = {\n  [A in AppEventEntityActions<'ReleaseAction'>]: AppEventBase<'ReleaseAction', A>\n}[AppEventEntityActions<'ReleaseAction'>]\n\nexport type AppEventScheduledAction = {\n  [A in AppEventEntityActions<'ScheduledAction'>]: AppEventBase<'ScheduledAction', A>\n}[AppEventEntityActions<'ScheduledAction'>]\n\nexport type AppEventBulkAction = {\n  [A in AppEventEntityActions<'BulkAction'>]: AppEventBase<'BulkAction', A>\n}[AppEventEntityActions<'BulkAction'>]\n\nexport type AppEventTemplateInstallation = {\n  [A in AppEventEntityActions<'TemplateInstallation'>]: AppEventBase<'TemplateInstallation', A>\n}[AppEventEntityActions<'TemplateInstallation'>]\n\nexport type AppEventWorkflow = {\n  [A in AppEventEntityActions<'Workflow'>]: AppEventBase<'Workflow', A>\n}[AppEventEntityActions<'Workflow'>]\n\nexport type AppEventRequest = {\n  [T in AppEventEntityName]: {\n    [A in AppEventEntityActions<T>]: AppEventBase<T, A>\n  }[AppEventEntityActions<T>]\n}[AppEventEntityName]\n\nexport type AppEventFilterResponse = {\n  result: boolean\n}\n\nexport type AppEventTransformationResponse = {\n  headers: Record<string, string | number>\n  body: Record<string, unknown>\n}\n\nexport type AppEventHandlerResponse = void\n\n/**\n * The app action request body will contain different parameters depending on the category of the app action\n *\n * Specify your app action category as the generic type `Category` to get the correct body type,\n * e.g. `const { body: { message, recipient }} = event as AppActionRequest<'Notification.v1.0'>`\n *\n * If you are using the Custom category, you can specify the parameter shape as the second generic type `CustomCategoryBody`,\n * e.g. `const { body: { myNumber }} = event as AppActionRequest<'Custom', { myNumber: number }>`\n */\nexport type AppActionRequest<\n  CategoryType extends AppActionCategoryType = 'Custom',\n  CustomCategoryBody = AppActionCategoryBodyMap['Custom'],\n> = {\n  headers: Record<string, string | number>\n  body: CategoryType extends 'Custom' ? CustomCategoryBody : AppActionRequestBody<CategoryType>\n  type: FunctionTypeEnum.AppActionCall\n}\n\nexport type AppActionResponse = void | Record<string, unknown>\n\n/**\n * P: Possibility to type app installation parameters\n */\nexport type FunctionEventContext<P extends Record<string, any> = Record<string, any>> = {\n  spaceId: string\n  environmentId: string\n  appInstallationParameters: P\n  cmaClientOptions?: ClientOptions\n  cma?: PlainClientAPI\n  originalRequest?: {\n    headers: Record<string, any>\n  }\n}\n\n/**\n * T: Possibility to type app action category\n * U: Possibility to type app action body (only applies to the Custom category)\n */\ntype FunctionEventHandlers<\n  T extends AppActionCategoryType = never,\n  U extends AppActionRequestBody<T> = never,\n> = {\n  [FunctionTypeEnum.GraphqlFieldMapping]: {\n    event: GraphQLFieldTypeMappingRequest\n    response: GraphQLFieldTypeMappingResponse\n  }\n  [FunctionTypeEnum.GraphqlResourceTypeMapping]: {\n    event: GraphQLResourceTypeMappingRequest\n    response: GraphQLResourceTypeMappingResponse\n  }\n  [FunctionTypeEnum.GraphqlQuery]: {\n    event: GraphQLQueryRequest\n    response: GraphQLQueryResponse\n  }\n  [FunctionTypeEnum.AppActionCall]: {\n    event: AppActionRequest<T, U>\n    response: AppActionResponse\n  }\n  [FunctionTypeEnum.AppEventFilter]: {\n    event: AppEventRequest\n    response: AppEventFilterResponse\n  }\n  [FunctionTypeEnum.AppEventHandler]: {\n    event: AppEventRequest\n    response: AppEventHandlerResponse\n  }\n  [FunctionTypeEnum.AppEventTransformation]: {\n    event: AppEventRequest\n    response: AppEventTransformationResponse\n  }\n  [FunctionTypeEnum.ResourcesSearch]: {\n    event: ResourcesSearchRequest\n    response: ResourcesSearchResponse\n  }\n  [FunctionTypeEnum.ResourcesLookup]: {\n    event: ResourcesLookupRequest\n    response: ResourcesLookupResponse\n  }\n}\n\nexport type FunctionEvent =\n  | GraphQLFieldTypeMappingRequest\n  | GraphQLResourceTypeMappingRequest\n  | GraphQLQueryRequest\n  | AppActionRequest\n  | AppEventRequest\n  | ResourcesSearchRequest\n  | ResourcesLookupRequest\nexport type FunctionEventType = keyof FunctionEventHandlers\n\n/**\n * Event handler type that needs to be exported as `handler` from your function.\n * e.g. `const handler: FunctionEventHandler = (event, context) => { ... }`\n *\n * This type can also be used to construct helper functions for specific events\n * e.g. `const queryHandler: FunctionEventHandler<'graphql.query'> = (event, context) => { ... }`\n */\nexport type FunctionEventHandler<\n  K extends FunctionEventType = FunctionEventType,\n  P extends Record<string, any> = Record<string, any>,\n> = (\n  event: FunctionEventHandlers[K]['event'],\n  context: FunctionEventContext<P>,\n) => Promise<FunctionEventHandlers[K]['response']> | FunctionEventHandlers[K]['response']\n","// Remove when this eslint rule covers all the cases\n// https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/ROADMAP.md\n/*eslint-disable no-unused-vars*/\nimport { CanonicalRequest } from './validators'\n\nexport enum ContentfulHeader {\n  Timestamp = 'x-contentful-timestamp',\n  SignedHeaders = 'x-contentful-signed-headers',\n  Signature = 'x-contentful-signature',\n}\n\nexport enum ContentfulContextHeader {\n  CRN = 'x-contentful-crn',\n  SpaceId = 'x-contentful-space-id',\n  EnvironmentId = 'x-contentful-environment-id',\n  UserId = 'x-contentful-user-id',\n  AppId = 'x-contentful-app-id',\n}\n\nexport type NormalizedCanonicalRequest = {\n  method: CanonicalRequest['method']\n  path: CanonicalRequest['path']\n  headers: [key: string, value: string][]\n  body: CanonicalRequest['body']\n}\n\nexport type SubjectHeadersApp = { appId: string }\nexport type AppContextSignedHeaders = { [ContentfulContextHeader.AppId]: string }\nexport type SubjectHeadersUser = { userId: string }\nexport type UserContextSignedHeaders = { [ContentfulContextHeader.UserId]: string }\n\nexport type Context<SubjectContext> = {\n  crn?: string\n  spaceId: string\n  envId: string\n} & SubjectContext\n\ntype SignedHeadersWithoutSubject = {\n  [ContentfulContextHeader.CRN]?: string\n  [ContentfulContextHeader.SpaceId]: string\n  [ContentfulContextHeader.EnvironmentId]: string\n}\n\nexport type SignedContextHeaders<SubjectSignedHeaders> = SignedHeadersWithoutSubject &\n  SubjectSignedHeaders\n\nexport type SignedRequestWithoutContextHeaders = {\n  [key in ContentfulHeader]: string\n}\nexport type SignedRequestWithContextHeadersWithUser = SignedRequestWithoutContextHeaders &\n  SignedContextHeaders<UserContextSignedHeaders>\nexport type SignedRequestWithContextHeadersWithApp = SignedRequestWithoutContextHeaders &\n  SignedContextHeaders<AppContextSignedHeaders>\n\nexport type SignedRequestHeaders =\n  | SignedRequestWithContextHeadersWithUser\n  | SignedRequestWithContextHeadersWithApp\n  | SignedRequestWithoutContextHeaders\n","import * as runtypes from 'runtypes'\n\nconst MethodValidator = runtypes.Union(\n  runtypes.Literal('GET'),\n  runtypes.Literal('PATCH'),\n  runtypes.Literal('HEAD'),\n  runtypes.Literal('POST'),\n  runtypes.Literal('DELETE'),\n  runtypes.Literal('OPTIONS'),\n  runtypes.Literal('PUT'),\n)\n\nconst PathValidator = runtypes.String.withConstraint((s) => s.startsWith('/'), {\n  name: 'CanonicalURI',\n})\n\nconst SignatureValidator = runtypes.String.withConstraint((s) => s.length === 64, {\n  name: 'SignatureLength',\n})\n\nexport const CanonicalRequestValidator = runtypes\n  .Record({\n    method: MethodValidator,\n    path: PathValidator,\n  })\n  .And(\n    runtypes.Partial({\n      headers: runtypes.Dictionary(runtypes.String, 'string'),\n      body: runtypes.String,\n    }),\n  )\nexport type CanonicalRequest = runtypes.Static<typeof CanonicalRequestValidator>\n\nexport const SecretValidator = runtypes.String.withConstraint((s) => s.length === 64, {\n  name: 'SecretLength',\n})\nexport type Secret = runtypes.Static<typeof SecretValidator>\n\n// Only dates after 01-01-2020\nexport const TimestampValidator = runtypes.Number.withConstraint((n) => n > 1577836800000, {\n  name: 'TimestampAge',\n})\nexport type Timestamp = runtypes.Static<typeof TimestampValidator>\n\nconst SignedHeadersValidator = runtypes\n  .Array(runtypes.String)\n  .withConstraint((l) => l.length >= 2, { name: 'MissingTimestampOrSignedHeaders' })\n\nexport const RequestMetadataValidator = runtypes.Record({\n  signature: SignatureValidator,\n  timestamp: TimestampValidator,\n  signedHeaders: SignedHeadersValidator,\n})\nexport type RequestMetadata = runtypes.Static<typeof RequestMetadataValidator>\n\nexport const TimeToLiveValidator = runtypes.Number.withConstraint((n) => n >= 0, {\n  name: 'PositiveNumber',\n})\nexport type TimeToLive = runtypes.Static<typeof TimeToLiveValidator>\n","import * as querystring from 'querystring'\nimport {\n  AppContextSignedHeaders,\n  ContentfulContextHeader,\n  Context,\n  SignedContextHeaders,\n  SubjectHeadersApp,\n  SubjectHeadersUser,\n  UserContextSignedHeaders,\n} from './typings'\n\nexport const getNormalizedEncodedURI = (uri: string) => {\n  const [pathname, search] = uri.split('?')\n  const escapedSearch = search ? querystring.escape(search) : ''\n\n  return encodeURI(escapedSearch ? `${pathname}?${escapedSearch}` : pathname)\n}\n\nexport const sortHeaderKeys = (keyA: string, keyB: string) => (keyA > keyB ? 1 : -1)\n\nconst normalizeHeaderKey = (key: string) => key.toLowerCase().trim()\nconst normalizeHeaderValue = (value: string) => value.trim()\nexport const normalizeHeaders = (headers: Record<string, string>) =>\n  map(headers, ([key, value]) => [normalizeHeaderKey(key), normalizeHeaderValue(value)])\n\nexport const pickHeaders = (headers?: Record<string, string>, keys?: string[]) => {\n  if (!headers) {\n    return {}\n  }\n\n  if (!keys) {\n    return headers\n  }\n\n  return filter(headers, ([key]) => keys.includes(key))\n}\n\nconst contextHeadersMap: Record<string, ContentfulContextHeader> = {\n  crn: ContentfulContextHeader.CRN,\n  spaceId: ContentfulContextHeader.SpaceId,\n  envId: ContentfulContextHeader.EnvironmentId,\n  appId: ContentfulContextHeader.AppId,\n  userId: ContentfulContextHeader.UserId,\n}\n\n// Remove when this eslint rule covers all the cases\n// https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/ROADMAP.md\n/*eslint-disable no-unused-vars, no-redeclare*/\nexport function normalizeContextHeaders(\n  rawContext: Context<SubjectHeadersApp>,\n): SignedContextHeaders<AppContextSignedHeaders>\nexport function normalizeContextHeaders(\n  rawContext: Context<SubjectHeadersUser>,\n): SignedContextHeaders<UserContextSignedHeaders>\nexport function normalizeContextHeaders(\n  rawContext: Context<SubjectHeadersApp> | Context<SubjectHeadersUser>,\n) {\n  return Object.keys(rawContext).reduce(\n    (acc, header) => {\n      if (contextHeadersMap[header]) {\n        const key = normalizeHeaderKey(contextHeadersMap[header]) as ContentfulContextHeader\n        acc[key] = normalizeHeaderValue(\n          acc[key] ??\n            rawContext[header as keyof (Context<SubjectHeadersUser> | Context<SubjectHeadersApp>)],\n        )\n      }\n      return acc\n    },\n    {} as Record<ContentfulContextHeader, string>,\n  )\n}\n/*eslint-enable no-unused-vars, no-redeclare*/\n\n// Remove when this eslint rule covers all the cases\n// https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/ROADMAP.md\n/*eslint-disable no-unused-vars*/\nexport const filter = <T = string>(\n  obj: Record<string, any>,\n  callback: (entry: [string, T]) => boolean,\n) => {\n  return Object.fromEntries(Object.entries(obj).filter(callback))\n}\n\nexport const map = <T = string>(\n  obj: Record<string, any>,\n  callback: (entry: [string, T]) => [string, T],\n) => {\n  return Object.fromEntries(Object.entries(obj).map(callback))\n}\n/*eslint-enable no-unused-vars*/\n","import * as crypto from 'crypto'\n\nconst textEncoder = new TextEncoder()\n\n/** Constant-time compare of UTF-8 bytes; preserves string `===` semantics (e.g. hex case). */\nexport const timingSafeUtf8StringEqual = (a: string, b: string): boolean => {\n  const aBuf = textEncoder.encode(a)\n  const bBuf = textEncoder.encode(b)\n  if (aBuf.length !== bBuf.length) {\n    return false\n  }\n  return crypto.timingSafeEqual(aBuf, bBuf)\n}\n","import {\n  CanonicalRequest,\n  CanonicalRequestValidator,\n  RequestMetadata,\n  RequestMetadataValidator,\n  Secret,\n  SecretValidator,\n  Timestamp,\n  TimeToLive,\n  ContentfulHeader,\n} from './typings'\nimport { normalizeHeaders, pickHeaders } from './utils'\nimport { timingSafeUtf8StringEqual } from './timing-safe-string-equal'\nimport { signRequest } from './sign-request'\nimport { ExpiredRequestException } from './exceptions'\n\nconst getRequestMetadata = (normalizedHeaders: Record<string, string>): RequestMetadata => {\n  const signature = normalizedHeaders[ContentfulHeader.Signature]\n  const signedHeaders = (normalizedHeaders[ContentfulHeader.SignedHeaders] ?? '').split(',')\n  const timestamp = Number.parseInt(normalizedHeaders[ContentfulHeader.Timestamp] ?? '', 10)\n\n  return RequestMetadataValidator.check({ signature, signedHeaders, timestamp })\n}\n\nconst isRequestTimestampTooOld = (ttl: number, timestamp: Timestamp) => {\n  return Date.now() - timestamp >= ttl * 1000\n}\n\n/**\n * Given a secret verifies a CanonicalRequest. It also throws when signature is older than `rawTimeToLive` seconds.\n * Pass `rawTimeToLive = 0` to disable TTL checks.\n *\n * ~~~\n * const { verifyRequest } = require('@contentful/node-apps-toolkit')\n * const { server } = require('./imaginary-server')\n * const { makeCanonicalRequestFromImaginaryServerRequest } = require('./imaginary-utils')\n *\n * const SECRET = process.env.SECRET\n * const REQUEST_TTL = Number.parseInt(process.env.REQUEST_TTL, 10)\n *\n * server.post('/api/my-resources', (req, res) => {\n *   const canonicalRequest = makeCanonicalRequestFromImaginaryServerRequest(req)\n *\n *   try {\n *     const isVerifiedRequest = verifyRequest(SECRET, canonicalRequest, REQUEST_TTL)\n *\n *     if (!isVerifiedRequest) {\n *       res.send(403, 'Invalid signature')\n *     }\n *   } catch (_error) {\n *     res.send(422, 'Unable to verify request')\n *   }\n *\n *   // Rest of the code\n * })\n *\n * ~~~\n * @category Requests\n */\nexport const verifyRequest = (\n  rawSecret: Secret,\n  rawCanonicalRequest: CanonicalRequest,\n  rawTimeToLive: TimeToLive = 30,\n): boolean => {\n  const canonicalRequest = CanonicalRequestValidator.check(rawCanonicalRequest)\n  const secret = SecretValidator.check(rawSecret)\n\n  const normalizedHeaders = normalizeHeaders(canonicalRequest.headers ?? {})\n\n  const { signature, signedHeaders, timestamp } = getRequestMetadata(normalizedHeaders)\n\n  if (rawTimeToLive !== 0 && isRequestTimestampTooOld(rawTimeToLive, timestamp)) {\n    throw new ExpiredRequestException(rawTimeToLive)\n  }\n\n  const requestToValidate = {\n    ...canonicalRequest,\n    headers: pickHeaders(normalizedHeaders, signedHeaders),\n  }\n\n  const { [ContentfulHeader.Signature]: computedSignature } = signRequest(\n    secret,\n    requestToValidate,\n    timestamp,\n  )\n\n  return timingSafeUtf8StringEqual(signature, computedSignature)\n}\n"],"mappings":"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","names":["FunctionTypeEnum","ContentfulHeader","ContentfulContextHeader","crypto"]}