Description: >
  MediaSearch Solution - Finder stack (v0.2.0)
  
Resources:
  ##Create Cognito Userpool for Authentication
  UserPool:
    Type: 'AWS::Cognito::UserPool'
    Properties:
      AliasAttributes:
        - email
      AutoVerifiedAttributes:
        - email
      UsernameConfiguration:
        CaseSensitive: false
      AdminCreateUserConfig:
        AllowAdminCreateUserOnly: true
        InviteMessageTemplate:
          EmailMessage: "<p>Hello {username},\n<p>Welcome to Finder App! Your temporary password is:\n<br><p>     {####}<br>\n<p>When the CloudFormation stack is COMPLETE, use the MediaSearchFinderURL in the Outputs tab of the CloudFormation stack to login using {username} as username, set your permanent password, and start searching!\n<p>Good luck!\n"
          EmailSubject: "Welcome to Finder Web App"
      
  Admins:
    Type: 'AWS::Cognito::UserPoolGroup'
    Condition: EnableAuth
    Properties:
      GroupName: 'Admins'
      UserPoolId: !Ref UserPool
      
  AdminUser:
    Type: 'AWS::Cognito::UserPoolUser'
    Condition: EnableAuth
    Properties:
      DesiredDeliveryMediums:
        - EMAIL
      UserAttributes:
        - Name: 'email'
          Value: !Ref AdminEmail
      Username: 'Admin'
      UserPoolId: !Ref UserPool
        
  AdminToAdmins:
    Type: 'AWS::Cognito::UserPoolUserToGroupAttachment'
    Condition: EnableAuth
    Properties:
      GroupName: !Ref Admins
      Username: !Ref AdminUser
      UserPoolId: !Ref UserPool
      
  ##Create Cognito IdentityPool for Authorization and associate the UserPool client with it
  IdentityPool:
    Type: 'AWS::Cognito::IdentityPool'
    Properties:
      AllowClassicFlow: false
      AllowUnauthenticatedIdentities: true
      CognitoIdentityProviders:
        - ClientId: !Ref UserPoolClient
          ProviderName: !Sub
            - 'cognito-idp.${region}.amazonaws.com/${client}'
            - region: !Ref 'AWS::Region'
              client: !Ref UserPool
              
  UserPoolClient:
    Type: 'AWS::Cognito::UserPoolClient'
    Properties:
      UserPoolId: !Ref UserPool
      
  ##Attach Auth/UnAuth roles for to ID Pool
  IdentityPoolRoleAttachment:
    Type: 'AWS::Cognito::IdentityPoolRoleAttachment'
    Properties:
      IdentityPoolId: !Ref IdentityPool
      Roles:
        'authenticated': !GetAtt IDPoolAuthRole.Arn
        'unauthenticated': !GetAtt IDPoolUnauthRole.Arn
          
  ##Role to be used as Media role for the Identity Pool
  IDPoolAuthRole:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Sid: ''
            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': authenticated
          - Effect: Allow
            Principal:
              Service: amplify.amazonaws.com
            Action: sts:AssumeRole
      
  ##Role to be used as Unauth role for the Identity Pool
  IDPoolUnauthRole:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Sid: ''
            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
          - Effect: Allow
            Principal:
              Service: amplify.amazonaws.com
            Action: sts:AssumeRole
      
  ## Role to be used by the repository 
  MediaRepositoryAssumeRole:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Sid: ''
            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': authenticated
          - Effect: Allow
            Principal:
              Service: amplify.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Resource:
                  - !GetAtt
                    - Repository
                    - Arn
                Action:
                  - 'codecommit:GitPull'
          PolicyName: MediaRepositoryExecutionPolicy
  
  ##The role to be assumed by the application using sts_assume_role
  MediaAppCredsRole:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Sid: ''
            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': authenticated
          - Effect: Allow
            Principal:
              Service: amplify.amazonaws.com
            Action: sts:AssumeRole
          - Effect: Allow
            Principal:
              AWS:
                - !GetAtt IDPoolAuthRole.Arn
                - !Join
                  - ''
                  - - 'arn:aws:sts::'
                    - !Ref 'AWS::AccountId'
                    - ':assumed-role/'
                    - !Ref IDPoolAuthRole
                    - '/CognitoIdentityCredentials'
                - !GetAtt IDPoolAuthRole.Arn
                - !Join
                  - ''
                  - - 'arn:aws:sts::'
                    - !Ref 'AWS::AccountId'
                    - ':assumed-role/'
                    - !Ref IDPoolUnauthRole
                    - '/CognitoIdentityCredentials'
            Action: 'sts:AssumeRole'
      Policies:
        - PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Resource: !Sub
                  - 'arn:aws:kendra:${region}:${account}:index/${index}'
                  - region: !Ref 'AWS::Region'
                    account: !Ref 'AWS::AccountId'
                    index: !Ref KendraIndexId
                Action:
                  - 'kendra:DescribeIndex'
                  - 'kendra:SubmitFeedback'
                  - 'kendra:ListDataSources'
                  - 'kendra:Query'
              - Effect: Allow
                Resource: !Split
                  - ','
                  - !Sub
                    - 'arn:aws:s3:::${inner}/*'
                    - inner: !Join
                      - '/*,arn:aws:s3:::'
                      - !Ref MediaBucketNames
                Action:
                  - 's3:GetObject'                
          PolicyName: AWSMediaAppCredsPolicy
      
  ##Create CodeCommit Repository for the code of the application
  Repository:
    Type: 'AWS::CodeCommit::Repository'
    Properties:
      Code:
        BranchName: main
        S3:
          Bucket: '<ARTIFACT_BUCKET_TOKEN>'
          Key: !Join
          - ''
          - - '<ARTIFACT_PREFIX_TOKEN>'
            - '<FINDER_ZIPFILE>'
      RepositoryName: !Join
        - ''
        - - !Ref 'AWS::StackName'
          - '-Repo'
          
  ##Create an App for our application in the Amplify Console
  AmplifyApp:
    Type: 'AWS::Amplify::App'
    Properties:
      BuildSpec: |-
        version: 1
        frontend:
          phases:
            preBuild:
              commands:
                - npm install
            build:
              commands:
                - npm run build
                - REACT_APP_INDEX_ID=$REACT_APP_INDEX_ID
                - REACT_APP_REGION=$REACT_APP_REGION
                - REACT_APP_PROJECT_REGION=$REACT_APP_PROJECT_REGION
                - REACT_APP_IDENTITY_POOL_ID=$REACT_APP_IDENTITY_POOL_ID
                - REACT_APP_COGNITO_REGION=$REACT_APP_COGNITO_REGION
                - REACT_APP_USER_POOL_ID=$REACT_APP_USER_POOL_ID
                - REACT_APP_WEB_CLIENT_ID=$REACT_APP_WEB_CLIENT_ID
                - REACT_APP_ROLE_ARN=$REACT_APP_ROLE_ARN
          artifacts:
            baseDirectory: build
            files:
              - '**/*'
          cache:
            paths:
              - node_modules/**/*
      EnvironmentVariables:
        - Name: REACT_APP_COGNITO_REGION
          Value: !Ref 'AWS::Region'
        - Name: REACT_APP_IDENTITY_POOL_ID
          Value: !Ref IdentityPool
        - Name: REACT_APP_INDEX_ID
          Value: !Ref KendraIndexId 
        - Name: REACT_APP_PROJECT_REGION
          Value: !Ref 'AWS::Region'
        - Name: REACT_APP_REGION
          Value: !Ref 'AWS::Region'
        - Name: REACT_APP_USER_POOL_ID
          Value: !Ref UserPool
        - Name: REACT_APP_WEB_CLIENT_ID
          Value: !Ref UserPoolClient
        - Name: REACT_APP_ROLE_ARN
          Value: !GetAtt MediaAppCredsRole.Arn
        - Name: REACT_APP_ENABLE_AUTH
          Value: !If [EnableAuth, 'true', 'false']
        - Name: REACT_APP_ENABLE_GUEST
          Value: !Ref EnableGuestUser
        - Name: REACT_APP_ENABLE_ACCESSTOKENS
          Value: !Ref EnableAccessTokens
      IAMServiceRole: !GetAtt MediaRepositoryAssumeRole.Arn
      Name: !Join
        - ''
        - - !Ref 'AWS::StackName'
          - '-App'
      Repository: !GetAtt
        - Repository
        - CloneUrlHttp
  ##Create a branch for the App to be built
  AmplifyBranch:
    Type: 'AWS::Amplify::Branch'
    Properties:
      AppId: !GetAtt
        - AmplifyApp
        - AppId
      EnableAutoBuild: true
      BranchName: main

  MediaLambdaRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Resource: '*'
                Action:
                  - 'amplify:*'
          PolicyName: MediaLambdaPolicy
        
  BuildTriggerLambda:
    Type: AWS::Lambda::Function
    Properties:
      Handler: lambda_function.lambda_handler
      Runtime: python3.8
      Role: !GetAtt 'MediaLambdaRole.Arn'
      Timeout: 300
      Code:
        S3Bucket: '<ARTIFACT_BUCKET_TOKEN>'
        S3Key: !Join
          - ''
          - - '<ARTIFACT_PREFIX_TOKEN>'
            - '<BUILDTRIGGER_ZIPFILE>'
      Environment:
        Variables:
          APP_ID: !GetAtt AmplifyApp.AppId
      
  BuildTrigger:
    Type: Custom::BuildTrigger
    DependsOn: 
      - AmplifyApp
      - AmplifyBranch
      - MediaAppCredsRole
    Properties:
      ServiceToken: !GetAtt BuildTriggerLambda.Arn
      Param1: '<ARTIFACT_BUCKET_TOKEN>'
      Param2: '<ARTIFACT_PREFIX_TOKEN>'
      Param3: !Ref KendraIndexId
      Param4: !Ref MediaBucketNames
      Param5: !Ref EnableAccessTokens
      Param6: !Ref EnableGuestUser
      Param7: !Ref AdminEmail
  
  TokenEnablerLambdaRole:
    Type: AWS::IAM::Role
    Condition: EnableAccess
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Resource: !Sub
                  - 'arn:aws:kendra:${region}:${account}:index/${index}*'
                  - region: !Ref 'AWS::Region'
                    account: !Ref 'AWS::AccountId'
                    index: !Ref KendraIndexId
                Action:
                  - 'kendra:*'
          PolicyName: TokenEnablerLambdaPolicy
          
  TokenEnablerLambda:
    Type: AWS::Lambda::Function
    Condition: EnableAccess
    Properties:
      Handler: lambda_function.lambda_handler
      Runtime: python3.8
      Role: !GetAtt 'TokenEnablerLambdaRole.Arn'
      Timeout: 900
      MemorySize: 1024
      Code:
        S3Bucket: '<ARTIFACT_BUCKET_TOKEN>'
        S3Key: !Join
          - ''
          - - '<ARTIFACT_PREFIX_TOKEN>'
            - '<TOKEN_ENABLER_ZIPFILE>'
      Environment:
        Variables:
          INDEX_ID: !Ref KendraIndexId
          SIGNING_KEY_URL: !Join
            - ''
            - - 'https://cognito-idp.'
              - !Ref 'AWS::Region'
              - '.amazonaws.com/'
              - !Ref UserPool
              - '/.well-known/jwks.json'
  
  TokenEnabler:
    Type: Custom::TokenEnabler
    Condition: EnableAccess
    DependsOn: 
      - AmplifyApp
    Properties:
      ServiceToken: !GetAtt TokenEnablerLambda.Arn
      Param1: '<ARTIFACT_BUCKET_TOKEN>'
      Param2: '<ARTIFACT_PREFIX_TOKEN>'
      Param3: !Ref KendraIndexId
      Param4: !Join
        - ''
        - - 'https://cognito-idp.'
          - !Ref 'AWS::Region'
          - '.amazonaws.com/'
          - !Ref UserPool
          - '/.well-known/jwks.json'
    
