How to call AWS AppSync from lambda? - amazon-web-services

I am trying to call AWS AppSync service from AWS Lambda function using IAM permissions. Just a basic mutation or query call. I found this tutorial:
https://aws.amazon.com/blogs/mobile/supporting-backend-and-internal-processes-with-aws-appsync-multiple-authorization-types/
but it no longer works with the latest sdks. Errors out on lines 3, 4, etc.
require('isomorphic-fetch');
const AWS = require('aws-sdk/global');
const AUTH_TYPE = require('aws-appsync').AUTH_TYPE;
const AWSAppSyncClient = require('aws-appsync').default;
const gql = require('graphql-tag');
const config = {
url: process.env.APPSYNC_ENDPOINT,
region: process.env.AWS_REGION,
auth: {
type: AUTH_TYPE.AWS_IAM,
credentials: AWS.config.credentials,
},
disableOffline: true
};
const createLogMutation =
`mutation createLog($input: CreateLogInput!) {
createLog(input: $input) {
id
event
detail
}
}`;
const client = new AWSAppSyncClient(config);
exports.handler = (event, context, callback) => {
// An expected payload has the following format:
// {
// "event": "sample event",
// "detail": "sample detail"
// }
const payload = event['Records'][0]["Sns"]['Message'];
if (!payload['event']) {
callback(Error("event must be provided in the message body"));
return;
}
const logDetails = {
event: payload['event'],
detail: payload['detail']
};
(async () => {
try {
const result = await client.mutate({
mutation: gql(createLogMutation),
variables: {input: logDetails}
});
console.log(result.data);
callback(null, result.data);
} catch (e) {
console.warn('Error sending mutation: ', e);
callback(Error(e));
}
})();
};
Can someone please post a basic code for calling query/mutation from lambda using IAM permissions?

Related

AWS Lambda Function Permission Denied when trying to access AppSync GraphQL endpoint

new to AWS and just not sure how to define the relevant authenitcation to get my lambda function to be able to call my graphQL endpoint for a post req. Assuming I need to put an API key somewhere in this function but just am a bit lost. Any help at all would be great. Have put the function below - created it using the amplify cli and the generategraphqlpermissions flag is set to true if thats any help narrowing it down.
import crypto from '#aws-crypto/sha256-js';
import { defaultProvider } from '#aws-sdk/credential-provider-node';
import { SignatureV4 } from '#aws-sdk/signature-v4';
import { HttpRequest } from '#aws-sdk/protocol-http';
import { default as fetch, Request } from 'node-fetch';
const GRAPHQL_ENDPOINT = <myEndpoint>;
const AWS_REGION = process.env.AWS_REGION || 'us-east-1';
const { Sha256 } = crypto;
const query = /* GraphQL */ `mutation CreateCalendarEvent($input: CreateCalendarEventInput!, $condition: ModelCalendarEventConditionInput) {
createCalendarEvent(input: $input, condition: $condition) {
__typename
id
start
end
title
actions
allDay
resizable
draggable
colour
createdAt
updatedAt
}
}`;
/**
* #type {import('#types/aws-lambda').APIGatewayProxyHandler}
*/
export const handler = async (event) => {
console.log(`EVENT: ${JSON.stringify(event)}`);
console.log(GRAPHQL_ENDPOINT);
const endpoint = new URL(GRAPHQL_ENDPOINT);
const signer = new SignatureV4({
credentials: defaultProvider(),
region: AWS_REGION,
service: 'appsync',
sha256: Sha256
});
const requestToBeSigned = new HttpRequest({
method: 'POST',
headers: {
'Content-Type': 'application/json',
host: endpoint.host
},
hostname: endpoint.host,
body: JSON.stringify({ query }),
path: endpoint.pathname
});
const signed = await signer.sign(requestToBeSigned);
const request = new Request(endpoint, signed);
let statusCode = 200;
let body;
let response;
try {
response = await fetch(request);
body = await response.json();
if (body.errors) statusCode = 400;
} catch (error) {
statusCode = 500;
body = {
errors: [
{
message: error.message
}
]
};
}
return {
statusCode,
// Uncomment below to enable CORS requests
// headers: {
// "Access-Control-Allow-Origin": "*",
// "Access-Control-Allow-Headers": "*"
// },
body: JSON.stringify(body)
};
};
WHen invoking an AWS Service from Lambda, you do not need the keys. Instead, you can give the IAM role that the Lambda function runs under the permissions to invoke that service. In your case, give the role permission to invoke app sync.
More information can be found here:
https://docs.aws.amazon.com/lambda/latest/dg/lambda-permissions.html

Lambda failed silently without any logs, when updating a record from dynamodb table

I am using a lambda function for dynamo streaming after a record is inserted.
However, after the dynamoDB.update call, it seems the lambda is dead, and there are no other logs printed. Can anyone help?
Here is my javascript code:
/* Amplify Params - DO NOT EDIT
API_SCHOLARGRAPH_EMPLOYEEINFOTABLE_ARN
API_SCHOLARGRAPH_EMPLOYEEINFOTABLE_NAME
ENV
REGION
Amplify Params - DO NOT EDIT */
const AWS = require('aws-sdk');
const awsConfig = {
region: process.env.REGION,
endpoint:
process.env.DYNAMODB_ENDPOINT ||
`https://dynamodb.${process.env.REGION}.amazonaws.com`
};
const dynamoDB = new AWS.DynamoDB.DocumentClient(awsConfig);
async function handleNewEmployeeInfo(dynamoId, employeeId) {
console.log(
`[handleNewEmployeeInfo] begin. dynamoId(${dynamoId}) employeeId(${employeeId})`
);
try {
const employeeName = "TestString";
// this log is working
console.log(
'try to update table:',
process.env.API_SCHOLARGRAPH_EMPLOYEEINFOTABLE_NAME,
dynamoId,
employeeName
);
// something wrong with the update
await dynamoDB
.update({
TableName: process.env.API_SCHOLARGRAPH_EMPLOYEEINFOTABLE_NAME,
Key: {
id: dynamoId
},
UpdateExpression: `set employeeName = :employeeName`,
ExpressionAttributeValues: {
':employeeName': employeeName
}
})
.promise()
.then((data) => console.log(data.Attributes))
.catch(console.error);
} catch (error) {
// NOT working
console.log('[ERROR]:', error);
}
// NOT working
console.log('[FINISH] call end');
}
async function handleEventRecord(record) {
console.log(record.eventID);
console.log(record.eventName);
try {
// after EmployeeInfo is created by the admin
if (record.eventName === 'INSERT') {
const arn = record.eventSourceARN;
console.log(`[INSERT][${arn}]: ${JSON.stringify(record)}`);
if (arn.includes(':table/EmployeeInfo')) {
const dynamoId = record.dynamodb.NewImage.id['S'];
const employeeId = record.dynamodb.NewImage.employeeId['S'];
await handleNewEmployeeInfo(dynamoId, employeeId);
}
}
} catch (error) {
console.log('[ERROR]:', error);
}
}
/**
* #type {import('#types/aws-lambda').APIGatewayProxyHandler}
*/
exports.handler = async (event) => {
console.log(`EVENT: ${JSON.stringify(event)}`);
event.Records.forEach(async (record) => {
await handleEventRecord(record);
});
return Promise.resolve('Successfully processed DynamoDB record');
};
After I remove the update logic, the following log works fine. So I am pretty sure it is the update line has the problem.

Items doesn't save correctly in dynamo db through the lambda function

In my react native application which has an AWS amplify backend I use a post confirmation lambda function to save the cognito users in my dynamo db data store. My post confirmation lambda function runs fine and I can see the newly created user in my dynamo db but I can't query that user inside my app and also I cannot see the user in admin UI interface. But after about several hours I can query that user and also see the user through admin UI. How to fix this ?
/**
* #type {import('#types/aws-lambda').APIGatewayProxyHandler}
*/
const aws = require("aws-sdk");
const ddb = new aws.DynamoDB();
const tableName = process.env.USERTABLE;
exports.handler = async (event) => {
// insert code to be executed by your lambda trigger
if(!event?.request?.userAttributes?.sub){
console.log("no sub provided")
return;
}
const now = new Date();
const timestamp = now.getTime();
const userItem = {
__typename: { S: 'User' },
_lastChangedAt: { N: timestamp.toString() },
_version: { N: "1" },
updatedAt: { S: now.toISOString() },
createdAt: { S: now.toISOString() },
id: { S: event.request.userAttributes.sub },
Name: { S: event.request.userAttributes.name },
Email: { S: event.request.userAttributes.email },
Phonenumb: { S: event.request.userAttributes.phone_number },
DeliveryAddress: { S: ''}
}
const params = {
Item: userItem,
TableName: tableName
}
try {
await ddb.putItem(params).promise();
console.log("Success");
} catch (e) {
console.log(e)
}
};
this is my lambda function
This is how my query code look
const getUser = async () => {
const userData = await Auth.currentAuthenticatedUser();
const currentUserId = userData.attributes.sub
await DataStore.query(User, currentUserId).then(setUser);
console.log("getting user in home");
};
useEffect(() => {
getUser();
}, []);

Can I pass a parameter into a WebSocket connection with AWS API Gateway?

