├── 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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | }
--------------------------------------------------------------------------------