import cdk = require ( '@aws-cdk/core' ) ; import lambda = require ( '@aws-cdk/aws-lambda' ) ; import apigw = require ( '@aws-cdk/aws-apigateway' ) ; import cognito = require ( '@aws-cdk/aws-cognito' ) ; import * as S3 from '@aws-cdk/aws-s3' ; import * as IAM from '@aws-cdk/aws-iam' ; import * as route53 from '@aws-cdk/aws-route53' ; import { CloudFrontWebDistribution , OriginAccessIdentity , CloudFrontAllowedMethods , SSLMethod , CloudFrontWebDistributionProps } from '@aws-cdk/aws-cloudfront' ; import { VerificationEmailStyle , OAuthScope , CfnUserPool } from '@aws-cdk/aws-cognito' ; import { StackOutput } from './stackoutput' ; import { Duration } from '@aws-cdk/core' ; import { Config } from '../../../scripts/config' ; export class BaseStack extends cdk . Stack { bucket : S3 . Bucket ; distribution : CloudFrontWebDistribution ; endpoint : apigw . LambdaRestApi ; userPool : cognito . UserPool ; userPoolClient : cognito . UserPoolClient ; apiFunction : lambda . Function ; constructor ( scope , id , props ? ) { super ( scope , id , props ) ; this . cognito ( ) ; this . backend ( ) ; this . frontend ( ) ; this . outputs ( ) ; } frontend ( ) { this . bucket = new S3 . Bucket ( this , Config . instance . appEnv + '-hosting-bucket' , { bucketName : Config . instance . appEnv + '-hosting-bucket' , websiteIndexDocument : 'index.html' , websiteErrorDocument : 'index.html' , removalPolicy : cdk . RemovalPolicy . DESTROY , } ) ; let oai = new OriginAccessIdentity ( this , Config . instance . appEnv + '-oai' ) ; let distributionProps = { comment : Config . instance . appEnv + ' distribution' , aliasConfiguration : Config . instance . domain ? { names : [ Config . instance . subdomain ] , acmCertRef : Config . instance . certificateArn , sslMethod : SSLMethod . SNI } : undefined , originConfigs : [ { s3OriginSource : { s3BucketSource : this . bucket , originAccessIdentity : oai , } , behaviors : [ { isDefaultBehavior : true } ] , } , { customOriginSource : { domainName : `${ this . endpoint . restApiId }.execute-api.${ this . region }.${ this . urlSuffix }` } , originPath : `/${ this . endpoint . deploymentStage . stageName }` , behaviors : [ { pathPattern : '/api/*' , allowedMethods : CloudFrontAllowedMethods . ALL , defaultTtl : Duration . millis ( 0 ) , maxTtl : Duration . millis ( 0 ) , minTtl : Duration . millis ( 0 ) , forwardedValues : { queryString : true , cookies : { forward : 'all' } , } } ] } ] } ; this . distribution = new CloudFrontWebDistribution ( this , Config . instance . appEnv + '-distribution' , distributionProps ) ; if ( Config . instance . domain ) { new route53 . CfnRecordSet ( this , Config . instance . appEnv + '-recordset' , { hostedZoneName : Config . instance . domain + '.' , name : Config . instance . subdomain , type : 'A' , aliasTarget : { hostedZoneId : 'Z2FDTNDATAQYW2' , dnsName : this . distribution . domainName } } ) ; } this . bucket . addToResourcePolicy ( new IAM . PolicyStatement ( { effect : IAM . Effect . ALLOW , resources : [ this . bucket . bucketArn , this . bucket . bucketArn + '/*' ] , actions : [ 's3:GetBucket*' , 's3:GetObject*' , 's3:List*' , ] , principals : [ new IAM . CanonicalUserPrincipal ( oai . cloudFrontOriginAccessIdentityS3CanonicalUserId ) ] } ) ) ; } backend ( ) { this . apiFunction = new lambda . Function ( this , Config . instance . appEnv + '-api' , { functionName : Config . instance . appEnv + '-api' , code : lambda . Code . asset ( '../api/dist' ) , runtime : lambda . Runtime . NODEJS_10_X , handler : 'generic/handler.handler' , environment : { } } ) ; this . apiFunction . addToRolePolicy ( new IAM . PolicyStatement ( { actions : [ 'cognito-idp:*' ] , resources : [ this . userPool . userPoolArn ] } ) ) ; this . endpoint = new apigw . LambdaRestApi ( this , Config . instance . appEnv + '-endpoint' , { restApiName : Config . instance . appEnv + '-endpoint' , handler : this . apiFunction , proxy : true } ) ; } cognito ( ) { this . userPool = new cognito . UserPool ( this , Config . instance . appEnv + '-user-pool' , { userPoolName : Config . instance . appEnv + '-userpool' , selfSignUpEnabled : true , signInAliases : { email : true } , autoVerify : { email : true } , userVerification : { emailSubject : 'Verify your email for ' + Config . instance . app , emailBody : `Your verification code is: <h1>{####}</h1>` , emailStyle : VerificationEmailStyle . CODE , } , passwordPolicy : { minLength : 8 , requireDigits : true , requireLowercase : true , requireUppercase : true , requireSymbols : false } , } ) ; if ( Config . instance . cognitoEmailArn ) { const cfnUserPool = this . userPool . node . defaultChild as CfnUserPool ; cfnUserPool . emailConfiguration = { emailSendingAccount : 'DEVELOPER' , replyToEmailAddress : 'noreply@' + Config . instance . domain , from : 'noreply@' + Config . instance . domain , sourceArn : Config . instance . cognitoEmailArn } ; } this . userPoolClient = new cognito . UserPoolClient ( this , Config . instance . appEnv + 'user-pool-client' , { userPoolClientName : Config . instance . appEnv + 'user-pool-client' , authFlows : { adminUserPassword : true , refreshToken : true } , generateSecret : false , userPool : this . userPool , oAuth : { flows : { authorizationCodeGrant : true , implicitCodeGrant : true } , scopes : [ OAuthScope . OPENID , OAuthScope . COGNITO_ADMIN ] } } ) ; const cfnUserPoolClient = this . userPoolClient . node . defaultChild as cognito . CfnUserPoolClient ; cfnUserPoolClient . supportedIdentityProviders = [ 'COGNITO' ] ; new cognito . UserPoolDomain ( this , Config . instance . appEnv + '-user-pool-domain' , { userPool : this . userPool , cognitoDomain : { domainPrefix : Config . instance . appEnv } } ) ; } outputs ( ) { let outputs = { DistributionUri : 'https://' + this . distribution . domainName , DistributionId : this . distribution . distributionId , HostingBucket : 's3://' + this . bucket . bucketName , UserPoolId : this . userPool . userPoolId , UserPoolClientId : this . userPoolClient . userPoolClientId , FunctionName : this . apiFunction . functionName , EndpointUrl : this . endpoint . url , } ; Object . keys ( outputs ) . forEach ( ( k ) => new cdk . CfnOutput ( this , k , { value : outputs [ k as keyof StackOutput ] } ) ) ; } } const app = new cdk . App ( ) ; new BaseStack ( app , Config . instance . appEnv ) ;