Following this example project: https://github.com/aws-samples/simple-websockets-chat-app
The onconnect method looks like this:
const AWS = require('aws-sdk');
const ddb = new AWS.DynamoDB.DocumentClient({ apiVersion: '2012-08-10', region: process.env.AWS_REGION });
exports.handler = async event => {
const putParams = {
TableName: process.env.TABLE_NAME,
Item: {
connectionId: event.requestContext.connectionId
}
};
try {
await ddb.put(putParams).promise();
} catch (err) {
return { statusCode: 500, body: 'Failed to connect: ' + JSON.stringify(err) };
}
return { statusCode: 200, body: 'Connected.' };
};
How can I see what other fields this event object has? I can't find the documentation.
Can I pass in a parameter from the client when connecting to the websocket? Like wss://path.to.socket/someparameter and how do I access it from this event object?
I want to add another parameter to the database:
const putParams = {
TableName: process.env.TABLE_NAME,
Item: {
connectionId: event.requestContext.connectionId,
someparameter: event.someparameter // <-- What's the right way?
}
};
Thanks!
To pass parameters when connecting: wss://path.to.socket?param1=value1&param2=value2.
If using wscat you might need to add quotes:
wscat -c 'wss://path.to.socket?param1=value1&param2=value2'
To access to the parameters from the lambda:
exports.handler = async event => {
const { connectionId, domainName, stage } = event.requestContext;
const param1 = event.queryStringParameters.param1;
const param2 = event.queryStringParameters.param2;
//or: const { param1, param2 } = event.queryStringParameters;
...
};

Manually sign AppSync URL to use in Lambda gives bad signature error

In a Lambda, I would like to sign my AppSync endpoint with aws-signature-v4 in order to use it for a mutation.
The URL generated seems to be ok but it gives me the following error when I try it:
{
"errors" : [ {
"errorType" : "InvalidSignatureException",
"message" : "The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details. etc...
} ]
}
Here is my lambda function
import { Context, Callback } from 'aws-lambda';
import { GraphQLClient } from 'graphql-request';
const v4 = require('aws-signature-v4');
export async function handle(event: any, context: Context, callback: Callback) {
context.callbackWaitsForEmptyEventLoop = false;
const url = v4.createPresignedURL(
'POST',
'xxxxxxxxxxxxxxxxx.appsync-api.eu-west-1.amazonaws.com',
'/graphql',
'appsync',
'UNSIGNED-PAYLOAD',
{
key: 'yyyyyyyyyyyyyyyyyyyy',
secret: 'zzzzzzzzzzzzzzzzzzzzz',
region: 'eu-west-1'
}
);
const mutation = `{
FAKEviewProduct(title: "Inception") {
productId
}
}`;
const client = new GraphQLClient(url, {
headers: {
'Content-Type': 'application/graphql',
action: 'GetDataSource',
version: '2017-07-25'
}
});
try {
await client.request(mutation, { productId: 'jfsjfksldjfsdkjfsl' });
} catch (err) {
console.log(err);
callback(Error());
}
callback(null, {});
}
I got my key and secret by creating a new user and Allowing him appsync:GraphQL action.
What am I doing wrong?
This is how I trigger an AppSync mutation using by making a simple HTTP-request, using axios.
const AWS = require('aws-sdk');
const axios = require('axios');
exports.handler = async (event) => {
let result.data = await updateDb(event);
return result.data;
};
function updateDb({ owner, thingName, key }){
let req = new AWS.HttpRequest('https://xxxxxxxxxxx.appsync-api.eu-central-1.amazonaws.com/graphql', 'eu-central-1');
req.method = 'POST';
req.headers.host = 'xxxxxxxxxxx.appsync-api.eu-central-1.amazonaws.com';
req.headers['Content-Type'] = 'multipart/form-data';
req.body = JSON.stringify({
"query":"mutation ($input: UpdateUsersCamsInput!) { updateUsersCams(input: $input){ latestImage uid name } }",
"variables": {
"input": {
"uid": owner,
"name": thingName,
"latestImage": key
}
}
});
let signer = new AWS.Signers.V4(req, 'appsync', true);
signer.addAuthorization(AWS.config.credentials, AWS.util.date.getDate());
return axios({
method: 'post',
url: 'https://xxxxxxxxxxx.appsync-api.eu-central-1.amazonaws.com/graphql',
data: req.body,
headers: req.headers
});
}
Make sure to give the IAM-role your Lambda function is running as, permissions for appsync:GraphQL.
Adding an answer here because I had difficulty getting the accepted answer to work and I found an issue on the AWS SDK GitHub issues that said it's not recommended to use the AWS.Signers.V4 object in production. This is how I got it to work using the popular aws4 npm module that is recommended later on in the issue linked above.
const axios = require('axios');
const aws4 = require('aws4');
const query = `
query Query {
todos {
id,
title
}
}`
const sigOptions = {
method: 'POST',
host: 'xxxxxxxxxx.appsync-api.eu-west.amazonaws.com',
region: 'eu-west-1',
path: 'graphql',
body: JSON.stringify({
query
}),
service: 'appsync'
};
const creds = {
// AWS access tokens
}
axios({
url: 'https://xxxxxxxxxx.appsync-api.eu-west/graphql',
method: 'post',
headers: aws4.sign(sigOptions, creds).headers,
data: {
query
}
}).then(res => res.data))
You don't need to construct a pre-signed URL to call an AWS AppSync endpoint. Set the authentication mode on the AppSync endpoint to AWS_IAM, grant permissions to your Lambda execution role, and then follow the steps in the "Building a JavaScript Client" tutorial to invoke a mutation or query.