Example lambda function(s) for Campaign hook pinpoint - amazon-web-services

I was working on something that would modify my promotional sms messages. I read that it is possible via CampaignHook in Pinpoint. But from the documentation, I couldn't gather how this will actually work. I have followed it until adding permission and linking the pinpoint app id and with it. I have followed this link: https://github.com/Ryanjlowe/lambda-powered-pinpoint-templates
For some reason, I am not able to follow what I need to do on the Lambda (boto3) function side to try and make this work. Is there an example code (python) or well-documented example for this? It would help me a lot. Thanks!

The Pinpoint developer guide describes how to setup a CampaignHook under the chapter "Customizing segments with AWS Lambda".
https://docs.aws.amazon.com/pinpoint/latest/developerguide/segments-dynamic.html
"To assign a Lambda function to a campaign, you define the campaign's CampaignHook settings by using the Campaign resource in the Amazon Pinpoint API. These settings include the Lambda function name. They also include the CampaignHook mode, which specifies whether Amazon Pinpoint receives a return value from the function."
The docs show an example Lambda function:
'use strict';
exports.handler = (event, context, callback) => {
for (var key in event.Endpoints) {
if (event.Endpoints.hasOwnProperty(key)) {
var endpoint = event.Endpoints[key];
var attr = endpoint.Attributes;
if (!attr) {
attr = {};
endpoint.Attributes = attr;
}
attr["CreditScore"] = [ Math.floor(Math.random() * 200) + 650];
}
}
console.log("Received event:", JSON.stringify(event, null, 2));
callback(null, event.Endpoints);
};
"In this example, the handler iterates through each endpoint in the event.Endpoints object, and it adds a new attribute, CreditScore, to the endpoint. The value of the CreditScore attribute is simply a random number."

Related

aws - should I create two lambda function to handle dynamodb query and non-query use case?

Let's say I have an "Banner" Table.
There are 2 possible use cases for this table.
1.get all banner data from table
My lambda function might like below:
'use strict'
const AWS = require('aws-sdk');
exports.handler = async function (event, context, callback) {
const documentClient = new AWS.DynamoDB.DocumentClient();
let responseBody = "";
let statusCode = 0;
const params = {
TableName : "Banner",
};
try{
const data = await documentClient.scan(params).promise();
responseBody = JSON.stringify(data.Items);
statusCode = 200
}catch(err){
responseBody = `Unabel to get products: ${err}`;
statusCode = 403
}
const response = {
statusCode: statusCode,
headers:{
"Content-Type": "application/json",
'Access-Control-Allow-Origin': '*', // Required for CORS support to work
},
body: responseBody
}
return response
}
2.Query by user partition key/GSI
I may need to query based on banner id or banner title to get the corresponding table.
At first, I was thinking combine this two user case in one single lambda function.
until I opened below post.
 
aws - how to set lambda function to make dynamic query to dynamodb
One of the comments provide a way for me to do the dynamic query for these 2 user case, but he/she also mention that:
you are giving anyone invoke the request the ability to put any query in the request, that might put you vulnerable to some type of SQL Injection attacks.
This makes me thinking whether I should separate these 2 user cases in two lambda function?
What is the general practise for these kinds of things?
Generally speaking, also if the "SQL Injection" can be blocked it's good to separate this into 2 functions, Lambda handler should be single responsibility. If you want to reuse the code you can create some common DAL that you can create with the common code.
I think this comes down to personal preference, but I'd recommend splitting the functionality into two lambdas.
It sounds like you have two access patterns:
Get all banners
Get banners by user
I would probably implement these with two separate lambdas. If I were exposing this functionality via an API, I'd probably create two endpoints:
GET /banners. (fetches all banners)
GET /users/[user_id]/banners. (fetches all banners for a given user)
Each of these endpoints would route to their own lambda that services the specific request. If you serve the request with a single lambda, you'll have to introduce logic within your lambdas to determine which type of request you're fulfilling. I can't imagine what you'd gain by using only one lambda.
Keep your lambda code focused on a single responsibility, it'll make it easier to develop, test, and debug.

