@microsoft/agents-hosting 1.1.0-alpha.5 → 1.1.0-alpha.58

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 (139) hide show
  1. package/dist/package.json +10 -6
  2. package/dist/src/activityWireCompat.js +8 -3
  3. package/dist/src/activityWireCompat.js.map +1 -1
  4. package/dist/src/agent-client/agentClient.js +7 -3
  5. package/dist/src/agent-client/agentClient.js.map +1 -1
  6. package/dist/src/agent-client/agentResponseHandler.js +6 -2
  7. package/dist/src/agent-client/agentResponseHandler.js.map +1 -1
  8. package/dist/src/app/agentApplication.d.ts +26 -11
  9. package/dist/src/app/agentApplication.js +93 -82
  10. package/dist/src/app/agentApplication.js.map +1 -1
  11. package/dist/src/app/agentApplicationBuilder.d.ts +2 -2
  12. package/dist/src/app/agentApplicationBuilder.js.map +1 -1
  13. package/dist/src/app/agentApplicationOptions.d.ts +9 -2
  14. package/dist/src/app/appRoute.d.ts +7 -0
  15. package/dist/src/app/{authorization.d.ts → auth/authorization.d.ts} +33 -139
  16. package/dist/src/app/auth/authorization.js +188 -0
  17. package/dist/src/app/auth/authorization.js.map +1 -0
  18. package/dist/src/app/auth/authorizationManager.d.ts +71 -0
  19. package/dist/src/app/auth/authorizationManager.js +170 -0
  20. package/dist/src/app/auth/authorizationManager.js.map +1 -0
  21. package/dist/src/app/auth/handlerStorage.d.ts +36 -0
  22. package/dist/src/app/auth/handlerStorage.js +62 -0
  23. package/dist/src/app/auth/handlerStorage.js.map +1 -0
  24. package/dist/src/app/auth/handlers/agenticAuthorization.d.ts +97 -0
  25. package/dist/src/app/auth/handlers/agenticAuthorization.js +145 -0
  26. package/dist/src/app/auth/handlers/agenticAuthorization.js.map +1 -0
  27. package/dist/src/app/auth/handlers/azureBotAuthorization.d.ts +222 -0
  28. package/dist/src/app/auth/handlers/azureBotAuthorization.js +428 -0
  29. package/dist/src/app/auth/handlers/azureBotAuthorization.js.map +1 -0
  30. package/dist/src/app/auth/handlers/index.d.ts +2 -0
  31. package/dist/src/app/auth/handlers/index.js +19 -0
  32. package/dist/src/app/auth/handlers/index.js.map +1 -0
  33. package/dist/src/app/auth/index.d.ts +2 -0
  34. package/dist/src/app/auth/index.js +19 -0
  35. package/dist/src/app/auth/index.js.map +1 -0
  36. package/dist/src/app/auth/types.d.ts +104 -0
  37. package/dist/src/app/auth/types.js +24 -0
  38. package/dist/src/app/auth/types.js.map +1 -0
  39. package/dist/src/app/index.d.ts +2 -3
  40. package/dist/src/app/index.js +2 -3
  41. package/dist/src/app/index.js.map +1 -1
  42. package/dist/src/app/routeList.d.ts +1 -1
  43. package/dist/src/app/routeList.js +22 -5
  44. package/dist/src/app/routeList.js.map +1 -1
  45. package/dist/src/app/streaming/streamingResponse.js +2 -1
  46. package/dist/src/app/streaming/streamingResponse.js.map +1 -1
  47. package/dist/src/auth/MemoryCache.d.ts +16 -0
  48. package/dist/src/auth/MemoryCache.js +58 -0
  49. package/dist/src/auth/MemoryCache.js.map +1 -0
  50. package/dist/src/auth/authConfiguration.d.ts +44 -2
  51. package/dist/src/auth/authConfiguration.js +218 -53
  52. package/dist/src/auth/authConfiguration.js.map +1 -1
  53. package/dist/src/auth/authConstants.d.ts +11 -0
  54. package/dist/src/auth/authConstants.js +15 -0
  55. package/dist/src/auth/authConstants.js.map +1 -0
  56. package/dist/src/auth/authProvider.d.ts +23 -0
  57. package/dist/src/auth/connections.d.ts +40 -0
  58. package/dist/src/auth/connections.js +7 -0
  59. package/dist/src/auth/connections.js.map +1 -0
  60. package/dist/src/auth/index.d.ts +2 -0
  61. package/dist/src/auth/index.js +2 -0
  62. package/dist/src/auth/index.js.map +1 -1
  63. package/dist/src/auth/jwt-middleware.js +31 -18
  64. package/dist/src/auth/jwt-middleware.js.map +1 -1
  65. package/dist/src/auth/msalConnectionManager.d.ts +63 -0
  66. package/dist/src/auth/msalConnectionManager.js +124 -0
  67. package/dist/src/auth/msalConnectionManager.js.map +1 -0
  68. package/dist/src/auth/msalTokenProvider.d.ts +31 -0
  69. package/dist/src/auth/msalTokenProvider.js +167 -16
  70. package/dist/src/auth/msalTokenProvider.js.map +1 -1
  71. package/dist/src/baseAdapter.d.ts +10 -25
  72. package/dist/src/baseAdapter.js +2 -15
  73. package/dist/src/baseAdapter.js.map +1 -1
  74. package/dist/src/cloudAdapter.d.ts +40 -23
  75. package/dist/src/cloudAdapter.js +132 -56
  76. package/dist/src/cloudAdapter.js.map +1 -1
  77. package/dist/src/connector-client/connectorClient.d.ts +9 -0
  78. package/dist/src/connector-client/connectorClient.js +39 -9
  79. package/dist/src/connector-client/connectorClient.js.map +1 -1
  80. package/dist/src/index.d.ts +0 -1
  81. package/dist/src/index.js +0 -1
  82. package/dist/src/index.js.map +1 -1
  83. package/dist/src/oauth/index.d.ts +0 -1
  84. package/dist/src/oauth/index.js +0 -1
  85. package/dist/src/oauth/index.js.map +1 -1
  86. package/dist/src/oauth/userTokenClient.d.ts +30 -13
  87. package/dist/src/oauth/userTokenClient.js +64 -26
  88. package/dist/src/oauth/userTokenClient.js.map +1 -1
  89. package/dist/src/oauth/userTokenClient.types.d.ts +19 -6
  90. package/dist/src/turnContext.d.ts +7 -1
  91. package/dist/src/turnContext.js +11 -4
  92. package/dist/src/turnContext.js.map +1 -1
  93. package/package.json +10 -6
  94. package/src/activityWireCompat.ts +8 -3
  95. package/src/agent-client/agentClient.ts +9 -3
  96. package/src/agent-client/agentResponseHandler.ts +5 -2
  97. package/src/app/agentApplication.ts +97 -75
  98. package/src/app/agentApplicationBuilder.ts +2 -2
  99. package/src/app/agentApplicationOptions.ts +10 -2
  100. package/src/app/appRoute.ts +8 -0
  101. package/src/app/auth/authorization.ts +252 -0
  102. package/src/app/auth/authorizationManager.ts +213 -0
  103. package/src/app/auth/handlerStorage.ts +61 -0
  104. package/src/app/auth/handlers/agenticAuthorization.ts +194 -0
  105. package/src/app/auth/handlers/azureBotAuthorization.ts +599 -0
  106. package/src/app/auth/handlers/index.ts +2 -0
  107. package/src/app/auth/index.ts +2 -0
  108. package/src/app/auth/types.ts +111 -0
  109. package/src/app/index.ts +2 -3
  110. package/src/app/routeList.ts +24 -5
  111. package/src/app/streaming/streamingResponse.ts +2 -1
  112. package/src/auth/MemoryCache.ts +59 -0
  113. package/src/auth/authConfiguration.ts +258 -52
  114. package/src/auth/authConstants.ts +11 -0
  115. package/src/auth/authProvider.ts +31 -0
  116. package/src/auth/connections.ts +46 -0
  117. package/src/auth/index.ts +2 -0
  118. package/src/auth/jwt-middleware.ts +38 -21
  119. package/src/auth/msalConnectionManager.ts +150 -0
  120. package/src/auth/msalTokenProvider.ts +209 -9
  121. package/src/baseAdapter.ts +10 -29
  122. package/src/cloudAdapter.ts +192 -67
  123. package/src/connector-client/connectorClient.ts +49 -10
  124. package/src/index.ts +0 -1
  125. package/src/oauth/index.ts +0 -1
  126. package/src/oauth/userTokenClient.ts +79 -23
  127. package/src/oauth/userTokenClient.types.ts +20 -8
  128. package/src/turnContext.ts +16 -5
  129. package/dist/src/app/authorization.js +0 -387
  130. package/dist/src/app/authorization.js.map +0 -1
  131. package/dist/src/claimsIdentity.d.ts +0 -35
  132. package/dist/src/claimsIdentity.js +0 -43
  133. package/dist/src/claimsIdentity.js.map +0 -1
  134. package/dist/src/oauth/oAuthFlow.d.ts +0 -119
  135. package/dist/src/oauth/oAuthFlow.js +0 -316
  136. package/dist/src/oauth/oAuthFlow.js.map +0 -1
  137. package/src/app/authorization.ts +0 -432
  138. package/src/claimsIdentity.ts +0 -47
  139. package/src/oauth/oAuthFlow.ts +0 -378
