Storing username with post confirmation trigger (AWS, Lambda, DynamoDB) - node.js

I try to store some parameters in a AWS DynamoDB with Cognito post confirmation trigger.
The lambda is written in node.js but I am not able to store the username of the signed up cognito user.
const AWS = require('aws-sdk');
const dynamodb = new AWS.DynamoDB.DocumentClient({region: 'eu-central-1'});
exports.handler = async (event, context, callback) => {
console.log(event);
const username = event.userName;
await createMessage(username).then(() => {
callback(null, {
statusCode: 201,
body: '',
headers: {
'Access Control Allow Origin' : '*'
}
});
}).catch((err) => {
console.error(err);
});
};
function createMessage(username){
const familyid = (new Date()).getTime().toString(36) + Math.random().toString(36).slice(2);
const params = {
TableName: 'eldertech',
Item: {
'UserId' : username,
'message' : familyid
}
};
return dynamodb.put(params).promise();
}
My test:
{
"username":"admin",
"email":"admin#admin.com",
"userId": "AD87S"
}
The error:
ERROR ValidationException: One or more parameter values were invalid: Missing the key UserId in the item
Can somebody please help a newbie?

Okay, I just had a typo....
const username = event.username;
No capital n in username...

Related

AWS Cognito returns 'No email provided but email_verified was true' after adding migration

I have added migration between two user pools. The following are the actions I took :
create lambda function based on sample code given by AWS docs for cognito migration
https://docs.aws.amazon.com/code-samples/latest/catalog/javascript-cognito-lambda-trigger-migrate-user.js.html
create the trigger for migration in cognito and connect the lambda.
add policies for relevant roles.
After trying it out I get 'No email provided but email_verified was true' as the error responded by cognito and no cloud watch logs for the migration trigger lambda function.
User login code :
const authenticationData = {
Username: email,
Email: email,
Password: userPassword,
};
const authDetails = AWS.authenticationDetails(authenticationData);
const userPool = AWS.cognitoUserPool(poolData);
const userData = {
Username: email,
Email: email,
Pool: userPool,
};
const cognitoUser = AWS.cognitoUser(userData);
cognitoUser.setAuthenticationFlowType('USER_PASSWORD_AUTH');
authDetails.email = email;
try {
const authResult = await utilsHelper.promisifySF(cognitoUser.authenticateUser.bind(cognitoUser))(authDetails);
console.log('authResult : ', authResult);
} catch (e) {
console.log('user verify exception : ', e);
}
lambda function code
'use strict';
var CLIENT_ID = '';
var USER_POOL_ID = '';
var OLD_CLIENT_ID = '';
var OLD_USER_POOL_ID = '';
var OLD_USER_POOL_REGION = '';
var OLD_ROLE_ARN = '';
var OLD_EXTERNAL_ID = '';
var AWS = require('aws-sdk');
exports.handler = (event, context, callback) => {
var user;
if ( event.triggerSource == "UserMigration_Authentication" ) {
// authenticate the user with your existing user directory service
user = authenticateUser(event.userName, event.request.password);
if ( user ) {
event.response.userAttributes = {
"email": user.emailAddress,
"email_verified": "true"
};
event.response.finalUserStatus = "CONFIRMED";
event.response.messageAction = "SUPPRESS";
context.succeed(event);
}
else {
// Return error to Amazon Cognito
callback("Bad password");
}
}
else {
// Return error to Amazon Cognito
callback("Bad triggerSource " + event.triggerSource);
}
};
async function authenticateUser(username, password) {
const isp = new AWS.CognitoIdentityServiceProvider();
// Validate username/password
const resAuth = await isp.adminInitiateAuth({
AuthFlow: 'ADMIN_USER_PASSWORD_AUTH',
AuthParameters: {
PASSWORD: password,
USERNAME: username,
},
ClientId: OLD_CLIENT_ID,
UserPoolId: OLD_USER_POOL_ID,
}).promise();
if (resAuth.code && resAuth.message) {
return undefined;
}
// Load user data
const resGet = await isp.adminGetUser({
UserPoolId: OLD_USER_POOL_ID,
Username: username,
}).promise();
if (resGet.code && resGet.message) {
return undefined;
}
return {
emailAddress: resGet.UserAttributes.find(e => e.Name === 'email').Value,
};
}
Congito error response
{
code: 'UserNotFoundException',
name: 'UserNotFoundException',
message: 'User does not exist.'
}
Please let me know if any further details are required. It would be great if you could help me. Thank you in advance for your help.

Basic HTTP Authentication for CloudFront with Lambda#Edge in NodeJS

