Skip to content

Instantly share code, notes, and snippets.

@honkskillet
Created May 16, 2018 15:19
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save honkskillet/39fc060ef533fab173af96b55f609d74 to your computer and use it in GitHub Desktop.
Save honkskillet/39fc060ef533fab173af96b55f609d74 to your computer and use it in GitHub Desktop.
Serverless + AppSync + IAM/Cognito Auth + Lambda datasources (for mongoDB)
# Keep all sensitive data in this file and add it to your .gitignore
dev:
awsAccountId: 1111111111
mongoUrl: "url to dev monogDB"
region: us-east-1 #if not set will default to us-west-2
# prod:
# awsAccountId: 1111111111
# mongoUrl: "url to production monogDB "
# NOTES
# This setup allows both authenticated and unauthenticated access to AppSync datasources via IAM.
# A cognito user pool stores the user info
# This AppSync uses lambda datasources (not dynamoDB)
# This is a WIP. Many of the policies allow overbroad access.
# There are 2 UserPoolClientWeb, one of which I believe is unneeded and could probably be removed.
service: MEMORYCARDS
# You can pin your service to only deploy with a specific Serverless version
# Check out our docs for more details
frameworkVersion: ">=1.21.0 <2.0.0"
package:
individually: true
provider:
name: aws
runtime: nodejs8.10
stage: ${opt:stage, "dev"}
region: ${file(./serverless.env.yml):${self:provider.stage}.region, "us-west-2"}
iamRoleStatements: ##This adds policies to the default lambda function's roles created by serverless
- Effect: "Allow"
Action:
- "cognito-idp:*"
Resource: "arn:aws:cognito-idp:*"
#stackTags: # Optional CF stack tags
plugins:
- serverless-stack-output #provides output from deploy for use by frontend build: https://github.com/sbstjn/serverless-stack-output
custom:
accountId: ${file(./serverless.env.yml):${self:provider.stage}.awsAccountId}
appName: 'MEMORYCARDS' # ? remove or just reference service?
output: #config the serverless-stack-output plugin, output values come from resources.Outputs at bototm of file
handler: scripts/output.handler # Same syntax as you already know
file: .serverless/outputs.yaml # toml, yaml, yml, and json format is available
functions:
graphql:
handler: lambdaFunctions/resolver/handler.graphqlHandler
environment:
MONGO_URL: ${file(./serverless.env.yml):${self:provider.stage}.mongoUrl}
REGION: ${self:provider.region}
USER_POOL_ID:
Ref: UserPool
package:
exclude: #exclude all the files in the root directory
- '**'
include: #add back the files in the lambdas directory
- lambdaFunctions/resolver/**
addUserToGroup:
handler: lambdaFunctions/addUserToGroup/handler.addUserToGroup
environment:
GROUP_NAME: Users
package:
exclude: #exclude all the files in the root directory
- '**'
include: #add back the files in the lambdas directory
- lambdaFunctions/addUserToGroup/**
resources:
Resources:
##############################
######### IAM ROLES ##########
##############################
AppSyncLambdaServiceRole:
Type: "AWS::IAM::Role"
Properties:
RoleName: "${self:custom.appName}-Lambda-AppSyncServiceRole--${self:provider.region}-${self:provider.stage}"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
-
Effect: "Allow"
Principal:
Service:
- "appsync.amazonaws.com"
Action:
- "sts:AssumeRole"
Policies:
-
PolicyName: "Lambda-AppSyncServiceRole-Policy"
PolicyDocument:
Version: "2012-10-17"
Statement:
-
Effect: "Allow"
Action:
- "lambda:invokeFunction"
Resource: "*" #TODO: specify the lambda data source that is called
# - "arn:aws:lambda:us-west-2:*:function:serverless-graphql-appsync-lda-${self:provider.stage}-graphql" #previously production
# - "arn:aws:lambda:us-west-2:*:function:serverless-graphql-appsync-lda-${self:provider.stage}-graphql:*" #previously production
# Create a role for unauthorized acces to AWS resources. Control what your user can access.
CognitoUnAuthorizedRole:
Type: "AWS::IAM::Role"
Properties:
RoleName: "${self:custom.appName}-CognitoUnAuthorizedRole-${self:provider.region}-${self:provider.stage}"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Principal:
Federated: "cognito-identity.amazonaws.com"
Action:
- "sts:AssumeRoleWithWebIdentity"
Condition:
StringEquals:
"cognito-identity.amazonaws.com:aud":
Ref: IdentityPool
"ForAnyValue:StringLike":
"cognito-identity.amazonaws.com:amr": unauthenticated
Policies:
- PolicyName: "CognitoUnauthorizedPolicy"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Action:
- "appsync:GraphQL"
Resource: "*"
#- "arn:aws:appsync:us-west-2:${self:custom.accountId}:apis/${self:custom.appSync.apiId}/*"
- Effect: "Allow"
Action:
- "appsync:GraphQL" #OLD
- "cognito-identity:*" #OLD
- "mobileanalytics:PutEvents" #NEW
- "cognito-sync:*" #NEW
Resource: "*"
# TODO: see which Actions Resources are needed and limit them to given resource
# Create a role for authorized acces to AWS resources.
CognitoAuthorizedRole:
Type: "AWS::IAM::Role"
Properties:
RoleName: "${self:custom.appName}-CognitoAuthorizedRole-${self:provider.region}-${self:provider.stage}"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Principal:
Federated: "cognito-identity.amazonaws.com"
Action:
- "sts:AssumeRoleWithWebIdentity"
Condition:
StringEquals:
"cognito-identity.amazonaws.com:aud":
Ref: IdentityPool
# StringEquals:
# "cognito-identity.amazonaws.com:sub":
# Ref: IdentityPool
"ForAnyValue:StringLike":
"cognito-identity.amazonaws.com:amr": authenticated
Policies:
- PolicyName: "${self:custom.appName}_CognitoAuthorizedPolicy"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Action:
- "appsync:GraphQL" #OLD
# - "mobileanalytics:PutEvents" #NEW
# - "cognito-sync:*" #NEW
Resource:
Fn::Join :
- ""
- - "arn:aws:appsync:${self:provider.region}:${self:custom.accountId}:apis/"
- Fn::GetAtt: [ GraphQlApi, ApiId ]
- "/*"
- PolicyName: "${self:custom.appName}_SignInPolicy"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Action:
- cognito-identity:GetId
Resource:
Fn::Join :
- ""
- - "arn:aws:cognito-identity:*:*:identityPool/"
- Ref: IdentityPool
- Effect: "Allow"
Action:
- cognito-idp:*
Resource:
'Fn::GetAtt': [ UserPool, Arn ]
# - Effect: "Allow"
# Action:
# - "lambda:InvokeFunction" #NEW
# Resource: "*"
# TODO: see which Actions and resources are needed and limit them to given resource
######################################
######### COGNITO RESOURCES ##########
######################################
UserPool:
Type: "AWS::Cognito::UserPool"
Properties:
UserPoolName: '${self:custom.appName}-${self:provider.stage}-user-pool'
SmsVerificationMessage: "Your verification code is {####}."
AliasAttributes:
- email
- preferred_username
AutoVerifiedAttributes:
- email
MfaConfiguration: "OFF"
EmailVerificationSubject: "Your ${self:custom.appName} verification code"
EmailVerificationMessage: "Your ${self:custom.appName} verification code is {####}."
SmsAuthenticationMessage: "Your ${self:custom.appName} authentication code is {####}."
LambdaConfig:
PostConfirmation: "arn:aws:lambda:${self:provider.region}:${self:custom.accountId}:function:${self:custom.appName}-${self:provider.stage}-addUserToGroup"
Schema:
- Name: family_name
AttributeDataType: String
Mutable: true
Required: false
- Name: given_name
AttributeDataType: String
Mutable: true
Required: false
- Name: email
AttributeDataType: String
Mutable: false
Required: true
Policies:
PasswordPolicy:
RequireLowercase: true
RequireSymbols: false
RequireNumbers: true
MinimumLength: 8
RequireUppercase: true
AdminCreateUserConfig:
InviteMessageTemplate:
EmailMessage: "Your ${self:custom.appName} username is {username} and temporary password is {####}."
EmailSubject: "Your temporary ${self:custom.appName} password"
SMSMessage: "Your ${self:custom.appName} username is {username} and temporary password is {####}."
UnusedAccountValidityDays: 7
AllowAdminCreateUserOnly: false
UserPoolTags:
'STAGE': '${self:provider.stage}'
UserPoolGroupUsers:
Type: "AWS::Cognito::UserPoolGroup"
Properties:
Description: "Group represents all normal users of the app"
GroupName: "Users"
# RoleArn: String
UserPoolId:
Ref: UserPool
# Creates a User Pool Client to be used by the identity pool
UserPoolClient:
Type: "AWS::Cognito::UserPoolClient"
Properties:
ClientName: ${self:custom.appName}-${self:provider.stage}-client
GenerateSecret: true
UserPoolId:
Ref: UserPool
UserPoolClientWeb:
Type: "AWS::Cognito::UserPoolClient"
Properties:
ClientName: "${self:custom.appName}-${self:provider.stage}-client web"
GenerateSecret: false
UserPoolId:
Ref: UserPool
# Creates a federeated Identity pool
IdentityPool:
Type: "AWS::Cognito::IdentityPool"
Properties:
IdentityPoolName: ${self:custom.appName}_${self:provider.stage}_identity
AllowUnauthenticatedIdentities: true
CognitoIdentityProviders:
- ClientId:
Ref: UserPoolClient
ProviderName:
'Fn::GetAtt': [ UserPool, ProviderName ]
- ClientId:
Ref: UserPoolClientWeb
ProviderName:
'Fn::GetAtt': [ UserPool, ProviderName ]
# Assigns the roles to the Identity Pool
IdentityPoolRoleMapping:
Type: "AWS::Cognito::IdentityPoolRoleAttachment"
Properties:
IdentityPoolId:
Ref: IdentityPool
Roles:
authenticated:
'Fn::GetAtt': [ CognitoAuthorizedRole, Arn ]
unauthenticated:
'Fn::GetAtt': [ CognitoUnAuthorizedRole, Arn ]
######################################
######### GRAPHQL RESOURCES ##########
######################################
GraphQlApi:
Type: 'AWS::AppSync::GraphQLApi'
Properties:
Name: '${self:custom.appName}_${self:provider.stage}'
AuthenticationType: 'AWS_IAM' ##'AMAZON_COGNITO_USER_POOLS'
# UserPoolConfig:
# AwsRegion: ${self:provider.region}
# DefaultAction: 'ALLOW'
# UserPoolId:
# Ref: UserPool
GraphQlSchema:
Type: 'AWS::AppSync::GraphQLSchema'
Properties:
Definition: '${file(schema.graphql)}'
ApiId:
Fn::GetAtt: [ GraphQlApi, ApiId ]
GraphQlLambdaDataSource:
Type: 'AWS::AppSync::DataSource'
Properties:
ApiId:
Fn::GetAtt: [ GraphQlApi, ApiId ]
Name: '${self:custom.appName}_LambdaDataSource'
Description: 'Lambda for MongoDB'
Type: "AWS_LAMBDA"
LambdaConfig:
LambdaFunctionArn: "arn:aws:lambda:${self:provider.region}:${self:custom.accountId}:function:${self:service}-${self:provider.stage}-graphql"
ServiceRoleArn:
Fn::GetAtt: [ AppSyncLambdaServiceRole, Arn ]
############################
######## RESOLVERS #########
######## QUERIES ###########
GraphQlAllPostsResolver:
Type: "AWS::AppSync::Resolver"
DependsOn: GraphQlSchema
Properties:
ApiId:
Fn::GetAtt: [GraphQlApi, ApiId]
TypeName: Query
FieldName: allPosts
DataSourceName:
Fn::GetAtt: [GraphQlLambdaDataSource, Name]
RequestMappingTemplate: |
{
"version" : "2017-02-28",
"operation": "Invoke",
"payload": {
"field": "allPosts",
"context": $util.toJson($context)
}
}
ResponseMappingTemplate: '$util.toJson($context.result)'
GraphQlGetPostResolver:
Type: "AWS::AppSync::Resolver"
DependsOn: GraphQlSchema
Properties:
ApiId:
Fn::GetAtt: [GraphQlApi, ApiId]
TypeName: Query
FieldName: getPost
DataSourceName:
Fn::GetAtt: [GraphQlLambdaDataSource, Name]
RequestMappingTemplate: |
{
"version" : "2017-02-28",
"operation": "Invoke",
"payload": {
"field": "getPost",
"context": $util.toJson($context)
}
}
ResponseMappingTemplate: '$util.toJson($context.result)'
####### MUTATIONS ############
GraphQlAddPostResolver:
Type: "AWS::AppSync::Resolver"
DependsOn: GraphQlSchema
Properties:
ApiId:
Fn::GetAtt: [GraphQlApi, ApiId]
TypeName: Mutation
FieldName: addPost
DataSourceName:
Fn::GetAtt: [GraphQlLambdaDataSource, Name]
RequestMappingTemplate: |
{
"version" : "2017-02-28",
"operation": "Invoke",
"payload": {
"field": "addPost",
"context": $util.toJson($context)
}
}
ResponseMappingTemplate: '$util.toJson($context.result)'
GraphQlUpdatePostResolver:
Type: "AWS::AppSync::Resolver"
DependsOn: GraphQlSchema
Properties:
ApiId:
Fn::GetAtt: [GraphQlApi, ApiId]
TypeName: Mutation
FieldName: updatePost
DataSourceName:
Fn::GetAtt: [GraphQlLambdaDataSource, Name]
RequestMappingTemplate: |
{
"version" : "2017-02-28",
"operation": "Invoke",
"payload": {
"field": "updatePost",
"context": $util.toJson($context)
}
}
ResponseMappingTemplate: '$util.toJson($context.result)'
GraphQlDeletePostResolver:
Type: "AWS::AppSync::Resolver"
DependsOn: GraphQlSchema
Properties:
ApiId:
Fn::GetAtt: [GraphQlApi, ApiId]
TypeName: Mutation
FieldName: deletePost
DataSourceName:
Fn::GetAtt: [GraphQlLambdaDataSource, Name]
RequestMappingTemplate: |
{
"version" : "2017-02-28",
"operation": "Invoke",
"payload": {
"field": "deletePost",
"context": $util.toJson($context)
}
}
ResponseMappingTemplate: '$util.toJson($context.result)'
#'${file(mapping-templates/TemplateTypes/create-response-mapping.txt)}'
############################
######### OUTPUTS ##########
############################
Outputs:
GraphQlApiEndpoint:
Description: The GraphQL API endpoint.
Value:
Fn::GetAtt: [ GraphQlApi, GraphQLUrl ]
AppSyncRegion:
Value: '${self:provider.region}'
CognitoIdentityPooId:
Value:
Ref: IdentityPool
UserPoolId:
Value:
Ref: UserPool
UserPoolsWebClienId:
Value:
Ref: UserPoolClientWeb
CognitoRegion:
Value: '${self:provider.region}'
AmplifySigInEnabled:
Description: For use with AWS Amplify config
Value: 'enable'
AmplifyUserPools:
Description: For use with AWS Amplify config
Value: 'enable'
AmplifyUserPoolsMfaType:
Description: For use with AWS Amplify config
Value: 'OFF'
# LambdaAppSyncServiceRole:
# Description: The AppSync service role created with permissions to AWS Lambda operations.
# Value: !GetAtt AppSyncServiceRole.Arn
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment