Avoid Circular References in Post Confirmation Triggers 🌱
This is a growing idea. It might evolve into a full article, or it might stay as a helpful snippet. Either way, I hope you find it useful!
Avoid Circular References in AWS PostConfirmation Triggers 🌱
Problem
Todo: Figure out a way to explain this without melting the reader's brain.
Link to adding a user to a group with a post confirmation trigger: here
Solution
Use AppSync JavaScript resolvers
On the AppSync API
import { defineSchema, defineData } from '@aws-amplify/backend'
// Let Amplify create the table and all the CRUDL operations for us
Business: a.model({
name: a.string().required(),
email: a.email().required(),
})
//This is what gets called in a post confirmation trigger (after the user is successfully created)
createBusinessInGroup: a
.mutation() // mark this as a custom mutation
.arguments({
email: a.email().required(),
groupName: a.string().required(),
})
.returns(a.boolean())
.handler([
// We know there's a user, so add them to the gruop
a.handler.custom({
dataSource: 'cognitoDS',
entry: './addUserToGroup.js',
}),
// Next, add them to the database
a.handler.custom({
dataSource: 'businessTableDS',
entry: './createBusiness.js',
}),
])
.authorization((allow) => [allow.group('NONE')]), // Don't let this be called by anyone by specifying a group that doesn't exist
export type Schema = ClientSchema<typeof schema>
export const data = defineData({
name: 'addUserToGroupAndDatabaseAPI',
schema,
authorizationModes: {
defaultAuthorizationMode: 'userPool',
},
logging: { //enale logging to see the errors and data in cloudwatch
fieldLogLevel: 'all',
}
})
Creating the Resolver to add a user to a group
// addUserToGroup.js
//docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_AdminAddUserToGroup.html
import { util } from '@aws-appsync/utils'
export function request(ctx) {
return {
resourcePath: '/',
method: 'POST',
params: {
headers: {
'content-type': 'application/x-amz-json-1.1',
'x-amz-target': 'AWSCognitoIdentityProviderService.AdminAddUserToGroup',
},
body: {
GroupName: ctx.args.groupName,
Username: ctx.args.email,
UserPoolId: ctx.env.COGNITO_USER_POOL_ID,
},
},
}
}
export function response(ctx) {
console.log('the context', ctx)
if (ctx.error) {
util.error(ctx.error.message, ctx.error.type)
}
return { created: true } // This let's the next step in the pipeline know that this step was successful
}
Creating the Resolver to add a user to a database
// createBusiness.js
import { util, CognitoIdentity } from '@aws-appsync/utils'
import * as ddb from '@aws-appsync/utils/dynamodb'
export function request(ctx) {
console.log('the ctx', ctx)
if (!ctx.prev.created) {
//error out if the previous step failed
util.error('Business was not created', 'BusinessNotFound')
}
const identity = ctx.identity as CognitoIdentity
const id = ctx.args.id
const now = util.time.nowISO8601()
const item = {
__typename: 'Business',
id: util.autoId(),
owner: `${identity.sub}::${identity.sub}`, //https://docs.amplify.aws/react/build-a-backend/data/customize-authz/per-user-per-owner-data-access/
email: ctx.args.email,
name: ctx.args.name,
createdAt: now,
updatedAt: now,
}
console.log('the full item', item)
return ddb.put({
key: { id },
item,
})
}
export function response(ctx) {
if (ctx.error) {
util.error(ctx.error.message, ctx.error.type)
}
console.log('the ctx', ctx)
console.log('the result', ctx.result)
return ctx.result
}
Adding the Permissions for the AppSync Data Source
//backend.ts (similar to what you'd also do in CDK)
//Note that this is creating a new data source, not using the one that Amplify created for us, which is fine.
const businessTableDS = backend.data.addDynamoDbDataSource(
'businessTableDS',
backend.data.resources.tables['Business'],
)
businessTableDS.grantPrincipal.addToPrincipalPolicy(
new PolicyStatement({
actions: ['dynamodb:PutItem'],
resources: [backend.data.resources.tables['Business'].tableArn],
}),
)
Adding the Permissions for the Cognito Data Source
//Create an HTTP data source for the Cognito API
const cognitoDS = backend.data.addHttpDataSource(
'cognitoDS',
'https://cognito-idp.us-east-1.amazonaws.com', //TODO: make this region dynamic
{
authorizationConfig: {
signingRegion: 'us-east-1', //TODO: make this region dynamic
signingServiceName: 'cognito-idp',
},
}
)
backend.data.resources.cfnResources.cfnGraphqlApi.environmentVariables = {
COGNITO_USER_POOL_ID: backend.auth.resources.userPool.userPoolId,
}
//allow the datasource to call the createAdminUser operation
cognitoDS.grantPrincipal.addToPrincipalPolicy(
new PolicyStatement({
actions: ['cognito-idp:AdminAddUserToGroup'],
resources: [backend.auth.resources.userPool.userPoolArn],
})
Testing
Setup Amplify in a Lambda function, assign the trigger and call the mutation
Same stuff as shown in the previous article