Skip to content

Bug: Payload validation broken for DynamoDBPersistenceLayer #2058

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
kevin-secrist opened this issue Feb 12, 2024 · 7 comments · Fixed by #2083
Closed

Bug: Payload validation broken for DynamoDBPersistenceLayer #2058

kevin-secrist opened this issue Feb 12, 2024 · 7 comments · Fixed by #2083
Assignees
Labels
bug Something isn't working completed This item is complete and has been merged/shipped idempotency This item relates to the Idempotency Utility

Comments

@kevin-secrist
Copy link
Contributor

kevin-secrist commented Feb 12, 2024

Expected Behaviour

The IdempotencyHandler should throw a IdempotencyValidationError when the validation hash in DynamoDB does not match the incoming event's validation hash (specified by payloadValidationJmesPath).

Current Behaviour

It does not do that.

Code snippet

I would not expect the example code to work: https://docs.powertools.aws.dev/lambda/typescript/latest/utilities/idempotency/#payload-validation

Steps to Reproduce

  1. Specify payloadValidationJmesPath in the IdempotencyConfig
  2. Run the same payload twice (except with a difference at the field specified at payloadValidationJmesPath

No IdempotencyValidationError will be thrown.

Possible Solution

Reverting to 1.17 fixes the issue.

The changes done in this PR #1779 does not do payload hash checking on the returned document, because that logic is short circuited when returning the existingRecord on the error.

const idempotencyRecord: IdempotencyRecord =
e.existingRecord ||
(await this.#persistenceStore.getRecord(
this.#functionPayloadToBeHashed
));

The following logic within BasePersistenceLayer.getRecord() is skipped, so the IdempotencyValidationError error is not thrown.

public async getRecord(data: JSONValue): Promise<IdempotencyRecord> {
const idempotencyKey = this.getHashedIdempotencyKey(data);
const cachedRecord = this.getFromCache(idempotencyKey);
if (cachedRecord) {
this.validatePayload(data, cachedRecord);
return cachedRecord;
}
const record = await this._getRecord(idempotencyKey);
this.saveToCache(record);
this.validatePayload(data, record);
return record;
}

Not sure how stable the interface for BaseIdempotencyLayer is but exposing validatePayload (and also saveToCache, because local caching is also being skipped) as protected and calling that on the returned record would solve the immediate problem but it doesn't seem like the most elegant solution.

Powertools for AWS Lambda (TypeScript) version

latest

AWS Lambda function runtime

18.x

Packaging format used

npm

Execution logs

No response

@kevin-secrist kevin-secrist added triage This item has not been triaged by a maintainer, please wait bug Something isn't working labels Feb 12, 2024
@dreamorosi
Copy link
Contributor

Hi @kevin-secrist thank you for opening this issue.

Could you please share an example of the two payloads you're sending to the function?

