Using AWS API Gateway SDK in Node.JS causes issues - node.js

So I am trying to follow the AWS API Gateway SDK tutorial on setting up a secure connection between the NodeJS App and API Gateway link here.
My test function looks like something like below:
require('./lib/apigClient');
require('./lib/axios/dist/axios.standalone');
require('./lib/CryptoJS/rollups/hmac-sha256');
require('./lib/CryptoJS/rollups/sha256');
require('./lib/CryptoJS/components/hmac');
require('./lib/CryptoJS/components/enc-base64');
require('./lib/url-template/url-template');
require('./lib/apiGatewayCore/sigV4Client');
require('./lib/apiGatewayCore/apiGatewayClient');
require('./lib/apiGatewayCore/simpleHttpClient');
require('./lib/apiGatewayCore/utils');
var body = {
"type": "the_best_kind",
"status": "processed",
"name": "test123"
};
export function getRatingsTest(req, res) {
var apigClient = apigClientFactory.newClient({
accessKey: 'ACCESS_KEY',
secretKey: 'SECRET_KEY',
sessionToken:'SESSION_TOKEn', //OPTIONAL: If you are using temporary credentials you must include the session token
region: 'eu-west-1' // OPTIONAL: The region where the API is deployed, by default this parameter is set to us-east-1
});
console.log('NEW CLIENT =====', apigClient)
apigClient.calcRatingPost(null, body, null)
.then(function(result){
//This is where you would put a success callback
}).catch( function(result){
//This is where you would put an error callback
});
return res.json({test: 123})
}
I keep getting error such a apiGateway is not defined, CryptoJs is not defined and so on. All the 11 javascript files are not modular which is unfortunate.
What is the best approach for getting this to work in NodeJS?
I also tried the aws-api-gateway-client npm package but no luck yet.

Related

How do I initiate a conversation with AWS LEX from node js?

My context is this: I am attempting to build a chat bot into my Mozilla Hubs client, which is a node js / React project. I have a lex bot created on AWS, and I have installed the client-lex-runtime-v2 package and can import it successfully, but I have no idea how to set up a StartConversationCommand, give it credentials, etc. Most of the javascript examples seem to go the other way, where Lex calls a lambda function after processing a request, but I have user input in my app and I need to send it to Lex, and then deliver the resulting text back to the user inside my application.
This seems like very basic Lex usage - if anyone could point me to a code example I would be most grateful.
John,
You need to make use of the LexRuntimeV2Client in the SDK as demonstrated here.
From the linked documentation, the below is how you import and instantiate a new client:
import { LexRuntimeV2Client, DeleteSessionCommand } from "#aws-sdk/client-lex-runtime-v2";
const client = new LexRuntimeV2Client({ region: "REGION" });
Once configured with your respective AWS environment details, credentials etc you will be able to invoke your Lex bot (again, from the linked documentation):
try {
const data = await client.send(command);
// process data.
} catch (error) {
// error handling.
} finally {
// finally.
}
Take a look at this sample repo on GitHub as well: aws-lex-web-ui
So for anyone else stuck where I was, I cannot say this is the right way to do it, because I never did get it working, but the closest I got to at least forming up my credentials and trying to make a connection was this:
const client = new LexRuntimeV2Client({
region: "us-east-1",
credentials: new AWS.Credentials({
accessKeyId: "my_IAM_access_key_id",
secretAccessKey: "my_secret_access_key"
})
});
const lexparams = {
"botAliasId": "my alias_id",
"botId": "bot_id_from_lex",
"localeId": "en_US",
"inputText": "hello, this is a test sample",
"sessionId": "some_session_id"
};
let cmd = new StartConversationCommand(lexparams);
try {
const data = await client.send(cmd);
console.log("Success. Response is: ", data.message);
} catch (err) {
console.log("Error responding to message. ", err);
}
As said, buyer beware, and best of luck out there! I hope this might help somebody in some slight way. We taking a momentary break on this problem until a member of our team with better aws fu can take a look at it. :-)
This is working for me:
var AWS = require('aws-sdk');
const { LexRuntimeV2 } = require("#aws-sdk/client-lex-runtime-v2");
const lexruntime = new LexRuntimeV2({
region: "us-west-2", // Set your Bot Region
credentials: new AWS.Credentials({
accessKeyId: "***", // Add your access IAM accessKeyId
secretAccessKey: "***" // Add your access IAM secretAccessKey
})
});
const lexparams = {
"botAliasId": "HG****", // Enter the botAliasId
"botId": "HG***", // Enter the botId
"localeId": "en_US",
"text": "Book Car",
"sessionId": "some_session_id"
};
lexruntime.recognizeText(lexparams, function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data); // successful response
});

