nitro icon indicating copy to clipboard operation
nitro copied to clipboard

aws-lambda preset should use cookies vs set-cookie header in response

Open brtinney opened this issue 2 years ago • 0 comments

Environment

aws-lambda deployment setting

nitro via [email protected]

Reproduction

See below

Describe the bug

It's not possible to pass multiple cookies with the set-cookie header via AWS Lambda, so the normalizeOutgoingHeaders does not help with cookies in responses.

const handler = async function handler2(event, context) {
  const url = withQuery(event.path || event.rawPath, event.queryStringParameters || {});
  const method = event.httpMethod || event.requestContext?.http?.method || "get";
  if ("cookies" in event && event.cookies) {
    event.headers.cookie = event.cookies.join(",");
  }
  const r = await nitroApp.localCall({
    event,
    url,
    context,
    headers: normalizeIncomingHeaders(event.headers),
    method,
    query: event.queryStringParameters,
    body: event.body
  });
  return {
    statusCode: r.status,
    headers: normalizeOutgoingHeaders(r.headers),
    body: r.body.toString()
  };
};
function normalizeIncomingHeaders(headers) {
  return Object.fromEntries(Object.entries(headers).map(([key, value]) => [key.toLowerCase(), value]));
}
function normalizeOutgoingHeaders(headers) {
  return Object.fromEntries(Object.entries(headers).map(([k, v]) => [k, Array.isArray(v) ? v.join(",") : v]));
}

This results in issues like the following for some requests (e.g. adapted @auth0/nextjs code for SSR-compatible serverless auth), as only the first cookie is actually set:

{"url":"/api/auth/callback?code=xxx&state=yyy","statusCode":400,"statusMessage":"checks.state argument is missing","message":"checks.state argument is missing","description":""}

Additional context

Documentation here: https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html#http-api-develop-integrations-lambda.v2

I was able to fix the issue with this hack to the handler, which is very inelegant:

const handler = async function handler2(event, context) {
  const url = withQuery(event.path || event.rawPath, event.queryStringParameters || {});
  const method = event.httpMethod || event.requestContext?.http?.method || "get";
  if ("cookies" in event && event.cookies) {
    event.headers.cookie = event.cookies.join("; ");
  }
  const r = await nitroApp.localCall({
    event,
    url,
    context,
    headers: normalizeIncomingHeaders(event.headers),
    method,
    query: event.queryStringParameters,
    body: event.body
  });
  const headers = normalizeOutgoingHeaders(r.headers);
  return Object.assign({
    statusCode: r.status,
    body: r.body.toString()
  }, headers);
};
function normalizeIncomingHeaders(headers) {
  return Object.fromEntries(Object.entries(headers).map(([key, value]) => [key.toLowerCase(), value]));
}
function normalizeOutgoingHeaders(headers) {
  const obj = {
    headers: {},
    cookies: []
  };
  for (const k in headers) {
    const v = headers[k];
    if (k.toLowerCase() === 'set-cookie') {
      if (Array.isArray(v)) {
        obj.cookies = obj.cookies.concat(v);
      } else {
        obj.cookies = obj.cookies.concat(v.split(','));
      }
    } else {
      obj.headers[k] = v;
    }
  }
  return obj;
}

Note that I also changed the .join() to use ; because the cookie packages expects that format.

Apologies if this should be on nuxt/framework instead. This took me many hours to find out, and I just wanted to make sure it was documented somewhere to eventually get fixed, so that my hack is no longer required as a post-build step.

Logs

No response

brtinney avatar May 16 '22 05:05 brtinney