How to access Cognito Userpool from inside a lambda function? - aws-lambda

I'm using AWS Amplify for authentication in my app. I'm using email address as username and phone number for MFA. But, I also need the phone numbers to be unique, so I created this pre-signup lambda trigger:
const aws = require('aws-sdk');
exports.handler = async (event, context, callback) => {
const cognito = new aws.CognitoIdentityServiceProvider();
const params = {
AttributesToGet: [],
Filter: `phone_number = "${event.request.userAttributes.phone_number}"`,
Limit: 1,
UserPoolId: event.userPoolId,
};
try {
const result = await cognito.listUsers(params).promise();
if(result.Users.length === 0) {
callback(null, event);
} else {
const error = new Error("Phone number has already been used.");
callback(error, event);
}
} catch (err) {
console.log(err);
}
};
But, the function returns the following error:
validatePhoneNumber-dev is not authorized to perform: cognito-idp:ListUsers on resource: xxx
How can I resolve that?

This means your function has no permission to listUsers on the Cognito UserPool
On your PreSignup-cloudformation-template.json file you need to add the required permission:
On the file, search for the lambdaexecutionpolicy, and then PolicyDocument inside it.
Add your required permission under Statement:
"Statement": [
...
{
"Sid": "Cognito",
"Effect": "Allow",
"Action": [
"cognito-idp:ListUsers"
],
"Resource": "arn:aws:cognito-idp:us-east-1:679504623344:userpool/xxxxx"
}
Push your Amplify changes running amplify push
It should work now.

Related

Apollo GraphQL Lambda Handler Cannot read property 'method' of undefined

I am trying to run Apollo GraphQL server inside my AWS lambda. I'm using the library from here. I'm also using CDK to deploy my lambda and the REST API Gateway.
My infrastructure is as follows:
const helloFunction = new NodejsFunction(this, 'lambda', {
entry: path.join(__dirname, "lambda.ts"),
handler: "handler"
});
new LambdaRestApi(this, 'apigw', {
handler: helloFunction,
});
The lambda implementation is as follows:
const typeDefs = `#graphql
type Query {
hello: String
}`;
const resolvers = {
Query: {
hello: () => 'world',
},
};
const server = new ApolloServer({
typeDefs,
resolvers,
introspection: true,
})
console.log('###? running lambda')
export const handler = startServerAndCreateLambdaHandler(
server,
handlers.createAPIGatewayProxyEventV2RequestHandler(), {
middleware: [
async (event) => {
console.log('###? received event=' + JSON.stringify(event, null, 2))
return async (result) => {
console.log(("###? result=" + JSON.stringify(result, null, 2)))
result
}
}
]
});
When I POST to my endpoint with the appropriate query I get this error:
{
"statusCode": 400,
"body": "Cannot read property 'method' of undefined"
}
I'm seeing my logging inside the lambda as expected and I can confirm the error is being returned in the 'result' from within startServerAndCreateLambdaHandler(). This code is based on the example for the #as-integrations/aws-lambda library. I don't understand why this is failing.
Need to use:
handlers.createAPIGatewayProxyEventRequestHandler()
Instead of:
handlers.createAPIGatewayProxyEventV2RequestHandler()
So final code is:
export const handler = startServerAndCreateLambdaHandler(
server,
handlers.createAPIGatewayProxyEventRequestHandler(),
{
middleware: [
async (event) => {
console.log('###? received event=' + JSON.stringify(event))
}
]
}
);

Strapi returns 404 for custom route only when deployed to Heroku

I have created a custom route in Strapi v4 called "user-screens". Locally I hit it with my FE code and it returns some data as expected. However when I deploy it to Heroku and attempt to access the endpoint with code also deployed to Heroku it returns a 404. I've tailed the Heroku logs and can see that the endpoint is hit on the server side, but the logs don't give anymore info other than it returned a 404.
I am doing other non custom route api calls and these all work fine on Heroku. I am able to auth, save the token, and hit the api with the JWT token and all other endpoints return data. This is only happening on my custom route when deployed to Heroku. I've set up cors with the appropriate origins, and I am wondering if I need to add something to my policies and middlewares in the custom route. I have verified the permissions and verified the route is accessible to authenticated users in the Strapi admin.
Here is my route:
module.exports = {
routes: [
{
method: "GET",
path: "/user-screens",
handler: "user-screens.getUserScreens",
config: {
policies: [],
middlewares: [],
},
},
],
};
And my controller:
"use strict";
/**
* A set of functions called "actions" for `user-screens`
*/
module.exports = {
getUserScreens: async (ctx) => {
const user = ctx.state.user;
if (!user) {
return ctx.badRequest(null, [
{ messages: [{ id: "No authorization header was found" }] },
]);
}
strapi.entityService
.findMany("api::screen.screen", {
owner: user.id,
populate: ["image"],
})
.then((result) => {
ctx.send(result);
});
},
};
For anyone facing this, the answer was to change how I returned the ctx response from a 'send' to a 'return' from the controller method. I am not sure why this works locally and not on Heroku, but this fixes it:
New controller code:
module.exports = {
getUserScreens: async (ctx) => {
const user = ctx.state.user;
if (!user) {
return ctx.badRequest(null, [
{ messages: [{ id: "No authorization header was found" }] },
]);
}
return strapi.entityService
.findMany("api::screen.screen", {
owner: user.id,
populate: ["image"],
})
.then((result) => {
return result;
})
.catch((error) => {
return error;
});
},
};

Want to get Users group in lambda function

I'm creating a custom template to verify email. For this i have created a lambda function in node.js. I need user groups to get role but unable to find in events. Below is my code. I have tried to find a solution but could not get it.
'use strict';
exports.handler = async (event, context, callback) => {
const email = event.request.userAttributes.email;
console.log(context);
console.log(event.request.userAttributes.email);
console.log(event);
const name = event.request.userAttributes.name;
const template = (name) => `<html> </html>`;
if (event.triggerSource === "CustomMessage_SignUp") {
event.response = {
emailSubject: "Activate: Confirm LAGO Account Email",
emailMessage: template(name, link)
};
}
console.log(event.response);
callback(null, event);
};
You will need to call another method from the sdk to do so in your lambda function. Group name is not available in the event. Change your function to add this before your custom message.
const AWS = require('aws-sdk')
const cognito = new AWS.CognitoIdentityServiceProvider()
exports.handler = async event => {
// Params for user group list
const params = {
UserPoolId: userPoolId, /* required */
Username: userName, /* required */
}
// Check if user is in a group and list Groups as array
try {
const userGroup = await cognito.adminListGroupsForUser(params).promise()
console.log(userGroup)
}catch (err) {
console.log('Error', err)
}
// Handle your custom message after
...
};
You will need to add policy to this lambda as well
[
{
"Action": [
"cognito-identity:Describe*",
"cognito-identity:Get*",
"cognito-identity:List*",
"cognito-idp:Describe*",
"cognito-idp:AdminGetDevice",
"cognito-idp:AdminGetUser",
"cognito-idp:AdminList*",
"cognito-idp:List*",
"cognito-sync:Describe*",
"cognito-sync:Get*",
"cognito-sync:List*",
"cognito-idp:AdminListGroupsForUser"
],
"Resource": [
"arn:aws:xxxxx" //Cognito pool arn
]
}
]

Trying to write to AWS dynamo db via api

I am new to AWS and I've slowly been trying to perform different actions. I recently set up an API that allows me to query a dynamodb table and now I am trying to set up an api that will allow me to update a value in the table with the current temperature. This data will come from a script running on a raspberry pi.
I've been wading through so many tutorials but I haven't gotten this quite locked down. I am able to write to the db using a hard-coded python script so I know my db and roles is set up correctly. I am now trying to create a node-based lambda function that will accept parms from the URL and put the values into the table. I am missing something.
First, do I need to map the values in the api? Some guides do it, others do not. Like I said, ideally I want to pass them in as URL parms.
const AWS = require('aws-sdk');
const dynamodb = new AWS.DynamoDB({apiVersion: '2012-08-10'});
exports.handler = (event, context, callback) => {
dynamodb.putItem({
TableName: "temperature",
Item: {
"tempid": {
S: event.queryStringParameters["tempid"]
}
}
}, function(err, data) {
if (err) {
console.log(err, err.stack);
callback(null, {
statusCode: '500',
body: err
});
} else {
callback(null, {
statusCode: '200',
body: 'Result from ' + event.queryStringParameters["tempid"] + '!'
});
}
})
};
When I test it in the api using "tempid=hotttub1" in the query string I get this error:
START RequestId: 1beb4572-65bf-4ab8-81a0-c217677c3acc Version: $LATEST
2020-07-09T14:02:05.773Z 1beb4572-65bf-4ab8-81a0-c217677c3acc INFO { tempid: 'hottub1' }
2020-07-09T14:02:05.774Z 1beb4572-65bf-4ab8-81a0-c217677c3acc ERROR Invoke Error {"errorType":"TypeError","errorMessage":"Cannot read property 'tempid' of undefined","stack":["TypeError: Cannot read property 'tempid' of undefined"," at Runtime.exports.handler (/var/task/index.js:11:47)"," at Runtime.handleOnce (/var/runtime/Runtime.js:66:25)"]}
EDIT
If I print out event I can see that the value is coming in and I am apparently referencing it wrong. Still looking.
{
"tempid": "hottub1"
}
It needed to be in this format:
const AWS = require('aws-sdk');
const dynamodb = new AWS.DynamoDB({apiVersion: '2012-08-10'});
exports.handler = (event, context, callback) => {
console.info("EVENT\n" + JSON.stringify(event.tempid, null, 2))
var temperatureid = JSON.stringify(event.tempid, null, 2)
dynamodb.putItem({
TableName: "temperature",
Item: {
"tempid": {
S: temperatureid
}
}

How to pass a parameter in Koa middleware?

So I have this function in Koa, that basically checks if a user can access a specific route.
exports.requireRole = async role =>
async (ctx, next) => {
const { user } = ctx.state.user;
try {
const foundUser = await User.findById(user.id);
// If the user couldn't be found, return an error
if (!foundUser) {
ctx.status = 404;
ctx.body = { errors: [{ error: ERRORS.USER_NOT_FOUND }] };
} else {
// Otherwise, continue checking role
if (getRole(user.role) >= getRole(role)) {
await next();
}
ctx.status = 403;
ctx.body = { errors: [{ error: ERRORS.NO_PERMISSION }] };
}
} catch (err) {
ctx.throw(500, err);
}
};
And I want to use it as a middleware:
router.delete('/:id', combine([jwtAuth, requireRole(ROLES.ADMIN)]), deleteUser);
But then I get an error saying:
middleware must be a function not object
This happens only when I try to pass an argument into it.
What am I doing wrong here?
The issue you are having is due to the fact that Promises are objects, and async functions return Promises. You need to change your initial function to be as follows:
exports.requireRole = role =>
instead of
exports.requireRole = async role =>
I was going over middleware myself, and ran into this issue as well.
Your middleware looks fine, what is combine?
Also, since you are using koa-router you don't need it.
router.delete('/:id', jwtAuth, requireRole(ROLES.ADMIN), deleteUser);

Resources