AWS sdk get credentials using sso

I'm trying to use the AWS secrets manager, when I'm using regular credentials its works fine.
but I want to use SSO for it. when I don't have the .aws/credentials file and only .aws/config file.
In AWS documentation i saw this functions:
var params = {
accessToken: 'STRING_VALUE', /* required */
accountId: 'STRING_VALUE', /* required */
roleName: 'STRING_VALUE' /* required */
};
sso.getRoleCredentials(params, function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data); // successful response
});
but I don't understand where I can get the access token, account id, and role name.
indeed I saw that in the .aws/config file there is an account id and role name, but I don't understand how can I get them into my code (maybe something like the function SharedIniFileCredentials) and also how can I get the access token?
Also,
I tried to add this env variable AWS_SDK_LOAD_CONFIG=1 but still I'm getting this error:
Missing credentials in config, if using AWS_CONFIG_FILE, set AWS_SDK_LOAD_CONFIG=1
You can do this with credential-provider-sso. The documentation gives a full explanation but a quick example:
const { SecretsManager } = require('#aws-sdk/client-secrets-manager');
const { fromSSO } = require('#aws-sdk/credential-provider-sso');
const secretsManager = new SecretsManager({credentials: fromSSO()});
secretsManager.listSecrets({}, (err, data) => {
console.error(err);
console.info(data);
})
This will use the profile configured in the AWS_PROFILE environment variable, or you can pass {'profile': 'profilename'} to the fromSSO function.

Can I run Cognito in a Lambda function?

