@microsoft/agents-hosting 1.10.0-beta.8.g158a2c07ad → 1.10.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/README.md +41 -2
- package/dist/package.json +3 -3
- package/dist/src/activityHandler.js +6 -3
- package/dist/src/activityHandler.js.map +1 -1
- package/dist/src/agent-client/agentResponseHandler.d.ts +7 -0
- package/dist/src/agent-client/agentResponseHandler.js.map +1 -1
- package/dist/src/agent-client/createAgentResponseHandler.d.ts +2 -0
- package/dist/src/agent-client/createAgentResponseHandler.js.map +1 -1
- package/dist/src/app/adaptiveCards/adaptiveCardsActions.d.ts +1 -0
- package/dist/src/app/adaptiveCards/adaptiveCardsActions.js +1 -0
- package/dist/src/app/adaptiveCards/adaptiveCardsActions.js.map +1 -1
- package/dist/src/app/attachmentDownloadHttpClient.d.ts +13 -0
- package/dist/src/app/attachmentDownloadHttpClient.js +80 -0
- package/dist/src/app/attachmentDownloadHttpClient.js.map +1 -0
- package/dist/src/app/attachmentDownloader.d.ts +2 -0
- package/dist/src/app/attachmentDownloader.js +6 -11
- package/dist/src/app/attachmentDownloader.js.map +1 -1
- package/dist/src/app/auth/authorization.d.ts +58 -0
- package/dist/src/app/auth/authorization.js.map +1 -1
- package/dist/src/app/proactive/conversation.d.ts +18 -0
- package/dist/src/app/proactive/conversation.js +6 -0
- package/dist/src/app/proactive/conversation.js.map +1 -1
- package/dist/src/app/proactive/conversationBuilder.js +2 -1
- package/dist/src/app/proactive/conversationBuilder.js.map +1 -1
- package/dist/src/app/proactive/conversationReferenceBuilder.js +8 -4
- package/dist/src/app/proactive/conversationReferenceBuilder.js.map +1 -1
- package/dist/src/app/proactive/createConversationOptionsBuilder.d.ts +1 -0
- package/dist/src/app/proactive/createConversationOptionsBuilder.js +2 -1
- package/dist/src/app/proactive/createConversationOptionsBuilder.js.map +1 -1
- package/dist/src/app/proactive/proactive.d.ts +6 -0
- package/dist/src/app/proactive/proactive.js +32 -1
- package/dist/src/app/proactive/proactive.js.map +1 -1
- package/dist/src/app/routeList.d.ts +21 -0
- package/dist/src/app/routeList.js +27 -3
- package/dist/src/app/routeList.js.map +1 -1
- package/dist/src/app/teamsAttachmentDownloader.d.ts +8 -0
- package/dist/src/app/teamsAttachmentDownloader.js +17 -12
- package/dist/src/app/teamsAttachmentDownloader.js.map +1 -1
- package/dist/src/auth/authConfiguration.d.ts +1 -0
- package/dist/src/auth/authConfiguration.js.map +1 -1
- package/dist/src/auth/authConstants.d.ts +7 -0
- package/dist/src/auth/authConstants.js +7 -0
- package/dist/src/auth/authConstants.js.map +1 -1
- package/dist/src/auth/authProvider.d.ts +36 -0
- package/dist/src/auth/connectionManager.js +2 -1
- package/dist/src/auth/connectionManager.js.map +1 -1
- package/dist/src/auth/connections.d.ts +1 -0
- package/dist/src/auth/msal/msalConnectionManager.d.ts +7 -0
- package/dist/src/auth/msal/msalConnectionManager.js +7 -0
- package/dist/src/auth/msal/msalConnectionManager.js.map +1 -1
- package/dist/src/auth/msal/msalTokenProvider.d.ts +44 -0
- package/dist/src/auth/msal/msalTokenProvider.js +43 -0
- package/dist/src/auth/msal/msalTokenProvider.js.map +1 -1
- package/dist/src/auth/settings.d.ts +28 -0
- package/dist/src/auth/settings.js +23 -0
- package/dist/src/auth/settings.js.map +1 -1
- package/dist/src/auth/sidecar/sidecarAuthProvider.d.ts +3 -0
- package/dist/src/auth/sidecar/sidecarAuthProvider.js.map +1 -1
- package/dist/src/auth/sidecar/sidecarHttpClient.d.ts +0 -8
- package/dist/src/auth/sidecar/sidecarHttpClient.js +2 -68
- package/dist/src/auth/sidecar/sidecarHttpClient.js.map +1 -1
- package/dist/src/cards/taskModuleAction.d.ts +6 -0
- package/dist/src/cards/taskModuleAction.js +6 -0
- package/dist/src/cards/taskModuleAction.js.map +1 -1
- package/dist/src/cloudAdapter.js +16 -8
- package/dist/src/cloudAdapter.js.map +1 -1
- package/dist/src/configuration/configuration.d.ts +1 -0
- package/dist/src/configuration/configuration.js +2 -1
- package/dist/src/configuration/configuration.js.map +1 -1
- package/dist/src/configuration/environmentConfiguration.js +1 -1
- package/dist/src/configuration/environmentConfiguration.js.map +1 -1
- package/dist/src/connector-client/connectorClient.d.ts +8 -0
- package/dist/src/connector-client/connectorClient.js +8 -0
- package/dist/src/connector-client/connectorClient.js.map +1 -1
- package/dist/src/createCloudAdapter.d.ts +2 -0
- package/dist/src/createCloudAdapter.js.map +1 -1
- package/dist/src/errorHelper.d.ts +1 -0
- package/dist/src/errorHelper.js +9 -0
- package/dist/src/errorHelper.js.map +1 -1
- package/dist/src/headerPropagation.d.ts +38 -0
- package/dist/src/headerPropagation.js +33 -0
- package/dist/src/headerPropagation.js.map +1 -1
- package/dist/src/httpClient.d.ts +58 -0
- package/dist/src/httpClient.js +40 -0
- package/dist/src/httpClient.js.map +1 -1
- package/dist/src/interfaces/webResponse.d.ts +7 -0
- package/dist/src/middlewareSet.d.ts +6 -0
- package/dist/src/middlewareSet.js.map +1 -1
- package/dist/src/oauth/userTokenClient.d.ts +6 -0
- package/dist/src/oauth/userTokenClient.js +12 -0
- package/dist/src/oauth/userTokenClient.js.map +1 -1
- package/dist/src/outboundHostValidator.d.ts +33 -1
- package/dist/src/outboundHostValidator.js +60 -25
- package/dist/src/outboundHostValidator.js.map +1 -1
- package/dist/src/storage/fileStorage.d.ts +26 -0
- package/dist/src/storage/fileStorage.js +35 -6
- package/dist/src/storage/fileStorage.js.map +1 -1
- package/dist/src/storage/memoryStorage.d.ts +34 -0
- package/dist/src/storage/memoryStorage.js +41 -4
- package/dist/src/storage/memoryStorage.js.map +1 -1
- package/dist/src/storage/storage.d.ts +20 -0
- package/dist/src/storage/storage.js +7 -0
- package/dist/src/storage/storage.js.map +1 -1
- package/dist/src/storage/storageCompatibility.js +12 -6
- package/dist/src/storage/storageCompatibility.js.map +1 -1
- package/dist/src/storage/storageExpiry.d.ts +7 -0
- package/dist/src/storage/storageExpiry.js +7 -0
- package/dist/src/storage/storageExpiry.js.map +1 -1
- package/dist/src/turnContext.d.ts +10 -0
- package/dist/src/turnContext.js +5 -0
- package/dist/src/turnContext.js.map +1 -1
- package/dist/src/utils/env.js +22 -11
- package/dist/src/utils/env.js.map +1 -1
- package/dist/src/utils/ipAddress.d.ts +6 -0
- package/dist/src/utils/ipAddress.js +64 -0
- package/dist/src/utils/ipAddress.js.map +1 -0
- package/dist/src/utils/outboundUrl.d.ts +7 -0
- package/dist/src/utils/outboundUrl.js +52 -0
- package/dist/src/utils/outboundUrl.js.map +1 -0
- package/dist/src/utils.js +2 -1
- package/dist/src/utils.js.map +1 -1
- package/package.json +3 -3
- package/src/activityHandler.ts +9 -3
- package/src/agent-client/agentResponseHandler.ts +7 -0
- package/src/agent-client/createAgentResponseHandler.ts +2 -0
- package/src/app/adaptiveCards/adaptiveCardsActions.ts +1 -0
- package/src/app/attachmentDownloadHttpClient.ts +80 -0
- package/src/app/attachmentDownloader.ts +6 -9
- package/src/app/auth/authorization.ts +58 -0
- package/src/app/proactive/conversation.ts +24 -0
- package/src/app/proactive/conversationBuilder.ts +3 -1
- package/src/app/proactive/conversationReferenceBuilder.ts +12 -4
- package/src/app/proactive/createConversationOptionsBuilder.ts +5 -1
- package/src/app/proactive/proactive.ts +33 -1
- package/src/app/routeList.ts +30 -3
- package/src/app/teamsAttachmentDownloader.ts +17 -10
- package/src/auth/authConfiguration.ts +1 -0
- package/src/auth/authConstants.ts +7 -0
- package/src/auth/authProvider.ts +36 -0
- package/src/auth/connectionManager.ts +3 -1
- package/src/auth/connections.ts +1 -0
- package/src/auth/msal/msalConnectionManager.ts +7 -0
- package/src/auth/msal/msalTokenProvider.ts +59 -0
- package/src/auth/settings.ts +28 -0
- package/src/auth/sidecar/sidecarAuthProvider.ts +4 -0
- package/src/auth/sidecar/sidecarHttpClient.ts +2 -62
- package/src/cards/taskModuleAction.ts +6 -0
- package/src/cloudAdapter.ts +30 -10
- package/src/configuration/configuration.ts +4 -2
- package/src/configuration/environmentConfiguration.ts +7 -1
- package/src/connector-client/connectorClient.ts +8 -0
- package/src/createCloudAdapter.ts +2 -0
- package/src/errorHelper.ts +11 -0
- package/src/headerPropagation.ts +38 -0
- package/src/httpClient.ts +58 -0
- package/src/interfaces/webResponse.ts +7 -0
- package/src/middlewareSet.ts +6 -0
- package/src/oauth/userTokenClient.ts +13 -0
- package/src/outboundHostValidator.ts +84 -23
- package/src/storage/fileStorage.ts +46 -6
- package/src/storage/memoryStorage.ts +45 -4
- package/src/storage/storage.ts +20 -0
- package/src/storage/storageCompatibility.ts +18 -6
- package/src/storage/storageExpiry.ts +7 -0
- package/src/turnContext.ts +11 -0
- package/src/utils/env.ts +33 -11
- package/src/utils/ipAddress.ts +63 -0
- package/src/utils/outboundUrl.ts +50 -0
- package/src/utils.ts +3 -1
package/src/headerPropagation.ts
CHANGED
|
@@ -17,14 +17,21 @@ export class HeaderPropagation implements HeaderPropagationCollection {
|
|
|
17
17
|
|
|
18
18
|
private _protectedHeaders = ['x-ms-correlation-id']
|
|
19
19
|
|
|
20
|
+
/** Gets the normalized headers received on the incoming request. */
|
|
20
21
|
public get incoming (): Record<string, string> {
|
|
21
22
|
return this._incomingRequests
|
|
22
23
|
}
|
|
23
24
|
|
|
25
|
+
/** Gets the headers selected for propagation to outgoing requests. */
|
|
24
26
|
public get outgoing (): Record<string, string> {
|
|
25
27
|
return this._outgoingHeaders
|
|
26
28
|
}
|
|
27
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Creates a header propagation collection from incoming request headers.
|
|
32
|
+
*
|
|
33
|
+
* @param headers The incoming headers, including optional multi-value headers.
|
|
34
|
+
*/
|
|
28
35
|
constructor (headers: Record<string, string | string[] | undefined>) {
|
|
29
36
|
if (!headers) {
|
|
30
37
|
throw ExceptionHelper.generateException(Error, Errors.HeadersRequired)
|
|
@@ -39,6 +46,11 @@ export class HeaderPropagation implements HeaderPropagationCollection {
|
|
|
39
46
|
this._protectedHeaders = protectedHeaders // Restore protected headers list after propagation
|
|
40
47
|
}
|
|
41
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Copies existing incoming header values to the outgoing collection.
|
|
51
|
+
*
|
|
52
|
+
* @param headers The header names to propagate.
|
|
53
|
+
*/
|
|
42
54
|
propagate (headers: string[]) {
|
|
43
55
|
this.process(headers.map(h => [h, '']), key => {
|
|
44
56
|
if (this._incomingRequests[key] && !this._outgoingHeaders[key]) {
|
|
@@ -47,6 +59,11 @@ export class HeaderPropagation implements HeaderPropagationCollection {
|
|
|
47
59
|
})
|
|
48
60
|
}
|
|
49
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Adds headers that do not already exist in either collection.
|
|
64
|
+
*
|
|
65
|
+
* @param headers The headers to add.
|
|
66
|
+
*/
|
|
50
67
|
add (headers: Record<string, string>) {
|
|
51
68
|
this.process(Object.entries(headers ?? {}), (key, value) => {
|
|
52
69
|
if (!this._incomingRequests[key] && !this._outgoingHeaders[key]) {
|
|
@@ -55,6 +72,11 @@ export class HeaderPropagation implements HeaderPropagationCollection {
|
|
|
55
72
|
})
|
|
56
73
|
}
|
|
57
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Appends values to headers that already exist in either collection.
|
|
77
|
+
*
|
|
78
|
+
* @param headers The values to append.
|
|
79
|
+
*/
|
|
58
80
|
concat (headers: Record<string, string>) {
|
|
59
81
|
this.process(Object.entries(headers ?? {}), (key, value) => {
|
|
60
82
|
if (this._incomingRequests[key] || this._outgoingHeaders[key]) {
|
|
@@ -63,12 +85,23 @@ export class HeaderPropagation implements HeaderPropagationCollection {
|
|
|
63
85
|
})
|
|
64
86
|
}
|
|
65
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Sets outgoing header values unless the headers are protected.
|
|
90
|
+
*
|
|
91
|
+
* @param headers The headers to set.
|
|
92
|
+
*/
|
|
66
93
|
override (headers: Record<string, string>) {
|
|
67
94
|
this.process(Object.entries(headers ?? {}), (key, value) => {
|
|
68
95
|
this._outgoingHeaders[key] = value
|
|
69
96
|
})
|
|
70
97
|
}
|
|
71
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Finds a known header name without regard to casing.
|
|
101
|
+
*
|
|
102
|
+
* @param key The header name to find.
|
|
103
|
+
* @returns The original-cased header name, or `undefined` when it is unknown.
|
|
104
|
+
*/
|
|
72
105
|
key (key: string) {
|
|
73
106
|
return this._keys.find(k => k.toLowerCase() === key.toLowerCase())
|
|
74
107
|
}
|
|
@@ -113,6 +146,11 @@ export class HeaderPropagation implements HeaderPropagationCollection {
|
|
|
113
146
|
* A function type that defines how headers should be propagated.
|
|
114
147
|
*/
|
|
115
148
|
export interface HeaderPropagationDefinition {
|
|
149
|
+
/**
|
|
150
|
+
* Configures outgoing headers for a request.
|
|
151
|
+
*
|
|
152
|
+
* @param headers The collection to configure.
|
|
153
|
+
*/
|
|
116
154
|
(headers: HeaderPropagationCollection): void
|
|
117
155
|
}
|
|
118
156
|
|
package/src/httpClient.ts
CHANGED
|
@@ -11,13 +11,21 @@ import { Errors } from './errorHelper'
|
|
|
11
11
|
* Configuration for an HTTP request.
|
|
12
12
|
*/
|
|
13
13
|
export interface HttpRequestConfig {
|
|
14
|
+
/** The HTTP method. */
|
|
14
15
|
method: string
|
|
16
|
+
/** The absolute URL or path relative to the client's base URL. */
|
|
15
17
|
url: string
|
|
18
|
+
/** Headers to merge with the client's default headers. */
|
|
16
19
|
headers?: Record<string, string>
|
|
20
|
+
/** The request body. */
|
|
17
21
|
data?: unknown
|
|
22
|
+
/** Query parameters to append to the URL. */
|
|
18
23
|
params?: Record<string, string | undefined>
|
|
24
|
+
/** The format used to read the response body. */
|
|
19
25
|
responseType?: 'json' | 'arraybuffer' | 'stream'
|
|
26
|
+
/** The request timeout in milliseconds. */
|
|
20
27
|
timeout?: number
|
|
28
|
+
/** A signal used to cancel the request. */
|
|
21
29
|
signal?: AbortSignal
|
|
22
30
|
}
|
|
23
31
|
|
|
@@ -25,10 +33,15 @@ export interface HttpRequestConfig {
|
|
|
25
33
|
* Represents an HTTP response.
|
|
26
34
|
*/
|
|
27
35
|
export interface HttpResponse<T = unknown> {
|
|
36
|
+
/** The parsed response body. */
|
|
28
37
|
data: T
|
|
38
|
+
/** The HTTP status code. */
|
|
29
39
|
status: number
|
|
40
|
+
/** The HTTP status text. */
|
|
30
41
|
statusText: string
|
|
42
|
+
/** The response headers. */
|
|
31
43
|
headers: Headers
|
|
44
|
+
/** The configuration used for the request. */
|
|
32
45
|
config: HttpRequestConfig
|
|
33
46
|
}
|
|
34
47
|
|
|
@@ -36,7 +49,9 @@ export interface HttpResponse<T = unknown> {
|
|
|
36
49
|
* Options for creating an HttpClient instance.
|
|
37
50
|
*/
|
|
38
51
|
export interface HttpClientOptions {
|
|
52
|
+
/** The base URL used for relative request URLs. */
|
|
39
53
|
baseURL?: string
|
|
54
|
+
/** Default headers included with every request. */
|
|
40
55
|
headers?: Record<string, string>
|
|
41
56
|
}
|
|
42
57
|
|
|
@@ -47,27 +62,48 @@ export class HttpClient {
|
|
|
47
62
|
private _baseURL: string
|
|
48
63
|
private _defaultHeaders: Record<string, string>
|
|
49
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Creates an HTTP client.
|
|
67
|
+
*
|
|
68
|
+
* @param options The base URL and default headers.
|
|
69
|
+
*/
|
|
50
70
|
constructor (options: HttpClientOptions = {}) {
|
|
51
71
|
this._baseURL = options.baseURL ?? ''
|
|
52
72
|
this._defaultHeaders = this.normalizeHeaders(options.headers)
|
|
53
73
|
}
|
|
54
74
|
|
|
75
|
+
/** Gets the base URL used for relative requests. */
|
|
55
76
|
get baseURL (): string {
|
|
56
77
|
return this._baseURL
|
|
57
78
|
}
|
|
58
79
|
|
|
80
|
+
/** Gets the normalized default request headers. */
|
|
59
81
|
get defaultHeaders (): Record<string, string> {
|
|
60
82
|
return this._defaultHeaders
|
|
61
83
|
}
|
|
62
84
|
|
|
85
|
+
/** Replaces the default request headers. */
|
|
63
86
|
set defaultHeaders (headers: Record<string, string>) {
|
|
64
87
|
this._defaultHeaders = this.normalizeHeaders(headers)
|
|
65
88
|
}
|
|
66
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Sets a default request header.
|
|
92
|
+
*
|
|
93
|
+
* @param name The header name.
|
|
94
|
+
* @param value The header value.
|
|
95
|
+
*/
|
|
67
96
|
setHeader (name: string, value: string): void {
|
|
68
97
|
this._defaultHeaders[name.toLowerCase()] = value
|
|
69
98
|
}
|
|
70
99
|
|
|
100
|
+
/**
|
|
101
|
+
* Sends an HTTP request.
|
|
102
|
+
*
|
|
103
|
+
* @param config The request configuration.
|
|
104
|
+
* @returns The response with its parsed body.
|
|
105
|
+
* @throws An {@link HttpError} when the response status is not successful.
|
|
106
|
+
*/
|
|
71
107
|
async request<T = unknown> (config: HttpRequestConfig): Promise<HttpResponse<T>> {
|
|
72
108
|
const url = this.buildUrl(config.url, config.params)
|
|
73
109
|
const headers = this.normalizeHeaders({ ...this._defaultHeaders, ...config.headers })
|
|
@@ -142,6 +178,13 @@ export class HttpClient {
|
|
|
142
178
|
return httpResponse
|
|
143
179
|
}
|
|
144
180
|
|
|
181
|
+
/**
|
|
182
|
+
* Sends an HTTP request, defaulting to GET.
|
|
183
|
+
*
|
|
184
|
+
* @param url The absolute URL or path relative to the client's base URL.
|
|
185
|
+
* @param options Additional request options. The `method` and `url` options override the defaults.
|
|
186
|
+
* @returns The response with its parsed body.
|
|
187
|
+
*/
|
|
145
188
|
async get<T = unknown> (url: string, options?: Partial<HttpRequestConfig>): Promise<HttpResponse<T>> {
|
|
146
189
|
return this.request<T>({ method: 'get', url, ...options })
|
|
147
190
|
}
|
|
@@ -192,10 +235,20 @@ export class HttpClient {
|
|
|
192
235
|
* Error thrown when an HTTP request fails.
|
|
193
236
|
*/
|
|
194
237
|
export class HttpError extends Error {
|
|
238
|
+
/** The unsuccessful HTTP response. */
|
|
195
239
|
public readonly response: HttpResponse
|
|
240
|
+
/** The configuration used for the request. */
|
|
196
241
|
public readonly config: HttpRequestConfig
|
|
242
|
+
/** The HTTP status code. */
|
|
197
243
|
public readonly status: number
|
|
198
244
|
|
|
245
|
+
/**
|
|
246
|
+
* Creates an HTTP request error.
|
|
247
|
+
*
|
|
248
|
+
* @param message The error message.
|
|
249
|
+
* @param response The unsuccessful response.
|
|
250
|
+
* @param config The request configuration.
|
|
251
|
+
*/
|
|
199
252
|
constructor (message: string, response: HttpResponse, config: HttpRequestConfig) {
|
|
200
253
|
super(message)
|
|
201
254
|
this.name = 'HttpError'
|
|
@@ -204,6 +257,11 @@ export class HttpError extends Error {
|
|
|
204
257
|
this.status = response.status
|
|
205
258
|
}
|
|
206
259
|
|
|
260
|
+
/**
|
|
261
|
+
* Returns a serializable representation of the error.
|
|
262
|
+
*
|
|
263
|
+
* @returns The error, request, and response details.
|
|
264
|
+
*/
|
|
207
265
|
toJSON (): Record<string, unknown> {
|
|
208
266
|
return {
|
|
209
267
|
message: this.message,
|
|
@@ -17,11 +17,17 @@
|
|
|
17
17
|
* longer needs to import types from `express` at compile time.
|
|
18
18
|
*/
|
|
19
19
|
export interface WebResponse {
|
|
20
|
+
/** Sets the HTTP status code. */
|
|
20
21
|
status (code: number): this
|
|
22
|
+
/** Sets a response header. */
|
|
21
23
|
setHeader (name: string, value: string): this
|
|
24
|
+
/** Sends an optional response body. */
|
|
22
25
|
send (body?: unknown): this
|
|
26
|
+
/** Ends the response. */
|
|
23
27
|
end (): this
|
|
28
|
+
/** Indicates whether response headers have been sent. */
|
|
24
29
|
headersSent: boolean
|
|
30
|
+
/** Indicates whether the response stream has ended. */
|
|
25
31
|
writableEnded: boolean
|
|
26
32
|
}
|
|
27
33
|
|
|
@@ -40,5 +46,6 @@ export type NextFunction = (err?: any) => void
|
|
|
40
46
|
* (`req.params.conversationId`) without coupling to Express's `Request` type.
|
|
41
47
|
*/
|
|
42
48
|
export interface WebRequestParamsCarrier {
|
|
49
|
+
/** Route parameters parsed from the request URL. */
|
|
43
50
|
params?: Record<string, string | undefined>
|
|
44
51
|
}
|
package/src/middlewareSet.ts
CHANGED
|
@@ -10,6 +10,12 @@ const logger = debug('agents:middleware')
|
|
|
10
10
|
* Interface for middleware.
|
|
11
11
|
*/
|
|
12
12
|
export interface Middleware {
|
|
13
|
+
/**
|
|
14
|
+
* Processes a turn and optionally continues the middleware chain.
|
|
15
|
+
*
|
|
16
|
+
* @param context The current turn context.
|
|
17
|
+
* @param next The callback that continues the middleware chain.
|
|
18
|
+
*/
|
|
13
19
|
onTurn: (context: TurnContext, next: () => Promise<void>) => Promise<void>
|
|
14
20
|
}
|
|
15
21
|
|
|
@@ -34,6 +34,7 @@ function formatHttpErrorMessage (error: HttpError): string {
|
|
|
34
34
|
* Client for managing user tokens.
|
|
35
35
|
*/
|
|
36
36
|
export class UserTokenClient {
|
|
37
|
+
/** The HTTP client used for token service requests. */
|
|
37
38
|
client: HttpClient
|
|
38
39
|
private msAppId: string = ''
|
|
39
40
|
private authProvider?: AuthProvider
|
|
@@ -58,6 +59,13 @@ export class UserTokenClient {
|
|
|
58
59
|
*/
|
|
59
60
|
constructor (httpClient: HttpClient, authProvider?: AuthProvider, authScope?: string)
|
|
60
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Initializes the client from an application ID or an HTTP client.
|
|
64
|
+
*
|
|
65
|
+
* @param param The application ID or configured HTTP client.
|
|
66
|
+
* @param authProvider The optional provider used to acquire an access token lazily.
|
|
67
|
+
* @param authScope The optional access-token scope.
|
|
68
|
+
*/
|
|
61
69
|
constructor (param: string | HttpClient, authProvider?: AuthProvider, authScope?: string) {
|
|
62
70
|
if (typeof param === 'string') {
|
|
63
71
|
const baseURL = getTokenServiceEndpoint()
|
|
@@ -260,6 +268,11 @@ export class UserTokenClient {
|
|
|
260
268
|
})
|
|
261
269
|
}
|
|
262
270
|
|
|
271
|
+
/**
|
|
272
|
+
* Replaces the bearer token used for token service requests.
|
|
273
|
+
*
|
|
274
|
+
* @param token The access token.
|
|
275
|
+
*/
|
|
263
276
|
public updateAuthToken (token: string): void {
|
|
264
277
|
this.client.setHeader('Authorization', `Bearer ${token}`)
|
|
265
278
|
this.authInitialized = true
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
} from './configuration/configuration'
|
|
10
10
|
import { loadModernEnvironmentConfiguration } from './configuration/environmentConfiguration'
|
|
11
11
|
import { mergeDefined } from './utils'
|
|
12
|
+
import { getHttpsUrl, hasAllowedOutboundAddresses, isLocalOrBlockedHost } from './utils/outboundUrl'
|
|
12
13
|
|
|
13
14
|
/** Hosts used by Microsoft channel callbacks and hosted attachments. */
|
|
14
15
|
const DEFAULT_MICROSOFT_HOSTS = Object.freeze([
|
|
@@ -37,6 +38,12 @@ export interface OutboundHostValidatorOptions {
|
|
|
37
38
|
*/
|
|
38
39
|
includeDefaultMicrosoftHosts?: boolean
|
|
39
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Allows allowlisted HTTPS destinations to use private, shared (RFC 6598),
|
|
43
|
+
* loopback, link-local, or unspecified addresses. Defaults to `false`.
|
|
44
|
+
*/
|
|
45
|
+
allowPrivateNetworkAddresses?: boolean
|
|
46
|
+
|
|
40
47
|
/** Additional exact hosts or host suffixes to allow.
|
|
41
48
|
* An entry matches a request host when the host equals the entry or is a subdomain of it (e.g. `contoso.com` matches `contoso.com` and `files.contoso.com`).
|
|
42
49
|
* A leading `*.` is accepted and ignored (treated as a suffix). Ports and paths are ignored if provided.
|
|
@@ -46,7 +53,14 @@ export interface OutboundHostValidatorOptions {
|
|
|
46
53
|
|
|
47
54
|
/** A policy that decides whether an outbound URL is safe to request. */
|
|
48
55
|
export interface OutboundUrlPolicy {
|
|
56
|
+
/** Indicates whether URL validation is enforced. */
|
|
49
57
|
readonly enabled: boolean
|
|
58
|
+
/**
|
|
59
|
+
* Determines whether an outbound URL is allowed.
|
|
60
|
+
*
|
|
61
|
+
* @param url The URL to validate.
|
|
62
|
+
* @returns `true` when the URL is allowed.
|
|
63
|
+
*/
|
|
50
64
|
isAllowed(url: string | URL | null | undefined): boolean
|
|
51
65
|
}
|
|
52
66
|
|
|
@@ -54,43 +68,88 @@ export interface OutboundUrlPolicy {
|
|
|
54
68
|
* Shared allowlist policy for server-side outbound requests.
|
|
55
69
|
*
|
|
56
70
|
* A configured suffix matches both the exact host and its subdomains. The
|
|
57
|
-
* policy is disabled by default to preserve existing SDK behavior.
|
|
71
|
+
* policy is disabled by default to preserve existing SDK behavior. Enabled
|
|
72
|
+
* policies require HTTPS without userinfo and restrict private addresses.
|
|
73
|
+
* Attachment downloaders additionally validate DNS answers and redirect hops.
|
|
58
74
|
*/
|
|
59
75
|
export class OutboundHostValidator implements OutboundUrlPolicy {
|
|
76
|
+
/** Indicates whether allowlist enforcement is enabled. */
|
|
60
77
|
public readonly enabled: boolean
|
|
61
78
|
private readonly suffixes: ReadonlySet<string>
|
|
79
|
+
private readonly allowPrivateNetworkAddresses: boolean
|
|
62
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Creates an outbound host validator.
|
|
83
|
+
*
|
|
84
|
+
* @param options The allowlist configuration.
|
|
85
|
+
*/
|
|
63
86
|
public constructor (options: OutboundHostValidatorOptions = {}) {
|
|
64
87
|
const resolved = options.configurationContext
|
|
65
88
|
? resolveOutboundHostValidatorOptions(options)
|
|
66
89
|
: options
|
|
67
90
|
this.enabled = resolved.enabled ?? false
|
|
91
|
+
this.allowPrivateNetworkAddresses = resolved.allowPrivateNetworkAddresses ?? false
|
|
68
92
|
|
|
69
93
|
const suffixes = new Set<string>()
|
|
70
94
|
if (resolved.includeDefaultMicrosoftHosts ?? true) {
|
|
71
|
-
for (const host of DEFAULT_MICROSOFT_HOSTS)
|
|
95
|
+
for (const host of DEFAULT_MICROSOFT_HOSTS) {
|
|
96
|
+
suffixes.add(host)
|
|
97
|
+
}
|
|
72
98
|
}
|
|
73
99
|
|
|
74
100
|
for (const host of resolved.hosts ?? []) {
|
|
75
101
|
const normalized = normalizeConfiguredHost(host)
|
|
76
|
-
if (normalized)
|
|
102
|
+
if (normalized) {
|
|
103
|
+
suffixes.add(normalized)
|
|
104
|
+
}
|
|
77
105
|
}
|
|
78
106
|
|
|
79
107
|
this.suffixes = suffixes
|
|
80
108
|
}
|
|
81
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Determines whether a URL's host matches the configured allowlist.
|
|
112
|
+
*
|
|
113
|
+
* @param input The absolute URL to validate.
|
|
114
|
+
* @returns `true` when validation is disabled or the host is allowed.
|
|
115
|
+
*/
|
|
82
116
|
public isAllowed (input: string | URL | null | undefined): boolean {
|
|
83
|
-
if (!this.enabled)
|
|
117
|
+
if (!this.enabled) {
|
|
118
|
+
return true
|
|
119
|
+
}
|
|
84
120
|
|
|
85
|
-
const
|
|
86
|
-
if (!
|
|
121
|
+
const url = getHttpsUrl(input)
|
|
122
|
+
if (!url) {
|
|
123
|
+
return false
|
|
124
|
+
}
|
|
125
|
+
const host = normalizeHostname(url.hostname)
|
|
126
|
+
if (!host || (!this.allowPrivateNetworkAddresses && isLocalOrBlockedHost(host))) {
|
|
127
|
+
return false
|
|
128
|
+
}
|
|
87
129
|
|
|
88
130
|
for (const suffix of this.suffixes) {
|
|
89
|
-
if (host === suffix || host.endsWith(`.${suffix}`))
|
|
131
|
+
if (host === suffix || host.endsWith(`.${suffix}`)) {
|
|
132
|
+
return true
|
|
133
|
+
}
|
|
90
134
|
}
|
|
91
135
|
|
|
92
136
|
return false
|
|
93
137
|
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Checks the host policy and all resolved destination addresses before an attachment request.
|
|
141
|
+
* DNS prevalidation does not pin the HTTP connection to the checked addresses.
|
|
142
|
+
*/
|
|
143
|
+
public async isAllowedAsync (input: string | URL | null | undefined): Promise<boolean> {
|
|
144
|
+
if (!this.isAllowed(input)) {
|
|
145
|
+
return false
|
|
146
|
+
}
|
|
147
|
+
if (!this.enabled) {
|
|
148
|
+
return true
|
|
149
|
+
}
|
|
150
|
+
const url = getHttpsUrl(input)
|
|
151
|
+
return url !== undefined && await hasAllowedOutboundAddresses(url, this.allowPrivateNetworkAddresses)
|
|
152
|
+
}
|
|
94
153
|
}
|
|
95
154
|
|
|
96
155
|
/**
|
|
@@ -115,11 +174,18 @@ function resolveOutboundHostValidatorOptions (
|
|
|
115
174
|
const external = getConfigurationSnapshot(options.configurationContext)
|
|
116
175
|
const fromEnv = loadOutboundHostValidatorOptionsFromEnv()
|
|
117
176
|
const direct: OutboundHostValidatorOptions = {}
|
|
118
|
-
if (options.enabled !== undefined)
|
|
177
|
+
if (options.enabled !== undefined) {
|
|
178
|
+
direct.enabled = options.enabled
|
|
179
|
+
}
|
|
119
180
|
if (options.includeDefaultMicrosoftHosts !== undefined) {
|
|
120
181
|
direct.includeDefaultMicrosoftHosts = options.includeDefaultMicrosoftHosts
|
|
121
182
|
}
|
|
122
|
-
if (options.hosts !== undefined)
|
|
183
|
+
if (options.hosts !== undefined) {
|
|
184
|
+
direct.hosts = options.hosts
|
|
185
|
+
}
|
|
186
|
+
if (options.allowPrivateNetworkAddresses !== undefined) {
|
|
187
|
+
direct.allowPrivateNetworkAddresses = options.allowPrivateNetworkAddresses
|
|
188
|
+
}
|
|
123
189
|
return mergeDefined<OutboundHostValidatorOptions>(
|
|
124
190
|
external.fallback.outboundHostValidator,
|
|
125
191
|
fromEnv,
|
|
@@ -129,25 +195,20 @@ function resolveOutboundHostValidatorOptions (
|
|
|
129
195
|
)
|
|
130
196
|
}
|
|
131
197
|
|
|
132
|
-
function getUrlHost (input: string | URL | null | undefined): string | undefined {
|
|
133
|
-
if (!input) return undefined
|
|
134
|
-
|
|
135
|
-
try {
|
|
136
|
-
const url = input instanceof URL ? input : new URL(input)
|
|
137
|
-
return normalizeHostname(url.hostname)
|
|
138
|
-
} catch {
|
|
139
|
-
return undefined
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
198
|
function normalizeConfiguredHost (input: string): string | undefined {
|
|
144
199
|
let candidate = input?.trim()
|
|
145
|
-
if (!candidate)
|
|
146
|
-
|
|
200
|
+
if (!candidate) {
|
|
201
|
+
return undefined
|
|
202
|
+
}
|
|
203
|
+
if (candidate.startsWith('*.')) {
|
|
204
|
+
candidate = candidate.slice(2)
|
|
205
|
+
}
|
|
147
206
|
|
|
148
207
|
try {
|
|
149
208
|
const absolute = new URL(candidate)
|
|
150
|
-
if (absolute.hostname)
|
|
209
|
+
if (absolute.hostname) {
|
|
210
|
+
return normalizeHostname(absolute.hostname)
|
|
211
|
+
}
|
|
151
212
|
} catch {
|
|
152
213
|
// A bare host, host:port, or host/path is handled below.
|
|
153
214
|
}
|
|
@@ -30,16 +30,29 @@ class FileStorageInternals {
|
|
|
30
30
|
private readonly statePath: string
|
|
31
31
|
private readonly versionsPath: string
|
|
32
32
|
private readonly expirationsPath: string
|
|
33
|
+
|
|
34
|
+
/** The stored values keyed by storage key. */
|
|
33
35
|
readonly state: Record<string, unknown>
|
|
36
|
+
|
|
37
|
+
/** The current version associated with each storage key. */
|
|
34
38
|
readonly versions: Record<string, string>
|
|
39
|
+
|
|
40
|
+
/** The expiration time associated with each storage key, expressed as Unix time in milliseconds. */
|
|
35
41
|
readonly expirations: Record<string, number>
|
|
36
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Creates the shared file storage implementation.
|
|
45
|
+
*
|
|
46
|
+
* @param folder The folder containing the storage files.
|
|
47
|
+
*/
|
|
37
48
|
constructor (folder: string) {
|
|
38
49
|
fs.mkdirSync(folder, { recursive: true })
|
|
39
50
|
this.statePath = path.join(folder, 'state.json')
|
|
40
51
|
this.versionsPath = path.join(folder, 'state.versions.json')
|
|
41
52
|
this.expirationsPath = path.join(folder, 'state.expirations.json')
|
|
42
|
-
if (!fs.existsSync(this.statePath))
|
|
53
|
+
if (!fs.existsSync(this.statePath)) {
|
|
54
|
+
fs.writeFileSync(this.statePath, '{}')
|
|
55
|
+
}
|
|
43
56
|
this.state = JSON.parse(fs.readFileSync(this.statePath, 'utf8')) as Record<string, unknown>
|
|
44
57
|
this.versions = fs.existsSync(this.versionsPath)
|
|
45
58
|
? JSON.parse(fs.readFileSync(this.versionsPath, 'utf8')) as Record<string, string>
|
|
@@ -49,6 +62,7 @@ class FileStorageInternals {
|
|
|
49
62
|
: {}
|
|
50
63
|
}
|
|
51
64
|
|
|
65
|
+
/** Writes the current storage state and metadata to disk. */
|
|
52
66
|
flush (): void {
|
|
53
67
|
fs.writeFileSync(this.statePath, JSON.stringify(this.state, null, 2))
|
|
54
68
|
if (fs.existsSync(this.versionsPath) || Object.keys(this.versions).length > 0) {
|
|
@@ -59,17 +73,34 @@ class FileStorageInternals {
|
|
|
59
73
|
}
|
|
60
74
|
}
|
|
61
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Gets the current version for a key, creating one when necessary.
|
|
78
|
+
*
|
|
79
|
+
* @param key The storage key.
|
|
80
|
+
* @returns The current version.
|
|
81
|
+
*/
|
|
62
82
|
getOrCreateVersion (key: string): string {
|
|
63
83
|
const version = this.versions[key] ?? randomUUID()
|
|
64
84
|
this.versions[key] = version
|
|
65
85
|
return version
|
|
66
86
|
}
|
|
67
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Determines whether a stored value has expired.
|
|
90
|
+
*
|
|
91
|
+
* @param key The storage key.
|
|
92
|
+
* @returns `true` when the value has expired; otherwise, `false`.
|
|
93
|
+
*/
|
|
68
94
|
isExpired (key: string): boolean {
|
|
69
95
|
const expiresAt = this.expirations[key]
|
|
70
96
|
return expiresAt !== undefined && expiresAt <= Date.now()
|
|
71
97
|
}
|
|
72
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Removes a value and its version and expiration metadata.
|
|
101
|
+
*
|
|
102
|
+
* @param key The storage key.
|
|
103
|
+
*/
|
|
73
104
|
remove (key: string): void {
|
|
74
105
|
delete this.state[key]
|
|
75
106
|
delete this.versions[key]
|
|
@@ -232,7 +263,9 @@ export class FileStorageV2 extends StorageV2 {
|
|
|
232
263
|
version: version ?? this.internals.getOrCreateVersion(key),
|
|
233
264
|
}]
|
|
234
265
|
}))
|
|
235
|
-
if (createdVersion)
|
|
266
|
+
if (createdVersion) {
|
|
267
|
+
this.internals.flush()
|
|
268
|
+
}
|
|
236
269
|
return results
|
|
237
270
|
})
|
|
238
271
|
}
|
|
@@ -277,13 +310,18 @@ export class FileStorageV2 extends StorageV2 {
|
|
|
277
310
|
const version = randomUUID()
|
|
278
311
|
this.internals.state[key] = structuredClone(value)
|
|
279
312
|
this.internals.versions[key] = version
|
|
280
|
-
if (expiresAt === undefined)
|
|
281
|
-
|
|
313
|
+
if (expiresAt === undefined) {
|
|
314
|
+
delete this.internals.expirations[key]
|
|
315
|
+
} else {
|
|
316
|
+
this.internals.expirations[key] = expiresAt
|
|
317
|
+
}
|
|
282
318
|
results[key] = { key, status: StorageOperationStatus.Succeeded, version }
|
|
283
319
|
changed = true
|
|
284
320
|
}
|
|
285
321
|
}
|
|
286
|
-
if (changed || createdVersion)
|
|
322
|
+
if (changed || createdVersion) {
|
|
323
|
+
this.internals.flush()
|
|
324
|
+
}
|
|
287
325
|
return results
|
|
288
326
|
})
|
|
289
327
|
}
|
|
@@ -325,7 +363,9 @@ export class FileStorageV2 extends StorageV2 {
|
|
|
325
363
|
changed = true
|
|
326
364
|
}
|
|
327
365
|
}
|
|
328
|
-
if (changed || createdVersion)
|
|
366
|
+
if (changed || createdVersion) {
|
|
367
|
+
this.internals.flush()
|
|
368
|
+
}
|
|
329
369
|
return results
|
|
330
370
|
})
|
|
331
371
|
}
|