AWSTemplateFormatVersion: 2010-09-09
Resources:
  CognitoUserPool:
    Type: 'AWS::Cognito::UserPool'
    Properties:
      UserPoolName: CognitoPool
  CognitoUserPoolDomain:
    Type: 'AWS::Cognito::UserPoolDomain'
    Properties:
      # using client id will make the domain unique
      Domain: !Sub dns-name-${CognitoUserPoolClient}
      UserPoolId: !Ref CognitoUserPool
    DependsOn:
      - CognitoUserPoolClient
  CognitoUserPoolGroup:
    Type: 'AWS::Cognito::UserPoolGroup'
    Properties:
      GroupName: pet-veterinarian
      UserPoolId: !Ref CognitoUserPool
    DependsOn:
      - CognitoUserPool
  # >>> helper to create Cognito User without the need to confirm email
  HelperCognitoLambdaRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: "/"
      Policies:
        - PolicyName: helperCognitoLambdaRole
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - cognito-idp:Admin*
                Resource: !GetAtt CognitoUserPool.Arn
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${AWS::StackName}-HelperInitCognitoFunction-*:*
  HelperInitCognitoFunction:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        ZipFile: >
          const AWS = require("aws-sdk");
          const response = require("cfn-response");
          const cognitoidentityserviceprovider = new AWS.CognitoIdentityServiceProvider({apiVersion: '2016-04-18'});

          exports.handler = function (event, context, callback) {
              var userPoolId = event.ResourceProperties.UserPoolId;
              var username = event.ResourceProperties.CognitoUserName;
              var pass = event.ResourceProperties.CognitoUserPassword;

              console.log("username: " + username);

              var params = {
                  UserPoolId: userPoolId,
                  Username: username,
                  TemporaryPassword: pass
              };

              cognitoidentityserviceprovider.adminCreateUser(params, function (err, data) {
                  if (err) {
                      console.log(err, err.stack);
                  } else {
                      console.log(data);
                  }

                  const params = {
                      UserPoolId: userPoolId,
                      Username: username,
                      Password: pass,
                      Permanent: true
                  };
                  cognitoidentityserviceprovider.adminSetUserPassword(params, function (err, data) {
                      if (err) {
                          response.send(event, context, "FAILED", {});
                      } else {
                          response.send(event, context, "SUCCESS", {});
                      }
                  });
              });
          };
      Handler: index.handler
      Role: !GetAtt HelperCognitoLambdaRole.Arn
      Runtime: nodejs12.x
      Timeout: 30
  HelperInitializeCognitoUser:
    Type: Custom::HelperInitCognitoFunction
    DependsOn: CognitoUserPool
    Properties:
      ServiceToken: !GetAtt HelperInitCognitoFunction.Arn
      UserPoolId: !Ref CognitoUserPool
      CognitoUserName: !Ref CognitoUserName
      CognitoUserPassword: !Ref CognitoUserPassword
  # <<< helper to create Cognito User without the need to confirm email
  CognitoUserPoolUserToGroupAttachment:
    Type: 'AWS::Cognito::UserPoolUserToGroupAttachment'
    Properties:
      GroupName: !Ref CognitoUserPoolGroup
      Username: !Ref CognitoUserName
      UserPoolId: !Ref CognitoUserPool
    DependsOn:
      - CognitoUserPoolGroup
      - HelperInitializeCognitoUser
  CognitoUserPoolClient:
    Type: 'AWS::Cognito::UserPoolClient'
    Properties:
      UserPoolId: !Ref CognitoUserPool
      AllowedOAuthFlows:
        - implicit
      AllowedOAuthFlowsUserPoolClient: true
      AllowedOAuthScopes:
        - email
        - openid
      CallbackURLs:
        - 'http://localhost'
      GenerateSecret: false
      ExplicitAuthFlows:
        - ALLOW_USER_PASSWORD_AUTH
        - ALLOW_USER_SRP_AUTH
        - ALLOW_REFRESH_TOKEN_AUTH
      SupportedIdentityProviders:
        - COGNITO
    DependsOn:
      - CognitoUserPool
  # <<< Amazon Cognito
  # >>> API Service
  ApiServiceIAMPolicy:
    Type: 'AWS::IAM::Policy'
    Properties:
      Roles:
        - !Ref ApiServiceIAMRole
      PolicyName: ApiServiceIAMPolicy
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Action:
              - cognito-idp:Admin*
            Resource: !GetAtt CognitoUserPool.Arn
          - Effect: Allow
            Action:
              - logs:CreateLogGroup
              - logs:CreateLogStream
              - logs:PutLogEvents
            Resource: !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/ApiServiceLambdaFunction:*
  ApiServiceIAMRole:
    Type: 'AWS::IAM::Role'
    Properties:
      RoleName: ApiServiceIAMRole
      AssumeRolePolicyDocument: |-
        {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Action": "sts:AssumeRole",
              "Principal": {
                "Service": "lambda.amazonaws.com"
              },
              "Effect": "Allow",
              "Sid": ""
            }
          ]
        }
  ApiServiceLambdaFunction:
    Type: 'AWS::Lambda::Function'
    Properties:
      FunctionName: ApiServiceLambdaFunction
      Runtime: "python3.9"
      Handler: "lambda.handler"
      Role: !GetAtt ApiServiceIAMRole.Arn
      Code:
        S3Bucket: !Sub ${AWS::StackName}-${AWS::AccountId}-${AWS::Region}-lambdas
        S3Key: "pets-api.zip"
  ApiServiceLambdaFunctionPermission:
    Type: 'AWS::Lambda::Permission'
    Properties:
      Action: "lambda:InvokeFunction"
      FunctionName: !GetAtt ApiServiceLambdaFunction.Arn
      Principal: "apigateway.amazonaws.com"
  # <<< API Service
  # >>> Amazon API gateway
  ApiGatewayRestApi:
    Type: 'AWS::ApiGateway::RestApi'
    Properties:
      Name: "MyApiGateway"
  ApiGatewayResource:
    Type: 'AWS::ApiGateway::Resource'
    Properties:
      RestApiId: !Ref ApiGatewayRestApi
      ParentId: !GetAtt ApiGatewayRestApi.RootResourceId
      PathPart: "{api+}"
  ApiGatewayMethod:
    Type: 'AWS::ApiGateway::Method'
    Properties:
      HttpMethod: "ANY"
      ResourceId: !Ref ApiGatewayResource
      RestApiId: !Ref ApiGatewayRestApi
      AuthorizationType: NONE
      Integration:
        Type: AWS_PROXY
        IntegrationHttpMethod: "POST"
        Uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${ApiServiceLambdaFunction.Arn}/invocations
  ApiGatewayDeploymentUnProtected:
    Type: AWS::ApiGateway::Deployment
    Properties:
      RestApiId: !Ref ApiGatewayRestApi
      StageName: dev
      Description: unprotected api
    DependsOn:
      - ApiGatewayMethod
