@microsoft/agents-hosting 1.6.1 → 1.7.0-beta.5.g67b2005119

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 (163) hide show
  1. package/README.md +16 -0
  2. package/dist/package.json +3 -3
  3. package/dist/src/agent-client/agentClient.js +4 -3
  4. package/dist/src/agent-client/agentClient.js.map +1 -1
  5. package/dist/src/agent-client/agentResponseHandler.d.ts +25 -6
  6. package/dist/src/agent-client/agentResponseHandler.js +16 -55
  7. package/dist/src/agent-client/agentResponseHandler.js.map +1 -1
  8. package/dist/src/agent-client/createAgentResponseHandler.d.ts +48 -0
  9. package/dist/src/agent-client/createAgentResponseHandler.js +92 -0
  10. package/dist/src/agent-client/createAgentResponseHandler.js.map +1 -0
  11. package/dist/src/agent-client/index.d.ts +1 -0
  12. package/dist/src/agent-client/index.js +1 -0
  13. package/dist/src/agent-client/index.js.map +1 -1
  14. package/dist/src/app/adaptiveCards/activityValueParsers.js +2 -1
  15. package/dist/src/app/adaptiveCards/activityValueParsers.js.map +1 -1
  16. package/dist/src/app/adaptiveCards/adaptiveCardsActions.js +13 -12
  17. package/dist/src/app/adaptiveCards/adaptiveCardsActions.js.map +1 -1
  18. package/dist/src/app/agentApplication.js +8 -8
  19. package/dist/src/app/agentApplication.js.map +1 -1
  20. package/dist/src/app/streaming/streamingResponse.d.ts +11 -0
  21. package/dist/src/app/streaming/streamingResponse.js +23 -2
  22. package/dist/src/app/streaming/streamingResponse.js.map +1 -1
  23. package/dist/src/app/turnState.d.ts +0 -1
  24. package/dist/src/app/turnState.js +15 -14
  25. package/dist/src/app/turnState.js.map +1 -1
  26. package/dist/src/app/turnStateProperty.js +4 -2
  27. package/dist/src/app/turnStateProperty.js.map +1 -1
  28. package/dist/src/auth/MemoryCache.d.ts +2 -1
  29. package/dist/src/auth/MemoryCache.js +7 -1
  30. package/dist/src/auth/MemoryCache.js.map +1 -1
  31. package/dist/src/auth/authConfiguration.d.ts +1 -1
  32. package/dist/src/auth/authConfiguration.js +41 -4
  33. package/dist/src/auth/authConfiguration.js.map +1 -1
  34. package/dist/src/auth/authConstants.d.ts +7 -7
  35. package/dist/src/auth/authConstants.js.map +1 -1
  36. package/dist/src/auth/connectionManager.d.ts +92 -0
  37. package/dist/src/auth/{msalConnectionManager.js → connectionManager.js} +50 -48
  38. package/dist/src/auth/connectionManager.js.map +1 -0
  39. package/dist/src/auth/index.d.ts +5 -3
  40. package/dist/src/auth/index.js +5 -3
  41. package/dist/src/auth/index.js.map +1 -1
  42. package/dist/src/auth/jwt-middleware.d.ts +8 -2
  43. package/dist/src/auth/jwt-middleware.js +116 -7
  44. package/dist/src/auth/jwt-middleware.js.map +1 -1
  45. package/dist/src/auth/msal/msalConnectionManager.d.ts +17 -0
  46. package/dist/src/auth/msal/msalConnectionManager.js +50 -0
  47. package/dist/src/auth/msal/msalConnectionManager.js.map +1 -0
  48. package/dist/src/auth/msal/msalConnectionSettings.d.ts +68 -0
  49. package/dist/src/auth/msal/msalConnectionSettings.js +7 -0
  50. package/dist/src/auth/msal/msalConnectionSettings.js.map +1 -0
  51. package/dist/src/auth/{msalTokenCredential.d.ts → msal/msalTokenCredential.d.ts} +1 -1
  52. package/dist/src/auth/{msalTokenCredential.js → msal/msalTokenCredential.js} +2 -2
  53. package/dist/src/auth/msal/msalTokenCredential.js.map +1 -0
  54. package/dist/src/auth/{msalTokenProvider.d.ts → msal/msalTokenProvider.d.ts} +23 -3
  55. package/dist/src/auth/{msalTokenProvider.js → msal/msalTokenProvider.js} +280 -176
  56. package/dist/src/auth/msal/msalTokenProvider.js.map +1 -0
  57. package/dist/src/auth/settings.d.ts +142 -133
  58. package/dist/src/auth/settings.js +49 -5
  59. package/dist/src/auth/settings.js.map +1 -1
  60. package/dist/src/auth/sidecar/sidecarAuthProvider.d.ts +74 -0
  61. package/dist/src/auth/sidecar/sidecarAuthProvider.js +181 -0
  62. package/dist/src/auth/sidecar/sidecarAuthProvider.js.map +1 -0
  63. package/dist/src/auth/sidecar/sidecarConnectionSettings.d.ts +65 -0
  64. package/dist/src/auth/sidecar/sidecarConnectionSettings.js +7 -0
  65. package/dist/src/auth/sidecar/sidecarConnectionSettings.js.map +1 -0
  66. package/dist/src/auth/sidecar/sidecarHttpClient.d.ts +95 -0
  67. package/dist/src/auth/sidecar/sidecarHttpClient.js +406 -0
  68. package/dist/src/auth/sidecar/sidecarHttpClient.js.map +1 -0
  69. package/dist/src/auth/sidecar/sidecarModels.d.ts +110 -0
  70. package/dist/src/auth/sidecar/sidecarModels.js +48 -0
  71. package/dist/src/auth/sidecar/sidecarModels.js.map +1 -0
  72. package/dist/src/auth/sidecar/sidecarTokenExpiry.d.ts +13 -0
  73. package/dist/src/auth/sidecar/sidecarTokenExpiry.js +39 -0
  74. package/dist/src/auth/sidecar/sidecarTokenExpiry.js.map +1 -0
  75. package/dist/src/baseAdapter.js +3 -1
  76. package/dist/src/baseAdapter.js.map +1 -1
  77. package/dist/src/cloudAdapter.d.ts +2 -2
  78. package/dist/src/cloudAdapter.js +11 -11
  79. package/dist/src/cloudAdapter.js.map +1 -1
  80. package/dist/src/createCloudAdapter.d.ts +40 -0
  81. package/dist/src/createCloudAdapter.js +44 -0
  82. package/dist/src/createCloudAdapter.js.map +1 -0
  83. package/dist/src/errorHelper.js +302 -79
  84. package/dist/src/errorHelper.js.map +1 -1
  85. package/dist/src/headerPropagation.js +3 -1
  86. package/dist/src/headerPropagation.js.map +1 -1
  87. package/dist/src/index.d.ts +3 -1
  88. package/dist/src/index.js +4 -1
  89. package/dist/src/index.js.map +1 -1
  90. package/dist/src/interfaces/index.d.ts +5 -0
  91. package/dist/src/interfaces/index.js +22 -0
  92. package/dist/src/interfaces/index.js.map +1 -0
  93. package/dist/src/interfaces/webResponse.d.ts +41 -0
  94. package/dist/src/interfaces/webResponse.js +7 -0
  95. package/dist/src/interfaces/webResponse.js.map +1 -0
  96. package/dist/src/middlewareSet.d.ts +0 -1
  97. package/dist/src/middlewareSet.js +4 -1
  98. package/dist/src/middlewareSet.js.map +1 -1
  99. package/dist/src/oauth/userTokenClient.d.ts +12 -0
  100. package/dist/src/oauth/userTokenClient.js +30 -9
  101. package/dist/src/oauth/userTokenClient.js.map +1 -1
  102. package/dist/src/state/conversationState.js +4 -2
  103. package/dist/src/state/conversationState.js.map +1 -1
  104. package/dist/src/state/userState.js +4 -2
  105. package/dist/src/state/userState.js.map +1 -1
  106. package/dist/src/storage/memoryStorage.js +5 -3
  107. package/dist/src/storage/memoryStorage.js.map +1 -1
  108. package/dist/src/transcript/consoleTranscriptLogger.js +3 -1
  109. package/dist/src/transcript/consoleTranscriptLogger.js.map +1 -1
  110. package/dist/src/transcript/fileTranscriptLogger.js +5 -4
  111. package/dist/src/transcript/fileTranscriptLogger.js.map +1 -1
  112. package/dist/src/transcript/transcriptLoggerMiddleware.js +2 -1
  113. package/dist/src/transcript/transcriptLoggerMiddleware.js.map +1 -1
  114. package/dist/src/turnContext.js +2 -1
  115. package/dist/src/turnContext.js.map +1 -1
  116. package/package.json +3 -3
  117. package/src/agent-client/agentClient.ts +5 -4
  118. package/src/agent-client/agentResponseHandler.ts +43 -67
  119. package/src/agent-client/createAgentResponseHandler.ts +133 -0
  120. package/src/agent-client/index.ts +1 -0
  121. package/src/app/adaptiveCards/activityValueParsers.ts +3 -2
  122. package/src/app/adaptiveCards/adaptiveCardsActions.ts +5 -5
  123. package/src/app/agentApplication.ts +8 -16
  124. package/src/app/streaming/streamingResponse.ts +26 -3
  125. package/src/app/turnState.ts +15 -14
  126. package/src/app/turnStateProperty.ts +4 -2
  127. package/src/auth/MemoryCache.ts +7 -1
  128. package/src/auth/authConfiguration.ts +41 -6
  129. package/src/auth/authConstants.ts +7 -7
  130. package/src/auth/{msalConnectionManager.ts → connectionManager.ts} +65 -61
  131. package/src/auth/index.ts +5 -3
  132. package/src/auth/jwt-middleware.ts +118 -9
  133. package/src/auth/msal/msalConnectionManager.ts +55 -0
  134. package/src/auth/msal/msalConnectionSettings.ts +79 -0
  135. package/src/auth/{msalTokenCredential.ts → msal/msalTokenCredential.ts} +2 -1
  136. package/src/auth/{msalTokenProvider.ts → msal/msalTokenProvider.ts} +353 -172
  137. package/src/auth/settings.ts +131 -76
  138. package/src/auth/sidecar/sidecarAuthProvider.ts +234 -0
  139. package/src/auth/sidecar/sidecarConnectionSettings.ts +72 -0
  140. package/src/auth/sidecar/sidecarHttpClient.ts +443 -0
  141. package/src/auth/sidecar/sidecarModels.ts +153 -0
  142. package/src/auth/sidecar/sidecarTokenExpiry.ts +35 -0
  143. package/src/baseAdapter.ts +3 -2
  144. package/src/cloudAdapter.ts +13 -13
  145. package/src/createCloudAdapter.ts +56 -0
  146. package/src/errorHelper.ts +354 -89
  147. package/src/headerPropagation.ts +4 -1
  148. package/src/index.ts +3 -1
  149. package/src/interfaces/index.ts +6 -0
  150. package/src/interfaces/webResponse.ts +44 -0
  151. package/src/middlewareSet.ts +3 -1
  152. package/src/oauth/userTokenClient.ts +46 -11
  153. package/src/state/conversationState.ts +4 -3
  154. package/src/state/userState.ts +4 -3
  155. package/src/storage/memoryStorage.ts +5 -3
  156. package/src/transcript/consoleTranscriptLogger.ts +3 -2
  157. package/src/transcript/fileTranscriptLogger.ts +6 -5
  158. package/src/transcript/transcriptLoggerMiddleware.ts +3 -2
  159. package/src/turnContext.ts +3 -2
  160. package/dist/src/auth/msalConnectionManager.d.ts +0 -64
  161. package/dist/src/auth/msalConnectionManager.js.map +0 -1
  162. package/dist/src/auth/msalTokenCredential.js.map +0 -1
  163. package/dist/src/auth/msalTokenProvider.js.map +0 -1
