Issue verifying eager notification

markking
markking Member Posts: 2
edited January 29 in Developer APIs

Hello, I'm having issues verifying eager notifications.

Here is the following eager notification code from my API (using Directus CMS backend):

router.post(EAGER_NOTIFICATIONS_PATH, async (req, res) => {
      /**
       * @type {import('../../event-auditions-endpoint/node_modules/@directus/api/dist/services').ItemsService<Directus.Videos>}
       */
      const videosService = new ItemsService('videos', {
        schema: req.schema,
        accountability: { admin: true },
      });

      logger.info(`body: ${JSON.stringify(req.body)} `);

      try {
        if (!isValidNotificationSignature(req, logger)) {
          return res
            .status(401)
            .send({ message: 'Invalid notification signature' });
        }

        await videosService.updateByQuery(
          { filter: { public_id: { _eq: req.body.public_id } } },
          { encoded_time: new Date().toISOString() },
        );
        res.status(204).send();
      } catch (err) {
        logger.error(err, 'Error handling notification');
        res.status(500).send({ message: 'Internal server error' });
      }
    });
  },
});

function isValidNotificationSignature(req, logger) {
  try {
    const timestamp = parseInt(req.header('X-Cld-Timestamp'));
    const signature = req.header('X-Cld-Signature');

    logger.info(`timestamp: ${timestamp} signature: ${signature}`)

    const isValid = cloudinary.utils.verifyNotificationSignature(
      JSON.stringify(req.body),
      timestamp,
      signature,
    );
    if (!isValid) {
      logger.error(`notification signature is not valid`);
      return false;
    }
    return true;
  } catch (error) {
    logger.error(error, 'Unable to verify signature');
  }
  return false;
}

Here is also code used to generate signed URLS:

router.get('/:fileName', async (req, res) => {
  try {
    const { fileName } = req.params;

    const { name } = path.parse(fileName);

    const timestamp = Math.round(new Date().getTime() / 1000);
    const signature = cloudinary.utils.api_sign_request(
      {
        timestamp,
        public_id: name,
        upload_preset: 'hls-encode',
        folder: STORAGE_CLOUDINARY_ROOT,
        eager_notification_url: eagerNotificationURL,
      },
      STORAGE_CLOUDINARY_API_SECRET,
    );

    const signedUrl = new URL(
      `/v1_1/${STORAGE_CLOUDINARY_CLOUD_NAME}/auto/upload`,
      'https://api.cloudinary.com',
    );
    signedUrl.searchParams.set('timestamp', timestamp.toString());
    signedUrl.searchParams.set('signature', signature);
    signedUrl.searchParams.set('public_id', name);
    signedUrl.searchParams.set('upload_preset', 'hls-encode');
    signedUrl.searchParams.set('folder', STORAGE_CLOUDINARY_ROOT);
    signedUrl.searchParams.set('api_key', STORAGE_CLOUDINARY_API_KEY);
    signedUrl.searchParams.set(
      'eager_notification_url',
      eagerNotificationURL,
    );

    logger.info(`signedUrl: ${signedUrl}`);

    res.status(200).send({
      message: 'success',
      data: { signedURL: signedUrl },
    });
  } catch (e) {
    res.status(400).send({
      message: 'error generating signedUrl',
      data: {},
    });
  }
});

I've logged the body received from the eager notifcation here:

{"notification_type":"eager","timestamp":"2024-01-27T17:24:07+00:00","request_id":"f9b85857ed7dc496a8f3c3d1befc7d59","eager":[{"transformation":"sp_full_hd/m3u8","bytes":877,"format":"m3u8","url":"http://res.cloudinary.com/hhhkasdw4/video/upload/sp_full_hd/v1706376224/batalla/5AA2F824-2261-44A3-B535-EAC4205E1B6D.m3u8","secure_url":"https://res.cloudinary.com/hhhkasdw4/video/upload/sp_full_hd/v1706376224/batalla/5AA2F824-2261-44A3-B535-EAC4205E1B6D.m3u8"},{"transformation":"c_scale,g_north_east,h_112,l_batalla:icons:Logo_Positive-Bare,o_50,y_56","width":320,"height":568,"bytes":817896,"format":"mp4","url":"http://res.cloudinary.com/hhhkasdw4/video/upload/c_scale,g_north_east,h_112,l_batalla:icons:Logo_Positive-Bare,o_50,y_56/v1706376224/batalla/5AA2F824-2261-44A3-B535-EAC4205E1B6D.mp4","secure_url":"https://res.cloudinary.com/hhhkasdw4/video/upload/c_scale,g_north_east,h_112,l_batalla:icons:Logo_Positive-Bare,o_50,y_56/v1706376224/batalla/5AA2F824-2261-44A3-B535-EAC4205E1B6D.mp4"}],"batch_id":"45cba5399880972d1b631573c9384dee058a38949d21563707c2dcab9196f2339e0220c0bf7b97b9cbc73df72cd87f53","asset_id":"35a76bfeb05231371cd16b3f06997d0e","public_id":"batalla/5AA2F824-2261-44A3-B535-EAC4205E1B6D","notification_context":{"triggered_at":"2024-01-27T17:23:44.175217Z","triggered_by":{"source":"api","id":"135922992395181"}},"signature_key":"877472399842481"}


I've confirmed the timestamps are valid within the default valid_for so it must be an issue comparing the hash values. I've tried setting the hashing algorithm to both 'sha1' and 'sha256' with no success.

I've confirmed I am receiving these values from the headers as well:

const timestamp = parseInt(req.header('X-Cld-Timestamp'));
const signature = req.header('X-Cld-Signature');

Any tips on why this verification might be failing?

I can provide more info if necessary.

Thank you.

Tagged:

Answers

  • aleksandar
    aleksandar Member, Cloudinary Staff Posts: 14

    Hi @markking,

    Thanks for your message.

    I checked internally and I see you submitted a ticket with our Support team directly and they shared details on why you could be encountering the error. That response from our side was sent on January 28th at 01:05 UTC. I'm not sharing that response here since it contains account-specific details.

    May I please ask you to check if you received that response from us and if not, please reply back in that support ticket and we can resend our response.

    Best,

    Aleksandar

  • markking
    markking Member Posts: 2

    Yes I received a response from the support team this discussion can be deleted thanks!