@microsoft/agents-hosting 0.5.12-g2d752e9b13 → 0.5.19-gc1e2ea1096

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.
Files changed (58) hide show
  1. package/dist/src/app/agentApplication.d.ts +186 -20
  2. package/dist/src/app/agentApplication.js +234 -32
  3. package/dist/src/app/agentApplication.js.map +1 -1
  4. package/dist/src/app/agentApplicationBuilder.d.ts +1 -1
  5. package/dist/src/app/agentApplicationOptions.d.ts +1 -1
  6. package/dist/src/app/appRoute.d.ts +5 -0
  7. package/dist/src/app/authorization.d.ts +294 -0
  8. package/dist/src/app/authorization.js +379 -0
  9. package/dist/src/app/authorization.js.map +1 -0
  10. package/dist/src/app/index.d.ts +1 -1
  11. package/dist/src/app/index.js +1 -1
  12. package/dist/src/app/index.js.map +1 -1
  13. package/dist/src/app/streaming/streamingResponse.js +1 -1
  14. package/dist/src/app/streaming/streamingResponse.js.map +1 -1
  15. package/dist/src/auth/authConfiguration.d.ts +2 -2
  16. package/dist/src/auth/authConfiguration.js +36 -17
  17. package/dist/src/auth/authConfiguration.js.map +1 -1
  18. package/dist/src/auth/index.d.ts +1 -0
  19. package/dist/src/auth/index.js +1 -0
  20. package/dist/src/auth/index.js.map +1 -1
  21. package/dist/src/auth/jwt-middleware.js.map +1 -1
  22. package/dist/src/auth/msalTokenCredential.d.ts +10 -0
  23. package/dist/src/auth/msalTokenCredential.js +19 -0
  24. package/dist/src/auth/msalTokenCredential.js.map +1 -0
  25. package/dist/src/auth/msalTokenProvider.d.ts +1 -0
  26. package/dist/src/auth/msalTokenProvider.js +15 -0
  27. package/dist/src/auth/msalTokenProvider.js.map +1 -1
  28. package/dist/src/baseAdapter.d.ts +1 -1
  29. package/dist/src/baseAdapter.js +0 -4
  30. package/dist/src/baseAdapter.js.map +1 -1
  31. package/dist/src/cloudAdapter.d.ts +1 -0
  32. package/dist/src/cloudAdapter.js.map +1 -1
  33. package/dist/src/oauth/oAuthFlow.d.ts +53 -9
  34. package/dist/src/oauth/oAuthFlow.js +164 -35
  35. package/dist/src/oauth/oAuthFlow.js.map +1 -1
  36. package/dist/src/oauth/userTokenClient.js +4 -0
  37. package/dist/src/oauth/userTokenClient.js.map +1 -1
  38. package/package.json +4 -3
  39. package/src/app/agentApplication.ts +247 -32
  40. package/src/app/agentApplicationBuilder.ts +1 -1
  41. package/src/app/agentApplicationOptions.ts +1 -1
  42. package/src/app/appRoute.ts +6 -0
  43. package/src/app/authorization.ts +424 -0
  44. package/src/app/index.ts +1 -1
  45. package/src/app/streaming/streamingResponse.ts +1 -1
  46. package/src/auth/authConfiguration.ts +36 -19
  47. package/src/auth/index.ts +1 -0
  48. package/src/auth/jwt-middleware.ts +1 -1
  49. package/src/auth/msalTokenCredential.ts +14 -0
  50. package/src/auth/msalTokenProvider.ts +17 -1
  51. package/src/baseAdapter.ts +1 -1
  52. package/src/cloudAdapter.ts +2 -2
  53. package/src/oauth/oAuthFlow.ts +197 -35
  54. package/src/oauth/userTokenClient.ts +3 -0
  55. package/dist/src/app/oauth/authorization.d.ts +0 -88
  56. package/dist/src/app/oauth/authorization.js +0 -134
  57. package/dist/src/app/oauth/authorization.js.map +0 -1
  58. package/src/app/oauth/authorization.ts +0 -160
