# This is the SAM template that represents the architecture of your serverless application # https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-template-basics.html # The AWSTemplateFormatVersion identifies the capabilities of the template # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/format-version-structure.html AWSTemplateFormatVersion: 2010-09-09 Description: >- NFT Gallery # Transform section specifies one or more macros that AWS CloudFormation uses to process your template # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/transform-section-structure.html Transform: - "AWS::Serverless-2016-10-31" # Resources declares the AWS resources that you want to include in the stack # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/resources-section-structure.html # More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst Globals: # Default settings for Lambda functions Function: Tracing: Active Timeout: 10 Runtime: nodejs16.x Architectures: - x86_64 # Default settings for API Gateway Api: Cors: AllowHeaders: "'Content-Type,X-Amz-Date,X-Amz-Security-Token,Authorization,X-Api-Key,X-Requested-With,Accept,Access-Control-Allow-Methods,Access-Control-Allow-Origin,Access-Control-Allow-Headers'" AllowOrigin: "'*'" AllowMethods: "'OPTIONS,GET,POST,PUT'" Auth: InvokeRole: NONE AddDefaultAuthorizerToCorsPreflight: False GatewayResponses: DEFAULT_4XX: ResponseParameters: Headers: Access-Control-Allow-Origin: "'*'" Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,X-Amz-Security-Token,Authorization,X-Api-Key,X-Requested-With,Accept,Access-Control-Allow-Methods,Access-Control-Allow-Origin,Access-Control-Allow-Headers'" Access-Control-Allow-Methods: "'OPTIONS,GET,POST'" DEFAULT_5XX: ResponseParameters: Headers: Access-Control-Allow-Origin: "'*'" Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,X-Amz-Security-Token,Authorization,X-Api-Key,X-Requested-With,Accept,Access-Control-Allow-Methods,Access-Control-Allow-Origin,Access-Control-Allow-Headers'" Access-Control-Allow-Methods: "'OPTIONS,GET,POST'" # Parameters taken as input to deploy the Cloud Formation stack Parameters: AlchemyEthUrl: Type: String Description: "The Alchemy URL containing the API Key. Used for HTTP proxy calls through API Gateway. Don't include trailing '/'. Format: https://eth-mainnet.g.alchemy.com/v2/kMJgN17xxxxxxxxxxxxxxx-xxxxxxxxx" AlchemyEthAPIKey: Type: String Description: "The Alchemy ETH API Key. Used by the Lambda function to call Alchemy." MoralisUrl: Type: String Description: "The Moralis URL. Used for HTTP proxy calls through API Gateway. Don't include trailing '/'. Format: https://deep-index.moralis.io/api/v2/{address}/nft" MoralisAPIKey: Type: String Description: "The Moralis API Key. Used by the Lambda function to call Moralis." Resources: # Model used in the mapping template to return an empty JSON body EmptyModel: Type: AWS::ApiGateway::Model Properties: ContentType: "application/json" RestApiId: !Ref Api Schema: {} # To force a redeployment of the API Gateway stage, change the number at the end of this resources name 'ApiDeployment${randomNumber}' # SAM will create this deployment resource only the first time so this hack forces a redeploy # If you change the API definition or a mapping template, you will need to force redeploy the stage # This can also be done through CLI, CI/CD pipeline, SDK or in the console. # If you have problem. Delete the stack and recreate it OR make the change by hand in the AWS console and re-deploy to the stage by hand. ApiDeployment46: DependsOn: - GetNFTsAlchemyOPTIONSMethod - GetNFTsAlchemyLambdaFunction - GetNFTsAlchemyGETMethod - GetNFTsMoralisOPTIONSMethod - GetNFTsMoralisGETMethod - GetNFTsMoralisLambdaFunction - GetNFTsCollectionAlchemyOPTIONSMethod - GetNFTsCollectionAlchemyGETMethod - CorsProxyFunction Type: "AWS::ApiGateway::Deployment" Properties: RestApiId: !Ref Api Description: Prod stage for NFT API StageName: prod # Cognito authorizer which will check our ID Token and grant authenticated users access to our API methods # This is used by GET Methods HTTP proxy APICognitoAuthorizer: Type: AWS::ApiGateway::Authorizer Properties: IdentitySource: method.request.header.Authorization Name: !Join - "-" - - !Ref "AWS::StackName" - CognitoAuthorizer RestApiId: !Ref Api Type: COGNITO_USER_POOLS ProviderARNs: - !GetAtt CognitoUserPool.Arn # The Api Gateway resource Api: Type: AWS::Serverless::Api Properties: Name: !Join - "-" - - !Ref "AWS::StackName" - Api StageName: prod Description: NFT Gallery API Gateway Auth: DefaultAuthorizer: APICognitoAuthorizer Authorizers: APICognitoAuthorizer: UserPoolArn: !GetAtt CognitoUserPool.Arn Identity: Header: Authorization ### ### Alchemy getNFTsCollection ### # An API Gateway route /getNFTsCollectionAlchemy GetNFTsCollectionAlchemyResource: Type: AWS::ApiGateway::Resource Properties: RestApiId: !Ref Api ParentId: !GetAtt Api.RootResourceId PathPart: getNFTsCollectionAlchemy # Public API OPTIONS method to /getNFTsCollectionAlchemy (to allow CORS) GetNFTsCollectionAlchemyOPTIONSMethod: Type: AWS::ApiGateway::Method Properties: AuthorizationType: NONE RestApiId: !Ref Api ResourceId: !Ref GetNFTsCollectionAlchemyResource HttpMethod: OPTIONS Integration: Type: MOCK PassthroughBehavior: NEVER RequestTemplates: application/json: '{"statusCode": 200}' IntegrationResponses: - StatusCode: "200" ResponseParameters: method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'" method.response.header.Access-Control-Allow-Methods: "'GET,OPTIONS'" method.response.header.Access-Control-Allow-Origin: "'*'" ResponseTemplates: application/json: "" MethodResponses: - StatusCode: "200" ResponseModels: application/json: !Ref EmptyModel ResponseParameters: method.response.header.Access-Control-Allow-Headers: false method.response.header.Access-Control-Allow-Methods: false method.response.header.Access-Control-Allow-Origin: false # Public API GET method to get NFT Collection from Alchemy (/getNFTsCollectionAlchemy). Passthrough API Gateway Proxy. No Lambda. # AuthorizationType: AWS_IAM # We use the Unauthenticated/Authenticated IAM Role given by the Cognito identity pool to authorize access to the route GetNFTsCollectionAlchemyGETMethod: Type: AWS::ApiGateway::Method Properties: RestApiId: !Ref Api ResourceId: !Ref GetNFTsCollectionAlchemyResource HttpMethod: GET AuthorizationType: AWS_IAM RequestParameters: method.request.querystring.contractAddress: true Integration: Type: HTTP IntegrationHttpMethod: GET PassthroughBehavior: WHEN_NO_TEMPLATES # We inject the Alchemy URL containing the API Key from our prod.parameters file Uri: !Join - "/" - - !Ref AlchemyEthUrl - getNFTsForCollection RequestParameters: integration.request.querystring.contractAddress: 'method.request.querystring.contractAddress' integration.request.querystring.withMetadata: "'true'" IntegrationResponses: - StatusCode: "200" ResponseParameters: method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'" method.response.header.Access-Control-Allow-Methods: "'GET,OPTIONS'" method.response.header.Access-Control-Allow-Origin: "'*'" - StatusCode: "500" ResponseParameters: method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'" method.response.header.Access-Control-Allow-Methods: "'GET,OPTIONS'" method.response.header.Access-Control-Allow-Origin: "'*'" MethodResponses: - StatusCode: "200" ResponseParameters: method.response.header.Access-Control-Allow-Headers: false method.response.header.Access-Control-Allow-Methods: false method.response.header.Access-Control-Allow-Origin: false - StatusCode: "500" ResponseParameters: method.response.header.Access-Control-Allow-Headers: false method.response.header.Access-Control-Allow-Methods: false method.response.header.Access-Control-Allow-Origin: false ### ### Alchemy getNFTs ### # An API Gateway route /getNFTsAlchemy GetNFTsAlchemyResource: Type: AWS::ApiGateway::Resource Properties: RestApiId: !Ref Api ParentId: !GetAtt Api.RootResourceId PathPart: getNFTsAlchemy # Public API OPTIONS method to /getNFTs (to allow CORS) GetNFTsAlchemyOPTIONSMethod: Type: AWS::ApiGateway::Method Properties: AuthorizationType: NONE RestApiId: !Ref Api ResourceId: !Ref GetNFTsAlchemyResource HttpMethod: OPTIONS Integration: Type: MOCK PassthroughBehavior: NEVER RequestTemplates: application/json: '{"statusCode": 200}' IntegrationResponses: - StatusCode: "200" ResponseParameters: method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'" method.response.header.Access-Control-Allow-Methods: "'GET,OPTIONS'" method.response.header.Access-Control-Allow-Origin: "'*'" ResponseTemplates: application/json: "" MethodResponses: - StatusCode: "200" ResponseModels: application/json: !Ref EmptyModel ResponseParameters: method.response.header.Access-Control-Allow-Headers: false method.response.header.Access-Control-Allow-Methods: false method.response.header.Access-Control-Allow-Origin: false # Protected API GET method to get NFTs from Alchemy (/getNFTsAlchemy). Passthrough API Gateway Proxy. No Lambda. # AuthorizationType: COGNITO_USER_POOLS # We use the token given by the Cognito UserPool to authorize access to the route GetNFTsAlchemyGETMethod: Type: AWS::ApiGateway::Method Properties: RestApiId: !Ref Api ResourceId: !Ref GetNFTsAlchemyResource HttpMethod: GET AuthorizationType: COGNITO_USER_POOLS AuthorizerId: !Ref APICognitoAuthorizer Integration: Type: HTTP IntegrationHttpMethod: GET PassthroughBehavior: NEVER # We inject the Alchemy URL containing the API Key from our prod.parameters file Uri: !Join - "/" - - !Ref AlchemyEthUrl - getNFTs # Here we override the querystring parameter 'owner' to inject the user's wallet address RequestTemplates: application/json: | #set($address = $context.authorizer.claims['cognito:username']) #set($context.requestOverride.querystring.owner = $address) $input.json("$") IntegrationResponses: - StatusCode: "200" ResponseParameters: method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'" method.response.header.Access-Control-Allow-Methods: "'GET,OPTIONS'" method.response.header.Access-Control-Allow-Origin: "'*'" - StatusCode: "500" ResponseParameters: method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'" method.response.header.Access-Control-Allow-Methods: "'GET,OPTIONS'" method.response.header.Access-Control-Allow-Origin: "'*'" MethodResponses: - StatusCode: "200" ResponseParameters: method.response.header.Access-Control-Allow-Headers: false method.response.header.Access-Control-Allow-Methods: false method.response.header.Access-Control-Allow-Origin: false - StatusCode: "500" ResponseParameters: method.response.header.Access-Control-Allow-Headers: false method.response.header.Access-Control-Allow-Methods: false method.response.header.Access-Control-Allow-Origin: false # Protected API Method executing a Lambda function to call Alchemy # We pass the API keys as environement variables GetNFTsAlchemyLambdaFunction: Type: AWS::Serverless::Function Properties: FunctionName: !Join - "-" - - !Ref "AWS::StackName" - GetNFTsAlchemyLambdaFunction Handler: src/handlers/getNFTsAlchemyLambda.handler MemorySize: 256 Environment: Variables: ALCHEMY_ETH_API_KEY: !Ref AlchemyEthAPIKey Description: Lambda to get data my NFTS from Alchemy. Events: ApiEvent: Type: Api Properties: Path: /getNFTsAlchemyLambda Method: GET RestApiId: !Ref Api # We attach our Lambda to our existing API ### ### Moralis getNFTs ### # An API Gateway route /getNFTsMoralis GetNFTsMoralisResource: Type: AWS::ApiGateway::Resource Properties: RestApiId: !Ref Api ParentId: !GetAtt Api.RootResourceId PathPart: getNFTsMoralis # Public API OPTIONS method to /getNFTs (to allow CORS) GetNFTsMoralisOPTIONSMethod: Type: AWS::ApiGateway::Method Properties: AuthorizationType: NONE RestApiId: !Ref Api ResourceId: !Ref GetNFTsMoralisResource HttpMethod: OPTIONS Integration: Type: MOCK PassthroughBehavior: NEVER RequestTemplates: application/json: '{"statusCode": 200}' IntegrationResponses: - StatusCode: "200" ResponseParameters: method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'" method.response.header.Access-Control-Allow-Methods: "'GET,OPTIONS'" method.response.header.Access-Control-Allow-Origin: "'*'" ResponseTemplates: application/json: "" MethodResponses: - StatusCode: "200" ResponseModels: application/json: !Ref EmptyModel ResponseParameters: method.response.header.Access-Control-Allow-Headers: false method.response.header.Access-Control-Allow-Methods: false method.response.header.Access-Control-Allow-Origin: false # Protected API GET method to get NFTs from Moralis (/getNFTsMoralis). Passthrough API Gateway Proxy. No Lambda. # AuthorizationType: COGNITO_USER_POOLS # We use the token given by the Cognito UserPool to authorize access to the route GetNFTsMoralisGETMethod: Type: AWS::ApiGateway::Method Properties: RestApiId: !Ref Api ResourceId: !Ref GetNFTsMoralisResource HttpMethod: GET AuthorizationType: COGNITO_USER_POOLS AuthorizerId: !Ref APICognitoAuthorizer RequestParameters: method.request.querystring.chain: false Integration: Type: HTTP IntegrationHttpMethod: GET PassthroughBehavior: NEVER # We inject the Moralis URL containing the API Key from our prod.parameters file Uri: !Ref MoralisUrl RequestParameters: integration.request.path.address: "'placeholder'" integration.request.querystring.chain: 'method.request.querystring.chain' # Here we inject the Moralis API key in the header # We also inject the user wallet address in the {address} variable located in the HTTP Integration URI RequestTemplates: application/json: !Sub | #set($address = $context.authorizer.claims['cognito:username']) #set($context.requestOverride.header.X-API-Key = '${MoralisAPIKey}') #set($context.requestOverride.path.address = $address) $input.json("$") IntegrationResponses: - StatusCode: "200" ResponseParameters: method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'" method.response.header.Access-Control-Allow-Methods: "'GET,OPTIONS'" method.response.header.Access-Control-Allow-Origin: "'*'" - StatusCode: "500" ResponseParameters: method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'" method.response.header.Access-Control-Allow-Methods: "'GET,OPTIONS'" method.response.header.Access-Control-Allow-Origin: "'*'" MethodResponses: - StatusCode: "200" ResponseParameters: method.response.header.Access-Control-Allow-Headers: false method.response.header.Access-Control-Allow-Methods: false method.response.header.Access-Control-Allow-Origin: false - StatusCode: "500" ResponseParameters: method.response.header.Access-Control-Allow-Headers: false method.response.header.Access-Control-Allow-Methods: false method.response.header.Access-Control-Allow-Origin: false # Protected API Method executing a Lambda function to call Moralis # We pass the API keys as environement variables GetNFTsMoralisLambdaFunction: Type: AWS::Serverless::Function Properties: FunctionName: !Join - "-" - - !Ref "AWS::StackName" - GetNFTsMoralisLambdaFunction Handler: src/handlers/getNFTsMoralisLambda.handler MemorySize: 256 Environment: Variables: MORALIS_API_KEY: !Ref MoralisAPIKey Description: Lambda to get data my NFTS from Moralis. Events: ApiEvent: Type: Api Properties: Path: /getNFTsMoralisLambda Method: GET RestApiId: !Ref Api # We attach our Lambda to our existing API ## ## CORS Proxy API /corsProxy ## Lambda function used to proxy all http calls to different domains to get the NFTs' .json metadata files ## Bypass CORS issues. Max 10MB payload ## CorsProxyFunction: Type: AWS::Serverless::Function Properties: FunctionName: !Join - "-" - - !Ref "AWS::StackName" - corsProxy Handler: src/handlers/corsProxy.handler MemorySize: 256 Description: | Provides an endpoint to get files through a controlled domain and allow CORS requests to different domains. 10MB payload max. Used to get the .json NFT metadata files from HTTP if they are not on IPFS. Events: ApiEvent: Type: Api Properties: Auth: Authorizer: NONE Path: /corsProxy Method: GET RestApiId: !Ref Api # We attach our Lambda to our existing API ## ## Lambda triggers for custom auth flow ## See: https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-challenge.html ## DefineAuthChallengeFunction: Type: AWS::Serverless::Function Properties: FunctionName: !Join - "-" - - !Ref "AWS::StackName" - DefineAuthChallengeFunction Handler: src/handlers/defineAuthChallenge.handler MemorySize: 256 Description: Lambda function - Cognito Trigger to define Auth Challenge Events: CognitoEvent: Type: Cognito Properties: UserPool: !Ref CognitoUserPool Trigger: DefineAuthChallenge CreateAuthChallengeFunction: Type: AWS::Serverless::Function Properties: FunctionName: !Join - "-" - - !Ref "AWS::StackName" - CreateAuthChallengeFunction Handler: src/handlers/createAuthChallenge.handler MemorySize: 256 Description: Lambda function - Cognito Trigger to creates Auth Challenge Events: CognitoEvent: Type: Cognito Properties: UserPool: !Ref CognitoUserPool Trigger: CreateAuthChallenge VerifyAuthChallengeResponseFunction: Type: AWS::Serverless::Function Properties: FunctionName: !Join - "-" - - !Ref "AWS::StackName" - VerifyAuthChallengeResponseFunction Handler: src/handlers/verifyAuthChallengeResponse.handler MemorySize: 256 Description: Lambda function - Cognito Trigger to verify Auth Challenge response Events: CognitoEvent: Type: Cognito Properties: UserPool: !Ref CognitoUserPool Trigger: VerifyAuthChallengeResponse PreSignUpFunction: Type: AWS::Serverless::Function Properties: FunctionName: !Join - "-" - - !Ref "AWS::StackName" - PreSignupFunction Handler: src/handlers/preSignUp.handler MemorySize: 256 Description: Lambda function - Cognito Trigger before signup Events: CognitoEvent: Type: Cognito Properties: UserPool: !Ref CognitoUserPool Trigger: PreSignUp ## ## Cognito ## # Cognito UserPool # Used to store our users identity and wallet addresses CognitoUserPool: Type: AWS::Cognito::UserPool Properties: Policies: PasswordPolicy: MinimumLength: 30 RequireLowercase: false RequireNumbers: false RequireSymbols: false RequireUppercase: false UserPoolName: !Join - "-" - - !Ref "AWS::StackName" - UserPool CognitoUserPoolWebClient: Type: AWS::Cognito::UserPoolClient Properties: ClientName: !Join - "-" - - !Ref "AWS::StackName" - UserPoolWebClient GenerateSecret: false UserPoolId: !Ref CognitoUserPool ExplicitAuthFlows: - ALLOW_REFRESH_TOKEN_AUTH - ALLOW_CUSTOM_AUTH PreventUserExistenceErrors: ENABLED # Cognito IdentityPool - Used by AWS_IAM Authorization mode # Allows unauthenticated users to still get a role and the permission to access certain routes # This way your API routes are never publicly available CognitoIdentityPool: Type: AWS::Cognito::IdentityPool Properties: AllowUnauthenticatedIdentities: true IdentityPoolName: !Join - "-" - - !Ref "AWS::StackName" - IdentityPool CognitoIdentityProviders: - ClientId: !Ref CognitoUserPoolWebClient ProviderName: !GetAtt CognitoUserPool.ProviderName # Associate roles to Cognito Identity pool CognitoIdentityPoolRolesMapping: Type: AWS::Cognito::IdentityPoolRoleAttachment Properties: IdentityPoolId: !Ref CognitoIdentityPool Roles: authenticated: !GetAtt CognitoAuthorizedRole.Arn unauthenticated: !GetAtt CognitoUnAuthorizedRole.Arn ## ## IAM Roles ## Note: Those IAM roles are used for API Routes using the AWS_IAM authorization method only ## # AWS IAM Role for unauthenticated users CognitoAuthorizedRole: Type: "AWS::IAM::Role" Properties: RoleName: !Join - "-" - - !Ref "AWS::StackName" - CognitoAuthorizedRole 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 CognitoIdentityPool "ForAnyValue:StringLike": "cognito-identity.amazonaws.com:amr": authenticated - Effect: "Allow" Principal: Service: "apigateway.amazonaws.com" Action: - "sts:AssumeRole" Policies: - PolicyName: !Join - "-" - - !Ref "AWS::StackName" - CognitoAuthorizedPolicy PolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: - "cognito-identity:*" Resource: "*" - Effect: "Allow" Action: - "execute-api:Invoke" Resource: - !Sub "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:*/*/GET/getNFTsCollectionAlchemy" # AWS IAM Role for authenticated users CognitoUnAuthorizedRole: Type: AWS::IAM::Role Properties: RoleName: !Join - "-" - - !Ref "AWS::StackName" - CognitoUnAuthorizedRole MaxSessionDuration: 43200 AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Federated: cognito-identity.amazonaws.com Action: sts:AssumeRoleWithWebIdentity Condition: "ForAnyValue:StringLike": "cognito-identity.amazonaws.com:amr": "unauthenticated" - Effect: "Allow" Principal: Service: "apigateway.amazonaws.com" Action: - "sts:AssumeRole" Policies: - PolicyName: !Join - "-" - - !Ref "AWS::StackName" - CognitoUnAuthorizedPolicy PolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: - "cognito-identity:*" Resource: "*" - Effect: "Allow" Action: - "execute-api:Invoke" Resource: - !Sub "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:*/*/GET/getNFTsCollectionAlchemy" ## ## S3 bucket and CloudFront distribution ## Can be used to deploy the dApp online ## CloudFrontOriginAccessControl: Type: AWS::CloudFront::OriginAccessControl Properties: OriginAccessControlConfig: Description: Access control to the S3 bucket Name: WebhostS3BucketOriginAccessControl OriginAccessControlOriginType: s3 SigningBehavior: always SigningProtocol: sigv4 CloudFrontDistribution: Type: "AWS::CloudFront::Distribution" Properties: DistributionConfig: Origins: - DomainName: !GetAtt WebhostS3Bucket.DomainName Id: s3-webhost OriginAccessControlId: !GetAtt CloudFrontOriginAccessControl.Id S3OriginConfig: OriginAccessIdentity: "" Enabled: true Comment: "Cloudfront distribution for NFT Gallery" DefaultRootObject: "index.html" PriceClass: PriceClass_All DefaultCacheBehavior: Compress: true TargetOriginId: s3-webhost ForwardedValues: Cookies: Forward: none QueryString: false ViewerProtocolPolicy: allow-all AllowedMethods: - GET - HEAD CachedMethods: - GET - HEAD WebhostS3Bucket: Type: AWS::S3::Bucket WebhostS3BucketPolicy: Type: AWS::S3::BucketPolicy Properties: Bucket: !Ref WebhostS3Bucket PolicyDocument: Statement: - Effect: Allow Action: 's3:GetObject' Resource: - !Sub "arn:aws:s3:::${WebhostS3Bucket}/*" Principal: Service: "cloudfront.amazonaws.com" Condition: StringEquals: AWS:SourceArn: !Sub "arn:aws:cloudfront::${AWS::AccountId}:distribution/${CloudFrontDistribution}" # Stack output Outputs: ApiGatewayEndpoint: Description: "API Gateway endpoint URL for 'prod' stage" Value: !Sub "https://${Api}.execute-api.${AWS::Region}.amazonaws.com/prod" CognitoUserPoolId: Description: "ID of the Cognito User Pool" Value: !Ref CognitoUserPool CognitoUserPoolWebClientId: Description: "Client ID associated to the Cognito UserPool Web Client" Value: !Ref CognitoUserPoolWebClient CognitoIdentityPoolId: Description: "ID of the Cognito Identity Pool" Value: !Ref CognitoIdentityPool CloudFrontDistributionEndpoint: Description: "URL of the Amazon CloudFront distribution you can use to access your webapp after deploying it to the S3 bucket" Value: !GetAtt CloudFrontDistribution.DomainName WebhostS3Bucket: Description: "The S3 bucket used as Origin to your CloudFront distribution. Deploy your webapp there." Value: !Ref WebhostS3Bucket