UNPKG

botframework-connector

Version:

Bot Connector is autorest generated connector client.

198 lines 11.8 kB
"use strict"; /** * @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