11// Msteams plugin module implements oauth.token behavior.
22import { resolveExpiresAtMsFromDurationSeconds } from "openclaw/plugin-sdk/number-runtime" ;
33import { readProviderJsonResponse } from "openclaw/plugin-sdk/provider-http" ;
4- import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime" ;
4+ import { fetchWithSsrFGuard , type LookupFn } from "openclaw/plugin-sdk/ssrf-runtime" ;
55import { createMSTeamsHttpError } from "./http-error.js" ;
66import {
77 MSTEAMS_DEFAULT_DELEGATED_SCOPES ,
@@ -21,6 +21,15 @@ type MSTeamsTokenResponse = {
2121 scope ?: string ;
2222} ;
2323
24+ type MSTeamsTokenFetch = ( input : RequestInfo | URL , init ?: RequestInit ) => Promise < Response > ;
25+
26+ /** Optional test hooks so token fetch can exercise the real guarded-fetch owner. */
27+ type MSTeamsTokenFetchDeps = {
28+ fetchImpl ?: MSTeamsTokenFetch ;
29+ lookupFn ?: LookupFn ;
30+ timeoutMs ?: number ;
31+ } ;
32+
2433function createMSTeamsTokenBody ( params : {
2534 clientId : string ;
2635 clientSecret : string ;
@@ -74,7 +83,13 @@ async function fetchMSTeamsTokens(params: {
7483 body : URLSearchParams ;
7584 auditContext : string ;
7685 failureLabel : string ;
86+ fetchImpl ?: MSTeamsTokenFetch ;
87+ lookupFn ?: LookupFn ;
88+ timeoutMs ?: number ;
7789} ) : Promise < MSTeamsTokenResponse > {
90+ // Guard-owned timeoutMs covers DNS/proxy preflight; init.signal alone does not.
91+ // Sibling graph.ts already passes top-level timeoutMs into fetchWithSsrFGuard.
92+ const timeoutMs = params . timeoutMs ?? MSTEAMS_DEFAULT_TOKEN_FETCH_TIMEOUT_MS ;
7893 const { response, release } = await fetchWithSsrFGuard ( {
7994 url : params . tokenUrl ,
8095 init : {
@@ -84,9 +99,11 @@ async function fetchMSTeamsTokens(params: {
8499 Accept : "application/json" ,
85100 } ,
86101 body : params . body ,
87- signal : AbortSignal . timeout ( MSTEAMS_DEFAULT_TOKEN_FETCH_TIMEOUT_MS ) ,
88102 } ,
89103 auditContext : params . auditContext ,
104+ timeoutMs,
105+ ...( params . fetchImpl ? { fetchImpl : params . fetchImpl } : { } ) ,
106+ ...( params . lookupFn ? { lookupFn : params . lookupFn } : { } ) ,
90107 } ) ;
91108
92109 try {
@@ -113,6 +130,9 @@ async function requestMSTeamsDelegatedTokens(params: {
113130 auditContext : string ;
114131 failureLabel : string ;
115132 resolveRefreshToken : ( data : MSTeamsTokenResponse ) => string ;
133+ fetchImpl ?: MSTeamsTokenFetch ;
134+ lookupFn ?: LookupFn ;
135+ timeoutMs ?: number ;
116136} ) : Promise < MSTeamsDelegatedTokens > {
117137 const scopes = params . scopes ?? MSTEAMS_DEFAULT_DELEGATED_SCOPES ;
118138 const body = createMSTeamsTokenBody ( {
@@ -127,6 +147,9 @@ async function requestMSTeamsDelegatedTokens(params: {
127147 body,
128148 auditContext : params . auditContext ,
129149 failureLabel : params . failureLabel ,
150+ fetchImpl : params . fetchImpl ,
151+ lookupFn : params . lookupFn ,
152+ timeoutMs : params . timeoutMs ,
130153 } ) ;
131154
132155 return {
@@ -137,14 +160,16 @@ async function requestMSTeamsDelegatedTokens(params: {
137160 } ;
138161}
139162
140- export async function exchangeMSTeamsCodeForTokens ( params : {
141- tenantId : string ;
142- clientId : string ;
143- clientSecret : string ;
144- code : string ;
145- verifier : string ;
146- scopes ?: readonly string [ ] ;
147- } ) : Promise < MSTeamsDelegatedTokens > {
163+ export async function exchangeMSTeamsCodeForTokens (
164+ params : {
165+ tenantId : string ;
166+ clientId : string ;
167+ clientSecret : string ;
168+ code : string ;
169+ verifier : string ;
170+ scopes ?: readonly string [ ] ;
171+ } & MSTeamsTokenFetchDeps ,
172+ ) : Promise < MSTeamsDelegatedTokens > {
148173 return await requestMSTeamsDelegatedTokens ( {
149174 tenantId : params . tenantId ,
150175 clientId : params . clientId ,
@@ -164,16 +189,21 @@ export async function exchangeMSTeamsCodeForTokens(params: {
164189 }
165190 return data . refresh_token ;
166191 } ,
192+ fetchImpl : params . fetchImpl ,
193+ lookupFn : params . lookupFn ,
194+ timeoutMs : params . timeoutMs ,
167195 } ) ;
168196}
169197
170- export async function refreshMSTeamsDelegatedTokens ( params : {
171- tenantId : string ;
172- clientId : string ;
173- clientSecret : string ;
174- refreshToken : string ;
175- scopes ?: readonly string [ ] ;
176- } ) : Promise < MSTeamsDelegatedTokens > {
198+ export async function refreshMSTeamsDelegatedTokens (
199+ params : {
200+ tenantId : string ;
201+ clientId : string ;
202+ clientSecret : string ;
203+ refreshToken : string ;
204+ scopes ?: readonly string [ ] ;
205+ } & MSTeamsTokenFetchDeps ,
206+ ) : Promise < MSTeamsDelegatedTokens > {
177207 return await requestMSTeamsDelegatedTokens ( {
178208 tenantId : params . tenantId ,
179209 clientId : params . clientId ,
@@ -186,5 +216,8 @@ export async function refreshMSTeamsDelegatedTokens(params: {
186216 auditContext : "msteams-oauth-token-refresh" ,
187217 failureLabel : "token refresh" ,
188218 resolveRefreshToken : ( data ) => data . refresh_token ?? params . refreshToken ,
219+ fetchImpl : params . fetchImpl ,
220+ lookupFn : params . lookupFn ,
221+ timeoutMs : params . timeoutMs ,
189222 } ) ;
190223}
0 commit comments