UNPKG

botframework-connector

Version:

Bot Connector is autorest generated connector client.

173 lines 9.21 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.JwtTokenExtractor = void 0; const jsonwebtoken_1 = require("jsonwebtoken"); const claimsIdentity_1 = require("./claimsIdentity"); const endorsementsValidator_1 = require("./endorsementsValidator"); const openIdMetadata_1 = require("./openIdMetadata"); const authenticationError_1 = require("./authenticationError"); const botframework_schema_1 = require("botframework-schema"); /** * A JWT token processing class that gets identity information and performs security token validation. */ class JwtTokenExtractor { /** * Initializes a new instance of the [JwtTokenExtractor](xref:botframework-connector.JwtTokenExtractor) class. Extracts relevant data from JWT Tokens. * * @param tokenValidationParameters Token validation parameters. * @param metadataUrl Metadata Url. * @param allowedSigningAlgorithms Allowed signing algorithms. * @param proxySettings The proxy settings for the request. */ constructor(tokenValidationParameters, metadataUrl, allowedSigningAlgorithms, proxySettings) { this.tokenValidationParameters = Object.assign({}, tokenValidationParameters); this.tokenValidationParameters.algorithms = allowedSigningAlgorithms; this.openIdMetadata = JwtTokenExtractor.getOrAddOpenIdMetadata(metadataUrl, proxySettings); } static getOrAddOpenIdMetadata(metadataUrl, proxySettings) { let metadata = this.openIdMetadataCache.get(metadataUrl); if (!metadata) { metadata = new openIdMetadata_1.OpenIdMetadata(metadataUrl, proxySettings); this.openIdMetadataCache.set(metadataUrl, metadata); } return metadata; } /** * Gets the claims identity associated with a request. * * @param authorizationHeader The raw HTTP header in the format: "Bearer [longString]". * @param channelId The Id of the channel being validated in the original request. * @param requiredEndorsements The required JWT endorsements. * @returns A `Promise` representation for either a [ClaimsIdentity](botframework-connector:module.ClaimsIdentity) or `null`. */ getIdentityFromAuthHeader(authorizationHeader, channelId, requiredEndorsements) { return __awaiter(this, void 0, void 0, function* () { if (!authorizationHeader) { return null; } const parts = authorizationHeader.split(' '); if (parts.length === 2) { return yield this.getIdentity(parts[0], parts[1], channelId, requiredEndorsements || []); } return null; }); } /** * Gets the claims identity associated with a request. * * @param scheme The associated scheme. * @param parameter The token. * @param channelId The Id of the channel being validated in the original request. * @param requiredEndorsements The required JWT endorsements. * @returns A `Promise` representation for either a [ClaimsIdentity](botframework-connector:module.ClaimsIdentity) or `null`. */ getIdentity(scheme, parameter, channelId, requiredEndorsements = []) { return __awaiter(this, void 0, void 0, function* () { // No header in correct scheme or no token if (scheme !== 'Bearer' || !parameter) { return null; } // Issuer isn't allowed? No need to check signature if (!this.hasAllowedIssuer(parameter)) { return null; } return yield this.validateToken(parameter, channelId, requiredEndorsements); }); } /** * @private */ hasAllowedIssuer(jwtToken) { const payload = (0, jsonwebtoken_1.decode)(jwtToken); let issuer; if (payload && typeof payload === 'object') { issuer = payload.iss; } else { return false; } if (Array.isArray(this.tokenValidationParameters.issuer)) { return this.tokenValidationParameters.issuer.indexOf(issuer) !== -1; } if (typeof this.tokenValidationParameters.issuer === 'string') { return this.tokenValidationParameters.issuer === issuer; } return false; } /** * @private */ validateToken(jwtToken, channelId, requiredEndorsements) { return __awaiter(this, void 0, void 0, function* () { let header = {}; const decodedToken = (0, jsonwebtoken_1.decode)(jwtToken, { complete: true }); if (decodedToken && typeof decodedToken === 'object') { header = decodedToken.header; } // Update the signing tokens from the last refresh const keyId = header.kid; const metadata = yield this.openIdMetadata.getKey(keyId); if (!metadata) { throw new authenticationError_1.AuthenticationError('Signing Key could not be retrieved.', botframework_schema_1.StatusCodes.UNAUTHORIZED); } try { let decodedPayload = {}; const verifyResults = (0, jsonwebtoken_1.verify)(jwtToken, metadata.key, this.tokenValidationParameters); if (verifyResults && typeof verifyResults === 'object') { // Note: casting is necessary here, but we know `object` is loosely equivalent to a Record decodedPayload = verifyResults; } // enforce endorsements in openIdMetadadata if there is any endorsements associated with the key const endorsements = metadata.endorsements; if (Array.isArray(endorsements) && endorsements.length !== 0) { const isEndorsed = endorsementsValidator_1.EndorsementsValidator.validate(channelId, endorsements); if (!isEndorsed) { throw new authenticationError_1.AuthenticationError(`Could not validate endorsement for key: ${keyId} with endorsements: ${endorsements.join(',')}`, botframework_schema_1.StatusCodes.UNAUTHORIZED); } // Verify that additional endorsements are satisfied. If no additional endorsements are expected, the requirement is satisfied as well const additionalEndorsementsSatisfied = requiredEndorsements.every((endorsement) => endorsementsValidator_1.EndorsementsValidator.validate(endorsement, endorsements)); if (!additionalEndorsementsSatisfied) { throw new authenticationError_1.AuthenticationError(`Could not validate additional endorsement for key: ${keyId} with endorsements: ${requiredEndorsements.join(',')}. Expected endorsements: ${requiredEndorsements.join(',')}`, botframework_schema_1.StatusCodes.UNAUTHORIZED); } } if (this.tokenValidationParameters.algorithms) { if (this.tokenValidationParameters.algorithms.indexOf(header.alg) === -1) { throw new authenticationError_1.AuthenticationError(`"Token signing algorithm '${header.alg}' not in allowed list`, botframework_schema_1.StatusCodes.UNAUTHORIZED); } } const claims = Object.entries(decodedPayload).map(([type, value]) => ({ type, value })); // Note: true is used here to indicate that these claims are to be considered authenticated. They are sourced // from a validated JWT (see `verify` above), so no harm in doing so. return new claimsIdentity_1.ClaimsIdentity(claims, true); } catch (err) { if (err.name === 'TokenExpiredError') { console.error(err); throw new authenticationError_1.AuthenticationError('The token has expired', botframework_schema_1.StatusCodes.UNAUTHORIZED); } console.error(`Error finding key for token. Available keys: ${metadata.key}`); throw err; } }); } } exports.JwtTokenExtractor = JwtTokenExtractor; // Cache for OpenIdConnect configuration managers (one per metadata URL) JwtTokenExtractor.openIdMetadataCache = new Map(); //# sourceMappingURL=jwtTokenExtractor.js.map