├── host.json ├── ConnectMessage ├── sample.dat ├── function.json ├── package.json ├── postSetup.sh ├── INFRA.md ├── serverless.yml ├── template.json └── index.js ├── .funcignore ├── proxies.json ├── .vscode ├── extensions.json ├── launch.json ├── tasks.json └── settings.json ├── docs ├── Azure.01.signup.png ├── Azure.07.add.queue.png ├── Azure.08.add.queue.png ├── Azure.02.svc.search.png ├── Azure.03.svc.create.png ├── Azure.05.add.policy.png ├── Azure.09.function.create.png ├── Azure.10.function.create.png ├── Azure.04.svc.namespace.page.png ├── Azure.06.connection.string.png ├── Azure.11.function.add.code.png ├── connect_listener_architecture.png ├── INSTALLATION_4_svc_bus_queue.md ├── INSTALLATION_2_svc_bus_namespace.md ├── INSTALLATION_3_svc_bus_connection_string.md └── INSTALLATION_5_function.md ├── extensions.csproj ├── INSTALLATION.md ├── LICENSE ├── .gitignore └── README.md /host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0" 3 | } 4 | -------------------------------------------------------------------------------- /ConnectMessage/sample.dat: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Azure" 3 | } -------------------------------------------------------------------------------- /.funcignore: -------------------------------------------------------------------------------- 1 | .git* 2 | .vscode 3 | local.settings.json 4 | test -------------------------------------------------------------------------------- /proxies.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/proxies", 3 | "proxies": {} 4 | } 5 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "ms-azuretools.vscode-azurefunctions" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /docs/Azure.01.signup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/connect-node-listener-azure/master/docs/Azure.01.signup.png -------------------------------------------------------------------------------- /docs/Azure.07.add.queue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/connect-node-listener-azure/master/docs/Azure.07.add.queue.png -------------------------------------------------------------------------------- /docs/Azure.08.add.queue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/connect-node-listener-azure/master/docs/Azure.08.add.queue.png -------------------------------------------------------------------------------- /docs/Azure.02.svc.search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/connect-node-listener-azure/master/docs/Azure.02.svc.search.png -------------------------------------------------------------------------------- /docs/Azure.03.svc.create.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/connect-node-listener-azure/master/docs/Azure.03.svc.create.png -------------------------------------------------------------------------------- /docs/Azure.05.add.policy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/connect-node-listener-azure/master/docs/Azure.05.add.policy.png -------------------------------------------------------------------------------- /docs/Azure.09.function.create.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/connect-node-listener-azure/master/docs/Azure.09.function.create.png -------------------------------------------------------------------------------- /docs/Azure.10.function.create.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/connect-node-listener-azure/master/docs/Azure.10.function.create.png -------------------------------------------------------------------------------- /docs/Azure.04.svc.namespace.page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/connect-node-listener-azure/master/docs/Azure.04.svc.namespace.page.png -------------------------------------------------------------------------------- /docs/Azure.06.connection.string.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/connect-node-listener-azure/master/docs/Azure.06.connection.string.png -------------------------------------------------------------------------------- /docs/Azure.11.function.add.code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/connect-node-listener-azure/master/docs/Azure.11.function.add.code.png -------------------------------------------------------------------------------- /docs/connect_listener_architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/connect-node-listener-azure/master/docs/connect_listener_architecture.png -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Attach to Node Functions", 6 | "type": "node", 7 | "request": "attach", 8 | "port": 9229, 9 | "preLaunchTask": "func: host start" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "func", 6 | "command": "host start", 7 | "problemMatcher": "$func-watch", 8 | "dependsOn": "func: extensions install", 9 | "isBackground": true 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "azureFunctions.projectRuntime": "~2", 3 | "azureFunctions.projectLanguage": "JavaScript", 4 | "azureFunctions.deploySubpath": ".", 5 | "azureFunctions.preDeployTask": "func: extensions install", 6 | "files.exclude": { 7 | "obj": true, 8 | "bin": true 9 | }, 10 | "debug.internalConsoleOptions": "neverOpen" 11 | } 12 | -------------------------------------------------------------------------------- /ConnectMessage/function.json: -------------------------------------------------------------------------------- 1 | { 2 | "bindings": [ 3 | { 4 | "authLevel": "anonymous", 5 | "type": "httpTrigger", 6 | "direction": "in", 7 | "name": "req", 8 | "methods": [ 9 | "get", 10 | "post" 11 | ] 12 | }, 13 | { 14 | "type": "http", 15 | "direction": "out", 16 | "name": "res" 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /extensions.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netstandard2.0 4 | 5 | ** 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /ConnectMessage/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "connect-node-listener-azure", 3 | "version": "1.0.0", 4 | "description": "Receive notification messages with Azure Functions and Node.js", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "DocuSign", 10 | "license": "MIT", 11 | "dependencies": { 12 | "@azure/service-bus": "^7.2.0" 13 | }, 14 | "devDependencies": { 15 | "serverless-azure-functions": "^2.1.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ConnectMessage/postSetup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "Be sure to run 'az login' first" 3 | echo " " 4 | echo "What is your resource group name?" 5 | read rg 6 | echo "What is your FunctionApp name?" 7 | read functionApp 8 | 9 | az deployment group create --name sdDeployment --resource-group $rg --template-file template.json 10 | connectionstr=$(az servicebus namespace authorization-rule keys list --resource-group $rg --namespace-name Example-Connect-Events-DS --name RootManageSharedAccessKey --query primaryConnectionString --output tsv) 11 | az functionapp config appsettings set --name $functionApp --resource-group $rg --settings "SVC_BUS_CONNECTION_STRING=$connectionstr" -------------------------------------------------------------------------------- /INSTALLATION.md: -------------------------------------------------------------------------------- 1 | # Long form installation instructions 2 | 3 | This guide assumes you have limited knowledge of the 4 | Azure produts. 5 | 6 | ## Feedback requested! 7 | If you have ideas on how these guides can be improved, 8 | please open an Issue for this repository. Or submit a 9 | pull request. Thank you. 10 | 11 | ## Contents 12 | 1. Azure account (below) 13 | 1. [Create a Service Bus Namespace](docs/INSTALLATION_2_svc_bus_namespace.md) 14 | 1. [Create a Service Bus Namespace Connection String](docs/INSTALLATION_3_svc_bus_connection_string.md) 15 | 1. [Create a Service Bus Queue](docs/INSTALLATION_4_svc_bus_queue.md) 16 | 1. [Create the Azure Function](docs/INSTALLATION_5_function.md) 17 | 18 | ## 1. Azure account 19 | 20 | 1. [Create an account on Azure](https://azure.microsoft.com) 21 | 22 | ![Create a topic](docs/Azure.01.signup.png) 23 | 24 | Figure 1. Signup: click **Start free** 25 | 26 | 1. During your first month of use, 27 | you do not need to add billing information to your Azure 28 | account. 29 | 30 | Next: Create a [Service Bus Namespace](docs/INSTALLATION_2_svc_bus_namespace.md) 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2019 DocuSign, Inc. (https://www.docusign.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/INSTALLATION_4_svc_bus_queue.md: -------------------------------------------------------------------------------- 1 | # Create a Service Bus Queue 2 | 3 | #### Summary 4 | Use this article to create a 5 | **Service Bus queue** for your Service Bus Namespace. 6 | 7 | The **queue name** will be used by both 8 | the listener function and the worker application. 9 | 10 | #### Steps 11 | 1. Open the 12 | [Azure Resource groups](https://portal.azure.com/#blade/HubsExtension/BrowseResourceGroupBlade/resourceType/Microsoft.Resources%2Fsubscriptions%2FresourceGroups) 13 | page. 14 | 1. Click on your resource group's name. 15 | 1. Your resource group's page will be shown. 16 | Click on your resource that has **Type** 17 | **Service Bus Namespace**. 18 | 1. The page for your Service Bus Namespace will be shown. 19 | 20 | In the top middle navigation section, click 21 | **+ Queue**. See figure 1. 22 | 23 | ![Service Bus Namespace](Azure.07.add.queue.png) 24 | 25 | Figure 1. The page for your Service Bus Namespace. Click **+ Queue**. 26 | 27 | 1. The **Create queue** form modal will be shown. 28 | Complete the form and click **Create** at the 29 | bottom of the form. 30 | See figure 2: 31 | 32 | ![Create queue](Azure.08.add.queue.png) 33 | 34 | Figure 2. Complete the form and 35 | click **Create** 36 | 37 | 1. Record the **Queue name**. It is used for both 38 | the listener function and the worker application. 39 | 40 | Next: Create the 41 | [Azure Function](INSTALLATION_5_function.md). 42 | 43 | 44 | -------------------------------------------------------------------------------- /docs/INSTALLATION_2_svc_bus_namespace.md: -------------------------------------------------------------------------------- 1 | # Create a Service Bus Namespace 2 | 3 | #### Summary 4 | Use this article to create 5 | an Azure Service Bus **Namespace**. 6 | 7 | #### Steps 8 | 9 | 1. Open the 10 | [Azure Portal](https://portal.azure.com/#home) 11 | page. 12 | 1. In the left-hand navigation column, 13 | at the top, click **Create a resoure**. 14 | 1. Search for **service** and then 15 | select **Service Bus** 16 | See figure 1, below. 17 | 18 | ![Search for Service Bus](Azure.02.svc.search.png) 19 | 20 | Figure 1. Search for, and then select **Service Bus** 21 | 22 | 1. The Service Bus product page will be shown. 23 | Click **Create**. 24 | 25 | 1. The **Create namespace** form is shown, see 26 | figure 2. Fill in the form: 27 | 28 | 1. Pricing tier: You can use **Basic**. 29 | 1. Subscription: Use your initial **Free Plan** or 30 | one of your billing plans. 31 | 1. Resource group: 32 | 33 | **Recommended**: Click **Create new** to 34 | create a resource group that you will use to 35 | group together all of your Connect Listener 36 | Azure services. 37 | 38 | ![Create namespace](Azure.03.svc.create.png) 39 | 40 | Figure 2. Complete the **Create namespace** form and click **Create** 41 | 1. After the namespace has been created, you will 42 | see it listed in your Azure Resource group. 43 | 44 | Next: [Create a Shared access policy](INSTALLATION_3_svc_bus_connection_string.md) 45 | and connection string for the namespace. 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | # Logs 3 | logs 4 | *.log 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | *.pid.lock 14 | 15 | # Directory for instrumented libs generated by jscoverage/JSCover 16 | lib-cov 17 | 18 | # Coverage directory used by tools like istanbul 19 | coverage 20 | 21 | # nyc test coverage 22 | .nyc_output 23 | 24 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 25 | .grunt 26 | 27 | # Bower dependency directory (https://bower.io/) 28 | bower_components 29 | 30 | # node-waf configuration 31 | .lock-wscript 32 | 33 | # Compiled binary addons (https://nodejs.org/api/addons.html) 34 | build/Release 35 | 36 | # Dependency directories 37 | node_modules/ 38 | jspm_packages/ 39 | 40 | # TypeScript v1 declaration files 41 | typings/ 42 | 43 | # Optional npm cache directory 44 | .npm 45 | 46 | # Optional eslint cache 47 | .eslintcache 48 | 49 | # Optional REPL history 50 | .node_repl_history 51 | 52 | # Output of 'npm pack' 53 | *.tgz 54 | 55 | # Yarn Integrity file 56 | .yarn-integrity 57 | 58 | # dotenv environment variables file 59 | .env 60 | .env.test 61 | 62 | # parcel-bundler cache (https://parceljs.org/) 63 | .cache 64 | 65 | # next.js build output 66 | .next 67 | 68 | # nuxt.js build output 69 | .nuxt 70 | 71 | # vuepress build output 72 | .vuepress/dist 73 | 74 | # Serverless directories 75 | .serverless/ 76 | 77 | # FuseBox cache 78 | .fusebox/ 79 | 80 | # DynamoDB Local files 81 | .dynamodb/ 82 | 83 | # Azure Functions artifacts 84 | bin 85 | obj 86 | appsettings.json 87 | local.settings.json 88 | 89 | # TypeScript output 90 | dist 91 | out 92 | -------------------------------------------------------------------------------- /ConnectMessage/INFRA.md: -------------------------------------------------------------------------------- 1 | # Azure Infrastructure 2 | 3 | To deploy the infrastructure in your Azure subscription, follow the instructions below. 4 | 5 | ## Prerequisites 6 | 7 | Ensure you have [Serverless Framework](https://www.serverless.com/framework/docs/getting-started/) installed. You will also need the [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) for the final setup step. Be sure to first run `npm i` in current directory. 8 | 9 | ## Steps: 10 | 1. Navigate to the `ConnectMessage` folder and open the `serverless.yml` file. Set the value of `framework version` according to version number of Serverless Framework installed on your device. For version `3.x.x`, you should set the value to `3`, for `2.x.x` - to `2` etc. 11 | 1. Define the following environment variables: 12 | - `LOCATION` : The Azure region to deploy the infra to. For example, `East US`. For a full list of regions, see [region list](https://azure.microsoft.com/en-us/global-infrastructure/geographies/#geographies). 13 | - `SUB_ID`: Your Azure subscription ID. 14 | - `AUTH_NAME`: Your Connect authentication user name. 15 | - `BASIC_AUTH_PW`: Your Connection authentication password. 16 | - `HMAC`: HMAC to use for verification. 17 | 1. After the variables are set, you can simply do: `sls deploy` to deploy the infrastructure. Serverless will promopt you to log into your Azure subscription. 18 | 1. The Azure plugin for Serverless doesn't currently allow for referencing the Azure resources in `serverless.yml` file and there is a feature request pending on [this](https://github.com/serverless/serverless-azure-functions/issues/531). As a workaround, you'd need to manually run `postSetup.sh` after step 2 is completed. 19 | 20 | After step 3 is completed, you'll be presented with a URL that you can use in your Connect configuration. Example: `https://xxxxs-connect-node-listener-azure.azurewebsites.net` 21 | 22 | 23 | Refer to [Serverless docs](https://serverless.com/framework/docs/providers/azure/guide/intro/) for more information. 24 | -------------------------------------------------------------------------------- /docs/INSTALLATION_3_svc_bus_connection_string.md: -------------------------------------------------------------------------------- 1 | # Create a Shared Access Policy 2 | 3 | #### Summary 4 | Use this article to create a 5 | **Shared access policy** and **Connection string** 6 | for your Service Bus Namespace. 7 | 8 | The connection string will be used by both 9 | the listener function and the worker application. 10 | 11 | #### Steps 12 | 1. Open the 13 | [Azure Resource groups](https://portal.azure.com/#blade/HubsExtension/BrowseResourceGroupBlade/resourceType/Microsoft.Resources%2Fsubscriptions%2FresourceGroups) 14 | page. 15 | 1. Click on your resource group's name. 16 | 1. Your resource group's page will be shown. 17 | Click on your resource that has **Type** 18 | **Service Bus Namespace**. This will be the 19 | resource you created with the previous article. 20 | 1. The page for your Service Bus Namespace will be shown. 21 | 22 | In the middle-left navigation column, 23 | click **Shared access policies**. See figure 1. 24 | 25 | ![Service Bus Namespace](Azure.04.svc.namespace.page.png) 26 | 27 | Figure 1. The page for your Service Bus Namespace. Click **Shared access policies**. 28 | 29 | 1. The list of Shared access policies for your namespace 30 | will be shown. 31 | 32 | Click **+ Add** near the top of the page. 33 | 34 | 1. The **Add SAS Policy** form modal will be shown. 35 | Complete the form and 36 | check the **Send** and **Listen** options. 37 | 38 | Then click **Create** at the bottom of the form. 39 | See figure 2: 40 | 41 | ![Add policy](Azure.05.add.policy.png) 42 | 43 | Figure 2. Complete the form and 44 | click **Create** 45 | 46 | 1. After the policy has been created, the 47 | Shared access policies page will show the policy 48 | in the list. 49 | 50 | Click on the policy name to open its details. 51 | 52 | Record the **Primary Connection String** of the 53 | policy. You will use it for both the listener 54 | function and worker application settings. See figure 3. 55 | 56 | 57 | ![connection string](Azure.06.connection.string.png) 58 | 59 | Figure 3. Record the **Primary Connection String** of the 60 | policy 61 | 62 | Next: Create a 63 | [Service Bus Queue](INSTALLATION_4_svc_bus_queue.md). -------------------------------------------------------------------------------- /ConnectMessage/serverless.yml: -------------------------------------------------------------------------------- 1 | # Welcome to Serverless! 2 | # 3 | # This file is the main config file for your service. 4 | # It's very minimal at this point and uses default values. 5 | # You can always add more config options for more control. 6 | # We've included some commented out config examples here. 7 | # Just uncomment any of them to get that config option. 8 | # 9 | # For full config options, check the docs: 10 | # docs.serverless.com 11 | # 12 | # Happy Coding! 13 | 14 | service: connect-node-listener-azure 15 | 16 | # You can pin your service to only deploy with a specific Serverless version 17 | # Check out our docs for more details 18 | frameworkVersion: '3' 19 | 20 | provider: 21 | name: azure 22 | region: ${env:LOCATION} 23 | runtime: nodejs10 24 | prefix: "example" # prefix of generated resource name 25 | subscriptionId: ${env:SUB_ID} 26 | 27 | environment: # these will be created as application settings 28 | SVC_BUS_QUEUE_NAME: connect-queue 29 | BASIC_AUTH_NAME: ${env:AUTH_NAME} 30 | BASIC_AUTH_PW: ${env:AUTH_PW} 31 | HMAC_1: ${env:HMAC} 32 | 33 | # Start of your API Management configuration 34 | apim: 35 | # API specifications 36 | apis: 37 | - name: categories-api 38 | subscriptionRequired: false 39 | # Display name 40 | displayName: Categories API 41 | # Description of API 42 | description: The Categories REST API 43 | # HTTP protocols allowed 44 | protocols: 45 | - https 46 | # Base path of API calls 47 | path: categories 48 | # No authorization 49 | authorization: none 50 | backends: 51 | - name: categories-backend 52 | url: api/categories 53 | # CORS Settings for APIM 54 | cors: 55 | allowCredentials: false 56 | allowedOrigins: 57 | - '*' 58 | allowedMethods: 59 | - GET 60 | - POST 61 | - PUT 62 | - DELETE 63 | - PATCH 64 | allowedHeaders: 65 | - '*' 66 | exposeHeaders: 67 | - '*' 68 | 69 | plugins: # look for additional plugins in the community plugins repo: https://github.com/serverless/plugins 70 | - serverless-azure-functions 71 | 72 | # you can add packaging information here 73 | package: 74 | exclude: 75 | - local.settings.json 76 | - .vscode/** 77 | 78 | functions: 79 | connect: 80 | handler: index.connectNotificationMessage 81 | events: 82 | - http: true 83 | authLevel: anonymous # can also be `function` or `admin` 84 | -------------------------------------------------------------------------------- /ConnectMessage/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "namespaces_Connect_Events_name": { 6 | "defaultValue": "Example-Connect-Events-DS", 7 | "type": "String" 8 | } 9 | }, 10 | "variables": {}, 11 | "resources": [ 12 | { 13 | "type": "Microsoft.ServiceBus/namespaces", 14 | "apiVersion": "2018-01-01-preview", 15 | "name": "[parameters('namespaces_Connect_Events_name')]", 16 | "location": "East US", 17 | "sku": { 18 | "name": "Basic", 19 | "tier": "Basic" 20 | }, 21 | "properties": { 22 | "zoneRedundant": false 23 | } 24 | }, 25 | { 26 | "type": "Microsoft.ServiceBus/namespaces/AuthorizationRules", 27 | "apiVersion": "2017-04-01", 28 | "name": "[concat(parameters('namespaces_Connect_Events_name'), '/RootManageSharedAccessKey')]", 29 | "location": "East US", 30 | "dependsOn": [ 31 | "[resourceId('Microsoft.ServiceBus/namespaces', parameters('namespaces_Connect_Events_name'))]" 32 | ], 33 | "properties": { 34 | "rights": [ 35 | "Listen", 36 | "Manage", 37 | "Send" 38 | ] 39 | } 40 | }, 41 | { 42 | "type": "Microsoft.ServiceBus/namespaces/queues", 43 | "apiVersion": "2018-01-01-preview", 44 | "name": "[concat(parameters('namespaces_Connect_Events_name'), '/connect-queue')]", 45 | "location": "East US", 46 | "dependsOn": [ 47 | "[resourceId('Microsoft.ServiceBus/namespaces', parameters('namespaces_Connect_Events_name'))]" 48 | ], 49 | "properties": { 50 | "lockDuration": "PT30S", 51 | "maxSizeInMegabytes": 1024, 52 | "requiresDuplicateDetection": false, 53 | "requiresSession": false, 54 | "defaultMessageTimeToLive": "P14D", 55 | "deadLetteringOnMessageExpiration": false, 56 | "enableBatchedOperations": true, 57 | "duplicateDetectionHistoryTimeWindow": "PT10M", 58 | "maxDeliveryCount": 10, 59 | "status": "Active", 60 | "enablePartitioning": false, 61 | "enableExpress": false 62 | } 63 | }, 64 | { 65 | "type": "Microsoft.ServiceBus/namespaces/queues/authorizationRules", 66 | "apiVersion": "2018-01-01-preview", 67 | "name": "[concat(parameters('namespaces_Connect_Events_name'), '/connect-queue/sendandlisten')]", 68 | "location": "East US", 69 | "dependsOn": [ 70 | "[resourceId('Microsoft.ServiceBus/namespaces/queues', parameters('namespaces_Connect_Events_name'), 'connect-queue')]", 71 | "[resourceId('Microsoft.ServiceBus/namespaces', parameters('namespaces_Connect_Events_name'))]" 72 | ], 73 | "properties": { 74 | "rights": [ 75 | "Listen", 76 | "Send" 77 | ] 78 | } 79 | } 80 | ], 81 | "outputs": { 82 | "hostname": { 83 | "type": "string", 84 | "value": "[listKeys(resourceId('Microsoft.ServiceBus/namespaces/AuthorizationRules',parameters('namespaces_Connect_Events_name'),'RootManageSharedAccessKey'),'2015-08-01').primaryConnectionString]" 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /docs/INSTALLATION_5_function.md: -------------------------------------------------------------------------------- 1 | # Create the Azure Function 2 | 3 | #### Summary 4 | Use this article to create an Azure 5 | **Function**. 6 | 7 | #### Steps 8 | 9 | 1. Open the 10 | [Azure Portal](https://portal.azure.com/#home) 11 | page. 12 | 1. In the left-hand navigation column, 13 | at the top, click **Create a resoure**. 14 | 1. Search for **function** and then 15 | select **Function App**. 16 | See figure 1: 17 | 18 | ![Search for Function App](Azure.09.function.create.png) 19 | 20 | Figure 1. Search for, and then select **Function App** 21 | 22 | 1. The Function App product page will be shown. 23 | Click **Create**. 24 | 25 | 1. The Function App Create form will be shown. 26 | Complete the form: 27 | 1. **App name**: The name of your function. 28 | 29 | As shown on the form, the URL for your 30 | function will be the combination of your 31 | function's name and `.azurewebsites.net` 32 | 33 | **Record** the complete URL for your function, 34 | you will use the URL when you create your 35 | DocuSign Connect subscription. 36 | 1. **Resource Group**: Use the Resource Group that you 37 | created with the Service Bus Namespace. 38 | 1. **OS**: This repository's function works with 39 | both the Windows and Linux settings. 40 | 1. **Hosting Plan**: Recommendation: use the 41 | **Consumption Plan**. 42 | 1. **Runtime Stack**: Use **Node.js**. 43 | 1. **Publish**: Use **Code**. 44 | 45 | 46 | Click the **Create** button. See figure 2: 47 | 48 | ![Create Function App](Azure.10.function.create.png) 49 | 50 | Figure 2. Complete the form, then click **Create**. 51 | 52 | 1. Azure will take a minute or two to provision your 53 | new function. The Azure portal will notify you 54 | when the function is ready. 55 | 56 | 1. Return to your Resource Group page to locate the 57 | new function. Click on its name to open the 58 | function's page. 59 | 60 | 1. On the function's page (see figure 3), 61 | click **+ New function** for help with 62 | uploading the function's code 63 | to Azure. 64 | 65 | ![Create Function App](Azure.11.function.add.code.png) 66 | 67 | Figure 3. Click **New function** for help with 68 | uploading code to the function. 69 | 70 | 1. You can use any of the three upload methods 71 | suggested by Azure. 72 | 73 | Notes: 74 | 75 | 1. Install and upload the Node.js libraries as 76 | part of the package. Navigate to `ConnectMessage` folder and run `npm install` after downloading this repository to your 77 | development machine. 78 | 79 | > **Note**: If the `package-lock.json` contains references to packages that are inaccessible, delete this file and run command again. 80 | 81 | 1. The directory / file layout used in this repository 82 | has been tested with the VS Code Azure plugin. 83 | 84 | 1. Set the following **Application Settings** (environment 85 | variables) for your function: 86 | 87 | * BASIC_AUTH_NAME (optional) 88 | * BASIC_AUTH_PW (optional) 89 | * HMAC_1 (optional) the HMAC secret for HMAC 90 | signature 1. 91 | * SVC_BUS_CONNECTION_STRING 92 | * SVC_BUS_QUEUE_NAME 93 | 94 | > **Note:** Make sure Azure Authentication is disabled for your function app, otherwise it may not receive any messages. 95 | 96 | ## Next steps 97 | 98 | ### Configure your Connect subscription 99 | Refer to the DocuSign Connect documentation for information 100 | on configuring the Connect subscription. 101 | 102 | Note that the Connect subscription must not 103 | include the envelopes' documents nor 104 | Certificates of Completion in the notification 105 | messages. 106 | 107 | ### Create and configure your worker application 108 | You can use any software stack and any 109 | language. See the 110 | [Service Bus quickstarts](https://docs.microsoft.com/en-us/azure/service-bus-messaging/) 111 | for more information. 112 | 113 | For an example worker application, see 114 | the [connect-node-worker-azure](../../connect-node-worker-azure) 115 | repository. 116 | 117 | ### Testing 118 | 119 | #### Testing with Connect 120 | Create a Connect subscription that will notify the 121 | Azure Function. Check that the notification message is then 122 | received and processed by your worker application. 123 | 124 | #### Integration tests 125 | Use the `runTest.js` test application in the example worker 126 | repository to run end-to-end integration tests. 127 | 128 | The tests send test messages from the Azure Function to the 129 | worker application behind the firewall. 130 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Connect Node Listener for Azure 2 | 3 | This application is a microservice for use with 4 | [Azure Functions](https://azure.microsoft.com/en-us/services/functions/). 5 | 6 | It acts as a server (a **listener**) for DocuSign 7 | Connect notification messages. After checking the 8 | message's Basic Authentication and HMAC values, 9 | the software enqueues the message onto an 10 | [Azure Service Bus](https://azure.microsoft.com/en-us/services/service-bus) 11 | queue for processing by other software apps. 12 | 13 | The repo 14 | [connect-node-worker-azure](../../../connect-node-worker-azure) 15 | is an example worker application. 16 | It receives messages from the queue 17 | and then processes 18 | them. See the repo for more information. 19 | 20 | ## Architecture 21 | ![Connect listener architecture](docs/connect_listener_architecture.png) 22 | 23 | This figure shows the solution's architecture. 24 | This application is written in Node.js. 25 | The example worker app is also written in Node.js but 26 | could be written in a different language. 27 | 28 | ## Installation 29 | 30 | Short form instructions are below. 31 | [Long form](INSTALLATION.md) instructions are also available. 32 | 33 | ## Infrastructure 34 | 35 | To deploy the needed infra using the Serverless framework follow [these instructions](connectMessage/INFRA.md) below. 36 | 37 | ### Azure Service Bus Namespace 38 | 1. Provision an 39 | [Azure Service Bus](https://azure.microsoft.com/en-us/services/service-bus/) **Namespace**. 40 | 41 | 1. Add a Shared access policy for the namespace. The policy will need 42 | **Send** and **Listen** scopes. 43 | 44 | 1. Record the **Primary Connection String** for the 45 | Shared access policy. 46 | 47 | The connection string is used 48 | for the `SVC_BUS_CONNECTION_STRING` setting for 49 | the listener function and the worker application. 50 | 51 | 1. Create a **Queue** in the namespace. Record the 52 | **Queue name**. 53 | 54 | The queue name is used 55 | for the `SVC_BUS_QUEUE_NAME` setting for 56 | the listener function and the worker application. 57 | 58 | ### Azure Function 59 | 1. Provision an Azure Function. 60 | 61 | **Runtime Stack**: `JavaScript`. 62 | 63 | The OS can be either Windows or Linux. 64 | 65 | Note the URL for the cloud function. 66 | Your DocuSign Connect subscription will be 67 | configured with this URL. 68 | 69 | 1. Download/clone this repo to a local directory. 70 | 1. Install Node.js version 8.x or later. 71 | 1. Navigate to `ConnectMessage` folder and run `npm install`. 72 | 73 | > **Note**: If the `package-lock.json` contains references to packages that are inaccessible, delete this file and run command again. 74 | 75 | 1. Use the VS Code or other tools suggested by 76 | Azure to upload the directory to your Azure function. 77 | 78 | 1. Set the environment (settings) variables for the function: 79 | 1. **BASIC_AUTH_NAME**: optional. The Basic Authentication 80 | name set in the Connect subscription. 81 | 1. **BASIC_AUTH_PW**: optional. The Basic Authentication 82 | password set in the Connect subscription. 83 | 1. **HMAC_1**: optional. The HMAC secret used by the 84 | Connect subscription. 85 | 1. **SVC_BUS_CONNECTION_STRING**: required. 86 | The connection string for a 87 | Shared access policy to the queue. 88 | 1. **SVC_BUS_QUEUE_NAME**: required. 89 | 90 | > **Note:** Make sure Azure Authentication is disabled for your function app, otherwise it may not receive any messages. 91 | 92 | ## Testing 93 | Configure a DocuSign Connect subscription to send notifications to 94 | the Azure Function. Create / complete a DocuSign envelope. 95 | Check the Connect logs for feedback. 96 | 97 | ### Test messages feature 98 | This application and the worker application enable test 99 | messages to be sent via the queuing system. The test 100 | messages do not include XML Connect notification 101 | messages. 102 | 103 | To send a test message, use the function's URL with 104 | query parameter `test` set to 105 | a test value. A GET or POST request can be used. 106 | 107 | ### Integration testing 108 | The worker application includes the test tool `runTest.js` 109 | 110 | See the worker application for information on running the 111 | integration tests. 112 | 113 | ## Usage 114 | **Do not include documents in the notification messages** 115 | The Message Bus queuing system will not support messages that 116 | include documents. Check that your Connect subscription 117 | is configured to not include envelope documents nor the 118 | envelope's Certificate of Completion. 119 | 120 | ## License and Pull Requests 121 | 122 | ### License 123 | This repository uses the MIT License. See the LICENSE file for more information. 124 | 125 | ### Pull Requests 126 | Pull requests are welcomed. Pull requests will only be considered if their content 127 | uses the MIT License. 128 | 129 | -------------------------------------------------------------------------------- /ConnectMessage/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file index.js -- Azure Function Node.js function. 3 | * This function receives a DocuSign Connect notification message 4 | * and enqueues it to an Azure Service Bus queue. 5 | **/ 6 | 7 | 'use strict'; 8 | 9 | const crypto = require('crypto') 10 | , { ServiceBusClient } = require("@azure/service-bus") // See https://github.com/Azure/azure-sdk-for-js/tree/master/sdk/servicebus/service-bus/ 11 | ; 12 | 13 | const debug = true; 14 | 15 | const sleep = (milliseconds) => { 16 | return new Promise(resolve => setTimeout(resolve, milliseconds)) 17 | } 18 | 19 | module.exports.connectNotificationMessage = async function (context, req) { 20 | context.log('JavaScript HTTP trigger function processed a request.'); 21 | 22 | const debugLog = msg => {if (debug) {context.log(msg)}}; 23 | const invocationId = context.executionContext && context.executionContext.invocationId; 24 | 25 | function checkBasicAuth() { 26 | const name = process.env['BASIC_AUTH_NAME'] 27 | , pw = process.env['BASIC_AUTH_PW'] 28 | , authRaw0 = (req.headers && req.headers.authorization) || '' 29 | , authRaw = authRaw0.split(' ')[1] || '' 30 | , authString = Buffer.from(authRaw, 'base64').toString() 31 | , authArray = authString.split(':') 32 | , authenticated = name == authArray[0] && pw == authArray[1] 33 | ; 34 | return authenticated 35 | } 36 | 37 | debugLog(`Started!. Invocation ID: ${invocationId}`); 38 | // Check Basic Authentication 39 | if (checkBasicAuth()) { 40 | debugLog("Authenticated!") 41 | } else { 42 | const headers = {'WWW-Authenticate': 'Basic realm="Connect Listener", charset="UTF-8"'}; 43 | context.res = {status: 401, body: "Unauthorized!", headers: headers}; 44 | return // EARLY return 45 | } 46 | 47 | // Check HMAC and enqueue. Allow for test messages 48 | const test = req.query.test ? req.query.test : false 49 | , rawBody = req.body 50 | , hmac1 = process.env['HMAC_1'] 51 | , hmacConfigured = hmac1; 52 | 53 | let body; 54 | debugLog(`content-type is ${req.headers['content-type']}`) 55 | 56 | if (req.headers['content-type'].toString().includes('text/xml')) { 57 | body = rawBody 58 | } else if (req.headers['content-type'].toString().includes('application/json')) { 59 | body = JSON.stringify(rawBody) 60 | } 61 | 62 | let hmacPassed; 63 | if (!test && hmacConfigured) { 64 | // Not a test: 65 | // Step 1. Check the HMAC 66 | // get the headers 67 | const authDigest = req.headers['x-authorization-digest'] 68 | , hmacSig1 = req.headers['x-docusign-signature-1'] 69 | ; 70 | 71 | hmacPassed = checkHmac(hmac1, body, authDigest, hmacSig1) 72 | if (!hmacPassed) { 73 | context.log.error(`HMAC did not pass!! HMAC Sig 1: ${hmacSig1}`); 74 | context.res = {status: 401, body: "Bad HMAC: unauthorized!"}; 75 | return // EARLY return 76 | } 77 | if (hmacPassed){ 78 | debugLog('HMAC passed!') 79 | } 80 | } else { 81 | // hmac is not configured or a test message. HMAC is not checked for tests. 82 | hmacPassed = true 83 | } 84 | 85 | if (test || hmacPassed) { 86 | // Step 2. Store in queue 87 | let error = await enqueue (body, test, req.headers['content-type'].toString()); 88 | if (error) { 89 | // Wait 25 sec and then try again 90 | await sleep(25000); 91 | error = await enqueue (body, test, req.headers['content-type'].toString()); 92 | } 93 | if (error) { 94 | context.res = {status: 400, body: `Problem! ${error}`} 95 | context.log.error(`Enqueue error: ${error}`); 96 | } else { 97 | context.res = {status: 200, body: 'enqueued'}; 98 | 99 | if (test) { 100 | debugLog (`Enqueued a test notification: ${test}`) 101 | } else { 102 | debugLog (`Enqueued a notification`) 103 | } 104 | } 105 | } 106 | }; 107 | 108 | /** 109 | * 110 | * @param {string} key1: The HMAC key for signature 1 111 | * @param {string} rawBody: the request body of the notification POST 112 | * @param {string} authDigest: The HMAC signature algorithmn used 113 | * @param {string} hmacSig1: The HMAC Signature number 1 114 | * @returns {boolean} sigGood: Is the signatures good? 115 | */ 116 | function checkHmac (key1, rawBody, authDigest, hmacSig1) { 117 | const authDigestExpected = 'HMACSHA256' 118 | , correctDigest = authDigestExpected === authDigest; 119 | if (!correctDigest) {return false} 120 | 121 | // The key is relative to the account. So if the 122 | // same listener is used for Connect notifications from 123 | // multiple accounts, use the accountIdHeader to look up 124 | // the secrets for the specific account. 125 | // 126 | // For this example, the key is supplied by the caller 127 | const sig1good = hmacSig1 === computeHmac(key1, rawBody); 128 | return sig1good 129 | } 130 | 131 | /** 132 | * Compute a SHA256 HMAC on the with the 133 | * The Base64 representation of the HMAC is then returned 134 | * @param {string} key 135 | * @param {*} content 136 | * @returns {string} Base64 encoded SHA256 HMAC 137 | */ 138 | function computeHmac(key, content) { 139 | const hmac = crypto.createHmac('sha256', key); 140 | hmac.write(content); 141 | hmac.end(); 142 | return hmac.read().toString('base64'); 143 | } 144 | 145 | 146 | /** 147 | * The enqueue function adds the xml to the queue. 148 | * If test is true then a test notification is sent. 149 | * 150 | * @param {string} rawBody 151 | * @param {boolean||integer} test 152 | * @param {string} contentType 153 | */ 154 | async function enqueue(rawBody, test, contentType) { 155 | if (!test) {test = ''} // always send a string 156 | let error = false; 157 | if (test) {rawBody = ''} 158 | 159 | let ns; 160 | try { 161 | const connString = process.env['SVC_BUS_CONNECTION_STRING'] 162 | , queueName = process.env['SVC_BUS_QUEUE_NAME']; 163 | let serviceBusClient = new ServiceBusClient(connString); 164 | const sender = serviceBusClient.createSender(queueName) 165 | , message = {body: {test: test, contentType: contentType ,payload: rawBody}} 166 | ; 167 | await sender.sendMessages(message); 168 | await sender.close(); 169 | } 170 | catch (e) { 171 | error = e 172 | } 173 | finally { 174 | if (ns) {await ns.close()} 175 | } 176 | return error 177 | } --------------------------------------------------------------------------------