Skip to content

Bug: LambdaFunctionUrlEnvelope assumes body is always a JSON string #3199

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
dreamorosi opened this issue Oct 14, 2024 · 2 comments · Fixed by #3514
Closed

Bug: LambdaFunctionUrlEnvelope assumes body is always a JSON string #3199

dreamorosi opened this issue Oct 14, 2024 · 2 comments · Fixed by #3514
Assignees
Labels
bug Something isn't working completed This item is complete and has been merged/shipped parser This item relates to the Parser Utility

Comments

@dreamorosi
Copy link
Contributor

dreamorosi commented Oct 14, 2024

Expected Behavior

When using Parser with Lambda Function URL I should be able to parse requests that have a valid body that is not encoded as a JSON string. For example, when the body is a plain string or when it's a binary (& the isBase64Encoded field is true).

Current Behavior

Unless the body field is a JSON string, the parsing fails.

Code snippet

import { Logger, LogLevel } from '@aws-lambda-powertools/logger';
import { parser } from '@aws-lambda-powertools/parser/middleware';
import { LambdaFunctionUrlEnvelope } from '@aws-lambda-powertools/parser/envelopes';
import type { ParsedResult } from '@aws-lambda-powertools/parser/types';
import middy from '@middy/core';
import { z } from 'zod';

const logger = new Logger({ logLevel: LogLevel.DEBUG });

export const handler = middy(async (event: ParsedResult) => {
  logger.logEventIfEnabled(event);

  return {
    statusCode: 200,
    body: JSON.stringify('Hello, World!'),
  };
}).use(
  parser({
    schema: z.string(),
    envelope: LambdaFunctionUrlEnvelope,
    safeParse: true,
  })
);

Steps to Reproduce

For binary requests:

import {
  type BinaryLike,
  type KeyObject,
  createHash,
  createHmac,
} from 'node:crypto';
import { URL } from 'node:url';
import { HttpRequest } from '@smithy/protocol-http';
import { SignatureV4 } from '@smithy/signature-v4';

class Sha256 {
  private readonly hash;

  public constructor(secret?: unknown) {
    this.hash = secret
      ? createHmac('sha256', secret as BinaryLike | KeyObject)
      : createHash('sha256');
  }

  public digest(): Promise<Uint8Array> {
    const buffer = this.hash.digest();

    return Promise.resolve(new Uint8Array(buffer.buffer));
  }

  public update(array: Uint8Array): void {
    this.hash.update(array);
  }
}

const signer = new SignatureV4({
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID ?? '',
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY ?? '',
    sessionToken: process.env.AWS_SESSION_TOKEN ?? '',
  },
  service: 'lambda',
  region: process.env.AWS_REGION ?? 'eu-west-1',
  sha256: Sha256,
});

const buildHttpRequest = (apiUrl: string): HttpRequest => {
  const url = new URL(apiUrl);

  const buffer = Buffer.alloc(10);

  return new HttpRequest({
    hostname: url.hostname,
    path: url.pathname,
    body: buffer,
    method: 'POST',
    headers: {
      host: url.hostname,
    },
  });
};

const sendAuthrequest = async (apiUrl: string): Promise<void> => {
  // Build the HTTP request to be signed
  const httpRequest = buildHttpRequest(apiUrl);
  // Sign the request
  const signedHttpRequest = await signer.sign(httpRequest);
  try {
    // Send the request
    const result = await fetch(apiUrl, {
      headers: new Headers(signedHttpRequest.headers),
      body: signedHttpRequest.body,
      method: signedHttpRequest.method,
    });

    if (!result.ok) throw new Error(result.statusText);

    const body = await result.json();

    console.log('Response:', body);
  } catch (err) {
    console.error(err as Error);
    throw new Error('Failed to send request', { cause: err });
  }
};

await sendAuthrequest(
  'https://<api-id>.lambda-url.eu-west-1.on.aws/'
);

For raw string requests, use the same code as above, but change the body in the buildHttpRequest function to just "hello world" or any string.

Possible Solution

No response

Powertools for AWS Lambda (TypeScript) version

latest

AWS Lambda function runtime

20.x

Packaging format used

npm

Execution logs

