@microsoft/agents-hosting
Version:
Microsoft 365 Agents SDK for JavaScript
184 lines (159 loc) • 5.85 kB
text/typescript
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import { debug } from '@microsoft/agents-activity'
import { TurnContext } from '../../../turnContext'
import { AuthorizationHandler, AuthorizationHandlerSettings, AuthorizationHandlerStatus, AuthorizationHandlerTokenOptions } from '../types'
import { TokenResponse } from '../../../oauth'
import { AuthProvider } from '../../../auth'
const logger = debug('agents:authorization:agentic')
/**
* Options for configuring the Agentic authorization handler.
*/
export interface AgenticAuthorizationOptions {
/**
* The type of authorization handler.
* @remarks
* When using environment variables, this can be set using the `${authHandlerId}_type` variable.
*/
type: 'agentic'
/**
* The scopes required for the authorization.
* @remarks
* When using environment variables, this can be set using the `${authHandlerId}_scopes` variable (comma-separated values, e.g. `scope1,scope2`).
*/
scopes?: string[]
/**
* (Optional) An alternative connection name to use for the authorization process.
* @remarks
* When using environment variables, this can be set using the `${authHandlerId}_altBlueprintConnectionName` variable.
*/
altBlueprintConnectionName?: string
}
/**
* Settings for configuring the Agentic authorization handler.
*/
export interface AgenticAuthorizationSettings extends AuthorizationHandlerSettings {}
/**
* Authorization handler for Agentic authentication.
*/
export class AgenticAuthorization implements AuthorizationHandler {
private _options: AgenticAuthorizationOptions
private _onSuccess?: Parameters<AuthorizationHandler['onSuccess']>[0]
private _onFailure?: Parameters<AuthorizationHandler['onFailure']>[0]
/**
* Creates an instance of the AgenticAuthorization class.
* @param id The unique identifier for the authorization handler.
* @param options The options for configuring the authorization handler.
* @param settings The settings for the authorization handler.
*/
constructor (public readonly id: string, options: AgenticAuthorizationOptions, private settings: AgenticAuthorizationSettings) {
if (!this.settings.connections) {
throw new Error(this.prefix('The \'connections\' option is not available in the app options. Ensure that the app is properly configured.'))
}
this._options = this.loadOptions(options)
}
/**
* Loads and validates the authorization handler options.
*/
private loadOptions (settings: AgenticAuthorizationOptions) {
const result: AgenticAuthorizationOptions = {
type: 'agentic',
altBlueprintConnectionName: settings.altBlueprintConnectionName ?? (process.env[`${this.id}_altBlueprintConnectionName`]),
scopes: settings.scopes ?? this.loadScopes(process.env[`${this.id}_scopes`]),
}
if (!result.scopes || result.scopes.length === 0) {
throw new Error(this.prefix('At least one scope must be specified for the Agentic authorization handler.'))
}
return result
}
/**
* @inheritdoc
*/
signin (): Promise<AuthorizationHandlerStatus> {
return Promise.resolve(AuthorizationHandlerStatus.IGNORED)
}
/**
* @inheritdoc
*/
signout (): Promise<boolean> {
return Promise.resolve(false)
}
/**
* @inheritdoc
*/
async token (context: TurnContext, options?: AuthorizationHandlerTokenOptions): Promise<TokenResponse> {
try {
const tokenResponse = this.getContext(context)
if (tokenResponse.token) {
logger.debug(this.prefix('Using cached Agentic user token'))
return tokenResponse
}
let connection: AuthProvider
if (this._options.altBlueprintConnectionName?.trim()) {
connection = this.settings.connections.getConnection(this._options.altBlueprintConnectionName)
} else {
connection = this.settings.connections.getTokenProvider(context.identity, context.activity.serviceUrl ?? '')
}
const token = await connection.getAgenticUserToken(
context.activity.getAgenticTenantId() ?? '',
context.activity.getAgenticInstanceId() ?? '',
context.activity.getAgenticUser() ?? '',
options?.scopes || this._options.scopes!
)
this.setContext(context, { token })
this._onSuccess?.(context)
return { token }
} catch (error) {
const reason = 'Error retrieving Agentic user token'
logger.error(this.prefix(reason), error)
this._onFailure?.(context, `${reason}: ${(error as Error).message}`)
return { token: undefined }
}
}
/**
* @inheritdoc
*/
onSuccess (callback: (context: TurnContext) => void): void {
this._onSuccess = callback
}
/**
* @inheritdoc
*/
onFailure (callback: (context: TurnContext, reason?: string) => void): void {
this._onFailure = callback
}
/**
* Prefixes a message with the handler ID.
*/
private prefix (message: string) {
return `[handler:${this.id}] ${message}`
}
private _key = `${AgenticAuthorization.name}/${this.id}`
/**
* Sets the authorization context in the turn state.
*/
private setContext (context: TurnContext, data: TokenResponse) {
return context.turnState.set(this._key, () => data)
}
/**
* Gets the authorization context from the turn state.
*/
private getContext (context: TurnContext): TokenResponse {
const result = context.turnState.get(this._key)
return result?.() ?? { token: undefined }
}
/**
* Loads the OAuth scopes from the environment variables.
*/
private loadScopes (value:string | undefined): string[] {
return value?.split(',').reduce<string[]>((acc, scope) => {
const trimmed = scope.trim()
if (trimmed) {
acc.push(trimmed)
}
return acc
}, []) ?? []
}
}