@@ -0,0 +1,424 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { TurnContext } from '../turnContext'
7
+ import { debug } from '../logger'
8
+ import { TurnState } from './turnState'
9
+ import { Storage } from '../storage'
10
+ import { OAuthFlow, TokenResponse } from '../oauth'
11
+ import { AuthConfiguration, loadAuthConfigFromEnv, MsalTokenProvider } from '../auth'
12
+ import jwt, { JwtPayload } from 'jsonwebtoken'
13
+ import { Activity } from '@microsoft/agents-activity'
14
+
15
+ const logger = debug('agents:authorization')
16
+
17
+ /**
18
+ * Interface representing the state of a sign-in process.
19
+ * @interface SignInState
20
+ */
21
+ export interface SignInState {
22
+ /** Optional activity to continue with after sign-in completion. */
23
+ continuationActivity?: Activity,
24
+ /** Identifier of the auth handler being used. */
25
+ handlerId?: string,
26
+ /** Whether the sign-in process has been completed. */
27
+ completed?: boolean
28
+ }
29
+
30
+ /**
31
+ * Interface defining an authorization handler for OAuth flows.
32
+ * @interface AuthHandler
33
+ */
34
+ export interface AuthHandler {
35
+ /** Connection name for the auth provider. */
36
+ name?: string,
37
+ /** The OAuth flow implementation. */
38
+ flow?: OAuthFlow,
39
+ /** Title to display on auth cards/UI. */
40
+ title?: string,
41
+ /** Text to display on auth cards/UI. */
42
+ text?: string,
43
+
44
+ cnxPrefix?: string
45
+ }
46
+
47
+ /**
48
+ * Options for configuring user authorization.
49
+ * Contains settings to configure OAuth connections.
50
+ * @interface AuthorizationHandlers
51
+ */
52
+ export interface AuthorizationHandlers extends Record<string, AuthHandler> {}
53
+
54
+ /**
55
+ * Class responsible for managing authorization and OAuth flows.
56
+ * Handles multiple OAuth providers and manages the complete authentication lifecycle.
57
+ *
58
+ * @remarks
59
+ * The Authorization class provides a centralized way to handle OAuth authentication
60
+ * flows within the agent application. It supports multiple authentication handlers,
61
+ * token exchange, on-behalf-of flows, and provides event handlers for success/failure scenarios.
62
+ *
63
+ * Key features:
64
+ * - Multiple OAuth provider support
65
+ * - Token caching and exchange
66
+ * - On-behalf-of (OBO) token flows
67
+ * - Sign-in success/failure event handling
68
+ * - Automatic configuration from environment variables
69
+ *
70
+ * Example usage:
71
+ * ```typescript
72
+ * const auth = new Authorization(storage, {
73
+ * 'microsoft': {
74
+ * name: 'Microsoft',
75
+ * title: 'Sign in with Microsoft',
76
+ * text: 'Please sign in'
77
+ * }
78
+ * });
79
+ *
80
+ * auth.onSignInSuccess(async (context, state) => {
81
+ * await context.sendActivity('Welcome! You are now signed in.');
82
+ * });
83
+ * ```
84
+ */
85
+ export class Authorization {
86
+ /**
87
+ * Dictionary of configured authentication handlers.
88
+ * @public
89
+ */
90
+ authHandlers: AuthorizationHandlers
91
+
92
+ /**
93
+ * Creates a new instance of Authorization.
94
+ *
95
+ * @param storage - The storage system to use for state management.
96
+ * @param authHandlers - Configuration for OAuth providers.
97
+ * @throws {Error} If storage is null/undefined or no auth handlers are provided.
98
+ *
99
+ * @remarks
100
+ * The constructor initializes all configured auth handlers and sets up OAuth flows.
101
+ * It automatically configures handler properties from environment variables if not provided:
102
+ * - Connection name: {handlerId}_connectionName
103
+ * - Connection title: {handlerId}_connectionTitle
104
+ * - Connection text: {handlerId}_connectionText
105
+ *
106
+ * Example usage:
107
+ * ```typescript
108
+ * const auth = new Authorization(storage, {
109
+ * 'microsoft': {
110
+ * name: 'Microsoft',
111
+ * title: 'Sign in with Microsoft'
112
+ * },
113
+ * 'google': {
114
+ * // Will use GOOGLE_connectionName from env vars
115
+ * }
116
+ * });
117
+ * ```
118
+ */
119
+ constructor (private storage: Storage, authHandlers: AuthorizationHandlers) {
120
+ if (storage === undefined || storage === null) {
121
+ throw new Error('Storage is required for UserAuthorization')
122
+ }
123
+ if (authHandlers === undefined || Object.keys(authHandlers).length === 0) {
124
+ throw new Error('The authorization does not have any auth handlers')
125
+ }
126
+ this.authHandlers = authHandlers
127
+ for (const ah in this.authHandlers) {
128
+ if (this.authHandlers![ah].name === undefined && process.env[ah + '_connectionName'] === undefined) {
129
+ throw new Error(`AuthHandler name ${ah}_connectionName not set in autorization and not found in env vars.`)
130
+ }
131
+ const currentAuthHandler = this.authHandlers![ah]
132
+ currentAuthHandler.name = currentAuthHandler.name ?? process.env[ah + '_connectionName'] as string
133
+ currentAuthHandler.title = currentAuthHandler.title ?? process.env[ah + '_connectionTitle'] as string
134
+ currentAuthHandler.text = currentAuthHandler.text ?? process.env[ah + '_connectionText'] as string
135
+ currentAuthHandler.cnxPrefix = currentAuthHandler.cnxPrefix ?? process.env[ah + '_cnxPrefix'] as string
136
+ currentAuthHandler.flow = new OAuthFlow(this.storage, currentAuthHandler.name, null!, currentAuthHandler.title, currentAuthHandler.text)
137
+ }
138
+ logger.info('Authorization handlers configured with', Object.keys(this.authHandlers).length, 'handlers')
139
+ }
140
+
141
+ /**
142
+ * Gets the token for a specific auth handler.
143
+ *
144
+ * @param context - The context object for the current turn.
145
+ * @param authHandlerId - ID of the auth handler to use.
146
+ * @returns A promise that resolves to the token response from the OAuth provider.
147
+ * @throws {Error} If the auth handler is not configured.
148
+ * @public
149
+ *
150
+ * @remarks
151
+ * This method retrieves an existing token for the specified auth handler.
152
+ * The token may be cached and will be retrieved from the OAuth provider if needed.
153
+ *
154
+ * Example usage:
155
+ * ```typescript
156
+ * const tokenResponse = await auth.getToken(context, 'microsoft');
157
+ * if (tokenResponse.token) {
158
+ * console.log('User is authenticated');
159
+ * }
160
+ * ```
161
+ */
162
+ public async getToken (context: TurnContext, authHandlerId: string): Promise<TokenResponse> {
163
+ logger.info('getToken from user token service for authHandlerId:', authHandlerId)
164
+ const authHandler = this.getAuthHandlerOrThrow(authHandlerId)
165
+ return await authHandler.flow?.getUserToken(context)!
166
+ }
167
+
168
+ /**
169
+ * Gets the auth handler by ID or throws an error if not found.
170
+ *
171
+ * @param authHandlerId - ID of the auth handler to retrieve.
172
+ * @returns The auth handler instance.
173
+ * @throws {Error} If the auth handler with the specified ID is not configured.
174
+ * @private
175
+ */
176
+ private getAuthHandlerOrThrow (authHandlerId: string): AuthHandler {
177
+ if (!Object.prototype.hasOwnProperty.call(this.authHandlers, authHandlerId)) {
178
+ throw new Error(`AuthHandler with ID ${authHandlerId} not configured`)
179
+ }
180
+ return this.authHandlers[authHandlerId]
181
+ }
182
+
183
+ /**
184
+ * Exchanges a token for a new token with different scopes.
185
+ *
186
+ * @param context - The context object for the current turn.
187
+ * @param scopes - Array of scopes to request for the new token.
188
+ * @param authHandlerId - ID of the auth handler to use.
189
+ * @returns A promise that resolves to the exchanged token response.
190
+ * @throws {Error} If the auth handler is not configured.
191
+ * @public
192
+ *
193
+ * @remarks
194
+ * This method handles token exchange scenarios, particularly for on-behalf-of (OBO) flows.
195
+ * It checks if the current token is exchangeable (e.g., has audience starting with 'api://')
196
+ * and performs the appropriate token exchange using MSAL.
197
+ *
198
+ * Example usage:
199
+ * ```typescript
200
+ * const exchangedToken = await auth.exchangeToken(
201
+ * context,
202
+ * ['https://graph.microsoft.com/.default'],
203
+ * 'microsoft'
204
+ * );
205
+ * ```
206
+ */
207
+ public async exchangeToken (context: TurnContext, scopes: string[], authHandlerId: string): Promise<TokenResponse> {
208
+ logger.info('exchangeToken from user token service for authHandlerId:', authHandlerId)
209
+ const authHandler = this.getAuthHandlerOrThrow(authHandlerId)
210
+ const tokenResponse = await authHandler.flow?.getUserToken(context)!
211
+ if (this.isExchangeable(tokenResponse.token)) {
212
+ return await this.handleObo(context, tokenResponse.token!, scopes, authHandler.cnxPrefix)
213
+ }
214
+ return tokenResponse
215
+ }
216
+
217
+ /**
218
+ * Checks if a token is exchangeable for an on-behalf-of flow.
219
+ *
220
+ * @param token - The token to check.
221
+ * @returns True if the token is exchangeable, false otherwise.
222
+ * @private
223
+ */
224
+ private isExchangeable (token: string | undefined): boolean {
225
+ if (!token || typeof token !== 'string') {
226
+ return false
227
+ }
228
+ const payload = jwt.decode(token) as JwtPayload
229
+ return payload?.aud?.indexOf('api://') === 0
230
+ }
231
+
232
+ /**
233
+ * Handles on-behalf-of token exchange using MSAL.
234
+ *
235
+ * @param context - The context object for the current turn.
236
+ * @param token - The token to exchange.
237
+ * @param scopes - Array of scopes to request for the new token.
238
+ * @returns A promise that resolves to the exchanged token response.
239
+ * @private
240
+ */
241
+ private async handleObo (context: TurnContext, token: string, scopes: string[], cnxPrefix?: string): Promise<TokenResponse> {
242
+ const msalTokenProvider = new MsalTokenProvider()
243
+ let authConfig: AuthConfiguration = context.adapter.authConfig
244
+ if (cnxPrefix) {
245
+ authConfig = loadAuthConfigFromEnv(cnxPrefix)
246
+ }
247
+ const newToken = await msalTokenProvider.acquireTokenOnBehalfOf(authConfig, scopes, token)
248
+ return { token: newToken }
249
+ }
250
+
251
+ /**
252
+ * Begins or continues an OAuth flow.
253
+ *
254
+ * @param context - The context object for the current turn.
255
+ * @param state - The state object for the current turn.
256
+ * @param authHandlerId - ID of the auth handler to use.
257
+ * @returns A promise that resolves to the token response from the OAuth provider.
258
+ * @throws {Error} If the auth handler is not configured.
259
+ * @public
260
+ *
261
+ * @remarks
262
+ * This method manages the complete OAuth authentication flow:
263
+ * - If no flow is active, it begins a new OAuth flow and shows the sign-in card
264
+ * - If a flow is active, it continues the flow and processes the authentication response
265
+ * - Handles success/failure callbacks and updates the sign-in state accordingly
266
+ *
267
+ * The method automatically manages the sign-in state and continuation activities,
268
+ * allowing the conversation to resume after successful authentication.
269
+ *
270
+ * Example usage:
271
+ * ```typescript
272
+ * const tokenResponse = await auth.beginOrContinueFlow(context, state, 'microsoft');
273
+ * if (tokenResponse && tokenResponse.token) {
274
+ * // User is now authenticated
275
+ * await context.sendActivity('Authentication successful!');
276
+ * }
277
+ * ```
278
+ */
279
+ public async beginOrContinueFlow (context: TurnContext, state: TurnState, authHandlerId: string, secRoute: boolean = true) : Promise<TokenResponse> {
280
+ const authHandler = this.getAuthHandlerOrThrow(authHandlerId)
281
+ logger.info('beginOrContinueFlow for authHandlerId:', authHandlerId)
282
+ const signInState: SignInState | undefined = state.getValue('user.__SIGNIN_STATE_') || { continuationActivity: undefined, handlerId: undefined, completed: false }
283
+ const flow = authHandler.flow!
284
+ let tokenResponse: TokenResponse | undefined
285
+ tokenResponse = await authHandler.flow?.getUserToken(context)
286
+
287
+ if (tokenResponse?.token && tokenResponse.token.length > 0) {
288
+ delete authHandler.flow?.state.eTag
289
+ authHandler.flow!.state.flowStarted = false
290
+ await authHandler.flow?.setFlowState(context, authHandler.flow.state)
291
+ if (secRoute) {
292
+ return tokenResponse!
293
+ }
294
+ }
295
+
296
+ if (flow.state === null || flow.state?.flowStarted === false || flow.state?.flowStarted === undefined) {
297
+ tokenResponse = await flow.beginFlow(context)
298
+ if (secRoute && tokenResponse?.token === undefined) {
299
+ signInState!.continuationActivity = context.activity
300
+ signInState!.handlerId = authHandlerId
301
+ state.setValue('user.__SIGNIN_STATE_', signInState)
302
+ }
303
+ } else {
304
+ tokenResponse = await flow.continueFlow(context)
305
+ if (tokenResponse && tokenResponse.token) {
306
+ if (this._signInSuccessHandler) {
307
+ await this._signInSuccessHandler(context, state, authHandlerId)
308
+ }
309
+ if (secRoute) {
310
+ state.deleteValue('user.__SIGNIN_STATE_')
311
+ }
312
+ } else {
313
+ logger.warn('Failed to complete OAuth flow, no token received')
314
+ if (this._signInFailureHandler) {
315
+ await this._signInFailureHandler(context, state, authHandlerId, 'Failed to complete the OAuth flow')
316
+ }
317
+ // signInState!.completed = false
318
+ // state.setValue('user.__SIGNIN_STATE_', signInState)
319
+ }
320
+ }
321
+ return tokenResponse!
322
+ }
323
+
324
+ /**
325
+ * Signs out the current user.
326
+ *
327
+ * @param context - The context object for the current turn.
328
+ * @param state - The state object for the current turn.
329
+ * @param authHandlerId - Optional ID of the auth handler to use for sign out. If not provided, signs out from all handlers.
330
+ * @returns A promise that resolves when sign out is complete.
331
+ * @throws {Error} If the specified auth handler is not configured.
332
+ * @public
333
+ *
334
+ * @remarks
335
+ * This method clears the user's token and resets the authentication state.
336
+ * If no specific authHandlerId is provided, it signs out from all configured handlers.
337
+ * This ensures complete cleanup of authentication state across all providers.
338
+ *
339
+ * Example usage:
340
+ * ```typescript
341
+ * // Sign out from specific handler
342
+ * await auth.signOut(context, state, 'microsoft');
343
+ *
344
+ * // Sign out from all handlers
345
+ * await auth.signOut(context, state);
346
+ * ```
347
+ */
348
+ async signOut (context: TurnContext, state: TurnState, authHandlerId?: string) : Promise<void> {
349
+ logger.info('signOut for authHandlerId:', authHandlerId)
350
+ if (authHandlerId === undefined) { // aw
351
+ for (const ah in this.authHandlers) {
352
+ const flow = this.authHandlers[ah].flow
353
+ await flow?.signOut(context)
354
+ }
355
+ } else {
356
+ const authHandler = this.getAuthHandlerOrThrow(authHandlerId)
357
+ await authHandler.flow?.signOut(context)
358
+ }
359
+ }
360
+
361
+ /**
362
+ * Private handler for successful sign-in events.
363
+ * @private
364
+ */
365
+ _signInSuccessHandler: ((context: TurnContext, state: TurnState, authHandlerId?: string) => Promise<void>) | null = null
366
+
367
+ /**
368
+ * Sets a handler to be called when sign-in is successfully completed.
369
+ *
370
+ * @param handler - The handler function to call on successful sign-in.
371
+ * @public
372
+ *
373
+ * @remarks
374
+ * This method allows you to register a callback that will be invoked whenever
375
+ * a user successfully completes the authentication process. The handler receives
376
+ * the turn context, state, and the ID of the auth handler that was used.
377
+ *
378
+ * Example usage:
379
+ * ```typescript
380
+ * auth.onSignInSuccess(async (context, state, authHandlerId) => {
381
+ * await context.sendActivity(`Welcome! You signed in using ${authHandlerId}.`);
382
+ * // Perform any post-authentication setup
383
+ * });
384
+ * ```
385
+ */
386
+ public onSignInSuccess (handler: (context: TurnContext, state: TurnState, authHandlerId?: string) => Promise<void>) {
387
+ this._signInSuccessHandler = handler
388
+ }
389
+
390
+ /**
391
+ * Private handler for failed sign-in events.
392
+ * @private
393
+ */
394
+ _signInFailureHandler: ((context: TurnContext, state: TurnState, authHandlerId?: string, errorMessage?: string) => Promise<void>) | null = null
395
+
396
+ /**
397
+ * Sets a handler to be called when sign-in fails.
398
+ *
399
+ * @param handler - The handler function to call on sign-in failure.
400
+ * @public
401
+ *
402
+ * @remarks
403
+ * This method allows you to register a callback that will be invoked whenever
404
+ * a user's authentication attempt fails. The handler receives the turn context,
405
+ * state, auth handler ID, and an optional error message describing the failure.
406
+ *
407
+ * Common failure scenarios include:
408
+ * - User cancels the authentication process
409
+ * - Invalid credentials or expired tokens
410
+ * - Network connectivity issues
411
+ * - OAuth provider errors
412
+ *
413
+ * Example usage:
414
+ * ```typescript
415
+ * auth.onSignInFailure(async (context, state, authHandlerId, errorMessage) => {
416
+ * await context.sendActivity(`Sign-in failed: ${errorMessage || 'Unknown error'}`);
417
+ * await context.sendActivity('Please try signing in again.');
418
+ * });
419
+ * ```
420
+ */
421
+ public onSignInFailure (handler: (context: TurnContext, state: TurnState, authHandlerId?: string, errorMessage?: string) => Promise<void>) {
422
+ this._signInFailureHandler = handler
423
+ }
424
+ }
package/src/app/index.ts CHANGED
@@ -3,7 +3,7 @@ export * from './agentApplicationBuilder'
3
3
  export * from './agentApplicationOptions'
