UNPKG

@microsoft/teams-ai

Version:

SDK focused on building AI based applications for Microsoft Teams.

146 lines 7.2 kB
"use strict"; // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. Object.defineProperty(exports, "__esModule", { value: true }); exports.TeamsSsoBotAuthentication = void 0; const botbuilder_1 = require("botbuilder"); const botbuilder_dialogs_1 = require("botbuilder-dialogs"); const BotAuthenticationBase_1 = require("./BotAuthenticationBase"); const TeamsBotSsoPrompt_1 = require("./TeamsBotSsoPrompt"); const TurnStateProperty_1 = require("../TurnStateProperty"); const SSO_DIALOG_ID = '_TeamsSsoDialog'; /** * @internal * * Handles authentication for Teams bots using Single Sign-On (SSO). * @template TState - The type of the turn state object. */ class TeamsSsoBotAuthentication extends BotAuthenticationBase_1.BotAuthenticationBase { _prompt; _tokenExchangeIdRegex; /** * Initializes a new instance of the TeamsSsoBotAuthentication class. * @param {Application<TState>} app - The application object. * @param {TeamsSsoSettings} settings - The settings for Teams SSO. * @param {string} settingName - The name of the setting. * @param {ConfidentialClientApplication} msal - The MSAL (Microsoft Authentication Library) object. * @param {Storage} storage - The storage object for storing state. */ constructor(app, settings, settingName, msal, storage) { super(app, settingName, storage); this._prompt = new TeamsBotSsoPrompt_1.TeamsSsoPrompt('TeamsSsoPrompt', settingName, settings, msal); this._tokenExchangeIdRegex = new RegExp(`[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}-${this._settingName}`); // Do not save state for duplicate token exchange events to avoid eTag conflicts app.turn('afterTurn', async (context, state) => { return state.temp.duplicateTokenExchange !== true; }); } /** * Run or continue the SSO dialog and returns the result. * @param {TurnContext} context - The turn context object. * @param {TState} state - The turn state object. * @param {string} dialogStateProperty - The name of the dialog state property. * @returns {Promise<DialogTurnResult<TokenResponse>>} A promise that resolves to the dialog turn result containing the token response. */ async runDialog(context, state, dialogStateProperty) { const dialogContext = await this.createSsoDialogContext(context, state, dialogStateProperty); let results = await dialogContext.continueDialog(); if (results.status === botbuilder_dialogs_1.DialogTurnStatus.empty) { results = await dialogContext.beginDialog(SSO_DIALOG_ID); } return results; } /** * Continues the SSO dialog and returns the result. * @param {TurnContext} context - The turn context object. * @param {TState} state - The turn state object. * @param {string} dialogStateProperty - The name of the dialog state property. * @returns {Promise<DialogTurnResult<TokenResponse>>} A promise that resolves to the dialog turn result containing the token response. */ async continueDialog(context, state, dialogStateProperty) { const dialogContext = await this.createSsoDialogContext(context, state, dialogStateProperty); return await dialogContext.continueDialog(); } /** * Determines whether the token exchange activity should be processed by current authentication setting. * @param {TurnContext} context - The turn context object. * @returns {Promise<boolean>} A promise that resolves to a boolean indicating whether the token exchange route should be processed by current class instance. */ async tokenExchangeRouteSelector(context) { return ((await super.tokenExchangeRouteSelector(context)) && this._tokenExchangeIdRegex.test(context.activity.value.id)); } /** * Creates the SSO dialog context. * @param {TurnContext} context - The turn context object. * @param {TState} state - The turn state object. * @param {string} dialogStateProperty - The name of the dialog state property. * @returns {Promise<DialogContext>} A promise that resolves to the dialog context object. */ async createSsoDialogContext(context, state, dialogStateProperty) { const accessor = new TurnStateProperty_1.TurnStateProperty(state, 'conversation', dialogStateProperty); const dialogSet = new botbuilder_dialogs_1.DialogSet(accessor); dialogSet.add(this._prompt); dialogSet.add(new botbuilder_dialogs_1.WaterfallDialog(SSO_DIALOG_ID, [ async (step) => { return await step.beginDialog(this._prompt.id); }, async (step) => { const tokenResponse = step.result; // Dedup token exchange responses if (tokenResponse && (await this.shouldDedup(context))) { state.temp.duplicateTokenExchange = true; return botbuilder_dialogs_1.Dialog.EndOfTurn; } return await step.endDialog(step.result); } ])); return await dialogSet.createContext(context); } /** * Checks if deduplication should be performed for token exchange. * @param {TurnContext} context - The turn context object. * @returns {Promise<boolean>} A promise that resolves to a boolean indicating whether deduplication should be performed. */ async shouldDedup(context) { const storeItem = { eTag: context.activity.value.id }; const key = this.getStorageKey(context); const storeItems = { [key]: storeItem }; try { await this._storage.write(storeItems); } catch (err) { if (err instanceof Error && err.message.indexOf('eTag conflict')) { return true; } throw err; } return false; } /** * Gets the storage key for storing the token exchange state. * @param {TurnContext} context - The turn context object. * @returns {string} The storage key. * @throws Error if the context is invalid or the activity is not a token exchange invoke. */ getStorageKey(context) { if (!context || !context.activity || !context.activity.conversation) { throw new Error('Invalid context, can not get storage key!'); } const activity = context.activity; const channelId = activity.channelId; const conversationId = activity.conversation.id; if (activity.type !== botbuilder_1.ActivityTypes.Invoke || activity.name !== botbuilder_1.tokenExchangeOperationName) { throw new Error('TokenExchangeState can only be used with Invokes of signin/tokenExchange.'); } const value = activity.value; if (!value || !value.id) { throw new Error('Invalid signin/tokenExchange. Missing activity.value.id.'); } return `${channelId}/${conversationId}/${value.id}`; } } exports.TeamsSsoBotAuthentication = TeamsSsoBotAuthentication; //# sourceMappingURL=TeamsSsoBotAuthentication.js.map