I am working on protecting a static website with a username and password. I created a basic HTTP Authentication for CloudFront with Lambda#Edge in NodeJS.
I am completely new to NodeJS. Initially, I had the user and the password hardcoded, and this worked properly.
'use strict';
exports.handler = (event, context, callback) => {
// Get request and request headers
const request = event.Records[0].cf.request;
const headers = request.headers;
// Configure authentication
const authUser = 'user';
const authPass = 'pass';
// Construct the Basic Auth string
const authString = 'Basic ' + new Buffer(authUser + ':' + authPass).toString('base64');
// Require Basic authentication
if (typeof headers.authorization == 'undefined' || headers.authorization[0].value != authString) {
const body = 'Unauthorized';
const response = {
status: '401',
statusDescription: 'Unauthorized',
body: body,
headers: {
'www-authenticate': [{key: 'WWW-Authenticate', value:'Basic'}]
},
};
callback(null, response);
}
// Continue request processing if authentication passed
callback(null, request);
};
I stored my secrets in SSM and I want to retrieve them through the function. I tested this piece of code separately in Lambda and it returns the credentials as espected.
'use strict';
exports.handler = async (event, context, callback) => {
const ssm = new (require('aws-sdk/clients/ssm'))();
let userData = await ssm.getParameters({Names: ['website-user']}).promise();
let userPass = await ssm.getParameters({Names: ['website-pass']}).promise();
let user = userData.Parameters[0].Value;
let pass = userPass.Parameters[0].Value;
return {user, pass};
};
But when I stitch the two, I get 503 ERROR The request could not be satisfied.
Does anyone know what I might be doing wrong? Thank you for your help!
The complete code:
'use strict';
exports.handler = async (event, context, callback) => {
const ssm = new (require('aws-sdk/clients/ssm'))();
let userData = await ssm.getParameters({Names: ['website-user']}).promise();
let userPass = await ssm.getParameters({Names: ['website-pass']}).promise();
let user = userData.Parameters[0].Value;
let pass = userPass.Parameters[0].Value;
// Get request and request headers
const request = event.Records[0].cf.request;
const headers = request.headers;
// Construct the Basic Auth string
let authString = 'Basic ' + new Buffer(user + ':' + pass).toString('base64');
// Require Basic authentication
if (typeof headers.authorization == 'undefined' || headers.authorization[0].value != authString) {
const body = 'Unauthorized';
const response = {
status: '401',
statusDescription: 'Unauthorized',
body: body,
headers: {
'www-authenticate': [{key: 'WWW-Authenticate', value:'Basic'}]
},
};
callback(null, response);
}
// Continue request processing if authentication passed
callback(null, request);
};
After reading about promises I was able to resolve the error. Here's the solution that worked for me:
'use strict';
var AWS = require('aws-sdk');
AWS.config.update({ region: 'us-east-1' });
var ssm = new AWS.SSM();
function getParameter(param) {
return new Promise(function (success, reject) {
ssm.getParameter(param, function (err, data) {
if (err) {
reject(err);
} else {
success(data);
}
});
});
};
exports.handler = (event, context, callback) => {
let request = event.Records[0].cf.request;
let headers = request.headers;
let user = {Name: 'user-path', WithDecryption: false};
let pass = {Name: 'password-path', WithDecryption: false};
let authUser;
let authPass;
var promises = [];
promises.push(getParameter(user), getParameter(pass));
Promise.all(promises)
.then(function (result) {
authUser = result[0].Parameter.Value;
authPass = result[1].Parameter.Value;
console.log(authUser);
console.log(authPass);
let authString = 'Basic ' + new Buffer(authUser + ':' + authPass).toString('base64');
if (typeof headers.authorization == 'undefined' || headers.authorization[0].value != authString) {
const body = 'Unauthorized';
const response = {
status: '401',
statusDescription: 'Unauthorized',
body: body,
headers: {
'www-authenticate': [{key: 'WWW-Authenticate', value:'Basic'}]
},
};
callback(null, response);
}
// Continue request processing if authentication passed
callback(null, request);
})
.catch(function (err) {
console.log(err);
});
};
Thank you for sharing the solution. And let me do my humble contribution.
To successfully authenticate additionally need the choice right version of Node.js(14.x)
Grant AWS Lambda Access to SSM Parameter Store to retrieve AWS Parameter Store:
Go to Lambda function -> Configuration -> Permissions -> Role name -> Whatever-Your-Role-Name-> Add Permissions -> Create inline Policy -> JSON -> And input the policy as shown below:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ssm:GetParameter",
"ssm:GetParameters",
"ssm:GetParametersByPath",
"ssm:PutParameter"
],
"Resource": [
"arn:aws:ssm:us-east-1:YOUR_ACCOUNT_NUMBER:PARAMETER_NAME_WITHOUT_LEADING_SLASH",
"arn:aws:ssm:us-east-1:252522211181:parameter/web-pass"
]
}
]
}
You could set "ssm:*" for the Action element in the policy to grant full parameter store access to the lambda function.
You could also set the Resource element to be *, which means the function can access all SSM parameters in the account.

