@microsoft/agents-hosting 1.1.0-alpha.9.g154c2c8a32 → 1.1.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.
Files changed (152) hide show
  1. package/dist/package.json +10 -6
  2. package/dist/src/activityWireCompat.d.ts +1 -1
  3. package/dist/src/activityWireCompat.js +11 -3
  4. package/dist/src/activityWireCompat.js.map +1 -1
  5. package/dist/src/agent-client/agentClient.js +7 -3
  6. package/dist/src/agent-client/agentClient.js.map +1 -1
  7. package/dist/src/agent-client/agentResponseHandler.js +6 -2
  8. package/dist/src/agent-client/agentResponseHandler.js.map +1 -1
  9. package/dist/src/app/agentApplication.d.ts +26 -11
  10. package/dist/src/app/agentApplication.js +90 -79
  11. package/dist/src/app/agentApplication.js.map +1 -1
  12. package/dist/src/app/agentApplicationBuilder.d.ts +2 -2
  13. package/dist/src/app/agentApplicationBuilder.js.map +1 -1
  14. package/dist/src/app/agentApplicationOptions.d.ts +9 -2
  15. package/dist/src/app/appRoute.d.ts +7 -0
  16. package/dist/src/app/{authorization.d.ts → auth/authorization.d.ts} +41 -139
  17. package/dist/src/app/auth/authorization.js +188 -0
  18. package/dist/src/app/auth/authorization.js.map +1 -0
  19. package/dist/src/app/auth/authorizationManager.d.ts +71 -0
  20. package/dist/src/app/auth/authorizationManager.js +170 -0
  21. package/dist/src/app/auth/authorizationManager.js.map +1 -0
  22. package/dist/src/app/auth/handlerStorage.d.ts +36 -0
  23. package/dist/src/app/auth/handlerStorage.js +62 -0
  24. package/dist/src/app/auth/handlerStorage.js.map +1 -0
  25. package/dist/src/app/auth/handlers/agenticAuthorization.d.ts +93 -0
  26. package/dist/src/app/auth/handlers/agenticAuthorization.js +134 -0
  27. package/dist/src/app/auth/handlers/agenticAuthorization.js.map +1 -0
  28. package/dist/src/app/auth/handlers/azureBotAuthorization.d.ts +226 -0
  29. package/dist/src/app/auth/handlers/azureBotAuthorization.js +429 -0
  30. package/dist/src/app/auth/handlers/azureBotAuthorization.js.map +1 -0
  31. package/dist/src/app/auth/handlers/index.d.ts +2 -0
  32. package/dist/src/app/auth/handlers/index.js +19 -0
  33. package/dist/src/app/auth/handlers/index.js.map +1 -0
  34. package/dist/src/app/auth/index.d.ts +2 -0
  35. package/dist/src/app/auth/index.js +19 -0
  36. package/dist/src/app/auth/index.js.map +1 -0
  37. package/dist/src/app/auth/types.d.ts +104 -0
  38. package/dist/src/app/auth/types.js +24 -0
  39. package/dist/src/app/auth/types.js.map +1 -0
  40. package/dist/src/app/index.d.ts +3 -3
  41. package/dist/src/app/index.js +2 -3
  42. package/dist/src/app/index.js.map +1 -1
  43. package/dist/src/app/routeList.d.ts +1 -1
  44. package/dist/src/app/routeList.js +22 -5
  45. package/dist/src/app/routeList.js.map +1 -1
  46. package/dist/src/app/streaming/streamingResponse.js +2 -1
  47. package/dist/src/app/streaming/streamingResponse.js.map +1 -1
  48. package/dist/src/auth/MemoryCache.d.ts +16 -0
  49. package/dist/src/auth/MemoryCache.js +58 -0
  50. package/dist/src/auth/MemoryCache.js.map +1 -0
  51. package/dist/src/auth/authConfiguration.d.ts +44 -2
  52. package/dist/src/auth/authConfiguration.js +209 -53
  53. package/dist/src/auth/authConfiguration.js.map +1 -1
  54. package/dist/src/auth/authConstants.d.ts +11 -0
  55. package/dist/src/auth/authConstants.js +15 -0
  56. package/dist/src/auth/authConstants.js.map +1 -0
  57. package/dist/src/auth/authProvider.d.ts +26 -0
  58. package/dist/src/auth/connections.d.ts +41 -0
  59. package/dist/src/auth/connections.js +7 -0
  60. package/dist/src/auth/connections.js.map +1 -0
  61. package/dist/src/auth/index.d.ts +2 -0
  62. package/dist/src/auth/index.js +2 -0
  63. package/dist/src/auth/index.js.map +1 -1
  64. package/dist/src/auth/jwt-middleware.js +31 -18
  65. package/dist/src/auth/jwt-middleware.js.map +1 -1
  66. package/dist/src/auth/msalConnectionManager.d.ts +64 -0
  67. package/dist/src/auth/msalConnectionManager.js +148 -0
  68. package/dist/src/auth/msalConnectionManager.js.map +1 -0
  69. package/dist/src/auth/msalTokenProvider.d.ts +38 -0
  70. package/dist/src/auth/msalTokenProvider.js +186 -16
  71. package/dist/src/auth/msalTokenProvider.js.map +1 -1
  72. package/dist/src/baseAdapter.d.ts +10 -25
  73. package/dist/src/baseAdapter.js +2 -15
  74. package/dist/src/baseAdapter.js.map +1 -1
  75. package/dist/src/cards/cardFactory.d.ts +2 -1
  76. package/dist/src/cards/cardFactory.js +3 -2
  77. package/dist/src/cards/cardFactory.js.map +1 -1
  78. package/dist/src/cloudAdapter.d.ts +40 -23
  79. package/dist/src/cloudAdapter.js +143 -63
  80. package/dist/src/cloudAdapter.js.map +1 -1
  81. package/dist/src/connector-client/connectorClient.d.ts +15 -0
  82. package/dist/src/connector-client/connectorClient.js +49 -15
  83. package/dist/src/connector-client/connectorClient.js.map +1 -1
  84. package/dist/src/index.d.ts +0 -1
  85. package/dist/src/index.js +0 -1
  86. package/dist/src/index.js.map +1 -1
  87. package/dist/src/oauth/customUserTokenAPI.d.ts +1 -0
  88. package/dist/src/oauth/customUserTokenAPI.js +11 -0
  89. package/dist/src/oauth/customUserTokenAPI.js.map +1 -0
  90. package/dist/src/oauth/index.d.ts +0 -1
  91. package/dist/src/oauth/index.js +0 -1
  92. package/dist/src/oauth/index.js.map +1 -1
  93. package/dist/src/oauth/userTokenClient.d.ts +30 -13
  94. package/dist/src/oauth/userTokenClient.js +62 -26
  95. package/dist/src/oauth/userTokenClient.js.map +1 -1
  96. package/dist/src/oauth/userTokenClient.types.d.ts +19 -6
  97. package/dist/src/transcript/fileTranscriptLogger.d.ts +109 -0
  98. package/dist/src/transcript/fileTranscriptLogger.js +398 -0
  99. package/dist/src/transcript/fileTranscriptLogger.js.map +1 -0
  100. package/dist/src/turnContext.d.ts +7 -1
  101. package/dist/src/turnContext.js +11 -4
  102. package/dist/src/turnContext.js.map +1 -1
  103. package/package.json +10 -6
  104. package/src/activityWireCompat.ts +12 -4
  105. package/src/agent-client/agentClient.ts +9 -3
  106. package/src/agent-client/agentResponseHandler.ts +5 -2
  107. package/src/app/agentApplication.ts +95 -74
  108. package/src/app/agentApplicationBuilder.ts +2 -2
  109. package/src/app/agentApplicationOptions.ts +10 -2
  110. package/src/app/appRoute.ts +8 -0
  111. package/src/app/auth/authorization.ts +261 -0
  112. package/src/app/auth/authorizationManager.ts +213 -0
  113. package/src/app/auth/handlerStorage.ts +61 -0
  114. package/src/app/auth/handlers/agenticAuthorization.ts +183 -0
  115. package/src/app/auth/handlers/azureBotAuthorization.ts +606 -0
  116. package/src/app/auth/handlers/index.ts +2 -0
  117. package/src/app/auth/index.ts +2 -0
  118. package/src/app/auth/types.ts +111 -0
  119. package/src/app/index.ts +3 -3
  120. package/src/app/routeList.ts +24 -5
  121. package/src/app/streaming/streamingResponse.ts +2 -1
  122. package/src/auth/MemoryCache.ts +59 -0
  123. package/src/auth/authConfiguration.ts +245 -52
  124. package/src/auth/authConstants.ts +11 -0
  125. package/src/auth/authProvider.ts +34 -0
  126. package/src/auth/connections.ts +47 -0
  127. package/src/auth/index.ts +2 -0
  128. package/src/auth/jwt-middleware.ts +38 -21
  129. package/src/auth/msalConnectionManager.ts +175 -0
  130. package/src/auth/msalTokenProvider.ts +228 -9
  131. package/src/baseAdapter.ts +10 -29
  132. package/src/cards/cardFactory.ts +3 -2
  133. package/src/cloudAdapter.ts +207 -72
  134. package/src/connector-client/connectorClient.ts +59 -15
  135. package/src/index.ts +0 -1
  136. package/src/oauth/customUserTokenAPI.ts +5 -0
  137. package/src/oauth/index.ts +0 -1
  138. package/src/oauth/userTokenClient.ts +76 -22
  139. package/src/oauth/userTokenClient.types.ts +20 -8
  140. package/src/transcript/fileTranscriptLogger.ts +409 -0
  141. package/src/turnContext.ts +16 -5
  142. package/dist/src/app/authorization.js +0 -387
  143. package/dist/src/app/authorization.js.map +0 -1
  144. package/dist/src/claimsIdentity.d.ts +0 -35
  145. package/dist/src/claimsIdentity.js +0 -43
  146. package/dist/src/claimsIdentity.js.map +0 -1
  147. package/dist/src/oauth/oAuthFlow.d.ts +0 -119
  148. package/dist/src/oauth/oAuthFlow.js +0 -316
  149. package/dist/src/oauth/oAuthFlow.js.map +0 -1
  150. package/src/app/authorization.ts +0 -432
  151. package/src/claimsIdentity.ts +0 -47
  152. package/src/oauth/oAuthFlow.ts +0 -378
