{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Metadata": {},
  "Parameters": {},
  "Rules": {},
  "Mappings": {},
  "Conditions": {},
  "Resources": {
    "PassApi": {
      "Type": "AWS::ApiGateway::RestApi",
      "Properties": {
        "Name": {
          "Fn::Sub": "${AWS::StackName}-webhook"
        },
        "FailOnWarnings": true,
        "EndpointConfiguration": {
          "Types": [
            "REGIONAL"
          ]
        }
      }
    },
    "PassStage": {
      "Type": "AWS::ApiGateway::Stage",
      "Properties": {
        "DeploymentId": {
          "Ref": "PassDeployment2a35d33e"
        },
        "StageName": "hookshot",
        "RestApiId": {
          "Ref": "PassApi"
        },
        "MethodSettings": [
          {
            "HttpMethod": "*",
            "ResourcePath": "/*",
            "ThrottlingBurstLimit": 20,
            "ThrottlingRateLimit": 5,
            "LoggingLevel": "OFF",
            "DataTraceEnabled": false,
            "MetricsEnabled": false
          }
        ]
      }
    },
    "PassDeployment2a35d33e": {
      "Type": "AWS::ApiGateway::Deployment",
      "DependsOn": "PassMethod",
      "Properties": {
        "RestApiId": {
          "Ref": "PassApi"
        },
        "StageName": "unused"
      }
    },
    "PassResource": {
      "Type": "AWS::ApiGateway::Resource",
      "Properties": {
        "ParentId": {
          "Fn::GetAtt": [
            "PassApi",
            "RootResourceId"
          ]
        },
        "RestApiId": {
          "Ref": "PassApi"
        },
        "PathPart": "webhook"
      }
    },
    "PassMethod": {
      "Type": "AWS::ApiGateway::Method",
      "Properties": {
        "RestApiId": {
          "Ref": "PassApi"
        },
        "ResourceId": {
          "Ref": "PassResource"
        },
        "ApiKeyRequired": false,
        "AuthorizationType": "NONE",
        "HttpMethod": "POST",
        "Integration": {
          "Type": "AWS",
          "IntegrationHttpMethod": "POST",
          "IntegrationResponses": [
            {
              "StatusCode": "200"
            },
            {
              "StatusCode": "500",
              "SelectionPattern": "^error.*"
            },
            {
              "StatusCode": "403",
              "SelectionPattern": "^invalid.*"
            }
          ],
          "Uri": {
            "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${PassFunction.Arn}/invocations"
          },
          "RequestTemplates": {
            "application/json": "{\"signature\":\"$input.params('X-Hub-Signature')\",\"body\":$input.json('$')}"
          }
        },
        "MethodResponses": [
          {
            "StatusCode": "200",
            "ResponseModels": {
              "application/json": "Empty"
            }
          },
          {
            "StatusCode": "500",
            "ResponseModels": {
              "application/json": "Empty"
            }
          },
          {
            "StatusCode": "403",
            "ResponseModels": {
              "application/json": "Empty"
            }
          }
        ]
      }
    },
    "PassPermission": {
      "Type": "AWS::Lambda::Permission",
      "Properties": {
        "FunctionName": {
          "Ref": "PassFunction"
        },
        "Action": "lambda:InvokeFunction",
        "Principal": "apigateway.amazonaws.com",
        "SourceArn": {
          "Fn::Sub": "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${PassApi}/*"
        }
      }
    },
    "PassFunctionLogs": {
      "Type": "AWS::Logs::LogGroup",
      "Properties": {
        "LogGroupName": {
          "Fn::Sub": [
            "/aws/lambda/${name}",
            {
              "name": {
                "Fn::Sub": "${AWS::StackName}-Pass"
              }
            }
          ]
        },
        "RetentionInDays": 14
      }
    },
    "PassFunction": {
      "Type": "AWS::Lambda::Function",
      "Properties": {
        "Code": {
          "ZipFile": {
            "Fn::Sub": [
              "'use strict';\n\nconst crypto = require('crypto');\nconst { InvokeCommand, LambdaClient } = require('@aws-sdk/client-lambda');\nconst client = new LambdaClient();\nconst secret = '${WebhookSecret}';\n\nmodule.exports.lambda = (event, context, callback) => {\n  const body = event.body;\n  const hash = 'sha1=' + crypto\n    .createHmac('sha1', secret)\n    .update(Buffer.from(JSON.stringify(body)))\n    .digest('hex');\n\n  if (event.signature !== hash)\n    return callback('invalid: signature does not match');\n\n  if (body.zen) return callback(null, 'ignored ping request');\n\n  const command = new InvokeCommand({\n    FunctionName: '${Destination}',\n    Payload: JSON.stringify(event.body),\n    InvocationType: 'Event'\n  });\n\n  client.send(command)\n    .then(() => callback(null, 'success'))\n    .catch((err) => callback(err));\n};",
              {
                "WebhookSecret": "abc123"
              }
            ]
          }
        },
        "Description": {
          "Fn::Sub": "Passthrough function for ${AWS::StackName}"
        },
        "FunctionName": {
          "Fn::Sub": "${AWS::StackName}-Pass"
        },
        "Handler": "index.lambda",
        "MemorySize": 128,
        "Runtime": "nodejs18.x",
        "Timeout": 30,
        "Role": {
          "Fn::GetAtt": [
            "PassFunctionRole",
            "Arn"
          ]
        }
      }
    },
    "PassFunctionErrorAlarm": {
      "Type": "AWS::CloudWatch::Alarm",
      "Properties": {
        "AlarmName": {
          "Fn::Sub": "${AWS::StackName}-PassFunction-Errors-${AWS::Region}"
        },
        "AlarmDescription": {
          "Fn::Sub": [
            "Error alarm for ${name} lambda function in ${AWS::StackName} stack",
            {
              "name": {
                "Fn::Sub": "${AWS::StackName}-Pass"
              }
            }
          ]
        },
        "AlarmActions": [],
        "Period": 60,
        "EvaluationPeriods": 1,
        "DatapointsToAlarm": 1,
        "Statistic": "Sum",
        "Threshold": 0,
        "ComparisonOperator": "GreaterThanThreshold",
        "TreatMissingData": "notBreaching",
        "Namespace": "AWS/Lambda",
        "Dimensions": [
          {
            "Name": "FunctionName",
            "Value": {
              "Ref": "PassFunction"
            }
          }
        ],
        "MetricName": "Errors"
      }
    },
    "PassFunctionLogPolicy": {
      "Type": "AWS::IAM::Policy",
      "DependsOn": "PassFunctionRole",
      "Properties": {
        "PolicyName": "lambda-log-access",
        "Roles": [
          {
            "Ref": "PassFunctionRole"
          }
        ],
        "PolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Action": "logs:*",
              "Resource": {
                "Fn::GetAtt": [
                  "PassFunctionLogs",
                  "Arn"
                ]
              }
            }
          ]
        }
      }
    },
    "PassFunctionRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Statement": [
            {
              "Effect": "Allow",
              "Action": "sts:AssumeRole",
              "Principal": {
                "Service": {
                  "Fn::Sub": "lambda.amazonaws.com"
                }
              }
            }
          ]
        },
        "Policies": [
          {
            "PolicyName": "main",
            "PolicyDocument": {
              "Version": "2012-10-17",
              "Statement": [
                {
                  "Effect": "Allow",
                  "Action": "lambda:InvokeFunction",
                  "Resource": {
                    "Fn::GetAtt": [
                      "Destination",
                      "Arn"
                    ]
                  }
                }
              ]
            }
          }
        ]
      }
    },
    "DestinationLogs": {
      "Type": "AWS::Logs::LogGroup",
      "Properties": {
        "LogGroupName": {
          "Fn::Sub": [
            "/aws/lambda/${name}",
            {
              "name": {
                "Fn::Sub": "${AWS::StackName}-Destination"
              }
            }
          ]
        },
        "RetentionInDays": 14
      }
    },
    "Destination": {
      "Type": "AWS::Lambda::Function",
      "Properties": {
        "Code": {
          "ZipFile": "module.exports.handler = (e, c, cb) => cb();"
        },
        "Description": {
          "Fn::Sub": "Destination in the ${AWS::StackName} stack"
        },
        "FunctionName": {
          "Fn::Sub": "${AWS::StackName}-Destination"
        },
        "Handler": "index.handler",
        "MemorySize": 128,
        "Runtime": "nodejs18.x",
        "Timeout": 300,
        "Role": {
          "Fn::GetAtt": [
            "DestinationRole",
            "Arn"
          ]
        }
      }
    },
    "DestinationErrorAlarm": {
      "Type": "AWS::CloudWatch::Alarm",
      "Properties": {
        "AlarmName": {
          "Fn::Sub": "${AWS::StackName}-Destination-Errors-${AWS::Region}"
        },
        "AlarmDescription": {
          "Fn::Sub": [
            "Error alarm for ${name} lambda function in ${AWS::StackName} stack",
            {
              "name": {
                "Fn::Sub": "${AWS::StackName}-Destination"
              }
            }
          ]
        },
        "AlarmActions": [],
        "Period": 60,
        "EvaluationPeriods": 5,
        "DatapointsToAlarm": 1,
        "Statistic": "Sum",
        "Threshold": 0,
        "ComparisonOperator": "GreaterThanThreshold",
        "TreatMissingData": "notBreaching",
        "Namespace": "AWS/Lambda",
        "Dimensions": [
          {
            "Name": "FunctionName",
            "Value": {
              "Ref": "Destination"
            }
          }
        ],
        "MetricName": "Errors"
      }
    },
    "DestinationLogPolicy": {
      "Type": "AWS::IAM::Policy",
      "DependsOn": "DestinationRole",
      "Properties": {
        "PolicyName": "lambda-log-access",
        "Roles": [
          {
            "Ref": "DestinationRole"
          }
        ],
        "PolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Action": "logs:*",
              "Resource": {
                "Fn::GetAtt": [
                  "DestinationLogs",
                  "Arn"
                ]
              }
            }
          ]
        }
      }
    },
    "DestinationRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Statement": [
            {
              "Effect": "Allow",
              "Action": "sts:AssumeRole",
              "Principal": {
                "Service": {
                  "Fn::Sub": "lambda.amazonaws.com"
                }
              }
            }
          ]
        }
      }
    }
  },
  "Outputs": {
    "PassEndpointOutput": {
      "Description": "The HTTPS endpoint used to send github webhooks",
      "Value": {
        "Fn::Sub": "https://${PassApi}.execute-api.${AWS::Region}.amazonaws.com/hookshot/webhook"
      }
    },
    "PassSecretOutput": {
      "Description": "A secret key to give Github to use when signing webhook requests",
      "Value": "abc123"
    }
  }
}