4
4
  export * from './appRoute'
5
5
  export * from './attachmentDownloader'
6
- export * from './oauth/authorization'
6
+ export * from './authorization'
7
7
  export * from './conversationUpdateEvents'
8
8
  export * from './routeHandler'
9
9
  export * from './routeSelector'
@@ -240,7 +240,7 @@ export class StreamingResponse {
240
240
  // Send final message
241
241
  return Activity.fromObject({
242
242
  type: 'message',
243
- text: this._message || 'end strean response',
243
+ text: this._message || 'end of stream response',
244
244
  attachments: this._attachments,
245
245
  channelData: {
246
246
  streamType: 'final',
@@ -15,7 +15,7 @@ export interface AuthConfiguration {
15
15
  /**
16
16
  * The client ID for the authentication configuration. Required in production.
17
17
  */
18
- clientId?: string
18
+ clientId: string
19
19
 
20
20
  /**
21
21
  * The client secret for the authentication configuration.
@@ -67,23 +67,40 @@ export interface AuthConfiguration {
67
67
  * @returns The authentication configuration.
68
68
  * @throws Will throw an error if clientId is not provided in production.
69
69
  */
70
- export const loadAuthConfigFromEnv: () => AuthConfiguration = () => {
71
- if (process.env.clientId === undefined && process.env.NODE_ENV === 'production') {
72
- throw new Error('ClientId required in production')
73
- }
74
- return {
75
- tenantId: process.env.tenantId,
76
- clientId: process.env.clientId,
77
- clientSecret: process.env.clientSecret,
78
- certPemFile: process.env.certPemFile,
79
- certKeyFile: process.env.certKeyFile,
80
- connectionName: process.env.connectionName,
81
- FICClientId: process.env.FICClientId,
82
- issuers: [
83
- 'https://api.botframework.com',
84
- `https://sts.windows.net/${process.env.tenantId}/`,
85
- `https://login.microsoftonline.com/${process.env.tenantId}/v2.0`
86
- ]
70
+ export const loadAuthConfigFromEnv: (cnxName?: string) => AuthConfiguration = (cnxName?: string) => {
71
+ if (cnxName === undefined) {
72
+ if (process.env.clientId === undefined && process.env.NODE_ENV === 'production') {
73
+ throw new Error('ClientId required in production')
74
+ }
75
+ return {
76
+ tenantId: process.env.tenantId,
77
+ clientId: process.env.clientId!,
78
+ clientSecret: process.env.clientSecret,
79
+ certPemFile: process.env.certPemFile,
80
+ certKeyFile: process.env.certKeyFile,
81
+ connectionName: process.env.connectionName,
82
+ FICClientId: process.env.FICClientId,
83
+ issuers: [
84
+ 'https://api.botframework.com',
85
+ `https://sts.windows.net/${process.env.tenantId}/`,
86
+ `https://login.microsoftonline.com/${process.env.tenantId}/v2.0`
87
+ ]
88
+ }
89
+ } else {
90
+ return {
91
+ tenantId: process.env[`${cnxName}_tenantId`],
92
+ clientId: process.env[`${cnxName}_clientId`] ?? (() => { throw new Error(`ClientId not found for connection: ${cnxName}`) })(),
93
+ clientSecret: process.env[`${cnxName}_clientSecret`],
94
+ certPemFile: process.env[`${cnxName}_certPemFile`],
95
+ certKeyFile: process.env[`${cnxName}_certKeyFile`],
96
+ connectionName: process.env[`${cnxName}_connectionName`],
97
+ FICClientId: process.env.FICClientId,
98
+ issuers: [
99
+ 'https://api.botframework.com',
100
+ `https://sts.windows.net/${process.env[`${cnxName}_tenantId`]}/`,
101
+ `https://login.microsoftonline.com/${process.env[`${cnxName}_tenantId`]}/v2.0`
102
+ ]
103
+ }
87
104
  }
88
105
  }
89
106
 
@@ -103,7 +120,7 @@ export const loadPrevAuthConfigFromEnv: () => AuthConfiguration = () => {
103
120
  }
104
121
  return {
105
122
  tenantId: process.env.MicrosoftAppTenantId,
106
- clientId: process.env.MicrosoftAppId,
123
+ clientId: process.env.MicrosoftAppId!,
107
124
  clientSecret: process.env.MicrosoftAppPassword,
108
125
  certPemFile: process.env.certPemFile,
109
126
  certKeyFile: process.env.certKeyFile,
package/src/auth/index.ts CHANGED
@@ -2,3 +2,4 @@ export * from './authConfiguration'
2
2
  export * from './authProvider'
3
3
  export * from './msalTokenProvider'
4
4
  export * from './request'
5
+ export * from './msalTokenCredential'
@@ -43,7 +43,7 @@ const verifyToken = async (raw: string, config: AuthConfiguration): Promise<JwtP
43
43
 
44
44
  return await new Promise((resolve, reject) => {
45
45
  const verifyOptions: jwt.VerifyOptions = {
46
- issuer: config.issuers,
46
+ issuer: config.issuers as [string, ...string[]],
47
47
  audience: [config.clientId!, 'https://api.botframework.com'],
48
48
  ignoreExpiration: false,
49
49
  algorithms: ['RS256'],
@@ -0,0 +1,14 @@
1
+ import { GetTokenOptions, TokenCredential } from '@azure/core-auth'
2
+ import { AuthConfiguration, MsalTokenProvider } from './'
3
+
4
+ export class MsalTokenCredential implements TokenCredential {
5
+ constructor (private authConfig: AuthConfiguration) {}
6
+ public async getToken (scopes: string[], options?: GetTokenOptions) {
7
+ const scope = scopes[0].substring(0, scopes[0].lastIndexOf('/'))
8
+ const token = await new MsalTokenProvider().getAccessToken(this.authConfig, scope)
9
+ return {
10
+ token,
11
+ expiresOnTimestamp: Date.now() + 10000
12
+ }
13
+ }
14
+ }
@@ -25,7 +25,7 @@ export class MsalTokenProvider implements AuthProvider {
25
25
  * @param scope The scope for the token.
26
26
  * @returns A promise that resolves to the access token.
27
27
  */
28
- async getAccessToken (authConfig: AuthConfiguration, scope: string): Promise<string> {
28
+ public async getAccessToken (authConfig: AuthConfiguration, scope: string): Promise<string> {
29
29
  if (!authConfig.clientId && process.env.NODE_ENV !== 'production') {
30
30
  return ''
31
31
  }
@@ -51,6 +51,22 @@ export class MsalTokenProvider implements AuthProvider {
51
51
  return token
52
52
  }
53
53
 
54
+ public async acquireTokenOnBehalfOf (authConfig: AuthConfiguration, scopes: string[], oboAssertion: string): Promise<string> {
55
+ const cca = new ConfidentialClientApplication({
56
+ auth: {
57
+ clientId: authConfig.clientId as string,
58
+ authority: `https://login.microsoftonline.com/${authConfig.tenantId || 'botframework.com'}`,
59
+ clientSecret: authConfig.clientSecret
60
+ },
61
+ system: this.sysOptions
62
+ })
63
+ const token = await cca.acquireTokenOnBehalfOf({
64
+ oboAssertion,
65
+ scopes
66
+ })
67
+ return token?.accessToken as string
68
+ }
69
+
54
70
  private readonly sysOptions: NodeSystemOptions = {
55
71
  loggerOptions: {
56
72
  logLevel: LogLevel.Trace,
@@ -53,7 +53,7 @@ export abstract class BaseAdapter {
53
53
  /**
54
54
  * The authentication configuration for the adapter.
55
55
  */
56
- authConfig: AuthConfiguration = { issuers: [] }
56
+ abstract authConfig: AuthConfiguration
57
57
 
58
58
  /**
59
59
  * Sends a set of activities to the conversation.
@@ -37,7 +37,7 @@ export class CloudAdapter extends BaseAdapter {
37
37
  * Client for connecting to the Bot Framework Connector service
38
38
  */
39
39
  public connectorClient!: ConnectorClient
40
-
40
+ authConfig: AuthConfiguration
41
41
  /**
42
42
  * Creates an instance of CloudAdapter.
43
43
  * @param authConfig - The authentication configuration for securing communications
@@ -98,7 +98,7 @@ export class CloudAdapter extends BaseAdapter {
98
98
  }
99
99
 
100
100
  async createTurnContextWithScope (activity: Activity, logic: AgentHandler, scope: string): Promise<TurnContext> {
101
- this.connectorClient = await ConnectorClient.createClientWithAuthAsync(activity.serviceUrl!, this.authConfig, this.authProvider, scope)
101
+ this.connectorClient = await ConnectorClient.createClientWithAuthAsync(activity.serviceUrl!, this.authConfig!, this.authProvider, scope)
102
102
  return new TurnContext(this, activity)
103
103
  }
104
104