@@ -6,7 +6,7 @@ const CACHE_PURGE_INTERVAL = 60000 // 60 seconds
6
6
 
7
7
  /**
8
8
  * Simple in-memory cache with TTL support.
9
- * This is used to store authentication tokens for Agentic Identity scenarios only!
9
+ * This is used to store authentication tokens.
10
10
  */
11
11
  export class MemoryCache<T> {
12
12
  private cache = new Map<string, { value: T; validUntil: number }>()
@@ -22,11 +22,17 @@ export class MemoryCache<T> {
22
22
  }
23
23
  }
24
24
 
25
+ clear (): void {
26
+ this.cache.clear()
27
+ this.destroy()
28
+ }
29
+
25
30
  set (key: string, value: T, ttlSeconds: number): void {
26
31
  const validUntil = Date.now() + (ttlSeconds * 1000)
27
32
  this.cache.set(key, { value, validUntil })
28
33
  if (!this.purgeInterval) {
29
34
  this.purgeInterval = setInterval(() => this.purge(), CACHE_PURGE_INTERVAL)
35
+ this.purgeInterval.unref?.()
30
36
  }
31
37
  }
32
38
 
@@ -3,12 +3,13 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import type { ConnectionMapItem } from './msalConnectionManager'
7
6
  import { debug, redactString, redactScopes, redactUrl } from '@microsoft/agents-telemetry'
