botframework-connector
Version:
Bot Connector is autorest generated connector client.
198 lines • 11.8 kB
JavaScript
;
/**
* @module botframework-connector
*/
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SkillValidation = void 0;
const authenticationConstants_1 = require("./authenticationConstants");
const authenticationError_1 = require("./authenticationError");
const claimsIdentity_1 = require("./claimsIdentity");
const governmentConstants_1 = require("./governmentConstants");
const jwtTokenExtractor_1 = require("./jwtTokenExtractor");
const jwtTokenValidation_1 = require("./jwtTokenValidation");
const botframework_schema_1 = require("botframework-schema");
const tokenValidationParameters_1 = require("./tokenValidationParameters");
const jsonwebtoken_1 = require("jsonwebtoken");
/**
* @deprecated Use `ConfigurationBotFrameworkAuthentication` instead to perform skill validation.
* Validates JWT tokens sent to and from a Skill.
*/
var SkillValidation;
(function (SkillValidation) {
/**
* Determines if a given Auth header is from a skill to bot or bot to skill request.
*
* @param {string} authHeader Bearer Token, in the "Bearer [Long String]" Format.
* @returns {boolean} True, if the token was issued for a skill to bot communication. Otherwise, false.
*/
function isSkillToken(authHeader) {
if (!jwtTokenValidation_1.JwtTokenValidation.isValidTokenFormat(authHeader)) {
return false;
}
// We know is a valid token, split it and work with it:
// [0] = "Bearer"
// [1] = "[Big Long String]"
const [, ...bearerTokens] = authHeader.trim().split(' ');
// Parse the Big Long String into an actual token.
const payload = (0, jsonwebtoken_1.decode)(bearerTokens.join(' '));
let claims = [];
if (payload && typeof payload === 'object') {
claims = Object.entries(payload).map(([type, value]) => ({
type,
value,
}));
}
return isSkillClaim(claims);
}
SkillValidation.isSkillToken = isSkillToken;
/**
* Checks if the given object of claims represents a skill.
*
* @remarks
* A skill claim should contain:
* An "AuthenticationConstants.VersionClaim" claim.
* An "AuthenticationConstants.AudienceClaim" claim.
* An "AuthenticationConstants.AppIdClaim" claim (v1) or an a "AuthenticationConstants.AuthorizedParty" claim (v2).
* And the appId claim should be different than the audience claim.
* The audience claim should be a guid, indicating that it is from another bot/skill.
* @param claims An object of claims.
* @returns {boolean} True if the object of claims is a skill claim, false if is not.
*/
function isSkillClaim(claims) {
if (!claims) {
throw new TypeError('SkillValidation.isSkillClaim(): missing claims.');
}
// Group claims by type for fast lookup
const claimsByType = claims.reduce((acc, claim) => (Object.assign(Object.assign({}, acc), { [claim.type]: claim })), {});
// Short circuit if this is a anonymous skill app ID (generated via createAnonymousSkillClaim)
const appIdClaim = claimsByType[authenticationConstants_1.AuthenticationConstants.AppIdClaim];
if (appIdClaim && appIdClaim.value === authenticationConstants_1.AuthenticationConstants.AnonymousSkillAppId) {
return true;
}
const versionClaim = claimsByType[authenticationConstants_1.AuthenticationConstants.VersionClaim];
const versionValue = versionClaim && versionClaim.value;
if (!versionValue) {
// Must have a version claim.
return false;
}
const audClaim = claimsByType[authenticationConstants_1.AuthenticationConstants.AudienceClaim];
const audienceValue = audClaim && audClaim.value;
if (!audClaim ||
authenticationConstants_1.AuthenticationConstants.ToBotFromChannelTokenIssuer === audienceValue ||
governmentConstants_1.GovernmentConstants.ToBotFromChannelTokenIssuer === audienceValue) {
// The audience is https://api.botframework.com and not an appId.
return false;
}
const appId = jwtTokenValidation_1.JwtTokenValidation.getAppIdFromClaims(claims);
if (!appId) {
return false;
}
// Skill claims must contain and app ID and the AppID must be different than the audience.
return appId !== audienceValue;
}
SkillValidation.isSkillClaim = isSkillClaim;
/**
* Validates that the incoming Auth Header is a token sent from a bot to a skill or from a skill to a bot.
*
* @param authHeader The raw HTTP header in the format: "Bearer [longString]".
* @param credentials The user defined set of valid credentials, such as the AppId.
* @param channelService The channelService value that distinguishes public Azure from US Government Azure.
* @param channelId The ID of the channel to validate.
* @param authConfig The authentication configuration.
* @returns {Promise<ClaimsIdentity>} A "ClaimsIdentity" instance if the validation is successful.
*/
function authenticateChannelToken(authHeader, credentials, channelService, channelId, authConfig) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
if (!authConfig) {
throw new authenticationError_1.AuthenticationError('SkillValidation.authenticateChannelToken(): invalid authConfig parameter', botframework_schema_1.StatusCodes.INTERNAL_SERVER_ERROR);
}
const openIdMetadataUrl = jwtTokenValidation_1.JwtTokenValidation.isGovernment(channelService)
? governmentConstants_1.GovernmentConstants.ToBotFromEmulatorOpenIdMetadataUrl
: authenticationConstants_1.AuthenticationConstants.ToBotFromEmulatorOpenIdMetadataUrl;
// Add allowed token issuers from configuration.
const verifyOptions = Object.assign(Object.assign({}, tokenValidationParameters_1.ToBotFromBotOrEmulatorTokenValidationParameters), { issuer: [
...tokenValidationParameters_1.ToBotFromBotOrEmulatorTokenValidationParameters.issuer,
...((_a = authConfig.validTokenIssuers) !== null && _a !== void 0 ? _a : []),
] });
const tokenExtractor = new jwtTokenExtractor_1.JwtTokenExtractor(verifyOptions, openIdMetadataUrl, authenticationConstants_1.AuthenticationConstants.AllowedSigningAlgorithms);
const parts = authHeader.split(' ');
const identity = yield tokenExtractor.getIdentity(parts[0], parts[1], channelId, authConfig.requiredEndorsements);
yield validateIdentity(identity, credentials);
return identity;
});
}
SkillValidation.authenticateChannelToken = authenticateChannelToken;
/**
* @ignore
* @private
* @param identity
* @param credentials
*/
function validateIdentity(identity, credentials) {
return __awaiter(this, void 0, void 0, function* () {
if (!identity) {
// No valid identity. Not Authorized.
throw new authenticationError_1.AuthenticationError('SkillValidation.validateIdentity(): Invalid identity', botframework_schema_1.StatusCodes.UNAUTHORIZED);
}
if (!identity.isAuthenticated) {
// The token is in some way invalid. Not Authorized.
throw new authenticationError_1.AuthenticationError('SkillValidation.validateIdentity(): Token not authenticated', botframework_schema_1.StatusCodes.UNAUTHORIZED);
}
const versionClaim = identity.getClaimValue(authenticationConstants_1.AuthenticationConstants.VersionClaim);
// const versionClaim = identity.claims.FirstOrDefault(c => c.Type == AuthenticationConstants.VersionClaim);
if (!versionClaim) {
// No version claim
throw new authenticationError_1.AuthenticationError(`SkillValidation.validateIdentity(): '${authenticationConstants_1.AuthenticationConstants.VersionClaim}' claim is required on skill Tokens.`, botframework_schema_1.StatusCodes.UNAUTHORIZED);
}
// Look for the "aud" claim, but only if issued from the Bot Framework
const audienceClaim = identity.getClaimValue(authenticationConstants_1.AuthenticationConstants.AudienceClaim);
if (!audienceClaim) {
// Claim is not present or doesn't have a value. Not Authorized.
throw new authenticationError_1.AuthenticationError(`SkillValidation.validateIdentity(): '${authenticationConstants_1.AuthenticationConstants.AudienceClaim}' claim is required on skill Tokens.`, botframework_schema_1.StatusCodes.UNAUTHORIZED);
}
if (!(yield credentials.isValidAppId(audienceClaim))) {
// The AppId is not valid. Not Authorized.
throw new authenticationError_1.AuthenticationError('SkillValidation.validateIdentity(): Invalid audience.', botframework_schema_1.StatusCodes.UNAUTHORIZED);
}
const appId = jwtTokenValidation_1.JwtTokenValidation.getAppIdFromClaims(identity.claims);
if (!appId) {
// Invalid appId
throw new authenticationError_1.AuthenticationError('SkillValidation.validateIdentity(): Invalid appId.', botframework_schema_1.StatusCodes.UNAUTHORIZED);
}
// TODO: check the appId against the registered skill client IDs.
// Check the AppId and ensure that only works against my whitelist authConfig can have info on how to get the
// whitelist AuthenticationConfiguration
// We may need to add a ClaimsIdentityValidator delegate or class that allows the dev to inject a custom validator.
});
}
SkillValidation.validateIdentity = validateIdentity;
/**
* Creates a set of claims that represent an anonymous skill. Useful for testing bots locally in the emulator
*
* @returns A [ClaimsIdentity](xref.botframework-connector.ClaimsIdentity) instance with authentication type set to [AuthenticationConstants.AnonymousAuthType](xref.botframework-connector.AuthenticationConstants) and a reserved [AuthenticationConstants.AnonymousSkillAppId](xref.botframework-connector.AuthenticationConstants) claim.
*/
function createAnonymousSkillClaim() {
return new claimsIdentity_1.ClaimsIdentity([
{
type: authenticationConstants_1.AuthenticationConstants.AppIdClaim,
value: authenticationConstants_1.AuthenticationConstants.AnonymousSkillAppId,
},
], authenticationConstants_1.AuthenticationConstants.AnonymousAuthType);
}
SkillValidation.createAnonymousSkillClaim = createAnonymousSkillClaim;
})(SkillValidation = exports.SkillValidation || (exports.SkillValidation = {}));
//# sourceMappingURL=skillValidation.js.map