Skip to content
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

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

Open
dreamorosi opened this issue Oct 14, 2024 · 0 comments
Open
Labels
bug Something isn't working confirmed The scope is clear, ready for implementation 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
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working confirmed The scope is clear, ready for implementation parser This item relates to the Parser Utility
Projects
Development

No branches or pull requests

1 participant