@microsoft/agents-hosting 1.5.0-beta.5.g18f3420031 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/package.json +10 -9
- package/dist/src/activityHandler.js +2 -2
- package/dist/src/activityHandler.js.map +1 -1
- package/dist/src/agent-client/agentClient.js +49 -40
- package/dist/src/agent-client/agentClient.js.map +1 -1
- package/dist/src/agent-client/agentResponseHandler.js +2 -2
- package/dist/src/agent-client/agentResponseHandler.js.map +1 -1
- package/dist/src/app/agentApplication.d.ts +36 -10
- package/dist/src/app/agentApplication.js +169 -99
- package/dist/src/app/agentApplication.js.map +1 -1
- package/dist/src/app/agentApplicationBuilder.d.ts +15 -0
- package/dist/src/app/agentApplicationBuilder.js +22 -4
- package/dist/src/app/agentApplicationBuilder.js.map +1 -1
- package/dist/src/app/agentApplicationOptions.d.ts +38 -0
- package/dist/src/app/attachmentDownloader.js +2 -2
- package/dist/src/app/attachmentDownloader.js.map +1 -1
- package/dist/src/app/auth/authorization.js +12 -9
- package/dist/src/app/auth/authorization.js.map +1 -1
- package/dist/src/app/auth/authorizationManager.d.ts +18 -5
- package/dist/src/app/auth/authorizationManager.js +258 -45
- package/dist/src/app/auth/authorizationManager.js.map +1 -1
- package/dist/src/app/auth/handlerStorage.js +3 -1
- package/dist/src/app/auth/handlerStorage.js.map +1 -1
- package/dist/src/app/auth/handlers/agenticAuthorization.d.ts +19 -16
- package/dist/src/app/auth/handlers/agenticAuthorization.js +46 -52
- package/dist/src/app/auth/handlers/agenticAuthorization.js.map +1 -1
- package/dist/src/app/auth/handlers/azureBotAuthorization.d.ts +51 -75
- package/dist/src/app/auth/handlers/azureBotAuthorization.js +217 -192
- package/dist/src/app/auth/handlers/azureBotAuthorization.js.map +1 -1
- package/dist/src/app/auth/types.d.ts +100 -1
- package/dist/src/app/auth/utils.d.ts +10 -0
- package/dist/src/app/auth/utils.js +21 -0
- package/dist/src/app/auth/utils.js.map +1 -0
- package/dist/src/app/index.d.ts +1 -0
- package/dist/src/app/index.js +1 -0
- package/dist/src/app/index.js.map +1 -1
- package/dist/src/app/proactive/conversation.d.ts +43 -0
- package/dist/src/app/proactive/conversation.js +67 -0
- package/dist/src/app/proactive/conversation.js.map +1 -0
- package/dist/src/app/proactive/conversationBuilder.d.ts +54 -0
- package/dist/src/app/proactive/conversationBuilder.js +110 -0
- package/dist/src/app/proactive/conversationBuilder.js.map +1 -0
- package/dist/src/app/proactive/conversationReferenceBuilder.d.ts +68 -0
- package/dist/src/app/proactive/conversationReferenceBuilder.js +125 -0
- package/dist/src/app/proactive/conversationReferenceBuilder.js.map +1 -0
- package/dist/src/app/proactive/createConversationOptions.d.ts +30 -0
- package/dist/src/app/proactive/createConversationOptions.js +10 -0
- package/dist/src/app/proactive/createConversationOptions.js.map +1 -0
- package/dist/src/app/proactive/createConversationOptionsBuilder.d.ts +69 -0
- package/dist/src/app/proactive/createConversationOptionsBuilder.js +141 -0
- package/dist/src/app/proactive/createConversationOptionsBuilder.js.map +1 -0
- package/dist/src/app/proactive/index.d.ts +7 -0
- package/dist/src/app/proactive/index.js +26 -0
- package/dist/src/app/proactive/index.js.map +1 -0
- package/dist/src/app/proactive/proactive.d.ts +248 -0
- package/dist/src/app/proactive/proactive.js +310 -0
- package/dist/src/app/proactive/proactive.js.map +1 -0
- package/dist/src/app/proactive/proactiveOptions.d.ts +19 -0
- package/dist/src/app/proactive/proactiveOptions.js +5 -0
- package/dist/src/app/proactive/proactiveOptions.js.map +1 -0
- package/dist/src/app/streaming/streamingResponse.js +2 -2
- package/dist/src/app/streaming/streamingResponse.js.map +1 -1
- package/dist/src/app/teamsAttachmentDownloader.js +2 -2
- package/dist/src/app/teamsAttachmentDownloader.js.map +1 -1
- package/dist/src/app/turnState.js +2 -2
- package/dist/src/app/turnState.js.map +1 -1
- package/dist/src/auth/authConfiguration.d.ts +61 -0
- package/dist/src/auth/authConfiguration.js +52 -3
- package/dist/src/auth/authConfiguration.js.map +1 -1
- package/dist/src/auth/jwt-middleware.js +2 -2
- package/dist/src/auth/jwt-middleware.js.map +1 -1
- package/dist/src/auth/msalConnectionManager.js +20 -0
- package/dist/src/auth/msalConnectionManager.js.map +1 -1
- package/dist/src/auth/msalTokenCredential.js +3 -0
- package/dist/src/auth/msalTokenCredential.js.map +1 -1
- package/dist/src/auth/msalTokenProvider.js +136 -110
- package/dist/src/auth/msalTokenProvider.js.map +1 -1
- package/dist/src/baseAdapter.js +2 -2
- package/dist/src/baseAdapter.js.map +1 -1
- package/dist/src/cloudAdapter.js +201 -154
- package/dist/src/cloudAdapter.js.map +1 -1
- package/dist/src/connector-client/connectorClient.js +176 -127
- package/dist/src/connector-client/connectorClient.js.map +1 -1
- package/dist/src/errorHelper.js +108 -0
- package/dist/src/errorHelper.js.map +1 -1
- package/dist/src/middlewareSet.js +2 -2
- package/dist/src/middlewareSet.js.map +1 -1
- package/dist/src/oauth/userTokenClient.js +78 -48
- package/dist/src/oauth/userTokenClient.js.map +1 -1
- package/dist/src/observability/index.d.ts +2 -0
- package/dist/src/observability/index.js +21 -0
- package/dist/src/observability/index.js.map +1 -0
- package/dist/src/observability/metrics.d.ts +21 -0
- package/dist/src/observability/metrics.js +87 -0
- package/dist/src/observability/metrics.js.map +1 -0
- package/dist/src/observability/traces.d.ts +234 -0
- package/dist/src/observability/traces.js +962 -0
- package/dist/src/observability/traces.js.map +1 -0
- package/dist/src/state/agentState.js +2 -2
- package/dist/src/state/agentState.js.map +1 -1
- package/dist/src/storage/fileStorage.js +38 -28
- package/dist/src/storage/fileStorage.js.map +1 -1
- package/dist/src/storage/memoryStorage.js +41 -30
- package/dist/src/storage/memoryStorage.js.map +1 -1
- package/dist/src/transcript/fileTranscriptLogger.js +2 -2
- package/dist/src/transcript/fileTranscriptLogger.js.map +1 -1
- package/dist/src/transcript/transcriptLoggerMiddleware.js +2 -2
- package/dist/src/transcript/transcriptLoggerMiddleware.js.map +1 -1
- package/dist/src/turnContext.js +48 -42
- package/dist/src/turnContext.js.map +1 -1
- package/package.json +10 -9
- package/src/activityHandler.ts +1 -1
- package/src/agent-client/agentClient.ts +53 -42
- package/src/agent-client/agentResponseHandler.ts +1 -1
- package/src/app/agentApplication.ts +212 -86
- package/src/app/agentApplicationBuilder.ts +26 -4
- package/src/app/agentApplicationOptions.ts +43 -0
- package/src/app/attachmentDownloader.ts +1 -1
- package/src/app/auth/authorization.ts +11 -8
- package/src/app/auth/authorizationManager.ts +297 -45
- package/src/app/auth/handlerStorage.ts +3 -1
- package/src/app/auth/handlers/agenticAuthorization.ts +68 -72
- package/src/app/auth/handlers/azureBotAuthorization.ts +260 -264
- package/src/app/auth/types.ts +102 -1
- package/src/app/auth/utils.ts +22 -0
- package/src/app/index.ts +1 -0
- package/src/app/proactive/conversation.ts +87 -0
- package/src/app/proactive/conversationBuilder.ts +139 -0
- package/src/app/proactive/conversationReferenceBuilder.ts +161 -0
- package/src/app/proactive/createConversationOptions.ts +35 -0
- package/src/app/proactive/createConversationOptionsBuilder.ts +181 -0
- package/src/app/proactive/index.ts +10 -0
- package/src/app/proactive/proactive.ts +524 -0
- package/src/app/proactive/proactiveOptions.ts +24 -0
- package/src/app/streaming/streamingResponse.ts +1 -1
- package/src/app/teamsAttachmentDownloader.ts +1 -1
- package/src/app/turnState.ts +1 -1
- package/src/auth/authConfiguration.ts +58 -1
- package/src/auth/jwt-middleware.ts +1 -1
- package/src/auth/msalConnectionManager.ts +22 -0
- package/src/auth/msalTokenCredential.ts +4 -0
- package/src/auth/msalTokenProvider.ts +138 -107
- package/src/baseAdapter.ts +1 -1
- package/src/cloudAdapter.ts +239 -184
- package/src/connector-client/connectorClient.ts +169 -126
- package/src/errorHelper.ts +124 -0
- package/src/middlewareSet.ts +1 -1
- package/src/oauth/userTokenClient.ts +70 -46
- package/src/observability/index.ts +5 -0
- package/src/observability/metrics.ts +103 -0
- package/src/observability/traces.ts +988 -0
- package/src/state/agentState.ts +1 -1
- package/src/storage/fileStorage.ts +36 -26
- package/src/storage/memoryStorage.ts +40 -29
- package/src/transcript/fileTranscriptLogger.ts +1 -1
- package/src/transcript/transcriptLoggerMiddleware.ts +1 -1
- package/src/turnContext.ts +47 -41
|
@@ -3,16 +3,19 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { debug } from '@microsoft/agents-activity/logger'
|
|
7
6
|
import { AuthorizationHandlerStatus, AuthorizationHandler, ActiveAuthorizationHandler, AuthorizationHandlerSettings, AuthorizationHandlerTokenOptions } from '../types'
|
|
8
7
|
import { MessageFactory } from '../../../messageFactory'
|
|
9
8
|
import { CardFactory } from '../../../cards'
|
|
10
9
|
import { TurnContext } from '../../../turnContext'
|
|
11
10
|
import { TokenExchangeRequest, TokenExchangeInvokeResponse, TokenResponse, UserTokenClient } from '../../../oauth'
|
|
12
|
-
import jwt
|
|
11
|
+
import jwt from 'jsonwebtoken'
|
|
13
12
|
import { HandlerStorage } from '../handlerStorage'
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
13
|
+
import { TokenExchangeInvokeRequest } from '../../../invoke'
|
|
14
|
+
import { sendInvokeResponse } from '../utils'
|
|
15
|
+
import { Channels, ExceptionHelper } from '@microsoft/agents-activity'
|
|
16
|
+
import { trace, debug } from '@microsoft/agents-telemetry'
|
|
17
|
+
import { AuthorizationTraceDefinitions } from '../../../observability'
|
|
18
|
+
import { Errors } from '../../../errorHelper'
|
|
16
19
|
|
|
17
20
|
const logger = debug('agents:authorization:azurebot')
|
|
18
21
|
|
|
@@ -37,46 +40,19 @@ interface AzureBotActiveHandler extends ActiveAuthorizationHandler {
|
|
|
37
40
|
category?: Category
|
|
38
41
|
}
|
|
39
42
|
|
|
40
|
-
/**
|
|
41
|
-
* Messages configuration for the AzureBotAuthorization handler.
|
|
42
|
-
*/
|
|
43
|
-
export interface AzureBotAuthorizationOptionsMessages {
|
|
44
|
-
/**
|
|
45
|
-
* Message displayed when an invalid code is entered.
|
|
46
|
-
* Use `{code}` as a placeholder for the entered code.
|
|
47
|
-
* Defaults to: 'The code entered is invalid. Please sign-in again to continue.'
|
|
48
|
-
*/
|
|
49
|
-
invalidCode?: string
|
|
50
|
-
/**
|
|
51
|
-
* Message displayed when the entered code format is invalid.
|
|
52
|
-
* Use `{attemptsLeft}` as a placeholder for the number of attempts left.
|
|
53
|
-
* Defaults to: 'Please enter a valid **6-digit** code format (_e.g. 123456_).\r\n**{attemptsLeft} attempt(s) left...**'
|
|
54
|
-
*/
|
|
55
|
-
invalidCodeFormat?: string
|
|
56
|
-
/**
|
|
57
|
-
* Message displayed when the maximum number of attempts is exceeded.
|
|
58
|
-
* Use `{maxAttempts}` as a placeholder for the maximum number of attempts.
|
|
59
|
-
* Defaults to: 'You have exceeded the maximum number of sign-in attempts ({maxAttempts}).'
|
|
60
|
-
*/
|
|
61
|
-
maxAttemptsExceeded?: string
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Settings for on-behalf-of token acquisition.
|
|
66
|
-
*/
|
|
67
|
-
export interface AzureBotAuthorizationOptionsOBO {
|
|
68
|
-
/**
|
|
69
|
-
* Connection name to use for on-behalf-of token acquisition.
|
|
70
|
-
*/
|
|
71
|
-
connection?: string
|
|
72
|
-
/**
|
|
73
|
-
* Scopes to request for on-behalf-of token acquisition.
|
|
74
|
-
*/
|
|
75
|
-
scopes?: string[]
|
|
76
|
-
}
|
|
77
|
-
|
|
78
43
|
/**
|
|
79
44
|
* Interface defining an authorization handler configuration.
|
|
45
|
+
* @remarks
|
|
46
|
+
* Properties can be configured via environment variables (case-insensitive).
|
|
47
|
+
* Use the format: `AgentApplication__UserAuthorization__handlers__{handlerId}__settings__{propertyName}`
|
|
48
|
+
* where `{handlerId}` is the handler's unique identifier and `{propertyName}` matches the property name.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```env
|
|
52
|
+
* # For a handler with id "myAuth":
|
|
53
|
+
* AgentApplication__UserAuthorization__handlers__myAuth__settings__azureBotOAuthConnectionName=MyConnection
|
|
54
|
+
* AgentApplication__UserAuthorization__handlers__myAuth__settings__oboScopes=api://scope1 api://scope2
|
|
55
|
+
* ```
|
|
80
56
|
*/
|
|
81
57
|
export interface AzureBotAuthorizationOptions {
|
|
82
58
|
/**
|
|
@@ -84,49 +60,50 @@ export interface AzureBotAuthorizationOptions {
|
|
|
84
60
|
* This property is optional and should not be set when configuring this handler.
|
|
85
61
|
* It is included here for completeness and type safety.
|
|
86
62
|
*/
|
|
87
|
-
type?: undefined
|
|
63
|
+
type?: 'AzureBotUserAuthorization' | undefined
|
|
88
64
|
/**
|
|
89
65
|
* Connection name for the auth provider.
|
|
90
|
-
* @remarks
|
|
91
|
-
* When using environment variables, this can be set using the `${authHandlerId}_connectionName` variable.
|
|
92
66
|
*/
|
|
93
|
-
|
|
67
|
+
azureBotOAuthConnectionName?: string,
|
|
94
68
|
/**
|
|
95
69
|
* Title to display on auth cards/UI.
|
|
96
|
-
* @remarks
|
|
97
|
-
* When using environment variables, this can be set using the `${authHandlerId}_connectionTitle` variable.
|
|
98
70
|
*/
|
|
99
71
|
title?: string,
|
|
100
72
|
/**
|
|
101
73
|
* Text to display on auth cards/UI.
|
|
102
|
-
* @remarks
|
|
103
|
-
* When using environment variables, this can be set using the `${authHandlerId}_connectionText` variable.
|
|
104
74
|
*/
|
|
105
75
|
text?: string,
|
|
106
76
|
/**
|
|
107
77
|
* Maximum number of attempts for entering the magic code. Defaults to 2.
|
|
108
|
-
* @remarks
|
|
109
|
-
* When using environment variables, this can be set using the `${authHandlerId}_maxAttempts` variable.
|
|
110
78
|
*/
|
|
111
|
-
|
|
79
|
+
invalidSignInRetryMax?: number
|
|
80
|
+
/**
|
|
81
|
+
* Message displayed when an invalid code is entered.
|
|
82
|
+
* Use `{code}` as a placeholder to display the entered code.
|
|
83
|
+
* Defaults to: 'The code entered is invalid. Please sign-in again to continue.'
|
|
84
|
+
*/
|
|
85
|
+
invalidSignInRetryMessage?: string
|
|
112
86
|
/**
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
* - `${authHandlerId}_messages_invalidCode`
|
|
117
|
-
* - `${authHandlerId}_messages_invalidCodeFormat`
|
|
118
|
-
* - `${authHandlerId}_messages_maxAttemptsExceeded`
|
|
87
|
+
* Message displayed when the entered code format is invalid.
|
|
88
|
+
* Use `{attemptsLeft}` as a placeholder to display the number of attempts left.
|
|
89
|
+
* Defaults to: 'Please enter a valid **6-digit** code format (_e.g. 123456_).\r\n**{attemptsLeft} attempt(s) left...**'
|
|
119
90
|
*/
|
|
120
|
-
|
|
91
|
+
invalidSignInRetryMessageFormat?: string
|
|
121
92
|
/**
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
* - `${authHandlerId}_obo_connection`
|
|
126
|
-
* - `${authHandlerId}_obo_scopes` (comma-separated values, e.g. `scope1,scope2`)
|
|
93
|
+
* Message displayed when the maximum number of attempts is exceeded.
|
|
94
|
+
* Use `{maxAttempts}` as a placeholder to display the maximum number of attempts.
|
|
95
|
+
* Defaults to: 'You have exceeded the maximum number of sign-in attempts ({maxAttempts}).'
|
|
127
96
|
*/
|
|
128
|
-
|
|
129
|
-
|
|
97
|
+
invalidSignInRetryMaxExceededMessage?: string
|
|
98
|
+
/**
|
|
99
|
+
* Connection name to use for on-behalf-of token acquisition.
|
|
100
|
+
*/
|
|
101
|
+
oboConnectionName?: string
|
|
102
|
+
/**
|
|
103
|
+
* Scopes to request for on-behalf-of token acquisition.
|
|
104
|
+
* @remarks When set via environment variable, use comma or space-separated values (e.g. `scope1,scope2` or `scope1 scope2`).
|
|
105
|
+
*/
|
|
106
|
+
oboScopes?: string[]
|
|
130
107
|
/**
|
|
131
108
|
* Option to enable SSO when authenticating using Azure Active Directory (AAD). Defaults to true.
|
|
132
109
|
*/
|
|
@@ -157,61 +134,43 @@ interface SignInFailureValue {
|
|
|
157
134
|
* Default implementation of an authorization handler using Azure Bot Service.
|
|
158
135
|
*/
|
|
159
136
|
export class AzureBotAuthorization implements AuthorizationHandler {
|
|
160
|
-
private _options: AzureBotAuthorizationOptions
|
|
161
137
|
private _onSuccess?: Parameters<AuthorizationHandler['onSuccess']>[0]
|
|
162
138
|
private _onFailure?: Parameters<AuthorizationHandler['onFailure']>[0]
|
|
163
139
|
|
|
164
140
|
/**
|
|
165
141
|
* Creates an instance of the AzureBotAuthorization.
|
|
166
142
|
* @param id The unique identifier for the handler.
|
|
167
|
-
* @param options The settings for the handler.
|
|
168
|
-
* @param
|
|
143
|
+
* @param options The settings for the handler (must be fully resolved).
|
|
144
|
+
* @param settings The authorization handler settings.
|
|
169
145
|
*/
|
|
170
|
-
constructor (public readonly id: string, options: AzureBotAuthorizationOptions, private settings: AzureBotAuthorizationSettings) {
|
|
146
|
+
constructor (public readonly id: string, private options: AzureBotAuthorizationOptions, private settings: AzureBotAuthorizationSettings) {
|
|
171
147
|
if (!this.settings.storage) {
|
|
172
|
-
throw
|
|
148
|
+
throw ExceptionHelper.generateException(Error, Errors.StorageOptionNotAvailable)
|
|
173
149
|
}
|
|
174
150
|
|
|
175
151
|
if (!this.settings.connections) {
|
|
176
|
-
throw
|
|
152
|
+
throw ExceptionHelper.generateException(Error, Errors.ConnectionsOptionNotAvailable)
|
|
177
153
|
}
|
|
178
154
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Loads and validates the authorization handler options.
|
|
184
|
-
*/
|
|
185
|
-
private loadOptions (settings: AzureBotAuthorizationOptions) {
|
|
186
|
-
const result: AzureBotAuthorizationOptions = {
|
|
187
|
-
name: settings.name ?? (process.env[`${this.id}_connectionName`]),
|
|
188
|
-
title: settings.title ?? (process.env[`${this.id}_connectionTitle`]) ?? 'Sign-in',
|
|
189
|
-
text: settings.text ?? (process.env[`${this.id}_connectionText`]) ?? 'Please sign-in to continue',
|
|
190
|
-
maxAttempts: settings.maxAttempts ?? parseInt(process.env[`${this.id}_maxAttempts`]!),
|
|
191
|
-
messages: {
|
|
192
|
-
invalidCode: settings.messages?.invalidCode ?? process.env[`${this.id}_messages_invalidCode`],
|
|
193
|
-
invalidCodeFormat: settings.messages?.invalidCodeFormat ?? process.env[`${this.id}_messages_invalidCodeFormat`],
|
|
194
|
-
maxAttemptsExceeded: settings.messages?.maxAttemptsExceeded ?? process.env[`${this.id}_messages_maxAttemptsExceeded`],
|
|
195
|
-
},
|
|
196
|
-
obo: {
|
|
197
|
-
connection: settings.obo?.connection ?? process.env[`${this.id}_obo_connection`],
|
|
198
|
-
scopes: settings.obo?.scopes ?? this.loadScopes(process.env[`${this.id}_obo_scopes`]),
|
|
199
|
-
},
|
|
200
|
-
enableSso: process.env[`${this.id}_enableSso`] !== 'false' // default value is true
|
|
155
|
+
if (!options.azureBotOAuthConnectionName) {
|
|
156
|
+
throw ExceptionHelper.generateException(Error, Errors.AzureBotOAuthConnectionNameRequired)
|
|
201
157
|
}
|
|
158
|
+
}
|
|
202
159
|
|
|
203
|
-
|
|
204
|
-
throw new Error(this.prefix(`The 'name' property or '${this.id}_connectionName' env variable is required to initialize the handler.`))
|
|
205
|
-
}
|
|
160
|
+
readonly type = 'azurebot'
|
|
206
161
|
|
|
207
|
-
|
|
162
|
+
/**
|
|
163
|
+
* The OBO scopes configured for this handler.
|
|
164
|
+
*/
|
|
165
|
+
get scopes (): string[] | undefined {
|
|
166
|
+
return this.options.oboScopes
|
|
208
167
|
}
|
|
209
168
|
|
|
210
169
|
/**
|
|
211
170
|
* Maximum number of attempts for magic code entry.
|
|
212
171
|
*/
|
|
213
172
|
private get maxAttempts (): number {
|
|
214
|
-
const attempts = this.
|
|
173
|
+
const attempts = this.options.invalidSignInRetryMax
|
|
215
174
|
const result = typeof attempts === 'number' && Number.isFinite(attempts) ? Math.round(attempts) : NaN
|
|
216
175
|
return result > 0 ? result : DEFAULT_SIGN_IN_ATTEMPTS
|
|
217
176
|
}
|
|
@@ -239,22 +198,21 @@ export class AzureBotAuthorization implements AuthorizationHandler {
|
|
|
239
198
|
* @returns The token response containing the token or undefined if not available.
|
|
240
199
|
*/
|
|
241
200
|
async token (context: TurnContext, options?: AuthorizationHandlerTokenOptions): Promise<TokenResponse> {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
if (!token?.trim()) {
|
|
245
|
-
const { activity } = context
|
|
201
|
+
const oboScopes = options?.scopes && options.scopes.length > 0 ? options.scopes : this.options.oboScopes
|
|
246
202
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
const
|
|
250
|
-
token =
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
if (!token?.trim()) {
|
|
254
|
-
return { token: undefined }
|
|
203
|
+
// OBO token acquisition
|
|
204
|
+
if (oboScopes && oboScopes.length > 0) {
|
|
205
|
+
const oboConnection = options?.connection ?? this.options.oboConnectionName
|
|
206
|
+
const token = await this.getOBOToken(context, oboConnection, oboScopes)
|
|
207
|
+
return { token }
|
|
255
208
|
}
|
|
256
209
|
|
|
257
|
-
|
|
210
|
+
// Regular token acquisition
|
|
211
|
+
return trace(AuthorizationTraceDefinitions.azureBotToken, async ({ record }) => {
|
|
212
|
+
record({ handlerId: this.id, connectionName: this.options.azureBotOAuthConnectionName })
|
|
213
|
+
const token = await this.getBaseToken(context)
|
|
214
|
+
return { token }
|
|
215
|
+
})
|
|
258
216
|
}
|
|
259
217
|
|
|
260
218
|
/**
|
|
@@ -263,18 +221,22 @@ export class AzureBotAuthorization implements AuthorizationHandler {
|
|
|
263
221
|
* @returns True if the signout was successful, false otherwise.
|
|
264
222
|
*/
|
|
265
223
|
async signout (context: TurnContext): Promise<boolean> {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
224
|
+
return trace(AuthorizationTraceDefinitions.azureBotSignout, async ({ record }) => {
|
|
225
|
+
const user = context.activity.from?.id
|
|
226
|
+
const channel = context.activity.channelId
|
|
227
|
+
const connection = this.options.azureBotOAuthConnectionName!
|
|
269
228
|
|
|
270
|
-
|
|
271
|
-
throw new Error(this.prefix('Both \'activity.channelId\' and \'activity.from.id\' are required to perform signout.'))
|
|
272
|
-
}
|
|
229
|
+
record({ handlerId: this.id, connectionName: connection, channelId: channel ?? 'unknown' })
|
|
273
230
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
231
|
+
if (!channel || !user) {
|
|
232
|
+
throw ExceptionHelper.generateException(Error, Errors.ChannelIdAndFromIdRequiredForSignout)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
logger.debug(this.prefix(`Signing out User '${user}' from => Channel: '${channel}', Connection: '${connection}'`), context.activity)
|
|
236
|
+
const userTokenClient = await this.getUserTokenClient(context)
|
|
237
|
+
await userTokenClient.signOut(user, connection, channel)
|
|
238
|
+
return true
|
|
239
|
+
})
|
|
278
240
|
}
|
|
279
241
|
|
|
280
242
|
/**
|
|
@@ -284,124 +246,180 @@ export class AzureBotAuthorization implements AuthorizationHandler {
|
|
|
284
246
|
* @returns The status of the sign-in attempt.
|
|
285
247
|
*/
|
|
286
248
|
async signin (context: TurnContext, active?: AzureBotActiveHandler): Promise<AuthorizationHandlerStatus> {
|
|
287
|
-
|
|
288
|
-
|
|
249
|
+
return trace(AuthorizationTraceDefinitions.azureBotSignin, async ({ record, actions }) => {
|
|
250
|
+
const reason = { message: '' }
|
|
251
|
+
let status: AuthorizationHandlerStatus | undefined
|
|
252
|
+
const { activity } = context
|
|
253
|
+
const [category] = activity.name?.split('/') ?? [Category.UNKNOWN]
|
|
254
|
+
|
|
255
|
+
const storage = new HandlerStorage<AzureBotActiveHandler>(this.settings.storage, context)
|
|
256
|
+
|
|
257
|
+
try {
|
|
258
|
+
if (!active) {
|
|
259
|
+
status = await this.setToken(storage, context, undefined, undefined, reason)
|
|
260
|
+
return status
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
logger.debug(this.prefix('Sign-in active session detected'), active.activity)
|
|
264
|
+
|
|
265
|
+
if (active.attemptsLeft <= 0) {
|
|
266
|
+
reason.message = 'Maximum sign-in attempts exceeded'
|
|
267
|
+
logger.warn(this.prefix(reason.message), activity)
|
|
268
|
+
await context.sendActivity(MessageFactory.text(this.messages.maxAttemptsExceeded(this.maxAttempts)))
|
|
269
|
+
status = AuthorizationHandlerStatus.REJECTED
|
|
270
|
+
return status
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (category === Category.SIGNIN) {
|
|
274
|
+
await storage.write({ ...active, category })
|
|
275
|
+
status = await this.handleSignInActivities(context, reason)
|
|
276
|
+
if (status !== AuthorizationHandlerStatus.IGNORED) {
|
|
277
|
+
return status
|
|
278
|
+
}
|
|
279
|
+
} else if (active.category === Category.SIGNIN && activity.channelId === Channels.Msteams) {
|
|
280
|
+
// Specific to MS Teams, M365 does not send signin/verifyState when user consent is required.
|
|
281
|
+
// This is only for safety in case of unexpected behaviors during the MS Teams sign-in process,
|
|
282
|
+
// e.g., user interrupts the flow by clicking the Consent Cancel button.
|
|
283
|
+
reason.message = 'The incoming activity will be revalidated due to a change in the sign-in flow'
|
|
284
|
+
logger.warn(this.prefix(reason.message), activity)
|
|
285
|
+
status = AuthorizationHandlerStatus.REVALIDATE
|
|
286
|
+
return status
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const verification = await this.codeVerification(storage, context, active, reason)
|
|
290
|
+
status = verification.status
|
|
291
|
+
if (status !== AuthorizationHandlerStatus.APPROVED) {
|
|
292
|
+
return status
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
try {
|
|
296
|
+
const result = await this.setToken(storage, context, active, verification.code, reason)
|
|
297
|
+
status = result
|
|
298
|
+
if (result !== AuthorizationHandlerStatus.APPROVED) {
|
|
299
|
+
await sendInvokeResponse(context, { status: 404 })
|
|
300
|
+
return result
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
await sendInvokeResponse(context, { status: 200 })
|
|
304
|
+
await this._onSuccess?.(context)
|
|
305
|
+
return result
|
|
306
|
+
} catch (error) {
|
|
307
|
+
await sendInvokeResponse(context, { status: 500 })
|
|
308
|
+
if (error instanceof Error) {
|
|
309
|
+
error.message = this.prefix(error.message)
|
|
310
|
+
}
|
|
311
|
+
throw error
|
|
312
|
+
}
|
|
313
|
+
} finally {
|
|
314
|
+
await actions.link(storage)
|
|
315
|
+
record({
|
|
316
|
+
handlerId: this.id,
|
|
317
|
+
status: status ?? 'unknown',
|
|
318
|
+
statusReason: reason.message,
|
|
319
|
+
connectionName: this.options.azureBotOAuthConnectionName ?? 'unknown'
|
|
320
|
+
})
|
|
321
|
+
}
|
|
322
|
+
})
|
|
323
|
+
}
|
|
289
324
|
|
|
290
|
-
|
|
325
|
+
/**
|
|
326
|
+
* Retrieves the base token from the turn state or the user token client.
|
|
327
|
+
* @param context The turn context.
|
|
328
|
+
* @returns The token string or undefined if not available.
|
|
329
|
+
*/
|
|
330
|
+
private async getBaseToken (context: TurnContext) {
|
|
331
|
+
const { token } = this.getContext(context)
|
|
291
332
|
|
|
292
|
-
if (!
|
|
293
|
-
|
|
333
|
+
if (!token?.trim()) {
|
|
334
|
+
const { activity } = context
|
|
335
|
+
const userTokenClient = await this.getUserTokenClient(context)
|
|
336
|
+
// Using getTokenOrSignInResource instead of getUserToken to avoid HTTP 404 errors.
|
|
337
|
+
const { tokenResponse } = await userTokenClient.getTokenOrSignInResource(activity.from?.id!, this.options.azureBotOAuthConnectionName!, activity.channelId!, activity.getConversationReference(), activity.relatesTo!, '')
|
|
338
|
+
return tokenResponse?.token
|
|
294
339
|
}
|
|
295
340
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
if (active.attemptsLeft <= 0) {
|
|
299
|
-
logger.warn(this.prefix('Maximum sign-in attempts exceeded'), activity)
|
|
300
|
-
await context.sendActivity(MessageFactory.text(this.messages.maxAttemptsExceeded(this.maxAttempts)))
|
|
301
|
-
return AuthorizationHandlerStatus.REJECTED
|
|
302
|
-
}
|
|
341
|
+
return token
|
|
342
|
+
}
|
|
303
343
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
344
|
+
/**
|
|
345
|
+
* Acquires an on-behalf-of token for the user based on the provided scopes and connection.
|
|
346
|
+
*/
|
|
347
|
+
private async getOBOToken (context: TurnContext, oboConnection: string | undefined, oboScopes: string[]) {
|
|
348
|
+
return trace(AuthorizationTraceDefinitions.azureBotOBOToken, async ({ record }) => {
|
|
349
|
+
record({ handlerId: this.id, connectionName: oboConnection, authScopes: oboScopes })
|
|
350
|
+
const token = await this.getBaseToken(context)
|
|
351
|
+
if (!token) {
|
|
352
|
+
return
|
|
309
353
|
}
|
|
310
|
-
} else if (active.category === Category.SIGNIN && activity.channelId === Channels.Msteams) {
|
|
311
|
-
// Specific to MS Teams, M365 does not send signin/verifyState when user consent is required.
|
|
312
|
-
// This is only for safety in case of unexpected behaviors during the MS Teams sign-in process,
|
|
313
|
-
// e.g., user interrupts the flow by clicking the Consent Cancel button.
|
|
314
|
-
logger.warn(this.prefix('The incoming activity will be revalidated due to a change in the sign-in flow'), activity)
|
|
315
|
-
return AuthorizationHandlerStatus.REVALIDATE
|
|
316
|
-
}
|
|
317
354
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
return status
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
try {
|
|
324
|
-
const result = await this.setToken(storage, context, active, code)
|
|
325
|
-
if (result !== AuthorizationHandlerStatus.APPROVED) {
|
|
326
|
-
await this.sendInvokeResponse(context, { status: 404 })
|
|
327
|
-
return result
|
|
355
|
+
if (!this.isExchangeable(token)) {
|
|
356
|
+
throw ExceptionHelper.generateException(Error, Errors.AzureBotConnectionTokenNotExchangeable, undefined, { connectionName: this.options.azureBotOAuthConnectionName! })
|
|
328
357
|
}
|
|
329
358
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
359
|
+
try {
|
|
360
|
+
const provider = oboConnection ? this.settings.connections.getConnection(oboConnection) : this.settings.connections.getDefaultConnection()
|
|
361
|
+
// Record the connection name again in case it changes.
|
|
362
|
+
record({ connectionName: provider?.connectionSettings?.connectionName ?? oboConnection })
|
|
363
|
+
const newToken = await provider.acquireTokenOnBehalfOf(oboScopes, token)
|
|
364
|
+
logger.debug(this.prefix('Successfully acquired on-behalf-of token'), { connection: oboConnection, scopes: oboScopes })
|
|
365
|
+
return newToken
|
|
366
|
+
} catch (error) {
|
|
367
|
+
logger.error(this.prefix('Failed to exchange on-behalf-of token'), { connection: oboConnection, scopes: oboScopes }, error)
|
|
368
|
+
throw error
|
|
337
369
|
}
|
|
338
|
-
|
|
339
|
-
}
|
|
370
|
+
})
|
|
340
371
|
}
|
|
341
372
|
|
|
342
373
|
/**
|
|
343
|
-
*
|
|
374
|
+
* Checks if a token is exchangeable for an on-behalf-of flow.
|
|
344
375
|
*/
|
|
345
|
-
private
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
if (!oboScopes || oboScopes.length === 0) {
|
|
350
|
-
return { token }
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
if (!this.isExchangeable(token)) {
|
|
354
|
-
throw new Error(this.prefix('The current token is not exchangeable for an on-behalf-of flow. Ensure the token audience starts with \'api://\'.'))
|
|
376
|
+
private isExchangeable (token: string | undefined): boolean {
|
|
377
|
+
if (!token || typeof token !== 'string') {
|
|
378
|
+
return false
|
|
355
379
|
}
|
|
356
380
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
logger.debug(this.prefix('Successfully acquired on-behalf-of token'), { connection: oboConnection, scopes: oboScopes })
|
|
361
|
-
return { token: newToken }
|
|
362
|
-
} catch (error) {
|
|
363
|
-
logger.error(this.prefix('Failed to exchange on-behalf-of token'), { connection: oboConnection, scopes: oboScopes }, error)
|
|
364
|
-
return { token: undefined }
|
|
381
|
+
const payload = jwt.decode(token)
|
|
382
|
+
if (!payload || typeof payload === 'string') {
|
|
383
|
+
return false
|
|
365
384
|
}
|
|
366
|
-
}
|
|
367
385
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
*/
|
|
371
|
-
private isExchangeable (token: string | undefined): boolean {
|
|
372
|
-
if (!token || typeof token !== 'string') {
|
|
386
|
+
const appid = payload.azp ?? payload.appid
|
|
387
|
+
if (typeof appid !== 'string' || appid.length === 0) {
|
|
373
388
|
return false
|
|
374
389
|
}
|
|
375
|
-
|
|
390
|
+
|
|
376
391
|
const audiences = Array.isArray(payload.aud) ? payload.aud : [payload.aud]
|
|
377
|
-
return audiences.some(aud => typeof aud === 'string' && aud.
|
|
392
|
+
return audiences.some(aud => typeof aud === 'string' && aud.includes(appid))
|
|
378
393
|
}
|
|
379
394
|
|
|
380
395
|
/**
|
|
381
396
|
* Sets the token from the token response or initiates the sign-in flow.
|
|
382
397
|
*/
|
|
383
|
-
private async setToken (storage: HandlerStorage<AzureBotActiveHandler>, context: TurnContext, active?: AzureBotActiveHandler, code?: string): Promise<AuthorizationHandlerStatus> {
|
|
398
|
+
private async setToken (storage: HandlerStorage<AzureBotActiveHandler>, context: TurnContext, active?: AzureBotActiveHandler, code?: string, reason: { message: string } = { message: '' }): Promise<AuthorizationHandlerStatus> {
|
|
384
399
|
const { activity } = context
|
|
385
400
|
|
|
386
401
|
const userTokenClient = await this.getUserTokenClient(context)
|
|
387
|
-
const { tokenResponse, signInResource } = await userTokenClient.getTokenOrSignInResource(activity.from?.id!, this.
|
|
402
|
+
const { tokenResponse, signInResource } = await userTokenClient.getTokenOrSignInResource(activity.from?.id!, this.options.azureBotOAuthConnectionName!, activity.channelId!, activity.getConversationReference(), activity.relatesTo!, code ?? '')
|
|
388
403
|
|
|
389
|
-
if (!tokenResponse && active) {
|
|
390
|
-
|
|
391
|
-
|
|
404
|
+
if (!tokenResponse && active && code) {
|
|
405
|
+
reason.message = 'Invalid code entered. Restarting sign-in flow'
|
|
406
|
+
logger.warn(this.prefix(reason.message), activity)
|
|
407
|
+
await context.sendActivity(MessageFactory.text(this.messages.invalidCode(code)))
|
|
392
408
|
return AuthorizationHandlerStatus.REJECTED
|
|
393
409
|
}
|
|
394
410
|
|
|
395
411
|
if (!tokenResponse) {
|
|
396
|
-
|
|
412
|
+
reason.message = 'Cannot find token. Sending sign-in card'
|
|
413
|
+
logger.debug(this.prefix(reason.message), activity)
|
|
397
414
|
|
|
398
|
-
const oCard = CardFactory.oauthCard(this.
|
|
415
|
+
const oCard = CardFactory.oauthCard(this.options.azureBotOAuthConnectionName!, this.options.title!, this.options.text!, signInResource, this.options.enableSso)
|
|
399
416
|
await context.sendActivity(MessageFactory.attachment(oCard))
|
|
400
417
|
await storage.write({ activity, id: this.id, ...(active ?? {}), attemptsLeft: this.maxAttempts })
|
|
401
418
|
return AuthorizationHandlerStatus.PENDING
|
|
402
419
|
}
|
|
403
420
|
|
|
404
|
-
|
|
421
|
+
reason.message = 'Successfully acquired token'
|
|
422
|
+
logger.debug(this.prefix(reason.message), activity)
|
|
405
423
|
this.setContext(context, { token: tokenResponse.token })
|
|
406
424
|
return AuthorizationHandlerStatus.APPROVED
|
|
407
425
|
}
|
|
@@ -409,7 +427,7 @@ export class AzureBotAuthorization implements AuthorizationHandler {
|
|
|
409
427
|
/**
|
|
410
428
|
* Handles sign-in related activities.
|
|
411
429
|
*/
|
|
412
|
-
private async handleSignInActivities (context: TurnContext): Promise<AuthorizationHandlerStatus> {
|
|
430
|
+
private async handleSignInActivities (context: TurnContext, reason: { message: string }): Promise<AuthorizationHandlerStatus> {
|
|
413
431
|
const { activity } = context
|
|
414
432
|
|
|
415
433
|
// Ignore signin/verifyState here (handled in codeVerification).
|
|
@@ -424,69 +442,71 @@ export class AzureBotAuthorization implements AuthorizationHandler {
|
|
|
424
442
|
const tokenExchangeRequest: TokenExchangeRequest = { token: tokenExchangeInvokeRequest?.token }
|
|
425
443
|
|
|
426
444
|
if (!tokenExchangeRequest?.token) {
|
|
427
|
-
|
|
428
|
-
await
|
|
445
|
+
reason.message = 'The Agent received an InvokeActivity that is missing a TokenExchangeInvokeRequest value. This is required to be sent with the InvokeActivity.'
|
|
446
|
+
await sendInvokeResponse<TokenExchangeInvokeResponse>(context, {
|
|
429
447
|
status: 400,
|
|
430
|
-
body: { connectionName: this.
|
|
448
|
+
body: { connectionName: this.options.azureBotOAuthConnectionName!, failureDetail: reason.message }
|
|
431
449
|
})
|
|
432
|
-
logger.error(this.prefix(reason))
|
|
433
|
-
await this._onFailure?.(context, reason)
|
|
450
|
+
logger.error(this.prefix(reason.message))
|
|
451
|
+
await this._onFailure?.(context, reason.message)
|
|
434
452
|
return AuthorizationHandlerStatus.REJECTED
|
|
435
453
|
}
|
|
436
454
|
|
|
437
|
-
if (tokenExchangeInvokeRequest.connectionName !== this.
|
|
438
|
-
|
|
439
|
-
await
|
|
455
|
+
if (tokenExchangeInvokeRequest.connectionName !== this.options.azureBotOAuthConnectionName) {
|
|
456
|
+
reason.message = `The Agent received an InvokeActivity with a TokenExchangeInvokeRequest for a different connection name ('${tokenExchangeInvokeRequest.connectionName}') than expected ('${this.options.azureBotOAuthConnectionName}').`
|
|
457
|
+
await sendInvokeResponse<TokenExchangeInvokeResponse>(context, {
|
|
440
458
|
status: 400,
|
|
441
|
-
body: { id: tokenExchangeInvokeRequest.id, connectionName: this.
|
|
459
|
+
body: { id: tokenExchangeInvokeRequest.id, connectionName: this.options.azureBotOAuthConnectionName!, failureDetail: reason.message }
|
|
442
460
|
})
|
|
443
|
-
logger.error(this.prefix(reason))
|
|
444
|
-
await this._onFailure?.(context, reason)
|
|
461
|
+
logger.error(this.prefix(reason.message))
|
|
462
|
+
await this._onFailure?.(context, reason.message)
|
|
445
463
|
return AuthorizationHandlerStatus.REJECTED
|
|
446
464
|
}
|
|
447
465
|
|
|
448
|
-
const { token } = await userTokenClient.exchangeTokenAsync(activity.from?.id!, this.
|
|
466
|
+
const { token } = await userTokenClient.exchangeTokenAsync(activity.from?.id!, this.options.azureBotOAuthConnectionName!, activity.channelId!, tokenExchangeRequest)
|
|
449
467
|
if (!token) {
|
|
450
|
-
|
|
451
|
-
await
|
|
468
|
+
reason.message = 'The MS Teams token service didn\'t send back the exchanged token. Waiting for MS Teams to send another signin/tokenExchange request. After multiple failed attempts, the user will be asked to enter the magic code.'
|
|
469
|
+
await sendInvokeResponse<TokenExchangeInvokeResponse>(context, {
|
|
452
470
|
status: 412,
|
|
453
|
-
body: { id: tokenExchangeInvokeRequest.id, connectionName: this.
|
|
471
|
+
body: { id: tokenExchangeInvokeRequest.id, connectionName: this.options.azureBotOAuthConnectionName, failureDetail: reason.message }
|
|
454
472
|
})
|
|
455
|
-
logger.debug(this.prefix(reason))
|
|
473
|
+
logger.debug(this.prefix(reason.message))
|
|
456
474
|
return AuthorizationHandlerStatus.PENDING
|
|
457
475
|
}
|
|
458
476
|
|
|
459
|
-
await
|
|
477
|
+
await sendInvokeResponse<TokenExchangeInvokeResponse>(context, {
|
|
460
478
|
status: 200,
|
|
461
|
-
body: { id: tokenExchangeInvokeRequest.id, connectionName: this.
|
|
479
|
+
body: { id: tokenExchangeInvokeRequest.id, connectionName: this.options.azureBotOAuthConnectionName! }
|
|
462
480
|
})
|
|
463
|
-
|
|
481
|
+
reason.message = 'Successfully exchanged token'
|
|
482
|
+
logger.debug(this.prefix(reason.message))
|
|
464
483
|
this.setContext(context, { token })
|
|
465
484
|
await this._onSuccess?.(context)
|
|
466
485
|
return AuthorizationHandlerStatus.APPROVED
|
|
467
486
|
}
|
|
468
487
|
|
|
469
488
|
if (activity.name === 'signin/failure') {
|
|
470
|
-
await
|
|
471
|
-
|
|
489
|
+
await sendInvokeResponse(context, { status: 200 })
|
|
490
|
+
reason.message = 'Failed to sign-in'
|
|
472
491
|
const value = activity.value as SignInFailureValue
|
|
473
|
-
logger.error(this.prefix(reason), value, activity)
|
|
492
|
+
logger.error(this.prefix(reason.message), value, activity)
|
|
474
493
|
if (this._onFailure) {
|
|
475
|
-
await this._onFailure(context, value.message || reason)
|
|
494
|
+
await this._onFailure(context, value.message || reason.message)
|
|
476
495
|
} else {
|
|
477
|
-
await context.sendActivity(MessageFactory.text(`${reason}. Please try again.`))
|
|
496
|
+
await context.sendActivity(MessageFactory.text(`${reason.message}. Please try again.`))
|
|
478
497
|
}
|
|
479
498
|
return AuthorizationHandlerStatus.REJECTED
|
|
480
499
|
}
|
|
481
500
|
|
|
482
|
-
|
|
501
|
+
reason.message = `Unknown sign-in activity name: ${activity.name}`
|
|
502
|
+
logger.error(this.prefix(reason.message), activity)
|
|
483
503
|
return AuthorizationHandlerStatus.REJECTED
|
|
484
504
|
}
|
|
485
505
|
|
|
486
506
|
/**
|
|
487
507
|
* Verifies the magic code provided by the user.
|
|
488
508
|
*/
|
|
489
|
-
private async codeVerification (storage: HandlerStorage<AzureBotActiveHandler>, context: TurnContext, active?: AzureBotActiveHandler): Promise<{ status: AuthorizationHandlerStatus, code?: string }> {
|
|
509
|
+
private async codeVerification (storage: HandlerStorage<AzureBotActiveHandler>, context: TurnContext, active?: AzureBotActiveHandler, reason: { message: string } = { message: '' }): Promise<{ status: AuthorizationHandlerStatus, code?: string }> {
|
|
490
510
|
if (!active) {
|
|
491
511
|
logger.debug(this.prefix('No active session found. Skipping code verification.'), context.activity)
|
|
492
512
|
return { status: AuthorizationHandlerStatus.IGNORED }
|
|
@@ -502,20 +522,23 @@ export class AzureBotAuthorization implements AuthorizationHandler {
|
|
|
502
522
|
}
|
|
503
523
|
|
|
504
524
|
if (state === 'CancelledByUser') {
|
|
505
|
-
await
|
|
506
|
-
|
|
525
|
+
await sendInvokeResponse(context, { status: 200 })
|
|
526
|
+
reason.message = 'Sign-in process was cancelled by the user'
|
|
527
|
+
logger.warn(this.prefix(reason.message), activity)
|
|
507
528
|
return { status: AuthorizationHandlerStatus.REJECTED }
|
|
508
529
|
}
|
|
509
530
|
|
|
510
531
|
if (!state?.match(/^\d{6}$/)) {
|
|
511
|
-
|
|
532
|
+
reason.message = `Invalid magic code entered. Attempts left: ${active.attemptsLeft}`
|
|
533
|
+
logger.warn(this.prefix(reason.message), activity)
|
|
512
534
|
await context.sendActivity(MessageFactory.text(this.messages.invalidCodeFormat(active.attemptsLeft)))
|
|
513
535
|
await storage.write({ ...active, attemptsLeft: active.attemptsLeft - 1 })
|
|
514
536
|
return { status: AuthorizationHandlerStatus.PENDING }
|
|
515
537
|
}
|
|
516
538
|
|
|
517
|
-
await
|
|
518
|
-
|
|
539
|
+
await sendInvokeResponse(context, { status: 200 })
|
|
540
|
+
reason.message = 'Code verification successful'
|
|
541
|
+
logger.debug(this.prefix(reason.message), activity)
|
|
519
542
|
return { status: AuthorizationHandlerStatus.APPROVED, code: state }
|
|
520
543
|
}
|
|
521
544
|
|
|
@@ -542,25 +565,11 @@ export class AzureBotAuthorization implements AuthorizationHandler {
|
|
|
542
565
|
private async getUserTokenClient (context: TurnContext): Promise<UserTokenClient> {
|
|
543
566
|
const userTokenClient = context.turnState.get<UserTokenClient>(context.adapter.UserTokenClientKey)
|
|
544
567
|
if (!userTokenClient) {
|
|
545
|
-
throw
|
|
568
|
+
throw ExceptionHelper.generateException(Error, Errors.UserTokenClientNotAvailable)
|
|
546
569
|
}
|
|
547
570
|
return userTokenClient
|
|
548
571
|
}
|
|
549
572
|
|
|
550
|
-
/**
|
|
551
|
-
* Sends an InvokeResponse activity if the channel is Microsoft Teams, including Copilot within MS Teams.
|
|
552
|
-
*/
|
|
553
|
-
private sendInvokeResponse <T>(context: TurnContext, response: InvokeResponse<T>) {
|
|
554
|
-
if (context.activity.channelIdChannel !== Channels.Msteams) {
|
|
555
|
-
return Promise.resolve()
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
return context.sendActivity(Activity.fromObject({
|
|
559
|
-
type: ActivityTypes.InvokeResponse,
|
|
560
|
-
value: response
|
|
561
|
-
}))
|
|
562
|
-
}
|
|
563
|
-
|
|
564
573
|
/**
|
|
565
574
|
* Prefixes a message with the handler ID.
|
|
566
575
|
*/
|
|
@@ -573,29 +582,16 @@ export class AzureBotAuthorization implements AuthorizationHandler {
|
|
|
573
582
|
*/
|
|
574
583
|
private messages = {
|
|
575
584
|
invalidCode: (code: string) => {
|
|
576
|
-
const message = this.
|
|
585
|
+
const message = this.options.invalidSignInRetryMessage ?? 'Invalid **{code}** code entered. Please try again with a new sign-in request.'
|
|
577
586
|
return message.replaceAll('{code}', code)
|
|
578
587
|
},
|
|
579
588
|
invalidCodeFormat: (attemptsLeft: number) => {
|
|
580
|
-
const message = this.
|
|
589
|
+
const message = this.options.invalidSignInRetryMessageFormat ?? 'Please enter a valid **6-digit** code format (_e.g. 123456_).\r\n**{attemptsLeft} attempt(s) left...**'
|
|
581
590
|
return message.replaceAll('{attemptsLeft}', attemptsLeft.toString())
|
|
582
591
|
},
|
|
583
592
|
maxAttemptsExceeded: (maxAttempts: number) => {
|
|
584
|
-
const message = this.
|
|
593
|
+
const message = this.options.invalidSignInRetryMaxExceededMessage ?? 'You have exceeded the maximum number of sign-in attempts ({maxAttempts}). Please try again with a new sign-in request.'
|
|
585
594
|
return message.replaceAll('{maxAttempts}', maxAttempts.toString())
|
|
586
595
|
},
|
|
587
596
|
}
|
|
588
|
-
|
|
589
|
-
/**
|
|
590
|
-
* Loads the OAuth scopes from the environment variables.
|
|
591
|
-
*/
|
|
592
|
-
private loadScopes (value:string | undefined): string[] {
|
|
593
|
-
return value?.split(',').reduce<string[]>((acc, scope) => {
|
|
594
|
-
const trimmed = scope.trim()
|
|
595
|
-
if (trimmed) {
|
|
596
|
-
acc.push(trimmed)
|
|
597
|
-
}
|
|
598
|
-
return acc
|
|
599
|
-
}, []) ?? []
|
|
600
|
-
}
|
|
601
597
|
}
|