UNPKG

@microsoft/teams-ai

Version:

SDK focused on building AI based applications for Microsoft Teams.

261 lines 11.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.BotAuthenticationBase = void 0; exports.setSettingNameInContextActivityValue = setSettingNameInContextActivityValue; exports.setTokenInState = setTokenInState; exports.deleteTokenFromState = deleteTokenFromState; exports.userInSignInFlow = userInSignInFlow; exports.setUserInSignInFlow = setUserInSignInFlow; exports.deleteUserInSignInFlow = deleteUserInSignInFlow; const botbuilder_dialogs_1 = require("botbuilder-dialogs"); const botbuilder_1 = require("botbuilder"); const Authentication_1 = require("./Authentication"); /** * @private */ const IS_SIGNED_IN_KEY = '__InSignInFlow__'; /** * @internal * Base class to handle Teams conversational bot authentication. * @template TState - The type of the turn state. */ class BotAuthenticationBase { _storage; _settingName; _userSignInSuccessHandler; _userSignInFailureHandler; /** * Creates a new instance of BotAuthenticationBase. * @param {Application<TState>} app - The application instance. * @param {string} settingName - The name of the setting. * @param {Storage} [storage] - The storage to save states. */ constructor(app, settingName, storage) { this._settingName = settingName; this._storage = storage || new botbuilder_1.MemoryStorage(); // Add application routes to handle OAuth callbacks app.addRoute(this.verifyStateRouteSelector.bind(this), async (context, state) => { await this.handleSignInActivity(context, state); }, true); app.addRoute(this.tokenExchangeRouteSelector.bind(this), async (context, state) => { await this.handleSignInActivity(context, state); }, true); } /** * Authenticates the user. * @param {TurnContext} context - The turn context. * @param {TState} state - The turn state. * @returns {Promise<string | undefined>} - The authentication token, or undefined if authentication failed. */ async authenticate(context, state) { // Get property names to use const userAuthStatePropertyName = this.getUserAuthStatePropertyName(context); const userDialogStatePropertyName = this.getUserDialogStatePropertyName(context); // Save message if not signed in if (!this.getUserAuthState(context, state)) { state.conversation[userAuthStatePropertyName] = { message: context.activity.text }; } const results = await this.runDialog(context, state, userDialogStatePropertyName); if (results.status === botbuilder_dialogs_1.DialogTurnStatus.complete) { // Delete user auth state this.deleteAuthFlowState(context, state); if (results.result?.token) { // Return token return results.result?.token; } else { // Completed dialog without a token. // This could mean the user declined the consent prompt in the previous turn. // Retry authentication flow again. return await this.authenticate(context, state); } } return undefined; } /** * Checks if the activity is a valid message activity * @param {TurnContext} context - The turn context. * @returns {boolean} - True if the activity is a valid message activity. */ isValidActivity(context) { // Should be a message activity with non-empty text property. return (context.activity.type === botbuilder_1.ActivityTypes.Message && context.activity.text != null && context.activity.text != undefined && context.activity.text.length > 0); } /** * The handler function is called when the user has successfully signed in * @template TState * @param {(context: TurnContext, state: TState) => Promise<void>} handler The handler function to call when the user has successfully signed in */ onUserSignInSuccess(handler) { this._userSignInSuccessHandler = handler; } /** * The handler function is called when the user sign in flow fails * @template TState * @param {(context: TurnContext, state: TState) => Promise<void>} handler The handler function to call when the user failed to signed in */ onUserSignInFailure(handler) { this._userSignInFailureHandler = handler; } /** * Handles the signin/verifyState activity. The onUserSignInSuccess and onUserSignInFailure handlers will be called based on the result. * @param {TurnContext} context - The turn context. * @param {TState} state - The turn state. */ async handleSignInActivity(context, state) { try { const userDialogStatePropertyName = this.getUserDialogStatePropertyName(context); const result = await this.continueDialog(context, state, userDialogStatePropertyName); if (result.status === botbuilder_dialogs_1.DialogTurnStatus.complete) { // OAuthPrompt dialog should have sent an invoke response already. if (result.result?.token) { // Successful sign in setTokenInState(state, this._settingName, result.result.token); // Get user auth state const userAuthState = this.getUserAuthState(context, state); // Restore previous user message context.activity.text = userAuthState.message || ''; await this._userSignInSuccessHandler?.(context, state); } else { // Failed sign in await this._userSignInFailureHandler?.(context, state, new Authentication_1.AuthError('Authentication flow completed without a token.', 'completionWithoutToken')); } } } catch (e) { const errorMessage = e instanceof Error ? e.message : JSON.stringify(e); const message = `Unexpected error encountered while signing in: ${errorMessage}. Incoming activity details: type: ${context.activity.type}, name: ${context.activity.name}`; await this._userSignInFailureHandler?.(context, state, new Authentication_1.AuthError(message)); } } /** * Deletes the user auth state and user dialog state from the turn state. So that the next message can start a new authentication flow. * @param {TurnContext} context - The turn context. * @param {TState} state - The turn state. */ deleteAuthFlowState(context, state) { // Delete user auth state const userAuthStatePropertyName = this.getUserAuthStatePropertyName(context); if (this.getUserAuthState(context, state)) { delete state.conversation[userAuthStatePropertyName]; } // Delete user dialog state const userDialogStatePropertyName = this.getUserDialogStatePropertyName(context); if (this.getUserDialogState(context, state)) { delete state.conversation[userDialogStatePropertyName]; } } /** * Gets the property name for storing user authentication state. * @param {TurnContext} context - The turn context. * @returns {string} - The property name. */ getUserAuthStatePropertyName(context) { return `__${context.activity.from.id}:${this._settingName}:Bot:AuthState__`; } /** * Gets the property name for storing user dialog state. * @param {TurnContext} context - The turn context. * @returns {string} - The property name. */ getUserDialogStatePropertyName(context) { return `__${context.activity.from.id}:${this._settingName}:DialogState__`; } getUserAuthState(context, state) { return state.conversation[this.getUserAuthStatePropertyName(context)]; } getUserDialogState(context, state) { const userDialogStatePropertyName = this.getUserDialogStatePropertyName(context); return state.conversation[userDialogStatePropertyName]; } async verifyStateRouteSelector(context) { return (context.activity.type === botbuilder_1.ActivityTypes.Invoke && context.activity.name === botbuilder_1.verifyStateOperationName && this._settingName == context.activity.value['settingName']); } async tokenExchangeRouteSelector(context) { return (context.activity.type === botbuilder_1.ActivityTypes.Invoke && context.activity.name === botbuilder_1.tokenExchangeOperationName && this._settingName == context.activity.value['settingName']); } } exports.BotAuthenticationBase = BotAuthenticationBase; /** * Sets the setting name in the context.activity.value object. * The setting name is needed in signIn/verifyState` and `signIn/tokenExchange` route selector to accurately route to the correct authentication setting. * @param {TurnContext} context The turn context object * @param {string} settingName The auth setting name */ function setSettingNameInContextActivityValue(context, settingName) { if (typeof context.activity.value == 'object') { context.activity.value['settingName'] = settingName; } else { context.activity.value = { settingName: settingName }; } } /** * Sets the token in the turn state * @param {TurnState} state The turn state * @param {string} settingName The name of the setting * @param {string} token The token to set * @internal */ function setTokenInState(state, settingName, token) { if (!state.temp.authTokens) { state.temp.authTokens = {}; } state.temp.authTokens[settingName] = token; } /** * Deletes the token from the turn state * @param {TurnState} state The turn state * @param {string} settingName The name of the setting */ function deleteTokenFromState(state, settingName) { if (!state.temp.authTokens || !state.temp.authTokens[settingName]) { return; } delete state.temp.authTokens[settingName]; } /** * Determines if the user is in the sign in flow. * @template TState * @param {TState} state - the turn state * @returns {string | undefined} The setting name if the user is in sign in flow. Otherwise, undefined. */ function userInSignInFlow(state) { if (IS_SIGNED_IN_KEY in state.user && typeof state.user[IS_SIGNED_IN_KEY] == 'string') { // returns the connection name if the user is in the sign in flow return state.user[IS_SIGNED_IN_KEY]; } return; } /** * Update the turn state to indicate the user is in the sign in flow by providing the authentication setting name used. * @template TState * @param {TState} state - the turn state * @param {string} settingName - the authentication setting name. */ function setUserInSignInFlow(state, settingName) { state.user[IS_SIGNED_IN_KEY] = settingName; } /** * Deletes the user sign in flow state from the turn state. * @template TState * Determines if the user is in the sign in flow. * @param {TState} state - the turn state */ function deleteUserInSignInFlow(state) { delete state.user[IS_SIGNED_IN_KEY]; } //# sourceMappingURL=BotAuthenticationBase.js.map