Sending custom status codes from AWS Lambda - amazon-web-services

I have a Lambda#Edge function set up in AWS that checks incoming requests for a given cookie and rejects the request if it's not present, or if it is present it modifies the request for another operation. So far the code works correctly in that the request is 'rejected' if the cookie isn't present, and passes if it is present, but the response I am getting from the Lambda is a 502 which isn't really appropriate for what I'm trying to achieve. Abbreviated code below:
exports.handler = (event, context, callback) => {
const request = event.Records[0].cf.request;
const requestHeaders = request.headers;
const response = event.Records[0].cf.response;
// Perform cookie check
const downloadCookie = 'download-cookie=true';
let downloadCookieFound = false;
if (requestHeaders.cookie) {
for (let i = 0; i < requestHeaders.cookie.length; i++) {
if (requestHeaders.cookie[i].value.indexOf(downloadCookie) >= 0) {
downloadCookieFound = true;
break;
}
}
}
// Reject if cookie not present
if (!downloadCookieFound) callback(null, request);
// I have tried this as well but it doesn't seem to work
// if (!downloadCookieFound) callback(null, { statusCode: 403, body: 'Cookie missing' };
// Perform other operations
// ...
//Return modified response
callback(null, response);
};
How do I get the Lambda to return a 403 response instead of this 502:
502 ERROR
The request could not be satisfied.
The Lambda function returned invalid JSON: The JSON output must be an object type. We can't connect to the server for this app or website at this time. There might be too much traffic or a configuration error. Try again later, or contact the app or website owner.
If you provide content to customers through CloudFront, you can find steps to troubleshoot and help prevent this error by reviewing the CloudFront documentation.

Solved it with luk2302's aid - change the line:
if (!downloadCookieFound) callback(null, request);
To:
if (!downloadCookieFound) callback(null, { status: '403' });
Other information can be added to the returned object if desired.

Related

Google Cloud Function with Basic Auth not working properly

React Client Code - Using request promises to send username and password in Header
var password = values.password;
var email = values.email;
request
.head(
"https://us-central1-simplineet-754e8.cloudfunctions.net/CreateUserAuth"
)
.set('Content-Type', 'application/x-www-form-urlencoded')
.auth(email, password, false)
.query(dataobj)
.then(res => {
console.log(res);
if (res.statusCode === 200) {
console.log("statusText",res.body);
} else {
console.log("statusText",res.statusText);
}
})
.catch(err => {});
Backend - Google Cloud Function to Handle Basic Auth Requests from Client
const express = require('express');
const app = express();
const cors = require('cors');
app.use(cors({origin: true}));
exports.CreateUserAuth = functions.https.onRequest((request, response) => {
var corsFn = cors();
corsFn(request, response, function () {
// Request Header
response.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
response.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type');
response.setHeader('Access-Control-Allow-Credentials', true);
response.setHeader('Access-Control-Allow-Origin', '*');
var auth = require('basic-auth') // basic-auth NPM package to extract username and password from header
var user = auth(request)
var email = user.name; // Getting username from Auth
var password = user.pass; // Getting password from Auth
var username = request.query.username;
response.send('Hello from Firebase!'); // Not getting this response in Client
});
});
Response Getting in Client :
Response {req: Request, xhr: XMLHttpRequest, text: null, statusText: "", statusCode: 200, …}
As per MDN docs, HEAD responses should not have a body:
The HTTP HEAD method requests the headers that are returned if the specified resource would be requested with an HTTP GET method. Such a request can be done before deciding to download a large resource to save bandwidth, for example.
A response to a HEAD method should not have a body. If so, it must be ignored. Even so, entity headers describing the content of the body, like Content-Length may be included in the response. They don't relate to the body of the HEAD response, which should be empty, but to the body of similar request using the GET method would have returned as a response.
My guess is that GCP is handling it as a GET and stripping out the body before returning a response.
However, keep in mind that Google Cloud Functions HTTP trigger docs don't explicitly say that HEAD is a supported method:
You can invoke Cloud Functions with an HTTP request using the POST, PUT, GET, DELETE, and OPTIONS HTTP methods.
It looks like you are making a HEAD request instead of a POST request. Change to request.post() and it should work