Run the same payload twice (except with a difference at the field specified at

I'm not sure I follow the wording here.

That would help us reproduce the bug.

@dreamorosi dreamorosi added idempotency This item relates to the Idempotency Utility discussing The issue needs to be discussed, elaborated, or refined need-response This item requires a response from a customer and will considered stale after 2 weeks labels Feb 12, 2024
@kevin-secrist
Copy link
Contributor Author

Hi @dreamorosi, the payload/code I'm using is simple and is similar to the example code. I've removed some extraneous fields and renamed things a bit but it should still illustrate the point.

const persistenceStore = new DynamoDBPersistenceLayer({
  tableName: process.env.IDEMPOTENCY_TABLE_NAME as string,
})
const config = new IdempotencyConfig({
  eventKeyJmesPath: '["rcn", "subscriptionType"]',
  payloadValidationJmesPath: 'customerAccounts',
  throwOnNoIdempotencyKey: true,
  expiresAfterSeconds: 3 * 24 * 60 * 60, // 3 days
})
const createSubscriptionIdempotent = makeIdempotent(
  (
    request: CreateSubscriptionRequest,
    client: IApiClient
  ): Promise<number> => client.createSubscription(request),
  {
    persistenceStore: createSubscriptionPersistenceStore,
    config,
  }
)
const triggerCheckSubscriptionEvent = async (
  event: CreateSubscriptionLambdaEvent
) => {
  // calls eventbridge PutEvents
};
const lamdaHandler = async (
  event: CreateSubscriptionLambdaEvent,
  context: CreateSubscriptionContext
): Promise<number> => {
  config.registerLambdaContext(context);

  const client = NewClient();
  try {
    const createSubscriptionResponse = await createSubscriptionIdempotent(
      event.detail,
      client
    );
    return createSubscriptionResponse;
  } catch (error) {
    if (error instanceof IdempotencyValidationError) {
      // a subscription was already created by another lambda
      // but with a different set of customerAccounts
      await triggerCheckSubscriptionEvent(event);
      // we don't have access to the existing idempotency record
      // so we will just return -1
      return -1;
    } else {
      throw error;
    }
  }
};

export const handler = middy(lamdaHandler).use(
  injectLambdaContext(logger, { logEvent: true })
);

And the payload looks something like this:

{
  "rcn": "RCN-000-000-000-000",
  "customerAccounts": {
    "customerAccount": [
      {
        "accountId": "12345",
        "accountType": "someaccounttype"
      }
    ]
  },
  "subscriptionType": "somesubscriptiontype"
}

and a second one

{
  "rcn": "RCN-000-000-000-000",
  "customerAccounts": {
    "customerAccount": [
      {
        "accountId": "54321",
        "accountType": "someaccounttype"
      }
    ]
  },
  "subscriptionType": "somesubscriptiontype"
}

The use case is that I'm basically turning concurrent creates into a create+updates, so for the same rcn and subscriptionType the first event would translate into a create and the second+future events will append the customerAccounts to the original entity as an update. I'm expecting the IdempotencyValidationError to be thrown because the customerAccounts property is different in the second payload. Reverting to 1.17 fixes the behavior.

@dreamorosi dreamorosi self-assigned this Feb 14, 2024
@dreamorosi
Copy link
Contributor

Hi @kevin-secrist thanks for sharing the additional info, I'll be looking into this today and try to reproduce the error.

@dreamorosi dreamorosi removed the need-response This item requires a response from a customer and will considered stale after 2 weeks label Feb 14, 2024
@dreamorosi
Copy link
Contributor

Hi again, I was able to reproduce the issue, indeed there seems to be a regression between versions 1.17.0 and 1.18.0.

I'm going to try implementing a fix.

@dreamorosi dreamorosi moved this from Triage to Working on it in Powertools for AWS Lambda (TypeScript) Feb 15, 2024
@dreamorosi dreamorosi added confirmed The scope is clear, ready for implementation and removed triage This item has not been triaged by a maintainer, please wait discussing The issue needs to be discussed, elaborated, or refined labels Feb 15, 2024
@dreamorosi dreamorosi moved this from Working on it to Pending review in Powertools for AWS Lambda (TypeScript) Feb 16, 2024
@dreamorosi
Copy link
Contributor

The PR is ready for review, we should be able to release the fix on Monday.

@github-project-automation github-project-automation bot moved this from Pending review to Coming soon in Powertools for AWS Lambda (TypeScript) Feb 19, 2024
Copy link
Contributor

⚠️ COMMENT VISIBILITY WARNING ⚠️

This issue is now closed. Please be mindful that future comments are hard for our team to see.

If you need more assistance, please either tag a team member or open a new issue that references this one.

If you wish to keep having a conversation with other community members under this issue feel free to do so.

@github-actions github-actions bot added pending-release This item has been merged and will be released soon and removed confirmed The scope is clear, ready for implementation labels Feb 19, 2024
Copy link
Contributor

This is now released under v1.18.1 version!

@github-actions github-actions bot added completed This item is complete and has been merged/shipped and removed pending-release This item has been merged and will be released soon labels Feb 20, 2024
@dreamorosi dreamorosi moved this from Coming soon to Shipped in Powertools for AWS Lambda (TypeScript) Feb 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working completed This item is complete and has been merged/shipped idempotency This item relates to the Idempotency Utility
Projects
2 participants