@microsoft/agents-hosting 1.7.0-beta.1.g8bcd4f11a7 → 1.7.0-beta.11.gb5d3e8c751
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 +25 -0
- package/dist/package.json +3 -3
- package/dist/src/agent-client/agentClient.d.ts +4 -0
- package/dist/src/agent-client/agentClient.js +32 -18
- package/dist/src/agent-client/agentClient.js.map +1 -1
- package/dist/src/agent-client/agentResponseHandler.d.ts +27 -6
- package/dist/src/agent-client/agentResponseHandler.js +18 -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 +134 -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 -2
- 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.d.ts +4 -0
- package/dist/src/app/agentApplication.js +20 -8
- package/dist/src/app/agentApplication.js.map +1 -1
- package/dist/src/app/streaming/streamingResponse.js +2 -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/authConfiguration.js +5 -3
- package/dist/src/auth/authConfiguration.js.map +1 -1
- package/dist/src/auth/index.d.ts +1 -0
- package/dist/src/auth/index.js +1 -0
- 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 +112 -14
- package/dist/src/auth/jwt-middleware.js.map +1 -1
- package/dist/src/auth/msal/msalTokenProvider.js +16 -16
- package/dist/src/auth/msal/msalTokenProvider.js.map +1 -1
- package/dist/src/baseAdapter.js +3 -1
- package/dist/src/baseAdapter.js.map +1 -1
- package/dist/src/cloudAdapter.d.ts +15 -2
- package/dist/src/cloudAdapter.js +28 -10
- 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 +206 -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 +2 -0
- package/dist/src/index.js +2 -0
- 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.js +2 -1
- 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 +37 -21
- package/src/agent-client/agentResponseHandler.ts +45 -67
- package/src/agent-client/createAgentResponseHandler.ts +189 -0
- package/src/agent-client/index.ts +1 -0
- package/src/app/adaptiveCards/activityValueParsers.ts +3 -3
- package/src/app/adaptiveCards/adaptiveCardsActions.ts +5 -5
- package/src/app/agentApplication.ts +23 -16
- package/src/app/streaming/streamingResponse.ts +2 -2
- package/src/app/turnState.ts +15 -14
- package/src/app/turnStateProperty.ts +4 -2
- package/src/auth/authConfiguration.ts +5 -3
- package/src/auth/index.ts +1 -0
- package/src/auth/jwt-middleware.ts +127 -17
- package/src/auth/msal/msalTokenProvider.ts +16 -16
- package/src/baseAdapter.ts +3 -2
- package/src/cloudAdapter.ts +33 -12
- package/src/createCloudAdapter.ts +56 -0
- package/src/errorHelper.ts +244 -89
- package/src/headerPropagation.ts +4 -1
- package/src/index.ts +2 -0
- 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 +3 -2
- 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
|
@@ -8,6 +8,8 @@ import { loadEnvSettings, AuthConfiguration, envParser, envParserUtils, LoadEnv,
|
|
|
8
8
|
|
|
9
9
|
export { type AuthConfiguration, type ConnectionSettings, type ConnectionSettingsBase, type MsalConnectionSettings, type SidecarConnectionSettings, AuthType, resolveAuthority, type ConnectionMapItem, resolveAuthType } from './settings'
|
|
10
10
|
import { prune } from '../utils'
|
|
11
|
+
import { ExceptionHelper } from '@microsoft/agents-activity'
|
|
12
|
+
import { Errors } from '../errorHelper'
|
|
11
13
|
|
|
12
14
|
const logger = debug('agents:authConfiguration')
|
|
13
15
|
|
|
@@ -118,12 +120,12 @@ const connectionsEnv = {
|
|
|
118
120
|
const map = connectionsMap ?? connectionsMapEnv.connectionsMap
|
|
119
121
|
const name = map?.find((item) => item.serviceUrl === '*')?.connection
|
|
120
122
|
if (!name) {
|
|
121
|
-
throw
|
|
123
|
+
throw ExceptionHelper.generateException(Error, Errors.NoDefaultConnectionFound)
|
|
122
124
|
}
|
|
123
125
|
|
|
124
126
|
const connection = conn?.get(name ?? '')
|
|
125
127
|
if (!connection) {
|
|
126
|
-
throw
|
|
128
|
+
throw ExceptionHelper.generateException(Error, Errors.ConnectionNotFoundInEnvironment, undefined, { connectionName: name })
|
|
127
129
|
}
|
|
128
130
|
|
|
129
131
|
return applyDefaultSettings({ ...connection, connections: conn, connectionsMap: map })
|
|
@@ -359,7 +361,7 @@ export const loadAuthConfigFromEnv = (cnxName?: string): AuthConfiguration => {
|
|
|
359
361
|
// No connections provided, we need to populate the connections map with the old config settings
|
|
360
362
|
const result = applyDefaultSettings(cnxName?.trim() ? legacyPrefixEnv.process(globalEnv.env, cnxName) : globalEnv.legacyPrefixSettings)
|
|
361
363
|
if (cnxName && !result.clientId) {
|
|
362
|
-
throw
|
|
364
|
+
throw ExceptionHelper.generateException(Error, Errors.ClientIdNotFoundForConnection, undefined, { connectionName: cnxName })
|
|
363
365
|
}
|
|
364
366
|
|
|
365
367
|
logger.info('Auth settings loaded from environment', {
|
package/src/auth/index.ts
CHANGED
|
@@ -5,5 +5,6 @@ export * from './msal/msalTokenProvider'
|
|
|
5
5
|
export * from './request'
|
|
6
6
|
export * from './msal/msalTokenCredential'
|
|
7
7
|
export * from './connectionManager'
|
|
8
|
+
export * from './connections'
|
|
8
9
|
export * from './msal/msalConnectionManager'
|
|
9
10
|
export * from './sidecar/sidecarAuthProvider'
|
|
@@ -4,8 +4,10 @@
|
|
|
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'
|
|
@@ -13,6 +15,25 @@ import { debug } from '@microsoft/agents-telemetry'
|
|
|
13
15
|
const logger = debug('agents:jwt-middleware')
|
|
14
16
|
const jwksClients = new Map<string, JwksClient>()
|
|
15
17
|
const maxJwksClients = 100
|
|
18
|
+
const authorizedAudience = Symbol('authorizedAudience')
|
|
19
|
+
|
|
20
|
+
interface AuthorizedRequest extends Request {
|
|
21
|
+
[authorizedAudience]?: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface VerifiedToken {
|
|
25
|
+
payload: JwtPayload
|
|
26
|
+
audience: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Gets the configured audience selected while authorizing a request.
|
|
31
|
+
* @param req The authorized request.
|
|
32
|
+
* @returns The matched configured client ID, or `undefined` for anonymous requests.
|
|
33
|
+
*/
|
|
34
|
+
export function getAuthorizedAudience (req: Request): string | undefined {
|
|
35
|
+
return (req as AuthorizedRequest)[authorizedAudience]
|
|
36
|
+
}
|
|
16
37
|
|
|
17
38
|
/**
|
|
18
39
|
* Clears process-wide JWKS clients.
|
|
@@ -57,24 +78,30 @@ export function getJwksClient (jwksUri: string): JwksClient {
|
|
|
57
78
|
* Verifies the JWT token.
|
|
58
79
|
* @param raw The raw JWT token.
|
|
59
80
|
* @param config The authentication configuration.
|
|
60
|
-
* @returns A promise that resolves to the
|
|
81
|
+
* @returns A promise that resolves to the verified payload and matched configured audience.
|
|
61
82
|
*/
|
|
62
|
-
const verifyToken = async (raw: string, config: AuthConfiguration): Promise<
|
|
83
|
+
const verifyToken = async (raw: string, config: AuthConfiguration): Promise<VerifiedToken> => {
|
|
63
84
|
const payload = jwt.decode(raw) as JwtPayload
|
|
64
85
|
logger.debug('jwt.decode ', JSON.stringify(payload))
|
|
65
86
|
|
|
66
87
|
if (!payload) {
|
|
67
|
-
throw
|
|
88
|
+
throw ExceptionHelper.generateException(Error, Errors.InvalidJwtToken)
|
|
68
89
|
}
|
|
69
|
-
const
|
|
90
|
+
const audiences = Array.isArray(payload.aud)
|
|
91
|
+
? payload.aud
|
|
92
|
+
: typeof payload.aud === 'string'
|
|
93
|
+
? [payload.aud]
|
|
94
|
+
: []
|
|
70
95
|
|
|
71
96
|
const matchingEntry = config.connections && config.connections.size > 0
|
|
72
|
-
? [...config.connections.entries()].find(([_, configuration]) =>
|
|
97
|
+
? [...config.connections.entries()].find(([_, configuration]) =>
|
|
98
|
+
typeof configuration.clientId === 'string' && audiences.includes(configuration.clientId)
|
|
99
|
+
)
|
|
73
100
|
: undefined
|
|
74
101
|
|
|
75
102
|
if (!matchingEntry) {
|
|
76
|
-
const err =
|
|
77
|
-
logger.error(err.message,
|
|
103
|
+
const err = ExceptionHelper.generateException(Error, Errors.JwtAudienceMismatch)
|
|
104
|
+
logger.error(err.message, audiences)
|
|
78
105
|
throw err
|
|
79
106
|
}
|
|
80
107
|
|
|
@@ -107,7 +134,7 @@ const verifyToken = async (raw: string, config: AuthConfiguration): Promise<JwtP
|
|
|
107
134
|
clockTolerance: 300
|
|
108
135
|
}
|
|
109
136
|
|
|
110
|
-
|
|
137
|
+
const verifiedPayload = await new Promise<JwtPayload>((resolve, reject) => {
|
|
111
138
|
jwt.verify(raw, getKey, verifyOptions, (err, user) => {
|
|
112
139
|
if (err) {
|
|
113
140
|
logger.error('jwt.verify ', JSON.stringify(err))
|
|
@@ -117,6 +144,75 @@ const verifyToken = async (raw: string, config: AuthConfiguration): Promise<JwtP
|
|
|
117
144
|
resolve(user as JwtPayload)
|
|
118
145
|
})
|
|
119
146
|
})
|
|
147
|
+
return {
|
|
148
|
+
payload: verifiedPayload,
|
|
149
|
+
audience: authConfig.clientId!
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Determines whether an `Authorization` header value is present (non-empty).
|
|
155
|
+
*
|
|
156
|
+
* The {@link Request} contract allows `string | string[] | undefined` because
|
|
157
|
+
* different web frameworks surface headers differently. This treats an empty
|
|
158
|
+
* string or empty array as "absent" so callers can distinguish a missing header
|
|
159
|
+
* (anonymous / 401) from a present-but-malformed one (always 401).
|
|
160
|
+
* @param authorization The raw `Authorization` header value.
|
|
161
|
+
* @returns `true` when a non-empty header value is present.
|
|
162
|
+
*/
|
|
163
|
+
function hasAuthorizationHeader (authorization: string | string[] | undefined): boolean {
|
|
164
|
+
if (Array.isArray(authorization)) {
|
|
165
|
+
return authorization.some((value) => typeof value === 'string' && value.trim().length > 0)
|
|
166
|
+
}
|
|
167
|
+
return typeof authorization === 'string' && authorization.trim().length > 0
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Parses a single `Authorization` header value and returns its bearer token.
|
|
172
|
+
*
|
|
173
|
+
* Validates the `Bearer <token>` scheme, returning `undefined` for anything
|
|
174
|
+
* malformed (wrong scheme, missing token, or extra whitespace-delimited parts).
|
|
175
|
+
* @param headerValue A single `Authorization` header value.
|
|
176
|
+
* @returns The bearer token, or `undefined` if the value is absent or malformed.
|
|
177
|
+
*/
|
|
178
|
+
function parseBearerValue (headerValue: string | undefined): string | undefined {
|
|
179
|
+
if (typeof headerValue !== 'string') {
|
|
180
|
+
return undefined
|
|
181
|
+
}
|
|
182
|
+
const parts = headerValue.trim().split(/\s+/)
|
|
183
|
+
if (parts.length !== 2) {
|
|
184
|
+
return undefined
|
|
185
|
+
}
|
|
186
|
+
const [scheme, token] = parts
|
|
187
|
+
if (scheme.toLowerCase() !== 'bearer' || !token) {
|
|
188
|
+
return undefined
|
|
189
|
+
}
|
|
190
|
+
return token
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Extracts the bearer token from a raw `Authorization` header value.
|
|
195
|
+
*
|
|
196
|
+
* On Node's core HTTP parser (which both Express and Fastify use) `Authorization`
|
|
197
|
+
* is always surfaced as a single string holding the *first* header line —
|
|
198
|
+
* duplicate `Authorization` headers are discarded, not comma-joined. The
|
|
199
|
+
* `string[]` case is handled only because the {@link Request} contract permits
|
|
200
|
+
* it for non-Node frameworks; when an array is supplied, every entry is inspected
|
|
201
|
+
* and the first valid `Bearer <token>` is returned (the bearer value is not
|
|
202
|
+
* assumed to be first). Returns `undefined` for anything malformed so the caller
|
|
203
|
+
* can emit a consistent 401 instead of throwing.
|
|
204
|
+
* @param authorization The raw `Authorization` header value.
|
|
205
|
+
* @returns The bearer token, or `undefined` if the header is absent or malformed.
|
|
206
|
+
*/
|
|
207
|
+
function extractBearerToken (authorization: string | string[] | undefined): string | undefined {
|
|
208
|
+
const values = Array.isArray(authorization) ? authorization : [authorization]
|
|
209
|
+
for (const value of values) {
|
|
210
|
+
const token = parseBearerValue(value)
|
|
211
|
+
if (token) {
|
|
212
|
+
return token
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return undefined
|
|
120
216
|
}
|
|
121
217
|
|
|
122
218
|
/**
|
|
@@ -125,7 +221,7 @@ const verifyToken = async (raw: string, config: AuthConfiguration): Promise<JwtP
|
|
|
125
221
|
* @returns An Express middleware function.
|
|
126
222
|
*/
|
|
127
223
|
export const authorizeJWT = (authConfig: AuthConfiguration) => {
|
|
128
|
-
return async function (req: Request, res:
|
|
224
|
+
return async function (req: Request, res: WebResponse, next: NextFunction) {
|
|
129
225
|
let failed = false
|
|
130
226
|
logger.debug('authorizing jwt')
|
|
131
227
|
if (req.method !== 'POST' && req.method !== 'GET') {
|
|
@@ -133,18 +229,32 @@ export const authorizeJWT = (authConfig: AuthConfiguration) => {
|
|
|
133
229
|
logger.warn('Method not allowed', req.method)
|
|
134
230
|
res.status(405).send({ 'jwt-auth-error': 'Method not allowed' })
|
|
135
231
|
} else {
|
|
136
|
-
const
|
|
137
|
-
if (
|
|
138
|
-
const token: string = authHeader.split(' ')[1] // Extract the token from the Bearer string
|
|
232
|
+
const token = extractBearerToken(req.headers.authorization)
|
|
233
|
+
if (token) {
|
|
139
234
|
try {
|
|
140
|
-
const
|
|
141
|
-
logger.debug('token verified for ',
|
|
142
|
-
req.user =
|
|
235
|
+
const verifiedToken = await verifyToken(token, authConfig)
|
|
236
|
+
logger.debug('token verified for ', verifiedToken.payload)
|
|
237
|
+
req.user = verifiedToken.payload
|
|
238
|
+
const authorizedRequest = req as AuthorizedRequest
|
|
239
|
+
authorizedRequest[authorizedAudience] = verifiedToken.audience
|
|
143
240
|
} catch (err: Error | any) {
|
|
144
241
|
failed = true
|
|
145
242
|
logger.error(err)
|
|
146
|
-
|
|
243
|
+
// Emit only the human-readable description rather than the
|
|
244
|
+
// ExceptionHelper-formatted "[code] - description - helplink" string,
|
|
245
|
+
// so the wire format does not leak internal error codes or help links.
|
|
246
|
+
// Fall back to a stable string so a thrown non-Error (no description/
|
|
247
|
+
// message) never serializes to an empty `{}` and drops the detail.
|
|
248
|
+
const wireMessage: string = err?.description ?? err?.message ?? 'unauthorized'
|
|
249
|
+
res.status(401).send({ 'jwt-auth-error': wireMessage })
|
|
147
250
|
}
|
|
251
|
+
} else if (hasAuthorizationHeader(req.headers.authorization)) {
|
|
252
|
+
// Header is present but not a well-formed `Bearer <token>` (e.g. wrong
|
|
253
|
+
// scheme, missing token, or an array value). Respond with a consistent
|
|
254
|
+
// 401 rather than letting malformed input throw before authorization.
|
|
255
|
+
failed = true
|
|
256
|
+
logger.warn('malformed authorization header')
|
|
257
|
+
res.status(401).send({ 'jwt-auth-error': 'invalid authorization header' })
|
|
148
258
|
} else {
|
|
149
259
|
if (!authConfig.clientId && process.env.NODE_ENV !== 'production') {
|
|
150
260
|
logger.info('using anonymous auth')
|
|
@@ -284,7 +284,7 @@ export class MsalTokenProvider implements AuthProvider {
|
|
|
284
284
|
if (typeof authConfigOrScope === 'string') {
|
|
285
285
|
// Called as getAccessToken(scope)
|
|
286
286
|
if (!this.connectionSettings) {
|
|
287
|
-
throw
|
|
287
|
+
throw ExceptionHelper.generateException(Error, Errors.ConnectionSettingsRequiredForGetAccessTokenScope)
|
|
288
288
|
}
|
|
289
289
|
authConfig = this.connectionSettings
|
|
290
290
|
actualScope = authConfigOrScope
|
|
@@ -378,7 +378,7 @@ export class MsalTokenProvider implements AuthProvider {
|
|
|
378
378
|
if (Array.isArray(authConfigOrScopes)) {
|
|
379
379
|
// Called as acquireTokenOnBehalfOf(scopes, oboAssertion)
|
|
380
380
|
if (!this.connectionSettings) {
|
|
381
|
-
throw
|
|
381
|
+
throw ExceptionHelper.generateException(Error, Errors.ConnectionSettingsRequiredForAcquireTokenOnBehalfOf)
|
|
382
382
|
}
|
|
383
383
|
authConfig = this.connectionSettings
|
|
384
384
|
actualScopes = authConfigOrScopes
|
|
@@ -399,7 +399,7 @@ export class MsalTokenProvider implements AuthProvider {
|
|
|
399
399
|
scopes: actualScopes
|
|
400
400
|
})
|
|
401
401
|
if (!token?.accessToken) {
|
|
402
|
-
throw
|
|
402
|
+
throw ExceptionHelper.generateException(Error, Errors.FailedToAcquireTokenOnBehalfOf)
|
|
403
403
|
}
|
|
404
404
|
|
|
405
405
|
return token.accessToken
|
|
@@ -412,7 +412,7 @@ export class MsalTokenProvider implements AuthProvider {
|
|
|
412
412
|
record({ agenticInstanceId: agentAppInstanceId })
|
|
413
413
|
|
|
414
414
|
if (!this.connectionSettings) {
|
|
415
|
-
throw
|
|
415
|
+
throw ExceptionHelper.generateException(Error, Errors.ConnectionSettingsRequiredForGetAgenticInstanceToken)
|
|
416
416
|
}
|
|
417
417
|
|
|
418
418
|
const instanceTokenCacheKey = MsalTokenProvider.cacheKey(
|
|
@@ -444,7 +444,7 @@ export class MsalTokenProvider implements AuthProvider {
|
|
|
444
444
|
})
|
|
445
445
|
|
|
446
446
|
if (!token?.accessToken) {
|
|
447
|
-
throw
|
|
447
|
+
throw ExceptionHelper.generateException(Error, Errors.FailedToAcquireInstanceTokenForAgentInstance, undefined, { agentAppInstanceId })
|
|
448
448
|
}
|
|
449
449
|
|
|
450
450
|
this.cacheAgenticAuthenticationResult(instanceTokenCacheKey, token)
|
|
@@ -494,7 +494,7 @@ export class MsalTokenProvider implements AuthProvider {
|
|
|
494
494
|
*/
|
|
495
495
|
private async acquireTokenForAgenticScenarios (tenantId: string, clientId: string, clientAssertion: string | undefined, scopes: string[], tokenBodyParameters: { [key: string]: any }): Promise<string | null> {
|
|
496
496
|
if (!this.connectionSettings) {
|
|
497
|
-
throw
|
|
497
|
+
throw ExceptionHelper.generateException(Error, Errors.ConnectionSettingsRequiredForGetAgenticInstanceToken)
|
|
498
498
|
}
|
|
499
499
|
|
|
500
500
|
logger.debug('acquireTokenForAgenticScenarios clientId=%s tenantId=%s scopes=%o grant_type=%s', clientId, tenantId, scopes, tokenBodyParameters.grant_type)
|
|
@@ -585,7 +585,7 @@ export class MsalTokenProvider implements AuthProvider {
|
|
|
585
585
|
})
|
|
586
586
|
|
|
587
587
|
if (!token) {
|
|
588
|
-
throw
|
|
588
|
+
throw ExceptionHelper.generateException(Error, Errors.FailedToAcquireInstanceTokenForUserToken, undefined, { agentAppInstanceId })
|
|
589
589
|
}
|
|
590
590
|
|
|
591
591
|
return token
|
|
@@ -594,7 +594,7 @@ export class MsalTokenProvider implements AuthProvider {
|
|
|
594
594
|
|
|
595
595
|
public async getAgenticApplicationToken (tenantId: string, agentAppInstanceId: string): Promise<string> {
|
|
596
596
|
if (!this.connectionSettings?.clientId) {
|
|
597
|
-
throw
|
|
597
|
+
throw ExceptionHelper.generateException(Error, Errors.ConnectionSettingsRequiredForGetAgenticApplicationToken)
|
|
598
598
|
}
|
|
599
599
|
logger.debug('getAgenticApplicationToken clientId=%s tenantId=%s agentAppInstanceId=%s', this.connectionSettings.clientId, tenantId, agentAppInstanceId)
|
|
600
600
|
|
|
@@ -605,7 +605,7 @@ export class MsalTokenProvider implements AuthProvider {
|
|
|
605
605
|
if (!this.connectionSettings.idpmResource) {
|
|
606
606
|
resource = 'api://AzureAdTokenExchange/.default'
|
|
607
607
|
} else if (!URL.canParse(this.connectionSettings.idpmResource)) {
|
|
608
|
-
throw
|
|
608
|
+
throw ExceptionHelper.generateException(Error, Errors.IdpmResourceAbsoluteUriRequired)
|
|
609
609
|
} else {
|
|
610
610
|
resource = this.connectionSettings.idpmResource
|
|
611
611
|
}
|
|
@@ -617,7 +617,7 @@ export class MsalTokenProvider implements AuthProvider {
|
|
|
617
617
|
})
|
|
618
618
|
const tokenResult = await msiApp.acquireToken({ resource })
|
|
619
619
|
if (!tokenResult?.accessToken) {
|
|
620
|
-
throw
|
|
620
|
+
throw ExceptionHelper.generateException(Error, Errors.FailedToAcquireTokenViaIdentityProxyManagerForAgentInstance, undefined, { agentAppInstanceId })
|
|
621
621
|
}
|
|
622
622
|
logger.debug('getAgenticApplicationToken via IdentityProxyManager clientId=%s resource=%s', this.connectionSettings.clientId, resource)
|
|
623
623
|
return tokenResult.accessToken
|
|
@@ -655,7 +655,7 @@ export class MsalTokenProvider implements AuthProvider {
|
|
|
655
655
|
})
|
|
656
656
|
|
|
657
657
|
if (!token) {
|
|
658
|
-
throw
|
|
658
|
+
throw ExceptionHelper.generateException(Error, Errors.FailedToAcquireInstanceTokenForAgentInstance, undefined, { agentAppInstanceId })
|
|
659
659
|
}
|
|
660
660
|
|
|
661
661
|
return token
|
|
@@ -780,7 +780,7 @@ export class MsalTokenProvider implements AuthProvider {
|
|
|
780
780
|
azureRegion: authConfig.azureRegion
|
|
781
781
|
})
|
|
782
782
|
if (!token?.accessToken) {
|
|
783
|
-
throw
|
|
783
|
+
throw ExceptionHelper.generateException(Error, Errors.FailedToAcquireTokenUsingCertificate)
|
|
784
784
|
}
|
|
785
785
|
return token.accessToken
|
|
786
786
|
}
|
|
@@ -799,7 +799,7 @@ export class MsalTokenProvider implements AuthProvider {
|
|
|
799
799
|
azureRegion: authConfig.azureRegion
|
|
800
800
|
})
|
|
801
801
|
if (!token?.accessToken) {
|
|
802
|
-
throw
|
|
802
|
+
throw ExceptionHelper.generateException(Error, Errors.FailedToAcquireTokenUsingClientSecret)
|
|
803
803
|
}
|
|
804
804
|
return token.accessToken
|
|
805
805
|
}
|
|
@@ -824,7 +824,7 @@ export class MsalTokenProvider implements AuthProvider {
|
|
|
824
824
|
const token = await cca.acquireTokenByClientCredential({ scopes, azureRegion: authConfig.azureRegion })
|
|
825
825
|
logger.debug('got token using FIC client assertion')
|
|
826
826
|
if (!token?.accessToken) {
|
|
827
|
-
throw
|
|
827
|
+
throw ExceptionHelper.generateException(Error, Errors.FailedToAcquireTokenUsingFICClientAssertion)
|
|
828
828
|
}
|
|
829
829
|
return token.accessToken
|
|
830
830
|
}
|
|
@@ -850,7 +850,7 @@ export class MsalTokenProvider implements AuthProvider {
|
|
|
850
850
|
const token = await cca.acquireTokenByClientCredential({ scopes, azureRegion: authConfig.azureRegion })
|
|
851
851
|
logger.debug('got token using WID client assertion')
|
|
852
852
|
if (!token?.accessToken) {
|
|
853
|
-
throw
|
|
853
|
+
throw ExceptionHelper.generateException(Error, Errors.FailedToAcquireTokenUsingWIDClientAssertion)
|
|
854
854
|
}
|
|
855
855
|
return token.accessToken
|
|
856
856
|
}
|
|
@@ -874,7 +874,7 @@ export class MsalTokenProvider implements AuthProvider {
|
|
|
874
874
|
})
|
|
875
875
|
logger.debug('got token for FIC')
|
|
876
876
|
if (!response?.accessToken) {
|
|
877
|
-
throw
|
|
877
|
+
throw ExceptionHelper.generateException(Error, Errors.FailedToAcquireExternalTokenForFICClientAssertion)
|
|
878
878
|
}
|
|
879
879
|
return response.accessToken
|
|
880
880
|
}
|
package/src/baseAdapter.ts
CHANGED
|
@@ -6,11 +6,12 @@
|
|
|
6
6
|
import { Middleware, MiddlewareHandler, MiddlewareSet } from './middlewareSet'
|
|
7
7
|
import { TurnContext } from './turnContext'
|
|
8
8
|
import { debug } from '@microsoft/agents-telemetry'
|
|
9
|
-
import { Activity, ConversationReference } from '@microsoft/agents-activity'
|
|
9
|
+
import { Activity, ConversationReference, ExceptionHelper } from '@microsoft/agents-activity'
|
|
10
10
|
import { ResourceResponse } from './connector-client/resourceResponse'
|
|
11
11
|
import { AttachmentData } from './connector-client/attachmentData'
|
|
12
12
|
import { AttachmentInfo } from './connector-client/attachmentInfo'
|
|
13
13
|
import { JwtPayload } from 'jsonwebtoken'
|
|
14
|
+
import { Errors } from './errorHelper'
|
|
14
15
|
|
|
15
16
|
const logger = debug('agents:base-adapter')
|
|
16
17
|
|
|
@@ -202,7 +203,7 @@ export abstract class BaseAdapter {
|
|
|
202
203
|
if (err instanceof Error) {
|
|
203
204
|
await this.onTurnError(pContext.proxy, err)
|
|
204
205
|
} else {
|
|
205
|
-
throw
|
|
206
|
+
throw ExceptionHelper.generateException(Error, Errors.UnknownErrorType, undefined, { errorMessage: err.message })
|
|
206
207
|
}
|
|
207
208
|
} else {
|
|
208
209
|
throw err
|
package/src/cloudAdapter.ts
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
import { AgentHandler, INVOKE_RESPONSE_KEY } from './activityHandler'
|
|
7
7
|
import { BaseAdapter } from './baseAdapter'
|
|
8
8
|
import { TurnContext } from './turnContext'
|
|
9
|
-
import { Response } from 'express'
|
|
10
9
|
import { Request } from './auth/request'
|
|
10
|
+
import { NextFunction, WebResponse } from './interfaces/webResponse'
|
|
11
11
|
import { ConnectorClient } from './connector-client/connectorClient'
|
|
12
12
|
import { AuthConfiguration, getAuthConfigWithDefaults } from './auth/authConfiguration'
|
|
13
13
|
import { AuthProvider } from './auth/authProvider'
|
|
@@ -32,6 +32,7 @@ import { parseBooleanEnv, suggestClosest } from './utils/env'
|
|
|
32
32
|
import { trace } from '@microsoft/agents-telemetry'
|
|
33
33
|
import { AdapterTraceDefinitions } from './observability'
|
|
34
34
|
import { applyAgenticHeaders } from './getProductInfo'
|
|
35
|
+
import { authorizeJWT } from './auth/jwt-middleware'
|
|
35
36
|
|
|
36
37
|
const logger = debug('agents:cloud-adapter')
|
|
37
38
|
|
|
@@ -238,6 +239,7 @@ function truncateActivityForLog (activity: unknown, max = 1024): string {
|
|
|
238
239
|
|
|
239
240
|
export class CloudAdapter extends BaseAdapter {
|
|
240
241
|
protected readonly authConfig: AuthConfiguration
|
|
242
|
+
private readonly jwtMiddleware: ReturnType<typeof authorizeJWT>
|
|
241
243
|
protected _agentName?: string
|
|
242
244
|
|
|
243
245
|
/**
|
|
@@ -257,6 +259,7 @@ export class CloudAdapter extends BaseAdapter {
|
|
|
257
259
|
constructor (authConfig?: AuthConfiguration, authProvider?: AuthProvider, userTokenClient?: UserTokenClient, options?: CloudAdapterOptions) {
|
|
258
260
|
super()
|
|
259
261
|
this.authConfig = authConfig = getAuthConfigWithDefaults(authConfig)
|
|
262
|
+
this.jwtMiddleware = authorizeJWT(authConfig)
|
|
260
263
|
this.connectionManager = new MsalConnectionManager(undefined, undefined, authConfig)
|
|
261
264
|
this._options = resolveCloudAdapterOptions(options)
|
|
262
265
|
|
|
@@ -289,7 +292,7 @@ export class CloudAdapter extends BaseAdapter {
|
|
|
289
292
|
*/
|
|
290
293
|
protected resolveIfConnectorClientIsNeeded (activity: Activity): boolean {
|
|
291
294
|
if (!activity) {
|
|
292
|
-
throw
|
|
295
|
+
throw ExceptionHelper.generateException(TypeError, Errors.ActivityParameterRequired)
|
|
293
296
|
}
|
|
294
297
|
|
|
295
298
|
switch (activity.deliveryMode) {
|
|
@@ -392,7 +395,7 @@ export class CloudAdapter extends BaseAdapter {
|
|
|
392
395
|
headers
|
|
393
396
|
)
|
|
394
397
|
} else {
|
|
395
|
-
throw
|
|
398
|
+
throw ExceptionHelper.generateException(Error, Errors.CannotCreateConnectorClientForAgenticUser)
|
|
396
399
|
}
|
|
397
400
|
} else {
|
|
398
401
|
// ABS tokens will not have an azp/appid so use the botframework scope.
|
|
@@ -425,6 +428,24 @@ export class CloudAdapter extends BaseAdapter {
|
|
|
425
428
|
} as JwtPayload
|
|
426
429
|
}
|
|
427
430
|
|
|
431
|
+
/**
|
|
432
|
+
* Authorizes an incoming web request using this adapter's resolved authentication configuration.
|
|
433
|
+
* @param req The incoming request.
|
|
434
|
+
* @param res The outgoing response.
|
|
435
|
+
* @param next Callback invoked after successful authorization.
|
|
436
|
+
*/
|
|
437
|
+
public async authorizeRequest (req: Request, res: WebResponse, next: NextFunction): Promise<void> {
|
|
438
|
+
await this.jwtMiddleware(req, res, next)
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Gets the client ID used by this adapter's default authentication connection.
|
|
443
|
+
* @returns The configured client ID, or `undefined` for anonymous development configuration.
|
|
444
|
+
*/
|
|
445
|
+
public getClientId (): string | undefined {
|
|
446
|
+
return this.authConfig.clientId
|
|
447
|
+
}
|
|
448
|
+
|
|
428
449
|
/**
|
|
429
450
|
* Sets the agent name for M365 agent header propagation.
|
|
430
451
|
* @param agentName The human-friendly agent name to set for header propagation.
|
|
@@ -580,7 +601,7 @@ export class CloudAdapter extends BaseAdapter {
|
|
|
580
601
|
*/
|
|
581
602
|
public async process (
|
|
582
603
|
request: Request,
|
|
583
|
-
res:
|
|
604
|
+
res: WebResponse,
|
|
584
605
|
logic: (context: TurnContext) => Promise<void>,
|
|
585
606
|
headerPropagation?: HeaderPropagationDefinition): Promise<void> {
|
|
586
607
|
return trace(AdapterTraceDefinitions.process, async ({ record }) => {
|
|
@@ -609,7 +630,7 @@ export class CloudAdapter extends BaseAdapter {
|
|
|
609
630
|
res.end()
|
|
610
631
|
}
|
|
611
632
|
if (!request.body) {
|
|
612
|
-
throw
|
|
633
|
+
throw ExceptionHelper.generateException(TypeError, Errors.MissingRequestBody)
|
|
613
634
|
}
|
|
614
635
|
const incoming = normalizeIncomingActivity(request.body!)
|
|
615
636
|
const activity = Activity.fromObject(incoming)
|
|
@@ -726,11 +747,11 @@ export class CloudAdapter extends BaseAdapter {
|
|
|
726
747
|
async updateActivity (context: TurnContext, activity: Activity): Promise<ResourceResponse | void> {
|
|
727
748
|
return trace(AdapterTraceDefinitions.updateActivity, async ({ record }) => {
|
|
728
749
|
if (!context) {
|
|
729
|
-
throw
|
|
750
|
+
throw ExceptionHelper.generateException(TypeError, Errors.ContextParameterRequired)
|
|
730
751
|
}
|
|
731
752
|
|
|
732
753
|
if (!activity) {
|
|
733
|
-
throw
|
|
754
|
+
throw ExceptionHelper.generateException(TypeError, Errors.ActivityParameterRequired)
|
|
734
755
|
}
|
|
735
756
|
|
|
736
757
|
record({ activity })
|
|
@@ -758,7 +779,7 @@ export class CloudAdapter extends BaseAdapter {
|
|
|
758
779
|
async deleteActivity (context: TurnContext, reference: Partial<ConversationReference>): Promise<void> {
|
|
759
780
|
return trace(AdapterTraceDefinitions.deleteActivity, async ({ record }) => {
|
|
760
781
|
if (!context) {
|
|
761
|
-
throw
|
|
782
|
+
throw ExceptionHelper.generateException(TypeError, Errors.ContextParameterRequired)
|
|
762
783
|
}
|
|
763
784
|
|
|
764
785
|
if (!reference || !reference.serviceUrl || (reference.conversation == null) || !reference.conversation.id || !reference.activityId) {
|
|
@@ -791,7 +812,7 @@ export class CloudAdapter extends BaseAdapter {
|
|
|
791
812
|
}
|
|
792
813
|
|
|
793
814
|
if (!botAppIdOrIdentity) {
|
|
794
|
-
throw
|
|
815
|
+
throw ExceptionHelper.generateException(TypeError, Errors.ContinueConversationBotAppIdOrIdentityRequired)
|
|
795
816
|
}
|
|
796
817
|
const botAppId = typeof botAppIdOrIdentity === 'string' ? botAppIdOrIdentity : botAppIdOrIdentity.aud as string
|
|
797
818
|
|
|
@@ -911,10 +932,10 @@ export class CloudAdapter extends BaseAdapter {
|
|
|
911
932
|
logic: (context: TurnContext) => Promise<void>
|
|
912
933
|
): Promise<void> {
|
|
913
934
|
if (typeof serviceUrl !== 'string' || !serviceUrl) {
|
|
914
|
-
throw
|
|
935
|
+
throw ExceptionHelper.generateException(TypeError, Errors.ServiceUrlRequired)
|
|
915
936
|
}
|
|
916
|
-
if (!conversationParameters) throw
|
|
917
|
-
if (!logic) throw
|
|
937
|
+
if (!conversationParameters) throw ExceptionHelper.generateException(TypeError, Errors.ConversationParametersRequired)
|
|
938
|
+
if (!logic) throw ExceptionHelper.generateException(TypeError, Errors.LogicParameterRequired)
|
|
918
939
|
|
|
919
940
|
const identity = CloudAdapter.createIdentity(audience)
|
|
920
941
|
const restClient = await this.createConnectorClient(serviceUrl, audience, identity)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { ActivityHandler } from './activityHandler'
|
|
7
|
+
import { AgentApplication } from './app/agentApplication'
|
|
8
|
+
import { AuthConfiguration } from './auth/authConfiguration'
|
|
9
|
+
import { CloudAdapter } from './cloudAdapter'
|
|
10
|
+
import { HeaderPropagationDefinition } from './headerPropagation'
|
|
11
|
+
import { TurnState } from './app/turnState'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Result of creating a CloudAdapter from an agent.
|
|
15
|
+
*/
|
|
16
|
+
export interface CloudAdapterResult {
|
|
17
|
+
adapter: CloudAdapter
|
|
18
|
+
headerPropagation: HeaderPropagationDefinition | undefined
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Creates a CloudAdapter for the given agent.
|
|
23
|
+
*
|
|
24
|
+
* If the agent is an AgentApplication with a pre-configured adapter, that adapter is reused.
|
|
25
|
+
* Otherwise, a new CloudAdapter is created.
|
|
26
|
+
*
|
|
27
|
+
* @param agent - The AgentApplication or ActivityHandler instance.
|
|
28
|
+
* @param authConfig - Optional auth configuration used when creating a new CloudAdapter.
|
|
29
|
+
* If the agent already has an adapter, that adapter is reused and this value is ignored.
|
|
30
|
+
* @returns An object containing the CloudAdapter and optional header propagation configuration.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```typescript
|
|
34
|
+
* import { AgentApplication, TurnState, createCloudAdapter } from '@microsoft/agents-hosting';
|
|
35
|
+
*
|
|
36
|
+
* const app = new AgentApplication<TurnState>();
|
|
37
|
+
* const { adapter, headerPropagation } = createCloudAdapter(app, { clientId: process.env.CLIENT_ID });
|
|
38
|
+
*
|
|
39
|
+
* // Use the adapter directly with request/response objects compatible with CloudAdapter.process
|
|
40
|
+
* adapter.process(req, res, (context) => app.run(context), headerPropagation);
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export const createCloudAdapter = (
|
|
44
|
+
agent: AgentApplication<TurnState<any, any>> | ActivityHandler,
|
|
45
|
+
authConfig?: AuthConfiguration
|
|
46
|
+
): CloudAdapterResult => {
|
|
47
|
+
if (agent instanceof ActivityHandler) {
|
|
48
|
+
return { adapter: new CloudAdapter(authConfig), headerPropagation: undefined }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// AgentApplication: reuse its pre-configured adapter when present, otherwise create one.
|
|
52
|
+
// headerPropagation is an application-level option, so read it regardless of adapter source.
|
|
53
|
+
const headerPropagation = agent.options?.headerPropagation
|
|
54
|
+
const adapter = agent.adapter ?? new CloudAdapter(authConfig)
|
|
55
|
+
return { adapter, headerPropagation }
|
|
56
|
+
}
|