I want to sign up users with Cognito in a Lambda function. However I am receiving "TypeError: fetch is not a function"
My code is basically step 3 in this. However I keep getting the above-mentioned error, even though I have node-fetch installed. From what I understand, the Cognito SDK makes use of fetch. So is this simply not possible? Will I need to spin up a Node server?
const AWS = require("aws-sdk");
const AmazonCognitoIdentity = require("amazon-cognito-identity-js");
//Configuring pool data of Cognito Identity Pool
const poolData = {
UserPoolId: "us-east-2_aCvZbFzeS",
ClientId: "4nv2krchr77pbrq3cpk0q0kknu"
};
const userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
AWS.config.region = "us-east-2";
const attributeList = [];
attributeList.push(
new AmazonCognitoIdentity.CognitoUserAttribute({
Name: "email",
Value: "sampleEmail#gmail.com"
})
);
userPool.signUp(
"sampleEmail#gmail.com",
"SamplePassword123",
attributeList,
null,
function(err, result) {
if (err) {
console.log(err);
return;
}
const cognitoUser = result.user;
console.log("user name is " + cognitoUser.getUsername());
}
);
const data = JSON.parse(event.body);
const headers = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Credentials": true
};
const response = {
statusCode: 200,
headers: headers,
"Content-Type": "application/json",
body: JSON.stringify(data.age)
};
callback(null, response);
};
//I keep receiving this error when attempting to hit the endpoint with Postman:
"errorMessage": "Uncaught error in your 'hello' handler",
"errorType": "TypeError",
"stackTrace": [
"TypeError: fetch is not a function"
You can definitely use Cognito from Lambda! Source: have done it.
You may not be able to use the AWS Cognito JS SDK from Lambda nicely, though.
The AWS Cognito JS SDK appears to be designed for client-side applications, where fetch is a built-in. You have installed node-fetch, but the SDK is not loading it because it doesn't think it needs to, because it is expecting it to be built-in.
I see two options:
If you aren't particularly attached to JS, you could use another language where you are confident that the library is designed and tested for server-side applications.
If you are attached to JS or have a large sunk cost, you could hack up the AWS Cognito JS SDK locally before deploying the code to Lambda to make it require node-fetch or otherwise make it functional server-side.
This thread has a good description of the same issue and some workarounds; probably the best one for you is:
global.fetch = require('node-fetch')
const AmazonCognitoIdentity = require('amazon-cognito-identity-js');
in your script, which should make it appear as a built-in to the SDK's code without hacking up the internals.

Unable to use custom authorizer in API Gateway

I have a couple of days trying to secure my API Gateway using custom authorizers with the auth0 service. I have my lambda which validates my bearer token, the Lambda does work if I invoke it inside the AWS console and when I create a custom authorizer I can successfully tested with a Bearer token.
When I try to attach the authorizer to my API Gateway methods and test the request with postman and the token provided by auth0 it always returns a 401 status code. I read my logs in CloudWatch and the authorization Lambda it's never triggered whenever I make the HTTP request. I am following this tutorial:
https://auth0.com/docs/integrations/aws-api-gateway/custom-authorizers/
And this is my Authorization lambda code:
Handler:
'use strict';
let jwtManager = require("./jwt_manager");
module.exports.authenticate = (event, context, callback) => {
jwtManager.validate(event, function (error, data) {
if (error) {
if (!error) {
context.fail("Unhandled error");
}
context.fail(error);
}
else {
console.log("SUCCEED");
context.succeed(data);
}
});
};
And this is the jwtManager:
"use strict";
require("dotenv").config({ silent: true });
let jwksClient = require("jwks-rsa");
let jwt = require("jsonwebtoken");
module.exports.validate = function(params, callback) {
var token = validateParams(params);
var client = jwksClient({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 10,
jwksUri: process.env.JWKS_URI
});
var decodedJwt = jwt.decode(token, { complete: true });
var kid = decodedJwt.header.kid;
client.getSigningKey(kid, function(error, data) {
if (error) {
console.log(error);
callback(error);
} else {
var signingKey = data.publicKey || data.rsaPublicKey;
jwt.verify(
token,
signingKey,
{ audience: process.env.AUDIENCE, issuer: process.env.ISSUER },
function(error, decoded) {
if (error) {
console.log("ERROR");
console.log(error);
callback(error);
}
else {
console.log(decoded);
var response = {
principalId: decoded.sub,
policyDocument: getPolicyDocument("Allow", params.methodArn),
context: {
scope: decoded.scope
}
}
console.log(response);
console.log(response.policyDocument);
callback(null, response);
}
}
);
}
});
};
function validateParams(params) {
var token;
if (!params.type || params.type !== "TOKEN") {
throw new Error("Expected 'event.type' parameter to have value TOKEN");
}
var tokenString = params.authorizationToken;
if (!tokenString) {
throw new Error("Expected 'event.authorizationToken' parameter to be set");
}
var match = tokenString.match(/^Bearer (.*)$/);
if (!match || match.length < 2) {
throw new Error(
"Invalid Authorization token - '" +
tokenString +
"' does not match 'Bearer .*'"
);
}
return match[1];
}
function getPolicyDocument(effect, resource) {
var policyDocument = {};
policyDocument.Version = '2012-10-17'; // default version
policyDocument.Statement = [];
var statementOne = {};
statementOne.Action = [ 'execute-api:Invoke', 'lambda:Invoke'] ; // default action
statementOne.Effect = effect;
statementOne.Resource = resource.split('/')[0] + '/*';
policyDocument.Statement[0] = statementOne;
return policyDocument;
}
Thanks in advance!
I would like to describe how I resolved this issue.
First thing, the custom authorizer always need bearer token in authorizationToken field but from while invoking API Gateway from Postman or any other client you can send the 'Bearer Token' in authorization header, as this is an industry standard, AWS has supported it.
The trick here is in 'Token Source' while configuring the 'custom authorizer'. I have attached an image here where you can configure that 'Token Source' this field describes that the input to custom authorizer is from 'Authorization Header'.
This way, you can still send the token in 'Authorzation' header from postman, and API Gateway would copy it from 'Authorization' header and copy it to 'authorizationToken' input field while invoking custom authorizer lambda.
Hope it's clear. Let me know if you need more details.
When you test an API Gateway with a custom authorizer attached but the auth lambda function never triggered, it is likely due to unsuccessful validation in token header name/ token pattern validation.
I am able to reproduce your issue.
The authorizer can only be triggered IF I change the header name from "Authorization" to "AuthorizationToken" in POSTMAN.
check the token header name I made the authorizer works
I think it is likely a new bug in AWS portal as I noticed they have changed the UI to configure API Gateway Authorizers not long ago.
It is very strange a HTTP request has to send bearer token in a header with name "AuthorizationToken". If your AWS plan allows you to access their technical support, you should alert them about this issue.
In my case, the same error (lambda not triggered, authorizer failing) was due to the fact that I didn't deployed the API yet. I may be wrong, but it seems that for testing an Authorizer, your API has to be deployed at least once.
So, I deployed the API, and the authorizer test began to work.
It isn't triggered this can be due to one of two reasons:
the APIGW authorizer response is cached
your configuration expects the token somewhere else.
In the case of the cache, you'll have to wait until it expires or use a different token. For the point of testing, you could remove the cache. In the latter cases, APIGW authorizers automatically reject requests with missing token when the request does not contain the token in the expected location. In those cases your authorizer is not even used.
You can see in this example the authorizer configuration looks at the Authorization header in the identity sources.
In the case you don't specify the authorization header, then the request is automatically rejected.
The other important part of the request is the Authorizer Type. Your code is validating the event.type to be TOKEN. But TOKEN is the legacy authorizer type. The current best practice is to use REQUEST. This exposes the whole request to your authorizer so that you can directly use the request.headers.Authorization header correctly.
It still isn't obvious the best way to handle this so I generally recommend something like this apigw authorizer library and then combining the parsing that library gives you with handling of the request. An example of how to handle the request can be seen here in an apigw authorizer.

Cloud Functions for Firebase: how to issue a request to my Cloud Endpoint

I'm trying to issue a request to my cloud endpoint project when a certain value is written in the firebase database. I can't find any example of how perform a request to Endpoints in Node.js. Here's what I come up with so far:
"use strict";
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const gapi = require('googleapis');
admin.initializeApp(functions.config().firebase);
exports.doCalc = functions.database.ref('/users/{uid}/calc').onWrite(event => {
return gapi.client.init({
'apiKey': 'AIzxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
'clientId': '1234567890-xxx.apps.googleusercontent.com',
'scope': 'donno what to put here'
}).then(function() {
return gapi.client.request({
'path': 'https://myproj.appspot.com/_ah/api/myApi/v1',
'params': {'query': 'startCalc', uid: event.params.uid }
})
}).then(function(response) {
console.log(response.result);
}, function(reason) {
console.log('Error: ' + reason.result.error.message);
});
});
When triggered, Functions' log spouts: TypeError: Cannot read property 'init' of undefined. i.e. doesn't even recognize gapi.client.
First, what is the right package to use for this request? googleapis? request-promise?
Second, am I setting up the correct path and parameters for a call to an endpoint? Assume the endpoint function is startCalc(int uid).
Update
It seems that Cloud Functions for Firebase blocks requests to their App Engine service - at least on the Spark plan (even though they're both owned by Google - so you'd assume "on the same network"). The request below, works on a local machine running Node.js, but fails on the Functions server, with a getaddrinfo EAI_AGAIN error, as described here. Evidently, it is not considered accessing Google API when you perform a request to your server running on Google's App Engine.
Can't explain why Firebase advocates here steer clear of this question like from fire.
Original Answer
Figured it out - switched to 'request-promise' library:
"use strict";
const functions = require('firebase-functions');
const request = require('request-promise');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.doCalc = functions.database.ref('/users/{uid}/calc').onWrite(event => {
return request({
url: `https://myproj.appspot.com/_ah/api/myApi/v1/startCalc/${event.params.uid}`,
method: 'POST'
}).then(function(resp) {
console.log(resp);
}).catch(function(error) {
console.log(error.message);
});
});

Resources