AWS Cognito TOKEN Endpoint giving a 400 Bad Request error "unauthorized_client"

Following the documentation from https://docs.aws.amazon.com/cognito/latest/developerguide/token-endpoint.html after successfully retrieving an authentication code.
As far as I can tell this is exactly how the request is supposed to be setup:
import request from 'request'
function fetchToken(code: any, clientId: string, clientSecret: string) {
try {
let tokenEndpoint = `https://example.auth.us-east-1.amazoncognito.com/oauth2/token`
const clientIdEncoded = Buffer.from(`${clientId}:${clientSecret}`).toString('base64')
request.post({
url:tokenEndpoint,
headers: {
'Content-Type':'application/x-www-form-urlencoded',
'Authorization':`Basic ${clientIdEncoded}`
},
form: {
code,
'grant_type':'authorization_code',
'client_id':clientId,
'redirect_uri':'http://localhost:3000'
}},
function(err,httpResponse,body){
console.log(httpResponse.statusCode)
//400
console.log(httpResponse.statusMessage)
//Bad Request
if(err) {
console.error(err)
}
console.log(body)
//{"error":"unauthorized_client"}
})
} catch (error) {
console.error(error)
}
}
Why would be getting unauthorized_client? Is there an easier way to debug this?
Edit: tested this in Postman with the same request and getting the same error
Headers
Body
Please check if the Cognito User Pool App is using secret key. If you have created with secret key option, that must be included in the Authorization header of the request.

CORS error with API Gateway and Lambda **only** when using Proxy Integration