Authenticate AWS lambda against Google Sheets API

I am trying to create an aws lambda function that will read rows from multiple Google Sheets documents using the Google Sheet API and will merge them afterwards and write in another spreadsheet. To do so I did all the necessary steps according to several tutorials:
Create credentials for the AWS user to have the key pair.
Create a Google Service Account, download the credentials.json file.
Share each necessary spreadsheet with the Google Service Account client_email.
When executing the program locally it works perfectly, it successfully logins using the credentials.json file and reads & writes all necessary documents.
However when uploading it to AWS Lambda using the serverless framework and google-spreadsheet, the program fails silently in the authentication step. I've tried changing the permissions as recommended in this question but it still fail. The file is read properly and I can print it to the console.
This is the simplified code:
async function getData(spreadsheet, psychologistName) {
await spreadsheet.useServiceAccountAuth(clientSecret);
// It never gets to this point, it fails silently
await spreadsheet.loadInfo();
... etc ...
}
async function main() {
const promises = Object.entries(psychologistSheetIDs).map(async (psychologistSheetIdPair) => {
const [psychologistName, googleSheetId] = psychologistSheetIdPair;
const sheet = new GoogleSpreadsheet(googleSheetId);
psychologistScheduleData = await getData(sheet, psychologistName);
return psychologistScheduleData;
});
//When all sheets are available, merge their data and write back in joint view.
Promise.all(promises).then(async (psychologistSchedules) => {
... merge the data ...
});
}
module.exports.main = async (event, context, callback) => {
const result = await main();
return {
statusCode: 200,
body: JSON.stringify(
result,
null,
2
),
};
I solved it,
While locally having a Promise.all(promises).then(result =>...) eventually returned the value and executed what was inside the then(), aws lambda returned before the promises were resolved.
This solved it:
const res = await Promise.all(promises);
mergeData(res);

How to pass a path parameter in API gateway to invoke lambda function?

I need to pass a path param in the API.
By using the mapping template, I am able to pass query params and use them in the function.
Mapping template:
{
"Id": "$input.params('Id')" //this works fine after passing params as <url>?param=vale
}
With reference to this I created the mapping template as following-
{
"Id": "$input.params().querystring.get('Id')" // requirement is to be able to use <url>/value
}
I tried using 'Method request template' under genrate template, but it didn't work either.
When I invoke the URL urlname.execute-api.us-east-2.amazonaws.com/stag/functionname, it gives the value as undefined.
This is how I'm using the param:
class Lambda {
static run(event, context, callback) {
callback(null, 'Id '+ event.Id);
}
}
module.exports = Lambda;
Also, please tell me how to use those params in code.
Be kind :)
In order to be able to use <url>/value and get value from event follow this (Tested):
Configure your API gateway resource
Under /api2/{id} - GET - Integration Request configure your Mapping Templates
Execute request https://123456.execute-api.my-region.amazonaws.com/stage/api2/123
Lambda
console.log(event.id)
callback(null, {
id:event.id
});
CloudWatch
Hope this helps

aws-sdk-js transact operations not exposed to DocumentClient?