Parameters:
  KendraIndexId:
    Type: String
  MediaBucketNames:
    Type: CommaDelimitedList
    Default: "<SAMPLES_BUCKET>"
    Description: >-
      (Required) A comma-delimited list of media bucket names - may include wildcards. Needed to support presigned URLs used to access media files contained in search results.
  AdminEmail:
    Type: String
    Description: 'To enable authentication please provide a valid email address for the admin user. This email address will be used for setting the admin password. This email will receive the temporary password for the admin user.'
    AllowedPattern: "(^$|^.+\\@.+\\..+)"
    Default: ''
    ConstraintDescription: 'Must be valid email address eg. johndoe@example.com'
  EnableGuestUser:
    Type: String
    Default: 'false'
    AllowedValues: ['true', 'false']
    Description: 'Set true to enable using the search application without logging in'
  EnableAccessTokens:
    Type: String
    Default: 'false'
    AllowedValues: ['true', 'false']
    Description: 'Set true to enable use of Cognito user pool access tokens in the Kendra index'
    
Metadata:
    AWS::CloudFormation::Interface:
        ParameterGroups:
            - Label:
                default: Kendra search webapp parameters
              Parameters:
                  - KendraIndexId
                  - MediaBucketNames
            - Label:
                default: Authentication and access control parameters
              Parameters:
                  - AdminEmail
                  - EnableGuestUser
                  - EnableAccessTokens
                  
Conditions:
  EnableAuth: !Not
    - !Equals
      - !Ref AdminEmail
      - ''
  EnableAccess: !Equals 
    - !Ref EnableAccessTokens
    - 'true'
    
Outputs:
  MediaSearchFinderURL:
    Value: !Join
      - ''
      - - 'https://main.'
        - !GetAtt AmplifyApp.DefaultDomain
        
  CognitoUserPool: 
    Value: !Ref UserPool
  
  CognitoSigningKeyURL:
    Value: !Join
      - ''
      - - 'https://cognito-idp.'
        - !Ref 'AWS::Region'
        - '.amazonaws.com/'
        - !Ref UserPool
        - '/.well-known/jwks.json'