diff --git a/extensions/teams/config.ts b/extensions/teams/config.ts index 6d99139a1..8330fc537 100644 --- a/extensions/teams/config.ts +++ b/extensions/teams/config.ts @@ -2,6 +2,7 @@ const config = { botId: process.env.BOT_ID, botPassword: process.env.BOT_PASSWORD, azureFunctionUrl: process.env.AZURE_FUNCTION_URL, + tenantId: process.env.TEAMS_APP_TENANT_ID, }; export default config; diff --git a/extensions/teams/index.ts b/extensions/teams/index.ts index 555a979b2..3eae007b8 100644 --- a/extensions/teams/index.ts +++ b/extensions/teams/index.ts @@ -19,7 +19,8 @@ import config from "./config"; const credentialsFactory = new ConfigurationServiceClientCredentialFactory({ MicrosoftAppId: config.botId, MicrosoftAppPassword: config.botPassword, - MicrosoftAppType: "MultiTenant", + MicrosoftAppType: "SingleTenant", + MicrosoftAppTenantId: config.tenantId }); const botFrameworkAuthentication = new ConfigurationBotFrameworkAuthentication( diff --git a/extensions/teams/infra/azure.bicep b/extensions/teams/infra/azure.bicep index 8c706f330..b5b57237f 100644 --- a/extensions/teams/infra/azure.bicep +++ b/extensions/teams/infra/azure.bicep @@ -1,4 +1,4 @@ -@maxLength(20) +@maxLength(25) @minLength(4) @description('Used to generate names for all resources in this file') param resourceBaseName string @@ -6,6 +6,9 @@ param resourceBaseName string @description('Required when create Azure Bot service') param botAadAppClientId string +@description('Required when using SingleTenant or UserAssignedMSI app type') +param botAadAppTenantId string + @secure() @description('Required by Bot Framework package in your bot project') param botAadAppClientSecret string @@ -69,6 +72,10 @@ resource webApp 'Microsoft.Web/sites@2021-02-01' = { name: 'AZURE_FUNCTION_URL' value: azureFunctionURL } + { + name: 'TEAMS_APP_TENANT_ID' + value: botAadAppTenantId + } ] ftpsState: 'FtpsOnly' } @@ -81,6 +88,7 @@ module azureBotRegistration './botRegistration/azurebot.bicep' = { params: { resourceBaseName: resourceBaseName botAadAppClientId: botAadAppClientId + botAadAppTenantId: botAadAppTenantId botAppDomain: webApp.properties.defaultHostName botDisplayName: botDisplayName } diff --git a/extensions/teams/infra/azure.parameters.json b/extensions/teams/infra/azure.parameters.json index 2d8bb37da..c342ec592 100644 --- a/extensions/teams/infra/azure.parameters.json +++ b/extensions/teams/infra/azure.parameters.json @@ -8,6 +8,9 @@ "botAadAppClientId": { "value": "${{BOT_ID}}" }, + "botAadAppTenantId": { + "value": "${{TEAMS_APP_TENANT_ID}}" + }, "botAadAppClientSecret": { "value": "${{SECRET_BOT_PASSWORD}}" }, diff --git a/extensions/teams/infra/botRegistration/azurebot.bicep b/extensions/teams/infra/botRegistration/azurebot.bicep index ab67c7a56..2251c96b4 100644 --- a/extensions/teams/infra/botRegistration/azurebot.bicep +++ b/extensions/teams/infra/botRegistration/azurebot.bicep @@ -1,4 +1,4 @@ -@maxLength(20) +@maxLength(25) // Changed from 20 to 25 to match parent template @minLength(4) @description('Used to generate names for all resources in this file') param resourceBaseName string @@ -10,9 +10,10 @@ param botServiceName string = resourceBaseName param botServiceSku string = 'F0' param botAadAppClientId string param botAppDomain string +param botAadAppTenantId string // Register your web service as a bot with the Bot Framework -resource botService 'Microsoft.BotService/botServices@2021-03-01' = { +resource botService 'Microsoft.BotService/botServices@2023-09-15-preview' = { kind: 'azurebot' location: 'global' name: botServiceName @@ -20,6 +21,8 @@ resource botService 'Microsoft.BotService/botServices@2021-03-01' = { displayName: botDisplayName endpoint: 'https://${botAppDomain}/api/messages' msaAppId: botAadAppClientId + msaAppType: 'SingleTenant' + msaAppTenantId: botAadAppTenantId } sku: { name: botServiceSku @@ -27,7 +30,7 @@ resource botService 'Microsoft.BotService/botServices@2021-03-01' = { } // Connect the bot service to Microsoft Teams -resource botServiceMsTeamsChannel 'Microsoft.BotService/botServices/channels@2021-03-01' = { +resource botServiceMsTeamsChannel 'Microsoft.BotService/botServices/channels@2023-09-15-preview' = { parent: botService location: 'global' name: 'MsTeamsChannel' diff --git a/extensions/teams/package.json b/extensions/teams/package.json index 72aa6557a..92fc66b2d 100644 --- a/extensions/teams/package.json +++ b/extensions/teams/package.json @@ -16,7 +16,8 @@ "build": "tsc --build", "start": "node ./lib/index.js", "watch": "nodemon --exec \"npm run start\"", - "test": "echo \"Error: no test specified\" && exit 1" + "test": "echo \"Error: no test specified\" && exit 1", + "enable-sp": "node ./scripts/enable-service-principal.js" }, "repository": { "type": "git", diff --git a/extensions/teams/scripts/enable-service-principal.js b/extensions/teams/scripts/enable-service-principal.js new file mode 100644 index 000000000..303a7509c --- /dev/null +++ b/extensions/teams/scripts/enable-service-principal.js @@ -0,0 +1,46 @@ +// Script to create a service principal for the Microsoft Entra application +const { exec } = require('child_process'); +const util = require('util'); +const execPromise = util.promisify(exec); + +async function createServicePrincipal() { + const appId = process.env.BOT_ID; + + if (!appId) { + console.error('Error: BOT_ID environment variable is not set'); + process.exit(1); + } + + try { + // Check if Azure CLI is installed and logged in + await execPromise('az account show'); + + // Check if service principal already exists + const checkCmd = `az ad sp list --filter "appId eq '${appId}'"`; + const { stdout } = await execPromise(checkCmd); + + const existingSpList = JSON.parse(stdout); + if (existingSpList && existingSpList.length > 0) { + console.log(`Service principal for application ID ${appId} already exists. Skipping creation.`); + process.exit(0); + } + + // Create service principal + const createCmd = `az ad sp create --id "${appId}"`; + await execPromise(createCmd); + + console.log('Service principal created successfully.'); + } catch (error) { + console.error('Error:', error.message); + if (error.message.includes('az: not found') || error.message.includes('not recognized as an internal or external command')) { + console.error('Azure CLI is not installed or not in PATH. Please install it first.'); + } else if (error.message.includes('Please run az login')) { + console.error('You are not logged into Azure. Please run az login first.'); + } else { + console.error('Failed to create service principal. Please ensure you have the right permissions.'); + } + process.exit(1); + } +} + +createServicePrincipal(); diff --git a/extensions/teams/teamsapp.yml b/extensions/teams/teamsapp.yml index 4c7018462..c4000bd75 100644 --- a/extensions/teams/teamsapp.yml +++ b/extensions/teams/teamsapp.yml @@ -27,6 +27,14 @@ provision: botId: BOT_ID # The Microsoft Entra application's client secret created for bot. botPassword: SECRET_BOT_PASSWORD + + # Create service principal for the Microsoft Entra application + - uses: cli/runNpmCommand + name: Enable Service Principal + with: + args: run enable-sp + env: + BOT_ID: ${{BOT_ID}} - uses: arm/deploy # Deploy given ARM templates parallelly. with: diff --git a/extensions/teams/tsconfig.json b/extensions/teams/tsconfig.json index 523f4a057..0d39545cd 100644 --- a/extensions/teams/tsconfig.json +++ b/extensions/teams/tsconfig.json @@ -2,7 +2,8 @@ "compilerOptions": { "declaration": true, "target": "es2021", - "module": "commonjs", + "module": "node16", + "moduleResolution": "node16", "outDir": "./lib", "rootDir": "./", "sourceMap": true,