@@ -365,6 +365,16 @@ describe("createBotFrameworkJwtValidator", () => {
365365 expect ( opts . audience ) . toContain ( "https://api.botframework.com" ) ;
366366 } ) ;
367367
368+ it ( "accepts tokens with documented serviceUrl claim casing" , async ( ) => {
369+ jwtState . decodedPayload = { iss : "https://api.botframework.com" } ;
370+ jwtState . verifyResult = {
371+ serviceUrl : activityServiceUrl ,
372+ } ;
373+
374+ const validator = await createBotFrameworkJwtValidator ( creds ) ;
375+ await expect ( validator . validate ( "Bearer botfw-token" , activityServiceUrl ) ) . resolves . toBe ( true ) ;
376+ } ) ;
377+
368378 it ( "accepts global audience tokens when azp matches the configured app id" , async ( ) => {
369379 jwtState . decodedPayload = { iss : "https://api.botframework.com" } ;
370380 jwtState . verifyResult = {
@@ -403,6 +413,18 @@ describe("createBotFrameworkJwtValidator", () => {
403413 await expect ( validator . validate ( "Bearer botfw-token" , activityServiceUrl ) ) . resolves . toBe ( false ) ;
404414 } ) ;
405415
416+ it ( "rejects schemeless activity serviceUrls even when the host matches the token claim" , async ( ) => {
417+ jwtState . decodedPayload = { iss : "https://api.botframework.com" } ;
418+ jwtState . verifyResult = {
419+ serviceurl : activityServiceUrl ,
420+ } ;
421+
422+ const validator = await createBotFrameworkJwtValidator ( creds ) ;
423+ await expect (
424+ validator . validate ( "Bearer botfw-token" , "smba.trafficmanager.net/amer/" ) ,
425+ ) . resolves . toBe ( false ) ;
426+ } ) ;
427+
406428 it ( "rejects tokens when the serviceurl claim is missing" , async ( ) => {
407429 jwtState . decodedPayload = { iss : "https://api.botframework.com" } ;
408430 jwtState . verifyResult = {
@@ -415,18 +437,69 @@ describe("createBotFrameworkJwtValidator", () => {
415437
416438 it ( "rejects tokens when the activity serviceUrl is missing" , async ( ) => {
417439 jwtState . decodedPayload = { iss : "https://api.botframework.com" } ;
440+ jwtState . verifyResult = {
441+ serviceurl : activityServiceUrl ,
442+ } ;
418443
419444 const validator = await createBotFrameworkJwtValidator ( creds ) ;
420445 await expect ( validator . validate ( "Bearer botfw-token" , undefined ) ) . resolves . toBe ( false ) ;
421446 } ) ;
422447
423- it ( "rejects serviceUrl values that are not Bot Framework base URLs " , async ( ) => {
448+ it ( "rejects tokens when the activity serviceUrl is malformed " , async ( ) => {
424449 jwtState . decodedPayload = { iss : "https://api.botframework.com" } ;
450+ jwtState . verifyResult = {
451+ serviceurl : activityServiceUrl ,
452+ } ;
425453
426454 const validator = await createBotFrameworkJwtValidator ( creds ) ;
427- await expect (
428- validator . validate ( "Bearer botfw-token" , `${ activityServiceUrl } ?target=attacker` ) ,
429- ) . resolves . toBe ( false ) ;
455+ await expect ( validator . validate ( "Bearer botfw-token" , "not a url" ) ) . resolves . toBe ( false ) ;
456+ } ) ;
457+
458+ it . each ( [
459+ "http://smba.trafficmanager.net/amer" ,
460+ "HTTP://smba.trafficmanager.net/amer" ,
461+ "wss://smba.trafficmanager.net/amer" ,
462+ "ftp://smba.trafficmanager.net/amer" ,
463+ ] ) ( "rejects non-HTTPS activity serviceUrl %s" , async ( serviceUrl ) => {
464+ jwtState . decodedPayload = { iss : "https://api.botframework.com" } ;
465+ jwtState . verifyResult = {
466+ serviceurl : serviceUrl ,
467+ } ;
468+
469+ const validator = await createBotFrameworkJwtValidator ( creds ) ;
470+ await expect ( validator . validate ( "Bearer botfw-token" , serviceUrl ) ) . resolves . toBe ( false ) ;
471+ } ) ;
472+
473+ it ( "rejects serviceUrl values with query strings" , async ( ) => {
474+ const queriedServiceUrl = `${ activityServiceUrl } ?target=attacker` ;
475+ jwtState . decodedPayload = { iss : "https://api.botframework.com" } ;
476+ jwtState . verifyResult = {
477+ serviceurl : queriedServiceUrl ,
478+ } ;
479+
480+ const validator = await createBotFrameworkJwtValidator ( creds ) ;
481+ await expect ( validator . validate ( "Bearer botfw-token" , queriedServiceUrl ) ) . resolves . toBe ( false ) ;
482+ } ) ;
483+
484+ it ( "rejects serviceUrl values with fragments" , async ( ) => {
485+ const fragmentServiceUrl = `${ activityServiceUrl } #fragment` ;
486+ jwtState . decodedPayload = { iss : "https://api.botframework.com" } ;
487+ jwtState . verifyResult = {
488+ serviceurl : fragmentServiceUrl ,
489+ } ;
490+
491+ const validator = await createBotFrameworkJwtValidator ( creds ) ;
492+ await expect ( validator . validate ( "Bearer botfw-token" , fragmentServiceUrl ) ) . resolves . toBe ( false ) ;
493+ } ) ;
494+
495+ it ( "rejects tokens when the serviceurl claim is not a string" , async ( ) => {
496+ jwtState . decodedPayload = { iss : "https://api.botframework.com" } ;
497+ jwtState . verifyResult = {
498+ serviceurl : 123 ,
499+ } ;
500+
501+ const validator = await createBotFrameworkJwtValidator ( creds ) ;
502+ await expect ( validator . validate ( "Bearer botfw-token" , activityServiceUrl ) ) . resolves . toBe ( false ) ;
430503 } ) ;
431504
432505 it ( "rejects non-object verified payloads" , async ( ) => {
0 commit comments