{
    "success": false,
    "error": {
        "name": "ParseError",
        "location": "file:///var/task/index.mjs:3",
        "message": "Failed to parse Lambda function URL body. This error was caused by: Failed to parse envelope. This error was caused by: Unexpected token 'A', \"AAAAAAAAAAAAAA==\" is not valid JSON..",
        "stack": "ParseError: Failed to parse Lambda function URL body. This error was caused by: Failed to parse envelope. This error was caused by: Unexpected token 'A', \"AAAAAAAAAAAAAA==\" is not valid JSON..\n    at Object.safeParse (file:///var/task/index.mjs:3:82990)\n    at _r (file:///var/task/index.mjs:3:9998)\n    at before (file:///var/task/index.mjs:3:10351)\n    at Vt (file:///var/task/index.mjs:3:85173)\n    at co (file:///var/task/index.mjs:3:84503)\n    at Runtime.i [as handler] (file:///var/task/index.mjs:3:83598)\n    at Runtime.handleOnceNonStreaming (file:///var/runtime/index.mjs:1173:29)",
        "cause": {
            "name": "ParseError",
            "location": "file:///var/task/index.mjs:3",
            "message": "Failed to parse envelope. This error was caused by: Unexpected token 'A', \"AAAAAAAAAAAAAA==\" is not valid JSON.",
            "stack": "ParseError: Failed to parse envelope. This error was caused by: Unexpected token 'A', \"AAAAAAAAAAAAAA==\" is not valid JSON.\n    at Object.safeParse (file:///var/task/index.mjs:3:70629)\n    at Object.safeParse (file:///var/task/index.mjs:3:82928)\n    at _r (file:///var/task/index.mjs:3:9998)\n    at before (file:///var/task/index.mjs:3:10351)\n    at Vt (file:///var/task/index.mjs:3:85173)\n    at co (file:///var/task/index.mjs:3:84503)\n    at Runtime.i [as handler] (file:///var/task/index.mjs:3:83598)\n    at Runtime.handleOnceNonStreaming (file:///var/runtime/index.mjs:1173:29)",
            "cause": {
                "name": "SyntaxError",
                "location": "file:///var/task/index.mjs:3",
                "message": "Unexpected token 'A', \"AAAAAAAAAAAAAA==\" is not valid JSON",
                "stack": "SyntaxError: Unexpected token 'A', \"AAAAAAAAAAAAAA==\" is not valid JSON\n    at JSON.parse (<anonymous>)\n    at Object.safeParse (file:///var/task/index.mjs:3:70457)\n    at Object.safeParse (file:///var/task/index.mjs:3:82928)\n    at _r (file:///var/task/index.mjs:3:9998)\n    at before (file:///var/task/index.mjs:3:10351)\n    at Vt (file:///var/task/index.mjs:3:85173)\n    at co (file:///var/task/index.mjs:3:84503)\n    at Runtime.i [as handler] (file:///var/task/index.mjs:3:83598)\n    at Runtime.handleOnceNonStreaming (file:///var/runtime/index.mjs:1173:29)"
            }
        }
    },
    "originalEvent": {
        "version": "2.0",
        "routeKey": "$default",
        "rawPath": "/",
        "rawQueryString": "",
        "headers": {
            "sec-fetch-mode": "cors",
            "x-amz-content-sha256": "01d448afd928065458cf670b60f5a594d735af0172c8d67f22a81680132681ca",
            "content-length": "10",
            "x-amzn-tls-version": "TLSv1.3",
            "accept-language": "*",
            "x-amz-date": "20241014T100603Z",
            "x-forwarded-proto": "https",
            "x-forwarded-port": "443",
            "x-forwarded-for": "92.177.95.206",
            "x-amz-security-token": "....",
            "accept": "*/*",
            "x-amzn-tls-cipher-suite": "TLS_AES_128_GCM_SHA256",
            "x-amzn-trace-id": "Root=1-670ced0b-7334ddc02966931874eeca3c",
            "host": "api-id.lambda-url.eu-west-1.on.aws",
            "accept-encoding": "br, gzip, deflate",
            "user-agent": "node"
        },
        "requestContext": {
            "accountId": "123456789023",
            "apiId": "api-id",
            "authorizer": {
                "iam": {
                    "accessKey": "ASIA...............Z",
                    "accountId": "123456789023",
                    "callerId": "AROA...............Z:aamorosi-role",
                    "cognitoIdentity": null,
                    "principalOrgId": null,
                    "userArn": "arn:aws:sts::123456789023:assumed-role/Admin/aamorosi-role",
                    "userId": "AROA...............Z:aamorosi-role"
                }
            },
            "domainName": "api-id.lambda-url.eu-west-1.on.aws",
            "domainPrefix": "api-id",
            "http": {
                "method": "POST",
                "path": "/",
                "protocol": "HTTP/1.1",
                "sourceIp": "92.177.95.206",
                "userAgent": "node"
            },
            "requestId": "1a2de596-d389-469e-ba8f-6d0b080b8297",
            "routeKey": "$default",
            "stage": "$default",
            "time": "14/Oct/2024:10:06:04 +0000",
            "timeEpoch": 1728900364011
        },
        "body": "AAAAAAAAAAAAAA==",
        "isBase64Encoded": true
    }
}
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 Jan 23, 2025
Copy link
Contributor

This is now released under v2.13.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 Jan 28, 2025
@dreamorosi dreamorosi moved this from Coming soon to Shipped in Powertools for AWS Lambda (TypeScript) Jan 28, 2025
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 parser This item relates to the Parser Utility
Projects
2 participants