@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.
- package/README.md +16 -0
- package/dist/package.json +3 -3
- package/dist/src/agent-client/agentClient.js +4 -3
- package/dist/src/agent-client/agentClient.js.map +1 -1
- package/dist/src/agent-client/agentResponseHandler.d.ts +25 -6
- package/dist/src/agent-client/agentResponseHandler.js +16 -55
- package/dist/src/agent-client/agentResponseHandler.js.map +1 -1
- package/dist/src/agent-client/createAgentResponseHandler.d.ts +48 -0
- package/dist/src/agent-client/createAgentResponseHandler.js +92 -0
- package/dist/src/agent-client/createAgentResponseHandler.js.map +1 -0
- package/dist/src/agent-client/index.d.ts +1 -0
- package/dist/src/agent-client/index.js +1 -0
- package/dist/src/agent-client/index.js.map +1 -1
- package/dist/src/app/adaptiveCards/activityValueParsers.js +2 -1
- package/dist/src/app/adaptiveCards/activityValueParsers.js.map +1 -1
- package/dist/src/app/adaptiveCards/adaptiveCardsActions.js +13 -12
- package/dist/src/app/adaptiveCards/adaptiveCardsActions.js.map +1 -1
- package/dist/src/app/agentApplication.js +8 -8
- package/dist/src/app/agentApplication.js.map +1 -1
- package/dist/src/app/streaming/streamingResponse.d.ts +11 -0
- package/dist/src/app/streaming/streamingResponse.js +23 -2
- package/dist/src/app/streaming/streamingResponse.js.map +1 -1
- package/dist/src/app/turnState.d.ts +0 -1
- package/dist/src/app/turnState.js +15 -14
- package/dist/src/app/turnState.js.map +1 -1
- package/dist/src/app/turnStateProperty.js +4 -2
- package/dist/src/app/turnStateProperty.js.map +1 -1
- package/dist/src/auth/MemoryCache.d.ts +2 -1
- package/dist/src/auth/MemoryCache.js +7 -1
- package/dist/src/auth/MemoryCache.js.map +1 -1
- package/dist/src/auth/authConfiguration.d.ts +1 -1
- package/dist/src/auth/authConfiguration.js +41 -4
- package/dist/src/auth/authConfiguration.js.map +1 -1
- package/dist/src/auth/authConstants.d.ts +7 -7
- package/dist/src/auth/authConstants.js.map +1 -1
- package/dist/src/auth/connectionManager.d.ts +92 -0
- package/dist/src/auth/{msalConnectionManager.js → connectionManager.js} +50 -48
- package/dist/src/auth/connectionManager.js.map +1 -0
- package/dist/src/auth/index.d.ts +5 -3
- package/dist/src/auth/index.js +5 -3
- package/dist/src/auth/index.js.map +1 -1
- package/dist/src/auth/jwt-middleware.d.ts +8 -2
- package/dist/src/auth/jwt-middleware.js +116 -7
- package/dist/src/auth/jwt-middleware.js.map +1 -1
- package/dist/src/auth/msal/msalConnectionManager.d.ts +17 -0
- package/dist/src/auth/msal/msalConnectionManager.js +50 -0
- package/dist/src/auth/msal/msalConnectionManager.js.map +1 -0
- package/dist/src/auth/msal/msalConnectionSettings.d.ts +68 -0
- package/dist/src/auth/msal/msalConnectionSettings.js +7 -0
- package/dist/src/auth/msal/msalConnectionSettings.js.map +1 -0
- package/dist/src/auth/{msalTokenCredential.d.ts → msal/msalTokenCredential.d.ts} +1 -1
- package/dist/src/auth/{msalTokenCredential.js → msal/msalTokenCredential.js} +2 -2
- package/dist/src/auth/msal/msalTokenCredential.js.map +1 -0
- package/dist/src/auth/{msalTokenProvider.d.ts → msal/msalTokenProvider.d.ts} +23 -3
- package/dist/src/auth/{msalTokenProvider.js → msal/msalTokenProvider.js} +280 -176
- package/dist/src/auth/msal/msalTokenProvider.js.map +1 -0
- package/dist/src/auth/settings.d.ts +142 -133
- package/dist/src/auth/settings.js +49 -5
- package/dist/src/auth/settings.js.map +1 -1
- package/dist/src/auth/sidecar/sidecarAuthProvider.d.ts +74 -0
- package/dist/src/auth/sidecar/sidecarAuthProvider.js +181 -0
- package/dist/src/auth/sidecar/sidecarAuthProvider.js.map +1 -0
- package/dist/src/auth/sidecar/sidecarConnectionSettings.d.ts +65 -0
- package/dist/src/auth/sidecar/sidecarConnectionSettings.js +7 -0
- package/dist/src/auth/sidecar/sidecarConnectionSettings.js.map +1 -0
- package/dist/src/auth/sidecar/sidecarHttpClient.d.ts +95 -0
- package/dist/src/auth/sidecar/sidecarHttpClient.js +406 -0
- package/dist/src/auth/sidecar/sidecarHttpClient.js.map +1 -0
- package/dist/src/auth/sidecar/sidecarModels.d.ts +110 -0
- package/dist/src/auth/sidecar/sidecarModels.js +48 -0
- package/dist/src/auth/sidecar/sidecarModels.js.map +1 -0
- package/dist/src/auth/sidecar/sidecarTokenExpiry.d.ts +13 -0
- package/dist/src/auth/sidecar/sidecarTokenExpiry.js +39 -0
- package/dist/src/auth/sidecar/sidecarTokenExpiry.js.map +1 -0
- package/dist/src/baseAdapter.js +3 -1
- package/dist/src/baseAdapter.js.map +1 -1
- package/dist/src/cloudAdapter.d.ts +2 -2
- package/dist/src/cloudAdapter.js +11 -11
- package/dist/src/cloudAdapter.js.map +1 -1
- package/dist/src/createCloudAdapter.d.ts +40 -0
- package/dist/src/createCloudAdapter.js +44 -0
- package/dist/src/createCloudAdapter.js.map +1 -0
- package/dist/src/errorHelper.js +302 -79
- package/dist/src/errorHelper.js.map +1 -1
- package/dist/src/headerPropagation.js +3 -1
- package/dist/src/headerPropagation.js.map +1 -1
- package/dist/src/index.d.ts +3 -1
- package/dist/src/index.js +4 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/interfaces/index.d.ts +5 -0
- package/dist/src/interfaces/index.js +22 -0
- package/dist/src/interfaces/index.js.map +1 -0
- package/dist/src/interfaces/webResponse.d.ts +41 -0
- package/dist/src/interfaces/webResponse.js +7 -0
- package/dist/src/interfaces/webResponse.js.map +1 -0
- package/dist/src/middlewareSet.d.ts +0 -1
- package/dist/src/middlewareSet.js +4 -1
- package/dist/src/middlewareSet.js.map +1 -1
- package/dist/src/oauth/userTokenClient.d.ts +12 -0
- package/dist/src/oauth/userTokenClient.js +30 -9
- package/dist/src/oauth/userTokenClient.js.map +1 -1
- package/dist/src/state/conversationState.js +4 -2
- package/dist/src/state/conversationState.js.map +1 -1
- package/dist/src/state/userState.js +4 -2
- package/dist/src/state/userState.js.map +1 -1
- package/dist/src/storage/memoryStorage.js +5 -3
- package/dist/src/storage/memoryStorage.js.map +1 -1
- package/dist/src/transcript/consoleTranscriptLogger.js +3 -1
- package/dist/src/transcript/consoleTranscriptLogger.js.map +1 -1
- package/dist/src/transcript/fileTranscriptLogger.js +5 -4
- package/dist/src/transcript/fileTranscriptLogger.js.map +1 -1
- package/dist/src/transcript/transcriptLoggerMiddleware.js +2 -1
- package/dist/src/transcript/transcriptLoggerMiddleware.js.map +1 -1
- package/dist/src/turnContext.js +2 -1
- package/dist/src/turnContext.js.map +1 -1
- package/package.json +3 -3
- package/src/agent-client/agentClient.ts +5 -4
- package/src/agent-client/agentResponseHandler.ts +43 -67
- package/src/agent-client/createAgentResponseHandler.ts +133 -0
- package/src/agent-client/index.ts +1 -0
- package/src/app/adaptiveCards/activityValueParsers.ts +3 -2
- package/src/app/adaptiveCards/adaptiveCardsActions.ts +5 -5
- package/src/app/agentApplication.ts +8 -16
- package/src/app/streaming/streamingResponse.ts +26 -3
- package/src/app/turnState.ts +15 -14
- package/src/app/turnStateProperty.ts +4 -2
- package/src/auth/MemoryCache.ts +7 -1
- package/src/auth/authConfiguration.ts +41 -6
- package/src/auth/authConstants.ts +7 -7
- package/src/auth/{msalConnectionManager.ts → connectionManager.ts} +65 -61
- package/src/auth/index.ts +5 -3
- package/src/auth/jwt-middleware.ts +118 -9
- package/src/auth/msal/msalConnectionManager.ts +55 -0
- package/src/auth/msal/msalConnectionSettings.ts +79 -0
- package/src/auth/{msalTokenCredential.ts → msal/msalTokenCredential.ts} +2 -1
- package/src/auth/{msalTokenProvider.ts → msal/msalTokenProvider.ts} +353 -172
- package/src/auth/settings.ts +131 -76
- package/src/auth/sidecar/sidecarAuthProvider.ts +234 -0
- package/src/auth/sidecar/sidecarConnectionSettings.ts +72 -0
- package/src/auth/sidecar/sidecarHttpClient.ts +443 -0
- package/src/auth/sidecar/sidecarModels.ts +153 -0
- package/src/auth/sidecar/sidecarTokenExpiry.ts +35 -0
- package/src/baseAdapter.ts +3 -2
- package/src/cloudAdapter.ts +13 -13
- package/src/createCloudAdapter.ts +56 -0
- package/src/errorHelper.ts +354 -89
- package/src/headerPropagation.ts +4 -1
- package/src/index.ts +3 -1
- package/src/interfaces/index.ts +6 -0
- package/src/interfaces/webResponse.ts +44 -0
- package/src/middlewareSet.ts +3 -1
- package/src/oauth/userTokenClient.ts +46 -11
- package/src/state/conversationState.ts +4 -3
- package/src/state/userState.ts +4 -3
- package/src/storage/memoryStorage.ts +5 -3
- package/src/transcript/consoleTranscriptLogger.ts +3 -2
- package/src/transcript/fileTranscriptLogger.ts +6 -5
- package/src/transcript/transcriptLoggerMiddleware.ts +3 -2
- package/src/turnContext.ts +3 -2
- package/dist/src/auth/msalConnectionManager.d.ts +0 -64
- package/dist/src/auth/msalConnectionManager.js.map +0 -1
- package/dist/src/auth/msalTokenCredential.js.map +0 -1
- package/dist/src/auth/msalTokenProvider.js.map +0 -1
package/src/auth/MemoryCache.ts
CHANGED
|
@@ -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
|
|
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
|
|
123
|
+
throw ExceptionHelper.generateException(Error, Errors.NoDefaultConnectionFound)
|
|
103
124
|
}
|
|
104
125
|
|
|
105
126
|
const connection = conn?.get(name ?? '')
|
|
106
127
|
if (!connection) {
|
|
107
|
-
throw
|
|
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
|
|
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,
|
|
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 {
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
39
|
-
|
|
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
|
|
48
|
-
|
|
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):
|
|
90
|
+
getConnection (connectionName: string): AuthProvider {
|
|
71
91
|
const conn = this._connections.get(connectionName)
|
|
72
92
|
if (!conn) {
|
|
73
|
-
throw
|
|
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 ():
|
|
102
|
+
getDefaultConnection (): AuthProvider {
|
|
83
103
|
if (this._connections.size === 0) {
|
|
84
|
-
throw
|
|
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
|
|
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):
|
|
137
|
+
getTokenProvider (identity: JwtPayload, serviceUrl: string): AuthProvider {
|
|
118
138
|
if (!identity) {
|
|
119
|
-
throw
|
|
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
|
|
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
|
|
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):
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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 './
|
|
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
|
|
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 =
|
|
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
|
|
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:
|
|
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
|
|
107
|
-
if (
|
|
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
|
-
|
|
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
|
+
}
|