198 lines
No EOL
12 KiB
JavaScript
198 lines
No EOL
12 KiB
JavaScript
"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
|