8
- import { loadEnvSettings, AuthConfiguration, envParser, envParserUtils, LoadEnv, applyDefaultSettings, DEFAULT_CONNECTION_MAP, ConnectionKeys, ConnectionMapKeys } from './settings'
7
+ import { loadEnvSettings, AuthConfiguration, envParser, envParserUtils, LoadEnv, applyDefaultSettings, DEFAULT_CONNECTION_MAP, ConnectionKeys, ConnectionMapKeys, ConnectionMapItem } from './settings'
9
8
 
10
- export { type AuthConfiguration, AuthType, resolveAuthority } from './settings'
9
+ export { type AuthConfiguration, type ConnectionSettings, type ConnectionSettingsBase, type MsalConnectionSettings, type SidecarConnectionSettings, AuthType, resolveAuthority, type ConnectionMapItem, resolveAuthType } from './settings'
11
10
  import { prune } from '../utils'
11
+ import { ExceptionHelper } from '@microsoft/agents-activity'
12
+ import { Errors } from '../errorHelper'
12
13
 
13
14
  const logger = debug('agents:authConfiguration')
14
15
 
@@ -36,8 +37,15 @@ function summarizeAuthConfiguration (authConfig: AuthConfiguration) {
36
37
  idpmResource: config.idpmResource ? redactUrl(config.idpmResource) : undefined,
37
38
  connectionName: config.connectionName,
38
39
  altBlueprintConnectionName: config.altBlueprintConnectionName,
40
+ alternateBlueprintConnectionName: undefined, // Alias of altBlueprintConnectionName, avoid logging duplicate info
39
41
  azureRegion: config.azureRegion,
40
42
  sendX5C: config.sendX5C,
43
+ sidecarBaseUrl: config.sidecarBaseUrl ? redactUrl(config.sidecarBaseUrl) : undefined,
44
+ serviceName: config.serviceName,
45
+ blueprintServiceName: config.blueprintServiceName,
46
+ bypassLocalNetworkRestriction: config.bypassLocalNetworkRestriction,
47
+ requestTimeout: config.requestTimeout,
48
+ retryCount: config.retryCount,
41
49
  // Don't log the following properties
42
50
  authority: undefined, // Deprecated, same as authorityEndpoint, avoid logging duplicate info
43
51
  FICClientId: undefined, // Deprecated, same as federatedClientId, avoid logging duplicate info
@@ -82,11 +90,24 @@ const connectionsEnv = {
82
90
  return { key: 'scopes', value: this.scopes(value)?.value } // redirect with single scope
83
91
  },
84
92
  altBlueprintConnectionName: envParserUtils.bypass,
93
+ alternateBlueprintConnectionName: (value) => ({ key: 'altBlueprintConnectionName', value }), // .NET parity alias
85
94
  WIDAssertionFile: envParserUtils.bypass,
86
95
  federatedTokenFile: envParserUtils.bypass,
87
96
  idpmResource: envParserUtils.bypass,
88
97
  azureRegion: envParserUtils.bypass,
89
98
  sendX5C: (value) => ({ value: value === 'true' }),
99
+ sidecarBaseUrl: envParserUtils.bypass,
100
+ serviceName: envParserUtils.bypass,
101
+ blueprintServiceName: envParserUtils.bypass,
102
+ bypassLocalNetworkRestriction: (value) => ({ value: value === 'true' }),
103
+ requestTimeout: (value) => {
104
+ const n = parseInt(value, 10)
105
+ return { value: Number.isFinite(n) && n > 0 ? n : undefined }
106
+ },
107
+ retryCount: (value) => {
108
+ const n = parseInt(value, 10)
109
+ return { value: Number.isFinite(n) && n >= 0 ? n : undefined }
110
+ },
90
111
  issuers (value) {
91
112
  if (value.includes(',')) {
92
113
  return { value: value.split(',').map(s => s.trim()).filter(Boolean) }
@@ -99,12 +120,12 @@ const connectionsEnv = {
99
120
  const map = connectionsMap ?? connectionsMapEnv.connectionsMap
100
121
  const name = map?.find((item) => item.serviceUrl === '*')?.connection
101
122
  if (!name) {
102
- throw new Error('No default connection found in environment connections.')
123
+ throw ExceptionHelper.generateException(Error, Errors.NoDefaultConnectionFound)
103
124
  }
104
125
 
105
126
  const connection = conn?.get(name ?? '')
106
127
  if (!connection) {
107
- throw new Error(`Connection "${name}" not found in environment connections.`)
128
+ throw ExceptionHelper.generateException(Error, Errors.ConnectionNotFoundInEnvironment, undefined, { connectionName: name })
108
129
  }
109
130
 
110
131
  return applyDefaultSettings({ ...connection, connections: conn, connectionsMap: map })
@@ -205,12 +226,19 @@ const legacyBotFrameworkEnv = {
205
226
  authorityEndpoint: envParserUtils.redirect(connectionsEnv.parser, 'authorityEndpoint'),
206
227
  scope: envParserUtils.redirect(connectionsEnv.parser, 'scopes'),
207
228
  altBlueprintConnectionName: envParserUtils.redirect(connectionsEnv.parser, 'altBlueprintConnectionName'),
229
+ alternateBlueprintConnectionName: envParserUtils.redirect(connectionsEnv.parser, 'altBlueprintConnectionName'),
208
230
  WIDAssertionFile: envParserUtils.redirect(connectionsEnv.parser, 'WIDAssertionFile'),
209
231
  azureRegion: envParserUtils.redirect(connectionsEnv.parser, 'azureRegion'),
210
232
  sendX5C: envParserUtils.redirect(connectionsEnv.parser, 'sendX5C'),
211
233
  authType: envParserUtils.redirect(connectionsEnv.parser, 'authType'),
212
234
  federatedTokenFile: envParserUtils.redirect(connectionsEnv.parser, 'federatedTokenFile'),
213
235
  idpmResource: envParserUtils.redirect(connectionsEnv.parser, 'idpmResource'),
236
+ sidecarBaseUrl: envParserUtils.redirect(connectionsEnv.parser, 'sidecarBaseUrl'),
237
+ serviceName: envParserUtils.redirect(connectionsEnv.parser, 'serviceName'),
238
+ blueprintServiceName: envParserUtils.redirect(connectionsEnv.parser, 'blueprintServiceName'),
239
+ bypassLocalNetworkRestriction: envParserUtils.redirect(connectionsEnv.parser, 'bypassLocalNetworkRestriction'),
240
+ requestTimeout: envParserUtils.redirect(connectionsEnv.parser, 'requestTimeout'),
241
+ retryCount: envParserUtils.redirect(connectionsEnv.parser, 'retryCount'),
214
242
  }),
215
243
  process (env: LoadEnv) {
216
244
  return legacyPrefixEnv.process.call(this, env)
@@ -233,12 +261,19 @@ const legacyPrefixEnv = {
233
261
  authorityEndpoint: envParserUtils.redirect(connectionsEnv.parser, 'authorityEndpoint'),
234
262
  scope: envParserUtils.redirect(connectionsEnv.parser, 'scopes'),
235
263
  altBlueprintConnectionName: envParserUtils.redirect(connectionsEnv.parser, 'altBlueprintConnectionName'),
264
+ alternateBlueprintConnectionName: envParserUtils.redirect(connectionsEnv.parser, 'altBlueprintConnectionName'),
236
265
  WIDAssertionFile: envParserUtils.redirect(connectionsEnv.parser, 'WIDAssertionFile'),
237
266
  azureRegion: envParserUtils.redirect(connectionsEnv.parser, 'azureRegion'),
238
267
  sendX5C: envParserUtils.redirect(connectionsEnv.parser, 'sendX5C'),
239
268
  authType: envParserUtils.redirect(connectionsEnv.parser, 'authType'),
240
269
  federatedTokenFile: envParserUtils.redirect(connectionsEnv.parser, 'federatedTokenFile'),
241
270
  idpmResource: envParserUtils.redirect(connectionsEnv.parser, 'idpmResource'),
271
+ sidecarBaseUrl: envParserUtils.redirect(connectionsEnv.parser, 'sidecarBaseUrl'),
272
+ serviceName: envParserUtils.redirect(connectionsEnv.parser, 'serviceName'),
273
+ blueprintServiceName: envParserUtils.redirect(connectionsEnv.parser, 'blueprintServiceName'),
274
+ bypassLocalNetworkRestriction: envParserUtils.redirect(connectionsEnv.parser, 'bypassLocalNetworkRestriction'),
275
+ requestTimeout: envParserUtils.redirect(connectionsEnv.parser, 'requestTimeout'),
276
+ retryCount: envParserUtils.redirect(connectionsEnv.parser, 'retryCount'),
242
277
  }),
243
278
  process (env: LoadEnv, prefix?: string) {
244
279
  const settings: Partial<AuthConfiguration> = {}
@@ -326,7 +361,7 @@ export const loadAuthConfigFromEnv = (cnxName?: string): AuthConfiguration => {
326
361
  // No connections provided, we need to populate the connections map with the old config settings
327
362
  const result = applyDefaultSettings(cnxName?.trim() ? legacyPrefixEnv.process(globalEnv.env, cnxName) : globalEnv.legacyPrefixSettings)
328
363
  if (cnxName && !result.clientId) {
329
- throw new Error(`ClientId not found for connection: ${cnxName}`)
364
+ throw ExceptionHelper.generateException(Error, Errors.ClientIdNotFoundForConnection, undefined, { connectionName: cnxName })
330
365
  }
331
366
 
332
367
  logger.info('Auth settings loaded from environment', {
@@ -2,10 +2,10 @@
2
2
  * Copyright (c) Microsoft Corporation. All rights reserved.
3
3
  * Licensed under the MIT License.
4
4
  */
5
- export const ApxLocalScope = 'c16e153d-5d2b-4c21-b7f4-b05ee5d516f1/.default'
6
- export const ApxDevScope = '0d94caae-b412-4943-8a68-83135ad6d35f/.default'
7
- export const ApxProductionScope = '5a807f24-c9de-44ee-a3a7-329e88a00ffc/.default'
8
- export const ApxGCCScope = 'c9475445-9789-4fef-9ec5-cde4a9bcd446/.default'
9
- export const ApxGCCHScope = '6f669b9e-7701-4e2b-b624-82c9207fde26/.default'
10
- export const ApxDoDScope = '0a069c81-8c7c-4712-886b-9c542d673ffb/.default'
11
- export const ApxGallatinScope = 'bd004c8e-5acf-4c48-8570-4e7d46b2f63b/.default'
5
+ export const ApxLocalScope: string = 'c16e153d-5d2b-4c21-b7f4-b05ee5d516f1/.default'
6
+ export const ApxDevScope: string = '0d94caae-b412-4943-8a68-83135ad6d35f/.default'
7
+ export const ApxProductionScope: string = '5a807f24-c9de-44ee-a3a7-329e88a00ffc/.default'
8
+ export const ApxGCCScope: string = 'c9475445-9789-4fef-9ec5-cde4a9bcd446/.default'
9
+ export const ApxGCCHScope: string = '6f669b9e-7701-4e2b-b624-82c9207fde26/.default'
10
+ export const ApxDoDScope: string = '0a069c81-8c7c-4712-886b-9c542d673ffb/.default'
11
+ export const ApxGallatinScope: string = 'bd004c8e-5acf-4c48-8570-4e7d46b2f63b/.default'
@@ -3,28 +3,58 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { Activity, RoleTypes } from '@microsoft/agents-activity'
7
- import { debug } from '@microsoft/agents-telemetry'
8
- import { AuthConfiguration, AuthType, resolveAuthority } from './authConfiguration'
6
+ import { Activity, ExceptionHelper, RoleTypes } from '@microsoft/agents-activity'
7
+ import { debug, redactString } from '@microsoft/agents-telemetry'
8
+ import { AuthConfiguration, AuthType, resolveAuthType } from './authConfiguration'
9
9
  import { Connections } from './connections'
10
- import { MsalTokenProvider } from './msalTokenProvider'
10
+ import { AuthProvider } from './authProvider'
11
+ import { MsalTokenProvider } from './msal/msalTokenProvider'
12
+ import { SidecarAuthProvider } from './sidecar/sidecarAuthProvider'
11
13
  import { JwtPayload } from 'jsonwebtoken'
14
+ import { Errors } from '../errorHelper'
15
+ import type { ConnectionMapItem } from './settings'
12
16
 
13
17
  const logger = debug('agents:authorization:connections')
14
18
 
15
- export interface ConnectionMapItem {
16
- audience?: string
17
- serviceUrl: string
18
- connection: string
19
- }
19
+ /**
20
+ * Factory function that creates an {@link AuthProvider} instance from a connection's
21
+ * {@link AuthConfiguration}.
22
+ */
23
+ export type AuthProviderFactory = (config: AuthConfiguration) => AuthProvider
24
+
25
+ /**
26
+ * Default {@link AuthProviderFactory} that dispatches per-connection by `authType`: connections with
27
+ * `authType` set to `EntraAuthSideCar` use {@link SidecarAuthProvider}; all others use
28
+ * {@link MsalTokenProvider}.
29
+ * @param config The connection authentication configuration.
30
+ * @returns The auth provider for the connection.
31
+ */
32
+ export const defaultAuthProviderFactory: AuthProviderFactory = (config: AuthConfiguration): AuthProvider =>
33
+ config?.authType === AuthType.EntraAuthSideCar
34
+ ? new SidecarAuthProvider(config)
35
+ : new MsalTokenProvider(config)
20
36
 
21
- export class MsalConnectionManager implements Connections {
22
- private _connections: Map<string, MsalTokenProvider>
23
- private _connectionsMap: ConnectionMapItem[]
24
- private _serviceConnectionConfiguration: AuthConfiguration
25
- private static readonly DEFAULT_CONNECTION = 'serviceConnection'
37
+ /**
38
+ * Generic, provider-agnostic connection manager. Dispatches connections to any {@link AuthProvider}
39
+ * implementation produced by the supplied {@link AuthProviderFactory}, while owning the
40
+ * provider-independent connection routing logic (audience matching, service URL dispatch,
41
+ * `altBlueprintConnectionName` handling, default connection resolution).
42
+ */
43
+ export class ConnectionManager implements Connections {
44
+ protected _connections: Map<string, AuthProvider>
45
+ protected _connectionsMap: ConnectionMapItem[]
46
+ protected _serviceConnectionConfiguration: AuthConfiguration
47
+ protected static readonly DEFAULT_CONNECTION = 'serviceConnection'
26
48
 
49
+ /**
50
+ * Creates a new {@link ConnectionManager}.
51
+ * @param providerFactory Factory used to instantiate an {@link AuthProvider} for each connection.
52
+ * @param connectionsConfigurations Map of connection names to their authentication configurations.
53
+ * @param connectionsMap Map items used to route activities to connections.
54
+ * @param configuration Fallback authentication configuration (used when the above are empty).
55
+ */
27
56
  constructor (
57
+ providerFactory: AuthProviderFactory = defaultAuthProviderFactory,
28
58
  connectionsConfigurations: Map<string, AuthConfiguration> = new Map(),
29
59
  connectionsMap: ConnectionMapItem[] = [],
30
60
  configuration: AuthConfiguration = {}) {
@@ -35,26 +65,16 @@ export class MsalConnectionManager implements Connections {
35
65
  const providedConnections = connectionsConfigurations.size > 0 ? connectionsConfigurations : (configuration.connections || new Map())
36
66
 
37
67
  for (const [name, config] of providedConnections) {
38
- // Instantiate MsalTokenProvider for each connection
39
- this._connections.set(name, new MsalTokenProvider(config))
40
- if (name === MsalConnectionManager.DEFAULT_CONNECTION) {
68
+ this._connections.set(name, providerFactory(config))
69
+ if (name === ConnectionManager.DEFAULT_CONNECTION) {
41
70
  this._serviceConnectionConfiguration = config
42
71
  }
43
72
  }
44
73
 
45
74
  for (const [name, provider] of this._connections.entries()) {
46
75
  const cfg = provider.connectionSettings
47
- const authType = cfg?.authType ??
48
- (cfg?.certPemFile
49
- ? AuthType.Certificate
50
- : cfg?.clientSecret
51
- ? AuthType.ClientSecret
52
- : cfg?.WIDAssertionFile
53
- ? AuthType.WorkloadIdentity
54
- : cfg?.federatedClientId || cfg?.FICClientId
55
- ? AuthType.FederatedCredentials
56
- : 'none')
57
- logger.debug('connection "%s" clientId=%s tenantId=%s authType=%s', name, cfg?.clientId ?? '<none>', cfg?.tenantId ?? '<none>', authType)
76
+ const authType = resolveAuthType(cfg)
77
+ logger.debug('connection "%s" clientId=%s tenantId=%s authType=%s', name, redactString(cfg?.clientId, true) ?? '<none>', redactString(cfg?.tenantId, true) ?? '<none>', authType)
58
78
  }
59
79
 
60
80
  for (const item of this._connectionsMap) {
@@ -67,10 +87,10 @@ export class MsalConnectionManager implements Connections {
67
87
  * @param connectionName The name of the connection.
68
88
  * @returns The OAuth connection for the agent.
69
89
  */
70
- getConnection (connectionName: string): MsalTokenProvider {
90
+ getConnection (connectionName: string): AuthProvider {
71
91
  const conn = this._connections.get(connectionName)
72
92
  if (!conn) {
73
- throw new Error(`Connection not found: ${connectionName}`)
93
+ throw ExceptionHelper.generateException(Error, Errors.ConnectionNotFound, undefined, { connectionName })
74
94
  }
75
95
  return this.applyConnectionDefaults(conn)
76
96
  }
@@ -79,9 +99,9 @@ export class MsalConnectionManager implements Connections {
79
99
  * Get the default OAuth connection for the agent.
80
100
  * @returns The default OAuth connection for the agent.
81
101
  */
82
- getDefaultConnection (): MsalTokenProvider {
102
+ getDefaultConnection (): AuthProvider {
83
103
  if (this._connections.size === 0) {
84
- throw new Error('No connections found for this Agent in the Connections Configuration.')
104
+ throw ExceptionHelper.generateException(Error, Errors.NoConnectionsFoundInConfiguration)
85
105
  }
86
106
 
87
107
  // Return the wildcard map item instance.
@@ -91,7 +111,7 @@ export class MsalConnectionManager implements Connections {
91
111
  }
92
112
  }
93
113
 
94
- const conn = this._connections.values().next().value as MsalTokenProvider
114
+ const conn = this._connections.values().next().value as AuthProvider
95
115
 
96
116
  return this.applyConnectionDefaults(conn)
97
117
  }
@@ -114,9 +134,9 @@ export class MsalConnectionManager implements Connections {
114
134
  * ServiceUrl is: A regex to match with, or "*" for any serviceUrl value.
115
135
  * Connection is: A name in the 'Connections' list.
116
136
  */
117
- getTokenProvider (identity: JwtPayload, serviceUrl: string): MsalTokenProvider {
137
+ getTokenProvider (identity: JwtPayload, serviceUrl: string): AuthProvider {
118
138
  if (!identity) {
119
- throw new Error('Identity is required to get the token provider.')
139
+ throw ExceptionHelper.generateException(Error, Errors.IdentityRequiredForTokenProvider)
120
140
  }
121
141
 
122
142
  let audience
@@ -126,7 +146,7 @@ export class MsalConnectionManager implements Connections {
126
146
  audience = identity.aud
127
147
  }
128
148
 
129
- if (!audience || !serviceUrl) throw new Error('Audience and Service URL are required to get the token provider.')
149
+ if (!audience || !serviceUrl) throw ExceptionHelper.generateException(Error, Errors.AudienceAndServiceUrlRequiredForTokenProvider)
130
150
 
131
151
  if (this._connectionsMap.length === 0) {
132
152
  logger.debug('no connectionsMap, using default connection for serviceUrl=%s', serviceUrl)
@@ -154,7 +174,7 @@ export class MsalConnectionManager implements Connections {
154
174
  }
155
175
  }
156
176
  }
157
- throw new Error(`No connection found for audience: ${audience} and serviceUrl: ${serviceUrl}`)
177
+ throw ExceptionHelper.generateException(Error, Errors.NoConnectionForAudienceAndServiceUrl, undefined, { audience: String(audience), serviceUrl })
158
178
  }
159
179
 
160
180
  /**
@@ -163,7 +183,7 @@ export class MsalConnectionManager implements Connections {
163
183
  * @param activity The activity.
164
184
  * @returns The TokenProvider for the connection.
165
185
  */
166
- getTokenProviderFromActivity (identity: JwtPayload, activity: Activity): MsalTokenProvider {
186
+ getTokenProviderFromActivity (identity: JwtPayload, activity: Activity): AuthProvider {
167
187
  let connection = this.getTokenProvider(identity, activity.serviceUrl || '')
168
188
 
169
189
  // This is for the case where the Agentic BlueprintId is not the same as the AppId
@@ -186,29 +206,13 @@ export class MsalConnectionManager implements Connections {
186
206
  return this._serviceConnectionConfiguration
187
207
  }
188
208
 
189
- private applyConnectionDefaults (conn: MsalTokenProvider): MsalTokenProvider {
190
- if (conn.connectionSettings) {
191
- conn.connectionSettings.authorityEndpoint ??= conn.connectionSettings.authority || 'https://login.microsoftonline.com'
192
- conn.connectionSettings.authority ??= conn.connectionSettings.authorityEndpoint
193
- conn.connectionSettings.issuers ??= [
194
- 'https://api.botframework.com',
195
- `${resolveAuthority('https://sts.windows.net', conn.connectionSettings.tenantId)}/`,
196
- `${resolveAuthority(conn.connectionSettings.authorityEndpoint, conn.connectionSettings.tenantId)}/v2.0`
197
- ]
198
- // For backward compatibility
199
- if (conn.connectionSettings.federatedClientId) {
200
- conn.connectionSettings.FICClientId = conn.connectionSettings.federatedClientId
201
- }
202
-
203
- if (conn.connectionSettings.scopes?.length) {
204
- conn.connectionSettings.scope = conn.connectionSettings.scopes?.[0]
205
- }
206
-
207
- if (conn.connectionSettings.authorityEndpoint) {
208
- conn.connectionSettings.authority = conn.connectionSettings.authorityEndpoint
209
- }
210
- }
211
-
209
+ /**
210
+ * Applies provider-specific defaults to a resolved connection before returning it. The generic
211
+ * base performs no mutation; provider-specific managers may override this.
212
+ * @param conn The resolved auth provider.
213
+ * @returns The auth provider, possibly with defaults applied.
214
+ */
215
+ protected applyConnectionDefaults (conn: AuthProvider): AuthProvider {
212
216
  return conn
213
217
  }
214
218
  }
package/src/auth/index.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  export * from './authConfiguration'
2
2
  export * from './authConstants'
3
3
  export * from './authProvider'
4
- export * from './msalTokenProvider'
4
+ export * from './msal/msalTokenProvider'
5
5
  export * from './request'
6
- export * from './msalTokenCredential'
7
- export * from './msalConnectionManager'
6
+ export * from './msal/msalTokenCredential'
7
+ export * from './connectionManager'
8
+ export * from './msal/msalConnectionManager'
9
+ export * from './sidecar/sidecarAuthProvider'
@@ -4,13 +4,24 @@
4
4
  */
5
5
 
6
6
  import { AuthConfiguration, resolveAuthority } from './authConfiguration'
7
- import { Response, NextFunction } from 'express'
8
7
  import { Request } from './request'
8
+ import { WebResponse, NextFunction } from '../interfaces/webResponse'
9
+ import { Errors } from '../errorHelper'
10
+ import { ExceptionHelper } from '@microsoft/agents-activity'
9
11
  import jwksRsa, { JwksClient, SigningKey } from 'jwks-rsa'
10
12
  import jwt, { JwtHeader, JwtPayload, SignCallback, GetPublicKeyOrSecret } from 'jsonwebtoken'
11
13
  import { debug } from '@microsoft/agents-telemetry'
12
14
 
13
15
  const logger = debug('agents:jwt-middleware')
16
+ const jwksClients = new Map<string, JwksClient>()
17
+ const maxJwksClients = 100
18
+
19
+ /**
20
+ * Clears process-wide JWKS clients.
21
+ */
22
+ export function clearJwksClients (): void {
23
+ jwksClients.clear()
24
+ }
14
25
 
15
26
  /**
16
27
  * Builds the JWKS URI for the given token issuer and auth configuration.
@@ -24,6 +35,26 @@ export function buildJwksUri (iss: string, authConfig: AuthConfiguration): strin
24
35
  : `${resolveAuthority(authConfig.authorityEndpoint ?? authConfig.authority, authConfig.tenantId)}/discovery/v2.0/keys`
25
36
  }
26
37
 
38
+ export function getJwksClient (jwksUri: string): JwksClient {
39
+ // Check if a client for this JWKS URI already exists in the cache.
40
+ let client = jwksClients.get(jwksUri)
41
+ if (!client) {
42
+ client = jwksRsa({ jwksUri })
43
+ jwksClients.set(jwksUri, client)
44
+ while (jwksClients.size > maxJwksClients) {
45
+ const oldestKey = jwksClients.keys().next().value
46
+ if (oldestKey === undefined) {
47
+ break
48
+ }
49
+ jwksClients.delete(oldestKey)
50
+ }
51
+ } else {
52
+ jwksClients.delete(jwksUri)
53
+ jwksClients.set(jwksUri, client)
54
+ }
55
+ return client
56
+ }
57
+
27
58
  /**
28
59
  * Verifies the JWT token.
29
60
  * @param raw The raw JWT token.
@@ -35,7 +66,7 @@ const verifyToken = async (raw: string, config: AuthConfiguration): Promise<JwtP
35
66
  logger.debug('jwt.decode ', JSON.stringify(payload))
36
67
 
37
68
  if (!payload) {
38
- throw new Error('invalid token')
69
+ throw ExceptionHelper.generateException(Error, Errors.InvalidJwtToken)
39
70
  }
40
71
  const audience = payload.aud
41
72
 
@@ -44,7 +75,7 @@ const verifyToken = async (raw: string, config: AuthConfiguration): Promise<JwtP
44
75
  : undefined
45
76
 
46
77
  if (!matchingEntry) {
47
- const err = new Error('Audience mismatch')
78
+ const err = ExceptionHelper.generateException(Error, Errors.JwtAudienceMismatch)
48
79
  logger.error(err.message, audience)
49
80
  throw err
50
81
  }
@@ -55,9 +86,10 @@ const verifyToken = async (raw: string, config: AuthConfiguration): Promise<JwtP
55
86
  const jwksUri = buildJwksUri(payload.iss as string, authConfig)
56
87
 
57
88
  logger.debug(`fetching keys from ${jwksUri}`)
58
- const jwksClient: JwksClient = jwksRsa({ jwksUri })
89
+ const jwksClient = getJwksClient(jwksUri)
59
90
 
60
91
  const getKey: GetPublicKeyOrSecret = (header: JwtHeader, callback: SignCallback) => {
92
+ // Retrieve the public, issuer-wide signing key from the JWKS endpoint using the kid from the token header.
61
93
  jwksClient.getSigningKey(header.kid, (err: Error | null, key: SigningKey | undefined): void => {
62
94
  if (err) {
63
95
  logger.error('jwksClient.getSigningKey ', JSON.stringify(err))
@@ -89,13 +121,78 @@ const verifyToken = async (raw: string, config: AuthConfiguration): Promise<JwtP
89
121
  })
90
122
  }
91
123
 
124
+ /**
125
+ * Determines whether an `Authorization` header value is present (non-empty).
126
+ *
127
+ * The {@link Request} contract allows `string | string[] | undefined` because
128
+ * different web frameworks surface headers differently. This treats an empty
129
+ * string or empty array as "absent" so callers can distinguish a missing header
130
+ * (anonymous / 401) from a present-but-malformed one (always 401).
131
+ * @param authorization The raw `Authorization` header value.
132
+ * @returns `true` when a non-empty header value is present.
133
+ */
134
+ function hasAuthorizationHeader (authorization: string | string[] | undefined): boolean {
135
+ if (Array.isArray(authorization)) {
136
+ return authorization.some((value) => typeof value === 'string' && value.trim().length > 0)
137
+ }
138
+ return typeof authorization === 'string' && authorization.trim().length > 0
139
+ }
140
+
141
+ /**
142
+ * Parses a single `Authorization` header value and returns its bearer token.
143
+ *
144
+ * Validates the `Bearer <token>` scheme, returning `undefined` for anything
145
+ * malformed (wrong scheme, missing token, or extra whitespace-delimited parts).
146
+ * @param headerValue A single `Authorization` header value.
147
+ * @returns The bearer token, or `undefined` if the value is absent or malformed.
148
+ */
149
+ function parseBearerValue (headerValue: string | undefined): string | undefined {
150
+ if (typeof headerValue !== 'string') {
151
+ return undefined
152
+ }
153
+ const parts = headerValue.trim().split(/\s+/)
154
+ if (parts.length !== 2) {
155
+ return undefined
156
+ }
157
+ const [scheme, token] = parts
158
+ if (scheme.toLowerCase() !== 'bearer' || !token) {
159
+ return undefined
160
+ }
161
+ return token
162
+ }
163
+
164
+ /**
165
+ * Extracts the bearer token from a raw `Authorization` header value.
166
+ *
167
+ * On Node's core HTTP parser (which both Express and Fastify use) `Authorization`
168
+ * is always surfaced as a single string holding the *first* header line —
169
+ * duplicate `Authorization` headers are discarded, not comma-joined. The
170
+ * `string[]` case is handled only because the {@link Request} contract permits
171
+ * it for non-Node frameworks; when an array is supplied, every entry is inspected
172
+ * and the first valid `Bearer <token>` is returned (the bearer value is not
173
+ * assumed to be first). Returns `undefined` for anything malformed so the caller
174
+ * can emit a consistent 401 instead of throwing.
175
+ * @param authorization The raw `Authorization` header value.
176
+ * @returns The bearer token, or `undefined` if the header is absent or malformed.
177
+ */
178
+ function extractBearerToken (authorization: string | string[] | undefined): string | undefined {
179
+ const values = Array.isArray(authorization) ? authorization : [authorization]
180
+ for (const value of values) {
181
+ const token = parseBearerValue(value)
182
+ if (token) {
183
+ return token
184
+ }
185
+ }
186
+ return undefined
187
+ }
188
+
92
189
  /**
93
190
  * Middleware to authorize JWT tokens.
94
191
  * @param authConfig The authentication configuration.
95
192
  * @returns An Express middleware function.
96
193
  */
97
194
  export const authorizeJWT = (authConfig: AuthConfiguration) => {
98
- return async function (req: Request, res: Response, next: NextFunction) {
195
+ return async function (req: Request, res: WebResponse, next: NextFunction) {
99
196
  let failed = false
100
197
  logger.debug('authorizing jwt')
101
198
  if (req.method !== 'POST' && req.method !== 'GET') {
@@ -103,9 +200,8 @@ export const authorizeJWT = (authConfig: AuthConfiguration) => {
103
200
  logger.warn('Method not allowed', req.method)
104
201
  res.status(405).send({ 'jwt-auth-error': 'Method not allowed' })
105
202
  } else {
106
- const authHeader = req.headers.authorization as string
107
- if (authHeader) {
108
- const token: string = authHeader.split(' ')[1] // Extract the token from the Bearer string
203
+ const token = extractBearerToken(req.headers.authorization)
204
+ if (token) {
109
205
  try {
110
206
  const user = await verifyToken(token, authConfig)
111
207
  logger.debug('token verified for ', user)
@@ -113,8 +209,21 @@ export const authorizeJWT = (authConfig: AuthConfiguration) => {
113
209
  } catch (err: Error | any) {
114
210
  failed = true
115
211
  logger.error(err)
116
- res.status(401).send({ 'jwt-auth-error': err.message })
212
+ // Emit only the human-readable description rather than the
213
+ // ExceptionHelper-formatted "[code] - description - helplink" string,
214
+ // so the wire format does not leak internal error codes or help links.
215
+ // Fall back to a stable string so a thrown non-Error (no description/
216
+ // message) never serializes to an empty `{}` and drops the detail.
217
+ const wireMessage: string = err?.description ?? err?.message ?? 'unauthorized'
218
+ res.status(401).send({ 'jwt-auth-error': wireMessage })
117
219
  }
220
+ } else if (hasAuthorizationHeader(req.headers.authorization)) {
221
+ // Header is present but not a well-formed `Bearer <token>` (e.g. wrong
222
+ // scheme, missing token, or an array value). Respond with a consistent
223
+ // 401 rather than letting malformed input throw before authorization.
224
+ failed = true
225
+ logger.warn('malformed authorization header')
226
+ res.status(401).send({ 'jwt-auth-error': 'invalid authorization header' })
118
227
  } else {
119
228
  if (!authConfig.clientId && process.env.NODE_ENV !== 'production') {
120
229
  logger.info('using anonymous auth')
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { AuthConfiguration, resolveAuthority } from '../authConfiguration'
7
+ import type { ConnectionMapItem } from '../settings'
8
+ import { AuthProvider } from '../authProvider'
9
+ import { ConnectionManager, defaultAuthProviderFactory } from '../connectionManager'
10
+
11
+ /**
12
+ * Connection manager backed by MSAL (and, for `EntraAuthSideCar` connections, the Entra sidecar
13
+ * provider). A thin convenience subclass of {@link ConnectionManager} that supplies the default
14
+ * provider factory and applies MSAL-specific connection defaults.
15
+ */
16
+ export class MsalConnectionManager extends ConnectionManager {
17
+ constructor (
18
+ connectionsConfigurations: Map<string, AuthConfiguration> = new Map(),
19
+ connectionsMap: ConnectionMapItem[] = [],
20
+ configuration: AuthConfiguration = {}) {
21
+ super(defaultAuthProviderFactory, connectionsConfigurations, connectionsMap, configuration)
22
+ }
23
+
24
+ protected applyConnectionDefaults (conn: AuthProvider): AuthProvider {
25
+ if (conn.connectionSettings) {
26
+ conn.connectionSettings.authorityEndpoint ??= conn.connectionSettings.authority || 'https://login.microsoftonline.com'
27
+ conn.connectionSettings.authority ??= conn.connectionSettings.authorityEndpoint
28
+ conn.connectionSettings.issuers ??= [
29
+ 'https://api.botframework.com',
30
+ `${resolveAuthority('https://sts.windows.net', conn.connectionSettings.tenantId)}/`,
31
+ `${resolveAuthority(conn.connectionSettings.authorityEndpoint, conn.connectionSettings.tenantId)}/v2.0`
32
+ ]
33
+ // For backward compatibility
34
+ if (conn.connectionSettings.federatedClientId) {
35
+ conn.connectionSettings.FICClientId = conn.connectionSettings.federatedClientId
36
+ }
37
+
38
+ if (conn.connectionSettings.scopes?.length) {
39
+ conn.connectionSettings.scope = conn.connectionSettings.scopes?.[0]
40
+ }
41
+
42
+ if (conn.connectionSettings.authorityEndpoint) {
43
+ conn.connectionSettings.authority = conn.connectionSettings.authorityEndpoint
44
+ }
45
+
46
+ // .NET parity alias: keep altBlueprintConnectionName and alternateBlueprintConnectionName in sync.
47
+ conn.connectionSettings.altBlueprintConnectionName ??= conn.connectionSettings.alternateBlueprintConnectionName
48
+ if (conn.connectionSettings.altBlueprintConnectionName) {
49
+ conn.connectionSettings.alternateBlueprintConnectionName = conn.connectionSettings.altBlueprintConnectionName
50
+ }
51
+ }
52
+
53
+ return conn
54
+ }
55
+ }