@@ -135,19 +135,31 @@ const isSigninInvokeAuthorized = vi.hoisted(() => vi.fn(async () => true));
135135const isCardActionInvokeAuthorized = vi . hoisted ( ( ) => vi . fn ( async ( ) => true ) ) ;
136136const runMSTeamsFileConsentInvokeHandler = vi . hoisted ( ( ) => vi . fn ( async ( ) => { } ) ) ;
137137const loadMSTeamsSdkWithAuth = vi . hoisted ( ( ) =>
138- vi . fn ( async ( _creds ?: unknown , _options ?: unknown ) => ( {
139- app : {
140- on : vi . fn ( ) ,
141- event : vi . fn ( ) ,
142- onTokenExchange : vi . fn ( async ( ) => ( { status : 200 } ) ) ,
143- onVerifyState : vi . fn ( async ( ) => ( { status : 200 } ) ) ,
144- initialize : vi . fn ( async ( ) => { } ) ,
145- tokenManager : {
146- getBotToken : vi . fn ( async ( ) => ( { toString : ( ) : string => "bot-token" } ) ) ,
147- getGraphToken : vi . fn ( async ( ) => ( { toString : ( ) : string => "graph-token" } ) ) ,
138+ vi . fn ( async ( _creds ?: unknown , options ?: unknown ) => {
139+ const cloud = ( options as { cloud ?: string } | undefined ) ?. cloud ;
140+ const tokenServiceUrl =
141+ cloud === "USGov"
142+ ? "https://tokengcch.botframework.azure.us"
143+ : cloud === "USGovDoD"
144+ ? "https://apiDoD.botframework.azure.us"
145+ : cloud === "China"
146+ ? "https://token.botframework.azure.cn"
147+ : "https://token.botframework.com" ;
148+ return {
149+ app : {
150+ on : vi . fn ( ) ,
151+ event : vi . fn ( ) ,
152+ onTokenExchange : vi . fn ( async ( ) => ( { status : 200 } ) ) ,
153+ onVerifyState : vi . fn ( async ( ) => ( { status : 200 } ) ) ,
154+ initialize : vi . fn ( async ( ) => { } ) ,
155+ cloud : { tokenServiceUrl } ,
156+ tokenManager : {
157+ getBotToken : vi . fn ( async ( ) => ( { toString : ( ) : string => "bot-token" } ) ) ,
158+ getGraphToken : vi . fn ( async ( ) => ( { toString : ( ) : string => "graph-token" } ) ) ,
159+ } ,
148160 } ,
149- } ,
150- } ) ) ,
161+ } ;
162+ } ) ,
151163) ;
152164
153165const ssoTokenStore = vi . hoisted ( ( ) => ( {
@@ -291,6 +303,10 @@ function createJsonResponse(body: unknown, status = 200): globalThis.Response {
291303 } ) ;
292304}
293305
306+ function getFetchInputUrl ( input : string | URL | Request ) : string {
307+ return typeof input === "string" ? input : input instanceof URL ? input . toString ( ) : input . url ;
308+ }
309+
294310function createOversizedResponse ( ) : globalThis . Response {
295311 const chunk = new Uint8Array ( 64 * 1024 ) . fill ( 0x41 ) ;
296312 let sent = 0 ;
@@ -508,8 +524,7 @@ describe("monitorMSTeamsProvider lifecycle", () => {
508524 } ) ;
509525 const fetchMock = vi . fn (
510526 async ( input : string | URL | Request , init ?: RequestInit ) : Promise < globalThis . Response > => {
511- const url =
512- typeof input === "string" ? input : input instanceof URL ? input . toString ( ) : input . url ;
527+ const url = getFetchInputUrl ( input ) ;
513528 if ( url . includes ( "/api/usertoken/exchange" ) ) {
514529 expect ( init ?. method ) . toBe ( "POST" ) ;
515530 expect ( new Headers ( init ?. headers ) . get ( "Authorization" ) ) . toBe ( "Bearer mock-token" ) ;
@@ -632,6 +647,98 @@ describe("monitorMSTeamsProvider lifecycle", () => {
632647 await task ;
633648 } ) ;
634649
650+ it ( "preserves the SDK cloud User Token service URL for registered SSO routes" , async ( ) => {
651+ const abort = new AbortController ( ) ;
652+ const cfg = createConfig ( 0 ) ;
653+ updateMSTeamsConfig ( cfg , {
654+ cloud : "USGov" ,
655+ serviceUrl : "https://smba.infra.gov.teams.microsoft.us/teams" ,
656+ sso : { enabled : true , connectionName : "graph" } ,
657+ } ) ;
658+ const requestedUrls : string [ ] = [ ] ;
659+ const fetchMock = vi . fn ( async ( input : string | URL | Request ) : Promise < globalThis . Response > => {
660+ const url = getFetchInputUrl ( input ) ;
661+ requestedUrls . push ( url ) ;
662+ if ( url . includes ( "/api/usertoken/exchange" ) ) {
663+ return createJsonResponse ( {
664+ channelId : "msteams" ,
665+ connectionName : "graph" ,
666+ token : "gov-token-exchange" ,
667+ } ) ;
668+ }
669+ if ( url . includes ( "/api/usertoken/GetToken" ) ) {
670+ return createJsonResponse ( {
671+ channelId : "msteams" ,
672+ connectionName : "graph" ,
673+ token : "gov-token-state" ,
674+ } ) ;
675+ }
676+ throw new Error ( `unexpected fetch ${ url } ` ) ;
677+ } ) ;
678+ vi . stubGlobal ( "fetch" , fetchMock ) ;
679+
680+ const task = monitorMSTeamsProvider ( {
681+ cfg,
682+ runtime : createRuntime ( ) ,
683+ abortSignal : abort . signal ,
684+ conversationStore : createStores ( ) . conversationStore ,
685+ pollStore : createStores ( ) . pollStore ,
686+ } ) ;
687+
688+ await vi . waitFor ( ( ) => {
689+ expect ( registerMSTeamsHandlers ) . toHaveBeenCalled ( ) ;
690+ } ) ;
691+
692+ expect ( loadMSTeamsSdkWithAuth . mock . calls [ 0 ] ?. [ 1 ] ) . toMatchObject ( {
693+ cloud : "USGov" ,
694+ serviceUrl : "https://smba.infra.gov.teams.microsoft.us/teams" ,
695+ oauthDefaultConnectionName : "graph" ,
696+ } ) ;
697+
698+ const tokenExchangeHandler = await getRegisteredMSTeamsRoute ( "signin.token-exchange" ) ;
699+ await expect (
700+ tokenExchangeHandler ( {
701+ activity : {
702+ type : "invoke" ,
703+ name : "signin/tokenExchange" ,
704+ channelId : "msteams" ,
705+ from : { id : "29:user" } ,
706+ value : {
707+ id : "exchange-gov" ,
708+ connectionName : "graph" ,
709+ token : "exchangeable-token" ,
710+ } ,
711+ } ,
712+ } ) ,
713+ ) . resolves . toEqual ( { status : 200 } ) ;
714+
715+ const verifyStateHandler = await getRegisteredMSTeamsRoute ( "signin.verify-state" ) ;
716+ await expect (
717+ verifyStateHandler ( {
718+ activity : {
719+ type : "invoke" ,
720+ name : "signin/verifyState" ,
721+ channelId : "msteams" ,
722+ from : { id : "29:user" } ,
723+ value : { state : "654321" } ,
724+ } ,
725+ } ) ,
726+ ) . resolves . toEqual ( { status : 200 } ) ;
727+
728+ expect ( requestedUrls ) . toHaveLength ( 2 ) ;
729+ expect ( requestedUrls ) . toEqual ( [
730+ expect . stringMatching (
731+ / ^ h t t p s : \/ \/ t o k e n g c c h \. b o t f r a m e w o r k \. a z u r e \. u s \/ a p i \/ u s e r t o k e n \/ e x c h a n g e \? / ,
732+ ) ,
733+ expect . stringMatching (
734+ / ^ h t t p s : \/ \/ t o k e n g c c h \. b o t f r a m e w o r k \. a z u r e \. u s \/ a p i \/ u s e r t o k e n \/ G e t T o k e n \? / ,
735+ ) ,
736+ ] ) ;
737+
738+ abort . abort ( ) ;
739+ await task ;
740+ } ) ;
741+
635742 it ( "rejects oversized User Token responses through registered SSO routes" , async ( ) => {
636743 const abort = new AbortController ( ) ;
637744 const cfg = createConfig ( 0 ) ;
0 commit comments