I have a lambda function and I wish to perform a 301 redirect from https://foo.test.com/user_id/bar/ to https://app.test.com/user_id/new
The user's request has an user_id that set as a variable (${userId}) and passed to the target URI (${newUri}).
The value of the user_id is numbers and letters (12-qw12)
I wrote the following code, which sets the ID as a variable and passes it to the Response Headers:
exports.handler = function handler(event, context, callback) {
let request = event.Records[0].cf.request;
let uri = request.uri;
let userId = uri.split('/')[3];
let newUri = `https://app.test.com/` + userId + `/new`;
const response = {
headers: {
'location': [{
key: 'Location',
value: newUri,
}],
},
status: '301',
statusDescription: 'Moved Permanently',
};
callback(null,response);
};
Unfortunately, I get an empty value from the ${userId} that passed to the Response headers:
https://app.test.com//new
If I write the value in the response headers as a string (value: 'https://app.test.com/12-qw12/new') then it works properly.
How can I pass a variable (${userId}) to the response headers without getting an empty value?
You can view the Request Event structure
here:
You can see the actual value of the request URI field by using console.log and checking the respective logs in Cloudwatch.
To print request uri add the folowing the following line before the last two lines in your code:
console.log(Request uri is "${request.uri}");
Then you can know in which index you read userid value.
I have a problem with setting up the basic authentication for my AWS lambda function written in Node.js.
The problem:
AWS lambda function which is a proxy for an additional service. This function only forwards the whole request and give the user the whole response. That's why I need to force the usage of the Authentication header and I would like to have the prompt window for passing the credentials: https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication
Apart from the proxy part of my lambda function, I focused on the problem with authentication and I have written this code:
export const proxy = async (event) => {
const authorizationHeader = event.headers.Authorization;
if (typeof authorizationHeader === undefined) {
throw new Error("Unauthorized");
}
...
};
service:
name: proxy-auth-test
plugins:
- serverless-webpack
provider:
name: aws
runtime: nodejs8.10
memorySize: 128
timeout: 10
functions:
proxy-async:
handler: handler.proxy
events:
- http:
method: get
path: api/proxy
resources:
Resources:
GatewayResponse:
Type: 'AWS::ApiGateway::GatewayResponse'
Properties:
ResponseParameters:
gatewayresponse.header.WWW-Authenticate: "'Basic'"
ResponseType: UNAUTHORIZED
RestApiId:
Ref: 'ApiGatewayRestApi'
StatusCode: '401'
The endpoint is working properly, but I can't get the prompt window for passing the credentials. I set up the GatewayResponse according to this https://medium.com/#Da_vidgf/http-basic-auth-with-api-gateway-and-serverless-5ae14ad0a270 but I don't wanna provide the additional lambda function which is responsible only for authorization of the users.
In my case, I can't authorize the users before executing the final lambda function because this function only forwards the request (credentials too), nothing more.
Has anyone ever tried to setup basic auth with the prompt window without the additional authorizer with the usage of serverless and AWS lambda?
When returning a response from an integration the WWW-Authenticate is remapped to X-Amzn-Remapped-WWW-Authenticate (1). Browsers will not handle this remapped header so they don't show a prompt.
This means that you have to move your authorization logic to the Lambda Authorizer at a HTTP request level and return 'unauthorized' to the callback as stated in the medium link that you referenced. This is the only way to return a WWW-Authenticate header as of now.
Sources:
1: https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-known-issues.html
You are not returning statusCode in the response. The article you are following seems to be using Custom Authorizer that always returns 401 status code with callback('Unauthorized'). Your Lambda function needs to return appropriate error code and headers.
if (typeof authorizationHeader === undefined) {
return {
statusCode: 401,
body: 'unauthorized',
headers: {
'WWW-Authenticate': 'Basic'
}
}
}
I've built a custom authorizer lambda function with NodeJS, which I've configured to authorize another lambda function in AWS. This other function is triggered from an HTTP endpoint, and has the URL that I configured in my Twilio Messaging Service as the webhook URL with a GET method.
I have to use GET, because AWS does not include a POST request's headers into the input param of the authorizer function, and I need the header in order to get the X-Twilio-Signature.
In the authorizer function, I'm invoking the Twilio Node Helper validateRequest(token, signature, url, params) function and providing my auth token, the Twilio signature from the request header, and the exact same URL as configured in the webhook (no query params, no frills, just an https url with a path to the api resource).
However, the params is where I think things are breaking down and why the validation fails.
Since I'm using a GET method for the webhook, does that mean that when Twilio created the signature hash on their end, there was no POST data appended (per their docs on https://www.twilio.com/docs/api/security), or should I provide all the form data which they provide in the querysting of my GET request??
No matter what I've tried, my validation keeps failing as if the params I'm using are different than what Twilio did to create the signature.
I've created a simple test to see if I can validate the request using the params and signature of an actual HTTP request I made, but it never seems to work. Here's my simple test:
const token = '[my auth token]';
const url = 'https://my-api.company.io/sms/receive';
const signature = '[twilio header signature]';
const params = {
MessagingServiceSid: '[sid to my msg svc]',
ApiVersion: '2010-04-01',
SmsSid: 'SM6b3e14ea5e87ff967adb0c00c81406b8',
SmsStatus: 'received',
SmsMessageSid: 'SM6b3e14ea5e87ff967adb0c00c81406b8',
NumSegments: '1',
ToState: 'TX',
From: '+19998675309',
MessageSid: 'SM6b3e14ea5e87ff967adb0c00c81406b8',
AccountSid: '[my account sid]',
ToZip: '75229',
ToCity: 'DALLAS',
FromCountry: 'US',
FromCity: 'IRVING',
To: '[my twilio number]',
FromZip: '75014',
ToCountry: 'US',
Body: 'Super duper',
NumMedia: '0',
FromState: 'TX'
};
const result = twilio.validateRequest(token, signature, url, params);
console.log(result);
UPDATE
To respond to an answer from Phil (Twilio Dev Evangelist), here's what I see in the logs from my authorizer function when I switch to using a POST webhook URL (this wouldn't fit in a comment, so I'm editing the Q).
Note that this payload does not have any of the above mentioned parameters which are provided by Twilio in the body of the POST request and which I'd presumably need to provide to the twilio.validateRequest function:
{
type: 'REQUEST',
methodArn: 'arn:aws:execute-api:us-east-1:********:********/dev/POST/receive',
resource: '/receive',
path: '/sms/receive',
httpMethod: 'POST',
headers: {
Accept: '*/*',
'CloudFront-Viewer-Country': 'US',
'CloudFront-Forwarded-Proto': 'https',
'CloudFront-Is-Tablet-Viewer': 'false',
'CloudFront-Is-Mobile-Viewer': 'false',
'User-Agent': 'TwilioProxy/1.1',
'X-Forwarded-Proto': 'https',
'CloudFront-Is-SmartTV-Viewer': 'false',
Host: 'api.myredactedcompany.io',
'X-Forwarded-Port': '443',
'X-Amzn-Trace-Id': 'Root=**************',
Via: '1.1 ***************.cloudfront.net (CloudFront)',
'Cache-Control': 'max-age=259200',
'X-Twilio-Signature': '***************************',
'X-Amz-Cf-Id': '****************************',
'X-Forwarded-For': '[redacted IP addresses]',
'Content-Length': '492',
'CloudFront-Is-Desktop-Viewer': 'true',
'Content-Type': 'application/x-www-form-urlencoded'
},
queryStringParameters: {},
pathParameters: {},
stageVariables: {},
requestContext: {
path: '/sms/receive',
accountId: '************',
resourceId: '*****',
stage: 'dev',
requestId: '5458adda-ce2c-11e7-ba08-b7e69bc7c01c',
identity: {
cognitoIdentityPoolId: null,
accountId: null,
cognitoIdentityId: null,
caller: null,
apiKey: '',
sourceIp: '[redacted IP]',
accessKey: null,
cognitoAuthenticationType: null,
cognitoAuthenticationProvider: null,
userArn: null,
userAgent: 'TwilioProxy/1.1',
user: null
},
resourcePath: '/receive',
httpMethod: 'POST',
apiId: '*******'
}
}
Twilio developer evangelist here.
The issue here is that query string parameters are treated differently to POST body parameters when generating the signature.
Notably part 3 of the steps used to generate the request signature says:
If your request is a POST, Twilio takes all the POST fields, sorts them by alphabetically by their name, and concatenates the parameter name and value to the end of the URL (with no delimiter).
(Emphasis mine.)
This means that if you are trying to reconstruct the original URL, you will need to reconstruct the original query string with &s and =. The difficulty I can see here is that you don't know the original order of the parameters and I don't know if the order is not arbitrary.
Issue number 2 is that the Request authorizer will not send the POST body to a Lambda function.
So, either way you try to work it, a custom authorizer will never get all the details that the Twilio request validator requires to validate the request.
My only advice now is to move away from using the authorizers and just build request validation into your final Lambda function. It's not as nice as separating the concerns of validating the request and responding to the request, but since custom authorizers do not support all the features required, it's the only thing I can think of right now.
Now, the other thing that caught my eye was saying that you couldn't get the headers in a POST request authorizer. I had a look around and this Stack Overflow answer suggests it is now possible (only since September) to receive all the headers to a POST request to a custom authorizer, as long as you use the Request type authorizer and not a Token authorizer.
So my advice would be to change over to a Request authorizer, a POST request webhook from Twilio and the code you already have should work.
Let me know if that helps at all.
I have an OpenWhisk action that returns a response object, because I want to be able to control the headers and HTTP status code. My action returns something like this:
return {
statusCode: 200,
headers: { 'Content-Type': 'application/json' },
body: { x: 1 }
};
I deploy the action in a package from the command-line with:
wsk action update myproj/myaction --kind nodejs:6 myaction.zip --web true
And expose it as API on IBM Cloud Functions with:
wsk api create /myproj /myaction get myproj/myaction
But when I visit the API call with curl, I get the whole response object, not just the data:
curl '.../myproj/myaction'
{
"statusCode": 200,
"headers": {
"Content-Type": "application/json"
},
"body": { x: 1 }
}
I was expecting to get just { x: 1 }.
What do I need to do to fix this?
The default API Gateway service behaviour expects body data to be returned from the action, not the full HTTP response parameters.
Change the return statement to the following to resolve this issue.
return {
x: 1
};
Controlling the full HTTP response using the returned action parameters needs the --response-type http flag setting on the web action.
$ wsk api create /myproj /myaction get myproj/myaction --response-type http
I want to display on my chatbot the link.
message: { contentType: 'PlainText', content:"Test Result" },
html tag displays as it is. How can I display the content as html?
You need to set the content-type to text/html in the headers of your response object, as typically it is (implicitly) set to application/json.
Projected to your case, you need to return your function in your AWS Lambda handler like:
callback(null, {
statusCode: 200,
headers: {"content-type": "text/html"},
body: "<html><body>OK</body></html>"
})
That will set the content type correctly for the client to parse.
For those that are using the serverless framework, you can configure the content-type of your function's response in your serverless.yml file, for example:
downloadImage:
handler: lib/client-app-services.downloadImage
events:
- http:
path: client/images/{filename}
method: get
cors: true
integration: lambda
response:
headers:
Content-Type: "'image/jpeg'"
Cache-Control: "'max-age=120'"