@microsoft/teams-ai
Version:
SDK focused on building AI based applications for Microsoft Teams.
261 lines • 11.3 kB
JavaScript
;
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