nitro
nitro copied to clipboard
aws-lambda preset should use cookies vs set-cookie header in response
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