@microsoft/agents-hosting 1.7.2 → 1.8.0-beta.36.gb033f0abc1
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/README.md +64 -2
- package/dist/package.json +3 -3
- package/dist/src/agent-client/agentClient.d.ts +18 -6
- package/dist/src/agent-client/agentClient.js +34 -18
- package/dist/src/agent-client/agentClient.js.map +1 -1
- package/dist/src/agent-client/agentResponseHandler.d.ts +14 -9
- package/dist/src/agent-client/agentResponseHandler.js +10 -5
- package/dist/src/agent-client/agentResponseHandler.js.map +1 -1
- package/dist/src/agent-client/createAgentResponseHandler.d.ts +7 -2
- package/dist/src/agent-client/createAgentResponseHandler.js +55 -8
- package/dist/src/agent-client/createAgentResponseHandler.js.map +1 -1
- package/dist/src/app/adaptiveCards/activityValueParsers.d.ts +1 -4
- package/dist/src/app/adaptiveCards/activityValueParsers.js +2 -5
- package/dist/src/app/adaptiveCards/activityValueParsers.js.map +1 -1
- package/dist/src/app/adaptiveCards/adaptiveCardsActions.d.ts +18 -0
- package/dist/src/app/adaptiveCards/adaptiveCardsActions.js +53 -23
- package/dist/src/app/adaptiveCards/adaptiveCardsActions.js.map +1 -1
- package/dist/src/app/agentApplication.d.ts +3 -0
- package/dist/src/app/agentApplication.js +140 -81
- package/dist/src/app/agentApplication.js.map +1 -1
- package/dist/src/app/attachmentDownloader.d.ts +4 -1
- package/dist/src/app/attachmentDownloader.js +27 -5
- package/dist/src/app/attachmentDownloader.js.map +1 -1
- package/dist/src/app/auth/handlers/agenticAuthorization.d.ts +1 -1
- package/dist/src/app/auth/handlers/azureBotAuthorization.d.ts +1 -1
- package/dist/src/app/auth/types.d.ts +3 -4
- package/dist/src/app/index.d.ts +2 -0
- package/dist/src/app/index.js.map +1 -1
- package/dist/src/app/streaming/streamingResponse.d.ts +56 -0
- package/dist/src/app/streaming/streamingResponse.js +250 -26
- package/dist/src/app/streaming/streamingResponse.js.map +1 -1
- package/dist/src/app/teamsAttachmentDownloader.d.ts +3 -1
- package/dist/src/app/teamsAttachmentDownloader.js +12 -1
- package/dist/src/app/teamsAttachmentDownloader.js.map +1 -1
- package/dist/src/auth/authConfiguration.js +12 -0
- package/dist/src/auth/authConfiguration.js.map +1 -1
- package/dist/src/auth/index.d.ts +2 -1
- package/dist/src/auth/index.js +2 -1
- package/dist/src/auth/index.js.map +1 -1
- package/dist/src/auth/jwt-middleware.d.ts +6 -0
- package/dist/src/auth/jwt-middleware.js +263 -13
- package/dist/src/auth/jwt-middleware.js.map +1 -1
- package/dist/src/auth/msal/msalConnectionSettings.d.ts +8 -0
- package/dist/src/auth/msal/msalHttpRetryHandlerHelper.d.ts +34 -0
- package/dist/src/auth/msal/msalHttpRetryHandlerHelper.js +126 -0
- package/dist/src/auth/msal/msalHttpRetryHandlerHelper.js.map +1 -0
- package/dist/src/auth/msal/msalTokenProvider.d.ts +1 -0
- package/dist/src/auth/msal/msalTokenProvider.js +23 -14
- package/dist/src/auth/msal/msalTokenProvider.js.map +1 -1
- package/dist/src/auth/settings.d.ts +18 -0
- package/dist/src/auth/settings.js +8 -2
- package/dist/src/auth/settings.js.map +1 -1
- package/dist/src/cloudAdapter.d.ts +29 -2
- package/dist/src/cloudAdapter.js +44 -2
- package/dist/src/cloudAdapter.js.map +1 -1
- package/dist/src/errorHelper.js +137 -101
- package/dist/src/errorHelper.js.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/invoke/adaptiveCardInvokeValue.d.ts +5 -0
- package/dist/src/oauth/userTokenClient.d.ts +0 -1
- package/dist/src/oauth/userTokenClient.js +0 -1
- package/dist/src/oauth/userTokenClient.js.map +1 -1
- package/dist/src/observability/traces.d.ts +6 -0
- package/dist/src/observability/traces.js +20 -2
- package/dist/src/observability/traces.js.map +1 -1
- package/dist/src/outboundHostValidator.d.ts +47 -0
- package/dist/src/outboundHostValidator.js +149 -0
- package/dist/src/outboundHostValidator.js.map +1 -0
- package/dist/src/state/agentState.d.ts +5 -0
- package/dist/src/state/agentState.js +16 -3
- package/dist/src/state/agentState.js.map +1 -1
- package/package.json +3 -3
- package/src/agent-client/agentClient.ts +46 -23
- package/src/agent-client/agentResponseHandler.ts +14 -9
- package/src/agent-client/createAgentResponseHandler.ts +70 -9
- package/src/app/adaptiveCards/activityValueParsers.ts +3 -9
- package/src/app/adaptiveCards/adaptiveCardsActions.ts +52 -24
- package/src/app/agentApplication.ts +149 -77
- package/src/app/attachmentDownloader.ts +25 -5
- package/src/app/auth/handlers/agenticAuthorization.ts +1 -1
- package/src/app/auth/handlers/azureBotAuthorization.ts +1 -1
- package/src/app/auth/types.ts +3 -3
- package/src/app/index.ts +8 -0
- package/src/app/streaming/streamingResponse.ts +276 -22
- package/src/app/teamsAttachmentDownloader.ts +11 -1
- package/src/auth/authConfiguration.ts +12 -0
- package/src/auth/index.ts +2 -1
- package/src/auth/jwt-middleware.ts +298 -14
- package/src/auth/msal/msalConnectionSettings.ts +10 -0
- package/src/auth/msal/msalHttpRetryHandlerHelper.ts +161 -0
- package/src/auth/msal/msalTokenProvider.ts +32 -14
- package/src/auth/settings.ts +18 -3
- package/src/cloudAdapter.ts +56 -3
- package/src/errorHelper.ts +155 -114
- package/src/index.ts +1 -0
- package/src/invoke/adaptiveCardInvokeValue.ts +5 -0
- package/src/oauth/userTokenClient.ts +0 -1
- package/src/observability/traces.ts +19 -1
- package/src/outboundHostValidator.ts +172 -0
- package/src/state/agentState.ts +24 -6
|
@@ -15,6 +15,7 @@ import crypto from 'crypto'
|
|
|
15
15
|
import { AuthenticationTraceDefinitions } from '../../observability'
|
|
16
16
|
import { ExceptionHelper } from '@microsoft/agents-activity'
|
|
17
17
|
import { Errors } from '../../errorHelper'
|
|
18
|
+
import { MsalHttpRetryHandlerHelper, normalizeMsalRetryCount } from './msalHttpRetryHandlerHelper'
|
|
18
19
|
|
|
19
20
|
const audience = 'api://AzureADTokenExchange'
|
|
20
21
|
const logger = debug('agents:msal')
|
|
@@ -203,12 +204,14 @@ export class MsalTokenProvider implements AuthProvider {
|
|
|
203
204
|
}
|
|
204
205
|
|
|
205
206
|
private getClientSecretClient (authConfig: AuthConfiguration): ConfidentialClientApplication {
|
|
207
|
+
const retryCount = normalizeMsalRetryCount(authConfig.msalRetryCount)
|
|
206
208
|
const cacheKey = MsalTokenProvider.cacheKey(
|
|
207
209
|
'confidential-client',
|
|
208
210
|
AuthType.ClientSecret,
|
|
209
211
|
authConfig.clientId,
|
|
210
212
|
resolveAuthorityUtil(authConfig.authorityEndpoint ?? authConfig.authority, authConfig.tenantId),
|
|
211
|
-
MsalTokenProvider.digest(authConfig.clientSecret)
|
|
213
|
+
MsalTokenProvider.digest(authConfig.clientSecret),
|
|
214
|
+
retryCount
|
|
212
215
|
)
|
|
213
216
|
|
|
214
217
|
return this.getOrCreateConfidentialClient(cacheKey, () => new ConfidentialClientApplication({
|
|
@@ -217,11 +220,12 @@ export class MsalTokenProvider implements AuthProvider {
|
|
|
217
220
|
authority: resolveAuthorityUtil(authConfig.authorityEndpoint ?? authConfig.authority, authConfig.tenantId),
|
|
218
221
|
clientSecret: authConfig.clientSecret
|
|
219
222
|
},
|
|
220
|
-
system: this.
|
|
223
|
+
system: this.getSystemOptions(authConfig)
|
|
221
224
|
}))
|
|
222
225
|
}
|
|
223
226
|
|
|
224
227
|
private getCertificateClient (authConfig: AuthConfiguration): ConfidentialClientApplication {
|
|
228
|
+
const retryCount = normalizeMsalRetryCount(authConfig.msalRetryCount)
|
|
225
229
|
const cacheKey = MsalTokenProvider.cacheKey(
|
|
226
230
|
'confidential-client',
|
|
227
231
|
authConfig.authType ?? AuthType.Certificate,
|
|
@@ -229,7 +233,8 @@ export class MsalTokenProvider implements AuthProvider {
|
|
|
229
233
|
resolveAuthorityUtil(authConfig.authorityEndpoint ?? authConfig.authority, authConfig.tenantId),
|
|
230
234
|
this.getFileCacheIdentity(authConfig.certPemFile),
|
|
231
235
|
this.getFileCacheIdentity(authConfig.certKeyFile),
|
|
232
|
-
authConfig.sendX5C
|
|
236
|
+
authConfig.sendX5C,
|
|
237
|
+
retryCount
|
|
233
238
|
)
|
|
234
239
|
|
|
235
240
|
return this.getOrCreateConfidentialClient(cacheKey, () => {
|
|
@@ -258,7 +263,7 @@ export class MsalTokenProvider implements AuthProvider {
|
|
|
258
263
|
x5c: pemFile.toString()
|
|
259
264
|
}
|
|
260
265
|
},
|
|
261
|
-
system: this.
|
|
266
|
+
system: this.getSystemOptions(authConfig)
|
|
262
267
|
})
|
|
263
268
|
})
|
|
264
269
|
}
|
|
@@ -434,7 +439,7 @@ export class MsalTokenProvider implements AuthProvider {
|
|
|
434
439
|
clientAssertion: appToken,
|
|
435
440
|
authority: this.resolveAuthority(tenantId),
|
|
436
441
|
},
|
|
437
|
-
system: this.
|
|
442
|
+
system: this.getSystemOptions(this.connectionSettings)
|
|
438
443
|
})
|
|
439
444
|
|
|
440
445
|
const token = await cca.acquireTokenByClientCredential({
|
|
@@ -613,7 +618,7 @@ export class MsalTokenProvider implements AuthProvider {
|
|
|
613
618
|
managedIdentityIdParams: {
|
|
614
619
|
userAssignedClientId: this.connectionSettings.clientId
|
|
615
620
|
},
|
|
616
|
-
system: this.
|
|
621
|
+
system: this.getSystemOptions(this.connectionSettings)
|
|
617
622
|
})
|
|
618
623
|
const tokenResult = await msiApp.acquireToken({ resource })
|
|
619
624
|
if (!tokenResult?.accessToken) {
|
|
@@ -638,7 +643,10 @@ export class MsalTokenProvider implements AuthProvider {
|
|
|
638
643
|
if (!this.connectionSettings.federatedClientId && !this.connectionSettings.FICClientId) {
|
|
639
644
|
throw ExceptionHelper.generateException(Error, Errors.FICClientIdRequired)
|
|
640
645
|
}
|
|
641
|
-
clientAssertion = await this.fetchExternalToken(
|
|
646
|
+
clientAssertion = await this.fetchExternalToken(
|
|
647
|
+
this.connectionSettings.federatedClientId as string || this.connectionSettings.FICClientId as string,
|
|
648
|
+
this.connectionSettings
|
|
649
|
+
)
|
|
642
650
|
break
|
|
643
651
|
case AuthType.Certificate:
|
|
644
652
|
case AuthType.CertificateSubjectName:
|
|
@@ -688,6 +696,13 @@ export class MsalTokenProvider implements AuthProvider {
|
|
|
688
696
|
}
|
|
689
697
|
}
|
|
690
698
|
|
|
699
|
+
private getSystemOptions (authConfig?: AuthConfiguration): NodeSystemOptions {
|
|
700
|
+
return {
|
|
701
|
+
...this.sysOptions,
|
|
702
|
+
networkClient: new MsalHttpRetryHandlerHelper(undefined, normalizeMsalRetryCount(authConfig?.msalRetryCount))
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
691
706
|
/**
|
|
692
707
|
* Generates the client assertion using the provided certificate.
|
|
693
708
|
* @param authConfig The authentication configuration.
|
|
@@ -742,7 +757,7 @@ export class MsalTokenProvider implements AuthProvider {
|
|
|
742
757
|
managedIdentityIdParams: {
|
|
743
758
|
userAssignedClientId: authConfig.clientId || ''
|
|
744
759
|
},
|
|
745
|
-
system: this.
|
|
760
|
+
system: this.getSystemOptions(authConfig)
|
|
746
761
|
})
|
|
747
762
|
const token = await mia.acquireToken({
|
|
748
763
|
resource: scope
|
|
@@ -758,7 +773,7 @@ export class MsalTokenProvider implements AuthProvider {
|
|
|
758
773
|
*/
|
|
759
774
|
private async acquireTokenWithSystemAssignedIdentity (authConfig: AuthConfiguration, scope: string) {
|
|
760
775
|
const mia = new ManagedIdentityApplication({
|
|
761
|
-
system: this.
|
|
776
|
+
system: this.getSystemOptions(authConfig)
|
|
762
777
|
})
|
|
763
778
|
const token = await mia.acquireToken({
|
|
764
779
|
resource: scope
|
|
@@ -812,14 +827,17 @@ export class MsalTokenProvider implements AuthProvider {
|
|
|
812
827
|
*/
|
|
813
828
|
private async acquireAccessTokenViaFIC (authConfig: AuthConfiguration, scope: string) : Promise<string> {
|
|
814
829
|
const scopes = [`${scope}/.default`]
|
|
815
|
-
const clientAssertion = await this.fetchExternalToken(
|
|
830
|
+
const clientAssertion = await this.fetchExternalToken(
|
|
831
|
+
authConfig.federatedClientId as string || authConfig.FICClientId as string,
|
|
832
|
+
authConfig
|
|
833
|
+
)
|
|
816
834
|
const cca = new ConfidentialClientApplication({
|
|
817
835
|
auth: {
|
|
818
836
|
clientId: authConfig.clientId as string,
|
|
819
837
|
authority: `${authConfig.authorityEndpoint ?? authConfig.authority}/${authConfig.tenantId}`,
|
|
820
838
|
clientAssertion
|
|
821
839
|
},
|
|
822
|
-
system: this.
|
|
840
|
+
system: this.getSystemOptions(authConfig)
|
|
823
841
|
})
|
|
824
842
|
const token = await cca.acquireTokenByClientCredential({ scopes, azureRegion: authConfig.azureRegion })
|
|
825
843
|
logger.debug('got token using FIC client assertion')
|
|
@@ -845,7 +863,7 @@ export class MsalTokenProvider implements AuthProvider {
|
|
|
845
863
|
authority: `https://login.microsoftonline.com/${authConfig.tenantId}`,
|
|
846
864
|
clientAssertion
|
|
847
865
|
},
|
|
848
|
-
system: this.
|
|
866
|
+
system: this.getSystemOptions(authConfig)
|
|
849
867
|
})
|
|
850
868
|
const token = await cca.acquireTokenByClientCredential({ scopes, azureRegion: authConfig.azureRegion })
|
|
851
869
|
logger.debug('got token using WID client assertion')
|
|
@@ -860,12 +878,12 @@ export class MsalTokenProvider implements AuthProvider {
|
|
|
860
878
|
* @param FICClientId The FIC client ID.
|
|
861
879
|
* @returns A promise that resolves to the external token.
|
|
862
880
|
*/
|
|
863
|
-
private async fetchExternalToken (FICClientId: string) : Promise<string> {
|
|
881
|
+
private async fetchExternalToken (FICClientId: string, authConfig?: AuthConfiguration) : Promise<string> {
|
|
864
882
|
const managedIdentityClientAssertion = new ManagedIdentityApplication({
|
|
865
883
|
managedIdentityIdParams: {
|
|
866
884
|
userAssignedClientId: FICClientId
|
|
867
885
|
},
|
|
868
|
-
system: this.
|
|
886
|
+
system: this.getSystemOptions(authConfig ?? this.connectionSettings)
|
|
869
887
|
}
|
|
870
888
|
)
|
|
871
889
|
const response = await managedIdentityClientAssertion.acquireToken({
|
package/src/auth/settings.ts
CHANGED
|
@@ -170,6 +170,12 @@ export function resolveAuthority (authority?: string, tenantId?: string): string
|
|
|
170
170
|
return `${base}/${tenantId ?? 'botframework.com'}`
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
+
function getEffectiveTenant (tenantId: string | undefined, authority: string): string | undefined {
|
|
174
|
+
const resolvedAuthority = new URL(trimTrailingSlashes(authority))
|
|
175
|
+
const embeddedTenant = resolvedAuthority.pathname.split('/').filter(Boolean).pop()
|
|
176
|
+
return embeddedTenant ?? tenantId
|
|
177
|
+
}
|
|
178
|
+
|
|
173
179
|
function trimTrailingSlashes (value: string): string {
|
|
174
180
|
let end = value.length
|
|
175
181
|
while (end > 0 && value.charCodeAt(end - 1) === 47) {
|
|
@@ -179,14 +185,14 @@ function trimTrailingSlashes (value: string): string {
|
|
|
179
185
|
return end === value.length ? value : value.slice(0, end)
|
|
180
186
|
}
|
|
181
187
|
|
|
182
|
-
function getDefaultIssuers (tenantId: string, authority: string) : string[] {
|
|
188
|
+
export function getDefaultIssuers (tenantId: string, authority: string) : string[] {
|
|
183
189
|
// Convert empty string to undefined so resolveAuthority applies its 'botframework.com' default
|
|
184
|
-
const t = tenantId || undefined
|
|
190
|
+
const t = getEffectiveTenant(tenantId || undefined, authority)
|
|
185
191
|
if (!t) {
|
|
186
192
|
logger.warn('tenantId is not configured, defaulting to botframework.com')
|
|
187
193
|
}
|
|
188
194
|
return [
|
|
189
|
-
'https://api.botframework.com',
|
|
195
|
+
/login\.microsoftonline\.us/i.test(authority) ? 'https://api.botframework.us' : 'https://api.botframework.com',
|
|
190
196
|
`${resolveAuthority('https://sts.windows.net', t)}/`,
|
|
191
197
|
`${resolveAuthority(authority, t)}/v2.0`
|
|
192
198
|
]
|
|
@@ -223,6 +229,15 @@ export interface ConnectionSettingsBase {
|
|
|
223
229
|
*/
|
|
224
230
|
issuers?: string[]
|
|
225
231
|
|
|
232
|
+
/**
|
|
233
|
+
* Whether to validate the token issuer against {@link issuers}.
|
|
234
|
+
*
|
|
235
|
+
* @remarks
|
|
236
|
+
* Disabled by default for backward compatibility. Tenant-to-issuer binding is always applied
|
|
237
|
+
* independently when both claims contain comparable tenant GUIDs.
|
|
238
|
+
*/
|
|
239
|
+
validateIssuer?: boolean
|
|
240
|
+
|
|
226
241
|
/**
|
|
227
242
|
* The connection name for the authentication configuration.
|
|
228
243
|
*/
|
package/src/cloudAdapter.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { AgentHandler, INVOKE_RESPONSE_KEY } from './activityHandler'
|
|
|
7
7
|
import { BaseAdapter } from './baseAdapter'
|
|
8
8
|
import { TurnContext } from './turnContext'
|
|
9
9
|
import { Request } from './auth/request'
|
|
10
|
-
import { WebResponse } from './interfaces/webResponse'
|
|
10
|
+
import { NextFunction, WebResponse } from './interfaces/webResponse'
|
|
11
11
|
import { ConnectorClient } from './connector-client/connectorClient'
|
|
12
12
|
import { AuthConfiguration, getAuthConfigWithDefaults } from './auth/authConfiguration'
|
|
13
13
|
import { AuthProvider } from './auth/authProvider'
|
|
@@ -32,6 +32,8 @@ import { parseBooleanEnv, suggestClosest } from './utils/env'
|
|
|
32
32
|
import { trace } from '@microsoft/agents-telemetry'
|
|
33
33
|
import { AdapterTraceDefinitions } from './observability'
|
|
34
34
|
import { applyAgenticHeaders } from './getProductInfo'
|
|
35
|
+
import { createOutboundHostValidator, OutboundUrlPolicy } from './outboundHostValidator'
|
|
36
|
+
import { authorizeJWT } from './auth/jwt-middleware'
|
|
35
37
|
|
|
36
38
|
const logger = debug('agents:cloud-adapter')
|
|
37
39
|
|
|
@@ -80,6 +82,9 @@ export interface CloudAdapterOptions {
|
|
|
80
82
|
* for one service URL tries to send activities targeting a different one.
|
|
81
83
|
*
|
|
82
84
|
* Env var: `CloudAdapterOptions__validateServiceUrl`.
|
|
85
|
+
*
|
|
86
|
+
* @deprecated Configure {@link OutboundUrlPolicy} instead. This option
|
|
87
|
+
* remains available for backward compatibility with claim-only validation.
|
|
83
88
|
*/
|
|
84
89
|
validateServiceUrl?: boolean
|
|
85
90
|
}
|
|
@@ -238,6 +243,7 @@ function truncateActivityForLog (activity: unknown, max = 1024): string {
|
|
|
238
243
|
|
|
239
244
|
export class CloudAdapter extends BaseAdapter {
|
|
240
245
|
protected readonly authConfig: AuthConfiguration
|
|
246
|
+
private readonly jwtMiddleware: ReturnType<typeof authorizeJWT>
|
|
241
247
|
protected _agentName?: string
|
|
242
248
|
|
|
243
249
|
/**
|
|
@@ -246,6 +252,7 @@ export class CloudAdapter extends BaseAdapter {
|
|
|
246
252
|
connectionManager: Connections
|
|
247
253
|
|
|
248
254
|
private readonly _options: Required<CloudAdapterOptions>
|
|
255
|
+
private readonly _hostValidator: OutboundUrlPolicy
|
|
249
256
|
|
|
250
257
|
/**
|
|
251
258
|
* Creates an instance of CloudAdapter.
|
|
@@ -253,12 +260,17 @@ export class CloudAdapter extends BaseAdapter {
|
|
|
253
260
|
* @param authProvider - No longer used.
|
|
254
261
|
* @param userTokenClient - No longer used.
|
|
255
262
|
* @param options - Optional runtime behavior overrides. See {@link CloudAdapterOptions}.
|
|
263
|
+
* @param outboundHostValidator - Optional policy for validating outbound `Activity.serviceUrl` hosts.
|
|
264
|
+
* When omitted, the policy is loaded from `OutboundHostValidator__*` environment variables
|
|
265
|
+
* and remains disabled by default.
|
|
256
266
|
*/
|
|
257
|
-
constructor (authConfig?: AuthConfiguration, authProvider?: AuthProvider, userTokenClient?: UserTokenClient, options?: CloudAdapterOptions) {
|
|
267
|
+
constructor (authConfig?: AuthConfiguration, authProvider?: AuthProvider, userTokenClient?: UserTokenClient, options?: CloudAdapterOptions, outboundHostValidator?: OutboundUrlPolicy) {
|
|
258
268
|
super()
|
|
259
269
|
this.authConfig = authConfig = getAuthConfigWithDefaults(authConfig)
|
|
270
|
+
this.jwtMiddleware = authorizeJWT(authConfig)
|
|
260
271
|
this.connectionManager = new MsalConnectionManager(undefined, undefined, authConfig)
|
|
261
272
|
this._options = resolveCloudAdapterOptions(options)
|
|
273
|
+
this._hostValidator = outboundHostValidator ?? createOutboundHostValidator()
|
|
262
274
|
|
|
263
275
|
// Install a CloudAdapter-aware default `onTurnError` that honors
|
|
264
276
|
// `emitStackTrace`. The base class default only logs the message; we
|
|
@@ -321,6 +333,8 @@ export class CloudAdapter extends BaseAdapter {
|
|
|
321
333
|
identity: JwtPayload,
|
|
322
334
|
headers?: HeaderPropagationCollection
|
|
323
335
|
): Promise<ConnectorClient> {
|
|
336
|
+
this.ensureOutboundServiceUrlAllowed(serviceUrl)
|
|
337
|
+
|
|
324
338
|
return trace(AdapterTraceDefinitions.createConnectorClient, async ({ record }) => {
|
|
325
339
|
record({ serviceUrl, scopes: [scope] })
|
|
326
340
|
|
|
@@ -349,6 +363,8 @@ export class CloudAdapter extends BaseAdapter {
|
|
|
349
363
|
identity: JwtPayload,
|
|
350
364
|
activity: Activity,
|
|
351
365
|
headers?: HeaderPropagationCollection) {
|
|
366
|
+
this.ensureOutboundServiceUrlAllowed(activity.serviceUrl)
|
|
367
|
+
|
|
352
368
|
return trace(AdapterTraceDefinitions.createConnectorClient, async ({ record }) => {
|
|
353
369
|
if (!identity?.aud) {
|
|
354
370
|
// anonymous
|
|
@@ -425,6 +441,24 @@ export class CloudAdapter extends BaseAdapter {
|
|
|
425
441
|
} as JwtPayload
|
|
426
442
|
}
|
|
427
443
|
|
|
444
|
+
/**
|
|
445
|
+
* Authorizes an incoming web request using this adapter's resolved authentication configuration.
|
|
446
|
+
* @param req The incoming request.
|
|
447
|
+
* @param res The outgoing response.
|
|
448
|
+
* @param next Callback invoked after successful authorization.
|
|
449
|
+
*/
|
|
450
|
+
public async authorizeRequest (req: Request, res: WebResponse, next: NextFunction): Promise<void> {
|
|
451
|
+
await this.jwtMiddleware(req, res, next)
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Gets the client ID used by this adapter's default authentication connection.
|
|
456
|
+
* @returns The configured client ID, or `undefined` for anonymous development configuration.
|
|
457
|
+
*/
|
|
458
|
+
public getClientId (): string | undefined {
|
|
459
|
+
return this.authConfig.clientId
|
|
460
|
+
}
|
|
461
|
+
|
|
428
462
|
/**
|
|
429
463
|
* Sets the agent name for M365 agent header propagation.
|
|
430
464
|
* @param agentName The human-friendly agent name to set for header propagation.
|
|
@@ -692,6 +726,8 @@ export class CloudAdapter extends BaseAdapter {
|
|
|
692
726
|
* should be rejected with a 400.
|
|
693
727
|
*/
|
|
694
728
|
private validateServiceUrl (identity: JwtPayload | undefined, activity: Activity): boolean {
|
|
729
|
+
if (!this.isOutboundServiceUrlAllowed(activity.serviceUrl)) return false
|
|
730
|
+
|
|
695
731
|
if (!identity) return true
|
|
696
732
|
if (!activity.serviceUrl) return true
|
|
697
733
|
|
|
@@ -709,7 +745,7 @@ export class CloudAdapter extends BaseAdapter {
|
|
|
709
745
|
|
|
710
746
|
const safeClaim = sanitizeForLog(claimValue)
|
|
711
747
|
const safeServiceUrl = sanitizeForLog(activity.serviceUrl)
|
|
712
|
-
if (this._options.validateServiceUrl) {
|
|
748
|
+
if (this._hostValidator.enabled || this._options.validateServiceUrl) {
|
|
713
749
|
logger.error(`Invalid service URL Claim='${safeClaim}', ServiceUrl='${safeServiceUrl}'`)
|
|
714
750
|
return false
|
|
715
751
|
}
|
|
@@ -717,6 +753,23 @@ export class CloudAdapter extends BaseAdapter {
|
|
|
717
753
|
return true
|
|
718
754
|
}
|
|
719
755
|
|
|
756
|
+
/**
|
|
757
|
+
* Applies the shared outbound host policy.
|
|
758
|
+
*/
|
|
759
|
+
private isOutboundServiceUrlAllowed (serviceUrl: string | undefined): boolean {
|
|
760
|
+
if (!this._hostValidator.enabled || !serviceUrl || this._hostValidator.isAllowed(serviceUrl)) return true
|
|
761
|
+
|
|
762
|
+
logger.warn(`ServiceUrl host is not in the configured allowed hosts. ServiceUrl='${sanitizeForLog(serviceUrl)}'`)
|
|
763
|
+
return false
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
/** Ensures connector-client creation cannot bypass the outbound host policy. */
|
|
767
|
+
private ensureOutboundServiceUrlAllowed (serviceUrl: string | undefined): void {
|
|
768
|
+
if (!this.isOutboundServiceUrlAllowed(serviceUrl)) {
|
|
769
|
+
throw ExceptionHelper.generateException(Error, Errors.OutboundServiceUrlNotAllowed)
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
720
773
|
/**
|
|
721
774
|
* Updates an activity.
|
|
722
775
|
* @param context - The TurnContext for the current turn.
|
package/src/errorHelper.ts
CHANGED
|
@@ -156,6 +156,14 @@ export const Errors: { [key: string]: AgentErrorDefinition } = {
|
|
|
156
156
|
description: 'serviceUrl must be a non-empty string'
|
|
157
157
|
},
|
|
158
158
|
|
|
159
|
+
/**
|
|
160
|
+
* Error thrown when the outbound service URL is denied by the configured host policy.
|
|
161
|
+
*/
|
|
162
|
+
OutboundServiceUrlNotAllowed: {
|
|
163
|
+
code: -120175,
|
|
164
|
+
description: 'serviceUrl host is not in the configured allowed hosts.'
|
|
165
|
+
},
|
|
166
|
+
|
|
159
167
|
/**
|
|
160
168
|
* Error thrown when conversationParameters must be defined.
|
|
161
169
|
*/
|
|
@@ -416,6 +424,22 @@ export const Errors: { [key: string]: AgentErrorDefinition } = {
|
|
|
416
424
|
description: 'Identity is required to get the token provider.'
|
|
417
425
|
},
|
|
418
426
|
|
|
427
|
+
/**
|
|
428
|
+
* Error thrown when an MSAL HTTP request cannot be completed.
|
|
429
|
+
*/
|
|
430
|
+
MsalHttpRequestFailed: {
|
|
431
|
+
code: -120391,
|
|
432
|
+
description: 'MSAL HTTP request failed: {message}'
|
|
433
|
+
},
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Error thrown when an MSAL HTTP response body cannot be parsed as JSON.
|
|
437
|
+
*/
|
|
438
|
+
MsalResponseUnparsable: {
|
|
439
|
+
code: -120392,
|
|
440
|
+
description: 'MSAL returned an unparsable response body'
|
|
441
|
+
},
|
|
442
|
+
|
|
419
443
|
FailedToAcquireTokenUsingCertificate: {
|
|
420
444
|
code: -120395,
|
|
421
445
|
description: 'Failed to acquire token using certificate'
|
|
@@ -673,108 +697,6 @@ export const Errors: { [key: string]: AgentErrorDefinition } = {
|
|
|
673
697
|
description: 'Token request timed out after {timeoutMs} ms'
|
|
674
698
|
},
|
|
675
699
|
|
|
676
|
-
// ============================================================================
|
|
677
|
-
// Entra Agent ID Sidecar Authentication Errors (-120800 to -120849)
|
|
678
|
-
// ============================================================================
|
|
679
|
-
|
|
680
|
-
/**
|
|
681
|
-
* Error thrown when the Entra sidecar auth provider is used without connection settings.
|
|
682
|
-
*/
|
|
683
|
-
SidecarConnectionSettingsRequired: {
|
|
684
|
-
code: -120800,
|
|
685
|
-
description: 'Connection settings must be provided when using the Entra sidecar auth provider'
|
|
686
|
-
},
|
|
687
|
-
|
|
688
|
-
/**
|
|
689
|
-
* Error thrown when the resolved sidecar base URL is not a valid absolute URL.
|
|
690
|
-
*/
|
|
691
|
-
SidecarBaseUrlInvalid: {
|
|
692
|
-
code: -120801,
|
|
693
|
-
description: 'The resolved sidecar base URL `{url}` is not a valid absolute URL'
|
|
694
|
-
},
|
|
695
|
-
|
|
696
|
-
/**
|
|
697
|
-
* Error thrown when the resolved sidecar base URL does not use the http or https scheme.
|
|
698
|
-
*/
|
|
699
|
-
SidecarBaseUrlInsecureScheme: {
|
|
700
|
-
code: -120802,
|
|
701
|
-
description: 'The resolved sidecar base URL `{url}` must use the http or https scheme'
|
|
702
|
-
},
|
|
703
|
-
|
|
704
|
-
/**
|
|
705
|
-
* Error thrown when the resolved sidecar base URL contains userinfo (credentials).
|
|
706
|
-
*/
|
|
707
|
-
SidecarBaseUrlUserInfo: {
|
|
708
|
-
code: -120803,
|
|
709
|
-
description: 'The resolved sidecar base URL `{url}` must not contain userinfo (credentials)'
|
|
710
|
-
},
|
|
711
|
-
|
|
712
|
-
/**
|
|
713
|
-
* Error thrown when the resolved sidecar base URL points to a non-loopback, non-private address.
|
|
714
|
-
*/
|
|
715
|
-
SidecarBaseUrlNotLocal: {
|
|
716
|
-
code: -120804,
|
|
717
|
-
description: 'The resolved sidecar base URL `{url}` points to a non-loopback, non-private address. The Entra Agent ID sidecar (agent container) must be reachable only from within the agent\'s network boundary. Set `bypassLocalNetworkRestriction` to true in the connection configuration to override this safety check (UNSAFE: only for carefully validated private-network configurations)'
|
|
718
|
-
},
|
|
719
|
-
|
|
720
|
-
/**
|
|
721
|
-
* Error thrown when both AgentUsername and AgentUserId are supplied on a sidecar request (mutually exclusive).
|
|
722
|
-
*/
|
|
723
|
-
SidecarUserIdentityMutuallyExclusive: {
|
|
724
|
-
code: -120805,
|
|
725
|
-
description: 'AgentUsername and AgentUserId are mutually exclusive; set only one on the sidecar request options'
|
|
726
|
-
},
|
|
727
|
-
|
|
728
|
-
/**
|
|
729
|
-
* Error thrown when a sidecar request fails after exhausting all retry attempts.
|
|
730
|
-
*/
|
|
731
|
-
SidecarRequestFailed: {
|
|
732
|
-
code: -120806,
|
|
733
|
-
description: 'Sidecar request failed after {attempts} attempt(s): {message}'
|
|
734
|
-
},
|
|
735
|
-
|
|
736
|
-
/**
|
|
737
|
-
* Error thrown when the sidecar responds with a non-success status code.
|
|
738
|
-
*/
|
|
739
|
-
SidecarErrorResponse: {
|
|
740
|
-
code: -120807,
|
|
741
|
-
description: 'Sidecar returned error status {status}: {message}'
|
|
742
|
-
},
|
|
743
|
-
|
|
744
|
-
/**
|
|
745
|
-
* Error thrown when the sidecar response is missing the authorizationHeader field.
|
|
746
|
-
*/
|
|
747
|
-
SidecarResponseMissingAuthorizationHeader: {
|
|
748
|
-
code: -120808,
|
|
749
|
-
description: 'Sidecar response missing `authorizationHeader` field'
|
|
750
|
-
},
|
|
751
|
-
|
|
752
|
-
/**
|
|
753
|
-
* Error thrown when the sidecar response body cannot be parsed.
|
|
754
|
-
*/
|
|
755
|
-
SidecarResponseUnparsable: {
|
|
756
|
-
code: -120809,
|
|
757
|
-
description: 'Sidecar returned an unparsable response body'
|
|
758
|
-
},
|
|
759
|
-
|
|
760
|
-
/**
|
|
761
|
-
* Error thrown when acquireTokenOnBehalfOf is called on the sidecar provider, which does not support OBO in Phase 1.
|
|
762
|
-
*/
|
|
763
|
-
OnBehalfOfNotSupportedBySidecar: {
|
|
764
|
-
code: -120810,
|
|
765
|
-
description: 'acquireTokenOnBehalfOf is not supported by the Entra sidecar auth provider in Phase 1'
|
|
766
|
-
},
|
|
767
|
-
|
|
768
|
-
/**
|
|
769
|
-
* Error thrown when the sidecar returns a token with an authorization scheme other than Bearer.
|
|
770
|
-
* The hosting stack always transmits tokens as `Bearer {token}`, so a non-Bearer scheme cannot be
|
|
771
|
-
* honored and is rejected rather than silently sent as an invalid Authorization header.
|
|
772
|
-
*/
|
|
773
|
-
SidecarUnsupportedAuthScheme: {
|
|
774
|
-
code: -120811,
|
|
775
|
-
description: 'The Entra Agent ID sidecar returned an unsupported authorization scheme `{scheme}`. Only the Bearer scheme is supported'
|
|
776
|
-
},
|
|
777
|
-
|
|
778
700
|
// ============================================================================
|
|
779
701
|
// Agent and Client Errors (-120600 to -120650)
|
|
780
702
|
// ============================================================================
|
|
@@ -1012,32 +934,105 @@ export const Errors: { [key: string]: AgentErrorDefinition } = {
|
|
|
1012
934
|
},
|
|
1013
935
|
|
|
1014
936
|
// ============================================================================
|
|
1015
|
-
//
|
|
937
|
+
// Entra Agent ID Sidecar Authentication Errors (-120800 to -120811)
|
|
1016
938
|
// ============================================================================
|
|
1017
939
|
|
|
1018
940
|
/**
|
|
1019
|
-
* Error thrown
|
|
1020
|
-
* Indicates the hosting layer (e.g. express.json() or Fastify's JSON parser) was not configured.
|
|
941
|
+
* Error thrown when the Entra sidecar auth provider is used without connection settings.
|
|
1021
942
|
*/
|
|
1022
|
-
|
|
943
|
+
SidecarConnectionSettingsRequired: {
|
|
1023
944
|
code: -120800,
|
|
1024
|
-
description: '
|
|
945
|
+
description: 'Connection settings must be provided when using the Entra sidecar auth provider'
|
|
1025
946
|
},
|
|
1026
947
|
|
|
1027
948
|
/**
|
|
1028
|
-
* Error thrown
|
|
949
|
+
* Error thrown when the resolved sidecar base URL is not a valid absolute URL.
|
|
1029
950
|
*/
|
|
1030
|
-
|
|
951
|
+
SidecarBaseUrlInvalid: {
|
|
952
|
+
code: -120801,
|
|
953
|
+
description: 'The resolved sidecar base URL `{url}` is not a valid absolute URL'
|
|
954
|
+
},
|
|
955
|
+
|
|
956
|
+
/**
|
|
957
|
+
* Error thrown when the resolved sidecar base URL does not use the http or https scheme.
|
|
958
|
+
*/
|
|
959
|
+
SidecarBaseUrlInsecureScheme: {
|
|
960
|
+
code: -120802,
|
|
961
|
+
description: 'The resolved sidecar base URL `{url}` must use the http or https scheme'
|
|
962
|
+
},
|
|
963
|
+
|
|
964
|
+
/**
|
|
965
|
+
* Error thrown when the resolved sidecar base URL contains userinfo (credentials).
|
|
966
|
+
*/
|
|
967
|
+
SidecarBaseUrlUserInfo: {
|
|
968
|
+
code: -120803,
|
|
969
|
+
description: 'The resolved sidecar base URL `{url}` must not contain userinfo (credentials)'
|
|
970
|
+
},
|
|
971
|
+
|
|
972
|
+
/**
|
|
973
|
+
* Error thrown when the resolved sidecar base URL points to a non-loopback, non-private address.
|
|
974
|
+
*/
|
|
975
|
+
SidecarBaseUrlNotLocal: {
|
|
976
|
+
code: -120804,
|
|
977
|
+
description: 'The resolved sidecar base URL `{url}` points to a non-loopback, non-private address. The Entra Agent ID sidecar (agent container) must be reachable only from within the agent\'s network boundary. Set `bypassLocalNetworkRestriction` to true in the connection configuration to override this safety check (UNSAFE: only for carefully validated private-network configurations)'
|
|
978
|
+
},
|
|
979
|
+
|
|
980
|
+
/**
|
|
981
|
+
* Error thrown when both AgentUsername and AgentUserId are supplied on a sidecar request (mutually exclusive).
|
|
982
|
+
*/
|
|
983
|
+
SidecarUserIdentityMutuallyExclusive: {
|
|
984
|
+
code: -120805,
|
|
985
|
+
description: 'AgentUsername and AgentUserId are mutually exclusive; set only one on the sidecar request options'
|
|
986
|
+
},
|
|
987
|
+
|
|
988
|
+
/**
|
|
989
|
+
* Error thrown when a sidecar request fails after exhausting all retry attempts.
|
|
990
|
+
*/
|
|
991
|
+
SidecarRequestFailed: {
|
|
992
|
+
code: -120806,
|
|
993
|
+
description: 'Sidecar request failed after {attempts} attempt(s): {message}'
|
|
994
|
+
},
|
|
995
|
+
|
|
996
|
+
/**
|
|
997
|
+
* Error thrown when the sidecar responds with a non-success status code.
|
|
998
|
+
*/
|
|
999
|
+
SidecarErrorResponse: {
|
|
1000
|
+
code: -120807,
|
|
1001
|
+
description: 'Sidecar returned error status {status}: {message}'
|
|
1002
|
+
},
|
|
1003
|
+
|
|
1004
|
+
/**
|
|
1005
|
+
* Error thrown when the sidecar response is missing the authorizationHeader field.
|
|
1006
|
+
*/
|
|
1007
|
+
SidecarResponseMissingAuthorizationHeader: {
|
|
1008
|
+
code: -120808,
|
|
1009
|
+
description: 'Sidecar response missing `authorizationHeader` field'
|
|
1010
|
+
},
|
|
1011
|
+
|
|
1012
|
+
/**
|
|
1013
|
+
* Error thrown when the sidecar response body cannot be parsed.
|
|
1014
|
+
*/
|
|
1015
|
+
SidecarResponseUnparsable: {
|
|
1016
|
+
code: -120809,
|
|
1017
|
+
description: 'Sidecar returned an unparsable response body'
|
|
1018
|
+
},
|
|
1019
|
+
|
|
1020
|
+
/**
|
|
1021
|
+
* Error thrown when acquireTokenOnBehalfOf is called on the sidecar provider, which does not support OBO in Phase 1.
|
|
1022
|
+
*/
|
|
1023
|
+
OnBehalfOfNotSupportedBySidecar: {
|
|
1031
1024
|
code: -120810,
|
|
1032
|
-
description: '
|
|
1025
|
+
description: 'acquireTokenOnBehalfOf is not supported by the Entra sidecar auth provider in Phase 1'
|
|
1033
1026
|
},
|
|
1034
1027
|
|
|
1035
1028
|
/**
|
|
1036
|
-
* Error thrown
|
|
1029
|
+
* Error thrown when the sidecar returns a token with an authorization scheme other than Bearer.
|
|
1030
|
+
* The hosting stack always transmits tokens as `Bearer {token}`, so a non-Bearer scheme cannot be
|
|
1031
|
+
* honored and is rejected rather than silently sent as an invalid Authorization header.
|
|
1037
1032
|
*/
|
|
1038
|
-
|
|
1039
|
-
code: -
|
|
1040
|
-
description: '
|
|
1033
|
+
SidecarUnsupportedAuthScheme: {
|
|
1034
|
+
code: -120811,
|
|
1035
|
+
description: 'The Entra Agent ID sidecar returned an unsupported authorization scheme `{scheme}`. Only the Bearer scheme is supported'
|
|
1041
1036
|
},
|
|
1042
1037
|
|
|
1043
1038
|
// ============================================================================
|
|
@@ -1102,6 +1097,52 @@ export const Errors: { [key: string]: AgentErrorDefinition } = {
|
|
|
1102
1097
|
description: "You must configure the Application with an 'adapter' before calling Application.continueConversationAsync()"
|
|
1103
1098
|
},
|
|
1104
1099
|
|
|
1100
|
+
// ============================================================================
|
|
1101
|
+
// Hosting / Web layer errors (-120910 to -120940)
|
|
1102
|
+
// ============================================================================
|
|
1103
|
+
|
|
1104
|
+
/**
|
|
1105
|
+
* Error thrown by CloudAdapter.process when the incoming request lacks a parsed body.
|
|
1106
|
+
* Indicates the hosting layer (e.g. express.json() or Fastify's JSON parser) was not configured.
|
|
1107
|
+
*/
|
|
1108
|
+
MissingRequestBody: {
|
|
1109
|
+
code: -120910,
|
|
1110
|
+
description: '`request.body` parameter required; ensure your hosting layer parses JSON request bodies before invoking the adapter (e.g., express.json() with Express or Fastify\'s built-in JSON parser).'
|
|
1111
|
+
},
|
|
1112
|
+
|
|
1113
|
+
/**
|
|
1114
|
+
* Error thrown by verifyToken when the provided JWT cannot be decoded.
|
|
1115
|
+
*/
|
|
1116
|
+
InvalidJwtToken: {
|
|
1117
|
+
code: -120920,
|
|
1118
|
+
description: 'invalid token'
|
|
1119
|
+
},
|
|
1120
|
+
|
|
1121
|
+
/**
|
|
1122
|
+
* Error thrown by verifyToken when the token audience does not match any configured connection clientId.
|
|
1123
|
+
*/
|
|
1124
|
+
JwtAudienceMismatch: {
|
|
1125
|
+
code: -120930,
|
|
1126
|
+
description: 'Audience mismatch'
|
|
1127
|
+
},
|
|
1128
|
+
|
|
1129
|
+
/**
|
|
1130
|
+
* Error thrown by verifyToken when the token issuer is not in the connection's allowed issuer list.
|
|
1131
|
+
*/
|
|
1132
|
+
JwtIssuerMismatch: {
|
|
1133
|
+
code: -120830,
|
|
1134
|
+
description: 'Issuer mismatch'
|
|
1135
|
+
},
|
|
1136
|
+
|
|
1137
|
+
/**
|
|
1138
|
+
* Error thrown by verifyToken when an Entra token's tenant id (`tid`) claim does not match the
|
|
1139
|
+
* tenant GUID embedded in its issuer (`iss`) claim.
|
|
1140
|
+
*/
|
|
1141
|
+
JwtTenantMismatch: {
|
|
1142
|
+
code: -120840,
|
|
1143
|
+
description: 'Tenant mismatch'
|
|
1144
|
+
},
|
|
1145
|
+
|
|
1105
1146
|
// ============================================================================
|
|
1106
1147
|
// Adaptive Cards Errors (-120950 to -120980)
|
|
1107
1148
|
// ============================================================================
|
package/src/index.ts
CHANGED
|
@@ -22,4 +22,9 @@ export interface AdaptiveCardInvokeValue {
|
|
|
22
22
|
* The state of the adaptive card.
|
|
23
23
|
*/
|
|
24
24
|
state: string
|
|
25
|
+
/**
|
|
26
|
+
* Indicates whether Teams initiated the action while rendering a refreshable
|
|
27
|
+
* card or a user explicitly selected an action.
|
|
28
|
+
*/
|
|
29
|
+
trigger?: 'automatic' | 'manual'
|
|
25
30
|
}
|