@microsoft/agents-hosting 1.6.0-beta.9.gce9d8facd2 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/package.json +4 -9
- package/dist/src/agent-client/agentClient.js +2 -2
- package/dist/src/agent-client/agentClient.js.map +1 -1
- package/dist/src/agent-client/agentResponseHandler.js +2 -2
- package/dist/src/agent-client/agentResponseHandler.js.map +1 -1
- package/dist/src/app/agentApplication.js +28 -2
- package/dist/src/app/agentApplication.js.map +1 -1
- package/dist/src/app/agentApplicationOptions.d.ts +10 -0
- package/dist/src/app/attachmentDownloader.js +5 -10
- package/dist/src/app/attachmentDownloader.js.map +1 -1
- package/dist/src/app/auth/authorizationManager.d.ts +4 -0
- package/dist/src/app/auth/authorizationManager.js +94 -67
- package/dist/src/app/auth/authorizationManager.js.map +1 -1
- package/dist/src/app/auth/handlers/azureBotAuthorization.js +2 -2
- package/dist/src/app/auth/handlers/azureBotAuthorization.js.map +1 -1
- package/dist/src/app/proactive/createConversationOptionsBuilder.js +10 -6
- package/dist/src/app/proactive/createConversationOptionsBuilder.js.map +1 -1
- package/dist/src/app/proactive/proactive.d.ts +1 -0
- package/dist/src/app/proactive/proactive.js +30 -15
- package/dist/src/app/proactive/proactive.js.map +1 -1
- package/dist/src/app/teamsAttachmentDownloader.js +6 -14
- package/dist/src/app/teamsAttachmentDownloader.js.map +1 -1
- package/dist/src/auth/authConfiguration.d.ts +2 -143
- package/dist/src/auth/authConfiguration.js +301 -257
- package/dist/src/auth/authConfiguration.js.map +1 -1
- package/dist/src/auth/jwt-middleware.js +2 -1
- package/dist/src/auth/jwt-middleware.js.map +1 -1
- package/dist/src/auth/msalConnectionManager.js +27 -14
- package/dist/src/auth/msalConnectionManager.js.map +1 -1
- package/dist/src/auth/msalTokenProvider.d.ts +7 -0
- package/dist/src/auth/msalTokenProvider.js +188 -44
- package/dist/src/auth/msalTokenProvider.js.map +1 -1
- package/dist/src/auth/settings.d.ts +327 -0
- package/dist/src/auth/settings.js +158 -0
- package/dist/src/auth/settings.js.map +1 -0
- package/dist/src/cloudAdapter.d.ts +58 -1
- package/dist/src/cloudAdapter.js +232 -52
- package/dist/src/cloudAdapter.js.map +1 -1
- package/dist/src/connector-client/connectorClient.d.ts +12 -8
- package/dist/src/connector-client/connectorClient.js +99 -82
- package/dist/src/connector-client/connectorClient.js.map +1 -1
- package/dist/src/errorHelper.js +85 -7
- package/dist/src/errorHelper.js.map +1 -1
- package/dist/src/getProductInfo.d.ts +23 -0
- package/dist/src/getProductInfo.js +71 -1
- package/dist/src/getProductInfo.js.map +1 -1
- package/dist/src/headerPropagation.d.ts +17 -2
- package/dist/src/headerPropagation.js +46 -24
- package/dist/src/headerPropagation.js.map +1 -1
- package/dist/src/httpClient.d.ts +60 -0
- package/dist/src/httpClient.js +173 -0
- package/dist/src/httpClient.js.map +1 -0
- package/dist/src/index.d.ts +2 -1
- package/dist/src/index.js +7 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/oauth/userTokenClient.d.ts +5 -4
- package/dist/src/oauth/userTokenClient.js +97 -78
- package/dist/src/oauth/userTokenClient.js.map +1 -1
- package/dist/src/observability/traces.d.ts +11 -4
- package/dist/src/observability/traces.js +39 -7
- package/dist/src/observability/traces.js.map +1 -1
- package/dist/src/utils/env.d.ts +50 -0
- package/dist/src/utils/env.js +113 -0
- package/dist/src/utils/env.js.map +1 -0
- package/dist/src/utils.d.ts +10 -0
- package/dist/src/utils.js +18 -0
- package/dist/src/utils.js.map +1 -0
- package/package.json +4 -9
- package/src/agent-client/agentClient.ts +2 -2
- package/src/agent-client/agentResponseHandler.ts +2 -2
- package/src/app/agentApplication.ts +30 -3
- package/src/app/agentApplicationOptions.ts +11 -0
- package/src/app/attachmentDownloader.ts +6 -8
- package/src/app/auth/authorizationManager.ts +84 -41
- package/src/app/auth/handlers/azureBotAuthorization.ts +2 -2
- package/src/app/proactive/createConversationOptionsBuilder.ts +8 -4
- package/src/app/proactive/proactive.ts +39 -23
- package/src/app/teamsAttachmentDownloader.ts +7 -11
- package/src/auth/authConfiguration.ts +317 -356
- package/src/auth/jwt-middleware.ts +1 -1
- package/src/auth/msalConnectionManager.ts +27 -10
- package/src/auth/msalTokenProvider.ts +180 -42
- package/src/auth/settings.ts +323 -0
- package/src/cloudAdapter.ts +291 -12
- package/src/connector-client/connectorClient.ts +116 -94
- package/src/errorHelper.ts +96 -7
- package/src/getProductInfo.ts +79 -1
- package/src/headerPropagation.ts +55 -23
- package/src/httpClient.ts +223 -0
- package/src/index.ts +5 -1
- package/src/oauth/userTokenClient.ts +103 -84
- package/src/observability/traces.ts +39 -8
- package/src/utils/env.ts +105 -0
- package/src/utils.ts +14 -0
|
@@ -3,102 +3,291 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import
|
|
6
|
+
import type { ConnectionMapItem } from './msalConnectionManager'
|
|
7
|
+
import { debug, redactString, redactScopes, redactUrl } from '@microsoft/agents-telemetry'
|
|
8
|
+
import { loadEnvSettings, AuthConfiguration, envParser, envParserUtils, LoadEnv, applyDefaultSettings, DEFAULT_CONNECTION_MAP, ConnectionKeys, ConnectionMapKeys } from './settings'
|
|
9
|
+
|
|
10
|
+
export { type AuthConfiguration, AuthType, resolveAuthority } from './settings'
|
|
11
|
+
import { prune } from '../utils'
|
|
9
12
|
|
|
10
13
|
const logger = debug('agents:authConfiguration')
|
|
11
|
-
|
|
14
|
+
|
|
15
|
+
type NonOptional<T> = { [K in keyof Required<T>]: T[K] }
|
|
12
16
|
|
|
13
17
|
/**
|
|
14
|
-
*
|
|
18
|
+
* Summarizes the authentication configuration for logging by redacting sensitive information and pruning undefined values. This is used to log the loaded authentication settings without exposing secrets or personally identifiable information.
|
|
19
|
+
* @remarks AuthConfiguration properties can change its shape, since this function is intended for logging, e.g. `scopes` will be a string instead of an array.
|
|
15
20
|
*/
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
sendX5C?: boolean
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* A list of valid issuers for the authentication configuration.
|
|
49
|
-
*/
|
|
50
|
-
issuers?: string[]
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* The connection name for the authentication configuration.
|
|
54
|
-
*/
|
|
55
|
-
connectionName?: string
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* The FIC (First-Party Integration Channel) client ID.
|
|
59
|
-
*/
|
|
60
|
-
FICClientId?: string,
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Entra Authentication Endpoint to use.
|
|
64
|
-
*
|
|
65
|
-
* @remarks
|
|
66
|
-
* If not populated the Entra Public Cloud endpoint is assumed.
|
|
67
|
-
* This example of Public Cloud Endpoint is https://login.microsoftonline.com
|
|
68
|
-
* see also https://learn.microsoft.com/entra/identity-platform/authentication-national-cloud
|
|
69
|
-
*/
|
|
70
|
-
authority?: string
|
|
71
|
-
|
|
72
|
-
scope?: string
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* A map of connection names to their respective authentication configurations.
|
|
76
|
-
*/
|
|
77
|
-
connections?: Map<string, AuthConfiguration>
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* A list of connection map items to map service URLs to connection names.
|
|
81
|
-
*/
|
|
82
|
-
connectionsMap?: ConnectionMapItem[],
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* An optional alternative blueprint Connection name used when constructing a connector client.
|
|
86
|
-
*/
|
|
87
|
-
altBlueprintConnectionName?: string
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* The path to K8s provided token.
|
|
91
|
-
*/
|
|
92
|
-
WIDAssertionFile?: string
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* The Azure region for ESTS-R regional token acquisition (e.g. 'westus', 'eastus').
|
|
96
|
-
* When set, MSAL routes token requests to the specified regional endpoint.
|
|
97
|
-
* See https://learn.microsoft.com/en-us/entra/msal/javascript/node/regional-authorities for details.
|
|
98
|
-
*/
|
|
99
|
-
azureRegion?: string
|
|
21
|
+
function summarizeAuthConfiguration (authConfig: AuthConfiguration) {
|
|
22
|
+
return [...authConfig.connections?.entries() ?? []].reduce((summary, [name, config]) => {
|
|
23
|
+
summary[name] = prune({
|
|
24
|
+
clientId: redactString(config.clientId, true),
|
|
25
|
+
tenantId: redactString(config.tenantId, true),
|
|
26
|
+
clientSecret: redactString(config.clientSecret),
|
|
27
|
+
authorityEndpoint: config.authorityEndpoint ? redactUrl(config.authorityEndpoint) : undefined,
|
|
28
|
+
scopes: (config.scopes ? redactScopes(config.scopes) : undefined) as any,
|
|
29
|
+
issuers: config.issuers?.map(redactUrl).filter(e => e !== undefined),
|
|
30
|
+
federatedClientId: redactString(config.federatedClientId, true),
|
|
31
|
+
certPemFile: redactString(config.certPemFile),
|
|
32
|
+
certKeyFile: redactString(config.certKeyFile),
|
|
33
|
+
WIDAssertionFile: redactString(config.WIDAssertionFile),
|
|
34
|
+
federatedTokenFile: config.federatedTokenFile ? redactString(config.federatedTokenFile) : undefined,
|
|
35
|
+
authType: config.authType ?? undefined,
|
|
36
|
+
idpmResource: config.idpmResource ? redactUrl(config.idpmResource) : undefined,
|
|
37
|
+
connectionName: config.connectionName,
|
|
38
|
+
altBlueprintConnectionName: config.altBlueprintConnectionName,
|
|
39
|
+
azureRegion: config.azureRegion,
|
|
40
|
+
sendX5C: config.sendX5C,
|
|
41
|
+
// Don't log the following properties
|
|
42
|
+
authority: undefined, // Deprecated, same as authorityEndpoint, avoid logging duplicate info
|
|
43
|
+
FICClientId: undefined, // Deprecated, same as federatedClientId, avoid logging duplicate info
|
|
44
|
+
scope: undefined, // Deprecated, same as scopes, avoid logging duplicate info
|
|
45
|
+
connections: undefined, // Avoid logging nested connections
|
|
46
|
+
connectionsMap: undefined, // Avoid logging nested connections map
|
|
47
|
+
} satisfies NonOptional<AuthConfiguration>)
|
|
48
|
+
return summary
|
|
49
|
+
}, {} as Record<string, AuthConfiguration>)
|
|
100
50
|
}
|
|
101
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Latest authentication configuration loaded from environment variables, with support for hot-reloading in test mode.
|
|
54
|
+
* Environment variables for connections should be in the format Connections__<id>__Settings__<property>, e.g. Connections__MyConnection__Settings__ClientId, Connections__MyConnection__Settings__TenantId, etc.
|
|
55
|
+
* Environment variables for connections map should be in the format ConnectionsMap__<index>__<property>, e.g. ConnectionsMap__0__ServiceUrl, ConnectionsMap__0__Connection, etc.
|
|
56
|
+
*/
|
|
57
|
+
const connectionsEnv = {
|
|
58
|
+
connections: new Map<string, AuthConfiguration>(),
|
|
59
|
+
parser: envParser<ConnectionKeys>({
|
|
60
|
+
authType: envParserUtils.bypass,
|
|
61
|
+
tenantId: envParserUtils.bypass,
|
|
62
|
+
clientId: envParserUtils.bypass,
|
|
63
|
+
clientSecret: envParserUtils.bypass,
|
|
64
|
+
certPemFile: envParserUtils.bypass,
|
|
65
|
+
certKeyFile: envParserUtils.bypass,
|
|
66
|
+
connectionName: envParserUtils.bypass,
|
|
67
|
+
federatedClientId: envParserUtils.bypass,
|
|
68
|
+
FICClientId (value) {
|
|
69
|
+
logger.warn('Connections__<id>__Settings__FICClientId is deprecated, please use Connections__<id>__Settings__FederatedClientId instead.')
|
|
70
|
+
return { key: 'federatedClientId', value } // redirect
|
|
71
|
+
},
|
|
72
|
+
authorityEndpoint: envParserUtils.bypass,
|
|
73
|
+
authority (value) {
|
|
74
|
+
logger.warn('Connections__<id>__Settings__Authority is deprecated, please use Connections__<id>__Settings__AuthorityEndpoint instead.')
|
|
75
|
+
return { key: 'authorityEndpoint', value } // redirect
|
|
76
|
+
},
|
|
77
|
+
scopes (value) {
|
|
78
|
+
return this.issuers(value) // scopes can be comma or space separated list, same as issuers.
|
|
79
|
+
},
|
|
80
|
+
scope (value) {
|
|
81
|
+
logger.warn('Connections__<id>__Settings__Scope is deprecated, please use Connections__<id>__Settings__Scopes instead.')
|
|
82
|
+
return { key: 'scopes', value: this.scopes(value)?.value } // redirect with single scope
|
|
83
|
+
},
|
|
84
|
+
altBlueprintConnectionName: envParserUtils.bypass,
|
|
85
|
+
WIDAssertionFile: envParserUtils.bypass,
|
|
86
|
+
federatedTokenFile: envParserUtils.bypass,
|
|
87
|
+
idpmResource: envParserUtils.bypass,
|
|
88
|
+
azureRegion: envParserUtils.bypass,
|
|
89
|
+
sendX5C: (value) => ({ value: value === 'true' }),
|
|
90
|
+
issuers (value) {
|
|
91
|
+
if (value.includes(',')) {
|
|
92
|
+
return { value: value.split(',').map(s => s.trim()).filter(Boolean) }
|
|
93
|
+
}
|
|
94
|
+
return { value: value.split(/\s+/).filter(Boolean) }
|
|
95
|
+
},
|
|
96
|
+
}),
|
|
97
|
+
default (connections?: AuthConfiguration['connections'], connectionsMap?: AuthConfiguration['connectionsMap']) {
|
|
98
|
+
const conn = connections ?? this.connections
|
|
99
|
+
const map = connectionsMap ?? connectionsMapEnv.connectionsMap
|
|
100
|
+
const name = map?.find((item) => item.serviceUrl === '*')?.connection
|
|
101
|
+
if (!name) {
|
|
102
|
+
throw new Error('No default connection found in environment connections.')
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const connection = conn?.get(name ?? '')
|
|
106
|
+
if (!connection) {
|
|
107
|
+
throw new Error(`Connection "${name}" not found in environment connections.`)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return applyDefaultSettings({ ...connection, connections: conn, connectionsMap: map })
|
|
111
|
+
},
|
|
112
|
+
process (key:string, value: string) {
|
|
113
|
+
const format = 'Connections__<id>__Settings__<property>'
|
|
114
|
+
const parts = key.split('__')
|
|
115
|
+
const [connections, id, settings, prop] = parts
|
|
116
|
+
|
|
117
|
+
if (`${connections}/${settings}`.toUpperCase() !== 'CONNECTIONS/SETTINGS') {
|
|
118
|
+
return false
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (parts.length !== 4) {
|
|
122
|
+
logger.warn(`Invalid connection environment variable: ${key}. Expected format: ${format}.`)
|
|
123
|
+
return false
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (!id?.trim()) {
|
|
127
|
+
logger.warn(`Invalid connection <id> in environment variable: ${key}. Expected format: ${format}.`)
|
|
128
|
+
return false
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (!prop?.trim()) {
|
|
132
|
+
logger.warn(`Invalid connection <property> in environment variable: ${key}. Expected format: ${format}.`)
|
|
133
|
+
return false
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const result = this.parser.parse(prop as ConnectionKeys, value)
|
|
137
|
+
if (!result.key) {
|
|
138
|
+
return false
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const config = this.connections.get(id) ?? {}
|
|
142
|
+
config[result.key as keyof AuthConfiguration] = result.value
|
|
143
|
+
this.connections.set(id, config)
|
|
144
|
+
return true
|
|
145
|
+
},
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const connectionsMapEnv = {
|
|
149
|
+
connectionsMap: [] as ConnectionMapItem[],
|
|
150
|
+
parser: envParser<ConnectionMapKeys>({
|
|
151
|
+
serviceUrl: envParserUtils.bypass,
|
|
152
|
+
connection: envParserUtils.bypass,
|
|
153
|
+
audience: envParserUtils.bypass,
|
|
154
|
+
}),
|
|
155
|
+
process (key: string, value: string) {
|
|
156
|
+
const format = 'ConnectionsMap__<index>__<property>'
|
|
157
|
+
const parts = key.split('__')
|
|
158
|
+
const [connectionsMap, index, prop] = parts
|
|
159
|
+
|
|
160
|
+
if (connectionsMap.toUpperCase() !== 'CONNECTIONSMAP') {
|
|
161
|
+
return false
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (parts.length !== 3) {
|
|
165
|
+
logger.warn(`Invalid connection map environment variable: ${key}. Expected format: ${format}.`)
|
|
166
|
+
return false
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const indexNumber = parseInt(index, 10)
|
|
170
|
+
if (!index?.trim() || isNaN(indexNumber) || indexNumber < 0) {
|
|
171
|
+
logger.warn(`Invalid connection map <index> in environment variable: ${key}. Expected format: ${format}, where <index> is a number.`)
|
|
172
|
+
return false
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (!prop?.trim()) {
|
|
176
|
+
logger.warn(`Invalid connection map <property> in environment variable: ${key}. Expected format: ${format}.`)
|
|
177
|
+
return false
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const result = this.parser.parse(prop as ConnectionMapKeys, value)
|
|
181
|
+
if (!result.key) {
|
|
182
|
+
return false
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const mapItem = this.connectionsMap[indexNumber] ?? { ...DEFAULT_CONNECTION_MAP }
|
|
186
|
+
mapItem[result.key as keyof ConnectionMapItem] = result.value
|
|
187
|
+
this.connectionsMap[indexNumber] = mapItem
|
|
188
|
+
return true
|
|
189
|
+
},
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Legacy BotFramework style-like authentication configuration loaded from environment variables, with support for hot-reloading in test mode.
|
|
194
|
+
* Environment variables should be named MicrosoftAppTenantId, MicrosoftAppId, MicrosoftAppPassword, etc.
|
|
195
|
+
*/
|
|
196
|
+
const legacyBotFrameworkEnv = {
|
|
197
|
+
parser: envParser({
|
|
198
|
+
MicrosoftAppTenantId: envParserUtils.redirect(connectionsEnv.parser, 'tenantId'),
|
|
199
|
+
MicrosoftAppId: envParserUtils.redirect(connectionsEnv.parser, 'clientId'),
|
|
200
|
+
MicrosoftAppPassword: envParserUtils.redirect(connectionsEnv.parser, 'clientSecret'),
|
|
201
|
+
certPemFile: envParserUtils.redirect(connectionsEnv.parser, 'certPemFile'),
|
|
202
|
+
certKeyFile: envParserUtils.redirect(connectionsEnv.parser, 'certKeyFile'),
|
|
203
|
+
connectionName: envParserUtils.redirect(connectionsEnv.parser, 'connectionName'),
|
|
204
|
+
MicrosoftAppClientId: envParserUtils.redirect(connectionsEnv.parser, 'federatedClientId'),
|
|
205
|
+
authorityEndpoint: envParserUtils.redirect(connectionsEnv.parser, 'authorityEndpoint'),
|
|
206
|
+
scope: envParserUtils.redirect(connectionsEnv.parser, 'scopes'),
|
|
207
|
+
altBlueprintConnectionName: envParserUtils.redirect(connectionsEnv.parser, 'altBlueprintConnectionName'),
|
|
208
|
+
WIDAssertionFile: envParserUtils.redirect(connectionsEnv.parser, 'WIDAssertionFile'),
|
|
209
|
+
azureRegion: envParserUtils.redirect(connectionsEnv.parser, 'azureRegion'),
|
|
210
|
+
sendX5C: envParserUtils.redirect(connectionsEnv.parser, 'sendX5C'),
|
|
211
|
+
authType: envParserUtils.redirect(connectionsEnv.parser, 'authType'),
|
|
212
|
+
federatedTokenFile: envParserUtils.redirect(connectionsEnv.parser, 'federatedTokenFile'),
|
|
213
|
+
idpmResource: envParserUtils.redirect(connectionsEnv.parser, 'idpmResource'),
|
|
214
|
+
}),
|
|
215
|
+
process (env: LoadEnv) {
|
|
216
|
+
return legacyPrefixEnv.process.call(this, env)
|
|
217
|
+
},
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Legacy prefix-based authentication configuration loaded from environment variables, with support for hot-reloading in test mode.
|
|
222
|
+
* Environment variables should be prefixed with the connection name, e.g. <CONNECTION_NAME>_ClientId, <CONNECTION_NAME>_TenantId, etc.
|
|
223
|
+
*/
|
|
224
|
+
const legacyPrefixEnv = {
|
|
225
|
+
parser: envParser({
|
|
226
|
+
tenantId: envParserUtils.redirect(connectionsEnv.parser, 'tenantId'),
|
|
227
|
+
clientId: envParserUtils.redirect(connectionsEnv.parser, 'clientId'),
|
|
228
|
+
clientSecret: envParserUtils.redirect(connectionsEnv.parser, 'clientSecret'),
|
|
229
|
+
certPemFile: envParserUtils.redirect(connectionsEnv.parser, 'certPemFile'),
|
|
230
|
+
certKeyFile: envParserUtils.redirect(connectionsEnv.parser, 'certKeyFile'),
|
|
231
|
+
connectionName: envParserUtils.redirect(connectionsEnv.parser, 'connectionName'),
|
|
232
|
+
FICClientId: envParserUtils.redirect(connectionsEnv.parser, 'federatedClientId'),
|
|
233
|
+
authorityEndpoint: envParserUtils.redirect(connectionsEnv.parser, 'authorityEndpoint'),
|
|
234
|
+
scope: envParserUtils.redirect(connectionsEnv.parser, 'scopes'),
|
|
235
|
+
altBlueprintConnectionName: envParserUtils.redirect(connectionsEnv.parser, 'altBlueprintConnectionName'),
|
|
236
|
+
WIDAssertionFile: envParserUtils.redirect(connectionsEnv.parser, 'WIDAssertionFile'),
|
|
237
|
+
azureRegion: envParserUtils.redirect(connectionsEnv.parser, 'azureRegion'),
|
|
238
|
+
sendX5C: envParserUtils.redirect(connectionsEnv.parser, 'sendX5C'),
|
|
239
|
+
authType: envParserUtils.redirect(connectionsEnv.parser, 'authType'),
|
|
240
|
+
federatedTokenFile: envParserUtils.redirect(connectionsEnv.parser, 'federatedTokenFile'),
|
|
241
|
+
idpmResource: envParserUtils.redirect(connectionsEnv.parser, 'idpmResource'),
|
|
242
|
+
}),
|
|
243
|
+
process (env: LoadEnv, prefix?: string) {
|
|
244
|
+
const settings: Partial<AuthConfiguration> = {}
|
|
245
|
+
for (const key of this.parser.keys) {
|
|
246
|
+
const k = prefix ? `${prefix}_${key}` : key
|
|
247
|
+
const envValue = env[k.toUpperCase()]
|
|
248
|
+
if (!envValue) {
|
|
249
|
+
continue
|
|
250
|
+
}
|
|
251
|
+
const result = this.parser.parse(key, envValue.value)
|
|
252
|
+
if (result.key) {
|
|
253
|
+
settings[result.key as keyof AuthConfiguration] = result.value
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return settings
|
|
257
|
+
},
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const loadEnv = () => {
|
|
261
|
+
connectionsEnv.connections = new Map<string, AuthConfiguration>()
|
|
262
|
+
connectionsMapEnv.connectionsMap = []
|
|
263
|
+
|
|
264
|
+
const env = loadEnvSettings((key, value) => {
|
|
265
|
+
// Process the first parser that matches the environment variable key and value,
|
|
266
|
+
// otherwise it continues to the next parser.
|
|
267
|
+
return connectionsEnv.process(key, value) ||
|
|
268
|
+
connectionsMapEnv.process(key, value)
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
if (connectionsEnv.connections.size === 0) {
|
|
272
|
+
logger.warn('No connections found in configuration.')
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (connectionsMapEnv.connectionsMap.length === 0 && connectionsEnv.connections.size > 0) {
|
|
276
|
+
logger.warn('No connections map found in configuration, assuming default connection map with serviceUrl "*" for the first connection.')
|
|
277
|
+
const [key] = connectionsEnv.connections.keys()
|
|
278
|
+
connectionsMapEnv.connectionsMap.push({ ...DEFAULT_CONNECTION_MAP, connection: key })
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return {
|
|
282
|
+
env,
|
|
283
|
+
legacyBotFrameworkSettings: legacyBotFrameworkEnv.process(env),
|
|
284
|
+
legacyPrefixSettings: legacyPrefixEnv.process(env)
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Initial load of environment variables
|
|
289
|
+
let globalEnv = loadEnv()
|
|
290
|
+
|
|
102
291
|
/**
|
|
103
292
|
* Loads the authentication configuration from environment variables.
|
|
104
293
|
*
|
|
@@ -126,43 +315,26 @@ export interface AuthConfiguration {
|
|
|
126
315
|
*
|
|
127
316
|
*/
|
|
128
317
|
export const loadAuthConfigFromEnv = (cnxName?: string): AuthConfiguration => {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
if (envConnections.connectionsMap.length === 0) {
|
|
133
|
-
// No connections provided, we need to populate the connections map with the old config settings
|
|
134
|
-
authConfig = buildLegacyAuthConfig(cnxName)
|
|
135
|
-
envConnections.connections.set(DEFAULT_CONNECTION, authConfig)
|
|
136
|
-
envConnections.connectionsMap.push({
|
|
137
|
-
serviceUrl: '*',
|
|
138
|
-
connection: DEFAULT_CONNECTION,
|
|
139
|
-
})
|
|
140
|
-
} else {
|
|
141
|
-
// There are connections provided, use the default or specified connection
|
|
142
|
-
if (cnxName) {
|
|
143
|
-
const entry = envConnections.connections.get(cnxName)
|
|
144
|
-
if (entry) {
|
|
145
|
-
authConfig = entry
|
|
146
|
-
} else {
|
|
147
|
-
throw new Error(`Connection "${cnxName}" not found in environment.`)
|
|
148
|
-
}
|
|
149
|
-
} else {
|
|
150
|
-
const defaultItem = envConnections.connectionsMap.find((item) => item.serviceUrl === '*')
|
|
151
|
-
const defaultConn = defaultItem ? envConnections.connections.get(defaultItem.connection) : undefined
|
|
152
|
-
if (!defaultConn) {
|
|
153
|
-
throw new Error('No default connection found in environment connections.')
|
|
154
|
-
}
|
|
155
|
-
authConfig = defaultConn
|
|
156
|
-
}
|
|
318
|
+
if (process.env.TEST_MODE === 'true') {
|
|
319
|
+
globalEnv = loadEnv()
|
|
320
|
+
}
|
|
157
321
|
|
|
158
|
-
|
|
159
|
-
|
|
322
|
+
if (connectionsEnv.connections.size > 0) {
|
|
323
|
+
return cnxName?.trim() ? connectionsEnv.default(undefined, [{ ...DEFAULT_CONNECTION_MAP, connection: cnxName }]) : connectionsEnv.default()
|
|
160
324
|
}
|
|
161
325
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
326
|
+
// No connections provided, we need to populate the connections map with the old config settings
|
|
327
|
+
const result = applyDefaultSettings(cnxName?.trim() ? legacyPrefixEnv.process(globalEnv.env, cnxName) : globalEnv.legacyPrefixSettings)
|
|
328
|
+
if (cnxName && !result.clientId) {
|
|
329
|
+
throw new Error(`ClientId not found for connection: ${cnxName}`)
|
|
165
330
|
}
|
|
331
|
+
|
|
332
|
+
logger.info('Auth settings loaded from environment', {
|
|
333
|
+
connections: summarizeAuthConfiguration(result),
|
|
334
|
+
connectionsMap: result.connectionsMap?.map(e => ({ ...e, serviceUrl: e.serviceUrl !== '*' ? redactUrl(e.serviceUrl) : e.serviceUrl })),
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
return result
|
|
166
338
|
}
|
|
167
339
|
|
|
168
340
|
/**
|
|
@@ -180,100 +352,20 @@ export const loadAuthConfigFromEnv = (cnxName?: string): AuthConfiguration => {
|
|
|
180
352
|
*
|
|
181
353
|
*/
|
|
182
354
|
export const loadPrevAuthConfigFromEnv: () => AuthConfiguration = () => {
|
|
183
|
-
|
|
184
|
-
|
|
355
|
+
if (process.env.TEST_MODE === 'true') {
|
|
356
|
+
globalEnv = loadEnv()
|
|
357
|
+
}
|
|
185
358
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
throw new Error('ClientId required in production')
|
|
190
|
-
}
|
|
191
|
-
const authority = process.env.authorityEndpoint ?? 'https://login.microsoftonline.com'
|
|
192
|
-
authConfig = {
|
|
193
|
-
tenantId: process.env.MicrosoftAppTenantId,
|
|
194
|
-
clientId: process.env.MicrosoftAppId,
|
|
195
|
-
clientSecret: process.env.MicrosoftAppPassword,
|
|
196
|
-
certPemFile: process.env.certPemFile,
|
|
197
|
-
certKeyFile: process.env.certKeyFile,
|
|
198
|
-
sendX5C: process.env.sendX5C === 'true',
|
|
199
|
-
connectionName: process.env.connectionName,
|
|
200
|
-
FICClientId: process.env.MicrosoftAppClientId,
|
|
201
|
-
authority,
|
|
202
|
-
scope: process.env.scope,
|
|
203
|
-
issuers: getDefaultIssuers(process.env.MicrosoftAppTenantId ?? '', authority),
|
|
204
|
-
altBlueprintConnectionName: process.env.altBlueprintConnectionName,
|
|
205
|
-
WIDAssertionFile: process.env.WIDAssertionFile,
|
|
206
|
-
azureRegion: process.env.azureRegion,
|
|
207
|
-
}
|
|
208
|
-
envConnections.connections.set(DEFAULT_CONNECTION, authConfig)
|
|
209
|
-
envConnections.connectionsMap.push({
|
|
210
|
-
serviceUrl: '*',
|
|
211
|
-
connection: DEFAULT_CONNECTION,
|
|
212
|
-
})
|
|
359
|
+
let result: AuthConfiguration
|
|
360
|
+
if (connectionsEnv.connections.size > 0) {
|
|
361
|
+
result = connectionsEnv.default()
|
|
213
362
|
} else {
|
|
214
|
-
//
|
|
215
|
-
|
|
216
|
-
const defaultConn = defaultItem ? envConnections.connections.get(defaultItem.connection) : undefined
|
|
217
|
-
if (!defaultConn) {
|
|
218
|
-
throw new Error('No default connection found in environment connections.')
|
|
219
|
-
}
|
|
220
|
-
authConfig = defaultConn
|
|
363
|
+
// No connections provided, we need to populate the connection map with the old config settings
|
|
364
|
+
result = applyDefaultSettings(globalEnv.legacyBotFrameworkSettings)
|
|
221
365
|
}
|
|
222
366
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
return { ...authConfig, ...envConnections }
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
function loadConnectionsMapFromEnv () {
|
|
230
|
-
const envVars = process.env
|
|
231
|
-
const connectionsObj: Record<string, any> = {}
|
|
232
|
-
const connectionsMap: ConnectionMapItem[] = []
|
|
233
|
-
const CONNECTIONS_PREFIX = 'connections__'
|
|
234
|
-
const CONNECTIONS_MAP_PREFIX = 'connectionsMap__'
|
|
235
|
-
|
|
236
|
-
for (const [key, rawValue] of Object.entries(envVars)) {
|
|
237
|
-
if (key.startsWith(CONNECTIONS_PREFIX)) {
|
|
238
|
-
// Convert to dot notation
|
|
239
|
-
let path = key.substring(CONNECTIONS_PREFIX.length).replace(/__/g, '.')
|
|
240
|
-
// Remove ".settings." from the path
|
|
241
|
-
path = path.replace('.settings.', '.')
|
|
242
|
-
// Convert "true"/"false" strings into boolean values
|
|
243
|
-
const value = rawValue === 'true' ? true : rawValue === 'false' ? false : rawValue
|
|
244
|
-
objectPath.set(connectionsObj, path, value)
|
|
245
|
-
} else if (key.startsWith(CONNECTIONS_MAP_PREFIX)) {
|
|
246
|
-
const path = key.substring(CONNECTIONS_MAP_PREFIX.length).replace(/__/g, '.')
|
|
247
|
-
objectPath.set(connectionsMap, path, rawValue)
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// Convert connectionsObj to Map<string, AuthConfiguration>
|
|
252
|
-
const connections: Map<string, AuthConfiguration> = new Map(Object.entries(connectionsObj))
|
|
253
|
-
|
|
254
|
-
if (connections.size === 0) {
|
|
255
|
-
logger.warn('No connections found in configuration.')
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
if (connectionsMap.length === 0) {
|
|
259
|
-
logger.warn('No connections map found in configuration.')
|
|
260
|
-
if (connections.size > 0) {
|
|
261
|
-
const firstEntry = connections.entries().next().value
|
|
262
|
-
|
|
263
|
-
if (firstEntry) {
|
|
264
|
-
const [firstKey] = firstEntry
|
|
265
|
-
// Provide a default connection map if none is specified
|
|
266
|
-
connectionsMap.push({
|
|
267
|
-
serviceUrl: '*',
|
|
268
|
-
connection: firstKey,
|
|
269
|
-
})
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
return {
|
|
274
|
-
connections,
|
|
275
|
-
connectionsMap,
|
|
276
|
-
}
|
|
367
|
+
logger.info('Legacy auth settings loaded from environment', summarizeAuthConfiguration(result), result.connectionsMap)
|
|
368
|
+
return result
|
|
277
369
|
}
|
|
278
370
|
|
|
279
371
|
/**
|
|
@@ -301,153 +393,22 @@ function loadConnectionsMapFromEnv () {
|
|
|
301
393
|
*
|
|
302
394
|
*/
|
|
303
395
|
export function getAuthConfigWithDefaults (config?: AuthConfiguration): AuthConfiguration {
|
|
304
|
-
if (
|
|
305
|
-
|
|
306
|
-
const providedConnections = config.connections && config.connectionsMap
|
|
307
|
-
? { connections: config.connections, connectionsMap: config.connectionsMap }
|
|
308
|
-
: undefined
|
|
309
|
-
|
|
310
|
-
const connections = providedConnections ?? loadConnectionsMapFromEnv()
|
|
311
|
-
|
|
312
|
-
let mergedConfig: AuthConfiguration
|
|
313
|
-
|
|
314
|
-
if (connections && connections.connectionsMap?.length === 0) {
|
|
315
|
-
// No connections provided, we need to populate the connections map with the old config settings
|
|
316
|
-
mergedConfig = buildLegacyAuthConfig(undefined, config)
|
|
317
|
-
connections.connections?.set(DEFAULT_CONNECTION, mergedConfig)
|
|
318
|
-
connections.connectionsMap.push({ serviceUrl: '*', connection: DEFAULT_CONNECTION })
|
|
319
|
-
} else {
|
|
320
|
-
// There are connections provided, use the default connection
|
|
321
|
-
const defaultItem = connections.connectionsMap?.find((item) => item.serviceUrl === '*')
|
|
322
|
-
const defaultConn = defaultItem ? connections.connections?.get(defaultItem.connection) : undefined
|
|
323
|
-
if (!defaultConn) {
|
|
324
|
-
throw new Error('No default connection found in environment connections.')
|
|
325
|
-
}
|
|
326
|
-
mergedConfig = buildLegacyAuthConfig(undefined, defaultConn)
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
return {
|
|
330
|
-
...mergedConfig,
|
|
331
|
-
...connections,
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
function buildLegacyAuthConfig (envPrefix: string = '', customConfig?: AuthConfiguration): AuthConfiguration {
|
|
336
|
-
const prefix = envPrefix ? `${envPrefix}_` : ''
|
|
337
|
-
const authority = customConfig?.authority ?? process.env[`${prefix}authorityEndpoint`] ?? 'https://login.microsoftonline.com'
|
|
338
|
-
|
|
339
|
-
const clientId = customConfig?.clientId ?? process.env[`${prefix}clientId`]
|
|
340
|
-
|
|
341
|
-
if (!clientId && !envPrefix && process.env.NODE_ENV === 'production') {
|
|
342
|
-
throw new Error('ClientId required in production')
|
|
343
|
-
}
|
|
344
|
-
if (!clientId && envPrefix) {
|
|
345
|
-
throw new Error(`ClientId not found for connection: ${envPrefix}`)
|
|
396
|
+
if (process.env.TEST_MODE === 'true') {
|
|
397
|
+
globalEnv = loadEnv()
|
|
346
398
|
}
|
|
347
399
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
connectionName: customConfig?.connectionName ?? process.env[`${prefix}connectionName`],
|
|
358
|
-
FICClientId: customConfig?.FICClientId ?? process.env[`${prefix}FICClientId`],
|
|
359
|
-
authority,
|
|
360
|
-
scope: customConfig?.scope ?? process.env[`${prefix}scope`],
|
|
361
|
-
issuers: customConfig?.issuers ?? getDefaultIssuers(tenantId as string, authority),
|
|
362
|
-
altBlueprintConnectionName: customConfig?.altBlueprintConnectionName ?? process.env[`${prefix}altBlueprintConnectionName`],
|
|
363
|
-
WIDAssertionFile: customConfig?.WIDAssertionFile ?? process.env[`${prefix}WIDAssertionFile`],
|
|
364
|
-
azureRegion: customConfig?.azureRegion ?? process.env[`${prefix}azureRegion`]
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
/**
|
|
369
|
-
* Resolves the full authority URL including the tenant ID.
|
|
370
|
-
* Supports both patterns:
|
|
371
|
-
* - Tenant embedded in authority: https://login.microsoftonline.com/my-tenant
|
|
372
|
-
* - Authority + separate tenantId: https://login.microsoftonline.com + tenantId
|
|
373
|
-
* Also handles trailing slashes on authority.
|
|
374
|
-
*/
|
|
375
|
-
export function resolveAuthority (authority?: string, tenantId?: string): string {
|
|
376
|
-
const base = (authority ?? 'https://login.microsoftonline.com').replace(/\/+$/, '')
|
|
377
|
-
const url = new URL(base)
|
|
378
|
-
const hasPathSegment = url.pathname !== '/'
|
|
379
|
-
if (hasPathSegment) {
|
|
380
|
-
return base
|
|
381
|
-
}
|
|
382
|
-
return `${base}/${tenantId ?? 'botframework.com'}`
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
function getDefaultIssuers (tenantId: string, authority: string) : string[] {
|
|
386
|
-
// Convert empty string to undefined so resolveAuthority applies its 'botframework.com' default
|
|
387
|
-
const t = tenantId || undefined
|
|
388
|
-
if (!t) {
|
|
389
|
-
logger.warn('tenantId is not configured, defaulting to botframework.com')
|
|
390
|
-
}
|
|
391
|
-
return [
|
|
392
|
-
'https://api.botframework.com',
|
|
393
|
-
`${resolveAuthority('https://sts.windows.net', t)}/`,
|
|
394
|
-
`${resolveAuthority(authority, t)}/v2.0`
|
|
395
|
-
]
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
/**
|
|
399
|
-
* A type representing a parser settings object.
|
|
400
|
-
*/
|
|
401
|
-
type ParserSettings<K extends string> = {
|
|
402
|
-
[key in K]: (value: string) => { key?: string, value?: any } | undefined
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
/**
|
|
406
|
-
* Creates an environment variable parser that maps the variable keys to parsing functions.
|
|
407
|
-
* @param settings An object where each key is an environment variable name and the value is a function
|
|
408
|
-
* that takes the variable value as input and returns an object with optional `key` and `value` properties.
|
|
409
|
-
* @remarks
|
|
410
|
-
* The `key` property in the returned object can be used to rename the environment variable key,
|
|
411
|
-
* while the `value` property contains the parsed value.
|
|
412
|
-
* @returns An object with a `parse` method that takes an environment variable key and value,
|
|
413
|
-
* and returns the parsed result.
|
|
414
|
-
*/
|
|
415
|
-
export function envParser<K extends string> (settings: ParserSettings<K> & ThisType<ParserSettings<K>>) {
|
|
416
|
-
const keys = Object.keys(settings) as K[]
|
|
417
|
-
return {
|
|
418
|
-
/**
|
|
419
|
-
* Parses the given environment variable key and value using the provided settings.
|
|
420
|
-
* @param key The environment variable key.
|
|
421
|
-
* @param value The environment variable value.
|
|
422
|
-
* @returns The parsed result with optional renamed key and parsed value.
|
|
423
|
-
*/
|
|
424
|
-
parse (key: K, value: string) {
|
|
425
|
-
const match = keys.find(k => k.toUpperCase() === key.toUpperCase())
|
|
426
|
-
if (!match) {
|
|
427
|
-
return {}
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
const result = settings[match](value)
|
|
431
|
-
return { key: result?.key ?? match, value: result?.value }
|
|
400
|
+
let result: AuthConfiguration
|
|
401
|
+
if (!config) {
|
|
402
|
+
result = loadAuthConfigFromEnv()
|
|
403
|
+
} else {
|
|
404
|
+
const { connections, connectionsMap } = config.connections?.size ? config : { connections: connectionsEnv.connections, connectionsMap: connectionsMapEnv.connectionsMap }
|
|
405
|
+
if (connections?.size) {
|
|
406
|
+
result = { ...globalEnv.legacyPrefixSettings, ...connectionsEnv.default(connections, connectionsMap) }
|
|
407
|
+
} else {
|
|
408
|
+
result = applyDefaultSettings({ ...globalEnv.legacyPrefixSettings, ...config })
|
|
432
409
|
}
|
|
433
410
|
}
|
|
434
|
-
}
|
|
435
411
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
*/
|
|
439
|
-
export const envParserUtils = {
|
|
440
|
-
/**
|
|
441
|
-
* Bypass parser that returns the value as is.
|
|
442
|
-
* @param value The environment variable value.
|
|
443
|
-
* @returns An object with the original value.
|
|
444
|
-
*/
|
|
445
|
-
bypass: (value: string) => ({ value }),
|
|
446
|
-
/**
|
|
447
|
-
* Redirects the parsing to another parser for a specific key.
|
|
448
|
-
* @param parser The target parser to redirect to.
|
|
449
|
-
* @param key The key to use in the target parser.
|
|
450
|
-
* @returns A function that takes the environment variable value and returns the parsed result from the target parser.
|
|
451
|
-
*/
|
|
452
|
-
redirect: <Parser extends ReturnType<typeof envParser>>(parser: Parser, key: Parameters<Parser['parse']>[0]) => (value: string) => parser.parse(key, value)
|
|
412
|
+
logger.info('Auth settings loaded from runtime configuration', summarizeAuthConfiguration(result), result.connectionsMap)
|
|
413
|
+
return result
|
|
453
414
|
}
|