# >>> Inputs
Parameters:
  CognitoUserName:
    Type: String
    Default: cognitouser
    Description: Enter Cognito username.
  CognitoUserPassword:
    Type: String
    AllowedPattern: '^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[\^$*.\[\]{}\(\)?\-“!@#%&/,><\’:;|_~`])\S{8,99}$'
    Description: |-
      Enter Cognito users password. Password must fulfill User Pool Password Requirements.
      See documentaton for more details https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-policies.html
# >>> Outputs
Outputs:
  CognitoUserPoolClientId:
    Value: !Ref CognitoUserPoolClient
  CognitoHostedUiUrl:
    Value: !Sub https://${CognitoUserPoolDomain}.auth.${AWS::Region}.amazoncognito.com/login?client_id=${CognitoUserPoolClient}&response_type=token&scope=email+openid&redirect_uri=http://localhost
  ApiGatewayDeploymentUrlApiEndpoint:
    Value: !Sub https://${ApiGatewayRestApi}.execute-api.${AWS::Region}.amazonaws.com/dev/petstore/v1/pets
  ApiGatewayDeploymentUrlApiEndpointV2:
    Value: !Sub https://${ApiGatewayRestApi}.execute-api.${AWS::Region}.amazonaws.com/dev/petstore/v2/pets