Can I run Cognito in a Lambda function? - node.js

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.

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 S3 SDK V3 for Node.js - GetObjectCommand v/s getSignedUrl

I'm building a web app in which I need to let users upload documents to their account and also read all the documents they have uploaded. In addition, I would like to allow users to be able to upload a profile photo as well. To handle file storage, I chose AWS S3.
However, I'm having a lot of trouble with the SDK (v3). Bear in mind I never used the previous version (v2). I installed 2 packages via npm, #aws-sdk/client-s3 and #aws-sdk/s3-request-presigner
. I'm having trouble finding proper documentation for all the functionality I need. The docs I have come across are not exactly beginner friendly and don't go into a lot of detail explaining all the functionality. For example, in the case of GetObjectCommand, I am able to get a response but I'm unsure about how to actually tap into the Body and use the contents.
I'm also unsure about whether I should be using GetObjectCommand or getSignedUrl for my use case. For context, I'm using Express to build my server.
My questions -
Is there any easier way to interact with S3 for my app rather than using the SDK? By easier I just mean properly documented.
Am I looking at the wrong documentation? Are there other resources that make this simpler?
What are the situations where one would use getSignedUrl over GetObjectCommand to read and then render stored files for a web app?
I will be extremely grateful for any and all help.
See response to question 2.
https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/s3-example-creating-buckets.html - the documentation is perhaps more beginner friendly
Depends on your use case. GetObjectCommand is the straightforward method, but you'll run into premission issues most likely. A presigned URL is a URL that you can provide to your users to grant temporary access to a specific S3 object.
Here's code for GetObjectCommand using getSignedUrl (I've also updated the doc.)
const {
S3,
CreateBucketCommand,
PutObjectCommand,
GetObjectCommand,
DeleteObjectCommand,
DeleteBucketCommand,
} = require("#aws-sdk/client-s3");
const { getSignedUrl } = require("#aws-sdk/s3-request-presigner");
const fetch = require("node-fetch");
// Set parameters
// Create random names for the Amazon Simple Storage Service (Amazon S3) bucket and key.
const params = {
Bucket: `test-bucket-${Math.ceil(Math.random() * 10 ** 10)}`,
Key: `test-object-${Math.ceil(Math.random() * 10 ** 10)}`,
Body: "BODY",
Region: "REGION"
};
// Create an Amazon S3 client object.
const s3Client = new S3({ region: params.Region });
const run = async () => {
// Create an Amazon S3 bucket.
try {
console.log(`Creating bucket ${params.Bucket}`);
const data = await s3Client.send(
new CreateBucketCommand({ Bucket: params.Bucket })
);
console.log(`Waiting for "${params.Bucket}" bucket creation...\n`);
} catch (err) {
console.log("Error creating bucket", err);
}
// Put the object in the Amazon S3 bucket.
try {
console.log(`Putting object "${params.Key}" in bucket`);
const data = await s3Client.send(
new PutObjectCommand({
Bucket: params.Bucket,
Key: params.Key,
Body: params.Body,
})
);
} catch (err) {
console.log("Error putting object", err);
}
// Create a presigned URL.
try {
// Create the command.
const command = new GetObjectCommand(params);
// Create the presigned URL.
const signedUrl = await getSignedUrl(s3Client, command, {
expiresIn: 3600,
});
console.log(
`\nGetting "${params.Key}" using signedUrl with body "${params.Body}" in v3`
);
console.log(signedUrl);
const response = await fetch(signedUrl);
console.log(
`\nResponse returned by signed URL: ${await response.text()}\n`
);
}
catch (err) {
console.log("Error creating presigned URL", err);
}
};
run();

Using AWS Cognito in a Lambda function with npm

I'm trying to use AWS Cognito in a Lambda function to authorize a user.
I have some sample code from a Udemy Course (no longer available): https://www.udemy.com/minimum-viable-aws-cognito-user-auth-in-javascript
The code uses the script files:
aws-cognito-sdk.min.js
amazon-cognito-identity.min.js
The second seems to available by npm as: amazon-cognito-identity-js
The first file is supposed to be a cut down version of the aws-sdk with just the Cognito api components. The full aws-sdk is available from npm as: aws-sdk but I cannot find the cutdown version in npm.
Is the cutdown file: aws-cognito-sdk.min.js available in npm?
EDIT:
According to Russell I should use the aws-sdk package.
So if I have code:
const AWS = require('aws-sdk');
var authenticationDetails = new AWS.AWSCognito.CognitoIdentityServiceProvider.AuthenticationDetails(authenticationData);
I get the error:
Cannot read property 'CognitoIdentityServiceProvider' of undefined
What is the correct path to AuthenticationDetails?
For Lambdas use the aws-sdk module as such:
const { CognitoIdentityServiceProvider } = require('aws-sdk')
//or
const CognitoIdentityServiceProvider = require('aws-sdk/clients/cognitoidentityserviceprovider') // Much smaller size
For authentication use the AdminInitiateAuth method.
const cognitoProvider = new CognitoIdentityServiceProvider({
apiVersion: '2016-04-18',
accessKeyId:...
secretAccessKey: ...
region:...
})
await cognitoProvider.adminInitiateAuth(...)
The amazon-cognito-identity-js package is meant for frontend clients (React, React Native, etc). It contains only the functionality necessary to connect to Cognito. It does not require the aws-sdk module (unless you need extra features).
While you may be able to use the amazon-cognito-identity-js for your use case it's far from ideal as you are just pretending to be an unauthenticated user with limited functionality compared to loading the admin method using your api key thereby providing you with much more functionality.
Got this working.
package.json needs dependencies:
"amazon-cognito-identity-js": "^1.31.0",
"aws-sdk": "^2.182.0",
AWS Lambda does not use Javascript ES6 and so you can't use the 'import' keyword.
const AWS = require('aws-sdk');
var AmazonCognitoIdentity = require('amazon-cognito-identity-js');
var CognitoUserPool = AmazonCognitoIdentity.CognitoUserPool;
var AuthenticationDetails = AmazonCognitoIdentity.AuthenticationDetails;
var CognitoUser = AmazonCognitoIdentity.CognitoUser;
var poolData = {
UserPoolId: 'THE USER POOL ID',
ClientId: 'THE CLIENT ID'
};
var userPool = new CognitoUserPool(poolData);
AWS.config.region = 'AWS_REGION';
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'THE USERPOOL ID',
});
var email = "someone#somewhere.com";
var password = "password";
var authenticationData = {
Username: email,
Password: password
};
var authenticationDetails = new AuthenticationDetails(authenticationData);
var userData = {
Username: email,
Pool: userPool
};
var cognitoUser = new CognitoUser(userData);
console.log(result);
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: function (result) {
console.log('access token + ' + result.getAccessToken().getJwtToken());
callback(null, result);
},
onFailure: function (err) {
console.log('Login error: ' + err);
callback(null, result);
}
});
I believe you are referring to the amazon-cognito-identity-js npm package here:
https://www.npmjs.com/package/amazon-cognito-identity-js
The NPM package includes both files.
The package includes the cognito SDK calls (aws-cognito-sdk). It also depends on the core AWS SDK.

Using AWS API Gateway SDK in Node.JS causes issues

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.

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