@@ -0,0 +1,252 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { debug } from '@microsoft/agents-activity/logger'
7
+ import { TokenResponse } from '../../oauth'
8
+ import { TurnContext } from '../../turnContext'
9
+ import { TurnState } from '../turnState'
10
+ import { AuthorizationManager } from './authorizationManager'
11
+ import { AuthorizationHandlerTokenOptions } from './types'
12
+
13
+ const logger = debug('agents:authorization')
14
+
15
+ /**
16
+ * Class responsible for managing authorization and OAuth flows.
17
+ * Handles multiple OAuth providers and manages the complete authentication lifecycle.
18
+ *
19
+ * @remarks
20
+ * The Authorization class provides a centralized way to handle OAuth authentication
21
+ * flows within the agent application. It supports multiple authentication handlers,
22
+ * token exchange, on-behalf-of flows, and provides event handlers for success/failure scenarios.
23
+ *
24
+ * Key features:
25
+ * - Multiple OAuth provider support
26
+ * - Token caching and exchange
27
+ * - On-behalf-of (OBO) token flows
28
+ * - Sign-in success/failure event handling
29
+ * - Automatic configuration from environment variables
30
+ *
31
+ */
32
+ export class Authorization {
33
+ /**
34
+ * Creates a new instance of Authorization.
35
+ * @param manager The AuthorizationManager instance to manage handlers.
36
+ */
37
+ constructor (private manager: AuthorizationManager) {}
38
+
39
+ /**
40
+ * Gets the token for a specific auth handler.
41
+ *
42
+ * @param context - The context object for the current turn.
43
+ * @param authHandlerId - ID of the auth handler to use.
44
+ * @returns A promise that resolves to the token response from the OAuth provider.
45
+ * @throws {Error} If the auth handler is not configured.
46
+ *
47
+ * @remarks
48
+ * This method retrieves an existing token for the specified auth handler.
49
+ * The token may be cached and will be retrieved from the OAuth provider if needed.
50
+ *
51
+ * @example
52
+ * ```typescript
53
+ * const tokenResponse = await auth.getToken(context, 'microsoft');
54
+ * if (tokenResponse.token) {
55
+ * console.log('User is authenticated');
56
+ * }
57
+ * ```
58
+ *
59
+ * @public
60
+ */
61
+ public async getToken (context: TurnContext, authHandlerId: string): Promise<TokenResponse> {
62
+ const handler = this.getHandler(authHandlerId)
63
+ const { token } = await handler.token(context)
64
+ return { token }
65
+ }
66
+
67
+ /**
68
+ * @deprecated
69
+ * Exchanges a token for a new token with different scopes.
70
+ *
71
+ * @param context - The context object for the current turn.
72
+ * @param scopes - Array of scopes to request for the new token.
73
+ * @param authHandlerId - ID of the auth handler to use.
74
+ * @returns A promise that resolves to the exchanged token response.
75
+ * @throws {Error} If the auth handler is not configured.
76
+ *
77
+ * @remarks
78
+ * This method handles token exchange scenarios, particularly for on-behalf-of (OBO) flows.
79
+ * It checks if the current token is exchangeable (e.g., has audience starting with 'api://')
80
+ * and performs the appropriate token exchange using MSAL.
81
+ *
82
+ * @example
83
+ * ```typescript
84
+ * const exchangedToken = await auth.exchangeToken(
85
+ * context,
86
+ * ['https://graph.microsoft.com/.default'],
87
+ * 'microsoft'
88
+ * );
89
+ * ```
90
+ *
91
+ * @public
92
+ */
93
+ public async exchangeToken (context: TurnContext, scopes: string[], authHandlerId: string): Promise<TokenResponse>
94
+ /**
95
+ * Exchanges a token for a new token with different scopes.
96
+ *
97
+ * @param context - The context object for the current turn.
98
+ * @param authHandlerId - ID of the auth handler to use.
99
+ * @param options - Optional token options. If `connection` and `scopes` are NOT provided, the auth handler's configured options are used
100
+ * (`AgentApplication.authorization.obo.connection` and `AgentApplication.authorization.obo.scopes`), and the token exchange operation will happen automatically,
101
+ * meaning that this method should not be called, otherwise it will perform the token exchange operation twice.
102
+ * Provide `options` only when you need to override the `AgentApplication.authorization.obo` configuration for a specific call.
103
+ * @returns A promise that resolves to the exchanged token response.
104
+ * @throws {Error} If the auth handler is not configured.
105
+ *
106
+ * @remarks
107
+ * This method handles token exchange scenarios, particularly for on-behalf-of (OBO) flows.
108
+ * It checks if the current token is exchangeable (e.g., has audience starting with 'api://')
109
+ * and performs the appropriate token exchange using MSAL.
110
+ *
111
+ * @example
112
+ * ```typescript
113
+ * const exchangedToken = await auth.exchangeToken(
114
+ * context,
115
+ * 'microsoft',
116
+ * { connection: 'oboConnection', scopes: ['https://graph.microsoft.com/.default'] }
117
+ * });
118
+ * ```
119
+ *
120
+ * @public
121
+ */
122
+ public async exchangeToken (context: TurnContext, authHandlerId: string, options?: AuthorizationHandlerTokenOptions): Promise<TokenResponse>
123
+
124
+ /**
125
+ * @internal
126
+ * Internal implementation of exchangeToken method.
127
+ * Handles both overloads and performs the actual token exchange logic.
128
+ */
129
+ public async exchangeToken (context: TurnContext, authHandlerId: string[] | string, options?: AuthorizationHandlerTokenOptions | string): Promise<TokenResponse> {
130
+ if (authHandlerId instanceof Array && typeof options === 'string') {
131
+ logger.warn('Authorization.exchangeToken(context, scopes, authHandlerId) is deprecated. Use Authorization.exchangeToken(context, authHandlerId, options) instead.')
132
+ const [handlerId, handlerScopes] = [options, authHandlerId]
133
+ return this.exchangeToken(context, handlerId, { scopes: handlerScopes })
134
+ }
135
+
136
+ if (typeof authHandlerId === 'string' && typeof options === 'object') {
137
+ const handler = this.getHandler(authHandlerId)
138
+ const { token } = await handler.token(context, options)
139
+ return { token }
140
+ }
141
+
142
+ throw new Error('Invalid parameters for exchangeToken method.')
143
+ }
144
+
145
+ /**
146
+ * Signs out the current user.
147
+ *
148
+ * @param context - The context object for the current turn.
149
+ * @param state - The state object for the current turn.
150
+ * @param authHandlerId - Optional ID of the auth handler to use for sign out. If not provided, signs out from all handlers.
151
+ * @returns A promise that resolves when sign out is complete.
152
+ * @throws {Error} If the specified auth handler is not configured.
153
+ *
154
+ * @remarks
155
+ * This method clears the user's token and resets the authentication state.
156
+ * If no specific authHandlerId is provided, it signs out from all configured handlers.
157
+ * This ensures complete cleanup of authentication state across all providers.
158
+ *
159
+ * @example
160
+ * ```typescript
161
+ * // Sign out from specific handler
162
+ * await auth.signOut(context, state, 'microsoft');
163
+ *
164
+ * // Sign out from all handlers
165
+ * await auth.signOut(context, state);
166
+ * ```
167
+ *
168
+ * @public
169
+ */
170
+ public async signOut (context: TurnContext, state: TurnState, authHandlerId?: string) : Promise<void> {
171
+ if (authHandlerId) {
172
+ await this.getHandler(authHandlerId).signout(context)
173
+ } else {
174
+ for (const handler of Object.values(this.manager.handlers)) {
175
+ await handler.signout(context)
176
+ }
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Sets a handler to be called when sign-in is successfully completed.
182
+ *
183
+ * @param handler - The handler function to call on successful sign-in.
184
+ *
185
+ * @remarks
186
+ * This method allows you to register a callback that will be invoked whenever
187
+ * a user successfully completes the authentication process. The handler receives
188
+ * the turn context, state, and the ID of the auth handler that was used.
189
+ *
190
+ * @example
191
+ * ```typescript
192
+ * auth.onSignInSuccess(async (context, state, authHandlerId) => {
193
+ * await context.sendActivity(`Welcome! You signed in using ${authHandlerId}.`);
194
+ * // Perform any post-authentication setup
195
+ * });
196
+ * ```
197
+ *
198
+ * @public
199
+ */
200
+ public onSignInSuccess (handler: (context: TurnContext, state: TurnState, authHandlerId?: string) => Promise<void>) {
201
+ for (const authHandler of Object.values(this.manager.handlers)) {
202
+ authHandler.onSuccess((context) => handler(context, new TurnState(), authHandler.id))
203
+ }
204
+ }
205
+
206
+ /**
207
+ * Sets a handler to be called when sign-in fails.
208
+ *
209
+ * @param handler - The handler function to call on sign-in failure.
210
+ *
211
+ * @remarks
212
+ * This method allows you to register a callback that will be invoked whenever
213
+ * a user's authentication attempt fails. The handler receives the turn context,
214
+ * state, auth handler ID, and an optional error message describing the failure.
215
+ *
216
+ * Common failure scenarios include:
217
+ * - User cancels the authentication process
218
+ * - Invalid credentials or expired tokens
219
+ * - Network connectivity issues
220
+ * - OAuth provider errors
221
+ *
222
+ * @example
223
+ * ```typescript
224
+ * auth.onSignInFailure(async (context, state, authHandlerId, errorMessage) => {
225
+ * await context.sendActivity(`Sign-in failed: ${errorMessage || 'Unknown error'}`);
226
+ * await context.sendActivity('Please try signing in again.');
227
+ * });
228
+ * ```
229
+ *
230
+ * @public
231
+ */
232
+ public onSignInFailure (handler: (context: TurnContext, state: TurnState, authHandlerId?: string, errorMessage?: string) => Promise<void>) {
233
+ for (const authHandler of Object.values(this.manager.handlers)) {
234
+ authHandler.onFailure((context, reason) => handler(context, new TurnState(), authHandler.id, reason))
235
+ }
236
+ }
237
+
238
+ /**
239
+ * Gets the auth handler by ID or throws an error if not found.
240
+ *
241
+ * @param id - ID of the auth handler to retrieve.
242
+ * @returns The auth handler instance.
243
+ * @throws {Error} If the auth handler with the specified ID is not configured.
244
+ * @private
245
+ */
246
+ private getHandler (id: string) {
247
+ if (!Object.prototype.hasOwnProperty.call(this.manager.handlers, id)) {
248
+ throw new Error(`Cannot find auth handler with ID '${id}'. Ensure it is configured in the agent application options.`)
249
+ }
250
+ return this.manager.handlers[id]
251
+ }
252
+ }
@@ -0,0 +1,213 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { Activity, debug } from '@microsoft/agents-activity'
7
+ import { AgentApplication } from '../agentApplication'
8
+ import { AgenticAuthorization, AzureBotAuthorization } from './handlers'
9
+ import { TurnContext } from '../../turnContext'
10
+ import { HandlerStorage } from './handlerStorage'
11
+ import { ActiveAuthorizationHandler, AuthorizationHandlerStatus, AuthorizationHandler, AuthorizationHandlerSettings, AuthorizationOptions } from './types'
12
+ import { Connections } from '../../auth/connections'
13
+
14
+ const logger = debug('agents:authorization:manager')
15
+
16
+ /**
17
+ * Active handler information used by the AuthorizationManager.
18
+ */
19
+ interface ManagerActiveHandler {
20
+ data: ActiveAuthorizationHandler;
21
+ handlers: AuthorizationHandler[];
22
+ }
23
+
24
+ /**
25
+ * Result of the authorization manager process.
26
+ */
27
+ export interface AuthorizationManagerProcessResult {
28
+ /**
29
+ * Indicates whether the authorization was successful.
30
+ */
31
+ authorized: boolean;
32
+ }
33
+
34
+ /**
35
+ * Function to retrieve handler IDs for the current activity.
36
+ */
37
+ export type GetHandlerIds = (activity: Activity) => string[] | Promise<string[]>
38
+
39
+ /**
40
+ * Manages multiple authorization handlers and their interactions.
41
+ * Processes authorization requests and maintains handler states.
42
+ * @remarks
43
+ * This class is responsible for coordinating the authorization process
44
+ * across multiple handlers, ensuring that each handler is invoked in
45
+ * the correct order and with the appropriate context.
46
+ */
47
+ export class AuthorizationManager {
48
+ private _handlers: Record<string, AuthorizationHandler> = {}
49
+
50
+ /**
51
+ * Creates an instance of the AuthorizationManager.
52
+ * @param app The agent application instance.
53
+ */
54
+ constructor (private app: AgentApplication<any>, connections: Connections) {
55
+ if (!app.options.storage) {
56
+ throw new Error('Storage is required for Authorization. Ensure that a storage provider is configured in the AgentApplication options.')
57
+ }
58
+
59
+ if (app.options.authorization === undefined || Object.keys(app.options.authorization).length === 0) {
60
+ throw new Error('The AgentApplication.authorization does not have any auth handlers')
61
+ }
62
+
63
+ const settings: AuthorizationHandlerSettings = { storage: app.options.storage, connections }
64
+ for (const [id, handler] of Object.entries(app.options.authorization)) {
65
+ const options = this.loadOptions(id, handler)
66
+ if (options.type === 'agentic') {
67
+ this._handlers[id] = new AgenticAuthorization(id, options, settings)
68
+ } else {
69
+ this._handlers[id] = new AzureBotAuthorization(id, options, settings)
70
+ }
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Loads and validates the authorization handler options.
76
+ */
77
+ private loadOptions (id: string, options: AuthorizationOptions[string]) {
78
+ const result: AuthorizationOptions[string] = {
79
+ ...options,
80
+ type: (options.type ?? process.env[`${id}_type`])?.toLowerCase() as typeof options.type,
81
+ }
82
+
83
+ // Validate supported types, agentic, and default (Azure Bot - undefined)
84
+ const supportedTypes = ['agentic', undefined]
85
+ if (!supportedTypes.includes(result.type)) {
86
+ throw new Error(`Unsupported authorization handler type: '${result.type}' for auth handler: '${id}'. Supported types are: '${supportedTypes.filter(Boolean).join('\', \'')}'.`)
87
+ }
88
+
89
+ return result
90
+ }
91
+
92
+ /**
93
+ * Gets the registered authorization handlers.
94
+ * @returns A record of authorization handlers by their IDs.
95
+ */
96
+ public get handlers (): Record<string, AuthorizationHandler> {
97
+ return this._handlers
98
+ }
99
+
100
+ /**
101
+ * Processes an authorization request.
102
+ * @param context The turn context.
103
+ * @param getHandlerIds A function to retrieve the handler IDs for the current activity.
104
+ * @returns The result of the authorization process.
105
+ */
106
+ public async process (context: TurnContext, getHandlerIds: GetHandlerIds): Promise<AuthorizationManagerProcessResult> {
107
+ const storage = new HandlerStorage(this.app.options.storage!, context)
108
+
109
+ let active = await this.active(storage, getHandlerIds)
110
+
111
+ const handlers = active?.handlers ?? this.mapHandlers(await getHandlerIds(context.activity) ?? []) ?? []
112
+
113
+ for (const handler of handlers) {
114
+ const status = await this.signin(storage, handler, context, active?.data)
115
+ logger.debug(this.prefix(handler.id, `Sign-in status: ${status}`))
116
+
117
+ if (status === AuthorizationHandlerStatus.IGNORED) {
118
+ await storage.delete()
119
+ return { authorized: true }
120
+ }
121
+
122
+ if (status === AuthorizationHandlerStatus.PENDING) {
123
+ return { authorized: false }
124
+ }
125
+
126
+ if (status === AuthorizationHandlerStatus.REJECTED) {
127
+ await storage.delete()
128
+ return { authorized: false }
129
+ }
130
+
131
+ if (status === AuthorizationHandlerStatus.REVALIDATE) {
132
+ await storage.delete()
133
+ return this.process(context, getHandlerIds)
134
+ }
135
+
136
+ if (status !== AuthorizationHandlerStatus.APPROVED) {
137
+ throw new Error(this.prefix(handler.id, `Unexpected registration status: ${status}`))
138
+ }
139
+
140
+ await storage.delete()
141
+
142
+ if (active) {
143
+ // Restore the original activity in the turn context for the next handler to process.
144
+ // This is done like this to avoid losing data that may be set in the turn context.
145
+ (context as any)._activity = Activity.fromObject(active.data.activity)
146
+ active = undefined
147
+ }
148
+ }
149
+
150
+ return { authorized: true }
151
+ }
152
+
153
+ /**
154
+ * Gets the active handler session from storage.
155
+ */
156
+ private async active (storage: HandlerStorage, getHandlerIds: GetHandlerIds): Promise<ManagerActiveHandler | undefined> {
157
+ const data = await storage.read()
158
+ if (!data) {
159
+ return
160
+ }
161
+
162
+ const handlerIds = await getHandlerIds(Activity.fromObject(data.activity))
163
+ let handlers = this.mapHandlers(handlerIds ?? [])
164
+
165
+ // Sort handlers to ensure the active handler is processed first, to ensure continuity.
166
+ handlers = handlers.sort((a, b) => {
167
+ if (a.id === data.id) {
168
+ return -1
169
+ }
170
+ if (b.id === data.id) {
171
+ return 1
172
+ }
173
+ return 0
174
+ }) ?? []
175
+ return { data, handlers }
176
+ }
177
+
178
+ /**
179
+ * Attempts to sign in using the specified handler and options.
180
+ */
181
+ private async signin (storage: HandlerStorage, handler: AuthorizationHandler, context: TurnContext, active?: ActiveAuthorizationHandler): Promise<AuthorizationHandlerStatus> {
182
+ try {
183
+ return await handler.signin(context, active)
184
+ } catch (cause) {
185
+ await storage.delete()
186
+ throw new Error(this.prefix(handler.id, 'Failed to sign in'), { cause })
187
+ }
188
+ }
189
+
190
+ /**
191
+ * Maps an array of handler IDs to their corresponding handler instances.
192
+ */
193
+ private mapHandlers (ids: string[]): AuthorizationHandler[] {
194
+ let unknownHandlers = ''
195
+ const handlers = ids.map(id => {
196
+ if (!this._handlers[id]) {
197
+ unknownHandlers += ` ${id}`
198
+ }
199
+ return this._handlers[id]
200
+ })
201
+ if (unknownHandlers) {
202
+ throw new Error(`Cannot find auth handlers with ID(s): ${unknownHandlers}`)
203
+ }
204
+ return handlers
205
+ }
206
+
207
+ /**
208
+ * Prefixes a message with the handler ID.
209
+ */
210
+ private prefix (id: string, message: string) {
211
+ return `[handler:${id}] ${message}`
212
+ }
213
+ }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { ActiveAuthorizationHandler } from './types'
7
+ import { TurnContext } from '../../turnContext'
8
+ import { Storage } from '../../storage'
9
+
10
+ /**
11
+ * Storage manager for handler state.
12
+ */
13
+ export class HandlerStorage<TActiveHandler extends ActiveAuthorizationHandler = ActiveAuthorizationHandler> {
14
+ /**
15
+ * Creates an instance of the HandlerStorage.
16
+ * @param storage The storage provider.
17
+ * @param context The turn context.
18
+ */
19
+ constructor (private storage: Storage, private context: TurnContext) { }
20
+
21
+ /**
22
+ * Gets the unique key for a handler session.
23
+ */
24
+ public get key (): string {
25
+ const channelId = this.context.activity.channelId?.trim()
26
+ const userId = this.context.activity.from?.id?.trim()
27
+ if (!channelId || !userId) {
28
+ throw new Error(`Both 'activity.channelId' and 'activity.from.id' are required to generate the ${HandlerStorage.name} key.`)
29
+ }
30
+ return `auth/${channelId}/${userId}`
31
+ }
32
+
33
+ /**
34
+ * Reads the active handler state from storage.
35
+ */
36
+ public async read (): Promise<TActiveHandler | undefined> {
37
+ const ongoing = await this.storage.read([this.key])
38
+ return ongoing?.[this.key]
39
+ }
40
+
41
+ /**
42
+ * Writes handler state to storage.
43
+ */
44
+ public write (data: TActiveHandler) : Promise<void> {
45
+ return this.storage.write({ [this.key]: data })
46
+ }
47
+
48
+ /**
49
+ * Deletes handler state from storage.
50
+ */
51
+ public async delete (): Promise<void> {
52
+ try {
53
+ await this.storage.delete([this.key])
54
+ } catch (error) {
55
+ if (error instanceof Error && 'code' in error && error.code === 404) {
56
+ return
57
+ }
58
+ throw error
59
+ }
60
+ }
61
+ }