From a2bdf1031d053719e3923f9becdeb1a126d16f32 Mon Sep 17 00:00:00 2001 From: CeciliaAvila Date: Thu, 16 Feb 2023 11:57:11 -0300 Subject: [PATCH] Add expired token exception and tests --- .../botbuilder/tests/cloudAdapter.test.js | 63 ++++++++++++++++++- .../src/auth/jwtTokenExtractor.ts | 4 ++ .../tests/auth/jwtTokenExtractor.test.js | 24 +++++++ 3 files changed, 89 insertions(+), 2 deletions(-) diff --git a/libraries/botbuilder/tests/cloudAdapter.test.js b/libraries/botbuilder/tests/cloudAdapter.test.js index 893efcf663..bb6adc81b9 100644 --- a/libraries/botbuilder/tests/cloudAdapter.test.js +++ b/libraries/botbuilder/tests/cloudAdapter.test.js @@ -5,9 +5,18 @@ const assert = require('assert'); const httpMocks = require('node-mocks-http'); const net = require('net'); const sinon = require('sinon'); -const { BotFrameworkAuthenticationFactory } = require('botframework-connector'); +const { + AuthenticationConfiguration, + BotFrameworkAuthenticationFactory, + allowedCallersClaimsValidator, +} = require('botframework-connector'); +const { + ConfigurationServiceClientCredentialFactory, + createBotFrameworkAuthenticationFromConfiguration, +} = require('botbuilder'); const { CloudAdapter, ActivityTypes, INVOKE_RESPONSE_KEY } = require('..'); const { NamedPipeServer } = require('botframework-streaming'); +const { StatusCodes } = require('botframework-schema'); const FakeBuffer = () => Buffer.from([]); const FakeNodeSocket = () => new net.Socket(); @@ -16,7 +25,7 @@ const noop = () => null; describe('CloudAdapter', function () { let sandbox; beforeEach(function () { - sandbox = sinon.createSandbox({ useFakeTimers: true }); + sandbox = sinon.createSandbox({ useFakeTimers: false }); }); afterEach(function () { @@ -87,6 +96,56 @@ describe('CloudAdapter', function () { mock.verify(); }); + + it('throws exception on expired token', async function () { + // Expired token with removed AppID + const authorization = + 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IjJaUXBKM1VwYmpBWVhZR2FYRUpsOGxWMFRPSSIsImtpZCI6IjJaUXBKM1VwYmpBWVhZR2FYRUpsOGxWMFRPSSJ9.eyJhdWQiOiJodHRwczovL2FwaS5ib3RmcmFtZXdvcmsuY29tIiwiaXNzIjoiaHR0cHM6Ly9zdHMud2luZG93cy5uZXQvZDZkNDk0MjAtZjM5Yi00ZGY3LWExZGMtZDU5YTkzNTg3MWRiLyIsImlhdCI6MTY3MDM1MDQxNSwibmJmIjoxNjcwMzUwNDE1LCJleHAiOjE2NzA0MzcxMTUsImFpbyI6IkUyWmdZTkJONEpWZmxlOTJUc2wxYjhtOHBjOWpBQT09IiwiYXBwaWQiOiI5ZGRmM2QwZS02ZDRlLTQ2MWEtYjM4Yi0zMTYzZWQ3Yjg1NmIiLCJhcHBpZGFjciI6IjEiLCJpZHAiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9kNmQ0OTQyMC1mMzliLTRkZjctYTFkYy1kNTlhOTM1ODcxZGIvIiwicmgiOiIwLkFXNEFJSlRVMXB2ejkwMmgzTldhazFoeDIwSXpMWTBwejFsSmxYY09EcS05RnJ4dUFBQS4iLCJ0aWQiOiJkNmQ0OTQyMC1mMzliLTRkZjctYTFkYy1kNTlhOTM1ODcxZGIiLCJ1dGkiOiJIWDlncld2bU1rMlhESTRkS3BHSEFBIiwidmVyIjoiMS4wIn0.PBLuja5sCcDfFjweoy-VucvbfHEyEcs1GyqXjekzBqgvK-mSc1UrEfqr5834qY6dLNsXVIMJzMFuH6WyPbnAfIfRcabdiVSOAl8N8e9Tex6vHfPi4h4P2F96VkXU80EtZX4QMjsJMDJ5eXbJlIDEAxXoJbAdHqgy-lHcVBx8XK7toJ_W7vSsFhis3C4CPCHI1cf1WuHVwfFXBiNwsOzj9cnRUKpea6UELV89q4C0L6aeSNdWYXehZmgq-wlo2wIaGgQ7rOXx4MlIrc83LBzMMc6TWvBJecK6O8pJWLe6BTwOltBI8Tmo2hWnY1OnsbOhbSSlfwLaZqKI7QpA50_2GQ'; + + const activity = { type: ActivityTypes.Invoke, value: 'invoke' }; + + const req = httpMocks.createRequest({ + method: 'POST', + headers: { authorization }, + body: activity, + }); + + const res = httpMocks.createResponse(); + + const logic = async (context) => { + context.turnState.set(INVOKE_RESPONSE_KEY, { + type: ActivityTypes.InvokeResponse, + value: { + status: 200, + body: 'invokeResponse', + }, + }); + }; + + const validTokenIssuers = []; + const claimsValidators = allowedCallersClaimsValidator(['*']); + const authConfig = new AuthenticationConfiguration([], claimsValidators, validTokenIssuers); + const credentialsFactory = new ConfigurationServiceClientCredentialFactory({ + MicrosoftAppId: '', + MicrosoftAppPassword: '', + MicrosoftAppType: '', + MicrosoftAppTenantId: '', + }); + + const botFrameworkAuthentication = createBotFrameworkAuthenticationFromConfiguration( + null, + credentialsFactory, + authConfig, + undefined, + undefined + ); + + const adapter = new CloudAdapter(botFrameworkAuthentication); + + await adapter.process(req, res, logic); + + assert.equal(StatusCodes.UNAUTHORIZED, res.statusCode); + }); }); describe('connectNamedPipe', function () { diff --git a/libraries/botframework-connector/src/auth/jwtTokenExtractor.ts b/libraries/botframework-connector/src/auth/jwtTokenExtractor.ts index 0aea0117bd..fd2f1f71bf 100644 --- a/libraries/botframework-connector/src/auth/jwtTokenExtractor.ts +++ b/libraries/botframework-connector/src/auth/jwtTokenExtractor.ts @@ -196,6 +196,10 @@ export class JwtTokenExtractor { // from a validated JWT (see `verify` above), so no harm in doing so. return new ClaimsIdentity(claims, true); } catch (err) { + if (err.name === 'TokenExpiredError') { + console.error(err); + throw new AuthenticationError('The token has expired', StatusCodes.UNAUTHORIZED); + } console.error(`Error finding key for token. Available keys: ${metadata.key}`); throw err; } diff --git a/libraries/botframework-connector/tests/auth/jwtTokenExtractor.test.js b/libraries/botframework-connector/tests/auth/jwtTokenExtractor.test.js index d8ff38ef39..f1027ae85e 100644 --- a/libraries/botframework-connector/tests/auth/jwtTokenExtractor.test.js +++ b/libraries/botframework-connector/tests/auth/jwtTokenExtractor.test.js @@ -4,6 +4,7 @@ const assert = require('assert'); const { jwt } = require('botbuilder-test-utils'); const { JwtTokenExtractor } = require('../../lib/auth/jwtTokenExtractor'); +const { StatusCodes } = require('../../../botframework-schema'); describe('JwtTokenExtractor', function () { jwt.mocha(); @@ -37,4 +38,27 @@ describe('JwtTokenExtractor', function () { verify(); }); }); + + describe('validateToken', function () { + it('throws with expired token', async function () { + const { algorithm, issuer, metadata, sign } = jwt.stub(); + const key = 'value'; + const token = sign({ key }); + + const client = new JwtTokenExtractor( + { + issuer, + clockTimestamp: Date.now(), + clockTolerance: -10, + }, + metadata, + [algorithm] + ); + + await assert.rejects(client.getIdentityFromAuthHeader(`Bearer ${token}`), { + message: 'The token has expired', + statusCode: StatusCodes.UNAUTHORIZED, + }); + }); + }); });