I am trying to add an Item to DynamoDB upon a post request from API Gateway using Lambda.
This is what my Lambda code looks like:
var AWS = require('aws-sdk');
var dynamoDB = new AWS.DynamoDB();
exports.handler = (event, context, callback) => {
var temp_id = "1";
var temp_ts = Date.now().toString();
var temp_calc = event['params']['calc'];
var params = {
TableName:"calc-store",
Item: {
Id: {
S: temp_id
},
timestamp: {
S: temp_ts
},
calc: {
S: temp_calc
}
}
};
dynamoDB.putItem(params,callback);
const response = {
statusCode: 200,
headers: {
'content-type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
body: event['params']['calc']
};
callback(null, response);
};
This is how I am calling the function from my client
axios.post(apiURL, {params:{calc:calc}})
.then ((res) => {
console.log(res);
})
I have enabled CORS over 30 times on my API Gateway, and I've also double checked by adding headers to the response. But no matter what I do, I keep getting a CORS error and for some reason, in my response, I can see that the "Access-Control-Allow-Origin" header is not being appended.
POST https://egezysuta5.execute-api.us-east-1.amazonaws.com/TEST 502
localhost/:1 Failed to load https://egezysuta5.execute-api.us-east-
1.amazonaws.com/TEST: No 'Access-Control-Allow-Origin' header is
present on the requested resource. Origin 'http://localhost:3000' is
therefore not
allowed access. The response had HTTP status code 502.
createError.js:17 Uncaught (in promise) Error: Network Error
at createError (createError.js:17)
at XMLHttpRequest.handleError (xhr.js:87)
I tried not using Lambda Proxy Integration, and it worked then, however, I was unable to access the params I passed.
EDIT: After spending hours on this, here is what I've boiled the problem down to. My client is making a successful pre-flight request to OPTIONS. The OPTIONS is successfully returning the correct CORS headers, but for some reason, these are not being passed to my POST request!
EDIT2: (This does not solve the problem) If I change the response body to a string there is no error!! There is something wrong with
event['params]['calc']
Your problem is with the flow of the code. Basically you're not waiting for putItem to complete before callback gets executed...Try this...
dynamoDB.putItem(params,(err,data) => {
if(err){
return callback(err, null)
}
const response = {
statusCode: 200,
headers: {
'content-type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
body: JSON.parse(event.body).calc
};
return callback(null, response);
});
There are 2 issues going on here:
your code is crashing because you are probably trying to access a null property in the event object
because your code fails before you can return the full response, the proper cors headers don’t get sent back to the browser.
Always try and catch errors in your lambda code. Always log the error and return the full response with a status code of 500, in the case of an error. Also, it’s important to handle async functions, like putItem with promises. Really grasp that concept before working with JavaScript!

Stop subsequent queries in apollo after 401 has been returned?

I am using apollo client to make a query in my Component. It is composed with 2 queries. How do i stop it from sending another query to it after it has given me a 401 error. I am using a onError Apollo Link Error to listen for errors. However it dispatches both queries and i cannot stop the next one.
Apollo Link Error allows you to intercept and handle query or network errors. It doesn't however provide an opportunity to manage subsequent requests. For this you will need to create your own link.
I've used something like the following in the past. The example below specifically handles bearer auth with refresh tokens but the same principle could be used to handle any auth failure.
import { ApolloLink, Observable } from 'apollo-link';
const isAuthError = (statusCode: number) => [401, 403].includes(statusCode);
const authLink = new ApolloLink((operation, forward) => {
// Set outgoing Authorization headers
const setHeaders = () =>
operation.setContext(({ store, headers, ...rest }) => {
// get the authentication token from local storage if it exists
const token = localStorage.getItem('token');
// return the headers to the context so httpLink can read them
return {
...rest,
store,
headers: {
...headers,
authorization: `Bearer ${token}`
}
};
});
setHeaders();
return new Observable(obs => {
const subscriber = {
next: obs.next.bind(obs),
// Handle auth errors. Only network or runtime errors appear here.
error: error => {
if (isAuthError(error.statusCode)) {
// Trigger an auth refresh.
refreshTokenOrLogin()
.then(setHeaders)
.then(() => forward(operation).subscribe(subscriber));
}
}
});
} else {
obs.error(error);
}
},
complete: obs.complete.bind(obs)
};
forward(operation).subscribe(subscriber);
});
});
The first portion sets the auth context as documented by Apollo. You should replace this with whichever auth mechanism you are using.
operation.setContext(({ store, headers, ...rest }) => {
// get the authentication token from local storage if it exists
const token = localStorage.getItem('token');
// return the headers to the context so httpLink can read them
return {
...rest,
store,
headers: {
...headers,
authorization: `Bearer ${token}`
}
};
});
Non terminating links like this must return an observable. This allows us to catch any network errors just as Apollo Link Error does except we can now handle what happens subsequently. In this case we create and return a new observable with an error handler that will trigger an auth token refresh and then retry the request. The next and completion handlers are passed through to the next link untouched.
new Observable(obs => {
const subscriber = {
next: obs.next.bind(obs),
// Handle auth errors. Only network or runtime errors appear here.
error: error => {
if (isAuthError(error.statusCode)) {
// Trigger an auth refresh.
refreshTokenOrLogin()
.then(setHeaders)
.then(() =>
// We can now retry the request following a successful token refresh.
forward(operation).subscribe(subscriber)
);
}
}
});
} else {
obs.error(error);
}
},
complete: obs.complete.bind(obs)
};
forward(operation).subscribe(subscriber);
});
It might be easier to think of this as 2 links. One that sets the outgoing auth context and the other that captures the response and handles the auth errors.

aws cognito && apiGateway returns status 401

I did cors configuration in response header of my api correctly.
when I tested my api via postman with a validated token
attached in header('Authorization'), it returned 200.
i checked my frontend fetch code to request that api, it seems that there is no error or fault.
how can it happen? does anyone who suffered from same as what I'm struggling now.
Added:
my front end fetch code looks like this.
export const getDoc = async (docId, token) => {
const path = `${apiGateway.URL}`;
const body = {
docId: docId
};
const headers = {
Authorization: token,
'Content-Type': 'application/json'
};
const result = await fetch(path, {
body,
headers,
});
if (result.status !== 200) {
throw new Error('failed to get doc');
}
return result.json();
};
you should just enter "Authorization" into the "Token Source" field, NOT 'method.request.headers.Authorization'. Otherwise, you will get a 401 error.