232 lines
No EOL
13 KiB
JavaScript
232 lines
No EOL
13 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.JwtTokenValidation = void 0;
|
|
const botframework_schema_1 = require("botframework-schema");
|
|
const authenticationError_1 = require("./authenticationError");
|
|
const authenticationConfiguration_1 = require("./authenticationConfiguration");
|
|
const authenticationConstants_1 = require("./authenticationConstants");
|
|
const channelValidation_1 = require("./channelValidation");
|
|
const claimsIdentity_1 = require("./claimsIdentity");
|
|
const emulatorValidation_1 = require("./emulatorValidation");
|
|
const enterpriseChannelValidation_1 = require("./enterpriseChannelValidation");
|
|
const governmentChannelValidation_1 = require("./governmentChannelValidation");
|
|
const governmentConstants_1 = require("./governmentConstants");
|
|
const skillValidation_1 = require("./skillValidation");
|
|
const aseChannelValidation_1 = require("./aseChannelValidation");
|
|
/**
|
|
* @deprecated Use `ConfigurationBotFrameworkAuthentication` instead to perform JWT token validation.
|
|
*/
|
|
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
var JwtTokenValidation;
|
|
(function (JwtTokenValidation) {
|
|
/**
|
|
* Authenticates the request and sets the service url in the set of trusted urls.
|
|
*
|
|
* @param {Partial<Activity>} activity The incoming Activity from the Bot Framework or the Emulator
|
|
* @param {string} authHeader The Bearer token included as part of the request
|
|
* @param {ICredentialProvider} credentials The set of valid credentials, such as the Bot Application ID
|
|
* @param {string} channelService The channel service
|
|
* @param {AuthenticationConfiguration} authConfig Optional, the auth config
|
|
* @returns {Promise<ClaimsIdentity>} Promise with ClaimsIdentity for the request.
|
|
*/
|
|
function authenticateRequest(activity, authHeader, credentials, channelService, authConfig) {
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|
if (!authConfig) {
|
|
authConfig = new authenticationConfiguration_1.AuthenticationConfiguration();
|
|
}
|
|
// eslint-disable-next-line prettier/prettier
|
|
if (!authHeader.trim()) { // CodeQL [SM01513] We manually validate incoming tokens. Checking for empty header as part of that.
|
|
const isAuthDisabled = yield credentials.isAuthenticationDisabled();
|
|
if (!isAuthDisabled) {
|
|
throw new authenticationError_1.AuthenticationError('Unauthorized Access. Request is not authorized', botframework_schema_1.StatusCodes.UNAUTHORIZED);
|
|
}
|
|
// Check if the activity is for a skill call and is coming from the Emulator.
|
|
if (activity.channelId === botframework_schema_1.Channels.Emulator &&
|
|
activity.recipient &&
|
|
activity.recipient.role === botframework_schema_1.RoleTypes.Skill) {
|
|
return skillValidation_1.SkillValidation.createAnonymousSkillClaim();
|
|
}
|
|
// In the scenario where Auth is disabled, we still want to have the
|
|
// IsAuthenticated flag set in the ClaimsIdentity. To do this requires
|
|
// adding in an empty claim.
|
|
return new claimsIdentity_1.ClaimsIdentity([], authenticationConstants_1.AuthenticationConstants.AnonymousAuthType);
|
|
}
|
|
const claimsIdentity = yield validateAuthHeader(authHeader, credentials, channelService, activity.channelId, activity.serviceUrl, authConfig);
|
|
return claimsIdentity;
|
|
});
|
|
}
|
|
JwtTokenValidation.authenticateRequest = authenticateRequest;
|
|
/**
|
|
* Validate an auth header.
|
|
*
|
|
* @param {string} authHeader the auth header
|
|
* @param {ICredentialProvider} credentials the credentials
|
|
* @param {string} channelService the channel service
|
|
* @param {string} channelId the channel ID
|
|
* @param {string} serviceUrl the service URL
|
|
* @param {AuthenticationConfiguration} authConfig the auth config
|
|
* @returns {Promise<ClaimsIdentity>} a promise that resolves to the validated claims, or rejects if validation fails
|
|
*/
|
|
function validateAuthHeader(authHeader, credentials, channelService, channelId, serviceUrl = '', authConfig = new authenticationConfiguration_1.AuthenticationConfiguration()) {
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|
if (!authHeader.trim()) {
|
|
throw new authenticationError_1.AuthenticationError("'authHeader' required.", botframework_schema_1.StatusCodes.BAD_REQUEST);
|
|
}
|
|
const identity = yield authenticateToken(authHeader, credentials, channelService, channelId, authConfig, serviceUrl);
|
|
yield validateClaims(authConfig, identity.claims);
|
|
return identity;
|
|
});
|
|
}
|
|
JwtTokenValidation.validateAuthHeader = validateAuthHeader;
|
|
function authenticateToken(authHeader, credentials, channelService, channelId, authConfig, serviceUrl) {
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|
if (aseChannelValidation_1.AseChannelValidation.isTokenFromAseChannel(channelId)) {
|
|
return aseChannelValidation_1.AseChannelValidation.authenticateAseChannelToken(authHeader);
|
|
}
|
|
if (skillValidation_1.SkillValidation.isSkillToken(authHeader)) {
|
|
return yield skillValidation_1.SkillValidation.authenticateChannelToken(authHeader, credentials, channelService, channelId, authConfig);
|
|
}
|
|
if (emulatorValidation_1.EmulatorValidation.isTokenFromEmulator(authHeader)) {
|
|
return yield emulatorValidation_1.EmulatorValidation.authenticateEmulatorToken(authHeader, credentials, channelService, channelId);
|
|
}
|
|
if (isPublicAzure(channelService)) {
|
|
// eslint-disable-next-line prettier/prettier
|
|
if (serviceUrl.trim()) { // CodeQL [SM01513] We manually validate incoming tokens. Checking for empty serviceUrl as part of that.
|
|
return yield channelValidation_1.ChannelValidation.authenticateChannelTokenWithServiceUrl(authHeader, credentials, serviceUrl, channelId);
|
|
}
|
|
return yield channelValidation_1.ChannelValidation.authenticateChannelToken(authHeader, credentials, channelId);
|
|
}
|
|
if (isGovernment(channelService)) {
|
|
// eslint-disable-next-line prettier/prettier
|
|
if (serviceUrl.trim()) { // CodeQL [SM01513] We manually validate incoming tokens. Checking for empty serviceUrl as part of that.
|
|
return yield governmentChannelValidation_1.GovernmentChannelValidation.authenticateChannelTokenWithServiceUrl(authHeader, credentials, serviceUrl, channelId);
|
|
}
|
|
return yield governmentChannelValidation_1.GovernmentChannelValidation.authenticateChannelToken(authHeader, credentials, channelId);
|
|
}
|
|
// Otherwise use Enterprise Channel Validation
|
|
// eslint-disable-next-line prettier/prettier
|
|
if (serviceUrl.trim()) { // CodeQL [SM01513] We manually validate incoming tokens. Checking for empty serviceUrl as part of that.
|
|
return yield enterpriseChannelValidation_1.EnterpriseChannelValidation.authenticateChannelTokenWithServiceUrl(authHeader, credentials, serviceUrl, channelId, channelService);
|
|
}
|
|
return yield enterpriseChannelValidation_1.EnterpriseChannelValidation.authenticateChannelToken(authHeader, credentials, channelId, channelService);
|
|
});
|
|
}
|
|
/**
|
|
* Validates the identity claims against the ClaimsValidator in AuthenticationConfiguration if present.
|
|
*
|
|
* @param authConfig The authentication configuration.
|
|
* @param claims The list of claims to validate.
|
|
*/
|
|
function validateClaims(authConfig, claims = []) {
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|
if (authConfig.validateClaims) {
|
|
// Call the validation method if defined (it should throw an exception if the validation fails)
|
|
yield authConfig.validateClaims(claims);
|
|
}
|
|
else if (skillValidation_1.SkillValidation.isSkillClaim(claims)) {
|
|
// Skill claims must be validated using AuthenticationConfiguration validateClaims
|
|
throw new authenticationError_1.AuthenticationError('Unauthorized Access. Request is not authorized. Skill Claims require validation.', botframework_schema_1.StatusCodes.UNAUTHORIZED);
|
|
}
|
|
});
|
|
}
|
|
/**
|
|
* Gets the AppId from a claims list.
|
|
*
|
|
* @summary
|
|
* In v1 tokens the AppId is in the "ver" AuthenticationConstants.AppIdClaim claim.
|
|
* In v2 tokens the AppId is in the "azp" AuthenticationConstants.AuthorizedParty claim.
|
|
* If the AuthenticationConstants.VersionClaim is not present, this method will attempt to
|
|
* obtain the attribute from the AuthenticationConstants.AppIdClaim or if present.
|
|
*
|
|
* Throws a TypeError if claims is falsy.
|
|
*
|
|
* @param {Claim[]} claims An object containing claims types and their values.
|
|
* @returns {string} the app ID
|
|
*/
|
|
function getAppIdFromClaims(claims) {
|
|
if (!claims) {
|
|
throw new TypeError('JwtTokenValidation.getAppIdFromClaims(): missing claims.');
|
|
}
|
|
let appId;
|
|
// Group claims by type for fast lookup
|
|
const claimsByType = claims.reduce((acc, claim) => (Object.assign(Object.assign({}, acc), { [claim.type]: claim })), {});
|
|
// Depending on Version, the AppId is either in the
|
|
// appid claim (Version 1) or the 'azp' claim (Version 2).
|
|
const versionClaim = claimsByType[authenticationConstants_1.AuthenticationConstants.VersionClaim];
|
|
const versionValue = versionClaim && versionClaim.value;
|
|
if (!versionValue || versionValue === '1.0') {
|
|
// No version or a version of '1.0' means we should look for
|
|
// the claim in the 'appid' claim.
|
|
const appIdClaim = claimsByType[authenticationConstants_1.AuthenticationConstants.AppIdClaim];
|
|
if (appIdClaim && appIdClaim.value) {
|
|
appId = appIdClaim.value;
|
|
}
|
|
}
|
|
else if (versionValue === '2.0') {
|
|
// Version '2.0' puts the AppId in the 'azp' claim.
|
|
const azpClaim = claimsByType[authenticationConstants_1.AuthenticationConstants.AuthorizedParty];
|
|
if (azpClaim && azpClaim.value) {
|
|
appId = azpClaim.value;
|
|
}
|
|
}
|
|
return appId;
|
|
}
|
|
JwtTokenValidation.getAppIdFromClaims = getAppIdFromClaims;
|
|
function isPublicAzure(channelService) {
|
|
return !channelService || channelService.length === 0;
|
|
}
|
|
/**
|
|
* Determine whether or not a channel service is government
|
|
*
|
|
* @param {string} channelService the channel service
|
|
* @returns {boolean} true if this is a government channel service
|
|
*/
|
|
function isGovernment(channelService) {
|
|
return channelService && channelService.toLowerCase() === governmentConstants_1.GovernmentConstants.ChannelService;
|
|
}
|
|
JwtTokenValidation.isGovernment = isGovernment;
|
|
/**
|
|
* Internal helper to check if the token has the shape we expect "Bearer [big long string]".
|
|
*
|
|
* @param {string} authHeader A string containing the token header.
|
|
* @returns {boolean} True if the token is valid, false if not.
|
|
*/
|
|
function isValidTokenFormat(authHeader) {
|
|
if (!authHeader) {
|
|
// No token, not valid.
|
|
return false;
|
|
}
|
|
const parts = authHeader.trim().split(' ');
|
|
if (parts.length !== 2) {
|
|
// Tokens MUST have exactly 2 parts. If we don't have 2 parts, it's not a valid token
|
|
return false;
|
|
}
|
|
// We now have an array that should be:
|
|
// [0] = "Bearer"
|
|
// [1] = "[Big Long String]"
|
|
const authScheme = parts[0];
|
|
if (authScheme !== 'Bearer') {
|
|
// The scheme MUST be "Bearer"
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
JwtTokenValidation.isValidTokenFormat = isValidTokenFormat;
|
|
})(JwtTokenValidation = exports.JwtTokenValidation || (exports.JwtTokenValidation = {}));
|
|
//# sourceMappingURL=jwtTokenValidation.js.map
|