NodeJS AWS Lambda, DynamoDB not writing result and not logging

I'm having some trouble with DynamoDB. I've set up my Lambda permissions for full CRUDL (administrator access, so no restrictions). The following is the handler, and it's based on the doca
https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GettingStarted.NodeJs.03.html
const uuidv4 = require("uuid/v4");
const services = require("./services/services");
var AWS = require("aws-sdk");
AWS.config.update({ region: "eu-west-2" });
var docClient = new AWS.DynamoDB.DocumentClient();
var tableName = "usersTable";
module.exports = {
registerUser: async (event, context) => {
const id = uuidv4();
let body;
if (event.body !== null && event.body !== undefined) {
body = JSON.parse(event.body);
}
const isValid = await services.validateUser(body);
if (isValid) {
var params = {
TableName: tableName,
Item: {
userId: "123abc",
firstName: "finn",
lastName: "murtons",
email: "email#email.com",
password: "secret"
}
};
console.log("Adding a new item...");
console.log(" Adding params", params);
docClient.put(params, function(err, data) {
if (err) {
console.error(
"Unable to add item. Error JSON:",
JSON.stringify(err, null, 2)
);
} else {
console.log("Added item:", JSON.stringify(data, null, 2));
}
});
}
},
... other functions
For this example, I'm hardcoding the params for clarity, but obviously I would usually get them from the event.body.
When I make a post request to this Lambda, I get a 502 error.
Looking at the cloudwatch logs, it gets as far as:
INFO Adding params { TableName: 'usersTable',
Item:
{ userId: '123abc',
firstName: 'finn',
lastName: 'murtons',
email: 'email#email.com',
password: 'secret' } }
Then there are no more logs after that. Ignore the isValid function, it's just checking that the request event body has the required fields.
Anybody have any ideas where I might be going wrong?
It's likely that the lambda is exiting before the DynamoDB call is made. You should make the call a Promise and await it:
await docClient.put(params).promise();

Decrypt text with AWS KMS in NodeJs

I am trying to decrypt some text encrypted with AWS KMS using aws-sdk and NodeJs. I started to play today with NodeJs so I am a newbie with it.
I have this problem resolved with Java but I am trying to migrate an existing Alexa skill from Java to NodeJs.
The code to decrypt is:
function decrypt(buffer) {
const kms = new aws.KMS({
accessKeyId: 'accessKeyId',
secretAccessKey: 'secretAccessKey',
region: 'eu-west-1'
});
return new Promise((resolve, reject) => {
let params = {
"CiphertextBlob" : buffer,
};
kms.decrypt(params, (err, data) => {
if (err) {
reject(err);
} else {
resolve(data.Plaintext);
}
});
});
};
When I run this code with a correct CiphertextBlob, I get this error:
Promise {
<rejected> { MissingRequiredParameter: Missing required key 'CiphertextBlob' in params
at ParamValidator.fail (D:\Developing\abono-transportes-js\node_modules\aws-sdk\lib\param_validator.js:50:37)
at ParamValidator.validateStructure (D:\Developing\abono-transportes-js\node_modules\aws-sdk\lib\param_validator.js:61:14)
at ParamValidator.validateMember (D:\Developing\abono-transportes-js\node_modules\aws-sdk\lib\param_validator.js:88:21)
at ParamValidator.validate (D:\Developing\abono-transportes-js\node_modules\aws-sdk\lib\param_validator.js:34:10)
at Request.VALIDATE_PARAMETERS (D:\Developing\abono-transportes-js\node_modules\aws-sdk\lib\event_listeners.js:126:42)
at Request.callListeners (D:\Developing\abono-transportes-js\node_modules\aws-sdk\lib\sequential_executor.js:106:20)
at callNextListener (D:\Developing\abono-transportes-js\node_modules\aws-sdk\lib\sequential_executor.js:96:12)
at D:\Developing\abono-transportes-js\node_modules\aws-sdk\lib\event_listeners.js:86:9
at finish (D:\Developing\abono-transportes-js\node_modules\aws-sdk\lib\config.js:349:7)
at D:\Developing\abono-transportes-js\node_modules\aws-sdk\lib\config.js:367:9
message: 'Missing required key \'CiphertextBlob\' in params',
code: 'MissingRequiredParameter',
time: 2019-06-30T20:29:18.890Z } }
I don't understand why I am receiving that if CiphertextBlob is in the params variable.
Anyone knows?
Thanks in advance!
EDIT 01/07
Test to code the feature:
First function:
const CheckExpirationDateHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest'
&& handlerInput.requestEnvelope.request.intent.name === 'TtpConsultaIntent';
},
handle(handlerInput) {
var fecha = "";
var speech = "";
userData = handlerInput.attributesManager.getSessionAttributes();
if (Object.keys(userData).length === 0) {
speech = consts.No_Card_Registered;
} else {
console.log("Retrieving expiration date from 3rd API");
fecha = crtm.expirationDate(cipher.decrypt(userData.code.toString()));
speech = "Tu abono caducará el " + fecha;
}
return handlerInput.responseBuilder
.speak(speech)
.shouldEndSession(true)
.getResponse();
}
}
Decrypt function provided with a log:
// source is plaintext
async function decrypt(source) {
console.log("Decrypt func INPUT: " + source)
const params = {
CiphertextBlob: Buffer.from(source, 'base64'),
};
const { Plaintext } = await kms.decrypt(params).promise();
return Plaintext.toString();
};
Output:
2019-07-01T19:01:12.814Z 38b45272-809d-4c84-b155-928bee61a4f8 INFO Retrieving expiration date from 3rd API
2019-07-01T19:01:12.814Z 38b45272-809d-4c84-b155-928bee61a4f8 INFO Decrypt func INPUT:
AYADeHK9xoVE19u/3vBTiug3LuYAewACABVhd3MtY3J5cHRvLXB1YmxpYy1rZXkAREF4UW0rcW5PSElnY1ZnZ2l1bHQ2bzc3ZnFLZWZMM2J6YWJEdnFCNVNGNzEyZGVQZ1dXTDB3RkxsdDJ2dFlRaEY4UT09AA10dHBDYXJkTnVtYmVyAAt0aXRsZU51bWJlcgABAAdhd3Mta21zAEthcm46YXdzOmttczpldS13ZXN0LTE6MjQwMTE3MzU1MTg4OmtleS81YTRkNmFmZS03MzkxLTRkMDQtYmUwYi0zZDJlMWRhZTRkMmIAuAECAQB4sE8Iv75TZ0A9b/ila9Yi/3vTSja3wM7mN/B0ThqiHZEBxYsoWpX7jCqHMoeoYOkVtAAAAH4wfAYJKoZIhvcNAQcGoG8wbQIBADBoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDNnGIwghz+b42E07KAIBEIA76sV3Gmp5ib99S9H4MnY0d1l............
2019-07-01T19:01:12.925Z 38b45272-809d-4c84-b155-928bee61a4f8 INFO Error
handled: handlerInput.responseBuilder.speak(...).shouldEndSession is
not a function
2019-07-01T19:01:13.018Z 38b45272-809d-4c84-b155-928bee61a4f8 ERROR Unhandled Promise
Rejection {"errorType":"Runtime.UnhandledPromiseRejection","errorMessage":"InvalidCiphertextException:
null","stack":["Runtime.UnhandledPromiseRejection:
InvalidCiphertextException: null","...
That either means you're missing key 'CiphertextBlob' or its value is undefined.
Please checkout the value you're passing in as buffer.
For reference, I also added my working code example that I used.
import { KMS } from 'aws-sdk';
import config from '../config';
const kms = new KMS({
accessKeyId: config.aws.accessKeyId,
secretAccessKey: config.aws.secretAccessKey,
region: config.aws.region,
});
// source is plaintext
async function encrypt(source) {
const params = {
KeyId: config.aws.kmsKeyId,
Plaintext: source,
};
const { CiphertextBlob } = await kms.encrypt(params).promise();
// store encrypted data as base64 encoded string
return CiphertextBlob.toString('base64');
}
// source is plaintext
async function decrypt(source) {
const params = {
CiphertextBlob: Buffer.from(source, 'base64'),
};
const { Plaintext } = await kms.decrypt(params).promise();
return Plaintext.toString();
}
export default {
encrypt,
decrypt,
};
----- ADDED -----
I was able to reproduce your issue.
decrypt("this text has never been encrypted before!");
This code throws same error.
So if you pass plain text that has never been encrypted before or has been encrypted with different key, it throws InvalidCiphertextException: null.
Now I'll give you one usage example.
encrypt("hello world!") // this will return base64 encoded string
.then(decrypt) // this one accepts encrypted string
.then(decoded => console.log(decoded)); // hello world!
I kept on getting this error in my AWS lambda when trying the accepted solution, using AWS KMS over an environment variable I had encrypted by using AWS user interface.
It worked for me with this code adapted from the AWS official solution:
decrypt.js
const AWS = require('aws-sdk');
AWS.config.update({ region: 'us-east-1' });
module.exports = async (env) => {
const functionName = process.env.AWS_LAMBDA_FUNCTION_NAME;
const encrypted = process.env[env];
if (!process.env[env]) {
throw Error(`Environment variable ${env} not found`)
}
const kms = new AWS.KMS();
try {
const data = await kms.decrypt({
CiphertextBlob: Buffer.from(process.env[env], 'base64'),
EncryptionContext: { LambdaFunctionName: functionName },
}).promise();
console.info(`Environment variable ${env} decrypted`)
return data.Plaintext.toString('ascii');
} catch (err) {
console.log('Decryption error:', err);
throw err;
}
}
To be used like this:
index.js
const decrypt = require("./decrypt.js")
exports.handler = async (event, context, callback) => {
console.log(await decrypt("MY_CRYPTED_ENVIRONMENT_VARIABLE"))
}
EncryptionContext is a must for this to work.
Let's say the name of EnvironmentVariable is Secret
The code below reads the EnvironmentVariable called Secret and returns decrypted secret as plain text in the body.
Please see the function code posted below
'use strict';
const aws = require('aws-sdk');
var kms = new aws.KMS();
exports.handler = (event, context, callback) => {
const functionName = process.env.AWS_LAMBDA_FUNCTION_NAME;
const encryptedSecret = process.env.Secret;
kms.decrypt({
CiphertextBlob: new Buffer(encryptedSecret, 'base64'),
EncryptionContext: {
LambdaFunctionName: functionName /*Providing the name of the function as the Encryption Context is a must*/
},
},
(err, data) => {
if (err) {
/*Handle the error please*/
}
var decryptedSecret = data.Plaintext.toString('ascii');
callback(null, {
statusCode: 200,
body: decryptedSecret,
headers: {
'Content-Type': 'application/json',
},
});
});
};

AWS cognito is giving serializationException(Start of structure or map found where not expected) while doing sign up in node.js How to fix this issue?

I'm trying to add signup functionality with AWS cognito, But While signing up up getting SerializationException How to resolve this issue?
My signup function look like this
const AmazonCognitoIdentity = require("amazon-cognito-identity-js");
const AWS = require("aws-sdk");
global.fetch = require("node-fetch");
const keys = require("../../config/keys");
AWS.config.update({
accessKeyId: keys.awsKeys.key,
secretAccessKey: keys.awsKeys.secret,
region: keys.region.awsRegionId
});
const poolConfig = {
UserPoolId: keys.cognito.userPoolId,
ClientId: keys.cognito.clientId
};
// create a new user pool
const userPool = new AmazonCognitoIdentity.CognitoUserPool(poolConfig);
async function signupFunc(userData) {
console.log('JSON string received : ' + JSON.stringify(userData));
const emailData = {
Name: "email",
Value: userData.email
};
const name = {
Name: "name",
Value: userData.name
}
const password = userData.password;
const familyname = {
Name: 'family_name',
Value: userData.familyname
}
return new Promise((resolve, reject) => {
try {
var attributeList = [];
attributeList.push(new AmazonCognitoIdentity.CognitoUserAttribute(name));
attributeList.push(new AmazonCognitoIdentity.CognitoUserAttribute(familyname));
userPool.signUp(emailData, password, attributeList, null, (err, result) => {
if (err) {
console.error(`ERROR : ${JSON.stringify(err)}`);
return reject({ status: 0, error: "Error!!!" });
}
return resolve({
status: "200",
message: "Check email and verify!"
});
});
} catch (error) {
console.log(`ERROR : ${JSON.stringify(error)}`);
return reject({error: error});
}
});
}
module.exports = signupFunc;
While executing this method I'm getting below exception.
{
"code":"SerializationException",
"name":"SerializationException",
"message":"Start of structure or map found where not expected."
}
any help will much appreciated.
I had the same problem, because I was mistakenly passing the password as an Object instead of a String. Make sure you password is a String:
Auth.completeNewPassword(user, password, {} ).then(data=> console.log(data)})
Here user is an Object and password is a String.

Resources