├── .gitignore ├── secrets.png ├── dockerfile ├── package.json ├── public ├── stylesheets │ └── style.css ├── index.html └── index.js ├── GeoPol.xml ├── Web.config ├── SECURITY.md ├── README.md ├── server.js └── azuredeploy.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .idea 3 | .env 4 | .deployment 5 | .vscode -------------------------------------------------------------------------------- /secrets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/HealthBotContainerSample/HEAD/secrets.png -------------------------------------------------------------------------------- /dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:latest 2 | 3 | # Create app directory 4 | WORKDIR /usr/src/app 5 | 6 | # Install app dependencies 7 | # A wildcard is used to ensure both package.json AND package-lock.json are copied 8 | # where available (npm@5+) 9 | COPY package*.json ./ 10 | 11 | RUN npm install 12 | # If you are building your code for production 13 | # RUN npm install --only=production 14 | 15 | # Bundle app source 16 | COPY . . 17 | 18 | # Delete the web.config file, only needed for IIS 19 | RUN rm ./Web.config 20 | 21 | EXPOSE 8080 22 | CMD [ "npm", "start" ] 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "healthbot-container", 3 | "version": "1.0.0", 4 | "description": "A simple web page to handoff users to the Microsoft Health bot", 5 | "main": "server.js", 6 | "license": "MIT", 7 | "repository": "https://github.com/Microsoft/HealthBotContainerSample.git", 8 | "dependencies": { 9 | "dotenv": "^8.2.0", 10 | "cookie-parser": "^1.4.5", 11 | "express": "^4.17.1", 12 | "jsonwebtoken": "^8.5.1", 13 | "node-fetch": "^2.6.7" 14 | }, 15 | "scripts": { 16 | "prestart": "npm install" 17 | }, 18 | "devDependencies": {} 19 | } 20 | -------------------------------------------------------------------------------- /public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | height: 100%; 3 | } 4 | body { 5 | margin: 0; 6 | } 7 | 8 | #webchat { 9 | height: 100%; 10 | width: 100%; 11 | } 12 | 13 | #webchat[watermark="true"] [role="complementary"] ul[role="list"]::after { 14 | content: "Powered By ..."; 15 | background: linear-gradient(rgba(248, 248, 248, 0), rgba(248, 248, 248, .63), #F8F8F8 40%); 16 | bottom: 0; 17 | right: 0; 18 | color: #707070; 19 | display: block; 20 | font-family: 'Segoe Semibold', Calibri, 'Helvetica Neue', Arial, sans-serif; 21 | font-size: 12px; 22 | padding: 15px 10px 10px; 23 | position: absolute; 24 | position: sticky; 25 | text-align: right; 26 | } 27 | 28 | #webchat[watermark="true"] .webchat__scrollToEndButton { 29 | bottom: 32px; 30 | left: 50%; 31 | right: unset; 32 | transform: translate(-50%, 0); 33 | } 34 | -------------------------------------------------------------------------------- /GeoPol.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | ]> 7 | 8 | 9 | 10 | &GitReposFolder;\AInR_NExT\&GitRepoName; 11 | &GitRepoName; 12 | 13 | 14 | . 15 | 16 | 17 | .gitignore 18 | GeoPol.xml 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /Web.config: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Health Bot Container 2 | 3 | A simple web page that allows users to communicate with the [Azure Health Bot](https://azure.microsoft.com/en-us/services/bot-services/health-bot/) through a WebChat. 4 | 5 | **Note:** In order to use this Web Chat with the Health Bot service, you will need to obtain your Web Chat secret by going to `Integration/Secrets` on the navigation panel. 6 | 7 | ![Secrets](/secrets.png) 8 | 9 | 1.Deploy the website: 10 | 11 | [![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fmicrosoft%2FHealthBotContainerSample%2Fmaster%2Fazuredeploy.json) 12 | 13 | 2.Set the following environment variables: 14 | 15 | `AUTH_JWT_SECRET` 16 | 17 | `WEBCHAT_SECRET` 18 | 19 | 3.Configure scenario invocation (optional): 20 | 21 | The Health Bot service uses [language models](https://docs.microsoft.com/HealthBot/language_model_howto) to interpret end user utterances and trigger the relevant scenario logic in response. 22 | 23 | Alternatively, you can programmaticaly invoke a scenario before the end user provides any input. 24 | 25 | To implement this behavior, uncomment the following code from the `function initBotConversation()` in the `/public/index.js` file: 26 | ```javascript 27 | triggeredScenario: { 28 |     trigger: "{scenario_id}", 29 |     args: { 30 |         myVar1: "{custom_arg_1}", 31 |         myVar2: "{custom_arg_2}" 32 |     } 33 | } 34 | ``` 35 | Replace {scenario_id} with the scenario ID of the scenario you would like to invoke. 36 | You can also pass different values through the "args" object.  37 | 38 | You can read more about programmatic client side scenario invocation [here](https://docs.microsoft.com/HealthBot/integrations/programmatic_invocation) 39 | 40 | 41 | 4.Set the Bot service direct line channel endpoint (optional) 42 | 43 | In some cases it is required to set the endpoint URI so that it points to a specific geography. The geographies supported by the bot service each have a unique direct line endpoint URI: 44 | 45 | - `directline.botframework.com` routes your client to the nearest datacenter. This is the best option if you do not know where your client is located. 46 | - `asia.directline.botframework.com` routes only to Direct Line servers in Eastern Asia. 47 | - `europe.directline.botframework.com` routes only to Direct Line servers in Europe. 48 | - `northamerica.directline.botframework.com` routes only to Direct Line servers in North America. 49 | 50 | Pass your preferred geographic endpoint URI by setting the environment variable: `DIRECTLINE_ENDPOINT_URI` in your deployment. If no variable is found it will default to `directline.botframework.com` 51 | 52 | **Note:** If you are deploying the code sample using the "Deploy to Azure" option, you should add the above secrets to the application settings for your App Service. 53 | 54 | ## Agent webchat 55 | If the agent webchat sample is also required, [switch to the live agent handoff branch](https://github.com/Microsoft/HealthBotContainerSample/tree/live_agent_handoff) 56 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const crypto = require('crypto'); 3 | const express = require("express"); 4 | const path = require("path"); 5 | const jwt = require("jsonwebtoken"); 6 | const fetch = require('node-fetch'); 7 | const cookieParser = require('cookie-parser'); 8 | const WEBCHAT_SECRET = process.env.WEBCHAT_SECRET; 9 | const DIRECTLINE_ENDPOINT_URI = process.env.DIRECTLINE_ENDPOINT_URI; 10 | const AUTH_JWT_SECRET = process.env.AUTH_JWT_SECRET; 11 | const directLineTokenEp = `https://${DIRECTLINE_ENDPOINT_URI || "directline.botframework.com"}/v3/directline/tokens/generate`; 12 | 13 | // Initialize the web app instance, 14 | const app = express(); 15 | app.use(cookieParser()); 16 | 17 | let options = {}; 18 | // uncomment the line below if you wish to allow only specific domains to embed this page as a frame 19 | //options = {setHeaders: (res, path, stat) => {res.set('Content-Security-Policy', 'frame-ancestors example.com')}}; 20 | // Indicate which directory static resources 21 | // (e.g. stylesheets) should be served from. 22 | app.use(express.static(path.join(__dirname, "public"), options)); 23 | // begin listening for requests. 24 | const port = process.env.PORT || 8080; 25 | const region = process.env.REGION || "Unknown"; 26 | 27 | app.listen(port, function() { 28 | console.log("Express server listening on port " + port); 29 | }); 30 | 31 | function isUserAuthenticated(){ 32 | // add here the logic to verify the user is authenticated 33 | return true; 34 | } 35 | 36 | const appConfig = { 37 | isHealthy : false, 38 | options : { 39 | method: 'POST', 40 | headers: { 41 | 'Authorization': 'Bearer ' + WEBCHAT_SECRET 42 | } 43 | } 44 | }; 45 | 46 | function healthResponse(res, statusCode, message) { 47 | res.status(statusCode).send({ 48 | health: message, 49 | region: region 50 | }); 51 | } 52 | function healthy(res) { 53 | healthResponse(res, 200, "Ok"); 54 | } 55 | 56 | function unhealthy(res) { 57 | healthResponse(res, 503, "Unhealthy"); 58 | } 59 | 60 | app.get('/health', async function(req, res){ 61 | if (!appConfig.isHealthy) { 62 | try { 63 | const fetchResponse = await fetch(directLineTokenEp, appConfig.options); 64 | const parsedBody = await fetchResponse.json(); 65 | appConfig.isHealthy = true; 66 | healthy(res); 67 | } 68 | catch (err) { 69 | unhealthy(res); 70 | } 71 | } 72 | else { 73 | healthy(res); 74 | } 75 | }); 76 | 77 | app.post('/chatBot', async function(req, res) { 78 | if (!isUserAuthenticated()) { 79 | res.status(403).send(); 80 | return; 81 | } 82 | try { 83 | const fetchResponse = await fetch(directLineTokenEp, appConfig.options); 84 | const parsedBody = await fetchResponse.json(); 85 | var userid = req.query.userId || req.cookies.userid; 86 | if (!userid) { 87 | userid = crypto.randomBytes(4).toString('hex'); 88 | res.cookie("userid", userid, { sameSite: "none", secure: true, httpOnly: true, expires: new Date(new Date().setFullYear(new Date().getFullYear() + 1)) }); 89 | } 90 | 91 | var response = {}; 92 | response['userId'] = userid; 93 | response['userName'] = req.query.userName; 94 | response['locale'] = req.query.locale; 95 | response['connectorToken'] = parsedBody.token; 96 | 97 | /* 98 | //Add any additional attributes 99 | response['optionalAttributes'] = {age: 33}; 100 | */ 101 | 102 | if (req.query.lat && req.query.long) { 103 | response['location'] = {lat: req.query.lat, long: req.query.long}; 104 | } 105 | response['directLineURI'] = DIRECTLINE_ENDPOINT_URI; 106 | const jwtToken = jwt.sign(response, AUTH_JWT_SECRET); 107 | res.send(jwtToken); 108 | } 109 | catch (err) { 110 | appConfig.isHealthy = false; 111 | res.status(err.statusCode).send(); 112 | console.log("failed"); 113 | } 114 | }); 115 | -------------------------------------------------------------------------------- /public/index.js: -------------------------------------------------------------------------------- 1 | const defaultLocale = 'en-US'; 2 | 3 | function requestChatBot(loc) { 4 | const params = new URLSearchParams(location.search); 5 | const oReq = new XMLHttpRequest(); 6 | oReq.addEventListener("load", initBotConversation); 7 | var path = "/chatBot?locale=" + extractLocale(params.get('locale')); 8 | 9 | if (loc) { 10 | path += "&lat=" + loc.lat + "&long=" + loc.long; 11 | } 12 | if (params.has('userId')) { 13 | path += "&userId=" + params.get('userId'); 14 | } 15 | if (params.has('userName')) { 16 | path += "&userName=" + params.get('userName'); 17 | } 18 | oReq.open("POST", path); 19 | oReq.send(); 20 | } 21 | 22 | function extractLocale(localeParam) { 23 | if (!localeParam) { 24 | return defaultLocale; 25 | } 26 | else if (localeParam === 'autodetect') { 27 | return navigator.language; 28 | } 29 | else { 30 | return localeParam; 31 | } 32 | } 33 | 34 | function chatRequested() { 35 | const params = new URLSearchParams(location.search); 36 | if (params.has('shareLocation')) { 37 | getUserLocation(requestChatBot); 38 | } 39 | else { 40 | requestChatBot(); 41 | } 42 | } 43 | 44 | function getUserLocation(callback) { 45 | navigator.geolocation.getCurrentPosition( 46 | function(position) { 47 | var latitude = position.coords.latitude; 48 | var longitude = position.coords.longitude; 49 | var location = { 50 | lat: latitude, 51 | long: longitude 52 | } 53 | callback(location); 54 | }, 55 | function(error) { 56 | // user declined to share location 57 | console.log("location error:" + error.message); 58 | callback(); 59 | }); 60 | } 61 | 62 | function initBotConversation() { 63 | if (this.status >= 400) { 64 | alert(this.statusText); 65 | return; 66 | } 67 | // extract the data from the JWT 68 | const jsonWebToken = this.response; 69 | const tokenPayload = JSON.parse(atob(jsonWebToken.split('.')[1])); 70 | const user = { 71 | id: tokenPayload.userId, 72 | name: tokenPayload.userName, 73 | locale: tokenPayload.locale 74 | }; 75 | let domain = undefined; 76 | if (tokenPayload.directLineURI) { 77 | domain = "https://" + tokenPayload.directLineURI + "/v3/directline"; 78 | } 79 | let location = undefined; 80 | if (tokenPayload.location) { 81 | location = tokenPayload.location; 82 | } else { 83 | // set default location if desired 84 | /*location = { 85 | lat: 44.86448450671394, 86 | long: -93.32597021107624 87 | }*/ 88 | } 89 | var botConnection = window.WebChat.createDirectLine({ 90 | token: tokenPayload.connectorToken, 91 | domain: domain 92 | }); 93 | const styleOptions = { 94 | botAvatarImage: 'https://docs.microsoft.com/en-us/azure/bot-service/v4sdk/media/logo_bot.svg?view=azure-bot-service-4.0', 95 | // botAvatarInitials: '', 96 | // userAvatarImage: '', 97 | hideSendBox: false, /* set to true to hide the send box from the view */ 98 | botAvatarInitials: 'Bot', 99 | userAvatarInitials: 'You', 100 | backgroundColor: '#F8F8F8' 101 | }; 102 | 103 | const store = window.WebChat.createStore({}, function(store) { return function(next) { return function(action) { 104 | if (action.type === 'DIRECT_LINE/CONNECT_FULFILLED') { 105 | store.dispatch({ 106 | type: 'DIRECT_LINE/POST_ACTIVITY', 107 | meta: {method: 'keyboard'}, 108 | payload: { 109 | activity: { 110 | type: "invoke", 111 | name: "InitConversation", 112 | locale: user.locale, 113 | value: { 114 | // must use for authenticated conversation. 115 | jsonWebToken: jsonWebToken, 116 | 117 | // Use the following activity to proactively invoke a bot scenario 118 | /* 119 | triggeredScenario: { 120 | trigger: "{scenario_id}", 121 | args: { 122 | location: location, 123 | myVar1: "{custom_arg_1}", 124 | myVar2: "{custom_arg_2}" 125 | } 126 | } 127 | */ 128 | } 129 | } 130 | } 131 | }); 132 | 133 | } 134 | else if (action.type === 'DIRECT_LINE/INCOMING_ACTIVITY') { 135 | if (action.payload && action.payload.activity && action.payload.activity.type === "event" && action.payload.activity.name === "ShareLocationEvent") { 136 | // share 137 | getUserLocation(function (location) { 138 | store.dispatch({ 139 | type: 'WEB_CHAT/SEND_POST_BACK', 140 | payload: { value: JSON.stringify(location) } 141 | }); 142 | }); 143 | } 144 | } 145 | return next(action); 146 | }}}); 147 | const webchatOptions = { 148 | directLine: botConnection, 149 | styleOptions: styleOptions, 150 | store: store, 151 | userID: user.id, 152 | username: user.name, 153 | locale: user.locale 154 | }; 155 | startChat(user, webchatOptions); 156 | } 157 | 158 | function startChat(user, webchatOptions) { 159 | const botContainer = document.getElementById('webchat'); 160 | window.WebChat.renderWebChat(webchatOptions, botContainer); 161 | } 162 | -------------------------------------------------------------------------------- /azuredeploy.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "siteName": { 6 | "type": "string", 7 | "defaultValue": "[concat('healthcare-bot-', uniqueString(utcNow('F')))]", 8 | "metadata":{ 9 | "description": "Web site name. Has to be unique." 10 | 11 | } 12 | }, 13 | "operatingSystem": { 14 | "type": "string", 15 | "defaultValue": "windows", 16 | "allowedValues": [ 17 | "windows", 18 | "linux" 19 | ], 20 | "metadata": { 21 | "description": "Host type: Windows or Linux. (Windows is recommended)" 22 | } 23 | }, 24 | "skuName": { 25 | "type": "string", 26 | "defaultValue": "P1V2", 27 | "allowedValues": [ 28 | "B1", 29 | "S1", 30 | "P1V2" 31 | ], 32 | "metadata": { 33 | "description": "Describes plan's pricing tier and instance size. Check details at https://azure.microsoft.com/en-us/pricing/details/app-service/" 34 | } 35 | }, 36 | "numberOfInstances": { 37 | "type": "int", 38 | "defaultValue": 2, 39 | "metadata":{ 40 | "description": "Number of instances the app service is scaled to. Check the maximum instances your plan can host at https://azure.microsoft.com/en-us/pricing/details/app-service" 41 | } 42 | }, 43 | "siteLocation": { 44 | "type": "string", 45 | "defaultValue": "[resourceGroup().location]", 46 | "metadata":{ 47 | "description": "Location for all resources." 48 | } 49 | }, 50 | "authJwtSecret": { 51 | "type": "securestring", 52 | "metadata":{ 53 | "description": "Healthbot authentication JWT secret." 54 | } 55 | }, 56 | "webchatSecret": { 57 | "type": "securestring", 58 | "metadata":{ 59 | "description": "Healthbot webchat secret." 60 | } 61 | }, 62 | "repoUrl": { 63 | "type": "string", 64 | "defaultValue": "https://github.com/microsoft/HealthBotContainerSample.git", 65 | "metadata": { 66 | "description": "The URL for the GitHub repository that contains the project to deploy." 67 | } 68 | }, 69 | "branch": { 70 | "type": "string", 71 | "defaultValue": "master", 72 | "metadata": { 73 | "description": "The branch of the GitHub repository to use." 74 | } 75 | } 76 | }, 77 | "variables":{ 78 | "alwaysOn": true, 79 | "skuCode": "[parameters('skuName')]", 80 | "numberOfWorkers": "[parameters('numberOfInstances')]", 81 | "linuxFxVersion": "NODE|lts", 82 | "hostingPlanNameLinux": "[concat('plan-linux-', parameters('siteName'))]", 83 | "hostingPlanNameWin": "[concat('plan-win-', parameters('siteName'))]", 84 | "kind": "[if(equals(parameters('operatingSystem'), 'windows'), 'app', 'linux')]", 85 | "linuxSiteName": "[if(equals(parameters('operatingSystem'), 'linux'), parameters('siteName'), 'app-na')]", 86 | "windowsSiteName": "[if(equals(parameters('operatingSystem'), 'windows'), parameters('siteName'), 'app-na')]", 87 | "WinSkuCode": "[parameters('skuName')]", 88 | "WinSku": "Standard", 89 | "workerSize": "0", 90 | "workerSizeId": "0", 91 | "hostingEnvironment": "", 92 | "nodeVersion": "~16", 93 | "currentStack": "node" 94 | }, 95 | "resources": [ 96 | { 97 | "apiVersion": "2018-02-01", 98 | "name": "[variables('linuxSiteName')]", 99 | "condition": "[equals(parameters('operatingSystem'),'linux')]", 100 | "type": "Microsoft.Web/sites", 101 | "location": "[parameters('siteLocation')]", 102 | "dependsOn": [ 103 | "[resourceId('Microsoft.Web/serverfarms/', variables('hostingPlanNameLinux'))]" 104 | ], 105 | "properties": { 106 | "name": "[variables('linuxSiteName')]", 107 | "siteConfig": { 108 | "linuxFxVersion": "[variables('linuxFxVersion')]", 109 | "alwaysOn": "[variables('alwaysOn')]", 110 | "appSettings": [ 111 | { 112 | "name": "AUTH_JWT_SECRET", 113 | "value": "[parameters('authJwtSecret')]" 114 | }, 115 | { 116 | "name": "WEBCHAT_SECRET", 117 | "value": "[parameters('webchatSecret')]" 118 | } 119 | ] 120 | }, 121 | "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanNameLinux'))]", 122 | "clientAffinityEnabled": false 123 | }, 124 | "resources": [ 125 | { 126 | "type": "sourcecontrols", 127 | "condition": "[equals(parameters('operatingSystem'),'linux')]", 128 | "apiVersion": "2018-02-01", 129 | "name": "web", 130 | "location": "[parameters('siteLocation')]", 131 | "dependsOn": [ 132 | "[resourceId('Microsoft.Web/sites', variables('linuxSiteName'))]" 133 | ], 134 | "properties": { 135 | "repoUrl": "[parameters('repoURL')]", 136 | "branch": "[parameters('branch')]", 137 | "isManualIntegration": true 138 | } 139 | } 140 | ] 141 | }, 142 | { 143 | "apiVersion": "2018-02-01", 144 | "condition": "[equals(parameters('operatingSystem'),'linux')]", 145 | "name": "[variables('hostingPlanNameLinux')]", 146 | "type": "Microsoft.Web/serverfarms", 147 | "location": "[parameters('siteLocation')]", 148 | "kind": "linux", 149 | "sku": { 150 | "Name": "[variables('skuCode')]" 151 | }, 152 | "properties": { 153 | "name": "[variables('hostingPlanNameLinux')]", 154 | "numberOfWorkers": "[variables('numberOfWorkers')]", 155 | "reserved": true 156 | } 157 | }, 158 | { 159 | "apiVersion": "2018-11-01", 160 | "condition": "[equals(parameters('operatingSystem'),'windows')]", 161 | "name": "[variables('windowsSiteName')]", 162 | "type": "Microsoft.Web/sites", 163 | "location": "[parameters('siteLocation')]", 164 | "tags": null, 165 | "dependsOn": [ 166 | "[concat('Microsoft.Web/serverfarms/', variables('hostingPlanNameWin'))]" 167 | ], 168 | "properties": { 169 | "name": "[variables('windowsSiteName')]", 170 | "siteConfig": { 171 | "appSettings": [ 172 | { 173 | "name": "AUTH_JWT_SECRET", 174 | "value": "[parameters('authJwtSecret')]" 175 | }, 176 | { 177 | "name": "WEBCHAT_SECRET", 178 | "value": "[parameters('webchatSecret')]" 179 | }, 180 | { 181 | "name": "WEBSITE_NODE_DEFAULT_VERSION", 182 | "value": "[variables('nodeVersion')]" 183 | } 184 | ], 185 | "metadata": [ 186 | { 187 | "name": "CURRENT_STACK", 188 | "value": "[variables('currentStack')]" 189 | } 190 | ], 191 | "nodeVersion": "[variables('nodeVersion')]", 192 | "alwaysOn": "[variables('alwaysOn')]" 193 | }, 194 | "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanNameWin'))]", 195 | "hostingEnvironment": "[variables('hostingEnvironment')]", 196 | "clientAffinityEnabled": true 197 | }, 198 | "resources": [ 199 | { 200 | "type": "sourcecontrols", 201 | "condition": "[equals(parameters('operatingSystem'),'windows')]", 202 | "apiVersion": "2018-11-01", 203 | "name": "web", 204 | "location": "[parameters('siteLocation')]", 205 | "dependsOn": [ 206 | "[resourceId('Microsoft.Web/sites', variables('windowsSiteName'))]" 207 | ], 208 | "properties": { 209 | "repoUrl": "[parameters('repoURL')]", 210 | "branch": "[parameters('branch')]", 211 | "isManualIntegration": true 212 | } 213 | } 214 | ] 215 | }, 216 | { 217 | "apiVersion": "2018-11-01", 218 | "name": "[variables('hostingPlanNameWin')]", 219 | "condition": "[equals(parameters('operatingSystem'),'windows')]", 220 | "type": "Microsoft.Web/serverfarms", 221 | "location": "[parameters('siteLocation')]", 222 | "kind": "", 223 | "tags": null, 224 | "dependsOn": [], 225 | "properties": { 226 | "name": "[variables('hostingPlanNameWin')]", 227 | "workerSize": "[variables('workerSize')]", 228 | "workerSizeId": "[variables('workerSizeId')]", 229 | "numberOfWorkers": "[variables('numberOfWorkers')]", 230 | "hostingEnvironment": "[variables('hostingEnvironment')]" 231 | }, 232 | "sku": { 233 | "Tier": "[variables('WinSku')]", 234 | "Name": "[variables('WinSkuCode')]" 235 | } 236 | } 237 | ] 238 | } 239 | --------------------------------------------------------------------------------