@microsoft/agents-hosting 0.5.4-ga4d0401645 → 0.5.12-g2d752e9b13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/app/adaptiveCards/adaptiveCardsActions.js +4 -4
- package/dist/src/app/adaptiveCards/adaptiveCardsActions.js.map +1 -1
- package/dist/src/app/agentApplication.js +1 -1
- package/dist/src/app/agentApplication.js.map +1 -1
- package/dist/src/app/oauth/authorization.d.ts +1 -0
- package/dist/src/app/oauth/authorization.js +3 -4
- package/dist/src/app/oauth/authorization.js.map +1 -1
- package/dist/src/app/streaming/citation.d.ts +25 -0
- package/dist/src/app/streaming/{AIEntity.js → citation.js} +1 -1
- package/dist/src/app/streaming/citation.js.map +1 -0
- package/dist/src/app/streaming/{utilities.d.ts → citationUtil.d.ts} +3 -3
- package/dist/src/app/streaming/{utilities.js → citationUtil.js} +5 -36
- package/dist/src/app/streaming/citationUtil.js.map +1 -0
- package/dist/src/app/streaming/streamingResponse.d.ts +3 -4
- package/dist/src/app/streaming/streamingResponse.js +24 -22
- package/dist/src/app/streaming/streamingResponse.js.map +1 -1
- package/dist/src/cards/cardFactory.d.ts +2 -2
- package/dist/src/cards/cardFactory.js.map +1 -1
- package/dist/src/oauth/index.d.ts +1 -4
- package/dist/src/oauth/index.js +1 -4
- package/dist/src/oauth/index.js.map +1 -1
- package/dist/src/oauth/oAuthFlow.d.ts +9 -10
- package/dist/src/oauth/oAuthFlow.js +35 -38
- package/dist/src/oauth/oAuthFlow.js.map +1 -1
- package/dist/src/oauth/userTokenClient.d.ts +37 -7
- package/dist/src/oauth/userTokenClient.js +56 -15
- package/dist/src/oauth/userTokenClient.js.map +1 -1
- package/dist/src/oauth/userTokenClient.types.d.ts +147 -0
- package/dist/src/oauth/{oAuthCard.js → userTokenClient.types.js} +1 -1
- package/dist/src/oauth/userTokenClient.types.js.map +1 -0
- package/package.json +3 -3
- package/src/app/adaptiveCards/adaptiveCardsActions.ts +4 -4
- package/src/app/agentApplication.ts +1 -1
- package/src/app/oauth/authorization.ts +6 -8
- package/src/app/streaming/citation.ts +29 -0
- package/src/app/streaming/{utilities.ts → citationUtil.ts} +3 -35
- package/src/app/streaming/streamingResponse.ts +28 -27
- package/src/cards/cardFactory.ts +2 -3
- package/src/oauth/index.ts +1 -4
- package/src/oauth/oAuthFlow.ts +39 -45
- package/src/oauth/userTokenClient.ts +62 -19
- package/src/oauth/userTokenClient.types.ts +173 -0
- package/dist/src/app/streaming/AIEntity.d.ts +0 -36
- package/dist/src/app/streaming/AIEntity.js.map +0 -1
- package/dist/src/app/streaming/actionCall.d.ts +0 -33
- package/dist/src/app/streaming/actionCall.js +0 -7
- package/dist/src/app/streaming/actionCall.js.map +0 -1
- package/dist/src/app/streaming/clientCitation.d.ts +0 -68
- package/dist/src/app/streaming/clientCitation.js +0 -10
- package/dist/src/app/streaming/clientCitation.js.map +0 -1
- package/dist/src/app/streaming/message.d.ts +0 -106
- package/dist/src/app/streaming/message.js +0 -7
- package/dist/src/app/streaming/message.js.map +0 -1
- package/dist/src/app/streaming/sensitivityUsageInfo.d.ts +0 -40
- package/dist/src/app/streaming/sensitivityUsageInfo.js +0 -3
- package/dist/src/app/streaming/sensitivityUsageInfo.js.map +0 -1
- package/dist/src/app/streaming/utilities.js.map +0 -1
- package/dist/src/oauth/oAuthCard.d.ts +0 -27
- package/dist/src/oauth/oAuthCard.js.map +0 -1
- package/dist/src/oauth/signingResource.d.ts +0 -43
- package/dist/src/oauth/signingResource.js +0 -5
- package/dist/src/oauth/signingResource.js.map +0 -1
- package/dist/src/oauth/tokenExchangeRequest.d.ts +0 -17
- package/dist/src/oauth/tokenExchangeRequest.js +0 -5
- package/dist/src/oauth/tokenExchangeRequest.js.map +0 -1
- package/dist/src/oauth/tokenResponse.d.ts +0 -29
- package/dist/src/oauth/tokenResponse.js +0 -25
- package/dist/src/oauth/tokenResponse.js.map +0 -1
- package/src/app/streaming/AIEntity.ts +0 -44
- package/src/app/streaming/actionCall.ts +0 -37
- package/src/app/streaming/clientCitation.ts +0 -102
- package/src/app/streaming/message.ts +0 -125
- package/src/app/streaming/sensitivityUsageInfo.ts +0 -48
- package/src/oauth/oAuthCard.ts +0 -30
- package/src/oauth/signingResource.ts +0 -48
- package/src/oauth/tokenExchangeRequest.ts +0 -20
- package/src/oauth/tokenResponse.ts +0 -43
|
@@ -219,10 +219,10 @@ export class AdaptiveCardsActions<TState extends TurnState> {
|
|
|
219
219
|
}
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
-
await context.sendActivity({
|
|
222
|
+
await context.sendActivity(Activity.fromObject({
|
|
223
223
|
value: { body: response, status: 200 } as InvokeResponse,
|
|
224
224
|
type: ActivityTypes.InvokeResponse
|
|
225
|
-
}
|
|
225
|
+
}))
|
|
226
226
|
}
|
|
227
227
|
},
|
|
228
228
|
true
|
|
@@ -313,8 +313,8 @@ function createSearchSelector (dataset: string | RegExp | RouteSelector): RouteS
|
|
|
313
313
|
}
|
|
314
314
|
|
|
315
315
|
async function sendInvokeResponse (context: TurnContext, response: AdaptiveCardInvokeResponse) {
|
|
316
|
-
await context.sendActivity({
|
|
316
|
+
await context.sendActivity(Activity.fromObject({
|
|
317
317
|
value: { body: response, status: 200 } as InvokeResponse,
|
|
318
318
|
type: ActivityTypes.InvokeResponse
|
|
319
|
-
}
|
|
319
|
+
}))
|
|
320
320
|
}
|
|
@@ -741,7 +741,7 @@ export class AgentApplication<TState extends TurnState> {
|
|
|
741
741
|
const k = keyword.toString().toLocaleLowerCase()
|
|
742
742
|
return (context: TurnContext) => {
|
|
743
743
|
if (context?.activity?.type === ActivityTypes.Message && context.activity.text) {
|
|
744
|
-
return Promise.resolve(context.activity.text.toLocaleLowerCase()
|
|
744
|
+
return Promise.resolve(context.activity.text.toLocaleLowerCase() === k)
|
|
745
745
|
} else {
|
|
746
746
|
return Promise.resolve(false)
|
|
747
747
|
}
|
|
@@ -7,8 +7,7 @@ import { TurnContext } from '../../turnContext'
|
|
|
7
7
|
import { debug } from '../../logger'
|
|
8
8
|
import { TurnState } from '../turnState'
|
|
9
9
|
import { Storage } from '../../storage'
|
|
10
|
-
import { OAuthFlow,
|
|
11
|
-
import { UserState } from '../../state'
|
|
10
|
+
import { OAuthFlow, TokenResponse } from '../../oauth'
|
|
12
11
|
|
|
13
12
|
const logger = debug('agents:authorization')
|
|
14
13
|
|
|
@@ -48,11 +47,10 @@ export class Authorization {
|
|
|
48
47
|
* @param {AuthorizationHandlers} authHandlers - Configuration for OAuth providers
|
|
49
48
|
* @throws {Error} If storage is null/undefined or no auth handlers are provided
|
|
50
49
|
*/
|
|
51
|
-
constructor (storage: Storage, authHandlers: AuthorizationHandlers) {
|
|
50
|
+
constructor (private storage: Storage, authHandlers: AuthorizationHandlers) {
|
|
52
51
|
if (storage === undefined || storage === null) {
|
|
53
52
|
throw new Error('Storage is required for UserAuthorization')
|
|
54
53
|
}
|
|
55
|
-
const userState = new UserState(storage)
|
|
56
54
|
if (authHandlers === undefined || Object.keys(authHandlers).length === 0) {
|
|
57
55
|
throw new Error('The authorization does not have any auth handlers')
|
|
58
56
|
}
|
|
@@ -66,7 +64,7 @@ export class Authorization {
|
|
|
66
64
|
currentAuthHandler.title = currentAuthHandler.title ?? process.env[ah + '_connectionTitle'] as string
|
|
67
65
|
currentAuthHandler.text = currentAuthHandler.text ?? process.env[ah + '_connectionText'] as string
|
|
68
66
|
currentAuthHandler.auto = currentAuthHandler.auto ?? process.env[ah + '_connectionAuto'] === 'true'
|
|
69
|
-
currentAuthHandler.flow = new OAuthFlow(
|
|
67
|
+
currentAuthHandler.flow = new OAuthFlow(this.storage, currentAuthHandler.name, null!, currentAuthHandler.title, currentAuthHandler.text)
|
|
70
68
|
}
|
|
71
69
|
logger.info('Authorization handlers configured with', this._authHandlers.length, 'handlers')
|
|
72
70
|
}
|
|
@@ -93,18 +91,18 @@ export class Authorization {
|
|
|
93
91
|
public async beginOrContinueFlow (context: TurnContext, state: TurnState, authHandlerId?: string) : Promise<TokenResponse> {
|
|
94
92
|
logger.info('beginOrContinueFlow for authHandlerId:', authHandlerId)
|
|
95
93
|
const flow = this.resolverHandler(authHandlerId).flow!
|
|
96
|
-
let tokenResponse: TokenResponse
|
|
94
|
+
let tokenResponse: TokenResponse | undefined
|
|
97
95
|
if (flow.state!.flowStarted === false) {
|
|
98
96
|
tokenResponse = await flow.beginFlow(context)
|
|
99
97
|
} else {
|
|
100
98
|
tokenResponse = await flow.continueFlow(context)
|
|
101
|
-
if (tokenResponse
|
|
99
|
+
if (tokenResponse && tokenResponse.token) {
|
|
102
100
|
if (this._signInHandler) {
|
|
103
101
|
await this._signInHandler(context, state, authHandlerId)
|
|
104
102
|
}
|
|
105
103
|
}
|
|
106
104
|
}
|
|
107
|
-
return tokenResponse
|
|
105
|
+
return tokenResponse!
|
|
108
106
|
}
|
|
109
107
|
|
|
110
108
|
/**
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Citations returned by the model.
|
|
8
|
+
*/
|
|
9
|
+
export interface Citation {
|
|
10
|
+
/**
|
|
11
|
+
* The content of the citation.
|
|
12
|
+
*/
|
|
13
|
+
content: string;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The title of the citation.
|
|
17
|
+
*/
|
|
18
|
+
title: string | null;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The URL of the citation.
|
|
22
|
+
*/
|
|
23
|
+
url: string | null;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The filepath of the document.
|
|
27
|
+
*/
|
|
28
|
+
filepath: string | null;
|
|
29
|
+
}
|
|
@@ -3,48 +3,16 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { ClientCitation } from '
|
|
6
|
+
import { ClientCitation } from '@microsoft/agents-activity/src/entity/AIEntity'
|
|
7
7
|
|
|
8
8
|
// import { stringify } from 'yaml'
|
|
9
9
|
|
|
10
10
|
// import { Tokenizer } from './tokenizers'
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* Utility functions for manipulating .
|
|
13
|
+
* Utility functions for manipulating text and citations.
|
|
14
14
|
*/
|
|
15
|
-
export class
|
|
16
|
-
// /**
|
|
17
|
-
// * Converts a value to a string.
|
|
18
|
-
// * @remarks
|
|
19
|
-
// * Dates are converted to ISO strings and Objects are converted to JSON or YAML, whichever is shorter.
|
|
20
|
-
// * @param {Tokenizer} tokenizer Tokenizer to use for encoding.
|
|
21
|
-
// * @param {any} value Value to convert.
|
|
22
|
-
// * @param {boolean} asJSON Optional. If true objects will always be converted to JSON instead of YAML. Defaults to false.
|
|
23
|
-
// * @returns {string} Converted value.
|
|
24
|
-
// */
|
|
25
|
-
// public static toString (tokenizer: Tokenizer, value: any, asJSON: boolean = false): string {
|
|
26
|
-
// if (value === undefined || value === null) {
|
|
27
|
-
// return ''
|
|
28
|
-
// } else if (typeof value === 'object') {
|
|
29
|
-
// if (typeof value.toISOString === 'function') {
|
|
30
|
-
// return value.toISOString()
|
|
31
|
-
// } else if (asJSON) {
|
|
32
|
-
// return JSON.stringify(value)
|
|
33
|
-
// } else {
|
|
34
|
-
// // Return shorter version of object
|
|
35
|
-
// const asYaml = stringify(value)
|
|
36
|
-
// const asJSON = JSON.stringify(value)
|
|
37
|
-
// if (tokenizer.encode(asYaml).length <= tokenizer.encode(asJSON).length) {
|
|
38
|
-
// return asYaml
|
|
39
|
-
// } else {
|
|
40
|
-
// return asJSON
|
|
41
|
-
// }
|
|
42
|
-
// }
|
|
43
|
-
// } else {
|
|
44
|
-
// return value.toString()
|
|
45
|
-
// }
|
|
46
|
-
// }
|
|
47
|
-
|
|
15
|
+
export class CitationUtil {
|
|
48
16
|
/**
|
|
49
17
|
*
|
|
50
18
|
* Clips the text to a maximum length in case it exceeds the limit.
|
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { Activity, Attachment, Entity } from '@microsoft/agents-activity'
|
|
6
|
+
import { Activity, addAIToActivity, Attachment, Entity } from '@microsoft/agents-activity'
|
|
7
7
|
import { TurnContext } from '../../turnContext'
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
8
|
+
import { Citation } from './citation'
|
|
9
|
+
import { CitationUtil } from './citationUtil'
|
|
10
|
+
import { debug } from '../../logger'
|
|
11
|
+
import { ClientCitation, SensitivityUsageInfo } from '@microsoft/agents-activity/src/entity/AIEntity'
|
|
12
|
+
|
|
13
|
+
const logger = debug('agents:streamingResponse')
|
|
12
14
|
|
|
13
15
|
/**
|
|
14
16
|
* A helper class for streaming responses to the client.
|
|
@@ -16,7 +18,7 @@ import { Utilities } from './utilities'
|
|
|
16
18
|
* This class is used to send a series of updates to the client in a single response. The expected
|
|
17
19
|
* sequence of calls is:
|
|
18
20
|
*
|
|
19
|
-
* `
|
|
21
|
+
* `queueInformativeUpdate()`, `queueTextChunk()`, `queueTextChunk()`, ..., `endStream()`.
|
|
20
22
|
*
|
|
21
23
|
* Once `endStream()` is called, the stream is considered ended and no further updates can be sent.
|
|
22
24
|
*/
|
|
@@ -111,7 +113,7 @@ export class StreamingResponse {
|
|
|
111
113
|
this._message += text
|
|
112
114
|
|
|
113
115
|
// If there are citations, modify the content so that the sources are numbers instead of [doc1], [doc2], etc.
|
|
114
|
-
this._message =
|
|
116
|
+
this._message = CitationUtil.formatCitationsResponse(this._message)
|
|
115
117
|
|
|
116
118
|
// Queue the next chunk
|
|
117
119
|
this.queueNextChunk()
|
|
@@ -168,7 +170,7 @@ export class StreamingResponse {
|
|
|
168
170
|
appearance: {
|
|
169
171
|
'@type': 'DigitalDocument',
|
|
170
172
|
name: citation.title || `Document #${currPos + 1}`,
|
|
171
|
-
abstract:
|
|
173
|
+
abstract: CitationUtil.snippet(citation.content, 477)
|
|
172
174
|
}
|
|
173
175
|
}
|
|
174
176
|
currPos++
|
|
@@ -238,10 +240,11 @@ export class StreamingResponse {
|
|
|
238
240
|
// Send final message
|
|
239
241
|
return Activity.fromObject({
|
|
240
242
|
type: 'message',
|
|
241
|
-
text: this._message,
|
|
243
|
+
text: this._message || 'end strean response',
|
|
242
244
|
attachments: this._attachments,
|
|
243
245
|
channelData: {
|
|
244
|
-
streamType: 'final'
|
|
246
|
+
streamType: 'final',
|
|
247
|
+
streamSequence: this._nextSequence++
|
|
245
248
|
} as StreamingChannelData
|
|
246
249
|
})
|
|
247
250
|
} else {
|
|
@@ -267,8 +270,8 @@ export class StreamingResponse {
|
|
|
267
270
|
// If there's no sync in progress, start one
|
|
268
271
|
if (!this._queueSync) {
|
|
269
272
|
this._queueSync = this.drainQueue().catch((err) => {
|
|
270
|
-
|
|
271
|
-
throw err
|
|
273
|
+
logger.error(`Error occured when sending activity while streaming: "${err}".`)
|
|
274
|
+
// throw err
|
|
272
275
|
})
|
|
273
276
|
}
|
|
274
277
|
}
|
|
@@ -282,12 +285,10 @@ export class StreamingResponse {
|
|
|
282
285
|
// eslint-disable-next-line no-async-promise-executor
|
|
283
286
|
return new Promise<void>(async (resolve, reject) => {
|
|
284
287
|
try {
|
|
288
|
+
logger.debug(`Draining queue with ${this._queue.length} activities.`)
|
|
285
289
|
while (this._queue.length > 0) {
|
|
286
|
-
// Get next activity from queue
|
|
287
290
|
const factory = this._queue.shift()!
|
|
288
291
|
const activity = factory()
|
|
289
|
-
|
|
290
|
-
// Send activity
|
|
291
292
|
await this.sendActivity(activity)
|
|
292
293
|
}
|
|
293
294
|
|
|
@@ -295,7 +296,6 @@ export class StreamingResponse {
|
|
|
295
296
|
} catch (err) {
|
|
296
297
|
reject(err)
|
|
297
298
|
} finally {
|
|
298
|
-
// Queue is empty, mark as idle
|
|
299
299
|
this._queueSync = undefined
|
|
300
300
|
}
|
|
301
301
|
})
|
|
@@ -323,7 +323,7 @@ export class StreamingResponse {
|
|
|
323
323
|
|
|
324
324
|
if (this._citations && this._citations.length > 0 && !this._ended) {
|
|
325
325
|
// Filter out the citations unused in content.
|
|
326
|
-
const currCitations =
|
|
326
|
+
const currCitations = CitationUtil.getUsedCitations(this._message, this._citations) ?? undefined
|
|
327
327
|
activity.entities.push({
|
|
328
328
|
type: 'https://schema.org/Message',
|
|
329
329
|
'@type': 'Message',
|
|
@@ -347,21 +347,22 @@ export class StreamingResponse {
|
|
|
347
347
|
|
|
348
348
|
// Add in Generated by AI
|
|
349
349
|
if (this._enableGeneratedByAILabel) {
|
|
350
|
-
activity.
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
350
|
+
addAIToActivity(activity, this._citations, this._sensitivityLabel)
|
|
351
|
+
// activity.entities.push({
|
|
352
|
+
// type: 'https://schema.org/Message',
|
|
353
|
+
// '@type': 'Message',
|
|
354
|
+
// '@context': 'https://schema.org',
|
|
355
|
+
// '@id': '',
|
|
356
|
+
// additionalType: ['AIGeneratedContent'],
|
|
357
|
+
// citation: this._citations && this._citations.length > 0 ? this._citations : [],
|
|
358
|
+
// usageInfo: this._sensitivityLabel
|
|
359
|
+
// } as unknown as Entity)
|
|
359
360
|
}
|
|
360
361
|
}
|
|
361
362
|
|
|
362
363
|
// Send activity
|
|
363
364
|
const response = await this._context.sendActivity(activity)
|
|
364
|
-
|
|
365
|
+
await new Promise((resolve) => setTimeout(resolve, 1500))
|
|
365
366
|
|
|
366
367
|
// Save assigned stream ID
|
|
367
368
|
if (!this._streamId) {
|
package/src/cards/cardFactory.ts
CHANGED
|
@@ -13,8 +13,7 @@ import { O365ConnectorCard } from './o365ConnectorCard'
|
|
|
13
13
|
import { ThumbnailCard } from './thumbnailCard'
|
|
14
14
|
import { VideoCard } from './videoCard'
|
|
15
15
|
import { CardImage } from './cardImage'
|
|
16
|
-
import { OAuthCard } from '../oauth/
|
|
17
|
-
import { SigningResource } from '../oauth/signingResource'
|
|
16
|
+
import { OAuthCard, SignInResource } from '../oauth/userTokenClient.types'
|
|
18
17
|
import { SigninCard } from './signinCard'
|
|
19
18
|
|
|
20
19
|
/**
|
|
@@ -218,7 +217,7 @@ export class CardFactory {
|
|
|
218
217
|
* @param signingResource The signing resource.
|
|
219
218
|
* @returns The OAuth card attachment.
|
|
220
219
|
*/
|
|
221
|
-
static oauthCard (connectionName: string, title: string, text: string, signingResource:
|
|
220
|
+
static oauthCard (connectionName: string, title: string, text: string, signingResource: SignInResource) : Attachment {
|
|
222
221
|
const card: Partial<OAuthCard> = {
|
|
223
222
|
buttons: [{
|
|
224
223
|
type: ActionTypes.Signin,
|
package/src/oauth/index.ts
CHANGED
package/src/oauth/oAuthFlow.ts
CHANGED
|
@@ -4,20 +4,18 @@ import { debug } from './../logger'
|
|
|
4
4
|
import { ActivityTypes, Attachment } from '@microsoft/agents-activity'
|
|
5
5
|
import {
|
|
6
6
|
CardFactory,
|
|
7
|
-
AgentStatePropertyAccessor,
|
|
8
|
-
UserState,
|
|
9
7
|
TurnContext,
|
|
8
|
+
Storage,
|
|
10
9
|
MessageFactory,
|
|
11
|
-
TokenExchangeRequest,
|
|
12
|
-
UserTokenClient
|
|
13
10
|
} from '../'
|
|
14
|
-
import {
|
|
11
|
+
import { UserTokenClient } from './userTokenClient'
|
|
12
|
+
import { TokenExchangeRequest, TokenResponse } from './userTokenClient.types'
|
|
15
13
|
|
|
16
14
|
const logger = debug('agents:oauth-flow')
|
|
17
15
|
|
|
18
|
-
export
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
export interface FlowState {
|
|
17
|
+
flowStarted: boolean,
|
|
18
|
+
flowExpires: number
|
|
21
19
|
}
|
|
22
20
|
|
|
23
21
|
interface TokenVerifyState {
|
|
@@ -35,12 +33,7 @@ export class OAuthFlow {
|
|
|
35
33
|
/**
|
|
36
34
|
* The current state of the OAuth flow.
|
|
37
35
|
*/
|
|
38
|
-
state: FlowState
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* The accessor for managing the flow state in user state.
|
|
42
|
-
*/
|
|
43
|
-
flowStateAccessor: AgentStatePropertyAccessor<FlowState | null>
|
|
36
|
+
state: FlowState
|
|
44
37
|
|
|
45
38
|
/**
|
|
46
39
|
* The ID of the token exchange request, used to deduplicate requests.
|
|
@@ -66,9 +59,8 @@ export class OAuthFlow {
|
|
|
66
59
|
* Creates a new instance of OAuthFlow.
|
|
67
60
|
* @param userState The user state.
|
|
68
61
|
*/
|
|
69
|
-
constructor (
|
|
70
|
-
this.state =
|
|
71
|
-
this.flowStateAccessor = userState.createProperty('flowState')
|
|
62
|
+
constructor (private storage: Storage, absOauthConnectionName: string, tokenClient?: UserTokenClient, cardTitle?: string, cardText?: string) {
|
|
63
|
+
this.state = { flowExpires: 0, flowStarted: false }
|
|
72
64
|
this.absOauthConnectionName = absOauthConnectionName
|
|
73
65
|
this.userTokenClient = tokenClient ?? null!
|
|
74
66
|
this.cardTitle = cardTitle ?? this.cardTitle
|
|
@@ -97,7 +89,7 @@ export class OAuthFlow {
|
|
|
97
89
|
* @param context The turn context.
|
|
98
90
|
* @returns A promise that resolves to the user token.
|
|
99
91
|
*/
|
|
100
|
-
public async beginFlow (context: TurnContext): Promise<TokenResponse> {
|
|
92
|
+
public async beginFlow (context: TurnContext): Promise<TokenResponse | undefined> {
|
|
101
93
|
this.state = await this.getUserState(context)
|
|
102
94
|
if (this.absOauthConnectionName === '') {
|
|
103
95
|
throw new Error('connectionName is not set')
|
|
@@ -105,27 +97,20 @@ export class OAuthFlow {
|
|
|
105
97
|
logger.info('Starting OAuth flow for connectionName:', this.absOauthConnectionName)
|
|
106
98
|
await this.initializeTokenClient(context)
|
|
107
99
|
|
|
108
|
-
const
|
|
109
|
-
|
|
100
|
+
const act = context.activity
|
|
101
|
+
const output = await this.userTokenClient.getTokenOrSignInResource(act.from?.id!, this.absOauthConnectionName, act.channelId!, act.getConversationReference(), act.relatesTo!, undefined!)
|
|
102
|
+
if (output && output.tokenResponse) {
|
|
110
103
|
this.state.flowStarted = false
|
|
111
104
|
this.state.flowExpires = 0
|
|
112
|
-
await this.
|
|
113
|
-
|
|
114
|
-
return tokenResponse
|
|
105
|
+
await this.storage.write({ [this.getFlowStateKey(context)]: this.state })
|
|
106
|
+
return output.tokenResponse
|
|
115
107
|
}
|
|
116
|
-
|
|
117
|
-
const authConfig = context.adapter.authConfig
|
|
118
|
-
const signingResource = await this.userTokenClient.getSignInResource(authConfig.clientId!, this.absOauthConnectionName, context.activity.getConversationReference(), context.activity.relatesTo)
|
|
119
|
-
const oCard: Attachment = CardFactory.oauthCard(this.absOauthConnectionName, this.cardTitle, this.cardText, signingResource)
|
|
108
|
+
const oCard: Attachment = CardFactory.oauthCard(this.absOauthConnectionName, this.cardTitle, this.cardText, output.signInResource)
|
|
120
109
|
await context.sendActivity(MessageFactory.attachment(oCard))
|
|
121
110
|
this.state.flowStarted = true
|
|
122
111
|
this.state.flowExpires = Date.now() + 30000
|
|
123
|
-
await this.
|
|
124
|
-
|
|
125
|
-
return {
|
|
126
|
-
token: undefined,
|
|
127
|
-
status: TokenRequestStatus.InProgress
|
|
128
|
-
}
|
|
112
|
+
await this.storage.write({ [this.getFlowStateKey(context)]: this.state })
|
|
113
|
+
return undefined
|
|
129
114
|
}
|
|
130
115
|
|
|
131
116
|
/**
|
|
@@ -140,7 +125,7 @@ export class OAuthFlow {
|
|
|
140
125
|
logger.warn('Flow expired')
|
|
141
126
|
this.state!.flowStarted = false
|
|
142
127
|
await context.sendActivity(MessageFactory.text('Sign-in session expired. Please try again.'))
|
|
143
|
-
return {
|
|
128
|
+
return { token: undefined }
|
|
144
129
|
}
|
|
145
130
|
const contFlowActivity = context.activity
|
|
146
131
|
if (contFlowActivity.type === ActivityTypes.Message) {
|
|
@@ -161,22 +146,22 @@ export class OAuthFlow {
|
|
|
161
146
|
logger.info('Continuing OAuth flow with tokenExchange')
|
|
162
147
|
const tokenExchangeRequest = contFlowActivity.value as TokenExchangeRequest
|
|
163
148
|
if (this.tokenExchangeId === tokenExchangeRequest.id) { // dedupe
|
|
164
|
-
return {
|
|
149
|
+
return { token: undefined }
|
|
165
150
|
}
|
|
166
151
|
this.tokenExchangeId = tokenExchangeRequest.id!
|
|
167
152
|
const userTokenResp = await this.userTokenClient?.exchangeTokenAsync(contFlowActivity.from?.id!, this.absOauthConnectionName, contFlowActivity.channelId!, tokenExchangeRequest)
|
|
168
|
-
if (userTokenResp
|
|
153
|
+
if (userTokenResp && userTokenResp.token) {
|
|
169
154
|
logger.info('Token exchanged')
|
|
170
155
|
this.state!.flowStarted = false
|
|
171
|
-
await this.
|
|
156
|
+
await this.storage.write({ [this.getFlowStateKey(context)]: this.state })
|
|
172
157
|
return userTokenResp
|
|
173
158
|
} else {
|
|
174
159
|
logger.warn('Token exchange failed')
|
|
175
160
|
this.state!.flowStarted = true
|
|
176
|
-
return {
|
|
161
|
+
return { token: undefined }
|
|
177
162
|
}
|
|
178
163
|
}
|
|
179
|
-
return {
|
|
164
|
+
return { token: undefined }
|
|
180
165
|
}
|
|
181
166
|
|
|
182
167
|
/**
|
|
@@ -189,7 +174,7 @@ export class OAuthFlow {
|
|
|
189
174
|
await this.initializeTokenClient(context)
|
|
190
175
|
await this.userTokenClient?.signOut(context.activity.from?.id as string, this.absOauthConnectionName, context.activity.channelId as string)
|
|
191
176
|
this.state!.flowExpires = 0
|
|
192
|
-
|
|
177
|
+
this.storage.write({ [this.getFlowStateKey(context)]: this.state })
|
|
193
178
|
logger.info('User signed out successfully')
|
|
194
179
|
}
|
|
195
180
|
|
|
@@ -199,10 +184,9 @@ export class OAuthFlow {
|
|
|
199
184
|
* @returns A promise that resolves to the user state.
|
|
200
185
|
*/
|
|
201
186
|
private async getUserState (context: TurnContext) {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
}
|
|
187
|
+
const key = this.getFlowStateKey(context)
|
|
188
|
+
const data = await this.storage.read([key])
|
|
189
|
+
const userProfile: FlowState = data[key] ?? { flowStarted: false, flowExpires: 0 }
|
|
206
190
|
return userProfile
|
|
207
191
|
}
|
|
208
192
|
|
|
@@ -210,7 +194,17 @@ export class OAuthFlow {
|
|
|
210
194
|
if (this.userTokenClient === undefined || this.userTokenClient === null) {
|
|
211
195
|
const scope = 'https://api.botframework.com'
|
|
212
196
|
const accessToken = await context.adapter.authProvider.getAccessToken(context.adapter.authConfig, scope)
|
|
213
|
-
this.userTokenClient = new UserTokenClient(accessToken)
|
|
197
|
+
this.userTokenClient = new UserTokenClient(accessToken, context.adapter.authConfig.clientId!)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
private getFlowStateKey (context: TurnContext): string {
|
|
202
|
+
const channelId = context.activity.channelId
|
|
203
|
+
const conversationId = context.activity.conversation?.id
|
|
204
|
+
const userId = context.activity.from?.id
|
|
205
|
+
if (!channelId || !conversationId || !userId) {
|
|
206
|
+
throw new Error('ChannelId, conversationId, and userId must be set in the activity')
|
|
214
207
|
}
|
|
208
|
+
return `oauth/${channelId}/${conversationId}/${userId}/flowState`
|
|
215
209
|
}
|
|
216
210
|
}
|
|
@@ -2,12 +2,10 @@
|
|
|
2
2
|
// Licensed under the MIT License.
|
|
3
3
|
|
|
4
4
|
import axios, { AxiosInstance } from 'axios'
|
|
5
|
-
import { SigningResource } from './signingResource'
|
|
6
5
|
import { ConversationReference } from '@microsoft/agents-activity'
|
|
7
6
|
import { debug } from '../logger'
|
|
8
|
-
import { TokenExchangeRequest } from './tokenExchangeRequest'
|
|
9
7
|
import { normalizeTokenExchangeState } from '../activityWireCompat'
|
|
10
|
-
import {
|
|
8
|
+
import { AadResourceUrls, SignInResource, TokenExchangeRequest, TokenOrSinginResourceResponse, TokenResponse, TokenStatus } from './userTokenClient.types'
|
|
11
9
|
import { getProductInfo } from '../getProductInfo'
|
|
12
10
|
|
|
13
11
|
const logger = debug('agents:user-token-client')
|
|
@@ -17,12 +15,14 @@ const logger = debug('agents:user-token-client')
|
|
|
17
15
|
*/
|
|
18
16
|
export class UserTokenClient {
|
|
19
17
|
client: AxiosInstance
|
|
20
|
-
|
|
18
|
+
msAppId: string
|
|
21
19
|
/**
|
|
22
20
|
* Creates a new instance of UserTokenClient.
|
|
23
21
|
* @param token The token to use for authentication.
|
|
22
|
+
* @param msAppId The Microsoft application ID.
|
|
24
23
|
*/
|
|
25
|
-
constructor (token: string) {
|
|
24
|
+
constructor (token: string, appId: string) {
|
|
25
|
+
this.msAppId = appId
|
|
26
26
|
const baseURL = 'https://api.botframework.com'
|
|
27
27
|
const axiosInstance = axios.create({
|
|
28
28
|
baseURL,
|
|
@@ -47,15 +47,12 @@ export class UserTokenClient {
|
|
|
47
47
|
try {
|
|
48
48
|
const params = { connectionName, channelId, userId, code }
|
|
49
49
|
const response = await this.client.get('/api/usertoken/GetToken', { params })
|
|
50
|
-
return
|
|
50
|
+
return response.data as TokenResponse
|
|
51
51
|
} catch (error: any) {
|
|
52
52
|
if (error.response?.status !== 404) {
|
|
53
53
|
logger.error(error)
|
|
54
54
|
}
|
|
55
|
-
return {
|
|
56
|
-
status: TokenRequestStatus.Failed,
|
|
57
|
-
token: undefined
|
|
58
|
-
}
|
|
55
|
+
return { token: undefined }
|
|
59
56
|
}
|
|
60
57
|
}
|
|
61
58
|
|
|
@@ -81,24 +78,24 @@ export class UserTokenClient {
|
|
|
81
78
|
|
|
82
79
|
/**
|
|
83
80
|
* Gets the sign-in resource.
|
|
84
|
-
* @param
|
|
85
|
-
* @param
|
|
81
|
+
* @param msAppId The application ID.
|
|
82
|
+
* @param connectionName The connection name.
|
|
86
83
|
* @param activity The activity.
|
|
87
84
|
* @returns A promise that resolves to the signing resource.
|
|
88
85
|
*/
|
|
89
|
-
async getSignInResource (
|
|
86
|
+
async getSignInResource (msAppId: string, connectionName: string, conversation: ConversationReference, relatesTo?: ConversationReference) : Promise<SignInResource> {
|
|
90
87
|
try {
|
|
91
88
|
const tokenExchangeState = {
|
|
92
|
-
connectionName
|
|
93
|
-
conversation
|
|
89
|
+
connectionName,
|
|
90
|
+
conversation,
|
|
94
91
|
relatesTo,
|
|
95
|
-
msAppId
|
|
92
|
+
msAppId
|
|
96
93
|
}
|
|
97
94
|
const tokenExchangeStateNormalized = normalizeTokenExchangeState(tokenExchangeState)
|
|
98
95
|
const state = Buffer.from(JSON.stringify(tokenExchangeStateNormalized)).toString('base64')
|
|
99
96
|
const params = { state }
|
|
100
97
|
const response = await this.client.get('/api/botsignin/GetSignInResource', { params })
|
|
101
|
-
return response.data as
|
|
98
|
+
return response.data as SignInResource
|
|
102
99
|
} catch (error: any) {
|
|
103
100
|
logger.error(error)
|
|
104
101
|
throw error
|
|
@@ -117,10 +114,56 @@ export class UserTokenClient {
|
|
|
117
114
|
try {
|
|
118
115
|
const params = { userId, connectionName, channelId }
|
|
119
116
|
const response = await this.client.post('/api/usertoken/exchange', tokenExchangeRequest, { params })
|
|
120
|
-
return
|
|
117
|
+
return response.data as TokenResponse
|
|
121
118
|
} catch (error: any) {
|
|
122
119
|
logger.error(error)
|
|
123
|
-
return {
|
|
120
|
+
return { token: undefined }
|
|
124
121
|
}
|
|
125
122
|
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Gets the token or sign-in resource.
|
|
126
|
+
* @param userId The user ID.
|
|
127
|
+
* @param connectionName The connection name.
|
|
128
|
+
* @param channelId The channel ID.
|
|
129
|
+
* @param conversation The conversation reference.
|
|
130
|
+
* @param relatesTo The related conversation reference.
|
|
131
|
+
* @param code The code.
|
|
132
|
+
* @param finalRedirect The final redirect URL.
|
|
133
|
+
* @param fwdUrl The forward URL.
|
|
134
|
+
* @returns A promise that resolves to the token or sign-in resource response.
|
|
135
|
+
*/
|
|
136
|
+
async getTokenOrSignInResource (userId: string, connectionName: string, channelId: string, conversation: ConversationReference, relatesTo: ConversationReference, code: string, finalRedirect: string = '', fwdUrl: string = '') : Promise<TokenOrSinginResourceResponse> {
|
|
137
|
+
const state = Buffer.from(JSON.stringify({ conversation, relatesTo, connectionName, msAppId: this.msAppId })).toString('base64')
|
|
138
|
+
const params = { userId, connectionName, channelId, state, code, finalRedirect, fwdUrl }
|
|
139
|
+
const response = await this.client.get('/api/usertoken/GetTokenOrSignInResource', { params })
|
|
140
|
+
return response.data as TokenOrSinginResourceResponse
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Gets the token status.
|
|
145
|
+
* @param userId The user ID.
|
|
146
|
+
* @param channelId The channel ID.
|
|
147
|
+
* @param include The optional include parameter.
|
|
148
|
+
* @returns A promise that resolves to the token status.
|
|
149
|
+
*/
|
|
150
|
+
async getTokenStatus (userId: string, channelId: string, include: string = null!): Promise<TokenStatus[]> {
|
|
151
|
+
const params = { userId, channelId, include }
|
|
152
|
+
const response = await this.client.get('/api/usertoken/GetTokenStatus', { params })
|
|
153
|
+
return response.data as TokenStatus[]
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Gets the AAD tokens.
|
|
158
|
+
* @param userId The user ID.
|
|
159
|
+
* @param connectionName The connection name.
|
|
160
|
+
* @param channelId The channel ID.
|
|
161
|
+
* @param resourceUrls The resource URLs.
|
|
162
|
+
* @returns A promise that resolves to the AAD tokens.
|
|
163
|
+
*/
|
|
164
|
+
async getAadTokens (userId: string, connectionName: string, channelId: string, resourceUrls: AadResourceUrls) : Promise<Record<string, TokenResponse>> {
|
|
165
|
+
const params = { userId, connectionName, channelId }
|
|
166
|
+
const response = await this.client.post('/api/usertoken/GetAadTokens', resourceUrls, { params })
|
|
167
|
+
return response.data as Record<string, TokenResponse>
|
|
168
|
+
}
|
|
126
169
|
}
|