diff --git a/libraries/botbuilder/tests/teams/composeMessagingExtensionAuth/.env b/libraries/botbuilder/tests/teams/composeMessagingExtensionAuth/.env new file mode 100644 index 0000000000..cb01b37ccc --- /dev/null +++ b/libraries/botbuilder/tests/teams/composeMessagingExtensionAuth/.env @@ -0,0 +1,3 @@ +MicrosoftAppId= +MicrosoftAppPassword= +ConnectionName= \ No newline at end of file diff --git a/libraries/botbuilder/tests/teams/composeMessagingExtensionAuth/package.json b/libraries/botbuilder/tests/teams/composeMessagingExtensionAuth/package.json new file mode 100644 index 0000000000..d96a615576 --- /dev/null +++ b/libraries/botbuilder/tests/teams/composeMessagingExtensionAuth/package.json @@ -0,0 +1,30 @@ +{ + "name": "messagingextension-auth", + "version": "1.0.0", + "description": "", + "main": "./lib/index.js", + "scripts": { + "start": "tsc --build && node ./lib/index.js", + "build": "tsc --build", + "watch": "nodemon --watch ./src -e ts --exec \"npm run start\"" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "botbuilder": "file:../../../", + "dotenv": "^8.1.0", + "node-fetch": "^2.6.0", + "restify": "^8.4.0", + "uuid": "^3.3.3" + }, + "devDependencies": { + "@types/node": "^12.7.1", + "@types/node-fetch": "^2.5.0", + "@types/request": "^2.48.1", + "@types/restify": "^7.2.7", + "nodemon": "^1.19.1", + "ts-node": "^7.0.1", + "typescript": "^3.2.4" + } +} diff --git a/libraries/botbuilder/tests/teams/composeMessagingExtensionAuth/src/composeMessagingExtensionAuthBot.ts b/libraries/botbuilder/tests/teams/composeMessagingExtensionAuth/src/composeMessagingExtensionAuthBot.ts new file mode 100644 index 0000000000..25aa8d9385 --- /dev/null +++ b/libraries/botbuilder/tests/teams/composeMessagingExtensionAuth/src/composeMessagingExtensionAuthBot.ts @@ -0,0 +1,209 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { + Attachment, + CardFactory, + MessagingExtensionActionResponse, + MessagingExtensionAction, + MessagingExtensionQuery, + TaskModuleContinueResponse, + TaskModuleTaskInfo, + TaskModuleRequest, + TeamsActivityHandler, + TurnContext, + BotFrameworkAdapter, +} from 'botbuilder'; + +import { + IUserTokenProvider, +} from 'botbuilder-core'; + +/* +* This Bot requires an Azure Bot Service OAuth connection name in appsettings.json +* see: https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-authentication +* +* Clicking this bot's Task Menu will retrieve the login dialog, if the user is not already signed in. +*/ +export class ComposeMessagingExtensionAuthBot extends TeamsActivityHandler { + connectionName: string; + constructor(authConnectionName: string) { + super(); + + this.connectionName = authConnectionName; + + // See https://aka.ms/about-bot-activity-message to learn more about the message and other activity types. + this.onMessage(async (context, next) => { + + // Hack around weird behavior of RemoveRecipientMention (it alters the activity.Text) + const originalText = context.activity.text; + TurnContext.removeRecipientMention(context.activity); + const text = context.activity.text.replace(' ', '').toUpperCase(); + context.activity.text = originalText; + + if (text === 'LOGOUT' || text === 'SIGNOUT') + { + const adapter: IUserTokenProvider = context.adapter as BotFrameworkAdapter; + + await adapter.signOutUser(context, this.connectionName); + await context.sendActivity(`Signed Out: ${context.activity.from.name}`); + + return; + } + + await context.sendActivity(`You said '${context.activity.text}'`); + // By calling next() you ensure that the next BotHandler is run. + await next(); + }); + + this.onMembersAdded(async (context, next) => { + const membersAdded = context.activity.membersAdded; + for (const member of membersAdded) { + if (member.id !== context.activity.recipient.id) { + await context.sendActivity('Hello and welcome!'); + } + } + + // By calling next() you ensure that the next BotHandler is run. + await next(); + }); + } + + protected async onTeamsMessagingExtensionFetchTask(context: TurnContext, query: MessagingExtensionQuery): Promise { + const adapter: IUserTokenProvider = context.adapter as BotFrameworkAdapter; + const userToken = await adapter.getUserToken(context, this.connectionName); + if (!userToken) + { + // There is no token, so the user has not signed in yet. + + // Retrieve the OAuth Sign in Link to use in the MessagingExtensionResult Suggested Actions + const signInLink = await adapter.getSignInLink(context, this.connectionName); + + const response : MessagingExtensionActionResponse = { + composeExtension: { + type: 'auth', + suggestedActions: { + actions: [{ + type: 'openUrl', + value: signInLink, + title: 'Bot Service OAuth' + }] + } + } + }; + return response; + }; + + // User is already signed in. + const continueResponse : TaskModuleContinueResponse = { + type: 'continue', + value: this.CreateSignedInTaskModuleTaskInfo(), + }; + + const response : MessagingExtensionActionResponse = { + task: continueResponse + }; + + return response; + } + + protected async onTeamsTaskModuleFetch(context: TurnContext, taskModuleRequest: TaskModuleRequest): Promise { + var data = context.activity.value; + if (data && data.state) + { + const adapter: IUserTokenProvider = context.adapter as BotFrameworkAdapter; + const tokenResponse = await adapter.getUserToken(context, this.connectionName, data.state); + return this.CreateSignedInTaskModuleTaskInfo(tokenResponse.token); + } + else + { + await context.sendActivity("OnTeamsTaskModuleFetchAsync called without 'state' in Activity.Value"); + return null; + } + } + + protected async onTeamsMessagingExtensionSubmitAction(context, action: MessagingExtensionAction): Promise { + if (action.data != null && action.data.key && action.data.key == "signout") + { + // User clicked the Sign Out button from a Task Module + await (context.adapter as IUserTokenProvider).signOutUser(context, this.connectionName); + await context.sendActivity(`Signed Out: ${context.activity.from.name}`); + } + + return null; + } + + private CreateSignedInTaskModuleTaskInfo(token?: string): TaskModuleTaskInfo { + const attachment = this.GetTaskModuleAdaptiveCard(); + let width = 350; + let height = 160; + if(token){ + + const subCard = CardFactory.adaptiveCard({ + version: '1.0.0', + type: 'AdaptiveCard', + body: [ + { + type: 'TextBlock', + text: `Your token is ` + token, + wrap: true, + } + ] + }); + + const card = attachment.content; + card.actions.push( + { + type: 'Action.ShowCard', + title: 'Show Token', + card: subCard.content, + } + ); + width = 500; + height = 300; + } + return { + card: attachment, + height: height, + width: width, + title: 'Compose Extension Auth Example', + }; + } + + private GetTaskModuleAdaptiveCard(): Attachment { + return CardFactory.adaptiveCard({ + version: '1.0.0', + type: 'AdaptiveCard', + body: [ + { + type: 'TextBlock', + text: `You are signed in!`, + }, + { + type: 'TextBlock', + text: `Send 'Log out' or 'Sign out' to start over.`, + }, + { + type: 'TextBlock', + text: `(Or click the Sign Out button below.)`, + }, + ], + actions: [ + { + type: 'Action.Submit', + title: 'Close', + data: { + key: 'close', + } + }, + { + type: 'Action.Submit', + title: 'Sign Out', + data: { + key: 'signout', + } + } + ] + }); + } +} diff --git a/libraries/botbuilder/tests/teams/composeMessagingExtensionAuth/src/index.ts b/libraries/botbuilder/tests/teams/composeMessagingExtensionAuth/src/index.ts new file mode 100644 index 0000000000..047204cd6e --- /dev/null +++ b/libraries/botbuilder/tests/teams/composeMessagingExtensionAuth/src/index.ts @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { config } from 'dotenv'; +import * as path from 'path'; +import * as restify from 'restify'; + +// Import required bot services. +// See https://aka.ms/bot-services to learn more about the different parts of a bot. +import { BotFrameworkAdapter } from 'botbuilder'; + +// This bot's main dialog. +import { ComposeMessagingExtensionAuthBot } from './composeMessagingExtensionAuthBot'; + +const ENV_FILE = path.join(__dirname, '..', '.env'); +config({ path: ENV_FILE }); + +// Create HTTP server. +const server = restify.createServer(); +server.listen(process.env.port || process.env.PORT || 3978, () => { + console.log(`\n${server.name} listening to ${server.url}`); + console.log(`\nGet Bot Framework Emulator: https://aka.ms/botframework-emulator`); + console.log(`\nTo test your bot, see: https://aka.ms/debug-with-emulator`); +}); + +// Create adapter. +// See https://aka.ms/about-bot-adapter to learn more about adapters. +const adapter = new BotFrameworkAdapter({ + appId: process.env.MicrosoftAppId, + appPassword: process.env.MicrosoftAppPassword +}); + +// Catch-all for errors. +adapter.onTurnError = async (context, error) => { + // This check writes out errors to console log .vs. app insights. + console.error('[onTurnError]:'); + console.error(error); + // Send a message to the user + await context.sendActivity(`Oops. Something went wrong in the bot!\n ${error.message}`); +}; + +// Create the main dialog. +const myBot = new ComposeMessagingExtensionAuthBot(process.env.ConnectionName); + +// Listen for incoming requests. +server.post('/api/messages', (req, res) => { + adapter.processActivity(req, res, async (context) => { + // Route to main dialog. + await myBot.run(context); + }); +}); diff --git a/libraries/botbuilder/tests/teams/composeMessagingExtensionAuth/teams-app-manifest/color.png b/libraries/botbuilder/tests/teams/composeMessagingExtensionAuth/teams-app-manifest/color.png new file mode 100644 index 0000000000..48a2de1330 Binary files /dev/null and b/libraries/botbuilder/tests/teams/composeMessagingExtensionAuth/teams-app-manifest/color.png differ diff --git a/libraries/botbuilder/tests/teams/composeMessagingExtensionAuth/teams-app-manifest/manifest.json b/libraries/botbuilder/tests/teams/composeMessagingExtensionAuth/teams-app-manifest/manifest.json new file mode 100644 index 0000000000..3e636a3d77 --- /dev/null +++ b/libraries/botbuilder/tests/teams/composeMessagingExtensionAuth/teams-app-manifest/manifest.json @@ -0,0 +1,73 @@ +{ + "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json", + "manifestVersion": "1.5", + "version": "1.0", + "id": "<>", + "packageName": "com.teams.sample.compose.extension", + "developer": { + "name": "Microsoft", + "websiteUrl": "https://www.microsoft.com", + "privacyUrl": "https://www.teams.com/privacy", + "termsOfUseUrl": "https://www.teams.com/termsofuser" + }, + "icons": { + "color": "color.png", + "outline": "outline.png" + }, + "name": { + "short": "Compose Extension Auth", + "full": "Compose Messaging Extension Auth Example" + }, + "description": { + "short": "Bot Service Auth in Compose Extension", + "full": "Demonstrates Bot Service Auth in a Compose Messaging Extension" + }, + "accentColor": "#FFFFFF", + "bots": [ + { + "botId": "<>", + "scopes": [ + "personal", + "team", + "groupchat" + ], + "supportsFiles": false, + "isNotificationOnly": false + } + ], + "composeExtensions": [ + { + "botId": "<>", + "canUpdateConfiguration": false, + "commands": [ + { + "id": "loginCommand", + "type": "action", + "title": "Log In", + "description": "Bot Service Auth flow in a Compose Messaging Extension", + "initialRun": false, + "fetchTask": true, + "context": [ + "commandBox", + "compose", + "message" + ], + "parameters": [ + { + "name": "param", + "title": "param", + "description": "" + } + ] + } + ] + } + ], + "permissions": [ + "identity", + "messageTeamMembers" + ], + "validDomains": [ + "*.botframework.com" + ] +} \ No newline at end of file diff --git a/libraries/botbuilder/tests/teams/composeMessagingExtensionAuth/teams-app-manifest/outline.png b/libraries/botbuilder/tests/teams/composeMessagingExtensionAuth/teams-app-manifest/outline.png new file mode 100644 index 0000000000..dbfa927729 Binary files /dev/null and b/libraries/botbuilder/tests/teams/composeMessagingExtensionAuth/teams-app-manifest/outline.png differ diff --git a/libraries/botbuilder/tests/teams/composeMessagingExtensionAuth/tsconfig.json b/libraries/botbuilder/tests/teams/composeMessagingExtensionAuth/tsconfig.json new file mode 100644 index 0000000000..a168d60662 --- /dev/null +++ b/libraries/botbuilder/tests/teams/composeMessagingExtensionAuth/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "es2016", + "module": "commonjs", + "composite": true, + "declaration": true, + "sourceMap": true, + "outDir": "./lib", + "rootDir": "./src", + } +} \ No newline at end of file