package/src/auth/index.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  export * from './authConfiguration'
2
+ export * from './authConstants'
2
3
  export * from './authProvider'
3
4
  export * from './msalTokenProvider'
4
5
  export * from './request'
5
6
  export * from './msalTokenCredential'
7
+ export * from './msalConnectionManager'
@@ -19,18 +19,37 @@ const logger = debug('agents:jwt-middleware')
19
19
  * @returns A promise that resolves to the JWT payload.
20
20
  */
21
21
  const verifyToken = async (raw: string, config: AuthConfiguration): Promise<JwtPayload> => {
22
- const getKey: GetPublicKeyOrSecret = (header: JwtHeader, callback: SignCallback) => {
23
- const payload = jwt.decode(raw) as JwtPayload
24
- logger.debug('jwt.decode ', JSON.stringify(payload))
25
- const jwksUri: string = payload.iss === 'https://api.botframework.com'
26
- ? 'https://login.botframework.com/v1/.well-known/keys'
27
- : `${config.authority}/${config.tenantId}/discovery/v2.0/keys`
22
+ const payload = jwt.decode(raw) as JwtPayload
23
+ logger.debug('jwt.decode ', JSON.stringify(payload))
24
+
25
+ if (!payload) {
26
+ throw new Error('invalid token')
27
+ }
28
+ const audience = payload.aud
29
+
30
+ const matchingEntry = config.connections && config.connections.size > 0
31
+ ? [...config.connections.entries()].find(([_, configuration]) => configuration.clientId === audience)
32
+ : undefined
33
+
34
+ if (!matchingEntry) {
35
+ const err = new Error('Audience mismatch')
36
+ logger.error(err.message, audience)
37
+ throw err
38
+ }
39
+
40
+ const [key, authConfig] = matchingEntry
41
+ logger.debug(`Audience found at key: ${key}`)
28
42
 
29
- logger.debug(`fetching keys from ${jwksUri}`)
30
- const jwksClient: JwksClient = jwksRsa({ jwksUri })
43
+ const jwksUri = payload.iss === 'https://api.botframework.com'
44
+ ? 'https://login.botframework.com/v1/.well-known/keys'
45
+ : `${authConfig.authority}/${authConfig.tenantId}/discovery/v2.0/keys`
31
46
 
47
+ logger.debug(`fetching keys from ${jwksUri}`)
48
+ const jwksClient: JwksClient = jwksRsa({ jwksUri })
49
+
50
+ const getKey: GetPublicKeyOrSecret = (header: JwtHeader, callback: SignCallback) => {
32
51
  jwksClient.getSigningKey(header.kid, (err: Error | null, key: SigningKey | undefined): void => {
33
- if (err != null) {
52
+ if (err) {
34
53
  logger.error('jwksClient.getSigningKey ', JSON.stringify(err))
35
54
  logger.error(JSON.stringify(err))
36
55
  callback(err, undefined)
@@ -41,24 +60,22 @@ const verifyToken = async (raw: string, config: AuthConfiguration): Promise<JwtP
41
60
  })
42
61
  }
43
62
 
44
- return await new Promise((resolve, reject) => {
45
- const verifyOptions: jwt.VerifyOptions = {
46
- issuer: config.issuers as [string, ...string[]],
47
- audience: [config.clientId!, 'https://api.botframework.com'],
48
- ignoreExpiration: false,
49
- algorithms: ['RS256'],
50
- clockTolerance: 300
51
- }
63
+ const verifyOptions: jwt.VerifyOptions = {
64
+ issuer: authConfig.issuers as [string, ...string[]],
65
+ audience: [authConfig.clientId!, 'https://api.botframework.com'],
66
+ ignoreExpiration: false,
67
+ algorithms: ['RS256'],
68
+ clockTolerance: 300
69
+ }
52
70
 
71
+ return await new Promise((resolve, reject) => {
53
72
  jwt.verify(raw, getKey, verifyOptions, (err, user) => {
54
- if (err != null) {
73
+ if (err) {
55
74
  logger.error('jwt.verify ', JSON.stringify(err))
56
75
  reject(err)
57
76
  return
58
77
  }
59
- const tokenClaims = user as JwtPayload
60
-
61
- resolve(tokenClaims)
78
+ resolve(user as JwtPayload)
62
79
  })
63
80
  })
64
81
  }
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { Activity, RoleTypes } from '@microsoft/agents-activity'
7
+ import { AuthConfiguration } from './authConfiguration'
8
+ import { Connections } from './connections'
9
+ import { MsalTokenProvider } from './msalTokenProvider'
10
+ import { JwtPayload } from 'jsonwebtoken'
11
+
12
+ export interface ConnectionMapItem {
13
+ audience?: string
14
+ serviceUrl: string
15
+ connection: string
16
+ }
17
+
18
+ export class MsalConnectionManager implements Connections {
19
+ private _connections: Map<string, MsalTokenProvider>
20
+ private _connectionsMap: ConnectionMapItem[]
21
+ private _serviceConnectionConfiguration: AuthConfiguration
22
+ private static readonly DEFAULT_CONNECTION = 'serviceConnection'
23
+
24
+ constructor (
25
+ connectionsConfigurations: Map<string, AuthConfiguration> = new Map(),
26
+ connectionsMap: ConnectionMapItem[] = [],
27
+ configuration: AuthConfiguration = {}) {
28
+ this._connections = new Map()
29
+ this._connectionsMap = connectionsMap.length > 0 ? connectionsMap : (configuration.connectionsMap || [])
30
+ this._serviceConnectionConfiguration = {}
31
+
32
+ const providedConnections = connectionsConfigurations.size > 0 ? connectionsConfigurations : (configuration.connections || new Map())
33
+
34
+ for (const [name, config] of providedConnections) {
35
+ // Instantiate MsalTokenProvider for each connection
36
+ this._connections.set(name, new MsalTokenProvider(config))
37
+ if (name === MsalConnectionManager.DEFAULT_CONNECTION) {
38
+ this._serviceConnectionConfiguration = config
39
+ }
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Get the OAuth connection for the agent.
45
+ * @param connectionName The name of the connection.
46
+ * @returns The OAuth connection for the agent.
47
+ */
48
+ getConnection (connectionName: string): MsalTokenProvider {
49
+ const conn = this._connections.get(connectionName)
50
+ if (!conn) {
51
+ throw new Error(`Connection not found: ${connectionName}`)
52
+ }
53
+ return this.applyConnectionDefaults(conn)
54
+ }
55
+
56
+ /**
57
+ * Get the default OAuth connection for the agent.
58
+ * @returns The default OAuth connection for the agent.
59
+ */
60
+ getDefaultConnection (): MsalTokenProvider {
61
+ if (this._connections.size === 0) {
62
+ throw new Error('No connections found for this Agent in the Connections Configuration.')
63
+ }
64
+
65
+ // Return the wildcard map item instance.
66
+ for (const item of this._connectionsMap) {
67
+ if (item.serviceUrl === '*' && !item.audience) {
68
+ return this.getConnection(item.connection)
69
+ }
70
+ }
71
+
72
+ const conn = this._connections.values().next().value as MsalTokenProvider
73
+
74
+ return this.applyConnectionDefaults(conn)
75
+ }
76
+
77
+ /**
78
+ * Finds a connection based on a map.
79
+ *
80
+ * @param identity - The identity. Usually TurnContext.identity.
81
+ * @param serviceUrl The service URL.
82
+ * @returns The TokenProvider for the connection.
83
+ *
84
+ * @remarks
85
+ * Example environment variables:
86
+ * connectionsMap__0__connection=seviceConnection
87
+ * connectionsMap__0__serviceUrl=http://*..botframework.com/*
88
+ * connectionsMap__0__audience=optional
89
+ * connectionsMap__1__connection=agentic
90
+ * connectionsMap__1__serviceUrl=agentic
91
+ *
92
+ * ServiceUrl is: A regex to match with, or "*" for any serviceUrl value.
93
+ * Connection is: A name in the 'Connections' list.
94
+ */
95
+ getTokenProvider (identity: JwtPayload, serviceUrl: string): MsalTokenProvider {
96
+ if (!identity) {
97
+ throw new Error('Identity is required to get the token provider.')
98
+ }
99
+
100
+ let audience
101
+ if (Array.isArray(identity?.aud)) {
102
+ audience = identity.aud[0]
103
+ } else {
104
+ audience = identity.aud
105
+ }
106
+
107
+ if (!audience || !serviceUrl) throw new Error('Audience and Service URL are required to get the token provider.')
108
+
109
+ if (this._connectionsMap.length === 0) {
110
+ return this.getDefaultConnection()
111
+ }
112
+
113
+ for (const item of this._connectionsMap) {
114
+ let audienceMatch = true
115
+
116
+ // if we have an audience to match against, match it.
117
+ if (item.audience && audience) {
118
+ audienceMatch = item.audience === audience
119
+ }
120
+
121
+ if (audienceMatch) {
122
+ if (item.serviceUrl === '*' || !item.serviceUrl) {
123
+ return this.getConnection(item.connection)
124
+ }
125
+
126
+ const regex = new RegExp(item.serviceUrl, 'i')
127
+ if (regex.test(serviceUrl)) {
128
+ return this.getConnection(item.connection)
129
+ }
130
+ }
131
+ }
132
+ throw new Error(`No connection found for audience: ${audience} and serviceUrl: ${serviceUrl}`)
133
+ }
134
+
135
+ /**
136
+ * Finds a connection based on an activity's blueprint.
137
+ * @param identity - The identity. Usually TurnContext.identity.
138
+ * @param activity The activity.
139
+ * @returns The TokenProvider for the connection.
140
+ */
141
+ getTokenProviderFromActivity (identity: JwtPayload, activity: Activity): MsalTokenProvider {
142
+ let connection = this.getTokenProvider(identity, activity.serviceUrl || '')
143
+
144
+ // This is for the case where the Agentic BlueprintId is not the same as the AppId
145
+ if (connection &&
146
+ (activity.recipient?.role === RoleTypes.AgenticIdentity ||
147
+ activity.recipient?.role === RoleTypes.AgenticUser)) {
148
+ if (connection.connectionSettings?.altBlueprintConnectionName &&
149
+ connection.connectionSettings.altBlueprintConnectionName.trim() !== '') {
150
+ connection = this.getConnection(connection.connectionSettings?.altBlueprintConnectionName as string)
151
+ }
152
+ }
153
+ return connection
154
+ }
155
+
156
+ /**
157
+ * Get the default connection configuration for the agent.
158
+ * @returns The default connection configuration for the agent.
159
+ */
160
+ getDefaultConnectionConfiguration (): AuthConfiguration {
161
+ return this._serviceConnectionConfiguration
162
+ }
163
+
164
+ private applyConnectionDefaults (conn: MsalTokenProvider): MsalTokenProvider {
165
+ if (conn.connectionSettings) {
166
+ conn.connectionSettings.authority ??= 'https://login.microsoftonline.com'
167
+ conn.connectionSettings.issuers ??= [
168
+ 'https://api.botframework.com',
169
+ `https://sts.windows.net/${conn.connectionSettings.tenantId}/`,
170
+ `${conn.connectionSettings.authority}/${conn.connectionSettings.tenantId}/v2.0`
171
+ ]
172
+ }
173
+ return conn
174
+ }
175
+ }
@@ -4,10 +4,12 @@
4
4
  */
5
5
 
6
6
  import { ConfidentialClientApplication, LogLevel, ManagedIdentityApplication, NodeSystemOptions } from '@azure/msal-node'
7
+ import axios from 'axios'
7
8
  import { AuthConfiguration } from './authConfiguration'
8
9
  import { AuthProvider } from './authProvider'
9
10
  import { debug } from '@microsoft/agents-activity/logger'
10
11
  import { v4 } from 'uuid'
12
+ import { MemoryCache } from './MemoryCache'
11
13
 
12
14
  import fs from 'fs'
13
15
  import crypto from 'crypto'
@@ -19,28 +21,62 @@ const logger = debug('agents:msal')
19
21
  * Provides tokens using MSAL.
20
22
  */
21
23
  export class MsalTokenProvider implements AuthProvider {
24
+ private readonly _agenticTokenCache: MemoryCache<string>
25
+ public readonly connectionSettings?: AuthConfiguration
26
+
27
+ constructor (connectionSettings?: AuthConfiguration) {
28
+ this._agenticTokenCache = new MemoryCache<string>()
29
+ this.connectionSettings = connectionSettings
30
+ }
31
+
32
+ /**
33
+ * Gets an access token using the auth configuration from the MsalTokenProvider instance and the provided scope.
34
+ * @param scope The scope for the token.
35
+ * @returns A promise that resolves to the access token.
36
+ */
37
+ public async getAccessToken (scope: string): Promise<string>
22
38
  /**
23
39
  * Gets an access token.
24
40
  * @param authConfig The authentication configuration.
25
41
  * @param scope The scope for the token.
26
42
  * @returns A promise that resolves to the access token.
27
43
  */
28
- public async getAccessToken (authConfig: AuthConfiguration, scope: string): Promise<string> {
44
+ public async getAccessToken (authConfig: AuthConfiguration, scope: string): Promise<string>
45
+
46
+ public async getAccessToken (authConfigOrScope: AuthConfiguration | string, scope?: string): Promise<string> {
47
+ let authConfig: AuthConfiguration
48
+ let actualScope: string
49
+
50
+ if (typeof authConfigOrScope === 'string') {
51
+ // Called as getAccessToken(scope)
52
+ if (!this.connectionSettings) {
53
+ throw new Error('Connection settings must be provided to constructor when calling getAccessToken(scope)')
54
+ }
55
+ authConfig = this.connectionSettings
56
+ actualScope = authConfigOrScope
57
+ } else {
58
+ // Called as getAccessToken(authConfig, scope)
59
+ authConfig = authConfigOrScope
60
+ actualScope = scope as string
61
+ }
62
+
29
63
  if (!authConfig.clientId && process.env.NODE_ENV !== 'production') {
30
64
  return ''
31
65
  }
32
66
  let token
33
- if (authConfig.FICClientId !== undefined) {
34
- token = await this.acquireAccessTokenViaFIC(authConfig, scope)
67
+ if (authConfig.WIDAssertionFile !== undefined) {
68
+ token = await this.acquireAccessTokenViaWID(authConfig, actualScope)
69
+ } else if (authConfig.FICClientId !== undefined) {
70
+ token = await this.acquireAccessTokenViaFIC(authConfig, actualScope)
35
71
  } else if (authConfig.clientSecret !== undefined) {
36
- token = await this.acquireAccessTokenViaSecret(authConfig, scope)
72
+ token = await this.acquireAccessTokenViaSecret(authConfig, actualScope)
37
73
  } else if (authConfig.certPemFile !== undefined &&
38
74
  authConfig.certKeyFile !== undefined) {
39
- token = await this.acquireTokenWithCertificate(authConfig, scope)
75
+ token = await this.acquireTokenWithCertificate(authConfig, actualScope)
40
76
  } else if (authConfig.clientSecret === undefined &&
41
77
  authConfig.certPemFile === undefined &&
42
78
  authConfig.certKeyFile === undefined) {
43
- token = await this.acquireTokenWithUserAssignedIdentity(authConfig, scope)
79
+ token = await this.acquireTokenWithUserAssignedIdentity(authConfig, actualScope)
44
80
  } else {
45
81
  throw new Error('Invalid authConfig. ')
46
82
  }
@@ -51,7 +87,33 @@ export class MsalTokenProvider implements AuthProvider {
51
87
  return token
52
88
  }
53
89
 
54
- public async acquireTokenOnBehalfOf (authConfig: AuthConfiguration, scopes: string[], oboAssertion: string): Promise<string> {
90
+ public async acquireTokenOnBehalfOf (scopes: string[], oboAssertion: string): Promise<string>
91
+ public async acquireTokenOnBehalfOf (authConfig: AuthConfiguration, scopes: string[], oboAssertion: string): Promise<string>
92
+
93
+ public async acquireTokenOnBehalfOf (
94
+ authConfigOrScopes: AuthConfiguration | string[],
95
+ scopesOrOboAssertion?: string[] | string,
96
+ oboAssertion?: string
97
+ ): Promise<string> {
98
+ let authConfig: AuthConfiguration
99
+ let actualScopes: string[]
100
+ let actualOboAssertion: string
101
+
102
+ if (Array.isArray(authConfigOrScopes)) {
103
+ // Called as acquireTokenOnBehalfOf(scopes, oboAssertion)
104
+ if (!this.connectionSettings) {
105
+ throw new Error('Connection settings must be provided to constructor when calling acquireTokenOnBehalfOf(scopes, oboAssertion)')
106
+ }
107
+ authConfig = this.connectionSettings
108
+ actualScopes = authConfigOrScopes
109
+ actualOboAssertion = scopesOrOboAssertion as string
110
+ } else {
111
+ // Called as acquireTokenOnBehalfOf(authConfig, scopes, oboAssertion)
112
+ authConfig = authConfigOrScopes
113
+ actualScopes = scopesOrOboAssertion as string[]
114
+ actualOboAssertion = oboAssertion!
115
+ }
116
+
55
117
  const cca = new ConfidentialClientApplication({
56
118
  auth: {
57
119
  clientId: authConfig.clientId as string,
@@ -61,12 +123,147 @@ export class MsalTokenProvider implements AuthProvider {
61
123
  system: this.sysOptions
62
124
  })
63
125
  const token = await cca.acquireTokenOnBehalfOf({
64
- oboAssertion,
65
- scopes
126
+ oboAssertion: actualOboAssertion,
127
+ scopes: actualScopes
66
128
  })
67
129
  return token?.accessToken as string
68
130
  }
69
131
 
132
+ public async getAgenticInstanceToken (tenantId: string, agentAppInstanceId: string): Promise<string> {
133
+ logger.debug('Getting agentic instance token')
134
+ if (!this.connectionSettings) {
135
+ throw new Error('Connection settings must be provided when calling getAgenticInstanceToken')
136
+ }
137
+ const appToken = await this.getAgenticApplicationToken(tenantId, agentAppInstanceId)
138
+ const cca = new ConfidentialClientApplication({
139
+ auth: {
140
+ clientId: agentAppInstanceId,
141
+ clientAssertion: appToken,
142
+ authority: this.resolveAuthority(tenantId),
143
+ },
144
+ system: this.sysOptions
145
+ })
146
+
147
+ const token = await cca.acquireTokenByClientCredential({
148
+ scopes: ['api://AzureAdTokenExchange/.default'],
149
+ correlationId: v4()
150
+ })
151
+
152
+ if (!token?.accessToken) {
153
+ throw new Error(`Failed to acquire instance token for agent instance: ${agentAppInstanceId}`)
154
+ }
155
+
156
+ return token.accessToken
157
+ }
158
+
159
+ /**
160
+ * This method can optionally accept a tenant ID that overrides the tenant ID in the connection settings, if the connection settings authority contains "common".
161
+ * @param tenantId
162
+ * @returns
163
+ */
164
+ private resolveAuthority (tenantId?: string) : string {
165
+ // if for some reason the agentic tenant ID is not in the message, fall back to the original configured auth settings
166
+ if (!tenantId) {
167
+ return this.connectionSettings?.authority ? `${this.connectionSettings.authority}/${this.connectionSettings?.tenantId}` : `https://login.microsoftonline.com/${this.connectionSettings?.tenantId || 'botframework.com'}`
168
+ }
169
+
170
+ if (this.connectionSettings?.tenantId === 'common') {
171
+ return this.connectionSettings?.authority ? `${this.connectionSettings.authority}/${tenantId}` : `https://login.microsoftonline.com/${tenantId}`
172
+ } else {
173
+ return this.connectionSettings?.authority ? `${this.connectionSettings.authority}/${this.connectionSettings?.tenantId}` : `https://login.microsoftonline.com/${this.connectionSettings?.tenantId || 'botframework.com'}`
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Does a direct HTTP call to acquire a token for agentic scenarios - do not use this directly!
179
+ * This method will be removed once MSAL is updated with the necessary features.
180
+ * (This is required in order to pass additional parameters into the auth call)
181
+ * @param tenantId
182
+ * @param clientId
183
+ * @param clientAssertion
184
+ * @param scopes
185
+ * @param tokenBodyParameters
186
+ * @returns
187
+ */
188
+ private async acquireTokenByForAgenticScenarios (tenantId: string, clientId: string, clientAssertion: string | undefined, scopes: string[], tokenBodyParameters: { [key: string]: any }): Promise<string | null> {
189
+ if (!this.connectionSettings) {
190
+ throw new Error('Connection settings must be provided when calling getAgenticInstanceToken')
191
+ }
192
+
193
+ // Check cache first
194
+ const cacheKey = `${clientId}/${Object.keys(tokenBodyParameters).map(key => key !== 'user_federated_identity_credential' ? `${key}=${tokenBodyParameters[key]}` : '').join('&')}/${scopes.join(';')}`
195
+ if (this._agenticTokenCache.get(cacheKey)) {
196
+ return this._agenticTokenCache.get(cacheKey) as string
197
+ }
198
+
199
+ const url = `${this.resolveAuthority(tenantId)}/oauth2/v2.0/token`
200
+
201
+ const data: { [key: string]: any } = {
202
+ client_id: clientId,
203
+ scope: scopes.join(' '),
204
+ ...tokenBodyParameters
205
+ }
206
+
207
+ if (clientAssertion) {
208
+ data.client_assertion_type = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'
209
+ data.client_assertion = clientAssertion
210
+ } else {
211
+ data.client_secret = this.connectionSettings.clientSecret
212
+ }
213
+
214
+ const token = await axios.post(
215
+ url,
216
+ data,
217
+ {
218
+ headers: {
219
+ 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'
220
+ }
221
+ }
222
+ ).catch((error) => {
223
+ logger.error('Error acquiring token: ', error.toJSON())
224
+ throw error
225
+ })
226
+
227
+ // capture token, expire local cache 5 minutes early
228
+ this._agenticTokenCache.set(cacheKey, token.data.access_token, token.data.expires_in - 300)
229
+ return token.data.access_token
230
+ }
231
+
232
+ public async getAgenticUserToken (tenantId: string, agentAppInstanceId: string, agenticUserId: string, scopes: string[]): Promise<string> {
233
+ logger.debug('Getting agentic user token')
234
+ const agentToken = await this.getAgenticApplicationToken(tenantId, agentAppInstanceId)
235
+ const instanceToken = await this.getAgenticInstanceToken(tenantId, agentAppInstanceId)
236
+
237
+ const token = await this.acquireTokenByForAgenticScenarios(tenantId, agentToken, instanceToken, scopes, {
238
+ user_id: agenticUserId,
239
+ user_federated_identity_credential: instanceToken,
240
+ grant_type: 'user_fic',
241
+ })
242
+
243
+ if (!token) {
244
+ throw new Error(`Failed to acquire instance token for user token: ${agentAppInstanceId}`)
245
+ }
246
+
247
+ return token
248
+ }
249
+
250
+ public async getAgenticApplicationToken (tenantId: string, agentAppInstanceId: string): Promise<string> {
251
+ if (!this.connectionSettings?.clientId) {
252
+ throw new Error('Connection settings must be provided when calling getAgenticApplicationToken')
253
+ }
254
+ logger.debug('Getting agentic application token')
255
+ const token = await this.acquireTokenByForAgenticScenarios(tenantId, this.connectionSettings.clientId, undefined, ['api://AzureAdTokenExchange/.default'], {
256
+ grant_type: 'client_credentials',
257
+ fmi_path: agentAppInstanceId,
258
+ })
259
+
260
+ if (!token) {
261
+ throw new Error(`Failed to acquire token for agent instance: ${agentAppInstanceId}`)
262
+ }
263
+
264
+ return token
265
+ }
266
+
70
267
  private readonly sysOptions: NodeSystemOptions = {
71
268
  loggerOptions: {
72
269
  logLevel: LogLevel.Trace,
@@ -197,6 +394,28 @@ export class MsalTokenProvider implements AuthProvider {
197
394
  return token?.accessToken as string
198
395
  }
199
396
 
397
+ /**
398
+ * Acquires a token using a Workload Identity client assertion.
399
+ * @param authConfig The authentication configuration.
400
+ * @param scope The scope for the token.
401
+ * @returns A promise that resolves to the access token.
402
+ */
403
+ private async acquireAccessTokenViaWID (authConfig: AuthConfiguration, scope: string) : Promise<string> {
404
+ const scopes = [`${scope}/.default`]
405
+ const clientAssertion = fs.readFileSync(authConfig.WIDAssertionFile as string, 'utf8')
406
+ const cca = new ConfidentialClientApplication({
407
+ auth: {
408
+ clientId: authConfig.clientId as string,
409
+ authority: `https://login.microsoftonline.com/${authConfig.tenantId}`,
410
+ clientAssertion
411
+ },
412
+ system: this.sysOptions
413
+ })
414
+ const token = await cca.acquireTokenByClientCredential({ scopes })
415
+ logger.info('got token using WID client assertion')
416
+ return token?.accessToken as string
417
+ }
418
+
200
419
  /**
201
420
  * Fetches an external token.
202
421
  * @param FICClientId The FIC client ID.
@@ -3,9 +3,6 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { AuthConfiguration } from './auth/authConfiguration'
7
- import { AuthProvider } from './auth/authProvider'
8
- import { MsalTokenProvider } from './auth/msalTokenProvider'
9
6
  import { Middleware, MiddlewareHandler, MiddlewareSet } from './middlewareSet'
10
7
  import { TurnContext } from './turnContext'
11
8
  import { debug } from '@microsoft/agents-activity/logger'
@@ -13,7 +10,7 @@ import { Activity, ConversationReference } from '@microsoft/agents-activity'
13
10
  import { ResourceResponse } from './connector-client/resourceResponse'
14
11
  import { AttachmentData } from './connector-client/attachmentData'
15
12
  import { AttachmentInfo } from './connector-client/attachmentInfo'
16
- import { UserTokenClient } from './oauth'
13
+ import { JwtPayload } from 'jsonwebtoken'
17
14
 
18
15
  const logger = debug('agents:base-adapter')
19
16
 
@@ -54,35 +51,15 @@ export abstract class BaseAdapter {
54
51
  await context.sendActivity('To continue to run this agent, please fix the source code.')
55
52
  }
56
53
 
57
- /**
58
- * Symbol key used to store agent identity information in the TurnContext.
59
- */
60
- readonly AgentIdentityKey = Symbol('AgentIdentity')
61
-
62
54
  /**
63
55
  * Symbol key used to store connector client instances in the TurnContext.
64
56
  */
65
57
  readonly ConnectorClientKey = Symbol('ConnectorClient')
66
58
 
67
59
  /**
68
- * Symbol key used to store OAuth scope information in the TurnContext.
69
- */
70
- readonly OAuthScopeKey = Symbol('OAuthScope')
71
-
72
- /**
73
- * The authentication provider used for token management.
74
- */
75
- authProvider: AuthProvider = new MsalTokenProvider()
76
-
77
- /**
78
- * The user token client used for managing user tokens.
79
- */
80
- userTokenClient: UserTokenClient | null = null
81
-
82
- /**
83
- * The authentication configuration for the adapter.
60
+ * Symbol key used to store User Token Client instances in the TurnContext.
84
61
  */
85
- abstract authConfig: AuthConfiguration
62
+ readonly UserTokenClientKey = Symbol('UserTokenClient')
86
63
 
87
64
  /**
88
65
  * Sends a set of activities to the conversation.
@@ -115,32 +92,36 @@ export abstract class BaseAdapter {
115
92
  * @returns A promise representing the completion of the continue operation.
116
93
  */
117
94
  abstract continueConversation (
95
+ botAppIdOrIdentity: string | JwtPayload,
118
96
  reference: Partial<ConversationReference>,
119
97
  logic: (revocableContext: TurnContext) => Promise<void>
120
98
  ): Promise<void>
121
99
 
122
100
  /**
101
+ * @deprecated This function will not be supported in future versions. Use TurnContext.turnState.get<ConnectorClient>(CloudAdapter.ConnectorClientKey).
123
102
  * Uploads an attachment.
124
103
  * @param conversationId - The conversation ID.
125
104
  * @param attachmentData - The attachment data.
126
105
  * @returns A promise representing the ResourceResponse for the uploaded attachment.
127
106
  */
128
- abstract uploadAttachment (conversationId: string, attachmentData: AttachmentData): Promise<ResourceResponse>
107
+ abstract uploadAttachment (context: TurnContext, conversationId: string, attachmentData: AttachmentData): Promise<ResourceResponse>
129
108
 
130
109
  /**
110
+ * @deprecated This function will not be supported in future versions. Use TurnContext.turnState.get<ConnectorClient>(CloudAdapter.ConnectorClientKey).
131
111
  * Gets attachment information.
132
112
  * @param attachmentId - The attachment ID.
133
113
  * @returns A promise representing the AttachmentInfo for the requested attachment.
134
114
  */
135
- abstract getAttachmentInfo (attachmentId: string): Promise<AttachmentInfo>
115
+ abstract getAttachmentInfo (context: TurnContext, attachmentId: string): Promise<AttachmentInfo>
136
116
 
137
117
  /**
118
+ * @deprecated This function will not be supported in future versions. Use TurnContext.turnState.get<ConnectorClient>(CloudAdapter.ConnectorClientKey).
138
119
  * Gets an attachment.
139
120
  * @param attachmentId - The attachment ID.
140
121
  * @param viewId - The view ID.
141
122
  * @returns A promise representing the NodeJS.ReadableStream for the requested attachment.
142
123
  */
143
- abstract getAttachment (attachmentId: string, viewId: string): Promise<NodeJS.ReadableStream>
124
+ abstract getAttachment (context: TurnContext, attachmentId: string, viewId: string): Promise<NodeJS.ReadableStream>
144
125
 
145
126
  /**
146
127
  * Gets the error handler for the adapter.
@@ -215,9 +215,10 @@ export class CardFactory {
215
215
  * @param title The title of the card.
216
216
  * @param text The optional text for the card.
217
217
  * @param signingResource The signing resource.
218
+ * @param enableSso The option to enable SSO when authenticating using AAD. Defaults to true.
218
219
  * @returns The OAuth card attachment.
219
220
  */
220
- static oauthCard (connectionName: string, title: string, text: string, signingResource: SignInResource) : Attachment {
221
+ static oauthCard (connectionName: string, title: string, text: string, signingResource: SignInResource, enableSso: boolean = true) : Attachment {
221
222
  const card: Partial<OAuthCard> = {
222
223
  buttons: [{
223
224
  type: ActionTypes.Signin,
@@ -226,7 +227,7 @@ export class CardFactory {
226
227
  channelData: undefined
227
228
  }],
228
229
  connectionName,
229
- tokenExchangeResource: signingResource.tokenExchangeResource,
230
+ tokenExchangeResource: enableSso ? signingResource.tokenExchangeResource : undefined,
230
231
  tokenPostResource: signingResource.tokenPostResource,
231
232
  }
232
233
  if (text) {