@microsoft/agents-hosting 0.5.12-g2d752e9b13 → 0.5.19-gc1e2ea1096
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/agentApplication.d.ts +186 -20
- package/dist/src/app/agentApplication.js +234 -32
- package/dist/src/app/agentApplication.js.map +1 -1
- package/dist/src/app/agentApplicationBuilder.d.ts +1 -1
- package/dist/src/app/agentApplicationOptions.d.ts +1 -1
- package/dist/src/app/appRoute.d.ts +5 -0
- package/dist/src/app/authorization.d.ts +294 -0
- package/dist/src/app/authorization.js +379 -0
- package/dist/src/app/authorization.js.map +1 -0
- package/dist/src/app/index.d.ts +1 -1
- package/dist/src/app/index.js +1 -1
- package/dist/src/app/index.js.map +1 -1
- package/dist/src/app/streaming/streamingResponse.js +1 -1
- package/dist/src/app/streaming/streamingResponse.js.map +1 -1
- package/dist/src/auth/authConfiguration.d.ts +2 -2
- package/dist/src/auth/authConfiguration.js +36 -17
- 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.js.map +1 -1
- package/dist/src/auth/msalTokenCredential.d.ts +10 -0
- package/dist/src/auth/msalTokenCredential.js +19 -0
- package/dist/src/auth/msalTokenCredential.js.map +1 -0
- package/dist/src/auth/msalTokenProvider.d.ts +1 -0
- package/dist/src/auth/msalTokenProvider.js +15 -0
- package/dist/src/auth/msalTokenProvider.js.map +1 -1
- package/dist/src/baseAdapter.d.ts +1 -1
- package/dist/src/baseAdapter.js +0 -4
- package/dist/src/baseAdapter.js.map +1 -1
- package/dist/src/cloudAdapter.d.ts +1 -0
- package/dist/src/cloudAdapter.js.map +1 -1
- package/dist/src/oauth/oAuthFlow.d.ts +53 -9
- package/dist/src/oauth/oAuthFlow.js +164 -35
- package/dist/src/oauth/oAuthFlow.js.map +1 -1
- package/dist/src/oauth/userTokenClient.js +4 -0
- package/dist/src/oauth/userTokenClient.js.map +1 -1
- package/package.json +4 -3
- package/src/app/agentApplication.ts +247 -32
- package/src/app/agentApplicationBuilder.ts +1 -1
- package/src/app/agentApplicationOptions.ts +1 -1
- package/src/app/appRoute.ts +6 -0
- package/src/app/authorization.ts +424 -0
- package/src/app/index.ts +1 -1
- package/src/app/streaming/streamingResponse.ts +1 -1
- package/src/auth/authConfiguration.ts +36 -19
- package/src/auth/index.ts +1 -0
- package/src/auth/jwt-middleware.ts +1 -1
- package/src/auth/msalTokenCredential.ts +14 -0
- package/src/auth/msalTokenProvider.ts +17 -1
- package/src/baseAdapter.ts +1 -1
- package/src/cloudAdapter.ts +2 -2
- package/src/oauth/oAuthFlow.ts +197 -35
- package/src/oauth/userTokenClient.ts +3 -0
- package/dist/src/app/oauth/authorization.d.ts +0 -88
- package/dist/src/app/oauth/authorization.js +0 -134
- package/dist/src/app/oauth/authorization.js.map +0 -1
- package/src/app/oauth/authorization.ts +0 -160
package/src/oauth/oAuthFlow.ts
CHANGED
|
@@ -1,26 +1,44 @@
|
|
|
1
1
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
2
2
|
// Licensed under the MIT License.
|
|
3
3
|
import { debug } from './../logger'
|
|
4
|
-
import { ActivityTypes, Attachment } from '@microsoft/agents-activity'
|
|
4
|
+
import { Activity, ActivityTypes, Attachment } from '@microsoft/agents-activity'
|
|
5
5
|
import {
|
|
6
6
|
CardFactory,
|
|
7
7
|
TurnContext,
|
|
8
8
|
Storage,
|
|
9
|
-
MessageFactory
|
|
9
|
+
MessageFactory
|
|
10
10
|
} from '../'
|
|
11
11
|
import { UserTokenClient } from './userTokenClient'
|
|
12
12
|
import { TokenExchangeRequest, TokenResponse } from './userTokenClient.types'
|
|
13
13
|
|
|
14
14
|
const logger = debug('agents:oauth-flow')
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Represents the state of the OAuth flow.
|
|
18
|
+
* @interface FlowState
|
|
19
|
+
*/
|
|
16
20
|
export interface FlowState {
|
|
17
|
-
|
|
18
|
-
|
|
21
|
+
/** Indicates whether the OAuth flow has been started */
|
|
22
|
+
flowStarted: boolean | undefined,
|
|
23
|
+
/** Timestamp when the OAuth flow expires (in milliseconds since epoch) */
|
|
24
|
+
flowExpires: number | undefined,
|
|
25
|
+
/** The absolute OAuth connection name used for the flow, null if not set */
|
|
26
|
+
absOauthConnectionName: string
|
|
27
|
+
/** Optional activity to continue the flow with, used for multi-turn scenarios */
|
|
28
|
+
continuationActivity?: Activity | null
|
|
29
|
+
|
|
30
|
+
eTag?: string // Optional ETag for optimistic concurrency control
|
|
19
31
|
}
|
|
20
32
|
|
|
21
33
|
interface TokenVerifyState {
|
|
22
34
|
state: string
|
|
23
35
|
}
|
|
36
|
+
|
|
37
|
+
interface CachedToken {
|
|
38
|
+
token: TokenResponse
|
|
39
|
+
expiresAt: number
|
|
40
|
+
}
|
|
41
|
+
|
|
24
42
|
/**
|
|
25
43
|
* Manages the OAuth flow
|
|
26
44
|
*/
|
|
@@ -40,6 +58,11 @@ export class OAuthFlow {
|
|
|
40
58
|
*/
|
|
41
59
|
tokenExchangeId: string | null = null
|
|
42
60
|
|
|
61
|
+
/**
|
|
62
|
+
* In-memory cache for tokens with expiration.
|
|
63
|
+
*/
|
|
64
|
+
private tokenCache: Map<string, CachedToken> = new Map()
|
|
65
|
+
|
|
43
66
|
/**
|
|
44
67
|
* The name of the OAuth connection.
|
|
45
68
|
*/
|
|
@@ -57,10 +80,14 @@ export class OAuthFlow {
|
|
|
57
80
|
|
|
58
81
|
/**
|
|
59
82
|
* Creates a new instance of OAuthFlow.
|
|
60
|
-
* @param
|
|
83
|
+
* @param storage The storage provider for persisting flow state.
|
|
84
|
+
* @param absOauthConnectionName The absolute OAuth connection name.
|
|
85
|
+
* @param tokenClient Optional user token client. If not provided, will be initialized automatically.
|
|
86
|
+
* @param cardTitle Optional title for the OAuth card. Defaults to 'Sign in'.
|
|
87
|
+
* @param cardText Optional text for the OAuth card. Defaults to 'login'.
|
|
61
88
|
*/
|
|
62
89
|
constructor (private storage: Storage, absOauthConnectionName: string, tokenClient?: UserTokenClient, cardTitle?: string, cardText?: string) {
|
|
63
|
-
this.state = {
|
|
90
|
+
this.state = { flowStarted: undefined, flowExpires: undefined, absOauthConnectionName }
|
|
64
91
|
this.absOauthConnectionName = absOauthConnectionName
|
|
65
92
|
this.userTokenClient = tokenClient ?? null!
|
|
66
93
|
this.cardTitle = cardTitle ?? this.cardTitle
|
|
@@ -68,29 +95,50 @@ export class OAuthFlow {
|
|
|
68
95
|
}
|
|
69
96
|
|
|
70
97
|
/**
|
|
71
|
-
* Retrieves the user token from the user token service.
|
|
98
|
+
* Retrieves the user token from the user token service with in-memory caching for 10 minutes.
|
|
72
99
|
* @param context The turn context containing the activity information.
|
|
73
100
|
* @returns A promise that resolves to the user token response.
|
|
74
101
|
* @throws Will throw an error if the channelId or from properties are not set in the activity.
|
|
75
102
|
*/
|
|
76
103
|
public async getUserToken (context: TurnContext): Promise<TokenResponse> {
|
|
77
104
|
await this.initializeTokenClient(context)
|
|
78
|
-
logger.info('Get token from user token service')
|
|
79
105
|
const activity = context.activity
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
} else {
|
|
106
|
+
|
|
107
|
+
if (!activity.channelId || !activity.from || !activity.from.id) {
|
|
83
108
|
throw new Error('UserTokenService requires channelId and from to be set')
|
|
84
109
|
}
|
|
110
|
+
|
|
111
|
+
const cacheKey = this.getCacheKey(context)
|
|
112
|
+
|
|
113
|
+
const cachedEntry = this.tokenCache.get(cacheKey)
|
|
114
|
+
if (cachedEntry && Date.now() < cachedEntry.expiresAt) {
|
|
115
|
+
logger.info(`Returning cached token for user with cache key: ${cacheKey}`)
|
|
116
|
+
return cachedEntry.token
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
logger.info('Get token from user token service')
|
|
120
|
+
const tokenResponse = await this.userTokenClient.getUserToken(this.absOauthConnectionName, activity.channelId, activity.from.id)
|
|
121
|
+
|
|
122
|
+
// Cache the token if it's valid (has a token value)
|
|
123
|
+
if (tokenResponse && tokenResponse.token) {
|
|
124
|
+
const cacheExpiry = Date.now() + (10 * 60 * 1000) // 10 minutes from now
|
|
125
|
+
this.tokenCache.set(cacheKey, {
|
|
126
|
+
token: tokenResponse,
|
|
127
|
+
expiresAt: cacheExpiry
|
|
128
|
+
})
|
|
129
|
+
logger.info('Token cached for 10 minutes')
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return tokenResponse
|
|
85
133
|
}
|
|
86
134
|
|
|
87
135
|
/**
|
|
88
136
|
* Begins the OAuth flow.
|
|
89
137
|
* @param context The turn context.
|
|
90
|
-
* @returns A promise that resolves to the user token.
|
|
138
|
+
* @returns A promise that resolves to the user token if available, or undefined if OAuth flow needs to be started.
|
|
91
139
|
*/
|
|
92
140
|
public async beginFlow (context: TurnContext): Promise<TokenResponse | undefined> {
|
|
93
|
-
this.state = await this.
|
|
141
|
+
this.state = await this.getFlowState(context)
|
|
94
142
|
if (this.absOauthConnectionName === '') {
|
|
95
143
|
throw new Error('connectionName is not set')
|
|
96
144
|
}
|
|
@@ -98,40 +146,87 @@ export class OAuthFlow {
|
|
|
98
146
|
await this.initializeTokenClient(context)
|
|
99
147
|
|
|
100
148
|
const act = context.activity
|
|
149
|
+
|
|
150
|
+
// Check cache first before starting OAuth flow
|
|
151
|
+
if (act.channelId && act.from && act.from.id) {
|
|
152
|
+
const cacheKey = this.getCacheKey(context)
|
|
153
|
+
const cachedEntry = this.tokenCache.get(cacheKey)
|
|
154
|
+
if (cachedEntry && Date.now() < cachedEntry.expiresAt) {
|
|
155
|
+
logger.info(`Returning cached token for user in beginFlow with cache key: ${cacheKey}`)
|
|
156
|
+
return cachedEntry.token
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
101
160
|
const output = await this.userTokenClient.getTokenOrSignInResource(act.from?.id!, this.absOauthConnectionName, act.channelId!, act.getConversationReference(), act.relatesTo!, undefined!)
|
|
102
161
|
if (output && output.tokenResponse) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
162
|
+
// Cache the token if it's valid
|
|
163
|
+
if (act.channelId && act.from && act.from.id) {
|
|
164
|
+
const cacheKey = this.getCacheKey(context)
|
|
165
|
+
const cacheExpiry = Date.now() + (10 * 60 * 1000) // 10 minutes from now
|
|
166
|
+
this.tokenCache.set(cacheKey, {
|
|
167
|
+
token: output.tokenResponse,
|
|
168
|
+
expiresAt: cacheExpiry
|
|
169
|
+
})
|
|
170
|
+
logger.info('Token cached for 10 minutes in beginFlow')
|
|
171
|
+
this.state = { flowStarted: false, flowExpires: 0, absOauthConnectionName: this.absOauthConnectionName }
|
|
172
|
+
}
|
|
173
|
+
logger.info('Token retrieved successfully')
|
|
106
174
|
return output.tokenResponse
|
|
107
175
|
}
|
|
108
176
|
const oCard: Attachment = CardFactory.oauthCard(this.absOauthConnectionName, this.cardTitle, this.cardText, output.signInResource)
|
|
109
177
|
await context.sendActivity(MessageFactory.attachment(oCard))
|
|
110
|
-
this.state
|
|
111
|
-
this.state.flowExpires = Date.now() + 30000
|
|
178
|
+
this.state = { flowStarted: true, flowExpires: Date.now() + 60 * 5 * 1000, absOauthConnectionName: this.absOauthConnectionName }
|
|
112
179
|
await this.storage.write({ [this.getFlowStateKey(context)]: this.state })
|
|
180
|
+
logger.info('OAuth card sent, flow started')
|
|
113
181
|
return undefined
|
|
114
182
|
}
|
|
115
183
|
|
|
116
184
|
/**
|
|
117
185
|
* Continues the OAuth flow.
|
|
118
186
|
* @param context The turn context.
|
|
119
|
-
* @returns A promise that resolves to the user token.
|
|
187
|
+
* @returns A promise that resolves to the user token response.
|
|
120
188
|
*/
|
|
121
189
|
public async continueFlow (context: TurnContext): Promise<TokenResponse> {
|
|
122
|
-
this.state = await this.
|
|
190
|
+
this.state = await this.getFlowState(context)
|
|
123
191
|
await this.initializeTokenClient(context)
|
|
124
|
-
if (this.state?.flowExpires !== 0 && Date.now() > this.state
|
|
192
|
+
if (this.state?.flowExpires !== 0 && Date.now() > this.state?.flowExpires!) {
|
|
125
193
|
logger.warn('Flow expired')
|
|
126
|
-
this.state!.flowStarted = false
|
|
127
194
|
await context.sendActivity(MessageFactory.text('Sign-in session expired. Please try again.'))
|
|
195
|
+
this.state!.flowStarted = false
|
|
128
196
|
return { token: undefined }
|
|
129
197
|
}
|
|
130
198
|
const contFlowActivity = context.activity
|
|
131
199
|
if (contFlowActivity.type === ActivityTypes.Message) {
|
|
132
200
|
const magicCode = contFlowActivity.text as string
|
|
133
|
-
|
|
134
|
-
|
|
201
|
+
if (magicCode.match(/^\d{6}$/)) {
|
|
202
|
+
const result = await this.userTokenClient?.getUserToken(this.absOauthConnectionName, contFlowActivity.channelId!, contFlowActivity.from?.id!, magicCode)!
|
|
203
|
+
if (result && result.token) {
|
|
204
|
+
// Cache the token if it's valid
|
|
205
|
+
if (contFlowActivity.channelId && contFlowActivity.from && contFlowActivity.from.id) {
|
|
206
|
+
const cacheKey = this.getCacheKey(context)
|
|
207
|
+
const cacheExpiry = Date.now() + (10 * 60 * 1000) // 10 minutes from now
|
|
208
|
+
this.tokenCache.set(cacheKey, {
|
|
209
|
+
token: result,
|
|
210
|
+
expiresAt: cacheExpiry
|
|
211
|
+
})
|
|
212
|
+
logger.info('Token cached for 10 minutes in continueFlow (magic code)')
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
await this.storage.delete([this.getFlowStateKey(context)])
|
|
216
|
+
logger.info('Token retrieved successfully')
|
|
217
|
+
return result
|
|
218
|
+
} else {
|
|
219
|
+
// await context.sendActivity(MessageFactory.text('Invalid code. Please try again.'))
|
|
220
|
+
logger.warn('Invalid magic code provided')
|
|
221
|
+
this.state = { flowStarted: true, flowExpires: Date.now() + 30000, absOauthConnectionName: this.absOauthConnectionName }
|
|
222
|
+
await this.storage.write({ [this.getFlowStateKey(context)]: this.state })
|
|
223
|
+
return { token: undefined }
|
|
224
|
+
}
|
|
225
|
+
} else {
|
|
226
|
+
logger.warn('Invalid magic code format')
|
|
227
|
+
await context.sendActivity(MessageFactory.text('Invalid code format. Please enter a 6-digit code.'))
|
|
228
|
+
return { token: undefined }
|
|
229
|
+
}
|
|
135
230
|
}
|
|
136
231
|
|
|
137
232
|
if (contFlowActivity.type === ActivityTypes.Invoke && contFlowActivity.name === 'signin/verifyState') {
|
|
@@ -139,6 +234,16 @@ export class OAuthFlow {
|
|
|
139
234
|
const tokenVerifyState = contFlowActivity.value as TokenVerifyState
|
|
140
235
|
const magicCode = tokenVerifyState.state
|
|
141
236
|
const result = await this.userTokenClient?.getUserToken(this.absOauthConnectionName, contFlowActivity.channelId!, contFlowActivity.from?.id!, magicCode)!
|
|
237
|
+
// Cache the token if it's valid
|
|
238
|
+
if (result && result.token && contFlowActivity.channelId && contFlowActivity.from && contFlowActivity.from.id) {
|
|
239
|
+
const cacheKey = this.getCacheKey(context)
|
|
240
|
+
const cacheExpiry = Date.now() + (10 * 60 * 1000) // 10 minutes from now
|
|
241
|
+
this.tokenCache.set(cacheKey, {
|
|
242
|
+
token: result,
|
|
243
|
+
expiresAt: cacheExpiry
|
|
244
|
+
})
|
|
245
|
+
logger.info('Token cached for 10 minutes in continueFlow (verifyState)')
|
|
246
|
+
}
|
|
142
247
|
return result
|
|
143
248
|
}
|
|
144
249
|
|
|
@@ -146,11 +251,23 @@ export class OAuthFlow {
|
|
|
146
251
|
logger.info('Continuing OAuth flow with tokenExchange')
|
|
147
252
|
const tokenExchangeRequest = contFlowActivity.value as TokenExchangeRequest
|
|
148
253
|
if (this.tokenExchangeId === tokenExchangeRequest.id) { // dedupe
|
|
254
|
+
logger.debug('Token exchange request already processed, skipping')
|
|
149
255
|
return { token: undefined }
|
|
150
256
|
}
|
|
151
257
|
this.tokenExchangeId = tokenExchangeRequest.id!
|
|
152
258
|
const userTokenResp = await this.userTokenClient?.exchangeTokenAsync(contFlowActivity.from?.id!, this.absOauthConnectionName, contFlowActivity.channelId!, tokenExchangeRequest)
|
|
153
259
|
if (userTokenResp && userTokenResp.token) {
|
|
260
|
+
// Cache the token if it's valid
|
|
261
|
+
if (contFlowActivity.channelId && contFlowActivity.from && contFlowActivity.from.id) {
|
|
262
|
+
const cacheKey = this.getCacheKey(context)
|
|
263
|
+
const cacheExpiry = Date.now() + (10 * 60 * 1000) // 10 minutes from now
|
|
264
|
+
this.tokenCache.set(cacheKey, {
|
|
265
|
+
token: userTokenResp,
|
|
266
|
+
expiresAt: cacheExpiry
|
|
267
|
+
})
|
|
268
|
+
logger.info('Token cached for 10 minutes in continueFlow (tokenExchange)')
|
|
269
|
+
}
|
|
270
|
+
|
|
154
271
|
logger.info('Token exchanged')
|
|
155
272
|
this.state!.flowStarted = false
|
|
156
273
|
await this.storage.write({ [this.getFlowStateKey(context)]: this.state })
|
|
@@ -170,34 +287,79 @@ export class OAuthFlow {
|
|
|
170
287
|
* @returns A promise that resolves when the sign-out operation is complete.
|
|
171
288
|
*/
|
|
172
289
|
public async signOut (context: TurnContext): Promise<void> {
|
|
173
|
-
this.state = await this.getUserState(context)
|
|
174
290
|
await this.initializeTokenClient(context)
|
|
291
|
+
|
|
292
|
+
// Clear cached token for this user
|
|
293
|
+
const activity = context.activity
|
|
294
|
+
if (activity.channelId && activity.from && activity.from.id) {
|
|
295
|
+
const cacheKey = this.getCacheKey(context)
|
|
296
|
+
this.tokenCache.delete(cacheKey)
|
|
297
|
+
logger.info('Cached token cleared for user')
|
|
298
|
+
}
|
|
299
|
+
|
|
175
300
|
await this.userTokenClient?.signOut(context.activity.from?.id as string, this.absOauthConnectionName, context.activity.channelId as string)
|
|
176
|
-
this.state
|
|
177
|
-
this.storage.
|
|
178
|
-
logger.info('User signed out successfully')
|
|
301
|
+
this.state = { flowStarted: false, flowExpires: 0, absOauthConnectionName: this.absOauthConnectionName }
|
|
302
|
+
await this.storage.delete([this.getFlowStateKey(context)])
|
|
303
|
+
logger.info('User signed out successfully from connection:', this.absOauthConnectionName)
|
|
179
304
|
}
|
|
180
305
|
|
|
181
306
|
/**
|
|
182
|
-
* Gets the user state.
|
|
307
|
+
* Gets the user state for the OAuth flow.
|
|
183
308
|
* @param context The turn context.
|
|
184
|
-
* @returns A promise that resolves to the
|
|
309
|
+
* @returns A promise that resolves to the flow state.
|
|
185
310
|
*/
|
|
186
|
-
|
|
311
|
+
public async getFlowState (context: TurnContext) : Promise<FlowState> {
|
|
187
312
|
const key = this.getFlowStateKey(context)
|
|
188
313
|
const data = await this.storage.read([key])
|
|
189
|
-
const
|
|
190
|
-
return
|
|
314
|
+
const flowState: FlowState = data[key] // ?? { flowStarted: false, flowExpires: 0 }
|
|
315
|
+
return flowState
|
|
191
316
|
}
|
|
192
317
|
|
|
318
|
+
/**
|
|
319
|
+
* Sets the flow state for the OAuth flow.
|
|
320
|
+
* @param context The turn context.
|
|
321
|
+
* @param flowState The flow state to set.
|
|
322
|
+
* @returns A promise that resolves when the flow state is set.
|
|
323
|
+
*/
|
|
324
|
+
public async setFlowState (context: TurnContext, flowState: FlowState) : Promise<void> {
|
|
325
|
+
const key = this.getFlowStateKey(context)
|
|
326
|
+
await this.storage.write({ [key]: flowState })
|
|
327
|
+
this.state = flowState
|
|
328
|
+
logger.info('Flow state set:', flowState)
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Initializes the user token client if not already initialized.
|
|
333
|
+
* @param context The turn context used to get authentication credentials.
|
|
334
|
+
*/
|
|
193
335
|
private async initializeTokenClient (context: TurnContext) {
|
|
194
336
|
if (this.userTokenClient === undefined || this.userTokenClient === null) {
|
|
195
337
|
const scope = 'https://api.botframework.com'
|
|
196
338
|
const accessToken = await context.adapter.authProvider.getAccessToken(context.adapter.authConfig, scope)
|
|
197
|
-
this.userTokenClient = new UserTokenClient(accessToken, context.adapter.authConfig
|
|
339
|
+
this.userTokenClient = new UserTokenClient(accessToken, context.adapter.authConfig!.clientId!)
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Generates a cache key for storing user tokens.
|
|
345
|
+
* @param context The turn context containing activity information.
|
|
346
|
+
* @returns The cache key string in format: channelId_userId_connectionName.
|
|
347
|
+
* @throws Will throw an error if required activity properties are missing.
|
|
348
|
+
*/
|
|
349
|
+
private getCacheKey (context: TurnContext): string {
|
|
350
|
+
const activity = context.activity
|
|
351
|
+
if (!activity.channelId || !activity.from || !activity.from.id) {
|
|
352
|
+
throw new Error('ChannelId and from.id must be set in the activity for cache key generation')
|
|
198
353
|
}
|
|
354
|
+
return `${activity.channelId}_${activity.from.id}_${this.absOauthConnectionName}`
|
|
199
355
|
}
|
|
200
356
|
|
|
357
|
+
/**
|
|
358
|
+
* Generates a storage key for persisting OAuth flow state.
|
|
359
|
+
* @param context The turn context containing activity information.
|
|
360
|
+
* @returns The storage key string in format: oauth/channelId/conversationId/userId/flowState.
|
|
361
|
+
* @throws Will throw an error if required activity properties are missing.
|
|
362
|
+
*/
|
|
201
363
|
private getFlowStateKey (context: TurnContext): string {
|
|
202
364
|
const channelId = context.activity.channelId
|
|
203
365
|
const conversationId = context.activity.conversation?.id
|
|
@@ -205,6 +367,6 @@ export class OAuthFlow {
|
|
|
205
367
|
if (!channelId || !conversationId || !userId) {
|
|
206
368
|
throw new Error('ChannelId, conversationId, and userId must be set in the activity')
|
|
207
369
|
}
|
|
208
|
-
return `oauth/${channelId}/${
|
|
370
|
+
return `oauth/${channelId}/${userId}/${this.absOauthConnectionName}/flowState`
|
|
209
371
|
}
|
|
210
372
|
}
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
-
* Licensed under the MIT License.
|
|
4
|
-
*/
|
|
5
|
-
import { TurnContext } from '../../turnContext';
|
|
6
|
-
import { TurnState } from '../turnState';
|
|
7
|
-
import { Storage } from '../../storage';
|
|
8
|
-
import { OAuthFlow, TokenResponse } from '../../oauth';
|
|
9
|
-
/**
|
|
10
|
-
* Interface defining an authorization handler for OAuth flows
|
|
11
|
-
* @interface AuthHandler
|
|
12
|
-
*/
|
|
13
|
-
export interface AuthHandler {
|
|
14
|
-
/** Connection name for the auth provider */
|
|
15
|
-
name?: string;
|
|
16
|
-
/** Whether authorization should be triggered automatically */
|
|
17
|
-
auto?: boolean;
|
|
18
|
-
/** The OAuth flow implementation */
|
|
19
|
-
flow?: OAuthFlow;
|
|
20
|
-
/** Title to display on auth cards/UI */
|
|
21
|
-
title?: string;
|
|
22
|
-
/** Text to display on auth cards/UI */
|
|
23
|
-
text?: string;
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Options for configuring user authorization.
|
|
27
|
-
* Contains settings to configure OAuth connections.
|
|
28
|
-
*/
|
|
29
|
-
export interface AuthorizationHandlers extends Record<string, AuthHandler> {
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Class responsible for managing authorization and OAuth flows
|
|
33
|
-
* @class Authorization
|
|
34
|
-
*/
|
|
35
|
-
export declare class Authorization {
|
|
36
|
-
private storage;
|
|
37
|
-
_authHandlers: AuthorizationHandlers;
|
|
38
|
-
/**
|
|
39
|
-
* Creates a new instance of UserAuthorization.
|
|
40
|
-
* @param {Storage} storage - The storage system to use for state management.
|
|
41
|
-
* @param {AuthorizationHandlers} authHandlers - Configuration for OAuth providers
|
|
42
|
-
* @throws {Error} If storage is null/undefined or no auth handlers are provided
|
|
43
|
-
*/
|
|
44
|
-
constructor(storage: Storage, authHandlers: AuthorizationHandlers);
|
|
45
|
-
/**
|
|
46
|
-
* Gets the token for a specific auth handler
|
|
47
|
-
* @param {TurnContext} context - The context object for the current turn
|
|
48
|
-
* @param {string} [authHandlerId] - Optional ID of the auth handler to use, defaults to first handler
|
|
49
|
-
* @returns {Promise<TokenResponse>} The token response from the OAuth provider
|
|
50
|
-
*/
|
|
51
|
-
getToken(context: TurnContext, authHandlerId?: string): Promise<TokenResponse>;
|
|
52
|
-
/**
|
|
53
|
-
* Begins or continues an OAuth flow
|
|
54
|
-
* @param {TurnContext} context - The context object for the current turn
|
|
55
|
-
* @param {TurnState} state - The state object for the current turn
|
|
56
|
-
* @param {string} [authHandlerId] - Optional ID of the auth handler to use, defaults to first handler
|
|
57
|
-
* @returns {Promise<TokenResponse>} The token response from the OAuth provider
|
|
58
|
-
*/
|
|
59
|
-
beginOrContinueFlow(context: TurnContext, state: TurnState, authHandlerId?: string): Promise<TokenResponse>;
|
|
60
|
-
/**
|
|
61
|
-
* Gets the current state of the OAuth flow
|
|
62
|
-
* @param {string} [authHandlerId] - Optional ID of the auth handler to check, defaults to first handler
|
|
63
|
-
* @returns {boolean} Whether the flow has started
|
|
64
|
-
*/
|
|
65
|
-
getFlowState(authHandlerId?: string): boolean;
|
|
66
|
-
/**
|
|
67
|
-
* Resolves the auth handler to use based on the provided ID
|
|
68
|
-
* @param {string} [authHandlerId] - Optional ID of the auth handler to resolve, defaults to first handler
|
|
69
|
-
* @returns {AuthHandler} The resolved auth handler
|
|
70
|
-
*/
|
|
71
|
-
resolverHandler: (authHandlerId?: string) => AuthHandler;
|
|
72
|
-
/**
|
|
73
|
-
* Signs out the current user.
|
|
74
|
-
* This method clears the user's token and resets the SSO state.
|
|
75
|
-
*
|
|
76
|
-
* @param {TurnContext} context - The context object for the current turn.
|
|
77
|
-
* @param {TurnState} state - The state object for the current turn.
|
|
78
|
-
* @param {string} [authHandlerId] - Optional ID of the auth handler to use for sign out
|
|
79
|
-
* @returns {Promise<void>}
|
|
80
|
-
*/
|
|
81
|
-
signOut(context: TurnContext, state: TurnState, authHandlerId?: string): Promise<void>;
|
|
82
|
-
_signInHandler: ((context: TurnContext, state: TurnState, authHandlerId?: string) => void) | null;
|
|
83
|
-
/**
|
|
84
|
-
* Sets a handler to be called when sign-in is successfully completed
|
|
85
|
-
* @param {Function} handler - The handler function to call on successful sign-in
|
|
86
|
-
*/
|
|
87
|
-
onSignInSuccess(handler: (context: TurnContext, state: TurnState, authHandlerId?: string) => void): void;
|
|
88
|
-
}
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
4
|
-
* Licensed under the MIT License.
|
|
5
|
-
*/
|
|
6
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.Authorization = void 0;
|
|
8
|
-
const logger_1 = require("../../logger");
|
|
9
|
-
const oauth_1 = require("../../oauth");
|
|
10
|
-
const logger = (0, logger_1.debug)('agents:authorization');
|
|
11
|
-
/**
|
|
12
|
-
* Class responsible for managing authorization and OAuth flows
|
|
13
|
-
* @class Authorization
|
|
14
|
-
*/
|
|
15
|
-
class Authorization {
|
|
16
|
-
/**
|
|
17
|
-
* Creates a new instance of UserAuthorization.
|
|
18
|
-
* @param {Storage} storage - The storage system to use for state management.
|
|
19
|
-
* @param {AuthorizationHandlers} authHandlers - Configuration for OAuth providers
|
|
20
|
-
* @throws {Error} If storage is null/undefined or no auth handlers are provided
|
|
21
|
-
*/
|
|
22
|
-
constructor(storage, authHandlers) {
|
|
23
|
-
var _a, _b, _c, _d;
|
|
24
|
-
this.storage = storage;
|
|
25
|
-
/**
|
|
26
|
-
* Resolves the auth handler to use based on the provided ID
|
|
27
|
-
* @param {string} [authHandlerId] - Optional ID of the auth handler to resolve, defaults to first handler
|
|
28
|
-
* @returns {AuthHandler} The resolved auth handler
|
|
29
|
-
*/
|
|
30
|
-
this.resolverHandler = (authHandlerId) => {
|
|
31
|
-
if (authHandlerId) {
|
|
32
|
-
return this._authHandlers[authHandlerId];
|
|
33
|
-
}
|
|
34
|
-
return this._authHandlers[Object.keys(this._authHandlers)[0]];
|
|
35
|
-
};
|
|
36
|
-
this._signInHandler = null;
|
|
37
|
-
if (storage === undefined || storage === null) {
|
|
38
|
-
throw new Error('Storage is required for UserAuthorization');
|
|
39
|
-
}
|
|
40
|
-
if (authHandlers === undefined || Object.keys(authHandlers).length === 0) {
|
|
41
|
-
throw new Error('The authorization does not have any auth handlers');
|
|
42
|
-
}
|
|
43
|
-
this._authHandlers = authHandlers;
|
|
44
|
-
for (const ah in this._authHandlers) {
|
|
45
|
-
if (this._authHandlers[ah].name === undefined && process.env[ah + '_connectionName'] === undefined) {
|
|
46
|
-
throw new Error(`AuthHandler name ${ah}_connectionName not set in autorization and not found in env vars.`);
|
|
47
|
-
}
|
|
48
|
-
const currentAuthHandler = this._authHandlers[ah];
|
|
49
|
-
currentAuthHandler.name = (_a = currentAuthHandler.name) !== null && _a !== void 0 ? _a : process.env[ah + '_connectionName'];
|
|
50
|
-
currentAuthHandler.title = (_b = currentAuthHandler.title) !== null && _b !== void 0 ? _b : process.env[ah + '_connectionTitle'];
|
|
51
|
-
currentAuthHandler.text = (_c = currentAuthHandler.text) !== null && _c !== void 0 ? _c : process.env[ah + '_connectionText'];
|
|
52
|
-
currentAuthHandler.auto = (_d = currentAuthHandler.auto) !== null && _d !== void 0 ? _d : process.env[ah + '_connectionAuto'] === 'true';
|
|
53
|
-
currentAuthHandler.flow = new oauth_1.OAuthFlow(this.storage, currentAuthHandler.name, null, currentAuthHandler.title, currentAuthHandler.text);
|
|
54
|
-
}
|
|
55
|
-
logger.info('Authorization handlers configured with', this._authHandlers.length, 'handlers');
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* Gets the token for a specific auth handler
|
|
59
|
-
* @param {TurnContext} context - The context object for the current turn
|
|
60
|
-
* @param {string} [authHandlerId] - Optional ID of the auth handler to use, defaults to first handler
|
|
61
|
-
* @returns {Promise<TokenResponse>} The token response from the OAuth provider
|
|
62
|
-
*/
|
|
63
|
-
async getToken(context, authHandlerId) {
|
|
64
|
-
var _a;
|
|
65
|
-
logger.info('getToken from user token service for authHandlerId:', authHandlerId);
|
|
66
|
-
const authHandler = this.resolverHandler(authHandlerId);
|
|
67
|
-
return await ((_a = authHandler.flow) === null || _a === void 0 ? void 0 : _a.getUserToken(context));
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* Begins or continues an OAuth flow
|
|
71
|
-
* @param {TurnContext} context - The context object for the current turn
|
|
72
|
-
* @param {TurnState} state - The state object for the current turn
|
|
73
|
-
* @param {string} [authHandlerId] - Optional ID of the auth handler to use, defaults to first handler
|
|
74
|
-
* @returns {Promise<TokenResponse>} The token response from the OAuth provider
|
|
75
|
-
*/
|
|
76
|
-
async beginOrContinueFlow(context, state, authHandlerId) {
|
|
77
|
-
logger.info('beginOrContinueFlow for authHandlerId:', authHandlerId);
|
|
78
|
-
const flow = this.resolverHandler(authHandlerId).flow;
|
|
79
|
-
let tokenResponse;
|
|
80
|
-
if (flow.state.flowStarted === false) {
|
|
81
|
-
tokenResponse = await flow.beginFlow(context);
|
|
82
|
-
}
|
|
83
|
-
else {
|
|
84
|
-
tokenResponse = await flow.continueFlow(context);
|
|
85
|
-
if (tokenResponse && tokenResponse.token) {
|
|
86
|
-
if (this._signInHandler) {
|
|
87
|
-
await this._signInHandler(context, state, authHandlerId);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
return tokenResponse;
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* Gets the current state of the OAuth flow
|
|
95
|
-
* @param {string} [authHandlerId] - Optional ID of the auth handler to check, defaults to first handler
|
|
96
|
-
* @returns {boolean} Whether the flow has started
|
|
97
|
-
*/
|
|
98
|
-
getFlowState(authHandlerId) {
|
|
99
|
-
var _a;
|
|
100
|
-
const flow = this.resolverHandler(authHandlerId).flow;
|
|
101
|
-
return (_a = flow.state) === null || _a === void 0 ? void 0 : _a.flowStarted;
|
|
102
|
-
}
|
|
103
|
-
/**
|
|
104
|
-
* Signs out the current user.
|
|
105
|
-
* This method clears the user's token and resets the SSO state.
|
|
106
|
-
*
|
|
107
|
-
* @param {TurnContext} context - The context object for the current turn.
|
|
108
|
-
* @param {TurnState} state - The state object for the current turn.
|
|
109
|
-
* @param {string} [authHandlerId] - Optional ID of the auth handler to use for sign out
|
|
110
|
-
* @returns {Promise<void>}
|
|
111
|
-
*/
|
|
112
|
-
async signOut(context, state, authHandlerId) {
|
|
113
|
-
var _a;
|
|
114
|
-
logger.info('signOut for authHandlerId:', authHandlerId);
|
|
115
|
-
if (authHandlerId === undefined) { // aw
|
|
116
|
-
for (const ah in this._authHandlers) {
|
|
117
|
-
const flow = this._authHandlers[ah].flow;
|
|
118
|
-
await (flow === null || flow === void 0 ? void 0 : flow.signOut(context));
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
else {
|
|
122
|
-
await ((_a = this.resolverHandler(authHandlerId).flow) === null || _a === void 0 ? void 0 : _a.signOut(context));
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
/**
|
|
126
|
-
* Sets a handler to be called when sign-in is successfully completed
|
|
127
|
-
* @param {Function} handler - The handler function to call on successful sign-in
|
|
128
|
-
*/
|
|
129
|
-
onSignInSuccess(handler) {
|
|
130
|
-
this._signInHandler = handler;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
exports.Authorization = Authorization;
|
|
134
|
-
//# sourceMappingURL=authorization.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"authorization.js","sourceRoot":"","sources":["../../../../src/app/oauth/authorization.ts"],"names":[],"mappings":"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"}
|