@microsoft/teams.apps
Version:
<p> <a href="https://www.npmjs.com/package/@microsoft/teams.apps" target="_blank"> <img src="https://img.shields.io/npm/v/@microsoft/teams.apps/latest" /> </a> <a href="https://www.npmjs.com/package/@microsoft/teams.apps?activeTab=code
133 lines • 11 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.InboundActivityTokenValidator = exports.AGENT_365_PLATFORM_APP_ID = void 0;
const teams_api_1 = require("@microsoft/teams.api");
const jwt_validator_1 = require("./jwt-validator");
const MAX_ENTRA_VALIDATOR_CACHE_SIZE = 100;
const ENTRA_V1_ISSUER_PREFIX = 'https://sts.windows.net/';
/**
* App ID of the Agent 365 platform.
*/
exports.AGENT_365_PLATFORM_APP_ID = '5a807f24-c9de-44ee-a3a7-329e88a00ffc';
/**
* Derives the JWKS keys URI from an OpenID metadata URL.
* e.g. "https://login.botframework.com/v1/.well-known/openidconfiguration"
* -> "https://login.botframework.com/v1/.well-known/keys"
*/
function openIdMetadataToKeysUri(openIdMetadataUrl) {
return openIdMetadataUrl.replace(/\/openidconfiguration$/, '/keys');
}
/**
* Validator for inbound Teams activities.
*
* Classic bot activities use Bot Framework connector tokens. AgenticIdentity
* activities use Entra tokens whose audience is the Agentic App Blueprint ID.
*/
class InboundActivityTokenValidator {
jwtValidator;
entraValidatorsByTenantId = new Map();
credentials;
appId;
cloud;
logger;
constructor(appId, tenantId, serviceUrl, logger, cloud) {
const env = cloud ?? teams_api_1.PUBLIC;
this.appId = appId;
this.cloud = env;
this.logger = logger;
this.jwtValidator = new jwt_validator_1.JwtValidator({
clientId: appId,
tenantId,
loginEndpoint: env.loginEndpoint,
validateIssuer: { allowedIssuer: env.tokenIssuer },
validateServiceUrl: serviceUrl ? { expectedServiceUrl: serviceUrl } : undefined,
jwksUriOptions: {
type: 'uri',
uri: openIdMetadataToKeysUri(env.openIdMetadataUrl),
},
}, logger);
this.credentials = { clientId: appId, tenantId };
}
async check(authHeader, body) {
// Extract token from "Bearer <token>" format
const token = authHeader.startsWith('Bearer ')
? authHeader.substring(7)
: authHeader;
if (!token) {
throw new Error('No token provided');
}
const unverifiedPayload = this.decodePayload(token);
const isEntraToken = this.isEntraIssuer(unverifiedPayload?.iss);
const payload = isEntraToken
? await this.validateEntraToken(token, unverifiedPayload)
: await this.jwtValidator.validateAccessToken(token, {
validateServiceUrl: body.serviceUrl ? { expectedServiceUrl: body.serviceUrl } : undefined
});
if (!payload) {
throw new Error('Invalid token');
}
const serviceUrl = body.serviceUrl || payload.serviceurl || '';
// Convert JWT payload to IToken
return {
appId: payload.appid || this.credentials?.clientId || '',
from: 'azure',
fromId: payload.sub || '',
serviceUrl,
isExpired: () => false, // Already validated by JWT validator
};
}
decodePayload(rawToken) {
return (0, jwt_validator_1.decodeJwtPayload)(rawToken);
}
isEntraIssuer(issuer) {
return typeof issuer === 'string' && (issuer.startsWith(this.cloud.loginEndpoint) || issuer.startsWith(ENTRA_V1_ISSUER_PREFIX));
}
async validateEntraToken(rawToken, unverifiedPayload) {
const tenantId = unverifiedPayload?.tid;
if (!tenantId || typeof tenantId !== 'string') {
throw new Error('Entra inbound token is missing tid');
}
const validator = this.getEntraValidator(tenantId);
// AgenticIdentity inbound Entra tokens currently do not include serviceurl.
// Revisit service URL validation when the platform defines a signed claim.
const payload = await validator.validateAccessToken(rawToken);
if (payload) {
this.validateEntraCallerApp(payload);
}
return payload;
}
/**
* Requires the client app that requested the token to be the Agent 365 platform.
* Entra v2 tokens carry the caller in `azp` and v1 tokens carry it in `appid`.
* `appid` is only consulted when `azp` is absent, so a present but invalid `azp` is rejected.
*/
validateEntraCallerApp(payload) {
const callerAppId = 'azp' in payload ? payload.azp : payload.appid;
if (typeof callerAppId !== 'string' || callerAppId.toLowerCase() !== exports.AGENT_365_PLATFORM_APP_ID) {
throw new Error('Entra inbound token caller app is not allowed');
}
}
getEntraValidator(tenantId) {
const cachedValidator = this.entraValidatorsByTenantId.get(tenantId);
if (cachedValidator) {
return cachedValidator;
}
const validator = new jwt_validator_1.JwtValidator({
clientId: this.appId,
tenantId,
loginEndpoint: this.cloud.loginEndpoint,
validateIssuer: { allowedTenantIds: [tenantId] },
jwksUriOptions: { type: 'tenantId' },
}, this.logger);
this.entraValidatorsByTenantId.set(tenantId, validator);
if (this.entraValidatorsByTenantId.size > MAX_ENTRA_VALIDATOR_CACHE_SIZE) {
const oldestTenantId = this.entraValidatorsByTenantId.keys().next().value;
if (oldestTenantId) {
this.entraValidatorsByTenantId.delete(oldestTenantId);
}
}
return validator;
}
}
exports.InboundActivityTokenValidator = InboundActivityTokenValidator;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5ib3VuZC1hY3Rpdml0eS10b2tlbi12YWxpZGF0b3IuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvbWlkZGxld2FyZS9hdXRoL2luYm91bmQtYWN0aXZpdHktdG9rZW4tdmFsaWRhdG9yLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUVBLG9EQUFxRjtBQUdyRixtREFBaUU7QUFFakUsTUFBTSw4QkFBOEIsR0FBRyxHQUFHLENBQUM7QUFDM0MsTUFBTSxzQkFBc0IsR0FBRywwQkFBMEIsQ0FBQztBQUUxRDs7R0FFRztBQUNVLFFBQUEseUJBQXlCLEdBQUcsc0NBQXNDLENBQUM7QUFFaEY7Ozs7R0FJRztBQUNILFNBQVMsdUJBQXVCLENBQUMsaUJBQXlCO0lBQ3hELE9BQU8saUJBQWlCLENBQUMsT0FBTyxDQUFDLHdCQUF3QixFQUFFLE9BQU8sQ0FBQyxDQUFDO0FBQ3RFLENBQUM7QUFFRDs7Ozs7R0FLRztBQUNILE1BQWEsNkJBQTZCO0lBQ2hDLFlBQVksQ0FBZTtJQUMzQix5QkFBeUIsR0FBRyxJQUFJLEdBQUcsRUFBd0IsQ0FBQztJQUM1RCxXQUFXLENBQWU7SUFDMUIsS0FBSyxDQUFTO0lBQ2QsS0FBSyxDQUFtQjtJQUN4QixNQUFNLENBQVc7SUFFekIsWUFDRSxLQUFhLEVBQ2IsUUFBaUIsRUFDakIsVUFBbUIsRUFDbkIsTUFBZ0IsRUFDaEIsS0FBd0I7UUFFeEIsTUFBTSxHQUFHLEdBQUcsS0FBSyxJQUFJLGtCQUFNLENBQUM7UUFDNUIsSUFBSSxDQUFDLEtBQUssR0FBRyxLQUFLLENBQUM7UUFDbkIsSUFBSSxDQUFDLEtBQUssR0FBRyxHQUFHLENBQUM7UUFDakIsSUFBSSxDQUFDLE1BQU0sR0FBRyxNQUFNLENBQUM7UUFDckIsSUFBSSxDQUFDLFlBQVksR0FBRyxJQUFJLDRCQUFZLENBQUM7WUFDbkMsUUFBUSxFQUFFLEtBQUs7WUFDZixRQUFRO1lBQ1IsYUFBYSxFQUFFLEdBQUcsQ0FBQyxhQUFhO1lB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