I'm developing some business administration software in JS and I find myself in need of ACID transactions with DynamoDb. Lucky of mine, AWS just released the transactGet and transactWrite APIs that covers just the right use case!
I'm using the AWS.DynamoDb.DocumentClient object to make my other calls and it seems like the transact operations are not exposed for me to use.
I hacked inside the aws-sdk code and found the interface exports like such ("aws-sdk": "^2.368.0", document_client.d.js, line 2076):
export interface TransactWriteItem {
/**
* A request to perform a check item operation.
*/
ConditionCheck?: ConditionCheck;
/**
* A request to perform a PutItem operation.
*/
Put?: Put;
/**
* A request to perform a DeleteItem operation.
*/
Delete?: Delete;
/**
* A request to perform an UpdateItem operation.
*/
Update?: Update;
}
However, whenever I try to call the client with this methods, I get back a Type error, namely:
TypeError: dynamoDb[action] is not a function
Locally, I can just hack the sdk and expose everything, but it is not accepted on my deployment environment.
How should I proceed?
Thank you very much!
Edit:
If it is worth anything, there is the code I'm using to do the calls:
dynamo-lib.js:
import AWS from "aws-sdk";
export function call(action, params) {
const dynamoDb = new AWS.DynamoDB.DocumentClient();
return dynamoDb[action](params).promise();
}
Lambda code:
import * as dynamoDbLib from '../libs/dynamodb-lib';
import { success, failure } from '../libs/response-lib';
export default async function main(params, callback) {
try {
const result = await dynamoDbLib.call("transactWrite", params);
callback(null, success(result));
} catch (e) {
console.log(e);
callback(null, failure({"status": "Internal server error"}));
}
}
Edit 2:
Seems like the document client really does not export the method.
AWS.DynamoDB.DocumentClient = AWS.util.inherit({
/**
* #api private
*/
operations: {
batchGetItem: 'batchGet',
batchWriteItem: 'batchWrite',
putItem: 'put',
getItem: 'get',
deleteItem: 'delete',
updateItem: 'update',
scan: 'scan',
query: 'query'
},
...
As mentioned in accepted answer, the workaround is to use the transactWriteItems method straight from the DynamoDB object.
Thanks for the help! =D
Cheers!
UPDATE:
The issue has been resolved
Github Thread
AWS SDK is not currently supporting transactions in dynamodb document client i have raised the issue on github a current workaround is just not to use document client
let aws=require("aws-sdk");
aws.config.update(
{
region:"us-east-1",
endpoint: "dynamodb.us-east-1.amazonaws.com"
}
);
var dynamodb = new aws.DynamoDB();
dynamodb.transactWriteItems();
Also make sure you are using SDK v2.365.0 plus
Update the aws-sdk package (>2.365)
Use
var dynamodb = new aws.DynamoDB();
instead of
var dynamodb = new aws.DynamoDB.DocumentClient();

Access permissions on AWS API Gateway

I'm building an application where some data within DynamoDb can be accessed by users over a Rest API.
What I have in mind is:
User accesses API Gateway, authenticated by a Cognito user pool;
API Gateway invokes a Lambda function (written in Python);
Lambda function accesses DynamoDB and returns data.
I'd like to be able to restrict the level of access to DynamoDb according to the user. Initially I thought that the Lambda function could inherit its permissions from the user, but this doesn't work because it needs an execution role.
What is the best way of achieving this? For example, can I pass user information to the Lambda function, which in turn can assume this role before accessing DynamoDb? If so a code example would be appreciated.
Take a look at SpaceFinder - Serverless Auth Reference App and Use API Gateway Lambda Authorizers
With Cognito you can use RBAC:
Amazon Cognito identity pools assign your authenticated users a set of
temporary, limited privilege credentials to access your AWS resources.
The permissions for each user are controlled through IAM roles that
you create. You can define rules to choose the role for each user
based on claims in the user's ID token. You can define a default role
for authenticated users. You can also define a separate IAM role with
limited permissions for guest users who are not authenticated.
so you can create specific roles for each user, although it would be better to use groups
With Lambda authorisers you create your own policy. An example is in awslabs.
In addition to blueCat's answer, I briefly tried giving my Lambda function sts:AssumeRole permissions, and then allowing it to assume the role of the Cognito user that invoked it via the API. I can then use this to get a new set of credentials and carry out some activity with the Cognito user's permissions. Roughly the code inside the lambda is:
def lambda_handler(event, context):
sts_client = boto3.client('sts')
role = event['requestContext']['authorizer']['claims']['cognito:roles']
cognito_role = sts_client.assume_role(
RoleArn=role,
RoleSessionName='lambda-session',
DurationSeconds=3600
)
credentials = cognito_role['Credentials']
sess = boto3.session.Session(
aws_access_key_id=credentials['AccessKeyId'],
aws_secret_access_key=credentials['SecretAccessKey'],
aws_session_token=credentials['SessionToken']
)
# Do something as the assumed user, e.g. access S3
s3_client = sess.client('s3')
# Do stuff here...
Although this works I found that there was roughly 0.5s overhead to assume the role and get the S3 client, and I can't re-use this session between invocations of the function because it is user-specific. As such this method didn't really suit my application.
I've decided instead to give my Lambda full access to the relevant DynamoDb tables, and use the Cognito user groups plus a Lambda authorizer to restrict the parts of the API that individual users are able to call.
I dealt with this issue also but I implemented my solution with Node.js and I figured that although your question is for a Python implementation, then maybe someone would stumble upon this question looking for an answer in JS and I figured this could help out the next person who comes along.
It sounds like you're trying to come up with an effective Authorization strategy after the user has Authenticated their credentials against your Cognito User Pool using custom attributes.
I created a library that I use to export a few functions that allow me to capture the UserPoolId and the Username for the authenticated user so that I can capture the custom:<attribute> I need within my lambda so that the conditions I have implemented can then consume the API to the remaining AWS Services I need to provide authorization to for each user that is authenticated by my app.
Here is My library:
import AWS from "aws-sdk";
// ensure correct AWS region is set
AWS.config.update({
region: "us-east-2"
});
// function will parse the user pool id from a string
export function parseUserPoolId(str) {
let regex = /[^[/]+(?=,)/g;
let match = regex.exec(str)[0].toString();
console.log("Here is the user pool id: ", match);
return match.toString();
}
// function will parse the username from a string
export function parseUserName(str) {
let regex = /[a-z,A-Z,0-9,-]+(?![^:]*:)/g;
let match = regex.exec(str)[0].toString();
console.log("Here is the username: ", match);
return match.toString();
}
// function retries UserAttributes array from cognito
export function getCustomUserAttributes(upid, un) {
// instantiate the cognito IdP
const cognito = new AWS.CognitoIdentityServiceProvider({
apiVersion: "2016-04-18"
});
const params = {
UserPoolId: upid,
Username: un
};
console.log("UserPoolId....: ", params.UserPoolId);
console.log("Username....: ", params.Username);
try {
const getUser = cognito.adminGetUser(params).promise();
console.log("GET USER....: ", getUser);
// return all of the attributes from cognito
return getUser;
} catch (err) {
console.log("ERROR in getCustomUserAttributes....: ", err.message);
return err;
}
}
With this library implemented it can now be used by any lambda you need to create an authorization strategy for.
Inside of your lambda, you need to import the library above (I have left out the import statements below, you will need to add those so you can access the exported functions), and you can implement their use as such::
export async function main(event, context) {
const upId = parseUserPoolId(
event.requestContext.identity.cognitoAuthenticationProvider
);
// Step 2 --> Get the UserName from the requestContext
const usrnm = parseUserName(
event.requestContext.identity.cognitoAuthenticationProvider
);
// Request body is passed to a json encoded string in
// the 'event.body'
const data = JSON.parse(event.body);
try {
// TODO: Make separate lambda for AUTHORIZATION
let res = await getCustomUserAttributes(upId, usrnm);
console.log("THIS IS THE custom:primaryAccountId: ", res.UserAttributes[4].Value);
console.log("THIS IS THE custom:ROLE: ", res.UserAttributes[3].Value);
console.log("THIS IS THE custom:userName: ", res.UserAttributes[1].Value);
const primaryAccountId = res.UserAttributes[4].Value;
} catch (err) {
// eslint-disable-next-line
console.log("This call failed to getattributes");
return failure({
status: false
});
}
}
The response from Cognito will provide an array with the custom attributes you need. Console.log the response from Cognito with console.log("THIS IS THE Cognito response: ", res.UserAttributes); and check the index numbers for the attributes you want in your CloudWatch logs and adjust the index needed with:
res.UserAttributes[n]
Now you have an authorization mechanism that you can use with different conditions within your lambda to permit the user to POST to DynamoDB, or use any other AWS Services from your app with the correct authorization for each authenticated user.